tree-walk.h log-tree.h
DIFF_OBJS = \
- diff.o diffcore-break.o diffcore-order.o \
+ diff-lib.o diffcore-break.o diffcore-order.o \
diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
diffcore-delta.o log-tree.o
#include "diffcore.h"
#include "quote.h"
#include "xdiff-interface.h"
+#include "log-tree.h"
static int uninteresting(struct diff_filepair *p)
{
sline->p_lno[i] = sline->p_lno[j];
}
+static void dump_quoted_path(const char *prefix, const char *path)
+{
+ fputs(prefix, stdout);
+ if (quote_c_style(path, NULL, NULL, 0))
+ quote_c_style(path, NULL, stdout, 0);
+ else
+ printf("%s", path);
+ putchar('\n');
+}
+
static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
- int dense, const char *header,
- struct diff_options *opt)
+ int dense, struct rev_info *rev)
{
+ struct diff_options *opt = &rev->diffopt;
unsigned long result_size, cnt, lno;
char *result, *cp;
struct sline *sline; /* survived lines */
if (show_hunks || mode_differs || working_tree_file) {
const char *abb;
- if (header) {
- shown_header++;
- printf("%s%c", header, opt->line_termination);
- }
- printf("diff --%s ", dense ? "cc" : "combined");
- if (quote_c_style(elem->path, NULL, NULL, 0))
- quote_c_style(elem->path, NULL, stdout, 0);
- else
- printf("%s", elem->path);
- putchar('\n');
+ if (rev->loginfo)
+ show_log(rev, rev->loginfo, "\n");
+ dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
printf("index ");
for (i = 0; i < num_parent; i++) {
abb = find_unique_abbrev(elem->parent[i].sha1,
}
putchar('\n');
}
+ dump_quoted_path("--- a/", elem->path);
+ dump_quoted_path("+++ b/", elem->path);
dump_sline(sline, cnt, num_parent);
}
free(result);
#define COLONS "::::::::::::::::::::::::::::::::"
-static void show_raw_diff(struct combine_diff_path *p, int num_parent, const char *header, struct diff_options *opt)
+static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev)
{
+ struct diff_options *opt = &rev->diffopt;
int i, offset;
const char *prefix;
int line_termination, inter_name_termination;
if (!line_termination)
inter_name_termination = 0;
- if (header)
- printf("%s%c", header, line_termination);
+ if (rev->loginfo)
+ show_log(rev, rev->loginfo, "\n");
if (opt->output_format == DIFF_FORMAT_RAW) {
offset = strlen(COLONS) - num_parent;
}
}
-int show_combined_diff(struct combine_diff_path *p,
+void show_combined_diff(struct combine_diff_path *p,
int num_parent,
int dense,
- const char *header,
- struct diff_options *opt)
+ struct rev_info *rev)
{
+ struct diff_options *opt = &rev->diffopt;
if (!p->len)
- return 0;
+ return;
switch (opt->output_format) {
case DIFF_FORMAT_RAW:
case DIFF_FORMAT_NAME_STATUS:
case DIFF_FORMAT_NAME:
- show_raw_diff(p, num_parent, header, opt);
- return 1;
-
- default:
+ show_raw_diff(p, num_parent, rev);
+ return;
case DIFF_FORMAT_PATCH:
- return show_patch_diff(p, num_parent, dense, header, opt);
+ show_patch_diff(p, num_parent, dense, rev);
+ return;
+ default:
+ return;
}
}
-const char *diff_tree_combined_merge(const unsigned char *sha1,
- const char *header, int dense,
- struct diff_options *opt)
+void diff_tree_combined_merge(const unsigned char *sha1,
+ int dense, struct rev_info *rev)
{
+ struct diff_options *opt = &rev->diffopt;
struct commit *commit = lookup_commit(sha1);
struct diff_options diffopts;
struct commit_list *parents;
struct combine_diff_path *p, *paths = NULL;
int num_parent, i, num_paths;
+ int do_diffstat;
+ do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
+ opt->with_stat);
diffopts = *opt;
- diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diffopts.with_raw = 0;
+ diffopts.with_stat = 0;
diffopts.recursive = 1;
/* count parents */
parents;
parents = parents->next, i++) {
struct commit *parent = parents->item;
+ /* show stat against the first parent even
+ * when doing combined diff.
+ */
+ if (i == 0 && do_diffstat)
+ diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+ else
+ diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
&diffopts);
diffcore_std(&diffopts);
paths = intersect_paths(paths, i, num_parent);
+
+ if (do_diffstat && rev->loginfo)
+ show_log(rev, rev->loginfo,
+ opt->with_stat ? "---\n" : "\n");
diff_flush(&diffopts);
+ if (opt->with_stat)
+ putchar('\n');
}
/* find out surviving paths */
int saved_format = opt->output_format;
opt->output_format = DIFF_FORMAT_RAW;
for (p = paths; p; p = p->next) {
- if (show_combined_diff(p, num_parent, dense,
- header, opt))
- header = NULL;
+ show_combined_diff(p, num_parent, dense, rev);
}
opt->output_format = saved_format;
putchar(opt->line_termination);
}
for (p = paths; p; p = p->next) {
- if (show_combined_diff(p, num_parent, dense,
- header, opt))
- header = NULL;
+ show_combined_diff(p, num_parent, dense, rev);
}
}
paths = paths->next;
free(tmp);
}
- return header;
}
CMIT_FMT_FULL,
CMIT_FMT_FULLER,
CMIT_FMT_ONELINE,
+
+ CMIT_FMT_UNSPECIFIED,
};
extern enum cmit_fmt get_commit_format(const char *arg);
*/
#include "cache.h"
#include "diff.h"
+#include "commit.h"
+#include "revision.h"
static const char diff_files_usage[] =
"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
COMMON_DIFF_OPTIONS_HELP;
-static struct diff_options diff_options;
+static struct rev_info rev;
static int silent = 0;
static int diff_unmerged_stage = 2;
static int combine_merges = 0;
static void show_unmerge(const char *path)
{
- diff_unmerge(&diff_options, path);
+ diff_unmerge(&rev.diffopt, path);
}
static void show_file(int pfx, struct cache_entry *ce)
{
- diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode),
+ diff_addremove(&rev.diffopt, pfx, ntohl(ce->ce_mode),
ce->sha1, ce->name, NULL);
}
const unsigned char *old_sha1, const unsigned char *sha1,
char *path)
{
- diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL);
+ diff_change(&rev.diffopt, oldmode, mode, old_sha1, sha1, path, NULL);
}
int main(int argc, const char **argv)
int entries, i;
git_config(git_diff_config);
- diff_setup(&diff_options);
+ diff_setup(&rev.diffopt);
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "--")) {
argv++;
dense_combined_merges = combine_merges = 1;
else {
int diff_opt_cnt;
- diff_opt_cnt = diff_opt_parse(&diff_options,
+ diff_opt_cnt = diff_opt_parse(&rev.diffopt,
argv+1, argc-1);
if (diff_opt_cnt < 0)
usage(diff_files_usage);
argv++; argc--;
}
if (dense_combined_merges)
- diff_options.output_format = DIFF_FORMAT_PATCH;
+ rev.diffopt.output_format = DIFF_FORMAT_PATCH;
/* Find the directory, and set up the pathspec */
pathspec = get_pathspec(prefix, argv + 1);
entries = read_cache();
- if (diff_setup_done(&diff_options) < 0)
+ if (diff_setup_done(&rev.diffopt) < 0)
usage(diff_files_usage);
/* At this point, if argc == 1, then we are doing everything.
if (combine_merges && num_compare_stages == 2) {
show_combined_diff(&combine.p, 2,
dense_combined_merges,
- NULL,
- &diff_options);
+ &rev);
free(combine.p.path);
continue;
}
continue;
}
changed = ce_match_stat(ce, &st, 0);
- if (!changed && !diff_options.find_copies_harder)
+ if (!changed && !rev.diffopt.find_copies_harder)
continue;
oldmode = ntohl(ce->ce_mode);
ce->sha1, (changed ? null_sha1 : ce->sha1),
ce->name);
}
- diffcore_std(&diff_options);
- diff_flush(&diff_options);
+ diffcore_std(&rev.diffopt);
+ diff_flush(&rev.diffopt);
return 0;
}
--- /dev/null
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "cache.h"
+#include "quote.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "xdiff-interface.h"
+
+static int use_size_cache;
+
+int diff_rename_limit_default = -1;
+
+int git_diff_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "diff.renamelimit")) {
+ diff_rename_limit_default = git_config_int(var, value);
+ return 0;
+ }
+
+ return git_default_config(var, value);
+}
+
+static char *quote_one(const char *str)
+{
+ int needlen;
+ char *xp;
+
+ if (!str)
+ return NULL;
+ needlen = quote_c_style(str, NULL, NULL, 0);
+ if (!needlen)
+ return strdup(str);
+ xp = xmalloc(needlen + 1);
+ quote_c_style(str, xp, NULL, 0);
+ return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+ int need_one = quote_c_style(one, NULL, NULL, 1);
+ int need_two = quote_c_style(two, NULL, NULL, 1);
+ char *xp;
+
+ if (need_one + need_two) {
+ if (!need_one) need_one = strlen(one);
+ if (!need_two) need_one = strlen(two);
+
+ xp = xmalloc(need_one + need_two + 3);
+ xp[0] = '"';
+ quote_c_style(one, xp + 1, NULL, 1);
+ quote_c_style(two, xp + need_one + 1, NULL, 1);
+ strcpy(xp + need_one + need_two + 1, "\"");
+ return xp;
+ }
+ need_one = strlen(one);
+ need_two = strlen(two);
+ xp = xmalloc(need_one + need_two + 1);
+ strcpy(xp, one);
+ strcpy(xp + need_one, two);
+ return xp;
+}
+
+static const char *external_diff(void)
+{
+ static const char *external_diff_cmd = NULL;
+ static int done_preparing = 0;
+
+ if (done_preparing)
+ return external_diff_cmd;
+ external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+ done_preparing = 1;
+ return external_diff_cmd;
+}
+
+#define TEMPFILE_PATH_LEN 50
+
+static struct diff_tempfile {
+ const char *name; /* filename external diff should read from */
+ char hex[41];
+ char mode[10];
+ char tmp_path[TEMPFILE_PATH_LEN];
+} diff_temp[2];
+
+static int count_lines(const char *data, int size)
+{
+ int count, ch, completely_empty = 1, nl_just_seen = 0;
+ count = 0;
+ while (0 < size--) {
+ ch = *data++;
+ if (ch == '\n') {
+ count++;
+ nl_just_seen = 1;
+ completely_empty = 0;
+ }
+ else {
+ nl_just_seen = 0;
+ completely_empty = 0;
+ }
+ }
+ if (completely_empty)
+ return 0;
+ if (!nl_just_seen)
+ count++; /* no trailing newline */
+ return count;
+}
+
+static void print_line_count(int count)
+{
+ switch (count) {
+ case 0:
+ printf("0,0");
+ break;
+ case 1:
+ printf("1");
+ break;
+ default:
+ printf("1,%d", count);
+ break;
+ }
+}
+
+static void copy_file(int prefix, const char *data, int size)
+{
+ int ch, nl_just_seen = 1;
+ while (0 < size--) {
+ ch = *data++;
+ if (nl_just_seen)
+ putchar(prefix);
+ putchar(ch);
+ if (ch == '\n')
+ nl_just_seen = 1;
+ else
+ nl_just_seen = 0;
+ }
+ if (!nl_just_seen)
+ printf("\n\\ No newline at end of file\n");
+}
+
+static void emit_rewrite_diff(const char *name_a,
+ const char *name_b,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ int lc_a, lc_b;
+ diff_populate_filespec(one, 0);
+ diff_populate_filespec(two, 0);
+ lc_a = count_lines(one->data, one->size);
+ lc_b = count_lines(two->data, two->size);
+ printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ print_line_count(lc_a);
+ printf(" +");
+ print_line_count(lc_b);
+ printf(" @@\n");
+ if (lc_a)
+ copy_file('-', one->data, one->size);
+ if (lc_b)
+ copy_file('+', two->data, two->size);
+}
+
+static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one)) {
+ mf->ptr = ""; /* does not matter */
+ mf->size = 0;
+ return 0;
+ }
+ else if (diff_populate_filespec(one, 0))
+ return -1;
+ mf->ptr = one->data;
+ mf->size = one->size;
+ return 0;
+}
+
+struct emit_callback {
+ const char **label_path;
+};
+
+static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+ int i;
+ struct emit_callback *ecbdata = priv;
+
+ if (ecbdata->label_path[0]) {
+ printf("--- %s\n", ecbdata->label_path[0]);
+ printf("+++ %s\n", ecbdata->label_path[1]);
+ ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
+ }
+ for (i = 0; i < nbuf; i++)
+ if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
+ return -1;
+ return 0;
+}
+
+struct diffstat_t {
+ struct xdiff_emit_state xm;
+
+ int nr;
+ int alloc;
+ struct diffstat_file {
+ char *name;
+ unsigned is_unmerged:1;
+ unsigned is_binary:1;
+ unsigned int added, deleted;
+ } **files;
+};
+
+static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
+ const char *name)
+{
+ struct diffstat_file *x;
+ x = xcalloc(sizeof (*x), 1);
+ if (diffstat->nr == diffstat->alloc) {
+ diffstat->alloc = alloc_nr(diffstat->alloc);
+ diffstat->files = xrealloc(diffstat->files,
+ diffstat->alloc * sizeof(x));
+ }
+ diffstat->files[diffstat->nr++] = x;
+ x->name = strdup(name);
+ return x;
+}
+
+static void diffstat_consume(void *priv, char *line, unsigned long len)
+{
+ struct diffstat_t *diffstat = priv;
+ struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+
+ if (line[0] == '+')
+ x->added++;
+ else if (line[0] == '-')
+ x->deleted++;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct diffstat_t* data)
+{
+ char *prefix = "";
+ int i, len, add, del, total, adds = 0, dels = 0;
+ int max, max_change = 0, max_len = 0;
+ int total_files = data->nr;
+
+ if (data->nr == 0)
+ return;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ len = strlen(file->name);
+ if (max_len < len)
+ max_len = len;
+
+ if (file->is_binary || file->is_unmerged)
+ continue;
+ if (max_change < file->added + file->deleted)
+ max_change = file->added + file->deleted;
+ }
+
+ for (i = 0; i < data->nr; i++) {
+ char *name = data->files[i]->name;
+ int added = data->files[i]->added;
+ int deleted = data->files[i]->deleted;
+
+ if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(name, qname, NULL, 0);
+ free(name);
+ data->files[i]->name = name = qname;
+ }
+
+ /*
+ * "scale" the filename
+ */
+ len = strlen(name);
+ max = max_len;
+ if (max > 50)
+ max = 50;
+ if (len > max) {
+ char *slash;
+ prefix = "...";
+ max -= 3;
+ name += len - max;
+ slash = strchr(name, '/');
+ if (slash)
+ name = slash;
+ }
+ len = max;
+
+ /*
+ * scale the add/delete
+ */
+ max = max_change;
+ if (max + len > 70)
+ max = 70 - len;
+
+ if (data->files[i]->is_binary) {
+ printf(" %s%-*s | Bin\n", prefix, len, name);
+ goto free_diffstat_file;
+ }
+ else if (data->files[i]->is_unmerged) {
+ printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ goto free_diffstat_file;
+ }
+ else if (added + deleted == 0) {
+ total_files--;
+ goto free_diffstat_file;
+ }
+
+ add = added;
+ del = deleted;
+ total = add + del;
+ adds += add;
+ dels += del;
+
+ if (max_change > 0) {
+ total = (total * max + max_change / 2) / max_change;
+ add = (add * max + max_change / 2) / max_change;
+ del = total - add;
+ }
+ printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+ len, name, added + deleted,
+ add, pluses, del, minuses);
+ free_diffstat_file:
+ free(data->files[i]->name);
+ free(data->files[i]);
+ }
+ free(data->files);
+ printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
+ total_files, adds, dels);
+}
+
+#define FIRST_FEW_BYTES 8000
+static int mmfile_is_binary(mmfile_t *mf)
+{
+ long sz = mf->size;
+ if (FIRST_FEW_BYTES < sz)
+ sz = FIRST_FEW_BYTES;
+ if (memchr(mf->ptr, 0, sz))
+ return 1;
+ return 0;
+}
+
+static void builtin_diff(const char *name_a,
+ const char *name_b,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ int complete_rewrite)
+{
+ mmfile_t mf1, mf2;
+ const char *lbl[2];
+ char *a_one, *b_two;
+
+ a_one = quote_two("a/", name_a);
+ b_two = quote_two("b/", name_b);
+ lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
+ lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+ printf("diff --git %s %s\n", a_one, b_two);
+ if (lbl[0][0] == '/') {
+ /* /dev/null */
+ printf("new file mode %06o\n", two->mode);
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ }
+ else if (lbl[1][0] == '/') {
+ printf("deleted file mode %06o\n", one->mode);
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ }
+ else {
+ if (one->mode != two->mode) {
+ printf("old mode %06o\n", one->mode);
+ printf("new mode %06o\n", two->mode);
+ }
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ /*
+ * we do not run diff between different kind
+ * of objects.
+ */
+ if ((one->mode ^ two->mode) & S_IFMT)
+ goto free_ab_and_return;
+ if (complete_rewrite) {
+ emit_rewrite_diff(name_a, name_b, one, two);
+ goto free_ab_and_return;
+ }
+ }
+
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
+ if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+ printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
+ else {
+ /* Crazy xdl interfaces.. */
+ const char *diffopts = getenv("GIT_DIFF_OPTS");
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+ struct emit_callback ecbdata;
+
+ ecbdata.label_path = lbl;
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 3;
+ xecfg.flags = XDL_EMIT_FUNCNAMES;
+ if (!diffopts)
+ ;
+ else if (!strncmp(diffopts, "--unified=", 10))
+ xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
+ else if (!strncmp(diffopts, "-u", 2))
+ xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
+ ecb.outf = fn_out;
+ ecb.priv = &ecbdata;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
+
+ free_ab_and_return:
+ free(a_one);
+ free(b_two);
+ return;
+}
+
+static void builtin_diffstat(const char *name_a, const char *name_b,
+ struct diff_filespec *one, struct diff_filespec *two,
+ struct diffstat_t *diffstat)
+{
+ mmfile_t mf1, mf2;
+ struct diffstat_file *data;
+
+ data = diffstat_add(diffstat, name_a ? name_a : name_b);
+
+ if (!one || !two) {
+ data->is_unmerged = 1;
+ return;
+ }
+
+ if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+ die("unable to read files to diff");
+
+ if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+ data->is_binary = 1;
+ else {
+ /* Crazy xdl interfaces.. */
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ xdemitcb_t ecb;
+
+ xpp.flags = XDF_NEED_MINIMAL;
+ xecfg.ctxlen = 0;
+ xecfg.flags = 0;
+ ecb.outf = xdiff_outf;
+ ecb.priv = diffstat;
+ xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+ }
+}
+
+struct diff_filespec *alloc_filespec(const char *path)
+{
+ int namelen = strlen(path);
+ struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+
+ memset(spec, 0, sizeof(*spec));
+ spec->path = (char *)(spec + 1);
+ memcpy(spec->path, path, namelen+1);
+ return spec;
+}
+
+void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
+ unsigned short mode)
+{
+ if (mode) {
+ spec->mode = canon_mode(mode);
+ memcpy(spec->sha1, sha1, 20);
+ spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+ }
+}
+
+/*
+ * Given a name and sha1 pair, if the dircache tells us the file in
+ * the work tree has that object contents, return true, so that
+ * prepare_temp_file() does not have to inflate and extract.
+ */
+static int work_tree_matches(const char *name, const unsigned char *sha1)
+{
+ struct cache_entry *ce;
+ struct stat st;
+ int pos, len;
+
+ /* We do not read the cache ourselves here, because the
+ * benchmark with my previous version that always reads cache
+ * shows that it makes things worse for diff-tree comparing
+ * two linux-2.6 kernel trees in an already checked out work
+ * tree. This is because most diff-tree comparisons deal with
+ * only a small number of files, while reading the cache is
+ * expensive for a large project, and its cost outweighs the
+ * savings we get by not inflating the object to a temporary
+ * file. Practically, this code only helps when we are used
+ * by diff-cache --cached, which does read the cache before
+ * calling us.
+ */
+ if (!active_cache)
+ return 0;
+
+ len = strlen(name);
+ pos = cache_name_pos(name, len);
+ if (pos < 0)
+ return 0;
+ ce = active_cache[pos];
+ if ((lstat(name, &st) < 0) ||
+ !S_ISREG(st.st_mode) || /* careful! */
+ ce_match_stat(ce, &st, 0) ||
+ memcmp(sha1, ce->sha1, 20))
+ return 0;
+ /* we return 1 only when we can stat, it is a regular file,
+ * stat information matches, and sha1 recorded in the cache
+ * matches. I.e. we know the file in the work tree really is
+ * the same as the <name, sha1> pair.
+ */
+ return 1;
+}
+
+static struct sha1_size_cache {
+ unsigned char sha1[20];
+ unsigned long size;
+} **sha1_size_cache;
+static int sha1_size_cache_nr, sha1_size_cache_alloc;
+
+static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
+ int find_only,
+ unsigned long size)
+{
+ int first, last;
+ struct sha1_size_cache *e;
+
+ first = 0;
+ last = sha1_size_cache_nr;
+ while (last > first) {
+ int cmp, next = (last + first) >> 1;
+ e = sha1_size_cache[next];
+ cmp = memcmp(e->sha1, sha1, 20);
+ if (!cmp)
+ return e;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ /* not found */
+ if (find_only)
+ return NULL;
+ /* insert to make it at "first" */
+ if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
+ sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
+ sha1_size_cache = xrealloc(sha1_size_cache,
+ sha1_size_cache_alloc *
+ sizeof(*sha1_size_cache));
+ }
+ sha1_size_cache_nr++;
+ if (first < sha1_size_cache_nr)
+ memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
+ (sha1_size_cache_nr - first - 1) *
+ sizeof(*sha1_size_cache));
+ e = xmalloc(sizeof(struct sha1_size_cache));
+ sha1_size_cache[first] = e;
+ memcpy(e->sha1, sha1, 20);
+ e->size = size;
+ return e;
+}
+
+/*
+ * While doing rename detection and pickaxe operation, we may need to
+ * grab the data for the blob (or file) for our own in-core comparison.
+ * diff_filespec has data and size fields for this purpose.
+ */
+int diff_populate_filespec(struct diff_filespec *s, int size_only)
+{
+ int err = 0;
+ if (!DIFF_FILE_VALID(s))
+ die("internal error: asking to populate invalid file.");
+ if (S_ISDIR(s->mode))
+ return -1;
+
+ if (!use_size_cache)
+ size_only = 0;
+
+ if (s->data)
+ return err;
+ if (!s->sha1_valid ||
+ work_tree_matches(s->path, s->sha1)) {
+ struct stat st;
+ int fd;
+ if (lstat(s->path, &st) < 0) {
+ if (errno == ENOENT) {
+ err_empty:
+ err = -1;
+ empty:
+ s->data = "";
+ s->size = 0;
+ return err;
+ }
+ }
+ s->size = st.st_size;
+ if (!s->size)
+ goto empty;
+ if (size_only)
+ return 0;
+ if (S_ISLNK(st.st_mode)) {
+ int ret;
+ s->data = xmalloc(s->size);
+ s->should_free = 1;
+ ret = readlink(s->path, s->data, s->size);
+ if (ret < 0) {
+ free(s->data);
+ goto err_empty;
+ }
+ return 0;
+ }
+ fd = open(s->path, O_RDONLY);
+ if (fd < 0)
+ goto err_empty;
+ s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (s->data == MAP_FAILED)
+ goto err_empty;
+ s->should_munmap = 1;
+ }
+ else {
+ char type[20];
+ struct sha1_size_cache *e;
+
+ if (size_only) {
+ e = locate_size_cache(s->sha1, 1, 0);
+ if (e) {
+ s->size = e->size;
+ return 0;
+ }
+ if (!sha1_object_info(s->sha1, type, &s->size))
+ locate_size_cache(s->sha1, 0, s->size);
+ }
+ else {
+ s->data = read_sha1_file(s->sha1, type, &s->size);
+ s->should_free = 1;
+ }
+ }
+ return 0;
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+ if (s->should_free)
+ free(s->data);
+ else if (s->should_munmap)
+ munmap(s->data, s->size);
+ s->should_free = s->should_munmap = 0;
+ s->data = NULL;
+ free(s->cnt_data);
+ s->cnt_data = NULL;
+}
+
+static void prep_temp_blob(struct diff_tempfile *temp,
+ void *blob,
+ unsigned long size,
+ const unsigned char *sha1,
+ int mode)
+{
+ int fd;
+
+ fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
+ if (fd < 0)
+ die("unable to create temp-file");
+ if (write(fd, blob, size) != size)
+ die("unable to write temp-file");
+ close(fd);
+ temp->name = temp->tmp_path;
+ strcpy(temp->hex, sha1_to_hex(sha1));
+ temp->hex[40] = 0;
+ sprintf(temp->mode, "%06o", mode);
+}
+
+static void prepare_temp_file(const char *name,
+ struct diff_tempfile *temp,
+ struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one)) {
+ not_a_valid_file:
+ /* A '-' entry produces this for file-2, and
+ * a '+' entry produces this for file-1.
+ */
+ temp->name = "/dev/null";
+ strcpy(temp->hex, ".");
+ strcpy(temp->mode, ".");
+ return;
+ }
+
+ if (!one->sha1_valid ||
+ work_tree_matches(name, one->sha1)) {
+ struct stat st;
+ if (lstat(name, &st) < 0) {
+ if (errno == ENOENT)
+ goto not_a_valid_file;
+ die("stat(%s): %s", name, strerror(errno));
+ }
+ if (S_ISLNK(st.st_mode)) {
+ int ret;
+ char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
+ if (sizeof(buf) <= st.st_size)
+ die("symlink too long: %s", name);
+ ret = readlink(name, buf, st.st_size);
+ if (ret < 0)
+ die("readlink(%s)", name);
+ prep_temp_blob(temp, buf, st.st_size,
+ (one->sha1_valid ?
+ one->sha1 : null_sha1),
+ (one->sha1_valid ?
+ one->mode : S_IFLNK));
+ }
+ else {
+ /* we can borrow from the file in the work tree */
+ temp->name = name;
+ if (!one->sha1_valid)
+ strcpy(temp->hex, sha1_to_hex(null_sha1));
+ else
+ strcpy(temp->hex, sha1_to_hex(one->sha1));
+ /* Even though we may sometimes borrow the
+ * contents from the work tree, we always want
+ * one->mode. mode is trustworthy even when
+ * !(one->sha1_valid), as long as
+ * DIFF_FILE_VALID(one).
+ */
+ sprintf(temp->mode, "%06o", one->mode);
+ }
+ return;
+ }
+ else {
+ if (diff_populate_filespec(one, 0))
+ die("cannot read data blob for %s", one->path);
+ prep_temp_blob(temp, one->data, one->size,
+ one->sha1, one->mode);
+ }
+}
+
+static void remove_tempfile(void)
+{
+ int i;
+
+ for (i = 0; i < 2; i++)
+ if (diff_temp[i].name == diff_temp[i].tmp_path) {
+ unlink(diff_temp[i].name);
+ diff_temp[i].name = NULL;
+ }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+ remove_tempfile();
+ signal(SIGINT, SIG_DFL);
+ raise(signo);
+}
+
+static int spawn_prog(const char *pgm, const char **arg)
+{
+ pid_t pid;
+ int status;
+
+ fflush(NULL);
+ pid = fork();
+ if (pid < 0)
+ die("unable to fork");
+ if (!pid) {
+ execvp(pgm, (char *const*) arg);
+ exit(255);
+ }
+
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+
+ /* Earlier we did not check the exit status because
+ * diff exits non-zero if files are different, and
+ * we are not interested in knowing that. It was a
+ * mistake which made it harder to quit a diff-*
+ * session that uses the git-apply-patch-script as
+ * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
+ * should also exit non-zero only when it wants to
+ * abort the entire diff-* session.
+ */
+ if (WIFEXITED(status) && !WEXITSTATUS(status))
+ return 0;
+ return -1;
+}
+
+/* An external diff command takes:
+ *
+ * diff-cmd name infile1 infile1-sha1 infile1-mode \
+ * infile2 infile2-sha1 infile2-mode [ rename-to ]
+ *
+ */
+static void run_external_diff(const char *pgm,
+ const char *name,
+ const char *other,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ int complete_rewrite)
+{
+ const char *spawn_arg[10];
+ struct diff_tempfile *temp = diff_temp;
+ int retval;
+ static int atexit_asked = 0;
+ const char *othername;
+ const char **arg = &spawn_arg[0];
+
+ othername = (other? other : name);
+ if (one && two) {
+ prepare_temp_file(name, &temp[0], one);
+ prepare_temp_file(othername, &temp[1], two);
+ if (! atexit_asked &&
+ (temp[0].name == temp[0].tmp_path ||
+ temp[1].name == temp[1].tmp_path)) {
+ atexit_asked = 1;
+ atexit(remove_tempfile);
+ }
+ signal(SIGINT, remove_tempfile_on_signal);
+ }
+
+ if (one && two) {
+ *arg++ = pgm;
+ *arg++ = name;
+ *arg++ = temp[0].name;
+ *arg++ = temp[0].hex;
+ *arg++ = temp[0].mode;
+ *arg++ = temp[1].name;
+ *arg++ = temp[1].hex;
+ *arg++ = temp[1].mode;
+ if (other) {
+ *arg++ = other;
+ *arg++ = xfrm_msg;
+ }
+ } else {
+ *arg++ = pgm;
+ *arg++ = name;
+ }
+ *arg = NULL;
+ retval = spawn_prog(pgm, spawn_arg);
+ remove_tempfile();
+ if (retval) {
+ fprintf(stderr, "external diff died, stopping at %s.\n", name);
+ exit(1);
+ }
+}
+
+static void run_diff_cmd(const char *pgm,
+ const char *name,
+ const char *other,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ int complete_rewrite)
+{
+ if (pgm) {
+ run_external_diff(pgm, name, other, one, two, xfrm_msg,
+ complete_rewrite);
+ return;
+ }
+ if (one && two)
+ builtin_diff(name, other ? other : name,
+ one, two, xfrm_msg, complete_rewrite);
+ else
+ printf("* Unmerged path %s\n", name);
+}
+
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+ if (DIFF_FILE_VALID(one)) {
+ if (!one->sha1_valid) {
+ struct stat st;
+ if (lstat(one->path, &st) < 0)
+ die("stat %s", one->path);
+ if (index_path(one->sha1, one->path, &st, 0))
+ die("cannot hash %s\n", one->path);
+ }
+ }
+ else
+ memset(one->sha1, 0, 20);
+}
+
+static void run_diff(struct diff_filepair *p, struct diff_options *o)
+{
+ const char *pgm = external_diff();
+ char msg[PATH_MAX*2+300], *xfrm_msg;
+ struct diff_filespec *one;
+ struct diff_filespec *two;
+ const char *name;
+ const char *other;
+ char *name_munged, *other_munged;
+ int complete_rewrite = 0;
+ int len;
+
+ if (DIFF_PAIR_UNMERGED(p)) {
+ /* unmerged */
+ run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
+ return;
+ }
+
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+ name_munged = quote_one(name);
+ other_munged = quote_one(other);
+ one = p->one; two = p->two;
+
+ diff_fill_sha1_info(one);
+ diff_fill_sha1_info(two);
+
+ len = 0;
+ switch (p->status) {
+ case DIFF_STATUS_COPIED:
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "similarity index %d%%\n"
+ "copy from %s\n"
+ "copy to %s\n",
+ (int)(0.5 + p->score * 100.0/MAX_SCORE),
+ name_munged, other_munged);
+ break;
+ case DIFF_STATUS_RENAMED:
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "similarity index %d%%\n"
+ "rename from %s\n"
+ "rename to %s\n",
+ (int)(0.5 + p->score * 100.0/MAX_SCORE),
+ name_munged, other_munged);
+ break;
+ case DIFF_STATUS_MODIFIED:
+ if (p->score) {
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "dissimilarity index %d%%\n",
+ (int)(0.5 + p->score *
+ 100.0/MAX_SCORE));
+ complete_rewrite = 1;
+ break;
+ }
+ /* fallthru */
+ default:
+ /* nothing */
+ ;
+ }
+
+ if (memcmp(one->sha1, two->sha1, 20)) {
+ char one_sha1[41];
+ int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+ memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
+
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "index %.*s..%.*s",
+ abbrev, one_sha1, abbrev,
+ sha1_to_hex(two->sha1));
+ if (one->mode == two->mode)
+ len += snprintf(msg + len, sizeof(msg) - len,
+ " %06o", one->mode);
+ len += snprintf(msg + len, sizeof(msg) - len, "\n");
+ }
+
+ if (len)
+ msg[--len] = 0;
+ xfrm_msg = len ? msg : NULL;
+
+ if (!pgm &&
+ DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
+ (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
+ /* a filepair that changes between file and symlink
+ * needs to be split into deletion and creation.
+ */
+ struct diff_filespec *null = alloc_filespec(two->path);
+ run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
+ free(null);
+ null = alloc_filespec(one->path);
+ run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
+ free(null);
+ }
+ else
+ run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
+ complete_rewrite);
+
+ free(name_munged);
+ free(other_munged);
+}
+
+static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
+ struct diffstat_t *diffstat)
+{
+ const char *name;
+ const char *other;
+
+ if (DIFF_PAIR_UNMERGED(p)) {
+ /* unmerged */
+ builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
+ return;
+ }
+
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+ diff_fill_sha1_info(p->one);
+ diff_fill_sha1_info(p->two);
+
+ builtin_diffstat(name, other, p->one, p->two, diffstat);
+}
+
+void diff_setup(struct diff_options *options)
+{
+ memset(options, 0, sizeof(*options));
+ options->output_format = DIFF_FORMAT_RAW;
+ options->line_termination = '\n';
+ options->break_opt = -1;
+ options->rename_limit = -1;
+
+ options->change = diff_change;
+ options->add_remove = diff_addremove;
+}
+
+int diff_setup_done(struct diff_options *options)
+{
+ if ((options->find_copies_harder &&
+ options->detect_rename != DIFF_DETECT_COPY) ||
+ (0 <= options->rename_limit && !options->detect_rename))
+ return -1;
+
+ /*
+ * These cases always need recursive; we do not drop caller-supplied
+ * recursive bits for other formats here.
+ */
+ if ((options->output_format == DIFF_FORMAT_PATCH) ||
+ (options->output_format == DIFF_FORMAT_DIFFSTAT))
+ options->recursive = 1;
+
+ if (options->detect_rename && options->rename_limit < 0)
+ options->rename_limit = diff_rename_limit_default;
+ if (options->setup & DIFF_SETUP_USE_CACHE) {
+ if (!active_cache)
+ /* read-cache does not die even when it fails
+ * so it is safe for us to do this here. Also
+ * it does not smudge active_cache or active_nr
+ * when it fails, so we do not have to worry about
+ * cleaning it up ourselves either.
+ */
+ read_cache();
+ }
+ if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
+ use_size_cache = 1;
+ if (options->abbrev <= 0 || 40 < options->abbrev)
+ options->abbrev = 40; /* full */
+
+ return 0;
+}
+
+int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+{
+ const char *arg = av[0];
+ if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+ options->output_format = DIFF_FORMAT_PATCH;
+ else if (!strcmp(arg, "--patch-with-raw")) {
+ options->output_format = DIFF_FORMAT_PATCH;
+ options->with_raw = 1;
+ }
+ else if (!strcmp(arg, "--stat"))
+ options->output_format = DIFF_FORMAT_DIFFSTAT;
+ else if (!strcmp(arg, "--patch-with-stat")) {
+ options->output_format = DIFF_FORMAT_PATCH;
+ options->with_stat = 1;
+ }
+ else if (!strcmp(arg, "-z"))
+ options->line_termination = 0;
+ else if (!strncmp(arg, "-l", 2))
+ options->rename_limit = strtoul(arg+2, NULL, 10);
+ else if (!strcmp(arg, "--full-index"))
+ options->full_index = 1;
+ else if (!strcmp(arg, "--name-only"))
+ options->output_format = DIFF_FORMAT_NAME;
+ else if (!strcmp(arg, "--name-status"))
+ options->output_format = DIFF_FORMAT_NAME_STATUS;
+ else if (!strcmp(arg, "-R"))
+ options->reverse_diff = 1;
+ else if (!strncmp(arg, "-S", 2))
+ options->pickaxe = arg + 2;
+ else if (!strcmp(arg, "-s"))
+ options->output_format = DIFF_FORMAT_NO_OUTPUT;
+ else if (!strncmp(arg, "-O", 2))
+ options->orderfile = arg + 2;
+ else if (!strncmp(arg, "--diff-filter=", 14))
+ options->filter = arg + 14;
+ else if (!strcmp(arg, "--pickaxe-all"))
+ options->pickaxe_opts = DIFF_PICKAXE_ALL;
+ else if (!strcmp(arg, "--pickaxe-regex"))
+ options->pickaxe_opts = DIFF_PICKAXE_REGEX;
+ else if (!strncmp(arg, "-B", 2)) {
+ if ((options->break_opt =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ }
+ else if (!strncmp(arg, "-M", 2)) {
+ if ((options->rename_score =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ options->detect_rename = DIFF_DETECT_RENAME;
+ }
+ else if (!strncmp(arg, "-C", 2)) {
+ if ((options->rename_score =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ options->detect_rename = DIFF_DETECT_COPY;
+ }
+ else if (!strcmp(arg, "--find-copies-harder"))
+ options->find_copies_harder = 1;
+ else if (!strcmp(arg, "--abbrev"))
+ options->abbrev = DEFAULT_ABBREV;
+ else if (!strncmp(arg, "--abbrev=", 9)) {
+ options->abbrev = strtoul(arg + 9, NULL, 10);
+ if (options->abbrev < MINIMUM_ABBREV)
+ options->abbrev = MINIMUM_ABBREV;
+ else if (40 < options->abbrev)
+ options->abbrev = 40;
+ }
+ else
+ return 0;
+ return 1;
+}
+
+static int parse_num(const char **cp_p)
+{
+ unsigned long num, scale;
+ int ch, dot;
+ const char *cp = *cp_p;
+
+ num = 0;
+ scale = 1;
+ dot = 0;
+ for(;;) {
+ ch = *cp;
+ if ( !dot && ch == '.' ) {
+ scale = 1;
+ dot = 1;
+ } else if ( ch == '%' ) {
+ scale = dot ? scale*100 : 100;
+ cp++; /* % is always at the end */
+ break;
+ } else if ( ch >= '0' && ch <= '9' ) {
+ if ( scale < 100000 ) {
+ scale *= 10;
+ num = (num*10) + (ch-'0');
+ }
+ } else {
+ break;
+ }
+ cp++;
+ }
+ *cp_p = cp;
+
+ /* user says num divided by scale and we say internally that
+ * is MAX_SCORE * num / scale.
+ */
+ return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
+}
+
+int diff_scoreopt_parse(const char *opt)
+{
+ int opt1, opt2, cmd;
+
+ if (*opt++ != '-')
+ return -1;
+ cmd = *opt++;
+ if (cmd != 'M' && cmd != 'C' && cmd != 'B')
+ return -1; /* that is not a -M, -C nor -B option */
+
+ opt1 = parse_num(&opt);
+ if (cmd != 'B')
+ opt2 = 0;
+ else {
+ if (*opt == 0)
+ opt2 = 0;
+ else if (*opt != '/')
+ return -1; /* we expect -B80/99 or -B80 */
+ else {
+ opt++;
+ opt2 = parse_num(&opt);
+ }
+ }
+ if (*opt != 0)
+ return -1;
+ return opt1 | (opt2 << 16);
+}
+
+struct diff_queue_struct diff_queued_diff;
+
+void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
+{
+ if (queue->alloc <= queue->nr) {
+ queue->alloc = alloc_nr(queue->alloc);
+ queue->queue = xrealloc(queue->queue,
+ sizeof(dp) * queue->alloc);
+ }
+ queue->queue[queue->nr++] = dp;
+}
+
+struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ struct diff_filepair *dp = xmalloc(sizeof(*dp));
+ dp->one = one;
+ dp->two = two;
+ dp->score = 0;
+ dp->status = 0;
+ dp->source_stays = 0;
+ dp->broken_pair = 0;
+ if (queue)
+ diff_q(queue, dp);
+ return dp;
+}
+
+void diff_free_filepair(struct diff_filepair *p)
+{
+ diff_free_filespec_data(p->one);
+ diff_free_filespec_data(p->two);
+ free(p->one);
+ free(p->two);
+ free(p);
+}
+
+/* This is different from find_unique_abbrev() in that
+ * it stuffs the result with dots for alignment.
+ */
+const char *diff_unique_abbrev(const unsigned char *sha1, int len)
+{
+ int abblen;
+ const char *abbrev;
+ if (len == 40)
+ return sha1_to_hex(sha1);
+
+ abbrev = find_unique_abbrev(sha1, len);
+ if (!abbrev)
+ return sha1_to_hex(sha1);
+ abblen = strlen(abbrev);
+ if (abblen < 37) {
+ static char hex[41];
+ if (len < abblen && abblen <= len + 2)
+ sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+ else
+ sprintf(hex, "%s...", abbrev);
+ return hex;
+ }
+ return sha1_to_hex(sha1);
+}
+
+static void diff_flush_raw(struct diff_filepair *p,
+ int line_termination,
+ int inter_name_termination,
+ struct diff_options *options,
+ int output_format)
+{
+ int two_paths;
+ char status[10];
+ int abbrev = options->abbrev;
+ const char *path_one, *path_two;
+
+ path_one = p->one->path;
+ path_two = p->two->path;
+ if (line_termination) {
+ path_one = quote_one(path_one);
+ path_two = quote_one(path_two);
+ }
+
+ if (p->score)
+ sprintf(status, "%c%03d", p->status,
+ (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ else {
+ status[0] = p->status;
+ status[1] = 0;
+ }
+ switch (p->status) {
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ two_paths = 1;
+ break;
+ case DIFF_STATUS_ADDED:
+ case DIFF_STATUS_DELETED:
+ two_paths = 0;
+ break;
+ default:
+ two_paths = 0;
+ break;
+ }
+ if (output_format != DIFF_FORMAT_NAME_STATUS) {
+ printf(":%06o %06o %s ",
+ p->one->mode, p->two->mode,
+ diff_unique_abbrev(p->one->sha1, abbrev));
+ printf("%s ",
+ diff_unique_abbrev(p->two->sha1, abbrev));
+ }
+ printf("%s%c%s", status, inter_name_termination, path_one);
+ if (two_paths)
+ printf("%c%s", inter_name_termination, path_two);
+ putchar(line_termination);
+ if (path_one != p->one->path)
+ free((void*)path_one);
+ if (path_two != p->two->path)
+ free((void*)path_two);
+}
+
+static void diff_flush_name(struct diff_filepair *p,
+ int inter_name_termination,
+ int line_termination)
+{
+ char *path = p->two->path;
+
+ if (line_termination)
+ path = quote_one(p->two->path);
+ else
+ path = p->two->path;
+ printf("%s%c", path, line_termination);
+ if (p->two->path != path)
+ free(path);
+}
+
+int diff_unmodified_pair(struct diff_filepair *p)
+{
+ /* This function is written stricter than necessary to support
+ * the currently implemented transformers, but the idea is to
+ * let transformers to produce diff_filepairs any way they want,
+ * and filter and clean them up here before producing the output.
+ */
+ struct diff_filespec *one, *two;
+
+ if (DIFF_PAIR_UNMERGED(p))
+ return 0; /* unmerged is interesting */
+
+ one = p->one;
+ two = p->two;
+
+ /* deletion, addition, mode or type change
+ * and rename are all interesting.
+ */
+ if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
+ DIFF_PAIR_MODE_CHANGED(p) ||
+ strcmp(one->path, two->path))
+ return 0;
+
+ /* both are valid and point at the same path. that is, we are
+ * dealing with a change.
+ */
+ if (one->sha1_valid && two->sha1_valid &&
+ !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+ return 1; /* no change */
+ if (!one->sha1_valid && !two->sha1_valid)
+ return 1; /* both look at the same file on the filesystem. */
+ return 0;
+}
+
+static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
+{
+ if (diff_unmodified_pair(p))
+ return;
+
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ return; /* no tree diffs in patch format */
+
+ run_diff(p, o);
+}
+
+static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
+ struct diffstat_t *diffstat)
+{
+ if (diff_unmodified_pair(p))
+ return;
+
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ return; /* no tree diffs in patch format */
+
+ run_diffstat(p, o, diffstat);
+}
+
+int diff_queue_is_empty(void)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ for (i = 0; i < q->nr; i++)
+ if (!diff_unmodified_pair(q->queue[i]))
+ return 0;
+ return 1;
+}
+
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
+{
+ fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
+ x, one ? one : "",
+ s->path,
+ DIFF_FILE_VALID(s) ? "valid" : "invalid",
+ s->mode,
+ s->sha1_valid ? sha1_to_hex(s->sha1) : "");
+ fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+ x, one ? one : "",
+ s->size, s->xfrm_flags);
+}
+
+void diff_debug_filepair(const struct diff_filepair *p, int i)
+{
+ diff_debug_filespec(p->one, i, "one");
+ diff_debug_filespec(p->two, i, "two");
+ fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+ p->score, p->status ? p->status : '?',
+ p->source_stays, p->broken_pair);
+}
+
+void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
+{
+ int i;
+ if (msg)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "q->nr = %d\n", q->nr);
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ diff_debug_filepair(p, i);
+ }
+}
+#endif
+
+static void diff_resolve_rename_copy(void)
+{
+ int i, j;
+ struct diff_filepair *p, *pp;
+ struct diff_queue_struct *q = &diff_queued_diff;
+
+ diff_debug_queue("resolve-rename-copy", q);
+
+ for (i = 0; i < q->nr; i++) {
+ p = q->queue[i];
+ p->status = 0; /* undecided */
+ if (DIFF_PAIR_UNMERGED(p))
+ p->status = DIFF_STATUS_UNMERGED;
+ else if (!DIFF_FILE_VALID(p->one))
+ p->status = DIFF_STATUS_ADDED;
+ else if (!DIFF_FILE_VALID(p->two))
+ p->status = DIFF_STATUS_DELETED;
+ else if (DIFF_PAIR_TYPE_CHANGED(p))
+ p->status = DIFF_STATUS_TYPE_CHANGED;
+
+ /* from this point on, we are dealing with a pair
+ * whose both sides are valid and of the same type, i.e.
+ * either in-place edit or rename/copy edit.
+ */
+ else if (DIFF_PAIR_RENAME(p)) {
+ if (p->source_stays) {
+ p->status = DIFF_STATUS_COPIED;
+ continue;
+ }
+ /* See if there is some other filepair that
+ * copies from the same source as us. If so
+ * we are a copy. Otherwise we are either a
+ * copy if the path stays, or a rename if it
+ * does not, but we already handled "stays" case.
+ */
+ for (j = i + 1; j < q->nr; j++) {
+ pp = q->queue[j];
+ if (strcmp(pp->one->path, p->one->path))
+ continue; /* not us */
+ if (!DIFF_PAIR_RENAME(pp))
+ continue; /* not a rename/copy */
+ /* pp is a rename/copy from the same source */
+ p->status = DIFF_STATUS_COPIED;
+ break;
+ }
+ if (!p->status)
+ p->status = DIFF_STATUS_RENAMED;
+ }
+ else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+ p->one->mode != p->two->mode)
+ p->status = DIFF_STATUS_MODIFIED;
+ else {
+ /* This is a "no-change" entry and should not
+ * happen anymore, but prepare for broken callers.
+ */
+ error("feeding unmodified %s to diffcore",
+ p->one->path);
+ p->status = DIFF_STATUS_UNKNOWN;
+ }
+ }
+ diff_debug_queue("resolve-rename-copy done", q);
+}
+
+static void flush_one_pair(struct diff_filepair *p,
+ int diff_output_format,
+ struct diff_options *options,
+ struct diffstat_t *diffstat)
+{
+ int inter_name_termination = '\t';
+ int line_termination = options->line_termination;
+ if (!line_termination)
+ inter_name_termination = 0;
+
+ switch (p->status) {
+ case DIFF_STATUS_UNKNOWN:
+ break;
+ case 0:
+ die("internal error in diff-resolve-rename-copy");
+ break;
+ default:
+ switch (diff_output_format) {
+ case DIFF_FORMAT_DIFFSTAT:
+ diff_flush_stat(p, options, diffstat);
+ break;
+ case DIFF_FORMAT_PATCH:
+ diff_flush_patch(p, options);
+ break;
+ case DIFF_FORMAT_RAW:
+ case DIFF_FORMAT_NAME_STATUS:
+ diff_flush_raw(p, line_termination,
+ inter_name_termination,
+ options, diff_output_format);
+ break;
+ case DIFF_FORMAT_NAME:
+ diff_flush_name(p,
+ inter_name_termination,
+ line_termination);
+ break;
+ case DIFF_FORMAT_NO_OUTPUT:
+ break;
+ }
+ }
+}
+
+void diff_flush(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ int diff_output_format = options->output_format;
+ struct diffstat_t *diffstat = NULL;
+
+ if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
+ diffstat = xcalloc(sizeof (struct diffstat_t), 1);
+ diffstat->xm.consume = diffstat_consume;
+ }
+
+ if (options->with_raw) {
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+ }
+ putchar(options->line_termination);
+ }
+ if (options->with_stat) {
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
+ diffstat);
+ }
+ show_stats(diffstat);
+ free(diffstat);
+ diffstat = NULL;
+ putchar(options->line_termination);
+ }
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ flush_one_pair(p, diff_output_format, options, diffstat);
+ diff_free_filepair(p);
+ }
+
+ if (diffstat) {
+ show_stats(diffstat);
+ free(diffstat);
+ }
+
+ free(q->queue);
+ q->queue = NULL;
+ q->nr = q->alloc = 0;
+}
+
+static void diffcore_apply_filter(const char *filter)
+{
+ int i;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq;
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ if (!filter)
+ return;
+
+ if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+ int found;
+ for (i = found = 0; !found && i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (((p->status == DIFF_STATUS_MODIFIED) &&
+ ((p->score &&
+ strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+ (!p->score &&
+ strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+ ((p->status != DIFF_STATUS_MODIFIED) &&
+ strchr(filter, p->status)))
+ found++;
+ }
+ if (found)
+ return;
+
+ /* otherwise we will clear the whole queue
+ * by copying the empty outq at the end of this
+ * function, but first clear the current entries
+ * in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ }
+ else {
+ /* Only the matching ones */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+
+ if (((p->status == DIFF_STATUS_MODIFIED) &&
+ ((p->score &&
+ strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+ (!p->score &&
+ strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+ ((p->status != DIFF_STATUS_MODIFIED) &&
+ strchr(filter, p->status)))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ }
+ free(q->queue);
+ *q = outq;
+}
+
+void diffcore_std(struct diff_options *options)
+{
+ if (options->break_opt != -1)
+ diffcore_break(options->break_opt);
+ if (options->detect_rename)
+ diffcore_rename(options);
+ if (options->break_opt != -1)
+ diffcore_merge_broken();
+ if (options->pickaxe)
+ diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ if (options->orderfile)
+ diffcore_order(options->orderfile);
+ diff_resolve_rename_copy();
+ diffcore_apply_filter(options->filter);
+}
+
+
+void diffcore_std_no_resolve(struct diff_options *options)
+{
+ if (options->pickaxe)
+ diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ if (options->orderfile)
+ diffcore_order(options->orderfile);
+ diffcore_apply_filter(options->filter);
+}
+
+void diff_addremove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ char concatpath[PATH_MAX];
+ struct diff_filespec *one, *two;
+
+ /* This may look odd, but it is a preparation for
+ * feeding "there are unchanged files which should
+ * not produce diffs, but when you are doing copy
+ * detection you would need them, so here they are"
+ * entries to the diff-core. They will be prefixed
+ * with something like '=' or '*' (I haven't decided
+ * which but should not make any difference).
+ * Feeding the same new and old to diff_change()
+ * also has the same effect.
+ * Before the final output happens, they are pruned after
+ * merged into rename/copy pairs as appropriate.
+ */
+ if (options->reverse_diff)
+ addremove = (addremove == '+' ? '-' :
+ addremove == '-' ? '+' : addremove);
+
+ if (!path) path = "";
+ sprintf(concatpath, "%s%s", base, path);
+ one = alloc_filespec(concatpath);
+ two = alloc_filespec(concatpath);
+
+ if (addremove != '+')
+ fill_filespec(one, sha1, mode);
+ if (addremove != '-')
+ fill_filespec(two, sha1, mode);
+
+ diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ char concatpath[PATH_MAX];
+ struct diff_filespec *one, *two;
+
+ if (options->reverse_diff) {
+ unsigned tmp;
+ const unsigned char *tmp_c;
+ tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+ tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+ }
+ if (!path) path = "";
+ sprintf(concatpath, "%s%s", base, path);
+ one = alloc_filespec(concatpath);
+ two = alloc_filespec(concatpath);
+ fill_filespec(one, old_sha1, old_mode);
+ fill_filespec(two, new_sha1, new_mode);
+
+ diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_unmerge(struct diff_options *options,
+ const char *path)
+{
+ struct diff_filespec *one, *two;
+ one = alloc_filespec(path);
+ two = alloc_filespec(path);
+ diff_queue(&diff_queued_diff, one, two);
+}
#include "commit.h"
#include "log-tree.h"
-static struct log_tree_opt log_tree_opt;
+static struct rev_info log_tree_opt;
static int diff_tree_commit_sha1(const unsigned char *sha1)
{
{
int nr_sha1;
char line[1000];
- unsigned char sha1[2][20];
- const char *prefix = setup_git_directory();
- static struct log_tree_opt *opt = &log_tree_opt;
+ struct object *tree1, *tree2;
+ static struct rev_info *opt = &log_tree_opt;
+ struct object_list *list;
int read_stdin = 0;
git_config(git_diff_config);
nr_sha1 = 0;
- init_log_tree_opt(opt);
+ init_revisions(opt);
+ opt->abbrev = 0;
+ opt->diff = 1;
+ argc = setup_revisions(argc, argv, opt, NULL);
- for (;;) {
- int opt_cnt;
- const char *arg;
+ while (--argc > 0) {
+ const char *arg = *++argv;
- argv++;
- argc--;
- arg = *argv;
- if (!arg)
- break;
-
- if (*arg != '-') {
- if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) {
- nr_sha1++;
- continue;
- }
- break;
- }
-
- opt_cnt = log_tree_opt_parse(opt, argv, argc);
- if (opt_cnt < 0)
- usage(diff_tree_usage);
- else if (opt_cnt) {
- argv += opt_cnt - 1;
- argc -= opt_cnt - 1;
- continue;
- }
-
- if (!strcmp(arg, "--")) {
- argv++;
- argc--;
- break;
- }
if (!strcmp(arg, "--stdin")) {
read_stdin = 1;
continue;
usage(diff_tree_usage);
}
- if (opt->combine_merges)
- opt->ignore_merges = 0;
-
- /* We can only do dense combined merges with diff output */
- if (opt->dense_combined_merges)
- opt->diffopt.output_format = DIFF_FORMAT_PATCH;
-
- diff_tree_setup_paths(get_pathspec(prefix, argv), &opt->diffopt);
- diff_setup_done(&opt->diffopt);
+ /*
+ * NOTE! "setup_revisions()" will have inserted the revisions
+ * it parsed in reverse order. So if you do
+ *
+ * git-diff-tree a b
+ *
+ * the commit list will be "b" -> "a" -> NULL, so we reverse
+ * the order of the objects if the first one is not marked
+ * UNINTERESTING.
+ */
+ nr_sha1 = 0;
+ list = opt->pending_objects;
+ if (list) {
+ nr_sha1++;
+ tree1 = list->item;
+ list = list->next;
+ if (list) {
+ nr_sha1++;
+ tree2 = tree1;
+ tree1 = list->item;
+ if (list->next)
+ usage(diff_tree_usage);
+ /* Switch them around if the second one was uninteresting.. */
+ if (tree2->flags & UNINTERESTING) {
+ struct object *tmp = tree2;
+ tree2 = tree1;
+ tree1 = tmp;
+ }
+ }
+ }
switch (nr_sha1) {
case 0:
usage(diff_tree_usage);
break;
case 1:
- diff_tree_commit_sha1(sha1[0]);
+ diff_tree_commit_sha1(tree1->sha1);
break;
case 2:
- diff_tree_sha1(sha1[0], sha1[1], "", &opt->diffopt);
+ diff_tree_sha1(tree1->sha1,
+ tree2->sha1,
+ "", &opt->diffopt);
log_tree_diff_flush(opt);
break;
}
+++ /dev/null
-/*
- * Copyright (C) 2005 Junio C Hamano
- */
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include "cache.h"
-#include "quote.h"
-#include "diff.h"
-#include "diffcore.h"
-#include "xdiff-interface.h"
-
-static int use_size_cache;
-
-int diff_rename_limit_default = -1;
-
-int git_diff_config(const char *var, const char *value)
-{
- if (!strcmp(var, "diff.renamelimit")) {
- diff_rename_limit_default = git_config_int(var, value);
- return 0;
- }
-
- return git_default_config(var, value);
-}
-
-static char *quote_one(const char *str)
-{
- int needlen;
- char *xp;
-
- if (!str)
- return NULL;
- needlen = quote_c_style(str, NULL, NULL, 0);
- if (!needlen)
- return strdup(str);
- xp = xmalloc(needlen + 1);
- quote_c_style(str, xp, NULL, 0);
- return xp;
-}
-
-static char *quote_two(const char *one, const char *two)
-{
- int need_one = quote_c_style(one, NULL, NULL, 1);
- int need_two = quote_c_style(two, NULL, NULL, 1);
- char *xp;
-
- if (need_one + need_two) {
- if (!need_one) need_one = strlen(one);
- if (!need_two) need_one = strlen(two);
-
- xp = xmalloc(need_one + need_two + 3);
- xp[0] = '"';
- quote_c_style(one, xp + 1, NULL, 1);
- quote_c_style(two, xp + need_one + 1, NULL, 1);
- strcpy(xp + need_one + need_two + 1, "\"");
- return xp;
- }
- need_one = strlen(one);
- need_two = strlen(two);
- xp = xmalloc(need_one + need_two + 1);
- strcpy(xp, one);
- strcpy(xp + need_one, two);
- return xp;
-}
-
-static const char *external_diff(void)
-{
- static const char *external_diff_cmd = NULL;
- static int done_preparing = 0;
-
- if (done_preparing)
- return external_diff_cmd;
- external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
- done_preparing = 1;
- return external_diff_cmd;
-}
-
-#define TEMPFILE_PATH_LEN 50
-
-static struct diff_tempfile {
- const char *name; /* filename external diff should read from */
- char hex[41];
- char mode[10];
- char tmp_path[TEMPFILE_PATH_LEN];
-} diff_temp[2];
-
-static int count_lines(const char *data, int size)
-{
- int count, ch, completely_empty = 1, nl_just_seen = 0;
- count = 0;
- while (0 < size--) {
- ch = *data++;
- if (ch == '\n') {
- count++;
- nl_just_seen = 1;
- completely_empty = 0;
- }
- else {
- nl_just_seen = 0;
- completely_empty = 0;
- }
- }
- if (completely_empty)
- return 0;
- if (!nl_just_seen)
- count++; /* no trailing newline */
- return count;
-}
-
-static void print_line_count(int count)
-{
- switch (count) {
- case 0:
- printf("0,0");
- break;
- case 1:
- printf("1");
- break;
- default:
- printf("1,%d", count);
- break;
- }
-}
-
-static void copy_file(int prefix, const char *data, int size)
-{
- int ch, nl_just_seen = 1;
- while (0 < size--) {
- ch = *data++;
- if (nl_just_seen)
- putchar(prefix);
- putchar(ch);
- if (ch == '\n')
- nl_just_seen = 1;
- else
- nl_just_seen = 0;
- }
- if (!nl_just_seen)
- printf("\n\\ No newline at end of file\n");
-}
-
-static void emit_rewrite_diff(const char *name_a,
- const char *name_b,
- struct diff_filespec *one,
- struct diff_filespec *two)
-{
- int lc_a, lc_b;
- diff_populate_filespec(one, 0);
- diff_populate_filespec(two, 0);
- lc_a = count_lines(one->data, one->size);
- lc_b = count_lines(two->data, two->size);
- printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
- print_line_count(lc_a);
- printf(" +");
- print_line_count(lc_b);
- printf(" @@\n");
- if (lc_a)
- copy_file('-', one->data, one->size);
- if (lc_b)
- copy_file('+', two->data, two->size);
-}
-
-static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
-{
- if (!DIFF_FILE_VALID(one)) {
- mf->ptr = ""; /* does not matter */
- mf->size = 0;
- return 0;
- }
- else if (diff_populate_filespec(one, 0))
- return -1;
- mf->ptr = one->data;
- mf->size = one->size;
- return 0;
-}
-
-struct emit_callback {
- const char **label_path;
-};
-
-static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
-{
- int i;
- struct emit_callback *ecbdata = priv;
-
- if (ecbdata->label_path[0]) {
- printf("--- %s\n", ecbdata->label_path[0]);
- printf("+++ %s\n", ecbdata->label_path[1]);
- ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
- }
- for (i = 0; i < nbuf; i++)
- if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
- return -1;
- return 0;
-}
-
-struct diffstat_t {
- struct xdiff_emit_state xm;
-
- int nr;
- int alloc;
- struct diffstat_file {
- char *name;
- unsigned is_unmerged:1;
- unsigned is_binary:1;
- unsigned int added, deleted;
- } **files;
-};
-
-static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
- const char *name)
-{
- struct diffstat_file *x;
- x = xcalloc(sizeof (*x), 1);
- if (diffstat->nr == diffstat->alloc) {
- diffstat->alloc = alloc_nr(diffstat->alloc);
- diffstat->files = xrealloc(diffstat->files,
- diffstat->alloc * sizeof(x));
- }
- diffstat->files[diffstat->nr++] = x;
- x->name = strdup(name);
- return x;
-}
-
-static void diffstat_consume(void *priv, char *line, unsigned long len)
-{
- struct diffstat_t *diffstat = priv;
- struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
-
- if (line[0] == '+')
- x->added++;
- else if (line[0] == '-')
- x->deleted++;
-}
-
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
-
-static void show_stats(struct diffstat_t* data)
-{
- char *prefix = "";
- int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
- int total_files = data->nr;
-
- if (data->nr == 0)
- return;
-
- for (i = 0; i < data->nr; i++) {
- struct diffstat_file *file = data->files[i];
-
- len = strlen(file->name);
- if (max_len < len)
- max_len = len;
-
- if (file->is_binary || file->is_unmerged)
- continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
- }
-
- for (i = 0; i < data->nr; i++) {
- char *name = data->files[i]->name;
- int added = data->files[i]->added;
- int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
-
- /*
- * "scale" the filename
- */
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
- char *slash;
- prefix = "...";
- max -= 3;
- name += len - max;
- slash = strchr(name, '/');
- if (slash)
- name = slash;
- }
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;
-
- if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
- goto free_diffstat_file;
- }
- else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
- goto free_diffstat_file;
- }
- else if (added + deleted == 0) {
- total_files--;
- goto free_diffstat_file;
- }
-
- add = added;
- del = deleted;
- total = add + del;
- adds += add;
- dels += del;
-
- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
- }
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
- free_diffstat_file:
- free(data->files[i]->name);
- free(data->files[i]);
- }
- free(data->files);
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
-}
-
-#define FIRST_FEW_BYTES 8000
-static int mmfile_is_binary(mmfile_t *mf)
-{
- long sz = mf->size;
- if (FIRST_FEW_BYTES < sz)
- sz = FIRST_FEW_BYTES;
- if (memchr(mf->ptr, 0, sz))
- return 1;
- return 0;
-}
-
-static void builtin_diff(const char *name_a,
- const char *name_b,
- struct diff_filespec *one,
- struct diff_filespec *two,
- const char *xfrm_msg,
- int complete_rewrite)
-{
- mmfile_t mf1, mf2;
- const char *lbl[2];
- char *a_one, *b_two;
-
- a_one = quote_two("a/", name_a);
- b_two = quote_two("b/", name_b);
- lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
- lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
- printf("diff --git %s %s\n", a_one, b_two);
- if (lbl[0][0] == '/') {
- /* /dev/null */
- printf("new file mode %06o\n", two->mode);
- if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
- }
- else if (lbl[1][0] == '/') {
- printf("deleted file mode %06o\n", one->mode);
- if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
- }
- else {
- if (one->mode != two->mode) {
- printf("old mode %06o\n", one->mode);
- printf("new mode %06o\n", two->mode);
- }
- if (xfrm_msg && xfrm_msg[0])
- puts(xfrm_msg);
- /*
- * we do not run diff between different kind
- * of objects.
- */
- if ((one->mode ^ two->mode) & S_IFMT)
- goto free_ab_and_return;
- if (complete_rewrite) {
- emit_rewrite_diff(name_a, name_b, one, two);
- goto free_ab_and_return;
- }
- }
-
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
-
- if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
- printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
- else {
- /* Crazy xdl interfaces.. */
- const char *diffopts = getenv("GIT_DIFF_OPTS");
- xpparam_t xpp;
- xdemitconf_t xecfg;
- xdemitcb_t ecb;
- struct emit_callback ecbdata;
-
- ecbdata.label_path = lbl;
- xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 3;
- xecfg.flags = XDL_EMIT_FUNCNAMES;
- if (!diffopts)
- ;
- else if (!strncmp(diffopts, "--unified=", 10))
- xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
- else if (!strncmp(diffopts, "-u", 2))
- xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
- ecb.outf = fn_out;
- ecb.priv = &ecbdata;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
- }
-
- free_ab_and_return:
- free(a_one);
- free(b_two);
- return;
-}
-
-static void builtin_diffstat(const char *name_a, const char *name_b,
- struct diff_filespec *one, struct diff_filespec *two,
- struct diffstat_t *diffstat)
-{
- mmfile_t mf1, mf2;
- struct diffstat_file *data;
-
- data = diffstat_add(diffstat, name_a ? name_a : name_b);
-
- if (!one || !two) {
- data->is_unmerged = 1;
- return;
- }
-
- if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
- die("unable to read files to diff");
-
- if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
- data->is_binary = 1;
- else {
- /* Crazy xdl interfaces.. */
- xpparam_t xpp;
- xdemitconf_t xecfg;
- xdemitcb_t ecb;
-
- xpp.flags = XDF_NEED_MINIMAL;
- xecfg.ctxlen = 0;
- xecfg.flags = 0;
- ecb.outf = xdiff_outf;
- ecb.priv = diffstat;
- xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
- }
-}
-
-struct diff_filespec *alloc_filespec(const char *path)
-{
- int namelen = strlen(path);
- struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
-
- memset(spec, 0, sizeof(*spec));
- spec->path = (char *)(spec + 1);
- memcpy(spec->path, path, namelen+1);
- return spec;
-}
-
-void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
- unsigned short mode)
-{
- if (mode) {
- spec->mode = canon_mode(mode);
- memcpy(spec->sha1, sha1, 20);
- spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
- }
-}
-
-/*
- * Given a name and sha1 pair, if the dircache tells us the file in
- * the work tree has that object contents, return true, so that
- * prepare_temp_file() does not have to inflate and extract.
- */
-static int work_tree_matches(const char *name, const unsigned char *sha1)
-{
- struct cache_entry *ce;
- struct stat st;
- int pos, len;
-
- /* We do not read the cache ourselves here, because the
- * benchmark with my previous version that always reads cache
- * shows that it makes things worse for diff-tree comparing
- * two linux-2.6 kernel trees in an already checked out work
- * tree. This is because most diff-tree comparisons deal with
- * only a small number of files, while reading the cache is
- * expensive for a large project, and its cost outweighs the
- * savings we get by not inflating the object to a temporary
- * file. Practically, this code only helps when we are used
- * by diff-cache --cached, which does read the cache before
- * calling us.
- */
- if (!active_cache)
- return 0;
-
- len = strlen(name);
- pos = cache_name_pos(name, len);
- if (pos < 0)
- return 0;
- ce = active_cache[pos];
- if ((lstat(name, &st) < 0) ||
- !S_ISREG(st.st_mode) || /* careful! */
- ce_match_stat(ce, &st, 0) ||
- memcmp(sha1, ce->sha1, 20))
- return 0;
- /* we return 1 only when we can stat, it is a regular file,
- * stat information matches, and sha1 recorded in the cache
- * matches. I.e. we know the file in the work tree really is
- * the same as the <name, sha1> pair.
- */
- return 1;
-}
-
-static struct sha1_size_cache {
- unsigned char sha1[20];
- unsigned long size;
-} **sha1_size_cache;
-static int sha1_size_cache_nr, sha1_size_cache_alloc;
-
-static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
- int find_only,
- unsigned long size)
-{
- int first, last;
- struct sha1_size_cache *e;
-
- first = 0;
- last = sha1_size_cache_nr;
- while (last > first) {
- int cmp, next = (last + first) >> 1;
- e = sha1_size_cache[next];
- cmp = memcmp(e->sha1, sha1, 20);
- if (!cmp)
- return e;
- if (cmp < 0) {
- last = next;
- continue;
- }
- first = next+1;
- }
- /* not found */
- if (find_only)
- return NULL;
- /* insert to make it at "first" */
- if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
- sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
- sha1_size_cache = xrealloc(sha1_size_cache,
- sha1_size_cache_alloc *
- sizeof(*sha1_size_cache));
- }
- sha1_size_cache_nr++;
- if (first < sha1_size_cache_nr)
- memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
- (sha1_size_cache_nr - first - 1) *
- sizeof(*sha1_size_cache));
- e = xmalloc(sizeof(struct sha1_size_cache));
- sha1_size_cache[first] = e;
- memcpy(e->sha1, sha1, 20);
- e->size = size;
- return e;
-}
-
-/*
- * While doing rename detection and pickaxe operation, we may need to
- * grab the data for the blob (or file) for our own in-core comparison.
- * diff_filespec has data and size fields for this purpose.
- */
-int diff_populate_filespec(struct diff_filespec *s, int size_only)
-{
- int err = 0;
- if (!DIFF_FILE_VALID(s))
- die("internal error: asking to populate invalid file.");
- if (S_ISDIR(s->mode))
- return -1;
-
- if (!use_size_cache)
- size_only = 0;
-
- if (s->data)
- return err;
- if (!s->sha1_valid ||
- work_tree_matches(s->path, s->sha1)) {
- struct stat st;
- int fd;
- if (lstat(s->path, &st) < 0) {
- if (errno == ENOENT) {
- err_empty:
- err = -1;
- empty:
- s->data = "";
- s->size = 0;
- return err;
- }
- }
- s->size = st.st_size;
- if (!s->size)
- goto empty;
- if (size_only)
- return 0;
- if (S_ISLNK(st.st_mode)) {
- int ret;
- s->data = xmalloc(s->size);
- s->should_free = 1;
- ret = readlink(s->path, s->data, s->size);
- if (ret < 0) {
- free(s->data);
- goto err_empty;
- }
- return 0;
- }
- fd = open(s->path, O_RDONLY);
- if (fd < 0)
- goto err_empty;
- s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
- close(fd);
- if (s->data == MAP_FAILED)
- goto err_empty;
- s->should_munmap = 1;
- }
- else {
- char type[20];
- struct sha1_size_cache *e;
-
- if (size_only) {
- e = locate_size_cache(s->sha1, 1, 0);
- if (e) {
- s->size = e->size;
- return 0;
- }
- if (!sha1_object_info(s->sha1, type, &s->size))
- locate_size_cache(s->sha1, 0, s->size);
- }
- else {
- s->data = read_sha1_file(s->sha1, type, &s->size);
- s->should_free = 1;
- }
- }
- return 0;
-}
-
-void diff_free_filespec_data(struct diff_filespec *s)
-{
- if (s->should_free)
- free(s->data);
- else if (s->should_munmap)
- munmap(s->data, s->size);
- s->should_free = s->should_munmap = 0;
- s->data = NULL;
- free(s->cnt_data);
- s->cnt_data = NULL;
-}
-
-static void prep_temp_blob(struct diff_tempfile *temp,
- void *blob,
- unsigned long size,
- const unsigned char *sha1,
- int mode)
-{
- int fd;
-
- fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
- if (fd < 0)
- die("unable to create temp-file");
- if (write(fd, blob, size) != size)
- die("unable to write temp-file");
- close(fd);
- temp->name = temp->tmp_path;
- strcpy(temp->hex, sha1_to_hex(sha1));
- temp->hex[40] = 0;
- sprintf(temp->mode, "%06o", mode);
-}
-
-static void prepare_temp_file(const char *name,
- struct diff_tempfile *temp,
- struct diff_filespec *one)
-{
- if (!DIFF_FILE_VALID(one)) {
- not_a_valid_file:
- /* A '-' entry produces this for file-2, and
- * a '+' entry produces this for file-1.
- */
- temp->name = "/dev/null";
- strcpy(temp->hex, ".");
- strcpy(temp->mode, ".");
- return;
- }
-
- if (!one->sha1_valid ||
- work_tree_matches(name, one->sha1)) {
- struct stat st;
- if (lstat(name, &st) < 0) {
- if (errno == ENOENT)
- goto not_a_valid_file;
- die("stat(%s): %s", name, strerror(errno));
- }
- if (S_ISLNK(st.st_mode)) {
- int ret;
- char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
- if (sizeof(buf) <= st.st_size)
- die("symlink too long: %s", name);
- ret = readlink(name, buf, st.st_size);
- if (ret < 0)
- die("readlink(%s)", name);
- prep_temp_blob(temp, buf, st.st_size,
- (one->sha1_valid ?
- one->sha1 : null_sha1),
- (one->sha1_valid ?
- one->mode : S_IFLNK));
- }
- else {
- /* we can borrow from the file in the work tree */
- temp->name = name;
- if (!one->sha1_valid)
- strcpy(temp->hex, sha1_to_hex(null_sha1));
- else
- strcpy(temp->hex, sha1_to_hex(one->sha1));
- /* Even though we may sometimes borrow the
- * contents from the work tree, we always want
- * one->mode. mode is trustworthy even when
- * !(one->sha1_valid), as long as
- * DIFF_FILE_VALID(one).
- */
- sprintf(temp->mode, "%06o", one->mode);
- }
- return;
- }
- else {
- if (diff_populate_filespec(one, 0))
- die("cannot read data blob for %s", one->path);
- prep_temp_blob(temp, one->data, one->size,
- one->sha1, one->mode);
- }
-}
-
-static void remove_tempfile(void)
-{
- int i;
-
- for (i = 0; i < 2; i++)
- if (diff_temp[i].name == diff_temp[i].tmp_path) {
- unlink(diff_temp[i].name);
- diff_temp[i].name = NULL;
- }
-}
-
-static void remove_tempfile_on_signal(int signo)
-{
- remove_tempfile();
- signal(SIGINT, SIG_DFL);
- raise(signo);
-}
-
-static int spawn_prog(const char *pgm, const char **arg)
-{
- pid_t pid;
- int status;
-
- fflush(NULL);
- pid = fork();
- if (pid < 0)
- die("unable to fork");
- if (!pid) {
- execvp(pgm, (char *const*) arg);
- exit(255);
- }
-
- while (waitpid(pid, &status, 0) < 0) {
- if (errno == EINTR)
- continue;
- return -1;
- }
-
- /* Earlier we did not check the exit status because
- * diff exits non-zero if files are different, and
- * we are not interested in knowing that. It was a
- * mistake which made it harder to quit a diff-*
- * session that uses the git-apply-patch-script as
- * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
- * should also exit non-zero only when it wants to
- * abort the entire diff-* session.
- */
- if (WIFEXITED(status) && !WEXITSTATUS(status))
- return 0;
- return -1;
-}
-
-/* An external diff command takes:
- *
- * diff-cmd name infile1 infile1-sha1 infile1-mode \
- * infile2 infile2-sha1 infile2-mode [ rename-to ]
- *
- */
-static void run_external_diff(const char *pgm,
- const char *name,
- const char *other,
- struct diff_filespec *one,
- struct diff_filespec *two,
- const char *xfrm_msg,
- int complete_rewrite)
-{
- const char *spawn_arg[10];
- struct diff_tempfile *temp = diff_temp;
- int retval;
- static int atexit_asked = 0;
- const char *othername;
- const char **arg = &spawn_arg[0];
-
- othername = (other? other : name);
- if (one && two) {
- prepare_temp_file(name, &temp[0], one);
- prepare_temp_file(othername, &temp[1], two);
- if (! atexit_asked &&
- (temp[0].name == temp[0].tmp_path ||
- temp[1].name == temp[1].tmp_path)) {
- atexit_asked = 1;
- atexit(remove_tempfile);
- }
- signal(SIGINT, remove_tempfile_on_signal);
- }
-
- if (one && two) {
- *arg++ = pgm;
- *arg++ = name;
- *arg++ = temp[0].name;
- *arg++ = temp[0].hex;
- *arg++ = temp[0].mode;
- *arg++ = temp[1].name;
- *arg++ = temp[1].hex;
- *arg++ = temp[1].mode;
- if (other) {
- *arg++ = other;
- *arg++ = xfrm_msg;
- }
- } else {
- *arg++ = pgm;
- *arg++ = name;
- }
- *arg = NULL;
- retval = spawn_prog(pgm, spawn_arg);
- remove_tempfile();
- if (retval) {
- fprintf(stderr, "external diff died, stopping at %s.\n", name);
- exit(1);
- }
-}
-
-static void run_diff_cmd(const char *pgm,
- const char *name,
- const char *other,
- struct diff_filespec *one,
- struct diff_filespec *two,
- const char *xfrm_msg,
- int complete_rewrite)
-{
- if (pgm) {
- run_external_diff(pgm, name, other, one, two, xfrm_msg,
- complete_rewrite);
- return;
- }
- if (one && two)
- builtin_diff(name, other ? other : name,
- one, two, xfrm_msg, complete_rewrite);
- else
- printf("* Unmerged path %s\n", name);
-}
-
-static void diff_fill_sha1_info(struct diff_filespec *one)
-{
- if (DIFF_FILE_VALID(one)) {
- if (!one->sha1_valid) {
- struct stat st;
- if (lstat(one->path, &st) < 0)
- die("stat %s", one->path);
- if (index_path(one->sha1, one->path, &st, 0))
- die("cannot hash %s\n", one->path);
- }
- }
- else
- memset(one->sha1, 0, 20);
-}
-
-static void run_diff(struct diff_filepair *p, struct diff_options *o)
-{
- const char *pgm = external_diff();
- char msg[PATH_MAX*2+300], *xfrm_msg;
- struct diff_filespec *one;
- struct diff_filespec *two;
- const char *name;
- const char *other;
- char *name_munged, *other_munged;
- int complete_rewrite = 0;
- int len;
-
- if (DIFF_PAIR_UNMERGED(p)) {
- /* unmerged */
- run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
- return;
- }
-
- name = p->one->path;
- other = (strcmp(name, p->two->path) ? p->two->path : NULL);
- name_munged = quote_one(name);
- other_munged = quote_one(other);
- one = p->one; two = p->two;
-
- diff_fill_sha1_info(one);
- diff_fill_sha1_info(two);
-
- len = 0;
- switch (p->status) {
- case DIFF_STATUS_COPIED:
- len += snprintf(msg + len, sizeof(msg) - len,
- "similarity index %d%%\n"
- "copy from %s\n"
- "copy to %s\n",
- (int)(0.5 + p->score * 100.0/MAX_SCORE),
- name_munged, other_munged);
- break;
- case DIFF_STATUS_RENAMED:
- len += snprintf(msg + len, sizeof(msg) - len,
- "similarity index %d%%\n"
- "rename from %s\n"
- "rename to %s\n",
- (int)(0.5 + p->score * 100.0/MAX_SCORE),
- name_munged, other_munged);
- break;
- case DIFF_STATUS_MODIFIED:
- if (p->score) {
- len += snprintf(msg + len, sizeof(msg) - len,
- "dissimilarity index %d%%\n",
- (int)(0.5 + p->score *
- 100.0/MAX_SCORE));
- complete_rewrite = 1;
- break;
- }
- /* fallthru */
- default:
- /* nothing */
- ;
- }
-
- if (memcmp(one->sha1, two->sha1, 20)) {
- char one_sha1[41];
- int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
- memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
-
- len += snprintf(msg + len, sizeof(msg) - len,
- "index %.*s..%.*s",
- abbrev, one_sha1, abbrev,
- sha1_to_hex(two->sha1));
- if (one->mode == two->mode)
- len += snprintf(msg + len, sizeof(msg) - len,
- " %06o", one->mode);
- len += snprintf(msg + len, sizeof(msg) - len, "\n");
- }
-
- if (len)
- msg[--len] = 0;
- xfrm_msg = len ? msg : NULL;
-
- if (!pgm &&
- DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
- (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
- /* a filepair that changes between file and symlink
- * needs to be split into deletion and creation.
- */
- struct diff_filespec *null = alloc_filespec(two->path);
- run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
- free(null);
- null = alloc_filespec(one->path);
- run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
- free(null);
- }
- else
- run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
- complete_rewrite);
-
- free(name_munged);
- free(other_munged);
-}
-
-static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
- struct diffstat_t *diffstat)
-{
- const char *name;
- const char *other;
-
- if (DIFF_PAIR_UNMERGED(p)) {
- /* unmerged */
- builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
- return;
- }
-
- name = p->one->path;
- other = (strcmp(name, p->two->path) ? p->two->path : NULL);
-
- diff_fill_sha1_info(p->one);
- diff_fill_sha1_info(p->two);
-
- builtin_diffstat(name, other, p->one, p->two, diffstat);
-}
-
-void diff_setup(struct diff_options *options)
-{
- memset(options, 0, sizeof(*options));
- options->output_format = DIFF_FORMAT_RAW;
- options->line_termination = '\n';
- options->break_opt = -1;
- options->rename_limit = -1;
-
- options->change = diff_change;
- options->add_remove = diff_addremove;
-}
-
-int diff_setup_done(struct diff_options *options)
-{
- if ((options->find_copies_harder &&
- options->detect_rename != DIFF_DETECT_COPY) ||
- (0 <= options->rename_limit && !options->detect_rename))
- return -1;
-
- /*
- * These cases always need recursive; we do not drop caller-supplied
- * recursive bits for other formats here.
- */
- if ((options->output_format == DIFF_FORMAT_PATCH) ||
- (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
- (options->with_stat))
- options->recursive = 1;
-
- if (options->detect_rename && options->rename_limit < 0)
- options->rename_limit = diff_rename_limit_default;
- if (options->setup & DIFF_SETUP_USE_CACHE) {
- if (!active_cache)
- /* read-cache does not die even when it fails
- * so it is safe for us to do this here. Also
- * it does not smudge active_cache or active_nr
- * when it fails, so we do not have to worry about
- * cleaning it up ourselves either.
- */
- read_cache();
- }
- if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
- use_size_cache = 1;
- if (options->abbrev <= 0 || 40 < options->abbrev)
- options->abbrev = 40; /* full */
-
- return 0;
-}
-
-int diff_opt_parse(struct diff_options *options, const char **av, int ac)
-{
- const char *arg = av[0];
- if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
- options->output_format = DIFF_FORMAT_PATCH;
- else if (!strcmp(arg, "--patch-with-raw")) {
- options->output_format = DIFF_FORMAT_PATCH;
- options->with_raw = 1;
- }
- else if (!strcmp(arg, "--stat"))
- options->output_format = DIFF_FORMAT_DIFFSTAT;
- else if (!strcmp(arg, "--patch-with-stat")) {
- options->output_format = DIFF_FORMAT_PATCH;
- options->with_stat = 1;
- }
- else if (!strcmp(arg, "-z"))
- options->line_termination = 0;
- else if (!strncmp(arg, "-l", 2))
- options->rename_limit = strtoul(arg+2, NULL, 10);
- else if (!strcmp(arg, "--full-index"))
- options->full_index = 1;
- else if (!strcmp(arg, "--name-only"))
- options->output_format = DIFF_FORMAT_NAME;
- else if (!strcmp(arg, "--name-status"))
- options->output_format = DIFF_FORMAT_NAME_STATUS;
- else if (!strcmp(arg, "-R"))
- options->reverse_diff = 1;
- else if (!strncmp(arg, "-S", 2))
- options->pickaxe = arg + 2;
- else if (!strcmp(arg, "-s"))
- options->output_format = DIFF_FORMAT_NO_OUTPUT;
- else if (!strncmp(arg, "-O", 2))
- options->orderfile = arg + 2;
- else if (!strncmp(arg, "--diff-filter=", 14))
- options->filter = arg + 14;
- else if (!strcmp(arg, "--pickaxe-all"))
- options->pickaxe_opts = DIFF_PICKAXE_ALL;
- else if (!strcmp(arg, "--pickaxe-regex"))
- options->pickaxe_opts = DIFF_PICKAXE_REGEX;
- else if (!strncmp(arg, "-B", 2)) {
- if ((options->break_opt =
- diff_scoreopt_parse(arg)) == -1)
- return -1;
- }
- else if (!strncmp(arg, "-M", 2)) {
- if ((options->rename_score =
- diff_scoreopt_parse(arg)) == -1)
- return -1;
- options->detect_rename = DIFF_DETECT_RENAME;
- }
- else if (!strncmp(arg, "-C", 2)) {
- if ((options->rename_score =
- diff_scoreopt_parse(arg)) == -1)
- return -1;
- options->detect_rename = DIFF_DETECT_COPY;
- }
- else if (!strcmp(arg, "--find-copies-harder"))
- options->find_copies_harder = 1;
- else if (!strcmp(arg, "--abbrev"))
- options->abbrev = DEFAULT_ABBREV;
- else if (!strncmp(arg, "--abbrev=", 9)) {
- options->abbrev = strtoul(arg + 9, NULL, 10);
- if (options->abbrev < MINIMUM_ABBREV)
- options->abbrev = MINIMUM_ABBREV;
- else if (40 < options->abbrev)
- options->abbrev = 40;
- }
- else
- return 0;
- return 1;
-}
-
-static int parse_num(const char **cp_p)
-{
- unsigned long num, scale;
- int ch, dot;
- const char *cp = *cp_p;
-
- num = 0;
- scale = 1;
- dot = 0;
- for(;;) {
- ch = *cp;
- if ( !dot && ch == '.' ) {
- scale = 1;
- dot = 1;
- } else if ( ch == '%' ) {
- scale = dot ? scale*100 : 100;
- cp++; /* % is always at the end */
- break;
- } else if ( ch >= '0' && ch <= '9' ) {
- if ( scale < 100000 ) {
- scale *= 10;
- num = (num*10) + (ch-'0');
- }
- } else {
- break;
- }
- cp++;
- }
- *cp_p = cp;
-
- /* user says num divided by scale and we say internally that
- * is MAX_SCORE * num / scale.
- */
- return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
-}
-
-int diff_scoreopt_parse(const char *opt)
-{
- int opt1, opt2, cmd;
-
- if (*opt++ != '-')
- return -1;
- cmd = *opt++;
- if (cmd != 'M' && cmd != 'C' && cmd != 'B')
- return -1; /* that is not a -M, -C nor -B option */
-
- opt1 = parse_num(&opt);
- if (cmd != 'B')
- opt2 = 0;
- else {
- if (*opt == 0)
- opt2 = 0;
- else if (*opt != '/')
- return -1; /* we expect -B80/99 or -B80 */
- else {
- opt++;
- opt2 = parse_num(&opt);
- }
- }
- if (*opt != 0)
- return -1;
- return opt1 | (opt2 << 16);
-}
-
-struct diff_queue_struct diff_queued_diff;
-
-void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
-{
- if (queue->alloc <= queue->nr) {
- queue->alloc = alloc_nr(queue->alloc);
- queue->queue = xrealloc(queue->queue,
- sizeof(dp) * queue->alloc);
- }
- queue->queue[queue->nr++] = dp;
-}
-
-struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
- struct diff_filespec *one,
- struct diff_filespec *two)
-{
- struct diff_filepair *dp = xmalloc(sizeof(*dp));
- dp->one = one;
- dp->two = two;
- dp->score = 0;
- dp->status = 0;
- dp->source_stays = 0;
- dp->broken_pair = 0;
- if (queue)
- diff_q(queue, dp);
- return dp;
-}
-
-void diff_free_filepair(struct diff_filepair *p)
-{
- diff_free_filespec_data(p->one);
- diff_free_filespec_data(p->two);
- free(p->one);
- free(p->two);
- free(p);
-}
-
-/* This is different from find_unique_abbrev() in that
- * it stuffs the result with dots for alignment.
- */
-const char *diff_unique_abbrev(const unsigned char *sha1, int len)
-{
- int abblen;
- const char *abbrev;
- if (len == 40)
- return sha1_to_hex(sha1);
-
- abbrev = find_unique_abbrev(sha1, len);
- if (!abbrev)
- return sha1_to_hex(sha1);
- abblen = strlen(abbrev);
- if (abblen < 37) {
- static char hex[41];
- if (len < abblen && abblen <= len + 2)
- sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
- else
- sprintf(hex, "%s...", abbrev);
- return hex;
- }
- return sha1_to_hex(sha1);
-}
-
-static void diff_flush_raw(struct diff_filepair *p,
- int line_termination,
- int inter_name_termination,
- struct diff_options *options,
- int output_format)
-{
- int two_paths;
- char status[10];
- int abbrev = options->abbrev;
- const char *path_one, *path_two;
-
- path_one = p->one->path;
- path_two = p->two->path;
- if (line_termination) {
- path_one = quote_one(path_one);
- path_two = quote_one(path_two);
- }
-
- if (p->score)
- sprintf(status, "%c%03d", p->status,
- (int)(0.5 + p->score * 100.0/MAX_SCORE));
- else {
- status[0] = p->status;
- status[1] = 0;
- }
- switch (p->status) {
- case DIFF_STATUS_COPIED:
- case DIFF_STATUS_RENAMED:
- two_paths = 1;
- break;
- case DIFF_STATUS_ADDED:
- case DIFF_STATUS_DELETED:
- two_paths = 0;
- break;
- default:
- two_paths = 0;
- break;
- }
- if (output_format != DIFF_FORMAT_NAME_STATUS) {
- printf(":%06o %06o %s ",
- p->one->mode, p->two->mode,
- diff_unique_abbrev(p->one->sha1, abbrev));
- printf("%s ",
- diff_unique_abbrev(p->two->sha1, abbrev));
- }
- printf("%s%c%s", status, inter_name_termination, path_one);
- if (two_paths)
- printf("%c%s", inter_name_termination, path_two);
- putchar(line_termination);
- if (path_one != p->one->path)
- free((void*)path_one);
- if (path_two != p->two->path)
- free((void*)path_two);
-}
-
-static void diff_flush_name(struct diff_filepair *p,
- int inter_name_termination,
- int line_termination)
-{
- char *path = p->two->path;
-
- if (line_termination)
- path = quote_one(p->two->path);
- else
- path = p->two->path;
- printf("%s%c", path, line_termination);
- if (p->two->path != path)
- free(path);
-}
-
-int diff_unmodified_pair(struct diff_filepair *p)
-{
- /* This function is written stricter than necessary to support
- * the currently implemented transformers, but the idea is to
- * let transformers to produce diff_filepairs any way they want,
- * and filter and clean them up here before producing the output.
- */
- struct diff_filespec *one, *two;
-
- if (DIFF_PAIR_UNMERGED(p))
- return 0; /* unmerged is interesting */
-
- one = p->one;
- two = p->two;
-
- /* deletion, addition, mode or type change
- * and rename are all interesting.
- */
- if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
- DIFF_PAIR_MODE_CHANGED(p) ||
- strcmp(one->path, two->path))
- return 0;
-
- /* both are valid and point at the same path. that is, we are
- * dealing with a change.
- */
- if (one->sha1_valid && two->sha1_valid &&
- !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
- return 1; /* no change */
- if (!one->sha1_valid && !two->sha1_valid)
- return 1; /* both look at the same file on the filesystem. */
- return 0;
-}
-
-static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
-{
- if (diff_unmodified_pair(p))
- return;
-
- if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
- (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
-
- run_diff(p, o);
-}
-
-static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
- struct diffstat_t *diffstat)
-{
- if (diff_unmodified_pair(p))
- return;
-
- if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
- (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
-
- run_diffstat(p, o, diffstat);
-}
-
-int diff_queue_is_empty(void)
-{
- struct diff_queue_struct *q = &diff_queued_diff;
- int i;
- for (i = 0; i < q->nr; i++)
- if (!diff_unmodified_pair(q->queue[i]))
- return 0;
- return 1;
-}
-
-#if DIFF_DEBUG
-void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
-{
- fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
- x, one ? one : "",
- s->path,
- DIFF_FILE_VALID(s) ? "valid" : "invalid",
- s->mode,
- s->sha1_valid ? sha1_to_hex(s->sha1) : "");
- fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
- x, one ? one : "",
- s->size, s->xfrm_flags);
-}
-
-void diff_debug_filepair(const struct diff_filepair *p, int i)
-{
- diff_debug_filespec(p->one, i, "one");
- diff_debug_filespec(p->two, i, "two");
- fprintf(stderr, "score %d, status %c stays %d broken %d\n",
- p->score, p->status ? p->status : '?',
- p->source_stays, p->broken_pair);
-}
-
-void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
-{
- int i;
- if (msg)
- fprintf(stderr, "%s\n", msg);
- fprintf(stderr, "q->nr = %d\n", q->nr);
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- diff_debug_filepair(p, i);
- }
-}
-#endif
-
-static void diff_resolve_rename_copy(void)
-{
- int i, j;
- struct diff_filepair *p, *pp;
- struct diff_queue_struct *q = &diff_queued_diff;
-
- diff_debug_queue("resolve-rename-copy", q);
-
- for (i = 0; i < q->nr; i++) {
- p = q->queue[i];
- p->status = 0; /* undecided */
- if (DIFF_PAIR_UNMERGED(p))
- p->status = DIFF_STATUS_UNMERGED;
- else if (!DIFF_FILE_VALID(p->one))
- p->status = DIFF_STATUS_ADDED;
- else if (!DIFF_FILE_VALID(p->two))
- p->status = DIFF_STATUS_DELETED;
- else if (DIFF_PAIR_TYPE_CHANGED(p))
- p->status = DIFF_STATUS_TYPE_CHANGED;
-
- /* from this point on, we are dealing with a pair
- * whose both sides are valid and of the same type, i.e.
- * either in-place edit or rename/copy edit.
- */
- else if (DIFF_PAIR_RENAME(p)) {
- if (p->source_stays) {
- p->status = DIFF_STATUS_COPIED;
- continue;
- }
- /* See if there is some other filepair that
- * copies from the same source as us. If so
- * we are a copy. Otherwise we are either a
- * copy if the path stays, or a rename if it
- * does not, but we already handled "stays" case.
- */
- for (j = i + 1; j < q->nr; j++) {
- pp = q->queue[j];
- if (strcmp(pp->one->path, p->one->path))
- continue; /* not us */
- if (!DIFF_PAIR_RENAME(pp))
- continue; /* not a rename/copy */
- /* pp is a rename/copy from the same source */
- p->status = DIFF_STATUS_COPIED;
- break;
- }
- if (!p->status)
- p->status = DIFF_STATUS_RENAMED;
- }
- else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
- p->one->mode != p->two->mode)
- p->status = DIFF_STATUS_MODIFIED;
- else {
- /* This is a "no-change" entry and should not
- * happen anymore, but prepare for broken callers.
- */
- error("feeding unmodified %s to diffcore",
- p->one->path);
- p->status = DIFF_STATUS_UNKNOWN;
- }
- }
- diff_debug_queue("resolve-rename-copy done", q);
-}
-
-static void flush_one_pair(struct diff_filepair *p,
- int diff_output_format,
- struct diff_options *options,
- struct diffstat_t *diffstat)
-{
- int inter_name_termination = '\t';
- int line_termination = options->line_termination;
- if (!line_termination)
- inter_name_termination = 0;
-
- switch (p->status) {
- case DIFF_STATUS_UNKNOWN:
- break;
- case 0:
- die("internal error in diff-resolve-rename-copy");
- break;
- default:
- switch (diff_output_format) {
- case DIFF_FORMAT_DIFFSTAT:
- diff_flush_stat(p, options, diffstat);
- break;
- case DIFF_FORMAT_PATCH:
- diff_flush_patch(p, options);
- break;
- case DIFF_FORMAT_RAW:
- case DIFF_FORMAT_NAME_STATUS:
- diff_flush_raw(p, line_termination,
- inter_name_termination,
- options, diff_output_format);
- break;
- case DIFF_FORMAT_NAME:
- diff_flush_name(p,
- inter_name_termination,
- line_termination);
- break;
- case DIFF_FORMAT_NO_OUTPUT:
- break;
- }
- }
-}
-
-void diff_flush(struct diff_options *options)
-{
- struct diff_queue_struct *q = &diff_queued_diff;
- int i;
- int diff_output_format = options->output_format;
- struct diffstat_t *diffstat = NULL;
-
- if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
- diffstat = xcalloc(sizeof (struct diffstat_t), 1);
- diffstat->xm.consume = diffstat_consume;
- }
-
- if (options->with_raw) {
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
- }
- putchar(options->line_termination);
- }
- if (options->with_stat) {
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
- diffstat);
- }
- show_stats(diffstat);
- free(diffstat);
- diffstat = NULL;
- putchar(options->line_termination);
- }
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- flush_one_pair(p, diff_output_format, options, diffstat);
- diff_free_filepair(p);
- }
-
- if (diffstat) {
- show_stats(diffstat);
- free(diffstat);
- }
-
- free(q->queue);
- q->queue = NULL;
- q->nr = q->alloc = 0;
-}
-
-static void diffcore_apply_filter(const char *filter)
-{
- int i;
- struct diff_queue_struct *q = &diff_queued_diff;
- struct diff_queue_struct outq;
- outq.queue = NULL;
- outq.nr = outq.alloc = 0;
-
- if (!filter)
- return;
-
- if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
- int found;
- for (i = found = 0; !found && i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
- if (((p->status == DIFF_STATUS_MODIFIED) &&
- ((p->score &&
- strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
- (!p->score &&
- strchr(filter, DIFF_STATUS_MODIFIED)))) ||
- ((p->status != DIFF_STATUS_MODIFIED) &&
- strchr(filter, p->status)))
- found++;
- }
- if (found)
- return;
-
- /* otherwise we will clear the whole queue
- * by copying the empty outq at the end of this
- * function, but first clear the current entries
- * in the queue.
- */
- for (i = 0; i < q->nr; i++)
- diff_free_filepair(q->queue[i]);
- }
- else {
- /* Only the matching ones */
- for (i = 0; i < q->nr; i++) {
- struct diff_filepair *p = q->queue[i];
-
- if (((p->status == DIFF_STATUS_MODIFIED) &&
- ((p->score &&
- strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
- (!p->score &&
- strchr(filter, DIFF_STATUS_MODIFIED)))) ||
- ((p->status != DIFF_STATUS_MODIFIED) &&
- strchr(filter, p->status)))
- diff_q(&outq, p);
- else
- diff_free_filepair(p);
- }
- }
- free(q->queue);
- *q = outq;
-}
-
-void diffcore_std(struct diff_options *options)
-{
- if (options->break_opt != -1)
- diffcore_break(options->break_opt);
- if (options->detect_rename)
- diffcore_rename(options);
- if (options->break_opt != -1)
- diffcore_merge_broken();
- if (options->pickaxe)
- diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
- if (options->orderfile)
- diffcore_order(options->orderfile);
- diff_resolve_rename_copy();
- diffcore_apply_filter(options->filter);
-}
-
-
-void diffcore_std_no_resolve(struct diff_options *options)
-{
- if (options->pickaxe)
- diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
- if (options->orderfile)
- diffcore_order(options->orderfile);
- diffcore_apply_filter(options->filter);
-}
-
-void diff_addremove(struct diff_options *options,
- int addremove, unsigned mode,
- const unsigned char *sha1,
- const char *base, const char *path)
-{
- char concatpath[PATH_MAX];
- struct diff_filespec *one, *two;
-
- /* This may look odd, but it is a preparation for
- * feeding "there are unchanged files which should
- * not produce diffs, but when you are doing copy
- * detection you would need them, so here they are"
- * entries to the diff-core. They will be prefixed
- * with something like '=' or '*' (I haven't decided
- * which but should not make any difference).
- * Feeding the same new and old to diff_change()
- * also has the same effect.
- * Before the final output happens, they are pruned after
- * merged into rename/copy pairs as appropriate.
- */
- if (options->reverse_diff)
- addremove = (addremove == '+' ? '-' :
- addremove == '-' ? '+' : addremove);
-
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
- one = alloc_filespec(concatpath);
- two = alloc_filespec(concatpath);
-
- if (addremove != '+')
- fill_filespec(one, sha1, mode);
- if (addremove != '-')
- fill_filespec(two, sha1, mode);
-
- diff_queue(&diff_queued_diff, one, two);
-}
-
-void diff_change(struct diff_options *options,
- unsigned old_mode, unsigned new_mode,
- const unsigned char *old_sha1,
- const unsigned char *new_sha1,
- const char *base, const char *path)
-{
- char concatpath[PATH_MAX];
- struct diff_filespec *one, *two;
-
- if (options->reverse_diff) {
- unsigned tmp;
- const unsigned char *tmp_c;
- tmp = old_mode; old_mode = new_mode; new_mode = tmp;
- tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
- }
- if (!path) path = "";
- sprintf(concatpath, "%s%s", base, path);
- one = alloc_filespec(concatpath);
- two = alloc_filespec(concatpath);
- fill_filespec(one, old_sha1, old_mode);
- fill_filespec(two, new_sha1, new_mode);
-
- diff_queue(&diff_queued_diff, one, two);
-}
-
-void diff_unmerge(struct diff_options *options,
- const char *path)
-{
- struct diff_filespec *one, *two;
- one = alloc_filespec(path);
- two = alloc_filespec(path);
- diff_queue(&diff_queued_diff, one, two);
-}
#include "tree-walk.h"
+struct rev_info;
struct diff_options;
typedef void (*change_fn_t)(struct diff_options *options,
(sizeof(struct combine_diff_path) + \
sizeof(struct combine_diff_parent) * (n) + (l) + 1)
-extern int show_combined_diff(struct combine_diff_path *elem, int num_parent,
- int dense, const char *header,
- struct diff_options *);
+extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
+ int dense, struct rev_info *);
-extern const char *diff_tree_combined_merge(const unsigned char *sha1, const char *, int, struct diff_options *opt);
+extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
extern void diff_addremove(struct diff_options *,
int addremove,
USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
. git-sh-setup
-
+
no_update_info= all_into_one= remove_redundant=
-local= quiet= no_reuse_delta=
+local= quiet= no_reuse_delta= extra=
while case "$#" in 0) break ;; esac
do
case "$1" in
-q) quiet=-q ;;
-f) no_reuse_delta=--no-reuse-delta ;;
-l) local=--local ;;
+ --window=*) extra="$extra $1" ;;
+ --depth=*) extra="$extra $1" ;;
*) usage ;;
esac
shift
find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
;;
esac
-pack_objects="$pack_objects $local $quiet $no_reuse_delta"
+pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
name=$(git-rev-list --objects --all $rev_list 2>&1 |
git-pack-objects --non-empty $pack_objects .tmp-pack) ||
exit 1
return 0;
}
-#define LOGSIZE (65536)
-
-static int cmd_log(int argc, const char **argv, char **envp)
+static int cmd_log_wc(int argc, const char **argv, char **envp,
+ struct rev_info *rev)
{
- struct rev_info rev;
struct commit *commit;
- char *buf = xmalloc(LOGSIZE);
- static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
- int abbrev = DEFAULT_ABBREV;
- int abbrev_commit = 0;
- const char *commit_prefix = "commit ";
- struct log_tree_opt opt;
- int shown = 0;
- int do_diff = 0;
- int full_diff = 0;
-
- init_log_tree_opt(&opt);
- argc = setup_revisions(argc, argv, &rev, "HEAD");
- while (1 < argc) {
- const char *arg = argv[1];
- if (!strncmp(arg, "--pretty", 8)) {
- commit_format = get_commit_format(arg + 8);
- if (commit_format == CMIT_FMT_ONELINE)
- commit_prefix = "";
- }
- else if (!strcmp(arg, "--no-abbrev")) {
- abbrev = 0;
- }
- else if (!strcmp(arg, "--abbrev")) {
- abbrev = DEFAULT_ABBREV;
- }
- else if (!strcmp(arg, "--abbrev-commit")) {
- abbrev_commit = 1;
- }
- else if (!strncmp(arg, "--abbrev=", 9)) {
- abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev && abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (40 < abbrev)
- abbrev = 40;
- }
- else if (!strcmp(arg, "--full-diff")) {
- do_diff = 1;
- full_diff = 1;
- }
- else {
- int cnt = log_tree_opt_parse(&opt, argv+1, argc-1);
- if (0 < cnt) {
- do_diff = 1;
- argv += cnt;
- argc -= cnt;
- continue;
- }
- die("unrecognized argument: %s", arg);
- }
- argc--; argv++;
- }
+ rev->abbrev = DEFAULT_ABBREV;
+ rev->commit_format = CMIT_FMT_DEFAULT;
+ rev->verbose_header = 1;
+ argc = setup_revisions(argc, argv, rev, "HEAD");
- if (do_diff) {
- opt.diffopt.abbrev = abbrev;
- opt.verbose_header = 0;
- opt.always_show_header = 0;
- opt.no_commit_id = 1;
- if (opt.combine_merges)
- opt.ignore_merges = 0;
- if (opt.dense_combined_merges)
- opt.diffopt.output_format = DIFF_FORMAT_PATCH;
- if (!full_diff && rev.prune_data)
- diff_tree_setup_paths(rev.prune_data, &opt.diffopt);
- diff_setup_done(&opt.diffopt);
- }
+ if (argc > 1)
+ die("unrecognized argument: %s", argv[1]);
- prepare_revision_walk(&rev);
+ prepare_revision_walk(rev);
setup_pager();
- while ((commit = get_revision(&rev)) != NULL) {
- if (shown && do_diff && commit_format != CMIT_FMT_ONELINE)
- putchar('\n');
- fputs(commit_prefix, stdout);
- if (abbrev_commit && abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, abbrev),
- stdout);
- else
- fputs(sha1_to_hex(commit->object.sha1), stdout);
- if (rev.parents) {
- struct commit_list *parents = commit->parents;
- while (parents) {
- struct object *o = &(parents->item->object);
- parents = parents->next;
- if (o->flags & TMP_MARK)
- continue;
- printf(" %s", sha1_to_hex(o->sha1));
- o->flags |= TMP_MARK;
- }
- /* TMP_MARK is a general purpose flag that can
- * be used locally, but the user should clean
- * things up after it is done with them.
- */
- for (parents = commit->parents;
- parents;
- parents = parents->next)
- parents->item->object.flags &= ~TMP_MARK;
- }
- if (commit_format == CMIT_FMT_ONELINE)
- putchar(' ');
- else
- putchar('\n');
- pretty_print_commit(commit_format, commit, ~0, buf,
- LOGSIZE, abbrev);
- printf("%s\n", buf);
- if (do_diff) {
- printf("---\n");
- log_tree_commit(&opt, commit);
- }
- shown = 1;
+ while ((commit = get_revision(rev)) != NULL) {
+ log_tree_commit(rev, commit);
free(commit->buffer);
commit->buffer = NULL;
}
- free(buf);
return 0;
}
+static int cmd_wc(int argc, const char **argv, char **envp)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev);
+ rev.diff = 1;
+ rev.diffopt.recursive = 1;
+ return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+static int cmd_show(int argc, const char **argv, char **envp)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev);
+ rev.diff = 1;
+ rev.diffopt.recursive = 1;
+ rev.combine_merges = 1;
+ rev.dense_combined_merges = 1;
+ rev.always_show_header = 1;
+ rev.ignore_merges = 0;
+ rev.no_walk = 1;
+ return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+static int cmd_log(int argc, const char **argv, char **envp)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev);
+ rev.always_show_header = 1;
+ rev.diffopt.recursive = 1;
+ return cmd_log_wc(argc, argv, envp, &rev);
+}
+
static void handle_internal_command(int argc, const char **argv, char **envp)
{
const char *cmd = argv[0];
{ "version", cmd_version },
{ "help", cmd_help },
{ "log", cmd_log },
+ { "whatchanged", cmd_wc },
+ { "show", cmd_show },
};
int i;
commit_argv[3] = old_sha1_hex;
commit_argc++;
}
+ init_revisions(&revs);
setup_revisions(commit_argc, commit_argv, &revs, NULL);
free(new_sha1_hex);
if (old_sha1_hex) {
#include "commit.h"
#include "log-tree.h"
-void init_log_tree_opt(struct log_tree_opt *opt)
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
{
- memset(opt, 0, sizeof *opt);
- opt->ignore_merges = 1;
- opt->header_prefix = "";
- opt->commit_format = CMIT_FMT_RAW;
- diff_setup(&opt->diffopt);
-}
-
-int log_tree_opt_parse(struct log_tree_opt *opt, const char **av, int ac)
-{
- const char *arg;
- int cnt = diff_opt_parse(&opt->diffopt, av, ac);
- if (0 < cnt)
- return cnt;
- arg = *av;
- if (!strcmp(arg, "-r"))
- opt->diffopt.recursive = 1;
- else if (!strcmp(arg, "-t")) {
- opt->diffopt.recursive = 1;
- opt->diffopt.tree_in_recursive = 1;
- }
- else if (!strcmp(arg, "-m"))
- opt->ignore_merges = 0;
- else if (!strcmp(arg, "-c"))
- opt->combine_merges = 1;
- else if (!strcmp(arg, "--cc")) {
- opt->dense_combined_merges = 1;
- opt->combine_merges = 1;
- }
- else if (!strcmp(arg, "-v")) {
- opt->verbose_header = 1;
- opt->header_prefix = "diff-tree ";
- }
- else if (!strncmp(arg, "--pretty", 8)) {
- opt->verbose_header = 1;
- opt->header_prefix = "diff-tree ";
- opt->commit_format = get_commit_format(arg+8);
+ static char this_header[16384];
+ struct commit *commit = log->commit, *parent = log->parent;
+ int abbrev = opt->diffopt.abbrev;
+ int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
+ const char *extra;
+ int len;
+
+ opt->loginfo = NULL;
+ if (!opt->verbose_header) {
+ puts(sha1_to_hex(commit->object.sha1));
+ return;
}
- else if (!strcmp(arg, "--root"))
- opt->show_root_diff = 1;
- else if (!strcmp(arg, "--no-commit-id"))
- opt->no_commit_id = 1;
- else if (!strcmp(arg, "--always"))
- opt->always_show_header = 1;
- else
- return 0;
- return 1;
+
+ /*
+ * The "oneline" format has several special cases:
+ * - The pretty-printed commit lacks a newline at the end
+ * of the buffer, but we do want to make sure that we
+ * have a newline there. If the separator isn't already
+ * a newline, add an extra one.
+ * - unlike other log messages, the one-line format does
+ * not have an empty line between entries.
+ */
+ extra = "";
+ if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
+ extra = "\n";
+ if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
+ putchar('\n');
+ opt->shown_one = 1;
+
+ /*
+ * Print header line of header..
+ */
+ printf("%s%s",
+ opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+ diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+ if (parent)
+ printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
+ putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+
+ /*
+ * And then the pretty-printed message itself
+ */
+ len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev);
+ printf("%s%s%s", this_header, extra, sep);
}
-int log_tree_diff_flush(struct log_tree_opt *opt)
+int log_tree_diff_flush(struct rev_info *opt)
{
diffcore_std(&opt->diffopt);
+
if (diff_queue_is_empty()) {
int saved_fmt = opt->diffopt.output_format;
opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
opt->diffopt.output_format = saved_fmt;
return 0;
}
- if (opt->header) {
- if (!opt->no_commit_id)
- printf("%s%c", opt->header,
- opt->diffopt.line_termination);
- opt->header = NULL;
- }
+
+ if (opt->loginfo && !opt->no_commit_id)
+ show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n");
diff_flush(&opt->diffopt);
return 1;
}
-static int diff_root_tree(struct log_tree_opt *opt,
+static int diff_root_tree(struct rev_info *opt,
const unsigned char *new, const char *base)
{
int retval;
return retval;
}
-static const char *generate_header(struct log_tree_opt *opt,
- const unsigned char *commit_sha1,
- const unsigned char *parent_sha1,
- const struct commit *commit)
-{
- static char this_header[16384];
- int offset;
- unsigned long len;
- int abbrev = opt->diffopt.abbrev;
- const char *msg = commit->buffer;
-
- if (!opt->verbose_header)
- return sha1_to_hex(commit_sha1);
-
- len = strlen(msg);
-
- offset = sprintf(this_header, "%s%s ",
- opt->header_prefix,
- diff_unique_abbrev(commit_sha1, abbrev));
- if (commit_sha1 != parent_sha1)
- offset += sprintf(this_header + offset, "(from %s)\n",
- parent_sha1
- ? diff_unique_abbrev(parent_sha1, abbrev)
- : "root");
- else
- offset += sprintf(this_header + offset, "(from parents)\n");
- offset += pretty_print_commit(opt->commit_format, commit, len,
- this_header + offset,
- sizeof(this_header) - offset, abbrev);
- if (opt->always_show_header) {
- puts(this_header);
- return NULL;
- }
- return this_header;
-}
-
-static int do_diff_combined(struct log_tree_opt *opt, struct commit *commit)
+static int do_diff_combined(struct rev_info *opt, struct commit *commit)
{
unsigned const char *sha1 = commit->object.sha1;
- opt->header = generate_header(opt, sha1, sha1, commit);
- opt->header = diff_tree_combined_merge(sha1, opt->header,
- opt->dense_combined_merges,
- &opt->diffopt);
- if (!opt->header && opt->verbose_header)
- opt->header_prefix = "\ndiff-tree ";
- return 0;
+ diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+ return !opt->loginfo;
}
-int log_tree_commit(struct log_tree_opt *opt, struct commit *commit)
+/*
+ * Show the diff of a commit.
+ *
+ * Return true if we printed any log info messages
+ */
+static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
{
+ int showed_log;
struct commit_list *parents;
unsigned const char *sha1 = commit->object.sha1;
+ if (!opt->diff)
+ return 0;
+
/* Root commit? */
- if (opt->show_root_diff && !commit->parents) {
- opt->header = generate_header(opt, sha1, NULL, commit);
- diff_root_tree(opt, sha1, "");
+ parents = commit->parents;
+ if (!parents) {
+ if (opt->show_root_diff)
+ diff_root_tree(opt, sha1, "");
+ return !opt->loginfo;
}
/* More than one parent? */
- if (commit->parents && commit->parents->next) {
+ if (parents && parents->next) {
if (opt->ignore_merges)
return 0;
else if (opt->combine_merges)
return do_diff_combined(opt, commit);
+
+ /* If we show individual diffs, show the parent info */
+ log->parent = parents->item;
}
- for (parents = commit->parents; parents; parents = parents->next) {
+ showed_log = 0;
+ for (;;) {
struct commit *parent = parents->item;
- unsigned const char *psha1 = parent->object.sha1;
- opt->header = generate_header(opt, sha1, psha1, commit);
- diff_tree_sha1(psha1, sha1, "", &opt->diffopt);
- log_tree_diff_flush(opt);
- if (!opt->header && opt->verbose_header)
- opt->header_prefix = "\ndiff-tree ";
+ diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+
+ showed_log |= !opt->loginfo;
+
+ /* Set up the log info for the next parent, if any.. */
+ parents = parents->next;
+ if (!parents)
+ break;
+ log->parent = parents->item;
+ opt->loginfo = log;
+ }
+ return showed_log;
+}
+
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
+{
+ struct log_info log;
+
+ log.commit = commit;
+ log.parent = NULL;
+ opt->loginfo = &log;
+
+ if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+ log.parent = NULL;
+ show_log(opt, opt->loginfo, "");
}
+ opt->loginfo = NULL;
return 0;
}
#ifndef LOG_TREE_H
#define LOG_TREE_H
-struct log_tree_opt {
- struct diff_options diffopt;
- int show_root_diff;
- int no_commit_id;
- int verbose_header;
- int ignore_merges;
- int combine_merges;
- int dense_combined_merges;
- int always_show_header;
- const char *header_prefix;
- const char *header;
- enum cmit_fmt commit_format;
+#include "revision.h"
+
+struct log_info {
+ struct commit *commit, *parent;
};
-void init_log_tree_opt(struct log_tree_opt *);
-int log_tree_diff_flush(struct log_tree_opt *);
-int log_tree_commit(struct log_tree_opt *, struct commit *);
-int log_tree_opt_parse(struct log_tree_opt *, const char **, int);
+void init_log_tree_opt(struct rev_info *);
+int log_tree_diff_flush(struct rev_info *);
+int log_tree_commit(struct rev_info *, struct commit *);
+int log_tree_opt_parse(struct rev_info *, const char **, int);
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep);
#endif
return;
if (!pager)
pager = "less";
- else if (!*pager)
+ else if (!*pager || !strcmp(pager, "cat"))
return;
if (pipe(fd) < 0)
struct rev_info revs;
static int bisect_list = 0;
-static int verbose_header = 0;
-static int abbrev = DEFAULT_ABBREV;
-static int abbrev_commit = 0;
static int show_timestamp = 0;
static int hdr_termination = 0;
-static const char *commit_prefix = "";
-static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+static const char *header_prefix;
static void show_commit(struct commit *commit)
{
if (show_timestamp)
printf("%lu ", commit->date);
- if (commit_prefix[0])
- fputs(commit_prefix, stdout);
+ if (header_prefix)
+ fputs(header_prefix, stdout);
if (commit->object.flags & BOUNDARY)
putchar('-');
- if (abbrev_commit && abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, abbrev), stdout);
+ if (revs.abbrev_commit && revs.abbrev)
+ fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+ stdout);
else
fputs(sha1_to_hex(commit->object.sha1), stdout);
if (revs.parents) {
parents = parents->next)
parents->item->object.flags &= ~TMP_MARK;
}
- if (commit_format == CMIT_FMT_ONELINE)
+ if (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
- if (verbose_header) {
+ if (revs.verbose_header) {
static char pretty_header[16384];
- pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
+ pretty_print_commit(revs.commit_format, commit, ~0,
+ pretty_header, sizeof(pretty_header),
+ revs.abbrev);
printf("%s%c", pretty_header, hdr_termination);
}
fflush(stdout);
struct commit_list *list;
int i;
+ init_revisions(&revs);
+ revs.abbrev = 0;
+ revs.commit_format = CMIT_FMT_UNSPECIFIED;
argc = setup_revisions(argc, argv, &revs, NULL);
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
- /* accept -<digit>, like traditilnal "head" */
- if ((*arg == '-') && isdigit(arg[1])) {
- revs.max_count = atoi(arg + 1);
- continue;
- }
- if (!strcmp(arg, "-n")) {
- if (++i >= argc)
- die("-n requires an argument");
- revs.max_count = atoi(argv[i]);
- continue;
- }
- if (!strncmp(arg,"-n",2)) {
- revs.max_count = atoi(arg + 2);
- continue;
- }
if (!strcmp(arg, "--header")) {
- verbose_header = 1;
- continue;
- }
- if (!strcmp(arg, "--no-abbrev")) {
- abbrev = 0;
- continue;
- }
- if (!strcmp(arg, "--abbrev")) {
- abbrev = DEFAULT_ABBREV;
- continue;
- }
- if (!strcmp(arg, "--abbrev-commit")) {
- abbrev_commit = 1;
- continue;
- }
- if (!strncmp(arg, "--abbrev=", 9)) {
- abbrev = strtoul(arg + 9, NULL, 10);
- if (abbrev && abbrev < MINIMUM_ABBREV)
- abbrev = MINIMUM_ABBREV;
- else if (40 < abbrev)
- abbrev = 40;
- continue;
- }
- if (!strncmp(arg, "--pretty", 8)) {
- commit_format = get_commit_format(arg+8);
- verbose_header = 1;
- hdr_termination = '\n';
- if (commit_format == CMIT_FMT_ONELINE)
- commit_prefix = "";
- else
- commit_prefix = "commit ";
+ revs.verbose_header = 1;
continue;
}
if (!strcmp(arg, "--timestamp")) {
usage(rev_list_usage);
}
+ if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+ /* The command line has a --pretty */
+ hdr_termination = '\n';
+ if (revs.commit_format == CMIT_FMT_ONELINE)
+ header_prefix = "";
+ else
+ header_prefix = "commit ";
+ }
+ else if (revs.verbose_header)
+ /* Only --header was specified */
+ revs.commit_format = CMIT_FMT_RAW;
list = revs.commits;
- if (!list &&
- (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
+ if ((!list &&
+ (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+ !revs.pending_objects)) ||
+ revs.diff)
usage(rev_list_usage);
- save_commit_buffer = verbose_header;
+ save_commit_buffer = revs.verbose_header;
track_object_refs = 0;
if (bisect_list)
revs.limited = 1;
add_object(obj, &revs->pending_objects, NULL, name);
}
-static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
{
struct object *object;
object = parse_object(sha1);
if (!object)
die("bad object %s", name);
+ object->flags |= flags;
+ return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+ unsigned long flags = object->flags;
/*
* Tag object? Look what it points to..
*/
while (object->type == tag_type) {
struct tag *tag = (struct tag *) object;
- object->flags |= flags;
- if (revs->tag_objects && !(object->flags & UNINTERESTING))
+ if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
object = parse_object(tag->tagged->sha1);
if (!object)
*/
if (object->type == commit_type) {
struct commit *commit = (struct commit *)object;
- object->flags |= flags;
if (parse_commit(commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
+ commit->object.flags |= UNINTERESTING;
mark_parents_uninteresting(commit);
revs->limited = 1;
}
return REV_TREE_DIFFERENT;
tree_difference = REV_TREE_SAME;
if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
- &revs->diffopt) < 0)
+ &revs->pruning) < 0)
return REV_TREE_DIFFERENT;
return tree_difference;
}
empty.size = 0;
tree_difference = 0;
- retval = diff_tree(&empty, &real, "", &revs->diffopt);
+ retval = diff_tree(&empty, &real, "", &revs->pruning);
free(tree);
return retval >= 0 && !tree_difference;
if (revs->prune_fn)
revs->prune_fn(revs, commit);
+ if (revs->no_walk)
+ return;
+
parent = commit->parents;
while (parent) {
struct commit *p = parent->item;
revs->commits = newlist;
}
-static void add_one_commit(struct commit *commit, struct rev_info *revs)
-{
- if (!commit || (commit->object.flags & SEEN))
- return;
- commit->object.flags |= SEEN;
- commit_list_insert(commit, &revs->commits);
-}
-
static int all_flags;
static struct rev_info *all_revs;
static int handle_one_ref(const char *path, const unsigned char *sha1)
{
- struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
- add_one_commit(commit, all_revs);
+ struct object *object = get_reference(all_revs, path, sha1, all_flags);
+ add_pending_object(all_revs, object, "");
return 0;
}
void init_revisions(struct rev_info *revs)
{
memset(revs, 0, sizeof(*revs));
- revs->diffopt.recursive = 1;
- revs->diffopt.add_remove = file_add_remove;
- revs->diffopt.change = file_change;
+
+ revs->abbrev = DEFAULT_ABBREV;
+ revs->ignore_merges = 1;
+ revs->pruning.recursive = 1;
+ revs->pruning.add_remove = file_add_remove;
+ revs->pruning.change = file_change;
revs->lifo = 1;
revs->dense = 1;
revs->prefix = setup_git_directory();
revs->topo_setter = topo_sort_default_setter;
revs->topo_getter = topo_sort_default_getter;
+
+ revs->commit_format = CMIT_FMT_DEFAULT;
+
+ diff_setup(&revs->diffopt);
}
/*
const char **unrecognized = argv + 1;
int left = 1;
- init_revisions(revs);
-
/* First, search for "--" */
seen_dashdash = 0;
for (i = 1; i < argc; i++) {
flags = 0;
for (i = 1; i < argc; i++) {
- struct commit *commit;
+ struct object *object;
const char *arg = argv[i];
unsigned char sha1[20];
char *dotdot;
int local_flags;
if (*arg == '-') {
+ int opts;
if (!strncmp(arg, "--max-count=", 12)) {
revs->max_count = atoi(arg + 12);
continue;
revs->unpacked = 1;
continue;
}
+ if (!strcmp(arg, "-r")) {
+ revs->diff = 1;
+ revs->diffopt.recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-t")) {
+ revs->diff = 1;
+ revs->diffopt.recursive = 1;
+ revs->diffopt.tree_in_recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-m")) {
+ revs->ignore_merges = 0;
+ continue;
+ }
+ if (!strcmp(arg, "-c")) {
+ revs->diff = 1;
+ revs->combine_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--cc")) {
+ revs->diff = 1;
+ revs->dense_combined_merges = 1;
+ revs->combine_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ revs->verbose_header = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--pretty", 8)) {
+ revs->verbose_header = 1;
+ revs->commit_format = get_commit_format(arg+8);
+ continue;
+ }
+ if (!strcmp(arg, "--root")) {
+ revs->show_root_diff = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-commit-id")) {
+ revs->no_commit_id = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--always")) {
+ revs->always_show_header = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-abbrev")) {
+ revs->abbrev = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--abbrev")) {
+ revs->abbrev = DEFAULT_ABBREV;
+ continue;
+ }
+ if (!strcmp(arg, "--abbrev-commit")) {
+ revs->abbrev_commit = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--full-diff")) {
+ revs->diff = 1;
+ revs->full_diff = 1;
+ continue;
+ }
+ opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+ if (opts > 0) {
+ revs->diff = 1;
+ i += opts - 1;
+ continue;
+ }
*unrecognized++ = arg;
left++;
continue;
this = "HEAD";
if (!get_sha1(this, from_sha1) &&
!get_sha1(next, sha1)) {
- struct commit *exclude;
- struct commit *include;
+ struct object *exclude;
+ struct object *include;
- exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
- include = get_commit_reference(revs, next, sha1, flags);
+ exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
+ include = get_reference(revs, next, sha1, flags);
if (!exclude || !include)
die("Invalid revision range %s..%s", arg, next);
- add_one_commit(exclude, revs);
- add_one_commit(include, revs);
+ add_pending_object(revs, exclude, this);
+ add_pending_object(revs, include, next);
continue;
}
*dotdot = '.';
revs->prune_data = get_pathspec(revs->prefix, argv + i);
break;
}
- commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
- add_one_commit(commit, revs);
+ object = get_reference(revs, arg, sha1, flags ^ local_flags);
+ add_pending_object(revs, object, arg);
}
- if (def && !revs->commits) {
+ if (def && !revs->pending_objects) {
unsigned char sha1[20];
- struct commit *commit;
+ struct object *object;
if (get_sha1(def, sha1) < 0)
die("bad default revision '%s'", def);
- commit = get_commit_reference(revs, def, sha1, 0);
- add_one_commit(commit, revs);
+ object = get_reference(revs, def, sha1, 0);
+ add_pending_object(revs, object, def);
}
if (revs->topo_order || revs->unpacked)
revs->limited = 1;
if (revs->prune_data) {
- diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+ diff_tree_setup_paths(revs->prune_data, &revs->pruning);
revs->prune_fn = try_to_simplify_commit;
+ if (!revs->full_diff)
+ diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
}
+ if (revs->combine_merges) {
+ revs->ignore_merges = 0;
+ if (revs->dense_combined_merges)
+ revs->diffopt.output_format = DIFF_FORMAT_PATCH;
+ }
+ revs->diffopt.abbrev = revs->abbrev;
+ diff_setup_done(&revs->diffopt);
return left;
}
void prepare_revision_walk(struct rev_info *revs)
{
- sort_by_date(&revs->commits);
+ struct object_list *list;
+
+ list = revs->pending_objects;
+ revs->pending_objects = NULL;
+ while (list) {
+ struct commit *commit = handle_commit(revs, list->item, list->name);
+ if (commit) {
+ if (!(commit->object.flags & SEEN)) {
+ commit->object.flags |= SEEN;
+ insert_by_date(commit, &revs->commits);
+ }
+ }
+ list = list->next;
+ }
+
+ if (revs->no_walk)
+ return;
if (revs->limited)
limit_list(revs);
if (revs->topo_order)
#define ADDED (1u<<7) /* Parents already parsed and added? */
struct rev_info;
+struct log_info;
typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
/* Traversal flags */
unsigned int dense:1,
no_merges:1,
+ no_walk:1,
remove_empty_trees:1,
lifo:1,
topo_order:1,
boundary:1,
parents:1;
+ /* Diff flags */
+ unsigned int diff:1,
+ full_diff:1,
+ show_root_diff:1,
+ no_commit_id:1,
+ verbose_header:1,
+ ignore_merges:1,
+ combine_merges:1,
+ dense_combined_merges:1,
+ always_show_header:1;
+
+ /* Format info */
+ unsigned int shown_one:1,
+ abbrev_commit:1;
+ unsigned int abbrev;
+ enum cmit_fmt commit_format;
+ struct log_info *loginfo;
+
/* special limits */
int max_count;
unsigned long max_age;
unsigned long min_age;
- /* paths limiting */
+ /* diff info for patches and for paths limiting */
struct diff_options diffopt;
+ struct diff_options pruning;
topo_sort_set_fn_t topo_setter;
topo_sort_get_fn_t topo_getter;
#include "commit.h"
#include "tree.h"
#include "blob.h"
+#include "tree-walk.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
*/
int get_sha1(const char *name, unsigned char *sha1)
{
+ int ret;
+ unsigned unused;
+
prepare_alt_odb();
- return get_sha1_1(name, strlen(name), sha1);
+ ret = get_sha1_1(name, strlen(name), sha1);
+ if (ret < 0) {
+ const char *cp = strchr(name, ':');
+ if (cp) {
+ unsigned char tree_sha1[20];
+ if (!get_sha1_1(name, cp-name, tree_sha1))
+ return get_tree_entry(tree_sha1, cp+1, sha1,
+ &unused);
+ }
+ }
+ return ret;
}
#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
cat > whatchanged.expect << EOF
-diff-tree VARIABLE (from root)
+commit VARIABLE
Author: VARIABLE
Date: VARIABLE
EOF
git-whatchanged -p --root | \
- sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+ sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
-e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
> whatchanged.output
test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
free(entry);
}
+static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
+{
+ int namelen = strlen(name);
+ while (t->size) {
+ const char *entry;
+ const unsigned char *sha1;
+ int entrylen, cmp;
+
+ sha1 = tree_entry_extract(t, &entry, mode);
+ update_tree_entry(t);
+ entrylen = strlen(entry);
+ if (entrylen > namelen)
+ continue;
+ cmp = memcmp(name, entry, entrylen);
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ break;
+ if (entrylen == namelen) {
+ memcpy(result, sha1, 20);
+ return 0;
+ }
+ if (name[entrylen] != '/')
+ continue;
+ if (!S_ISDIR(*mode))
+ break;
+ if (++entrylen == namelen) {
+ memcpy(result, sha1, 20);
+ return 0;
+ }
+ return get_tree_entry(sha1, name + entrylen, result, mode);
+ }
+ return -1;
+}
+
+int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode)
+{
+ int retval;
+ void *tree;
+ struct tree_desc t;
+
+ tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL);
+ if (!tree)
+ return -1;
+ t.buf = tree;
+ retval = find_tree_entry(&t, name, sha1, mode);
+ free(tree);
+ return retval;
+}
+
void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback);
+int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
+
#endif