#include "commit.h"
#include "pretty.h"
#include "userdiff.h"
+#include "apply.h"
struct patch_util {
/* For the search for an exact match */
struct object_id oid;
};
+static size_t find_end_of_line(char *buffer, unsigned long size)
+{
+ char *eol = memchr(buffer, '\n', size);
+
+ if (!eol)
+ return size;
+
+ *eol = '\0';
+ return eol + 1 - buffer;
+}
+
/*
* Reads the patches into a string list, with the `util` field being populated
* as struct object_id (will need to be free()d).
static int read_patches(const char *range, struct string_list *list)
{
struct child_process cp = CHILD_PROCESS_INIT;
- FILE *in;
- struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
+ struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT;
struct patch_util *util = NULL;
int in_header = 1;
+ char *line, *current_filename = NULL;
+ int offset, len;
+ size_t size;
argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
"--reverse", "--date-order", "--decorate=no",
+ /*
+ * Choose indicators that are not used anywhere
+ * else in diffs, but still look reasonable
+ * (e.g. will not be confusing when debugging)
+ */
+ "--output-indicator-new=>",
+ "--output-indicator-old=<",
+ "--output-indicator-context=#",
"--no-abbrev-commit", range,
NULL);
cp.out = -1;
if (start_command(&cp))
return error_errno(_("could not start `log`"));
- in = fdopen(cp.out, "r");
- if (!in) {
+ if (strbuf_read(&contents, cp.out, 0) < 0) {
error_errno(_("could not read `log` output"));
finish_command(&cp);
return -1;
}
- while (strbuf_getline(&line, in) != EOF) {
+ line = contents.buf;
+ size = contents.len;
+ for (offset = 0; size > 0; offset += len, size -= len, line += len) {
const char *p;
- if (skip_prefix(line.buf, "commit ", &p)) {
+ len = find_end_of_line(line, size);
+ line[len - 1] = '\0';
+ if (skip_prefix(line, "commit ", &p)) {
if (util) {
string_list_append(list, buf.buf)->util = util;
strbuf_reset(&buf);
free(util);
string_list_clear(list, 1);
strbuf_release(&buf);
- strbuf_release(&line);
- fclose(in);
+ strbuf_release(&contents);
finish_command(&cp);
return -1;
}
continue;
}
- if (starts_with(line.buf, "diff --git")) {
+ if (starts_with(line, "diff --git")) {
+ struct patch patch = { 0 };
+ struct strbuf root = STRBUF_INIT;
+ int linenr = 0;
+
in_header = 0;
strbuf_addch(&buf, '\n');
if (!util->diff_offset)
util->diff_offset = buf.len;
- strbuf_addbuf(&buf, &line);
+ line[len - 1] = '\n';
+ len = parse_git_diff_header(&root, &linenr, 1, line,
+ len, size, &patch);
+ if (len < 0)
+ die(_("could not parse git header '%.*s'"), (int)len, line);
+ strbuf_addstr(&buf, " ## ");
+ if (patch.is_new > 0)
+ strbuf_addf(&buf, "%s (new)", patch.new_name);
+ else if (patch.is_delete > 0)
+ strbuf_addf(&buf, "%s (deleted)", patch.old_name);
+ else if (patch.is_rename)
+ strbuf_addf(&buf, "%s => %s", patch.old_name, patch.new_name);
+ else
+ strbuf_addstr(&buf, patch.new_name);
+
+ free(current_filename);
+ if (patch.is_delete > 0)
+ current_filename = xstrdup(patch.old_name);
+ else
+ current_filename = xstrdup(patch.new_name);
+
+ if (patch.new_mode && patch.old_mode &&
+ patch.old_mode != patch.new_mode)
+ strbuf_addf(&buf, " (mode change %06o => %06o)",
+ patch.old_mode, patch.new_mode);
+
+ strbuf_addstr(&buf, " ##");
} else if (in_header) {
- if (starts_with(line.buf, "Author: ")) {
- strbuf_addbuf(&buf, &line);
+ if (starts_with(line, "Author: ")) {
+ strbuf_addstr(&buf, " ## Metadata ##\n");
+ strbuf_addstr(&buf, line);
strbuf_addstr(&buf, "\n\n");
- } else if (starts_with(line.buf, " ")) {
- strbuf_rtrim(&line);
- strbuf_addbuf(&buf, &line);
+ strbuf_addstr(&buf, " ## Commit message ##\n");
+ } else if (starts_with(line, " ")) {
+ p = line + len - 2;
+ while (isspace(*p) && p >= line)
+ p--;
+ strbuf_add(&buf, line, p - line + 1);
strbuf_addch(&buf, '\n');
}
continue;
- } else if (starts_with(line.buf, "@@ "))
+ } else if (skip_prefix(line, "@@ ", &p)) {
+ p = strstr(p, "@@");
strbuf_addstr(&buf, "@@");
- else if (!line.buf[0] || starts_with(line.buf, "index "))
+ if (current_filename && p[2])
+ strbuf_addf(&buf, " %s:", current_filename);
+ if (p)
+ strbuf_addstr(&buf, p + 2);
+ } else if (!line[0])
/*
* A completely blank (not ' \n', which is context)
* line is not valid in a diff. We skip it
* silently, because this neatly handles the blank
* separator line between commits in git-log
* output.
- *
- * We also want to ignore the diff's `index` lines
- * because they contain exact blob hashes in which
- * we are not interested.
*/
continue;
- else
- strbuf_addbuf(&buf, &line);
+ else if (line[0] == '>') {
+ strbuf_addch(&buf, '+');
+ strbuf_addstr(&buf, line + 1);
+ } else if (line[0] == '<') {
+ strbuf_addch(&buf, '-');
+ strbuf_addstr(&buf, line + 1);
+ } else if (line[0] == '#') {
+ strbuf_addch(&buf, ' ');
+ strbuf_addstr(&buf, line + 1);
+ } else {
+ strbuf_addch(&buf, ' ');
+ strbuf_addstr(&buf, line);
+ }
strbuf_addch(&buf, '\n');
util->diffsize++;
}
- fclose(in);
- strbuf_release(&line);
+ strbuf_release(&contents);
if (util)
string_list_append(list, buf.buf)->util = util;
strbuf_release(&buf);
+ free(current_filename);
if (finish_command(&cp))
return -1;
}
static int patch_util_cmp(const void *dummy, const struct patch_util *a,
- const struct patch_util *b, const char *keydata)
+ const struct patch_util *b, const char *keydata)
{
return strcmp(a->diff, keydata ? keydata : b->diff);
}
(*(int *)data)++;
}
+static void diffsize_hunk(void *data, long ob, long on, long nb, long nn,
+ const char *funcline, long funclen)
+{
+ diffsize_consume(data, NULL, 0);
+}
+
static int diffsize(const char *a, const char *b)
{
xpparam_t pp = { 0 };
mf2.size = strlen(b);
cfg.ctxlen = 3;
- if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
+ if (!xdi_diff_outf(&mf1, &mf2,
+ diffsize_hunk, diffsize_consume, &count,
+ &pp, &cfg))
return count;
error(_("failed to generate diff"));
}
strbuf_addf(buf, "%s\n", color_reset);
- fwrite(buf->buf, buf->len, 1, stdout);
+ fwrite(buf->buf, buf->len, 1, diffopt->file);
}
-static struct userdiff_driver no_func_name = {
- .funcname = { "$^", 0 }
+static struct userdiff_driver section_headers = {
+ .funcname = { "^ ## (.*) ##$\n"
+ "^.?@@ (.*)$", REG_EXTENDED }
};
static struct diff_filespec *get_filespec(const char *name, const char *p)
{
struct diff_filespec *spec = alloc_filespec(name);
- fill_filespec(spec, &null_oid, 0, 0644);
+ fill_filespec(spec, &null_oid, 0, 0100644);
spec->data = (char *)p;
spec->size = strlen(p);
spec->should_munmap = 0;
spec->is_stdin = 1;
- spec->driver = &no_func_name;
+ spec->driver = §ion_headers;
return spec;
}
static void patch_diff(const char *a, const char *b,
- struct diff_options *diffopt)
+ struct diff_options *diffopt)
{
diff_queue(&diff_queued_diff,
get_filespec("a", a), get_filespec("b", b));
strbuf_release(&dashes);
}
+static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
+{
+ return data;
+}
+
int show_range_diff(const char *range1, const char *range2,
- int creation_factor, struct diff_options *diffopt)
+ int creation_factor, int dual_color,
+ struct diff_options *diffopt)
{
int res = 0;
res = error(_("could not parse log for '%s'"), range2);
if (!res) {
+ struct diff_options opts;
+ struct strbuf indent = STRBUF_INIT;
+
+ if (diffopt)
+ memcpy(&opts, diffopt, sizeof(opts));
+ else
+ diff_setup(&opts);
+
+ if (!opts.output_format)
+ opts.output_format = DIFF_FORMAT_PATCH;
+ opts.flags.suppress_diff_headers = 1;
+ opts.flags.dual_color_diffed_diffs = dual_color;
+ opts.flags.suppress_hunk_header_line_count = 1;
+ opts.output_prefix = output_prefix_cb;
+ strbuf_addstr(&indent, " ");
+ opts.output_prefix_data = &indent;
+ diff_setup_done(&opts);
+
find_exact_matches(&branch1, &branch2);
get_correspondences(&branch1, &branch2, creation_factor);
- output(&branch1, &branch2, diffopt);
+ output(&branch1, &branch2, &opts);
+
+ strbuf_release(&indent);
}
string_list_clear(&branch1, 1);