Mon, 19 May 2025 16:05:58 +0200
add custom fragment indentation
0 | 1 | /* Copyright 2025 Mike Becker. All rights reserved. |
2 | * | |
3 | * Redistribution and use in source and binary forms, with or without | |
4 | * modification, are permitted provided that the following conditions are met: | |
5 | * | |
6 | * 1. Redistributions of source code must retain the above copyright | |
7 | * notice, this list of conditions and the following disclaimer. | |
8 | * | |
9 | * 2. Redistributions in binary form must reproduce the above copyright | |
10 | * notice, this list of conditions and the following disclaimer in the | |
11 | * documentation and/or other materials provided with the distribution. | |
12 | * | |
13 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
16 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
20 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
21 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
22 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | */ | |
24 | ||
1 | 25 | #include "settings.h" |
3 | 26 | #include "repositories.h" |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
27 | #include "process.h" |
5 | 28 | #include "heatmap.h" |
29 | #include "html.h" | |
1 | 30 | |
5 | 31 | #include <chrono> |
1 | 32 | #include <cstdlib> |
33 | #include <cstdio> | |
34 | #include <cstring> | |
35 | #include <cerrno> | |
36 | ||
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
37 | #include <numeric> |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
38 | |
13 | 39 | using namespace std::chrono; |
5 | 40 | |
41
19cc90878968
fix wrong escape in raw string
Mike Becker <universe@uap-core.de>
parents:
39
diff
changeset
|
41 | static constexpr auto program_version = "1.1.0 (dev)"; |
29 | 42 | |
1 | 43 | static void print_help() { |
44 | fputs( | |
23
b4979c194dc8
replace working title with official project title
Mike Becker <universe@uap-core.de>
parents:
21
diff
changeset
|
45 | "Usage: repoheat [OPTION]... [PATH]...\n\n" |
1 | 46 | "Options:\n" |
14 | 47 | " -a, --author <name> Only report this author\n" |
48 | " (repeat option to report multiple authors)\n" | |
49 | " -A, --authormap <file> Apply an author mapping file\n" | |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
50 | " -d, --depth <num> The search depth (default: 1, max: 255)\n" |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
51 | " -f, --fragment [indent] Output as fragment\n" |
14 | 52 | " -h, --help Print this help message\n" |
27
d2eee642a31b
do not pull repos by default - fixes #587
Mike Becker <universe@uap-core.de>
parents:
24
diff
changeset
|
53 | " -p, --pull Try to pull the repositories\n" |
14 | 54 | " -s, --separate Output a separate heat map for each repository\n" |
29 | 55 | " -V, --version Output the version of this program and exit\n" |
14 | 56 | " -y, --year <year> The year for which to create the heat map\n" |
57 | " --hg <path> Path to hg binary (default: /usr/bin/hg)\n" | |
58 | " --git <path> Path to git binary (default: /usr/bin/git)\n\n" | |
1 | 59 | "Scans all specified paths recursively for Mercurial and Git repositories and\n" |
60 | "creates a commit heat map for the specified \033[1myear\033[22m or the current year.\n" | |
61 | "By default, the recursion \033[1mdepth\033[22m is one, meaning that this tool assumes that\n" | |
62 | "each \033[1mpath\033[22m is either a repository or contains repositories as subdirectories.\n" | |
63 | "You can change the \033[1mdepth\033[22m to support other directory structures.\n\n" | |
27
d2eee642a31b
do not pull repos by default - fixes #587
Mike Becker <universe@uap-core.de>
parents:
24
diff
changeset
|
64 | "When you specify the \033[1m--pull\033[22m option, this tool will execute the pull command\n" |
1 | 65 | "(and for hg the update command) before retrieving the commit log, assuming\n" |
9
98312f94dbdd
skip authorization requests when pulling
Mike Becker <universe@uap-core.de>
parents:
8
diff
changeset
|
66 | "to be on the default branch with \033[4mno uncommitted changes\033[24m. If pulling leads to\n" |
98312f94dbdd
skip authorization requests when pulling
Mike Becker <universe@uap-core.de>
parents:
8
diff
changeset
|
67 | "an error, an error message is written to stderr and the process continues\n" |
98312f94dbdd
skip authorization requests when pulling
Mike Becker <universe@uap-core.de>
parents:
8
diff
changeset
|
68 | "with the repository in its current state. This is also the case when pulling\n" |
27
d2eee642a31b
do not pull repos by default - fixes #587
Mike Becker <universe@uap-core.de>
parents:
24
diff
changeset
|
69 | "would require authorization.\n\n" |
14 | 70 | "By default, this tool reports commits from all authors. If you want to include\n" |
71 | "only specific authors in the report, you can use the \033[1m--author\033[22m option with as\n" | |
72 | "many authors as you like. You can specify either the full author strings with\n" | |
73 | "name and mail address, just the mail address, or even just the local-part of\n" | |
74 | "the mail address. In case your repository contains commits from an author who\n" | |
75 | "used different names or mail addresses, you can use the \033[1m--authormap\033[22m option\n" | |
76 | "to specify a file that contains pairs of author strings, like in the following\n" | |
77 | "example:\n\n" | |
78 | " Full Name <full.name@example.org> = New Name <new.name@example.org>\n" | |
16 | 79 | " just.mail@example.org = Jus Mail <just.mail@example.org>\n" |
80 | " jane = Jane Doe <jane.doe@example.org>\n\n" | |
14 | 81 | "The different variants of full string, only mail address, and only local-part\n" |
16 | 82 | "should \033[4monly\033[24m be used on the left-hand side. When you use the \033[1m--author\033[22m option at\n" |
83 | "the same time, you only need to specify the new author names.\n\n" | |
14 | 84 | "Finally, this tool prints an HTML page to stdout. A separate heap map is\n" |
3 | 85 | "generated for each author showing commits across all repositories, unless the\n" |
86 | "\033[1m--separate\033[22m option is specified in which case each repository is displayed with\n" | |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
87 | "its own heat map. By using the \033[1m--fragment\033[22m option, the tool only outputs a\n" |
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
88 | "single HTML div container without any header or footer that can be embedded in\n" |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
89 | "your custom web page. You can optionally specify an indentation for that\n" |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
90 | "container (default is 0 and maximum is 12).\n" |
1 | 91 | , stderr); |
92 | } | |
93 | ||
3 | 94 | static bool chk_arg(const char *arg, const char *opt1, const char *opt2) { |
1 | 95 | return strcmp(arg, opt1) == 0 || (opt2 != nullptr && strcmp(arg, opt2) == 0); |
96 | } | |
97 | ||
98 | template<typename T> | |
99 | static bool parse_unsigned(const char *str, T *result, unsigned long max) { | |
100 | char *endptr; | |
101 | errno = 0; | |
102 | unsigned long d = strtoul(str, &endptr, 10); | |
103 | if (*endptr != '\0' || errno == ERANGE) return true; | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
104 | if (d <= max) { |
1 | 105 | *result = d; |
106 | return false; | |
107 | } else { | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
108 | errno = ERANGE; |
1 | 109 | return true; |
110 | } | |
111 | } | |
112 | ||
113 | static int parse_args(fm::settings &settings, int argc, char *argv[]) { | |
114 | for (int i = 1; i < argc; i++) { | |
115 | if (chk_arg(argv[i], "-h", "--help")) { | |
116 | print_help(); | |
29 | 117 | return 1; |
1 | 118 | } else if (chk_arg(argv[i], "-d", "--depth")) { |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
119 | if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.depth, 255)) { |
1 | 120 | fputs("missing or invalid depth\n", stderr); |
121 | return -1; | |
122 | } | |
123 | } else if (chk_arg(argv[i], "-y", "--year")) { | |
124 | if (i + 1 >= argc || parse_unsigned(argv[++i], &settings.year, 9999)) { | |
125 | fputs("missing or invalid year\n", stderr); | |
126 | return -1; | |
127 | } | |
14 | 128 | } else if (chk_arg(argv[i], "-a", "--author")) { |
129 | if (i + 1 < argc) { | |
130 | settings.authors.emplace_back(argv[++i]); | |
131 | } else { | |
132 | fputs("missing author name\n", stderr); | |
133 | return -1; | |
134 | } | |
27
d2eee642a31b
do not pull repos by default - fixes #587
Mike Becker <universe@uap-core.de>
parents:
24
diff
changeset
|
135 | } else if (chk_arg(argv[i], "-p", "--pull")) { |
d2eee642a31b
do not pull repos by default - fixes #587
Mike Becker <universe@uap-core.de>
parents:
24
diff
changeset
|
136 | settings.update_repos = true; |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
137 | } else if (chk_arg(argv[i], "-f", "--fragment")) { |
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
138 | settings.fragment = true; |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
139 | if (i + 1 < argc && !parse_unsigned(argv[i+1], &settings.fragment_indent, html::max_external_indentation)) { |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
140 | i++; |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
141 | } else if (errno == ERANGE) { |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
142 | fputs("invalid fragment indentation\n", stderr); |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
143 | return -1; |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
144 | } |
1 | 145 | } else if (chk_arg(argv[i], "-s", "--separate")) { |
146 | settings.separate = true; | |
16 | 147 | } else if (chk_arg(argv[i], "-A", "--authormap")) { |
148 | if (i + 1 < argc) { | |
149 | if (settings.parse_authormap(argv[++i])) { | |
150 | fputs("parsing authormap failed\n", stderr); | |
151 | return -1; | |
152 | } | |
153 | } else { | |
154 | fputs("missing filename for authormap\n", stderr); | |
155 | return -1; | |
156 | } | |
29 | 157 | } else if (chk_arg(argv[i], "-V", "--version")) { |
158 | printf("repoheat version %s\n", program_version); | |
159 | return 1; | |
1 | 160 | } else if (chk_arg(argv[i], "--hg", nullptr)) { |
161 | if (i + 1 < argc) { | |
162 | settings.hg.assign(argv[++i]); | |
163 | } else { | |
164 | fputs("--hg is expecting a path\n", stderr); | |
165 | return -1; | |
166 | } | |
167 | } else if (chk_arg(argv[i], "--git", nullptr)) { | |
168 | if (i + 1 < argc) { | |
169 | settings.git.assign(argv[++i]); | |
170 | } else { | |
171 | fputs("--git is expecting a path\n", stderr); | |
172 | return -1; | |
173 | } | |
174 | } else if (argv[i][0] == '-') { | |
175 | fprintf(stderr, "Unknown option: %s\n", argv[i]); | |
176 | return -1; | |
177 | } else { | |
178 | settings.paths.emplace_back(argv[i]); | |
179 | } | |
180 | } | |
181 | ||
182 | if (settings.paths.empty()) { | |
183 | settings.paths.emplace_back("./"); | |
184 | } | |
185 | ||
0 | 186 | return 0; |
187 | } | |
188 | ||
1 | 189 | int main(int argc, char *argv[]) { |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
190 | // parse settings |
1 | 191 | fm::settings settings; |
29 | 192 | if (int result = parse_args(settings, argc, argv); result != 0) { |
193 | return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
1 | 194 | } |
195 | ||
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
196 | // check hg and git |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
197 | fm::process proc; |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
198 | proc.setbin(settings.hg); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
199 | if (proc.exec({"--version"})) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
200 | fprintf(stderr, "Testing hg binary '%s' failed!\n", settings.hg.c_str()); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
201 | return EXIT_FAILURE; |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
202 | } |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
203 | proc.setbin(settings.git); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
204 | if (proc.exec({"--version"})) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
205 | fprintf(stderr, "Testing git binary '%s' failed!\n", settings.git.c_str()); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
206 | return EXIT_FAILURE; |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
207 | } |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
208 | |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
209 | // scan for repos |
3 | 210 | fm::repositories repos; |
211 | for (auto &&path: settings.paths) { | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
212 | if (!fm::repositories::exists(path)) { |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
213 | fprintf(stderr, "Path '%s' does not exist!\n", path.c_str()); |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
214 | return EXIT_FAILURE; |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
215 | } |
3 | 216 | repos.scan(path, settings.depth); |
217 | } | |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
218 | |
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
219 | // check if we found something |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
220 | if (repos.count() == 0) { |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
221 | fprintf(stderr, "No repositories found!\n"); |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
222 | return EXIT_FAILURE; |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
223 | } |
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
224 | |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
225 | // update repos, if not disabled |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
226 | if (settings.update_repos) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
227 | for (auto &&repo : repos.list()) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
228 | proc.chdir(repo.path); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
229 | if (repo.type == fm::HG) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
230 | proc.setbin(settings.hg); |
9
98312f94dbdd
skip authorization requests when pulling
Mike Becker <universe@uap-core.de>
parents:
8
diff
changeset
|
231 | if (proc.exec({"pull", "-y"})) { |
21
bc8b76ca9ee9
fix possible misleading error output
Mike Becker <universe@uap-core.de>
parents:
20
diff
changeset
|
232 | fprintf(stderr, "Pulling repo '%s' failed - continue without pull.\n", repo.path.c_str()); |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
233 | } else if (proc.exec({"update"})) { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
234 | fprintf(stderr, "Updating repo '%s' failed!\nMaybe there are local changes?\n", repo.path.c_str()); |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
235 | } |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
236 | } else { |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
237 | proc.setbin(settings.git); |
9
98312f94dbdd
skip authorization requests when pulling
Mike Becker <universe@uap-core.de>
parents:
8
diff
changeset
|
238 | if (proc.exec({"pull", "-q"})) { |
21
bc8b76ca9ee9
fix possible misleading error output
Mike Becker <universe@uap-core.de>
parents:
20
diff
changeset
|
239 | fprintf(stderr, "Pulling repo '%s' failed - continue without pull.\n", repo.path.c_str()); |
4
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
240 | } |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
241 | } |
82680ce258d6
add automatic pull/udate of repositories
Mike Becker <universe@uap-core.de>
parents:
3
diff
changeset
|
242 | } |
3 | 243 | } |
5 | 244 | |
245 | // determine our reporting range | |
13 | 246 | year report_year{ |
247 | settings.year == fm::settings_current_year | |
248 | ? year_month_day{floor<days>(system_clock::now())}.year() | |
249 | : year{settings.year} | |
250 | }; | |
251 | year_month_day report_begin{report_year, January, 1d}; | |
252 | year_month_day report_end{report_year, December, 31d}; | |
5 | 253 | |
254 | // read the commit logs | |
255 | fm::heatmap heatmap; | |
256 | for (auto &&repo : repos.list()) { | |
257 | if (settings.separate) { | |
37
d7e9a1200e21
improve headings in separate repository view
Mike Becker <universe@uap-core.de>
parents:
36
diff
changeset
|
258 | heatmap.set_repo(repo.name); |
5 | 259 | } |
260 | proc.chdir(repo.path); | |
261 | if (repo.type == fm::HG) { | |
262 | proc.setbin(settings.hg); | |
263 | if (proc.exec_log({"log", | |
36 | 264 | "--date", std::format("{0}-01-01 00:00:00 to {0}-12-31 23:59:59", report_year), |
5 | 265 | "--template", "{author}#{date|shortdate}\n"})) { |
266 | fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); | |
267 | return EXIT_FAILURE; | |
268 | } | |
16 | 269 | heatmap.add(settings, proc.output()); |
5 | 270 | } else { |
271 | proc.setbin(settings.git); | |
8
6a2e20a4a8ff
fix log not being captured from git
Mike Becker <universe@uap-core.de>
parents:
7
diff
changeset
|
272 | if (proc.exec_log({"log", |
36 | 273 | "--since", std::format("{0}-01-01 00:00:00", report_year), |
274 | "--until", std::format("{0}-12-31 23:59:59", report_year), | |
5 | 275 | "--format=tformat:%an <%ae>#%cs"})) { |
276 | fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); | |
277 | return EXIT_FAILURE; | |
278 | } | |
16 | 279 | heatmap.add(settings, proc.output()); |
5 | 280 | } |
281 | } | |
282 | ||
52
e9edc3bd0301
add custom fragment indentation
Mike Becker <universe@uap-core.de>
parents:
46
diff
changeset
|
283 | html::open(settings.fragment, settings.fragment_indent); |
5 | 284 | for (const auto &[repo, authors] : heatmap.data()) { |
24
2a4e97fc5ce5
fix repo h1 generated even when no author has commits - fixes #585
Mike Becker <universe@uap-core.de>
parents:
23
diff
changeset
|
285 | bool h1_rendered = false; |
5 | 286 | for (const auto &[author, entries] : authors) { |
14 | 287 | if (settings.exclude_author(author)) continue; |
24
2a4e97fc5ce5
fix repo h1 generated even when no author has commits - fixes #585
Mike Becker <universe@uap-core.de>
parents:
23
diff
changeset
|
288 | if (!h1_rendered) { |
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
289 | html::heading_repo(repo); |
24
2a4e97fc5ce5
fix repo h1 generated even when no author has commits - fixes #585
Mike Becker <universe@uap-core.de>
parents:
23
diff
changeset
|
290 | h1_rendered = true; |
2a4e97fc5ce5
fix repo h1 generated even when no author has commits - fixes #585
Mike Becker <universe@uap-core.de>
parents:
23
diff
changeset
|
291 | } |
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
292 | html::chart_begin(repo, author); |
44
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
293 | |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
294 | const auto commits_per_month = heatmap.commits_per_month(repo, author, report_year); |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
295 | const auto total_commits = std::accumulate(commits_per_month.begin(), commits_per_month.end(), 0u); |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
296 | html::heading_author(author, total_commits); |
de22ded6d50a
add total commits counters
Mike Becker <universe@uap-core.de>
parents:
41
diff
changeset
|
297 | html::table_begin(report_year, commits_per_month); |
5 | 298 | |
299 | // initialize counters | |
300 | unsigned column = 0, row = 0; | |
3 | 301 | |
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
302 | // initialize the first day (which must be a Monday, possibly the year before) |
13 | 303 | sys_days day_to_check = January / Monday[1] / report_year; |
19
2c128952f198
fix accidentally breaking the start of year
Mike Becker <universe@uap-core.de>
parents:
16
diff
changeset
|
304 | if (year_month_day{day_to_check}.day() != 1d) { |
2c128952f198
fix accidentally breaking the start of year
Mike Becker <universe@uap-core.de>
parents:
16
diff
changeset
|
305 | day_to_check -= days{7}; |
2c128952f198
fix accidentally breaking the start of year
Mike Becker <universe@uap-core.de>
parents:
16
diff
changeset
|
306 | } |
5 | 307 | |
308 | // remember the starting point | |
309 | auto start = day_to_check; | |
310 | ||
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
311 | // now add all entries for Monday, Tuesdays, etc. always starting back in January |
5 | 312 | while (true) { |
313 | html::row_begin(row); | |
314 | ||
315 | // check if we need to add blank cells | |
316 | while (day_to_check < report_begin) { | |
317 | html::cell_out_of_range(); | |
13 | 318 | day_to_check += days{7}; |
5 | 319 | column++; |
320 | } | |
321 | ||
322 | while (day_to_check <= report_end) { | |
323 | // get the entry from the heatmap | |
324 | auto find_result = entries.find(day_to_check); | |
325 | if (find_result == entries.end()) { | |
7 | 326 | html::cell(day_to_check, 0); |
5 | 327 | } else { |
7 | 328 | html::cell(day_to_check, find_result->second); |
5 | 329 | } |
330 | // advance seven days and one column | |
13 | 331 | day_to_check += days{7}; |
5 | 332 | column++; |
333 | } | |
334 | // fill remaining columns with blank cells | |
335 | for (unsigned i = column ; i < html::columns ; i++) { | |
336 | html::cell_out_of_range(); | |
337 | } | |
338 | ||
339 | // terminate the row | |
340 | html::row_end(); | |
341 | ||
342 | // if we have seen all seven weekdays, that's it | |
343 | if (++row == 7) break; | |
344 | ||
345 | // otherwise, advance the starting point by one day, reset, and begin a new row | |
13 | 346 | start += days{1}; |
5 | 347 | day_to_check = start; |
348 | column =0; | |
349 | } | |
350 | ||
351 | html::table_end(); | |
46
7e099403e5b0
make charts identifiable with a query - fixes #608
Mike Becker <universe@uap-core.de>
parents:
44
diff
changeset
|
352 | html::chart_end(); |
5 | 353 | } |
354 | } | |
20
8639ccd855ba
implement --fragment option
Mike Becker <universe@uap-core.de>
parents:
19
diff
changeset
|
355 | html::close(settings.fragment); |
3 | 356 | |
1 | 357 | return EXIT_SUCCESS; |
358 | } |