/* * The side by side rendering of a diff, the layout a reader gets instead of * the unified listing. ui-diff.c drives it, handing over each line xdiff * produces and calling in around the header and the footer of every file. * Removed and added lines are held back until their run ends, so that the two * sides can be paired into a row apiece. When a run has as many removals as * additions the paired lines are compared character by character, and what * differs within them is marked. */ #include "cgit.h" #include "html.h" #include "ui-diff.h" #include "ui-shared.h" #include "ui-ssdiff.h" // The longest old line and the longest new line the view will compare // character by character. The comparison fills a cell for every pair of // characters, so its cost is the product of the two, and a line past the limit // is shown whole instead. #define MAX_SSDIFF_M 128 #define MAX_SSDIFF_N 128 #define MAX_SSDIFF_SIZE (MAX_SSDIFF_M * MAX_SSDIFF_N) // One line held back until its run ends, owning the copy taken of it. struct deferred_line { int line_no; char *line; struct deferred_line *next; }; // The pair being rendered, whose two sides the line number links lead into. static struct diff_filepair *current_filepair; static int current_old_line, current_new_line; static int **lcs_table; static struct deferred_line *deferred_old, *deferred_old_last; static struct deferred_line *deferred_new, *deferred_new_last; /* * The table is reused by every comparison and nothing clears it in between, * because the fill in longest_common_subsequence works back from the far * corner and writes every cell it goes on to read. */ static void create_lcs_table(void) { int i; if (lcs_table) return; lcs_table = xcalloc(MAX_SSDIFF_M, sizeof(int *)); lcs_table[0] = xcalloc(MAX_SSDIFF_SIZE, sizeof(int)); for (i = 1; i < MAX_SSDIFF_M; i++) lcs_table[i] = lcs_table[0] + i * MAX_SSDIFF_N; } /* * The characters the two lines have in common, in order, which the caller * owns. A line too long for the table gets NULL back and is shown whole * instead. */ static char *longest_common_subsequence(const char *old_line, const char *new_line) { int old_len = strlen(old_line); int new_len = strlen(new_line); int i, j, pos, lcs_len; char *lcs; if (old_len >= MAX_SSDIFF_M || new_len >= MAX_SSDIFF_N) return NULL; create_lcs_table(); for (i = old_len; i >= 0; i--) { for (j = new_len; j >= 0; j--) { if (old_line[i] == '\0' || new_line[j] == '\0') { lcs_table[i][j] = 0; } else if (old_line[i] == new_line[j]) { lcs_table[i][j] = 1 + lcs_table[i + 1][j + 1]; } else { int drop_old = lcs_table[i + 1][j]; int drop_new = lcs_table[i][j + 1]; lcs_table[i][j] = (drop_old > drop_new ? drop_old : drop_new); } } } lcs_len = lcs_table[0][0]; lcs = xmalloc(lcs_len + 2); memset(lcs, 0, sizeof(*lcs) * (lcs_len + 2)); pos = 0; i = 0; j = 0; while (i < old_len && j < new_len) { if (old_line[i] == new_line[j]) { lcs[pos] = old_line[i]; pos++; i++; j++; } else if (lcs_table[i + 1][j] >= lcs_table[i][j + 1]) { i++; } else { j++; } } return lcs; } /* * The line with its tabs expanded, which the caller owns. Built in one * forward pass so a tab heavy line stays linear in its own length. */ static char *expand_tabs(const char *line) { struct strbuf out = STRBUF_INIT; const char *p; for (p = line; *p; p++) { if (*p == '\t') strbuf_addchars(&out, ' ', TAB_WIDTH - (out.len % TAB_WIDTH)); else strbuf_addch(&out, *p); } return strbuf_detach(&out, NULL); } static void flush_run(struct strbuf *run) { if (!run->len) return; html_txt(run->buf); strbuf_reset(run); } /* * A stretch the other side does not share is escaped in one call, so a * changed line does not go through the output path a byte at a time. */ static void print_line_with_lcs(const char *class, const char *line, const char *lcs) { int len = strlen(line); int in_common = 1; int matched = 0; int i; struct strbuf run = STRBUF_INIT; for (i = 0; i < len; i++) { if (in_common) { if (line[i] == lcs[matched]) { matched++; } else { in_common = 0; flush_run(&run); htmlf("", class); } } else if (line[i] == lcs[matched]) { in_common = 1; flush_run(&run); html(""); matched++; } strbuf_addch(&run, line[i]); } flush_run(&run); if (!in_common) html(""); strbuf_release(&run); } static void print_lineno_cell(struct diff_filespec *file, const struct object_id *rev, int line_no) { struct strbuf path = STRBUF_INIT; char *anchor, *query, *fileurl; const char *rev_hex; anchor = cgit_fmt("n%d", line_no); rev_hex = is_null_oid(&file->oid) ? "HEAD" : oid_to_hex(rev); query = cgit_fmt("id=%s#%s", rev_hex, anchor); // The path is repository content, so percent-encode it so that the url // stays a url whatever bytes the path holds. if (file->path) strbuf_add_percentencode(&path, file->path, 0); fileurl = cgit_fileurl(ctx.repo->url, "tree", path.buf, query); html("