diff options
Diffstat (limited to '')
-rw-r--r--source/cgit.c2
-rw-r--r--source/cgit.h2
-rw-r--r--source/html.c79
-rw-r--r--source/html.h13
-rw-r--r--source/ui-blame.c18
-rw-r--r--source/ui-patch.c12
-rw-r--r--source/ui-tree.c4
7 files changed, 121 insertions, 9 deletions
diff --git a/source/cgit.c b/source/cgit.c
index 5f8ecfa..a877e92 100644
--- a/source/cgit.c
+++ b/source/cgit.c
@@ -54,6 +54,8 @@ struct refmatch {
int found;
};
+const char *cgit_version = CGIT_VERSION;
+
/*
* Isolate git from the calling user's configuration, which could otherwise
* break once HOME is unset below. It runs as a constructor because git reads
diff --git a/source/cgit.h b/source/cgit.h
index 5b92632..c04193f 100644
--- a/source/cgit.h
+++ b/source/cgit.h
@@ -324,6 +324,8 @@ struct cgit_context {
int empty_repo;
};
+extern const char *cgit_version;
+
extern struct cgit_repolist cgit_repolist;
extern struct cgit_context ctx;
diff --git a/source/html.c b/source/html.c
index d1e28f8..d13ac5b 100644
--- a/source/html.c
+++ b/source/html.c
@@ -258,6 +258,85 @@ ssize_t html_ntxt(const char *txt, size_t len)
return left;
}
+/*
+ * git ends a line only at a line feed. A browser also starts one at a carriage
+ * return with no line feed after it, since the HTML parser turns that into a
+ * line feed, and may at the other characters Unicode makes mandatory breaks.
+ * Escaping those keeps one line on the page for every line git counts, so the
+ * numbers beside the text stay in step with it.
+ */
+const char *html_line_break_escape(const char *p, const char *end, size_t *width)
+{
+ const char *esc;
+
+ switch ((unsigned char)*p) {
+ case '\r':
+ // The parser reads CR LF as the single line feed git ends the
+ // line with.
+ if (p + 1 < end && p[1] == '\n')
+ return NULL;
+ *width = 1;
+ return "\\r";
+ case '\v':
+ *width = 1;
+ return "\\v";
+ case '\f':
+ *width = 1;
+ return "\\f";
+ case 0xc2:
+ if (end - p < 2 || memcmp(p, "\xc2\x85", 2))
+ return NULL;
+ *width = 2;
+ return "\\u0085";
+ case 0xe2:
+ if (end - p >= 3 && !memcmp(p, "\xe2\x80\xa8", 3))
+ esc = "\\u2028";
+ else if (end - p >= 3 && !memcmp(p, "\xe2\x80\xa9", 3))
+ esc = "\\u2029";
+ else
+ return NULL;
+ *width = 3;
+ return esc;
+ default:
+ return NULL;
+ }
+}
+
+void html_blob_txt(const char *txt, size_t len)
+{
+ const char *end = txt + len;
+ const char *p;
+
+ for (p = txt; p < end; p++) {
+ const char *entity = NULL, *esc = NULL;
+ size_t width = 1;
+
+ if (*p == '<')
+ entity = "&lt;";
+ else if (*p == '>')
+ entity = "&gt;";
+ else if (*p == '&')
+ entity = "&amp;";
+ else
+ esc = html_line_break_escape(p, end, &width);
+ if (!entity && !esc)
+ continue;
+ html_raw(txt, p - txt);
+ if (entity) {
+ html(entity);
+ } else {
+ // The span lets a stylesheet tell the escape from the
+ // same characters typed into the file.
+ html("<span class='escape'>");
+ html(esc);
+ html("</span>");
+ }
+ p += width - 1;
+ txt = p + 1;
+ }
+ html_raw(txt, end - txt);
+}
+
void html_attrf(const char *format, ...)
{
va_list args;
diff --git a/source/html.h b/source/html.h
index c6c0e19..9cad3a6 100644
--- a/source/html.h
+++ b/source/html.h
@@ -61,6 +61,19 @@ extern void html_txt(const char *txt);
*/
extern ssize_t html_ntxt(const char *txt, size_t len);
+/*
+ * The escape sequence a blob's text shows for the character at p when a
+ * browser would start a new line at it though git does not, or NULL for any
+ * other character. The character's length in bytes is stored in width.
+ */
+extern const char *html_line_break_escape(const char *p, const char *end, size_t *width);
+
+/*
+ * Escape the text of a blob for the page beside its line numbers, writing
+ * each character html_line_break_escape names as its escape sequence.
+ */
+extern void html_blob_txt(const char *txt, size_t len);
+
extern void html_attr(const char *txt);
extern void html_url_path(const char *txt);
diff --git a/source/ui-blame.c b/source/ui-blame.c
index 8fd6c83..f60020c 100644
--- a/source/ui-blame.c
+++ b/source/ui-blame.c
@@ -211,6 +211,15 @@ static size_t line_width(struct blame_scoreboard *sb, int line)
size_t width = 0;
while (pos < end) {
+ size_t len;
+ const char *esc = html_line_break_escape(pos, end, &len);
+
+ // An escaped character takes the room of its escape sequence.
+ if (esc) {
+ width += strlen(esc);
+ pos += len;
+ continue;
+ }
width++;
if (*pos++ == '\t')
width = (width + TAB_WIDTH - 1) & ~(TAB_WIDTH - 1);
@@ -297,9 +306,10 @@ static void print_blame_page(const struct object_id *oid, const char *path,
cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
html(")</div>\n");
- // A NUL past the window buffer_is_binary sniffs would end html_txt
- // early while the hash and line number columns still cover the whole
- // file, so a blob holding one anywhere is treated as binary too.
+ // A NUL past the window buffer_is_binary sniffs would end a Lua
+ // filter's output early while the hash and line number columns still
+ // cover the whole file, so a blob holding one anywhere is treated as
+ // binary too.
if (buffer_is_binary(buf, size) || memchr(buf, 0, size)) {
struct blame_entry *ent = sb.ent, *next;
@@ -334,7 +344,7 @@ static void print_blame_page(const struct object_id *oid, const char *path,
cgit_close_filter(ctx.repo->source_filter);
free(filter_arg);
} else {
- html_txt(buf);
+ html_blob_txt(buf, size);
}
html("</code></pre>");
diff --git a/source/ui-patch.c b/source/ui-patch.c
index 525e660..78c850c 100644
--- a/source/ui-patch.c
+++ b/source/ui-patch.c
@@ -62,6 +62,7 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref
struct commit *commit;
struct object_id new_oid, old_oid;
char rev_range[REV_RANGE_LEN];
+ const char *version;
// setup_revisions reads these the way git reads a command line, so the
// first entry stands in for the program name and is skipped, and the
// array has to stay null terminated because the path after the double
@@ -130,11 +131,16 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, const char *pref
ctx.page.filename = cgit_fmt("%s.patch", rev_range);
cgit_print_http_headers();
+ // git format-patch signs with its bare version number, so the v that
+ // cgit's tags carry is left off to read the same way.
+ if (!skip_prefix(cgit_version, "v", &version))
+ version = cgit_version;
+
while ((commit = get_revision(&rev))) {
log_tree_commit(&rev, commit);
// Two dashes and a space is the mail signature separator, so
- // git am and mail readers drop the line below rather than
- // carrying it into the commit message.
- printf("-- \ncgit\n\n");
+ // git am and mail readers drop the version note below rather
+ // than carrying it into the commit message.
+ printf("-- \ncgit %s\n\n", version);
}
}
diff --git a/source/ui-tree.c b/source/ui-tree.c
index 13a9787..1d8bb3d 100644
--- a/source/ui-tree.c
+++ b/source/ui-tree.c
@@ -113,7 +113,7 @@ static void print_text_buffer(const char *filename, char *buf, size_t size)
}
html("<td class='lines'><pre><code>");
- html_txt(buf);
+ html_blob_txt(buf, size);
html("</code></pre></td></tr></table>\n");
}
@@ -188,7 +188,7 @@ static bool print_object(const struct object_id *oid, const char *path,
return false;
}
// buffer_is_binary only sniffs the front of the blob, and a NUL past
- // that window would end the escaped text render early while the line
+ // that window would end a Lua filter's output early while the line
// number column still counts the whole file, so check the rest too.
is_binary = buffer_is_binary(buf, size) || memchr(buf, 0, size);