diff options
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Give every summary section the same columns
The ref rows always spanned five columns and the log three to five, so with the default config the log and clone rows stopped 160px short of the table's edge, and with any config the log rows did on a phone, where they alone lost their counts. A column no cell began in also made the summary and refs pages invalid HTML.
-rw-r--r--assets/cgit.css4
-rw-r--r--source/cmd.c2
-rw-r--r--source/ui-log.c27
-rw-r--r--source/ui-log.h8
-rw-r--r--source/ui-refs.c64
-rw-r--r--source/ui-refs.h10
-rw-r--r--source/ui-summary.c25
-rwxr-xr-xtests/t0102-summary.sh77
8 files changed, 172 insertions, 45 deletions
diff --git a/assets/cgit.css b/assets/cgit.css
index 954fafa..d5218ca 100644
--- a/assets/cgit.css
+++ b/assets/cgit.css
@@ -1372,8 +1372,8 @@ div#cgit table.ssdiff td.space {
display: none;
}
- div#cgit table.list th.col-count,
- div#cgit table.list td.col-count {
+ div#cgit table.log th.col-count,
+ div#cgit table.log td.col-count {
display: none;
}
diff --git a/source/cmd.c b/source/cmd.c
index 08de123..8f89e3b 100644
--- a/source/cmd.c
+++ b/source/cmd.c
@@ -96,7 +96,7 @@ static void log_fn(void)
cgit_print_log(
ctx.qry.oid, ctx.qry.ofs, ctx.cfg.max_commit_count, ctx.qry.grep,
ctx.qry.search, ctx.qry.path, 1, ctx.repo->enable_commit_graph,
- ctx.repo->commit_sort
+ ctx.repo->commit_sort, 0
);
}
diff --git a/source/ui-log.c b/source/ui-log.c
index 98efcf6..2812246 100644
--- a/source/ui-log.c
+++ b/source/ui-log.c
@@ -212,7 +212,15 @@ static void print_graph_padding(struct rev_info *revs, struct strbuf *graph, int
html("</td>\n");
}
-static void print_commit(struct commit *commit, struct rev_info *revs)
+// Ends a row in a table wider than the log with an empty cell for each column
+// the log has no use for.
+static void print_filler(int columns)
+{
+ while (columns-- > 0)
+ html("<td></td>");
+}
+
+static void print_commit(struct commit *commit, struct rev_info *revs, int filler)
{
struct commitinfo *info;
int columns = revs->graph ? 4 : 3;
@@ -223,6 +231,8 @@ static void print_commit(struct commit *commit, struct rev_info *revs)
columns++;
if (ctx.repo->enable_log_linecount)
columns++;
+ // The rows that span the table reach across the filler as well.
+ columns += filler;
if (revs->graph)
print_graph_above(revs, &graphbuf, columns);
@@ -270,7 +280,9 @@ static void print_commit(struct commit *commit, struct rev_info *revs)
htmlf("</td><td class='col-count'><span class='deletions'>-%d</span>/"
"<span class='insertions'>+%d</span>", removed_lines, added_lines);
- html("</td></tr>\n");
+ html("</td>");
+ print_filler(filler);
+ html("</tr>\n");
if ((revs->graph && !graph_is_commit_finished(revs->graph)) || ctx.qry.showmsg) {
html("<tr class='nohover-highlight'>");
@@ -449,12 +461,12 @@ void cgit_print_commit_decorations(struct commit *commit)
}
void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
- const char *path, int pager, int commit_graph, int commit_sort)
+ const char *path, int pager, int commit_graph, int commit_sort, int table_columns)
{
struct rev_info rev;
struct commit *commit;
struct strvec rev_argv = STRVEC_INIT;
- int i, columns = commit_graph ? 4 : 3;
+ int i, columns = commit_graph ? 4 : 3, filler = 0;
int must_free_tip = 0;
// setup_revisions skips the first entry the way a command skips its
@@ -591,6 +603,9 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
html("<th class='left col-count' scope='col'>Lines</th>");
columns++;
}
+ if (table_columns > columns)
+ filler = table_columns - columns;
+ print_filler(filler);
html("</tr>\n");
if (ofs < 0)
@@ -609,7 +624,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
counts_ready = 0;
if (should_show(commit, &rev)) {
i++;
- print_commit(commit, &rev);
+ print_commit(commit, &rev, filler);
}
release_commit_memory(the_repository->parsed_objects, commit);
commit->parents = NULL;
@@ -619,7 +634,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
cgit_print_layout_end();
} else {
if (get_revision(&rev)) {
- htmlf("<tr class='nohover'><td colspan='%d'>", columns);
+ htmlf("<tr class='nohover'><td colspan='%d'>", columns + filler);
cgit_log_link(
"[...]", NULL, NULL, ctx.qry.head, NULL, ctx.qry.vpath, 0,
NULL, NULL, ctx.qry.showmsg, ctx.qry.follow
diff --git a/source/ui-log.h b/source/ui-log.h
index 8e6e3db..d01a63d 100644
--- a/source/ui-log.h
+++ b/source/ui-log.h
@@ -8,8 +8,14 @@
#ifndef CGIT_UI_LOG_H
#define CGIT_UI_LOG_H
+/*
+ * With pager set the log is a page of its own, in a table it opens itself.
+ * Without it the rows go into a table the caller holds open, and table_columns
+ * is that table's width, which every row is filled out to. 0 leaves the rows
+ * at their own width.
+ */
extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
- const char *path, int pager, int commit_graph, int commit_sort);
+ const char *path, int pager, int commit_graph, int commit_sort, int table_columns);
extern void cgit_print_commit_decorations(struct commit *commit);
#endif // CGIT_UI_LOG_H
diff --git a/source/ui-refs.c b/source/ui-refs.c
index 38a5773..c8e8f3f 100644
--- a/source/ui-refs.c
+++ b/source/ui-refs.c
@@ -76,18 +76,30 @@ static void collect_branches(struct reflist *list)
refs_for_each_remote_ref(get_main_ref_store(the_repository), cgit_refs_cb, list);
}
-static void print_branch_header(void)
+/*
+ * The age is the last cell of a ref row, so in a table wider than a ref row
+ * needs, as on a summary whose log shows counts, it stretches across the
+ * columns left over.
+ */
+static const char *age_colspan(int columns)
+{
+ if (columns <= REF_COLUMNS)
+ return "";
+ return cgit_fmt(" colspan='%d'", columns - REF_COLUMNS + 1);
+}
+
+static void print_branch_header(int columns)
{
html(
"<tbody class='branches'>\n"
"<tr class='nohover'><th class='left' scope='col'>Branch</th>"
"<th class='left' scope='col'>Commit message</th>"
"<th class='left col-author' scope='col'>Author</th>"
- "<th colspan='2' class='left' scope='col'>Age</th></tr>\n"
);
+ htmlf("<th%s class='left' scope='col'>Age</th></tr>\n", age_colspan(columns));
}
-static int print_branch(struct refinfo *ref)
+static int print_branch(struct refinfo *ref, int columns)
{
struct commitinfo *info = ref->commit;
const char *name = ref->refname;
@@ -107,13 +119,13 @@ static int print_branch(struct refinfo *ref)
);
html_txt(info->author);
cgit_close_filter(ctx.repo->email_filter);
- html("</td><td colspan='2'>");
+ htmlf("</td><td%s>", age_colspan(columns));
cgit_print_age(info->committer_date, info->committer_tz, -1);
} else {
// The object link sits in the message column, and the empty
// tail cells keep the row as wide as the header.
cgit_object_link(ref->object, 0);
- html("</td><td class='col-author'></td><td colspan='2'>");
+ htmlf("</td><td class='col-author'></td><td%s>", age_colspan(columns));
}
html("</td></tr>\n");
return 0;
@@ -126,18 +138,18 @@ static void collect_tags(struct reflist *list)
refs_for_each_tag_ref(get_main_ref_store(the_repository), cgit_refs_cb, list);
}
-static void print_tag_header(void)
+static void print_tag_header(int columns)
{
html(
"<tbody class='tags'>\n"
"<tr class='nohover'><th class='left' scope='col'>Tag</th>"
"<th class='left' scope='col'>Download</th>"
"<th class='left col-author' scope='col'>Author</th>"
- "<th colspan='2' class='left' scope='col'>Age</th></tr>\n"
);
+ htmlf("<th%s class='left' scope='col'>Age</th></tr>\n", age_colspan(columns));
}
-static int print_tag(struct refinfo *ref)
+static int print_tag(struct refinfo *ref, int columns)
{
struct taginfo *info = NULL;
const char *name = ref->refname;
@@ -176,7 +188,7 @@ static int print_tag(struct refinfo *ref)
html_txt(ref->commit->author);
cgit_close_filter(ctx.repo->email_filter);
}
- html("</td><td colspan='2'>");
+ htmlf("</td><td%s>", age_colspan(columns));
if (info) {
if (info->tagger_date > 0)
cgit_print_age(info->tagger_date, info->tagger_tz, -1);
@@ -188,9 +200,9 @@ static int print_tag(struct refinfo *ref)
return 0;
}
-static void print_refs_link(const char *path)
+static void print_refs_link(const char *path, int columns)
{
- html("<tr class='nohover'><td colspan='5'>");
+ htmlf("<tr class='nohover'><td colspan='%d'>", columns);
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
html("</td></tr>\n");
}
@@ -199,7 +211,7 @@ static void print_ref_pager(int ofs, int pagesize, int count, const char *path)
{
char *url;
- html("<tr class='nohover'><td colspan='5' class='refs-pager'>");
+ htmlf("<tr class='nohover'><td colspan='%d' class='refs-pager'>", REF_COLUMNS);
if (ofs > 0) {
int prev_ofs = ofs > pagesize ? ofs - pagesize : 0;
@@ -243,7 +255,7 @@ static void print_branches_page(int pagesize)
struct ref_page page;
int i;
- print_branch_header();
+ print_branch_header(REF_COLUMNS);
collect_branches(&list);
cgit_qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
@@ -253,7 +265,7 @@ static void print_branches_page(int pagesize)
page = page_bounds(pagesize, list.count);
for (i = page.start; i < page.end; i++)
- print_branch(list.refs[i]);
+ print_branch(list.refs[i], REF_COLUMNS);
if (page.size < list.count)
print_ref_pager(page.start, page.size, list.count, "heads");
@@ -275,9 +287,9 @@ static void print_tags_page(int pagesize)
page = page_bounds(pagesize, list.count);
- print_tag_header();
+ print_tag_header(REF_COLUMNS);
for (i = page.start; i < page.end; i++)
- print_tag(list.refs[i]);
+ print_tag(list.refs[i], REF_COLUMNS);
if (page.size < list.count)
print_ref_pager(page.start, page.size, list.count, "tags");
@@ -286,12 +298,12 @@ static void print_tags_page(int pagesize)
cgit_free_reflist_inner(&list);
}
-void cgit_print_branches(int maxcount)
+void cgit_print_branches(int maxcount, int columns)
{
struct reflist list;
int i;
- print_branch_header();
+ print_branch_header(columns);
collect_branches(&list);
if (maxcount <= 0 || maxcount > list.count)
@@ -305,16 +317,16 @@ void cgit_print_branches(int maxcount)
cgit_qsort(list.refs, maxcount, sizeof(*list.refs), cmp_name);
for (i = 0; i < maxcount; i++)
- print_branch(list.refs[i]);
+ print_branch(list.refs[i], columns);
if (maxcount < list.count)
- print_refs_link("heads");
+ print_refs_link("heads", columns);
html("</tbody>\n");
cgit_free_reflist_inner(&list);
}
-void cgit_print_tags(int maxcount)
+void cgit_print_tags(int maxcount, int columns)
{
struct reflist list;
int i;
@@ -325,12 +337,12 @@ void cgit_print_tags(int maxcount)
cgit_qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
if (maxcount <= 0 || maxcount > list.count)
maxcount = list.count;
- print_tag_header();
+ print_tag_header(columns);
for (i = 0; i < maxcount; i++)
- print_tag(list.refs[i]);
+ print_tag(list.refs[i], columns);
if (maxcount < list.count)
- print_refs_link("tags");
+ print_refs_link("tags", columns);
html("</tbody>\n");
cgit_free_reflist_inner(&list);
@@ -346,8 +358,8 @@ void cgit_print_refs(void)
} else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) {
print_tags_page(ctx.cfg.max_ref_count);
} else {
- cgit_print_branches(ctx.cfg.max_ref_count);
- cgit_print_tags(ctx.cfg.max_ref_count);
+ cgit_print_branches(ctx.cfg.max_ref_count, REF_COLUMNS);
+ cgit_print_tags(ctx.cfg.max_ref_count, REF_COLUMNS);
}
html("</table>\n");
cgit_print_layout_end();
diff --git a/source/ui-refs.h b/source/ui-refs.h
index c66050b..b5d4aba 100644
--- a/source/ui-refs.h
+++ b/source/ui-refs.h
@@ -7,13 +7,17 @@
#ifndef CGIT_UI_REFS_H
#define CGIT_UI_REFS_H
+// The name, the commit message or downloads, the author and the age.
+#define REF_COLUMNS 4
+
/*
* Rows for the newest branches or tags, at most maxcount of them, where a
* maxcount of zero means every one. A section that leaves refs out ends in a
- * link to the matching form of the refs page.
+ * link to the matching form of the refs page. columns is the width of the
+ * table the rows go into, which is at least REF_COLUMNS.
*/
-extern void cgit_print_branches(int maxcount);
-extern void cgit_print_tags(int maxcount);
+extern void cgit_print_branches(int maxcount, int columns);
+extern void cgit_print_tags(int maxcount, int columns);
/*
* The whole refs page. Without a path it holds a capped section of each kind,
diff --git a/source/ui-summary.c b/source/ui-summary.c
index 005263a..f37650d 100644
--- a/source/ui-summary.c
+++ b/source/ui-summary.c
@@ -18,8 +18,7 @@
#include "ui-summary.h"
// Age, commit message and author, the three columns a log row always has.
-// ui-log.c adds one more for each of the two optional counts, and the rows
-// this page stretches across the table have to match that width.
+// ui-log.c adds one more for each of the two optional counts.
#define LOG_BASE_COLUMNS 3
static int clone_urls_printed;
@@ -35,9 +34,21 @@ static int log_columns(void)
return columns;
}
+/*
+ * The sections share one table, which is as wide as the widest of them, and
+ * every row fills it, since a short row would end its stripe short of the
+ * table's edge.
+ */
+static int table_columns(void)
+{
+ if (ctx.cfg.summary_log > 0 && log_columns() > REF_COLUMNS)
+ return log_columns();
+ return REF_COLUMNS;
+}
+
static void print_clone_url(const char *url)
{
- int columns = log_columns();
+ int columns = table_columns();
// cgit_add_clone_urls may call back no times at all, so the heading
// waits for a first url rather than being printed ahead of the walk.
@@ -52,14 +63,16 @@ static void print_clone_url(const char *url)
void cgit_print_summary(void)
{
+ int columns = table_columns();
+
cgit_print_layout_start();
// One table, so the columns line up across the sections, each of
// which is a body of its own.
html("<table class='list summary' aria-label='Summary'>\n");
- cgit_print_branches(ctx.cfg.summary_branches);
- cgit_print_tags(ctx.cfg.summary_tags);
+ cgit_print_branches(ctx.cfg.summary_branches, columns);
+ cgit_print_tags(ctx.cfg.summary_tags, columns);
if (ctx.cfg.summary_log > 0)
- cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, NULL, NULL, 0, 0, 0);
+ cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, NULL, NULL, 0, 0, 0, columns);
clone_urls_printed = 0;
cgit_add_clone_urls(print_clone_url);
if (clone_urls_printed)
diff --git a/tests/t0102-summary.sh b/tests/t0102-summary.sh
index 181040d..e95f270 100755
--- a/tests/t0102-summary.sh
+++ b/tests/t0102-summary.sh
@@ -51,4 +51,81 @@ test_expect_success 'an scp clone url row links to the http url' '
! grep "vcs-git. href=.git@" tmp
'
+# Prints how many columns each row of the page's list table spans, one row to
+# a line, counting a cell without a colspan as one.
+row_widths() {
+ sed -n "/<table class='list/,/<\/table>/p" "$1" | awk '
+ /^<tr[ >]/ {
+ width = 0
+ row = $0
+ while (match(row, /<t[dh][ >]/)) {
+ row = substr(row, RSTART + 3)
+ cell = substr(row, 1, index(row, ">"))
+ if (match(cell, /colspan=.[0-9]+/))
+ width += substr(cell, RSTART + 9, RLENGTH - 9)
+ else
+ width++
+ }
+ print width
+ }
+ '
+}
+
+# Fails unless every row of the list table at the url is as many columns wide
+# as the second argument says. A third names a config other than colsrc.
+test_widths() {
+ CGIT_CONFIG="$PWD/${3:-colsrc}" QUERY_STRING="url=$1" cgit >tmp &&
+ row_widths tmp | sort -u >actual &&
+ echo "$2" >expect &&
+ test_cmp expect actual
+}
+
+# The summary's sections share one table, so every row has to fill it, or the
+# stripe of a shorter row stops short of the table's edge. The table is as
+# wide as the log when both counts are on and as wide as a ref row otherwise.
+# Each repo below shows the same history with different counts, and every
+# section is cut short so that its [...] row is drawn too.
+test_expect_success 'set up a repo cut short in every section' '
+ mkrepo repos/cols 3 >/dev/null &&
+ git -C repos/cols branch other HEAD~1 &&
+ git -C repos/cols tag one HEAD~1 &&
+ git -C repos/cols tag -a -m two two &&
+ {
+ echo "virtual-root=/" &&
+ echo "cache-size=0" &&
+ echo "summary-branches=1" &&
+ echo "summary-tags=1" &&
+ echo "summary-log=2" &&
+ echo "max-ref-count=1" &&
+ echo "clone-url=https://example.org/\$CGIT_REPO_URL" &&
+ echo "repo.url=none" &&
+ echo "repo.path=$PWD/repos/cols/.git" &&
+ echo "repo.url=lines" &&
+ echo "repo.path=$PWD/repos/cols/.git" &&
+ echo "repo.enable-log-linecount=1" &&
+ echo "repo.url=both" &&
+ echo "repo.path=$PWD/repos/cols/.git" &&
+ echo "repo.enable-log-filecount=1" &&
+ echo "repo.enable-log-linecount=1"
+ } >colsrc &&
+ CGIT_CONFIG="$PWD/colsrc" QUERY_STRING="url=none" cgit >tmp &&
+ grep "<tbody class=.tags.>" tmp &&
+ grep "<tbody class=.clone.>" tmp &&
+ test $(grep -c ">\[...\]</a>" tmp) = 3
+'
+
+test_expect_success 'a summary without counts fills four columns' 'test_widths none 4'
+test_expect_success 'a summary with one count fills four columns' 'test_widths lines 4'
+test_expect_success 'a summary with both counts fills five columns' 'test_widths both 5'
+
+test_expect_success 'a summary without a log fills four columns' '
+ sed "s/^summary-log=2$/summary-log=0/" colsrc >nologrc &&
+ test_widths both 4 nologrc
+'
+
+# The refs page draws the same rows with nothing wider beside them.
+test_expect_success 'the refs page fills four columns' 'test_widths both/refs/ 4'
+test_expect_success 'a page of branches fills four columns' 'test_widths both/refs/heads 4'
+test_expect_success 'a page of tags fills four columns' 'test_widths both/refs/tags 4'
+
test_done