# HG changeset patch # User Mike Becker # Date 1751103128 -7200 # Node ID 586dcd606e47bc674997fa85dc0b1642ce033550 # Parent 1c80ba4a0d6259ef290e842d0e9e794e33d13f66 add popups with commit summaries - resolves #644 diff -r 1c80ba4a0d62 -r 586dcd606e47 src/Makefile --- a/src/Makefile Fri Jun 20 17:15:18 2025 +0200 +++ b/src/Makefile Sat Jun 28 11:32:08 2025 +0200 @@ -40,16 +40,16 @@ FORCE: -../build/heatmap.o: heatmap.cpp heatmap.h settings.h +../build/heatmap.o: heatmap.cpp heatmap.h settings.h commit-data.h @echo "Compiling $<" $(CXX) -o $@ $(CXXFLAGS) -c $< -../build/html.o: html.cpp html.h +../build/html.o: html.cpp html.h commit-data.h @echo "Compiling $<" $(CXX) -o $@ $(CXXFLAGS) -c $< ../build/main.o: main.cpp settings.h repositories.h process.h heatmap.h \ - html.h + commit-data.h html.h @echo "Compiling $<" $(CXX) -o $@ $(CXXFLAGS) -c $< diff -r 1c80ba4a0d62 -r 586dcd606e47 src/commit-data.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/commit-data.h Sat Jun 28 11:32:08 2025 +0200 @@ -0,0 +1,35 @@ +/* Copyright 2025 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef COMMIT_DATA_H +#define COMMIT_DATA_H + +namespace fm { + struct commits final { + std::vector summaries; + [[nodiscard]] unsigned count() const {return summaries.size();} + }; +} + +#endif //COMMIT_DATA_H diff -r 1c80ba4a0d62 -r 586dcd606e47 src/heatmap.cpp --- a/src/heatmap.cpp Fri Jun 20 17:15:18 2025 +0200 +++ b/src/heatmap.cpp Sat Jun 28 11:32:08 2025 +0200 @@ -48,9 +48,11 @@ std::from_chars(date_parts[0].begin(), date_parts[0].end(), year); std::from_chars(date_parts[1].begin(), date_parts[1].end(), month); std::from_chars(date_parts[2].begin(), date_parts[2].end(), day); - m_heatmap[m_current_repo][author][chrono::year_month_day{ + auto &[summaries] = m_heatmap[m_current_repo][author][chrono::year_month_day{ chrono::year{year}, chrono::month{month}, chrono::day{day} - }]++; + }]; + ++parts_iter; + summaries.emplace_back(std::string_view{*parts_iter}); } } @@ -62,7 +64,7 @@ std::array result{}; for (auto&& [ymd, commits] : m_heatmap.at(repo).at(author)) { if (ymd.year() != year) continue; - result[static_cast(ymd.month())-1] += commits; + result[static_cast(ymd.month())-1] += commits.count(); } return result; } diff -r 1c80ba4a0d62 -r 586dcd606e47 src/heatmap.h --- a/src/heatmap.h Fri Jun 20 17:15:18 2025 +0200 +++ b/src/heatmap.h Sat Jun 28 11:32:08 2025 +0200 @@ -31,6 +31,7 @@ #include #include "settings.h" +#include "commit-data.h" namespace fm { @@ -42,7 +43,7 @@ std::string, // author std::map< std::chrono::year_month_day, // date - unsigned int // commits + commits >>> m_heatmap; std::string m_current_repo = "All Repositories"; public: diff -r 1c80ba4a0d62 -r 586dcd606e47 src/html.cpp --- a/src/html.cpp Fri Jun 20 17:15:18 2025 +0200 +++ b/src/html.cpp Sat Jun 28 11:32:08 2025 +0200 @@ -24,6 +24,7 @@ #include "html.h" +#include #include #include @@ -36,7 +37,6 @@ static const char *tabs = " "; static void indent(int change = 0) { indentation += change; - assert(indentation >= 0); assert(indentation <= max_indentation); fwrite(tabs, 4, indentation, stdout); } @@ -61,6 +61,9 @@ case '>': buffer.append(">"); break; + case '#': + buffer.append("#"); + break; default: buffer.append(&pos, 1); break; @@ -79,22 +82,30 @@ puts(R"( + +
)"); @@ -217,28 +326,58 @@ puts(""); } -void html::cell(year_month_day ymd, unsigned commits) { +void html::cell(year_month_day ymd, const fm::commits &commits) { const char *color_class; - if (commits == 0) { + if (commits.count() == 0) { color_class = "zero-commits"; - } else if (commits == 1) { + } else if (commits.count() == 1) { color_class = "one-commit"; - } else if (commits <= 5) { + } else if (commits.count() <= 5) { color_class = "up-to-5-commits"; - } else if (commits <= 10) { + } else if (commits.count() <= 10) { color_class = "up-to-10-commits"; - } else if (commits <= 20) { + } else if (commits.count() <= 20) { color_class = "up-to-20-commits"; } else { color_class = "commit-spam"; } - indent(); - printf("\n", - color_class, + + + // Format date for display + char date_str[32]; + sprintf(date_str, "%s, %d-%02u-%02u", weekdays[weekday(ymd).iso_encoding() - 1], static_cast(ymd.year()), static_cast(ymd.month()), - static_cast(ymd.day()), - commits, - commits == 1 ? "commit" : "commits"); + static_cast(ymd.day())); + + // Build a JSON array of commit summaries + // We have to iterate in reverse order to sort the summaries chronologically + std::string summaries_json = "["; + for (const auto &summary : commits.summaries | std::views::reverse) { + // Escape quotes in JSON + size_t pos = summary.find('\"'); + if (pos == std::string::npos) { + summaries_json += "\"" + summary + "\","; + } else { + std::string escaped = summary; + do { + escaped.replace(pos, 1, "\\\""); + pos += 2; + } while ((pos = escaped.find('\"', pos)) != std::string::npos); + summaries_json += "\"" + escaped + "\","; + } + } + summaries_json.pop_back(); + summaries_json += "]"; + + indent(); + printf("\n", + color_class, + date_str, + commits.count(), + commits.count() == 1 ? "commit" : "commits", + date_str, + encode(summaries_json).c_str() + ); } diff -r 1c80ba4a0d62 -r 586dcd606e47 src/html.h --- a/src/html.h Fri Jun 20 17:15:18 2025 +0200 +++ b/src/html.h Sat Jun 28 11:32:08 2025 +0200 @@ -29,6 +29,8 @@ #include #include +#include "commit-data.h" + namespace html { static constexpr unsigned max_indentation = 16; @@ -47,7 +49,7 @@ void row_begin(unsigned int row); void row_end(); void cell_out_of_range(); - void cell(std::chrono::year_month_day ymd, unsigned commits); + void cell(std::chrono::year_month_day ymd, const fm::commits &commits = {}); } diff -r 1c80ba4a0d62 -r 586dcd606e47 src/main.cpp --- a/src/main.cpp Fri Jun 20 17:15:18 2025 +0200 +++ b/src/main.cpp Sat Jun 28 11:32:08 2025 +0200 @@ -262,7 +262,7 @@ proc.setbin(settings.hg); if (proc.exec_log({"log", "--date", std::format("{0}-01-01 00:00:00 to {0}-12-31 23:59:59", report_year), - "--template", "{author}#{date|shortdate}\n"})) { + "--template", "{author}#{date|shortdate}#{desc|firstline}\n"})) { fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); return EXIT_FAILURE; } @@ -272,7 +272,7 @@ if (proc.exec_log({"log", "--since", std::format("{0}-01-01 00:00:00", report_year), "--until", std::format("{0}-12-31 23:59:59", report_year), - "--format=tformat:%an <%ae>#%cs"})) { + "--format=tformat:%an <%ae>#%cs#%s"})) { fprintf(stderr, "Reading commit log for repo '%s' failed!\n", repo.path.c_str()); return EXIT_FAILURE; } @@ -323,7 +323,7 @@ // get the entry from the heatmap auto find_result = entries.find(day_to_check); if (find_result == entries.end()) { - html::cell(day_to_check, 0); + html::cell(day_to_check); } else { html::cell(day_to_check, find_result->second); }