From 9517e6b84357252e1882091343661c34d978771e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 3 Feb 2010 21:23:18 -0800 Subject: Typofixes outside documentation area begining -> beginning canonicalizations -> canonicalization comand -> command dewrapping -> unwrapping dirtyness -> dirtiness DISCLAMER -> DISCLAIMER explicitely -> explicitly feeded -> fed impiled -> implied madatory -> mandatory mimick -> mimic preceeding -> preceding reqeuest -> request substition -> substitution Signed-off-by: Junio C Hamano --- diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 381cc8d4fd..9038057a76 100644 --- a/diff.c +++ b/diff.c @@ -3642,7 +3642,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) struct diff_filepair *p = q->queue[i]; /* - * 1. Entries that come from stat info dirtyness + * 1. Entries that come from stat info dirtiness * always have both sides (iow, not create/delete), * one side of the object name is unknown, with * the same mode and size. Keep the ones that -- cgit v1.3 From 8324b977aef3d2301f170e23f498b50e11302575 Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Mon, 15 Feb 2010 23:10:45 -0500 Subject: diff: make sure --output=/bad/path is caught The return value from fopen wasn't being checked. Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- diff.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 17a2b4df29..8d8405aba2 100644 --- a/diff.c +++ b/diff.c @@ -2799,6 +2799,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); + if (!options->file) + die_errno("Could not open '%s'", arg + strlen("--output=")); options->close_file = 1; } else return 0; -- cgit v1.3 From 6977c250ac6cacb5ee441bff832fdeab4d0cd8f9 Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Tue, 16 Feb 2010 01:55:21 -0500 Subject: git diff --quiet -w: check and report the status The option -w tells the diff machinery to inspect the contents to set the exit status, instead of checking the blob object level difference alone. However, --quiet tells the diff machinery not to look at the contents, which means DIFF_FROM_CONTENTS has no chance to inspect the change. Work it around by calling diff_flush_patch() with output sent to /dev/null. Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- diff.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 381cc8d4fd..7216b1e8db 100644 --- a/diff.c +++ b/diff.c @@ -3520,6 +3520,29 @@ void diff_flush(struct diff_options *options) separator++; } + if (output_format & DIFF_FORMAT_NO_OUTPUT && + DIFF_OPT_TST(options, EXIT_WITH_STATUS) && + DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + /* + * run diff_flush_patch for the exit status. setting + * options->file to /dev/null should be safe, becaue we + * aren't supposed to produce any output anyway. + */ + if (options->close_file) + fclose(options->file); + options->file = fopen("/dev/null", "w"); + if (!options->file) + die_errno("Could not open /dev/null"); + options->close_file = 1; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, options); + if (options->found_changes) + break; + } + } + if (output_format & DIFF_FORMAT_PATCH) { if (separator) { putc(options->line_termination, options->file); -- cgit v1.3 From 73e9da019655261e456ed862340880de365111f0 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Tue, 16 Feb 2010 23:55:58 -0500 Subject: Add an optional argument for --color options Make git-branch, git-show-branch, git-grep, and all the diff-based programs accept an optional argument for --color. The argument is a colorbool: "always", "never", or "auto". If no argument is given, "always" is used; --no-color is an alias for --color=never. This makes the command-line interface consistent with other GNU tools, such as `ls' and `grep', and with the git-config color options. Note that, without an argument, --color and --no-color work exactly as before. To implement this, two internal changes were made: 1. Allow the first argument of git_config_colorbool() to be NULL, in which case it returns -1 if the argument isn't "always", "never", or "auto". 2. Add OPT_COLOR_FLAG(), OPT__COLOR(), and parse_opt_color_flag_cb() to the option parsing library. The callback uses git_config_colorbool(), so color.h is now a dependency of parse-options.c. Signed-off-by: Mark Lodato Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 4 +++- Documentation/git-branch.txt | 6 ++++-- Documentation/git-grep.txt | 6 ++++-- Documentation/git-show-branch.txt | 6 ++++-- Documentation/technical/api-parse-options.txt | 12 ++++++++++++ builtin-branch.c | 2 +- builtin-grep.c | 2 +- builtin-show-branch.c | 4 ++-- color.c | 3 +++ diff.c | 9 +++++++++ parse-options.c | 16 ++++++++++++++++ parse-options.h | 7 +++++++ 12 files changed, 66 insertions(+), 11 deletions(-) (limited to 'diff.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8707d0e740..60e922e6ef 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -117,12 +117,14 @@ any of those replacements occurred. option and lists the commits in that commit range like the 'summary' option of linkgit:git-submodule[1] does. ---color:: +--color[=]:: Show colored diff. + The value must be always (the default), never, or auto. --no-color:: Turn off colored diff, even when the configuration file gives the default to color output. + Same as `--color=never`. --color-words[=]:: Show colored word diff, i.e., color words which have changed. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 6b6c3da2d9..903a690f10 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches SYNOPSIS -------- [verse] -'git branch' [--color | --no-color] [-r | -a] +'git branch' [--color[=] | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]] [(--merged | --no-merged | --contains) []] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] @@ -84,12 +84,14 @@ OPTIONS -M:: Move/rename a branch even if the new branch name already exists. ---color:: +--color[=]:: Color branches to highlight current, local, and remote branches. + The value must be always (the default), never, or auto. --no-color:: Turn off branch colors, even when the configuration file gives the default to color output. + Same as `--color=never`. -r:: List or delete (if used with -d) the remote-tracking branches. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index e019e760b4..70c7ef95f6 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -18,7 +18,7 @@ SYNOPSIS [-z | --null] [-c | --count] [--all-match] [-q | --quiet] [--max-depth ] - [--color | --no-color] + [--color[=] | --no-color] [-A ] [-B ] [-C ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] [...] @@ -111,12 +111,14 @@ OPTIONS Instead of showing every matched line, show the number of lines that match. ---color:: +--color[=]:: Show colored matches. + The value must be always (the default), never, or auto. --no-color:: Turn off match highlighting, even when the configuration file gives the default to color output. + Same as `--color=never`. -[ABC] :: Show `context` trailing (`A` -- after), or leading (`B` diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 734336119c..519f9e1dd7 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order] - [--current] [--color | --no-color] [--sparse] + [--current] [--color[=] | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]... @@ -117,13 +117,15 @@ OPTIONS When no explicit parameter is given, it defaults to the current branch (or `HEAD` if it is detached). ---color:: +--color[=]:: Color the status sign (one of these: `*` `!` `+` `-`) of each commit corresponding to the branch it's in. + The value must be always (the default), never, or auto. --no-color:: Turn off colored output, even when the configuration file gives the default to color output. + Same as `--color=never`. Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 50f9e9ac17..312e3b2e2b 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -115,6 +115,9 @@ There are some macros to easily define options: `OPT__ABBREV(&int_var)`:: Add `\--abbrev[=]`. +`OPT__COLOR(&int_var, description)`:: + Add `\--color[=]` and `--no-color`. + `OPT__DRY_RUN(&int_var)`:: Add `-n, \--dry-run`. @@ -183,6 +186,15 @@ There are some macros to easily define options: arguments. Short options that happen to be digits take precedence over it. +`OPT_COLOR_FLAG(short, long, &int_var, description)`:: + Introduce an option that takes an optional argument that can + have one of three values: "always", "never", or "auto". If the + argument is not given, it defaults to "always". The `--no-` form + works like `--long=never`; it cannot take an argument. If + "always", set `int_var` to 1; if "never", set `int_var` to 0; if + "auto", set `int_var` to 1 if stdout is a tty or a pager, + 0 otherwise. + The last element of the array must be `OPT_END()`. diff --git a/builtin-branch.c b/builtin-branch.c index a28a13986d..6cf7e721e6 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -610,7 +610,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", BRANCH_TRACK_OVERRIDE), - OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), + OPT__COLOR(&branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { diff --git a/builtin-grep.c b/builtin-grep.c index 26d4deb1cc..00cbd90bf6 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -782,7 +782,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "print NUL after filenames"), OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), - OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1), + OPT__COLOR(&opt.color, "highlight matches"), OPT_GROUP(""), OPT_CALLBACK('C', NULL, &opt, "n", "show context lines before and after matches", diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 9f13caa76d..32d862ab23 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -6,7 +6,7 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]...", + "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=] | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]...", "git show-branch (-g|--reflog)[=[,]] [--list] []", NULL }; @@ -661,7 +661,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) "show remote-tracking and local branches"), OPT_BOOLEAN('r', "remotes", &all_remotes, "show remote-tracking branches"), - OPT_BOOLEAN(0, "color", &showbranch_use_color, + OPT__COLOR(&showbranch_use_color, "color '*!+-' corresponding to the branch"), { OPTION_INTEGER, 0, "more", &extra, "n", "show more commits after the common ancestor", diff --git a/color.c b/color.c index 62977f4808..8f07fc9547 100644 --- a/color.c +++ b/color.c @@ -138,6 +138,9 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) goto auto_color; } + if (!var) + return -1; + /* Missing or explicit false to turn off colorization */ if (!git_config_bool(var, value)) return 0; diff --git a/diff.c b/diff.c index 381cc8d4fd..8f645f6f8d 100644 --- a/diff.c +++ b/diff.c @@ -2826,6 +2826,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) DIFF_OPT_SET(options, COLOR_DIFF); + else if (!prefixcmp(arg, "--color=")) { + int value = git_config_colorbool(NULL, arg+8, -1); + if (value == 0) + DIFF_OPT_CLR(options, COLOR_DIFF); + else if (value > 0) + DIFF_OPT_SET(options, COLOR_DIFF); + else + return error("option `color' expects \"always\", \"auto\", or \"never\""); + } else if (!strcmp(arg, "--no-color")) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) { diff --git a/parse-options.c b/parse-options.c index d218122af5..c83035d013 100644 --- a/parse-options.c +++ b/parse-options.c @@ -2,6 +2,7 @@ #include "parse-options.h" #include "cache.h" #include "commit.h" +#include "color.h" static int parse_options_usage(const char * const *usagestr, const struct option *opts); @@ -599,6 +600,21 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_color_flag_cb(const struct option *opt, const char *arg, + int unset) +{ + int value; + + if (!arg) + arg = unset ? "never" : (const char *)opt->defval; + value = git_config_colorbool(NULL, arg, -1); + if (value < 0) + return opterror(opt, + "expects \"always\", \"auto\", or \"never\"", 0); + *(int *)opt->value = value; + return 0; +} + int parse_opt_verbosity_cb(const struct option *opt, const char *arg, int unset) { diff --git a/parse-options.h b/parse-options.h index 0c996916b6..9429f7e361 100644 --- a/parse-options.h +++ b/parse-options.h @@ -135,6 +135,10 @@ struct option { PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } #define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ "FILE", (h) } +#define OPT_COLOR_FLAG(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \ + parse_opt_color_flag_cb, (intptr_t)"always" } + /* parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. @@ -187,6 +191,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); +extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); @@ -203,5 +208,7 @@ extern int parse_opt_tertiary(const struct option *, const char *, int); { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ "use digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } +#define OPT__COLOR(var, h) \ + OPT_COLOR_FLAG(0, "color", (var), (h)) #endif -- cgit v1.3 From ae6d5c1b6f78ef48f606e5a267915fa31b37a679 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Thu, 11 Mar 2010 22:50:25 +0100 Subject: Refactor dirty submodule detection in diff-lib.c Moving duplicated code into the new function match_stat_with_submodule(). Replacing the implicit activation of detailed checks for the dirtiness of submodules when DIFF_FORMAT_PATCH was selected with explicitly setting the recently added DIFF_OPT_DIRTY_SUBMODULES option in diff_setup_done(). Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 45 +++++++++++++++++++++++++++------------------ diff.c | 6 ++++++ 2 files changed, 33 insertions(+), 18 deletions(-) (limited to 'diff.c') diff --git a/diff-lib.c b/diff-lib.c index 1ab286a3ce..ea9cf561cd 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -55,6 +55,29 @@ static int check_removed(const struct cache_entry *ce, struct stat *st) return 0; } +/* + * Has a file changed or has a submodule new commits or a dirty work tree? + * + * Return 1 when changes are detected, 0 otherwise. If the DIRTY_SUBMODULES + * option is set, the caller does not only want to know if a submodule is + * modified at all but wants to know all the conditions that are met (new + * commits, untracked content and/or modified content). + */ +static int match_stat_with_submodule(struct diff_options *diffopt, + struct cache_entry *ce, struct stat *st, + unsigned ce_option, unsigned *dirty_submodule) +{ + int changed = ce_match_stat(ce, st, ce_option); + if (S_ISGITLINK(ce->ce_mode) + && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { + *dirty_submodule = is_submodule_modified(ce->name); + if (*dirty_submodule) + changed = 1; + } + return changed; +} + int run_diff_files(struct rev_info *revs, unsigned int option) { int entries, i; @@ -177,15 +200,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) ce->sha1, ce->name, 0); continue; } - changed = ce_match_stat(ce, &st, ce_option); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(&revs->diffopt, IGNORE_SUBMODULES) - && (!changed || (revs->diffopt.output_format & DIFF_FORMAT_PATCH) - || DIFF_OPT_TST(&revs->diffopt, DIRTY_SUBMODULES))) { - dirty_submodule = is_submodule_modified(ce->name); - if (dirty_submodule) - changed = 1; - } + changed = match_stat_with_submodule(&revs->diffopt, ce, &st, + ce_option, &dirty_submodule); if (!changed) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) @@ -241,15 +257,8 @@ static int get_stat_data(struct cache_entry *ce, } return -1; } - changed = ce_match_stat(ce, &st, 0); - if (S_ISGITLINK(ce->ce_mode) - && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) - && (!changed || (diffopt->output_format & DIFF_FORMAT_PATCH) - || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { - *dirty_submodule = is_submodule_modified(ce->name); - if (*dirty_submodule) - changed = 1; - } + changed = match_stat_with_submodule(diffopt, ce, &st, + 0, dirty_submodule); if (changed) { mode = ce_mode_from_stat(ce, st.st_mode); sha1 = null_sha1; diff --git a/diff.c b/diff.c index 381cc8d4fd..240401b73d 100644 --- a/diff.c +++ b/diff.c @@ -2628,6 +2628,12 @@ int diff_setup_done(struct diff_options *options) */ if (options->pickaxe) DIFF_OPT_SET(options, RECURSIVE); + /* + * When patches are generated, submodules diffed against the work tree + * must be checked for dirtiness too so it can be shown in the output + */ + if (options->output_format & DIFF_FORMAT_PATCH) + DIFF_OPT_SET(options, DIRTY_SUBMODULES); if (options->detect_rename && options->rename_limit < 0) options->rename_limit = diff_rename_limit_default; -- cgit v1.3 From 85adbf2f751a91429de6b431c45737ba9d7e9e00 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 12 Mar 2010 22:23:52 +0100 Subject: git status: Fix false positive "new commits" output for dirty submodules Testing if the output "new commits" should appear in the long format of "git status" is done by comparing the hashes of the diffpair. This always resulted in printing "new commits" for submodules that contained untracked or modified content, even if they did not contain new commits. The reason was that match_stat_with_submodule() did set the "changed" flag for dirty submodules, resulting in two->sha1 being set to the null_sha1 at the call sites, which indicates that new commits are present. This is changed so that when no new commits are present, the same object names are in the sha1 field for both sides of the filepair, and the working tree side will have the "dirty_submodule" flag set when appropriate. For a submodule to be seen as modified even when it just has a dirty work tree, some conditions had to be extended to also check for the "dirty_submodule" flag. Unfortunately the test case that should have found this bug had been changed incorrectly too. It is fixed and extended to test for other combinations too. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- diff-lib.c | 6 ++-- diff.c | 7 ++-- t/t7506-status-submodule.sh | 84 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 9 deletions(-) (limited to 'diff.c') diff --git a/diff-lib.c b/diff-lib.c index ea9cf561cd..87a259111a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -72,8 +72,6 @@ static int match_stat_with_submodule(struct diff_options *diffopt, && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { *dirty_submodule = is_submodule_modified(ce->name); - if (*dirty_submodule) - changed = 1; } return changed; } @@ -202,7 +200,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } changed = match_stat_with_submodule(&revs->diffopt, ce, &st, ce_option, &dirty_submodule); - if (!changed) { + if (!changed && !dirty_submodule) { ce_mark_uptodate(ce); if (!DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) continue; @@ -333,7 +331,7 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !hashcmp(sha1, old->sha1) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !dirty_submodule && !DIFF_OPT_TST(&revs->diffopt, FIND_COPIES_HARDER)) return 0; diff --git a/diff.c b/diff.c index 240401b73d..8fd020fa47 100644 --- a/diff.c +++ b/diff.c @@ -2032,7 +2032,7 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only) char *data = xmalloc(100), *dirty = ""; /* Are we looking at the work tree? */ - if (!s->sha1_valid && s->dirty_submodule) + if (s->dirty_submodule) dirty = "-dirty"; len = snprintf(data, 100, @@ -3081,7 +3081,8 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !hashcmp(one->sha1, two->sha1)) + !hashcmp(one->sha1, two->sha1) && + !one->dirty_submodule && !two->dirty_submodule) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -3216,6 +3217,8 @@ static void diff_resolve_rename_copy(void) } else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode || + p->one->dirty_submodule || + p->two->dirty_submodule || is_null_sha1(p->one->sha1)) p->status = DIFF_STATUS_MODIFIED; else { diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 1c8d32a99e..dc9150a71f 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -34,7 +34,7 @@ test_expect_success 'status with modified file in submodule' ' (cd sub && git reset --hard) && echo "changed" >sub/foo && git status >output && - grep "modified: sub (new commits, modified content)" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with modified file in submodule (porcelain)' ' @@ -49,7 +49,7 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && - grep "modified: sub (new commits, modified content)" output + grep "modified: sub (modified content)" output ' test_expect_success 'status with added file in submodule (porcelain)' ' @@ -64,7 +64,7 @@ test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && git status >output && - grep "modified: sub (new commits, untracked content)" output + grep "modified: sub (untracked content)" output ' test_expect_success 'status with untracked file in submodule (porcelain)' ' @@ -74,6 +74,84 @@ test_expect_success 'status with untracked file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with added and untracked file in submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with modified file in modified submodule' ' + (cd sub && git reset --hard) && + rm sub/new-file && + (cd sub && echo "next change" >foo && git commit -m "next change" foo) && + echo "changed" >sub/foo && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with modified file in modified submodule (porcelain)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status >output && + grep "modified: sub (new commits, modified content)" output +' + +test_expect_success 'status with added file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with untracked file in modified submodule' ' + (cd sub && git reset --hard) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, untracked content)" output +' + +test_expect_success 'status with untracked file in modified submodule (porcelain)' ' + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + +test_expect_success 'status with added and untracked file in modified submodule' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status >output && + grep "modified: sub (new commits, modified content, untracked content)" output +' + +test_expect_success 'status with added and untracked file in modified submodule (porcelain)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + echo "content" >sub/new-file && + git status --porcelain >output && + diff output - <<-\EOF + M sub + EOF +' + test_expect_success 'rm submodule contents' ' rm -rf sub/* sub/.git ' -- cgit v1.3 From a757c646ee78ae21c9e8ac66dcc52e361c15c7d2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 24 Mar 2010 19:21:32 -0700 Subject: diff --check: honor conflict-marker-size attribute Signed-off-by: Junio C Hamano --- diff.c | 24 +++++++++++------------- t/t4017-diff-retval.sh | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 14 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 08bbd3e907..a09c97fa71 100644 --- a/diff.c +++ b/diff.c @@ -14,6 +14,7 @@ #include "userdiff.h" #include "sigchain.h" #include "submodule.h" +#include "ll-merge.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -1364,37 +1365,32 @@ static void free_diffstat_info(struct diffstat_t *diffstat) struct checkdiff_t { const char *filename; int lineno; + int conflict_marker_size; struct diff_options *o; unsigned ws_rule; unsigned status; }; -static int is_conflict_marker(const char *line, unsigned long len) +static int is_conflict_marker(const char *line, int marker_size, unsigned long len) { char firstchar; int cnt; - if (len < 8) + if (len < marker_size + 1) return 0; firstchar = line[0]; switch (firstchar) { - case '=': case '>': case '<': + case '=': case '>': case '<': case '|': break; default: return 0; } - for (cnt = 1; cnt < 7; cnt++) + for (cnt = 1; cnt < marker_size; cnt++) if (line[cnt] != firstchar) return 0; - /* line[0] thru line[6] are same as firstchar */ - if (firstchar == '=') { - /* divider between ours and theirs? */ - if (len != 8 || line[7] != '\n') - return 0; - } else if (len < 8 || !isspace(line[7])) { - /* not divider before ours nor after theirs */ + /* line[1] thru line[marker_size-1] are same as firstchar */ + if (len < marker_size + 1 || !isspace(line[marker_size])) return 0; - } return 1; } @@ -1402,6 +1398,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF); + int marker_size = data->conflict_marker_size; const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE); const char *reset = diff_get_color(color_diff, DIFF_RESET); const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); @@ -1410,7 +1407,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (is_conflict_marker(line + 1, len - 1)) { + if (is_conflict_marker(line + 1, marker_size, len - 1)) { data->status |= 1; fprintf(data->o->file, "%s:%d: leftover conflict marker\n", @@ -1841,6 +1838,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.lineno = 0; data.o = o; data.ws_rule = whitespace_rule(attr_path); + data.conflict_marker_size = ll_merge_marker_size(attr_path); if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh index 60dd2014d5..92e9137d1c 100755 --- a/t/t4017-diff-retval.sh +++ b/t/t4017-diff-retval.sh @@ -105,7 +105,6 @@ test_expect_success '--check with --no-pager returns 2 for dirty difference' ' ' - test_expect_success 'check should test not just the last line' ' echo "" >>a && git --no-pager diff --check @@ -127,4 +126,26 @@ test_expect_success 'check detects leftover conflict markers' ' git reset --hard ' +test_expect_success 'check honors conflict marker length' ' + git reset --hard && + echo ">>>>>>> boo" >>b && + echo "======" >>a && + git diff --check a && + ( + git diff --check b + test $? = 2 + ) && + git reset --hard && + echo ">>>>>>>> boo" >>b && + echo "========" >>a && + git diff --check && + echo "b conflict-marker-size=8" >.gitattributes && + ( + git diff --check b + test $? = 2 + ) && + git diff --check a && + git reset --hard +' + test_done -- cgit v1.3 From da1fbed3fff6ed2d64399ff26d8a9ab3bcf00540 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 30 Mar 2010 19:36:03 +0200 Subject: diff: fix textconv error zombies To make the code simpler, run_textconv lumps all of its error checking into one conditional. However, the short-circuit means that an error in reading will prevent us from calling finish_command, leaving a zombie child. Clean up properly after errors. Based-on-work-by: Jeff King Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- diff.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 0d465faa1e..99059231b4 100644 --- a/diff.c +++ b/diff.c @@ -3865,6 +3865,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, const char **arg = argv; struct child_process child; struct strbuf buf = STRBUF_INIT; + int err = 0; temp = prepare_temp_file(spec->path, spec); *arg++ = pgm; @@ -3875,16 +3876,20 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, child.use_shell = 1; child.argv = argv; child.out = -1; - if (start_command(&child) != 0 || - strbuf_read(&buf, child.out, 0) < 0 || - finish_command(&child) != 0) { - close(child.out); - strbuf_release(&buf); + if (start_command(&child)) { remove_tempfile(); - error("error running textconv command '%s'", pgm); return NULL; } + + if (strbuf_read(&buf, child.out, 0) < 0) + err = error("error reading from textconv command '%s'", pgm); close(child.out); + + if (finish_command(&child) || err) { + strbuf_release(&buf); + remove_tempfile(); + return NULL; + } remove_tempfile(); return strbuf_detach(&buf, outsize); -- cgit v1.3 From b76c056b95ccb15ae3b0723da983914de88b4bae Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 1 Apr 2010 20:04:14 -0400 Subject: fix textconv leak in emit_rewrite_diff We correctly free() for the normal diff case, but leak for rewrite diffs. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 2daa732a36..db2cd5d35f 100644 --- a/diff.c +++ b/diff.c @@ -550,6 +550,10 @@ static void emit_rewrite_diff(const char *name_a, emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); + if (textconv_one) + free(data_one); + if (textconv_two) + free(data_two); } struct diff_words_buffer { -- cgit v1.3 From 840383b2c2bd7179604f5c2595bf95e22a4e0c84 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 1 Apr 2010 20:09:26 -0400 Subject: textconv: refactor calls to run_textconv This patch adds a fill_textconv wrapper, which centralizes some minor logic like error checking and handling the case of no-textconv. In addition to dropping the number of lines, this will make it easier in future patches to handle multiple types of textconv. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 66 ++++++++++++++++++++++++++++++------------------------------------ 1 file changed, 30 insertions(+), 36 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index db2cd5d35f..9665d6d41f 100644 --- a/diff.c +++ b/diff.c @@ -43,7 +43,8 @@ static char diff_colors[][COLOR_MAXLEN] = { }; static void diff_filespec_load_driver(struct diff_filespec *one); -static char *run_textconv(const char *, struct diff_filespec *, size_t *); +static size_t fill_textconv(const char *cmd, + struct diff_filespec *df, char **outbuf); static int parse_diff_color_slot(const char *var, int ofs) { @@ -477,7 +478,7 @@ static void emit_rewrite_diff(const char *name_a, const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; - const char *data_one, *data_two; + char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; @@ -499,26 +500,8 @@ static void emit_rewrite_diff(const char *name_a, quote_two_c_style(&a_name, a_prefix, name_a, 0); quote_two_c_style(&b_name, b_prefix, name_b, 0); - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); - if (textconv_one) { - data_one = run_textconv(textconv_one, one, &size_one); - if (!data_one) - die("unable to read files to diff"); - } - else { - data_one = one->data; - size_one = one->size; - } - if (textconv_two) { - data_two = run_textconv(textconv_two, two, &size_two); - if (!data_two) - die("unable to read files to diff"); - } - else { - data_two = two->data; - size_two = two->size; - } + size_one = fill_textconv(textconv_one, one, &data_one); + size_two = fill_textconv(textconv_two, two, &data_two); memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = color_diff; @@ -1717,20 +1700,8 @@ static void builtin_diff(const char *name_a, strbuf_reset(&header); } - if (textconv_one) { - size_t size; - mf1.ptr = run_textconv(textconv_one, one, &size); - if (!mf1.ptr) - die("unable to read files to diff"); - mf1.size = size; - } - if (textconv_two) { - size_t size; - mf2.ptr = run_textconv(textconv_two, two, &size); - if (!mf2.ptr) - die("unable to read files to diff"); - mf2.size = size; - } + mf1.size = fill_textconv(textconv_one, one, &mf1.ptr); + mf2.size = fill_textconv(textconv_two, two, &mf2.ptr); pe = diff_funcname_pattern(one); if (!pe) @@ -3916,3 +3887,26 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } + +static size_t fill_textconv(const char *cmd, + struct diff_filespec *df, + char **outbuf) +{ + size_t size; + + if (!cmd) { + if (!DIFF_FILE_VALID(df)) { + *outbuf = ""; + return 0; + } + if (diff_populate_filespec(df, 0)) + die("unable to read files to diff"); + *outbuf = df->data; + return df->size; + } + + *outbuf = run_textconv(cmd, df, &size); + if (!*outbuf) + die("unable to read files to diff"); + return size; +} -- cgit v1.3 From d9bae1a178f0f8b198ea611e874975214ad6f990 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 1 Apr 2010 20:12:15 -0400 Subject: diff: cache textconv output Running a textconv filter can take a long time. It's particularly bad for a large file which needs to be spooled to disk, but even for small files, the fork+exec overhead can add up for something like "git log -p". This patch uses the notes-cache mechanism to keep a fast cache of textconv output. Caches are stored in refs/notes/textconv/$x, where $x is the userdiff driver defined in gitattributes. Caching is enabled only if diff.$x.cachetextconv is true. In my test repo, on a commit with 45 jpg and avi files changed and a textconv to show their exif tags: [before] $ time git show >/dev/null real 0m13.724s user 0m12.057s sys 0m1.624s [after, first run] $ git config diff.mfo.cachetextconv true $ time git show >/dev/null real 0m14.252s user 0m12.197s sys 0m1.800s [after, subsequent runs] $ time git show >/dev/null real 0m0.352s user 0m0.148s sys 0m0.200s So for a slight (3.8%) cost on the first run, we achieve an almost 40x speed up on subsequent runs. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 20 +++++++ diff.c | 52 +++++++++++++++---- t/t4042-diff-textconv-caching.sh | 109 +++++++++++++++++++++++++++++++++++++++ userdiff.c | 9 ++++ userdiff.h | 4 ++ 5 files changed, 185 insertions(+), 9 deletions(-) create mode 100755 t/t4042-diff-textconv-caching.sh (limited to 'diff.c') diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index d892e642ed..a8500d1772 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -414,6 +414,26 @@ because it quickly conveys the changes you have made), you should generate it separately and send it as a comment _in addition to_ the usual binary diff that you might send. +Because text conversion can be slow, especially when doing a +large number of them with `git log -p`, git provides a mechanism +to cache the output and use it in future diffs. To enable +caching, set the "cachetextconv" variable in your diff driver's +config. For example: + +------------------------ +[diff "jpg"] + textconv = exif + cachetextconv = true +------------------------ + +This will cache the result of running "exif" on each blob +indefinitely. If you change the textconv config variable for a +diff driver, git will automatically invalidate the cache entries +and re-run the textconv filter. If you want to invalidate the +cache manually (e.g., because your version of "exif" was updated +and now produces better output), you can remove the cache +manually with `git update-ref -d refs/notes/textconv/jpg` (where +"jpg" is the name of the diff driver, as in the example above). Performing a three-way merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/diff.c b/diff.c index 9665d6d41f..72d8503d89 100644 --- a/diff.c +++ b/diff.c @@ -43,7 +43,7 @@ static char diff_colors[][COLOR_MAXLEN] = { }; static void diff_filespec_load_driver(struct diff_filespec *one); -static size_t fill_textconv(const char *cmd, +static size_t fill_textconv(struct userdiff_driver *driver, struct diff_filespec *df, char **outbuf); static int parse_diff_color_slot(const char *var, int ofs) @@ -466,8 +466,8 @@ static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, - const char *textconv_one, - const char *textconv_two, + struct userdiff_driver *textconv_one, + struct userdiff_driver *textconv_two, struct diff_options *o) { int lc_a, lc_b; @@ -1569,14 +1569,26 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } -static const char *get_textconv(struct diff_filespec *one) +static struct userdiff_driver *get_textconv(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; if (!S_ISREG(one->mode)) return NULL; diff_filespec_load_driver(one); - return one->driver->textconv; + if (!one->driver->textconv) + return NULL; + + if (one->driver->textconv_want_cache && !one->driver->textconv_cache) { + struct notes_cache *c = xmalloc(sizeof(*c)); + struct strbuf name = STRBUF_INIT; + + strbuf_addf(&name, "textconv/%s", one->driver->name); + notes_cache_init(c, name.buf, one->driver->textconv); + one->driver->textconv_cache = c; + } + + return one->driver; } static void builtin_diff(const char *name_a, @@ -1593,7 +1605,8 @@ static void builtin_diff(const char *name_a, const char *set = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; - const char *textconv_one = NULL, *textconv_two = NULL; + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; if (DIFF_OPT_TST(o, SUBMODULE_LOG) && @@ -3888,13 +3901,13 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } -static size_t fill_textconv(const char *cmd, +static size_t fill_textconv(struct userdiff_driver *driver, struct diff_filespec *df, char **outbuf) { size_t size; - if (!cmd) { + if (!driver || !driver->textconv) { if (!DIFF_FILE_VALID(df)) { *outbuf = ""; return 0; @@ -3905,8 +3918,29 @@ static size_t fill_textconv(const char *cmd, return df->size; } - *outbuf = run_textconv(cmd, df, &size); + if (driver->textconv_cache) { + *outbuf = notes_cache_get(driver->textconv_cache, df->sha1, + &size); + if (*outbuf) + return size; + } + + *outbuf = run_textconv(driver->textconv, df, &size); if (!*outbuf) die("unable to read files to diff"); + + if (driver->textconv_cache) { + /* ignore errors, as we might be in a readonly repository */ + notes_cache_put(driver->textconv_cache, df->sha1, *outbuf, + size); + /* + * we could save up changes and flush them all at the end, + * but we would need an extra call after all diffing is done. + * Since generating a cache entry is the slow path anyway, + * this extra overhead probably isn't a big deal. + */ + notes_cache_write(driver->textconv_cache); + } + return size; } diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh new file mode 100755 index 0000000000..91f8198f05 --- /dev/null +++ b/t/t4042-diff-textconv-caching.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +test_description='test textconv caching' +. ./test-lib.sh + +cat >helper <<'EOF' +#!/bin/sh +sed 's/^/converted: /' "$@" >helper.out +cat helper.out +EOF +chmod +x helper + +test_expect_success 'setup' ' + echo foo content 1 >foo.bin && + echo bar content 1 >bar.bin && + git add . && + git commit -m one && + echo foo content 2 >foo.bin && + echo bar content 2 >bar.bin && + git commit -a -m two && + echo "*.bin diff=magic" >.gitattributes && + git config diff.magic.textconv ./helper && + git config diff.magic.cachetextconv true +' + +cat >expect <actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv produces same output' ' + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'cached textconv does not run helper' ' + rm -f helper.out && + git diff HEAD^ HEAD >actual && + test_cmp expect actual && + ! test -r helper.out +' + +cat >expect <other && + git config diff.magic.textconv "./helper other" && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +cat >expect <>.gitattributes && + git diff HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_done diff --git a/userdiff.c b/userdiff.c index df992490d5..67003fbb23 100644 --- a/userdiff.c +++ b/userdiff.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "userdiff.h" #include "cache.h" #include "attr.h" @@ -167,6 +168,12 @@ static int parse_tristate(int *b, const char *k, const char *v) return 1; } +static int parse_bool(int *b, const char *k, const char *v) +{ + *b = git_config_bool(k, v); + return 1; +} + int userdiff_config(const char *k, const char *v) { struct userdiff_driver *drv; @@ -181,6 +188,8 @@ int userdiff_config(const char *k, const char *v) return parse_string(&drv->external, k, v); if ((drv = parse_driver(k, v, "textconv"))) return parse_string(&drv->textconv, k, v); + if ((drv = parse_driver(k, v, "cachetextconv"))) + return parse_bool(&drv->textconv_want_cache, k, v); if ((drv = parse_driver(k, v, "wordregex"))) return parse_string(&drv->word_regex, k, v); diff --git a/userdiff.h b/userdiff.h index c3151594f5..942d594950 100644 --- a/userdiff.h +++ b/userdiff.h @@ -1,6 +1,8 @@ #ifndef USERDIFF_H #define USERDIFF_H +#include "notes-cache.h" + struct userdiff_funcname { const char *pattern; int cflags; @@ -13,6 +15,8 @@ struct userdiff_driver { struct userdiff_funcname funcname; const char *word_regex; const char *textconv; + struct notes_cache *textconv_cache; + int textconv_want_cache; }; int userdiff_config(const char *k, const char *v); -- cgit v1.3 From b3373982667dc983b8dacf33861d25b20bafb995 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 1 Apr 2010 20:14:24 -0400 Subject: diff: avoid useless filespec population builtin_diff calls fill_mmfile fairly early, which in turn calls diff_populate_filespec, which actually retrieves the file's blob contents into a buffer. Long ago, this was sensible as we would need to look at the blobs eventually. These days, however, we may not ever want those blobs if we end up using a textconv cache, and for large binary files (exactly the sort for which you might have a textconv cache), just retrieving the objects can be costly. This patch just pushes the fill_mmfile call a bit later, so we can avoid populating the filespec in some cases. There is one thing to note that looks like a bug but isn't. We push the fill_mmfile down into the first branch of a conditional. It seems like we would need it on the other branch, too, but we don't; fill_textconv does it for us (in fact, before this, we were just writing over the results of the fill_mmfile on that branch). Here's a timing sample on a commit with 45 changed jpgs and avis. The result is fully textconv cached, but we still wasted a lot of time just pulling the blobs from storage. The total size of the blobs (source and dest) is about 180M. [before] $ time git show >/dev/null real 0m0.352s user 0m0.148s sys 0m0.200s [after] $ time git show >/dev/null real 0m0.009s user 0m0.004s sys 0m0.004s And that's on a warm cache. On a cold cache, the "after" case is not much worse, but the "before" case has to do an extra 180M of I/O. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 72d8503d89..4cb6d9a9e8 100644 --- a/diff.c +++ b/diff.c @@ -1680,12 +1680,11 @@ static void builtin_diff(const char *name_a, } } - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - if (!DIFF_OPT_TST(o, TEXT) && - ( (diff_filespec_is_binary(one) && !textconv_one) || - (diff_filespec_is_binary(two) && !textconv_two) )) { + ( (!textconv_one && diff_filespec_is_binary(one)) || + (!textconv_two && diff_filespec_is_binary(two)) )) { + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); /* Quite common confusing case */ if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) -- cgit v1.3 From aed6ca52e73a35d0aac9aba7d631e09983e46a6f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 8 Apr 2010 23:30:49 -0700 Subject: diff.c: work around pointer constness warnings The textconv leak fix introduced two invocations of free() to release memory pointed by "const char *", which get annoying compiler warning. --- diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index db2cd5d35f..c23093d6c0 100644 --- a/diff.c +++ b/diff.c @@ -551,9 +551,9 @@ static void emit_rewrite_diff(const char *name_a, if (lc_b) emit_rewrite_lines(&ecbdata, '+', data_two, size_two); if (textconv_one) - free(data_one); + free((char *)data_one); if (textconv_two) - free(data_two); + free((char *)data_two); } struct diff_words_buffer { -- cgit v1.3 From 882749a04f828fccd795deec4d0bf10ba09ae549 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Wed, 14 Apr 2010 17:59:06 +0200 Subject: diff: add --word-diff option that generalizes --color-words This teaches the --color-words engine a more general interface that supports two new modes: * --word-diff=plain, inspired by the 'wdiff' utility (most similar to 'wdiff -n '): uses delimiters [-removed-] and {+added+} * --word-diff=porcelain, which generates an ad-hoc machine readable format: - each diff unit is prefixed by [-+ ] and terminated by newline as in unified diff - newlines in the input are output as a line consisting only of a tilde '~' Both of these formats still support color if it is enabled, using it to highlight the differences. --color-words becomes a synonym for --word-diff=color, which is the color-only format. Also adds some compatibility/convenience options. Thanks to Junio C Hamano and Miles Bader for good ideas. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 40 ++++++++++-- Documentation/gitattributes.txt | 2 +- color.c | 28 -------- color.h | 1 - diff.c | 139 ++++++++++++++++++++++++++++++++++------ diff.h | 10 ++- t/t4034-diff-words.sh | 122 +++++++++++++++++++++++++++++++++++ 7 files changed, 288 insertions(+), 54 deletions(-) (limited to 'diff.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 60e922e6ef..a616ca589f 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -126,11 +126,39 @@ any of those replacements occurred. gives the default to color output. Same as `--color=never`. ---color-words[=]:: - Show colored word diff, i.e., color words which have changed. - By default, words are separated by whitespace. +--word-diff[=]:: + Show a word diff, using the to delimit changed words. + By default, words are delimited by whitespace; see + `--word-diff-regex` below. The defaults to 'plain', and + must be one of: ++ +-- +color:: + Highlight changed words using only colors. Implies `--color`. +plain:: + Show words as `[-removed-]` and `{+added+}`. Makes no + attempts to escape the delimiters if they appear in the input, + so the output may be ambiguous. +porcelain:: + Use a special line-based format intended for script + consumption. Added/removed/unchanged runs are printed in the + usual unified diff format, starting with a `+`/`-`/` ` + character at the beginning of the line and extending to the + end of the line. Newlines in the input are represented by a + tilde `~` on a line of its own. +none:: + Disable word diff again. +-- ++ +Note that despite the name of the first mode, color is used to +highlight the changed parts in all modes if enabled. + +--word-diff-regex=:: + Use to decide what a word is, instead of considering + runs of non-whitespace to be a word. Also implies + `--word-diff` unless it was already enabled. + -When a is specified, every non-overlapping match of the +Every non-overlapping match of the is considered a word. Anything between these matches is considered whitespace and ignored(!) for the purposes of finding differences. You may want to append `|[^[:space:]]` to your regular @@ -142,6 +170,10 @@ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. + +--color-words[=]:: + Equivalent to `--word-diff=color` plus (if a regex was + specified) `--word-diff-regex=`. endif::git-format-patch[] --no-renames:: diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index d892e642ed..7554fcd07f 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -360,7 +360,7 @@ patterns are available: Customizing word diff ^^^^^^^^^^^^^^^^^^^^^ -You can customize the rules that `git diff --color-words` uses to +You can customize the rules that `git diff --word-diff` uses to split words in a line, by specifying an appropriate regular expression in the "diff.*.wordRegex" configuration variable. For example, in TeX a backslash followed by a sequence of letters forms a command, but diff --git a/color.c b/color.c index bcf4e2c192..1b00554dd5 100644 --- a/color.c +++ b/color.c @@ -211,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) va_end(args); return r; } - -/* - * This function splits the buffer by newlines and colors the lines individually. - * - * Returns 0 on success. - */ -int color_fwrite_lines(FILE *fp, const char *color, - size_t count, const char *buf) -{ - if (!*color) - return fwrite(buf, count, 1, fp) != 1; - while (count) { - char *p = memchr(buf, '\n', count); - if (p != buf && (fputs(color, fp) < 0 || - fwrite(buf, p ? p - buf : count, 1, fp) != 1 || - fputs(GIT_COLOR_RESET, fp) < 0)) - return -1; - if (!p) - return 0; - if (fputc('\n', fp) < 0) - return -1; - count -= p + 1 - buf; - buf = p + 1; - } - return 0; -} - - diff --git a/color.h b/color.h index 5c264b0ce3..03ca064748 100644 --- a/color.h +++ b/color.h @@ -61,6 +61,5 @@ __attribute__((format (printf, 3, 4))) int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); __attribute__((format (printf, 3, 4))) int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); -int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); #endif /* COLOR_H */ diff --git a/diff.c b/diff.c index 2daa732a36..d81a57a3c4 100644 --- a/diff.c +++ b/diff.c @@ -572,16 +572,68 @@ static void diff_words_append(char *line, unsigned long len, buffer->text.ptr[buffer->text.size] = '\0'; } +struct diff_words_style_elem +{ + const char *prefix; + const char *suffix; + const char *color; /* NULL; filled in by the setup code if + * color is enabled */ +}; + +struct diff_words_style +{ + enum diff_words_type type; + struct diff_words_style_elem new, old, ctx; + const char *newline; +}; + +struct diff_words_style diff_words_styles[] = { + { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" }, + { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" }, + { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" } +}; + struct diff_words_data { struct diff_words_buffer minus, plus; const char *current_plus; FILE *file; regex_t *word_regex; + enum diff_words_type type; + struct diff_words_style *style; }; +static int fn_out_diff_words_write_helper(FILE *fp, + struct diff_words_style_elem *st_el, + const char *newline, + size_t count, const char *buf) +{ + while (count) { + char *p = memchr(buf, '\n', count); + if (p != buf) { + if (st_el->color && fputs(st_el->color, fp) < 0) + return -1; + if (fputs(st_el->prefix, fp) < 0 || + fwrite(buf, p ? p - buf : count, 1, fp) != 1 || + fputs(st_el->suffix, fp) < 0) + return -1; + if (st_el->color && *st_el->color + && fputs(GIT_COLOR_RESET, fp) < 0) + return -1; + } + if (!p) + return 0; + if (fputs(newline, fp) < 0) + return -1; + count -= p + 1 - buf; + buf = p + 1; + } + return 0; +} + static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) { struct diff_words_data *diff_words = priv; + struct diff_words_style *style = diff_words->style; int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; @@ -605,16 +657,17 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) plus_begin = plus_end = diff_words->plus.orig[plus_first].end; if (diff_words->current_plus != plus_begin) - fwrite(diff_words->current_plus, - plus_begin - diff_words->current_plus, 1, - diff_words->file); + fn_out_diff_words_write_helper(diff_words->file, + &style->ctx, style->newline, + plus_begin - diff_words->current_plus, + diff_words->current_plus); if (minus_begin != minus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), + fn_out_diff_words_write_helper(diff_words->file, + &style->old, style->newline, minus_end - minus_begin, minus_begin); if (plus_begin != plus_end) - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_NEW), + fn_out_diff_words_write_helper(diff_words->file, + &style->new, style->newline, plus_end - plus_begin, plus_begin); diff_words->current_plus = plus_end; @@ -697,11 +750,12 @@ static void diff_words_show(struct diff_words_data *diff_words) xdemitconf_t xecfg; xdemitcb_t ecb; mmfile_t minus, plus; + struct diff_words_style *style = diff_words->style; /* special case: only removal */ if (!diff_words->plus.text.size) { - color_fwrite_lines(diff_words->file, - diff_get_color(1, DIFF_FILE_OLD), + fn_out_diff_words_write_helper(diff_words->file, + &style->old, style->newline, diff_words->minus.text.size, diff_words->minus.text.ptr); diff_words->minus.text.size = 0; return; @@ -722,10 +776,10 @@ static void diff_words_show(struct diff_words_data *diff_words) free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + diff_words->plus.text.size) - fwrite(diff_words->current_plus, + fn_out_diff_words_write_helper(diff_words->file, + &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, 1, - diff_words->file); + - diff_words->current_plus, diff_words->current_plus); diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -837,6 +891,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); + if (ecbdata->diff_words + && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) + fputs("~\n", ecbdata->file); return; } @@ -851,9 +908,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } diff_words_flush(ecbdata); - line++; - len--; - emit_line(ecbdata->file, plain, reset, line, len); + if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { + emit_line(ecbdata->file, plain, reset, line, len); + fputs("~\n", ecbdata->file); + } else { + /* don't print the prefix character */ + emit_line(ecbdata->file, plain, reset, line+1, len-1); + } return; } @@ -1755,10 +1816,13 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { + if (o->word_diff) { + int i; + ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); ecbdata.diff_words->file = o->file; + ecbdata.diff_words->type = o->word_diff; if (!o->word_regex) o->word_regex = userdiff_word_regex(one); if (!o->word_regex) @@ -1774,10 +1838,23 @@ static void builtin_diff(const char *name_a, die ("Invalid regular expression: %s", o->word_regex); } + for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) { + if (o->word_diff == diff_words_styles[i].type) { + ecbdata.diff_words->style = + &diff_words_styles[i]; + break; + } + } + if (DIFF_OPT_TST(o, COLOR_DIFF)) { + struct diff_words_style *st = ecbdata.diff_words->style; + st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD); + st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW); + st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN); + } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, &xpp, &xecfg, &ecb); - if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) + if (o->word_diff) free_diff_words_data(&ecbdata); if (textconv_one) free(mf1.ptr); @@ -2845,13 +2922,37 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; } else if (!prefixcmp(arg, "--color-words=")) { DIFF_OPT_SET(options, COLOR_DIFF); - DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + options->word_diff = DIFF_WORDS_COLOR; options->word_regex = arg + 14; } + else if (!strcmp(arg, "--word-diff")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + } + else if (!prefixcmp(arg, "--word-diff=")) { + const char *type = arg + 12; + if (!strcmp(type, "plain")) + options->word_diff = DIFF_WORDS_PLAIN; + else if (!strcmp(type, "color")) { + DIFF_OPT_SET(options, COLOR_DIFF); + options->word_diff = DIFF_WORDS_COLOR; + } + else if (!strcmp(type, "porcelain")) + options->word_diff = DIFF_WORDS_PORCELAIN; + else if (!strcmp(type, "none")) + options->word_diff = DIFF_WORDS_NONE; + else + die("bad --word-diff argument: %s", type); + } + else if (!prefixcmp(arg, "--word-diff-regex=")) { + if (options->word_diff == DIFF_WORDS_NONE) + options->word_diff = DIFF_WORDS_PLAIN; + options->word_regex = arg + 18; + } else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) diff --git a/diff.h b/diff.h index 6a71013dc6..9ace08cbae 100644 --- a/diff.h +++ b/diff.h @@ -54,7 +54,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_FIND_COPIES_HARDER (1 << 6) #define DIFF_OPT_FOLLOW_RENAMES (1 << 7) #define DIFF_OPT_COLOR_DIFF (1 << 8) -#define DIFF_OPT_COLOR_DIFF_WORDS (1 << 9) +/* (1 << 9) unused */ #define DIFF_OPT_HAS_CHANGES (1 << 10) #define DIFF_OPT_QUICK (1 << 11) #define DIFF_OPT_NO_INDEX (1 << 12) @@ -79,6 +79,13 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag) #define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag) +enum diff_words_type { + DIFF_WORDS_NONE = 0, + DIFF_WORDS_PORCELAIN, + DIFF_WORDS_PLAIN, + DIFF_WORDS_COLOR +}; + struct diff_options { const char *filter; const char *orderfile; @@ -108,6 +115,7 @@ struct diff_options { int stat_width; int stat_name_width; const char *word_regex; + enum diff_words_type word_diff; /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 2e2e103b31..6f7548c3a1 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -55,6 +55,93 @@ test_expect_success 'word diff with runs of whitespace' ' ' +test_expect_success '--word-diff=color' ' + + word_diff --word-diff=color + +' + +test_expect_success '--color --word-diff=color' ' + + word_diff --color --word-diff=color + +' + +sed 's/#.*$//' > expect < expect < expect <diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +[-h(4)-]{+h(4),hh[44]+} + +a = b + c + +{+aa = a+} + +{+aeff = aeff * ( aaa )+} +EOF + +test_expect_success '--word-diff=plain --color' ' + + word_diff --word-diff=plain --color + +' + cat > expect <<\EOF diff --git a/pre b/post index 330b04f..5ed8eff 100644 @@ -143,6 +230,25 @@ test_expect_success 'command-line overrides config' ' word_diff --color-words="[a-z]+" ' +cat > expect <<\EOF +diff --git a/pre b/post +index 330b04f..5ed8eff 100644 +--- a/pre ++++ b/post +@@ -1,3 +1,7 @@ +h(4),{+hh+}[44] + +a = b + c + +{+aa = a+} + +{+aeff = aeff * ( aaa+} ) +EOF + +test_expect_success 'command-line overrides config: --word-diff-regex' ' + word_diff --color --word-diff-regex="[a-z]+" +' + cp expect.non-whitespace-is-word expect test_expect_success '.gitattributes override config' ' @@ -209,4 +315,20 @@ test_expect_success 'test when words are only removed at the end' ' ' +cat > expect <<\EOF +diff --git a/pre b/post +index 289cb9d..2d06f37 100644 +--- a/pre ++++ b/post +@@ -1 +1 @@ +-(: ++( +EOF + +test_expect_success '--word-diff=none' ' + + word_diff --word-diff=plain --word-diff=none + +' + test_done -- cgit v1.3 From 0974c117ff4e17e8b6300519cae0fbc67d34adaa Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 17 Apr 2010 13:41:08 -0400 Subject: diff: use large integers for diffstat calculations The diffstat "added" and "changed" fields generally store line counts; however, for binary files, they store file sizes. Since we store and print these values as ints, a diffstat on a file larger than 2G can show a negative size. Instead, let's use uintmax_t, which should be at least 64 bits on modern platforms. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 08bbd3e907..0182237c2d 100644 --- a/diff.c +++ b/diff.c @@ -942,7 +942,7 @@ struct diffstat_t { unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; - unsigned int added, deleted; + uintmax_t added, deleted; } **files; }; @@ -1034,7 +1034,7 @@ static void fill_print_name(struct diffstat_file *file) static void show_stats(struct diffstat_t *data, struct diff_options *options) { int i, len, add, del, adds = 0, dels = 0; - int max_change = 0, max_len = 0; + uintmax_t max_change = 0, max_len = 0; int total_files = data->nr; int width, name_width; const char *reset, *set, *add_c, *del_c; @@ -1063,7 +1063,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - int change = file->added + file->deleted; + uintmax_t change = file->added + file->deleted; fill_print_name(file); len = strlen(file->print_name); if (max_len < len) @@ -1091,8 +1091,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { const char *prefix = ""; char *name = data->files[i]->print_name; - int added = data->files[i]->added; - int deleted = data->files[i]->deleted; + uintmax_t added = data->files[i]->added; + uintmax_t deleted = data->files[i]->deleted; int name_len; /* @@ -1113,9 +1113,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (data->files[i]->is_binary) { show_name(options->file, prefix, name, len); fprintf(options->file, " Bin "); - fprintf(options->file, "%s%d%s", del_c, deleted, reset); + fprintf(options->file, "%s%"PRIuMAX"%s", + del_c, deleted, reset); fprintf(options->file, " -> "); - fprintf(options->file, "%s%d%s", add_c, added, reset); + fprintf(options->file, "%s%"PRIuMAX"%s", + add_c, added, reset); fprintf(options->file, " bytes"); fprintf(options->file, "\n"); continue; @@ -1144,7 +1146,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) del = scale_linear(del, width, max_change); } show_name(options->file, prefix, name, len); - fprintf(options->file, "%5d%s", added + deleted, + fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, added + deleted ? " " : ""); show_graph(options->file, '+', add, add_c, reset); show_graph(options->file, '-', del, del_c, reset); @@ -1194,7 +1196,8 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) fprintf(options->file, "-\t-\t"); else fprintf(options->file, - "%d\t%d\t", file->added, file->deleted); + "%"PRIuMAX"\t%"PRIuMAX"\t", + file->added, file->deleted); if (options->line_termination) { fill_print_name(file); if (!file->is_renamed) -- cgit v1.3 From 582aa00bdffb27abcf1b27d541b4c231a395d3b8 Mon Sep 17 00:00:00 2001 From: René Scharfe Date: Sun, 2 May 2010 15:04:41 +0200 Subject: git diff too slow for a file Ever since the xdiff library had been introduced to git, all its callers have used the flag XDF_NEED_MINIMAL. It makes sure that the smallest possible diff is produced, but that takes quite some time if there are lots of differences that can be expressed in multiple ways. This flag makes a difference for only 0.1% of the non-merge commits in the git repo of Linux, both in terms of diff size and execution time. The patches there are mostly nice and small. SungHyun Nam however reported a case in a different repo where a diff took more than 20 times longer to generate with XDF_NEED_MINIMAL than without. Rebasing became really slow. This patch removes this flag from all callers. The default of xdiff is saner because it has minimal to no impact in the normal case of small diffs and doesn't incur that much of a speed penalty for large ones. A follow-up patch may introduce a command line option to set the flag if the user needs it, similar to GNU diff's -d/--minimal. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- builtin-blame.c | 2 +- builtin-merge-file.c | 2 +- builtin-merge-tree.c | 2 +- builtin-rerere.c | 2 +- combine-diff.c | 2 +- diff.c | 10 +++++----- merge-file.c | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) (limited to 'diff.c') diff --git a/builtin-blame.c b/builtin-blame.c index fc1586350f..8deeee12b6 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -39,7 +39,7 @@ static int show_root; static int reverse; static int blank_boundary; static int incremental; -static int xdl_opts = XDF_NEED_MINIMAL; +static int xdl_opts; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 1e70073a7e..e5e860fa78 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -25,7 +25,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) const char *names[3] = { NULL, NULL, NULL }; mmfile_t mmfs[3]; mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{XDF_NEED_MINIMAL}}; + xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int level = XDL_MERGE_ZEALOUS_ALNUM; int style = 0, quiet = 0; diff --git a/builtin-merge-tree.c b/builtin-merge-tree.c index a4a4f2ce4c..fc00d794d6 100644 --- a/builtin-merge-tree.c +++ b/builtin-merge-tree.c @@ -106,7 +106,7 @@ static void show_diff(struct merge_list *entry) xdemitconf_t xecfg; xdemitcb_t ecb; - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = show_outf; diff --git a/builtin-rerere.c b/builtin-rerere.c index 34f9acee91..0048f9ef7f 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -89,7 +89,7 @@ static int diff_two(const char *file1, const char *label1, printf("--- a/%s\n+++ b/%s\n", label1, label2); fflush(stdout); memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; ecb.outf = outf; diff --git a/combine-diff.c b/combine-diff.c index 3480dae824..13a8128961 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -221,7 +221,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, parent_file.ptr = grab_blob(parent, mode, &sz); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); state.nmask = nmask; diff --git a/diff.c b/diff.c index edec0f6b81..0924274dd3 100644 --- a/diff.c +++ b/diff.c @@ -714,7 +714,7 @@ static void diff_words_show(struct diff_words_data *diff_words) memset(&xecfg, 0, sizeof(xecfg)); diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex); diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, @@ -1743,7 +1743,7 @@ static void builtin_diff(const char *name_a, check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; ecbdata.header = header.len ? &header : NULL; - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1833,7 +1833,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); - xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; + xpp.flags = o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, &xpp, &xecfg, &ecb); } @@ -1882,7 +1882,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, &xpp, &xecfg, &ecb); @@ -3419,7 +3419,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) len2, p->two->path); git_SHA1_Update(&ctx, buffer, len1); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, diff --git a/merge-file.c b/merge-file.c index fd34d76e15..cafc274e2d 100644 --- a/merge-file.c +++ b/merge-file.c @@ -60,7 +60,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = XDF_NEED_MINIMAL; + xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_COMMON; -- cgit v1.3 From dfea79004c54bc96143386d6ac22de500ba4f747 Mon Sep 17 00:00:00 2001 From: René Scharfe Date: Tue, 4 May 2010 22:41:34 +0200 Subject: remove ecb parameter from xdi_diff_outf() xdi_diff_outf() overrides the structure members of its last parameter, ignoring any value that callers pass in. It's no surprise then that all callers pass a pointer to an uninitialized structure. They also don't read it after the call, so the parameter is neither used for input nor for output. Turn it into a local variable of xdi_diff_outf(). Signed-off-by: Rene Scharfe Acked-by: Jeff King Signed-off-by: Junio C Hamano --- combine-diff.c | 3 +-- diff.c | 15 +++++---------- xdiff-interface.c | 11 ++++++----- xdiff-interface.h | 3 +-- 4 files changed, 13 insertions(+), 19 deletions(-) (limited to 'diff.c') diff --git a/combine-diff.c b/combine-diff.c index 3480dae824..7557136c82 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -211,7 +211,6 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, xpparam_t xpp; xdemitconf_t xecfg; mmfile_t parent_file; - xdemitcb_t ecb; struct combine_diff_state state; unsigned long sz; @@ -231,7 +230,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, state.n = n; xdi_diff_outf(&parent_file, result_file, consume_line, &state, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); free(parent_file.ptr); /* Assign line numbers for this parent. diff --git a/diff.c b/diff.c index edec0f6b81..a2d8c7f9a7 100644 --- a/diff.c +++ b/diff.c @@ -696,7 +696,6 @@ static void diff_words_show(struct diff_words_data *diff_words) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t minus, plus; /* special case: only removal */ @@ -718,7 +717,7 @@ static void diff_words_show(struct diff_words_data *diff_words) /* as only the hunk header will be parsed, we need a 0-context */ xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + @@ -1704,7 +1703,6 @@ static void builtin_diff(const char *name_a, const char *diffopts = getenv("GIT_DIFF_OPTS"); xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; struct emit_callback ecbdata; const struct userdiff_funcname *pe; @@ -1776,7 +1774,7 @@ static void builtin_diff(const char *name_a, } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); if (textconv_one) @@ -1829,13 +1827,12 @@ static void builtin_diffstat(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } free_and_return: @@ -1877,14 +1874,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, /* Crazy xdl interfaces.. */ xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = XDF_NEED_MINIMAL; xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); if (data.ws_rule & WS_BLANK_AT_EOF) { struct emit_callback ecbdata; @@ -3361,7 +3357,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) for (i = 0; i < q->nr; i++) { xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; mmfile_t mf1, mf2; struct diff_filepair *p = q->queue[i]; int len1, len2; @@ -3423,7 +3418,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, - &xpp, &xecfg, &ecb); + &xpp, &xecfg); } git_SHA1_Final(sha1, &ctx); diff --git a/xdiff-interface.c b/xdiff-interface.c index 01f14fb50f..3cf39c39c4 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -138,19 +138,20 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_consume_fn fn, void *consume_callback_data, - xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *xecb) + xpparam_t const *xpp, xdemitconf_t const *xecfg) { int ret; struct xdiff_emit_state state; + xdemitcb_t ecb; memset(&state, 0, sizeof(state)); state.consume = fn; state.consume_callback_data = consume_callback_data; - xecb->outf = xdiff_outf; - xecb->priv = &state; + memset(&ecb, 0, sizeof(ecb)); + ecb.outf = xdiff_outf; + ecb.priv = &state; strbuf_init(&state.remainder, 0); - ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb); + ret = xdi_diff(mf1, mf2, xpp, xecfg, &ecb); strbuf_release(&state.remainder); return ret; } diff --git a/xdiff-interface.h b/xdiff-interface.h index 55572c39a1..0cd4511bf7 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -9,8 +9,7 @@ typedef void (*xdiff_emit_hunk_consume_fn)(void *, long, long, long); int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_consume_fn fn, void *consume_callback_data, - xpparam_t const *xpp, - xdemitconf_t const *xecfg, xdemitcb_t *xecb); + xpparam_t const *xpp, xdemitconf_t const *xecfg); int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2, xdiff_emit_hunk_consume_fn fn, void *consume_callback_data, xpparam_t const *xpp, xdemitconf_t *xecfg); -- cgit v1.3 From 9ca5df90615aa3c6b60e1bc8f03db6cae98e816c Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Thu, 6 May 2010 21:52:27 -0700 Subject: Add a macro DIFF_QUEUE_CLEAR. Refactor the diff_queue_struct code, this macro help to reset the structure. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 13 +++++-------- diffcore-break.c | 6 ++---- diffcore-pickaxe.c | 3 +-- diffcore-rename.c | 3 +-- diffcore.h | 5 +++++ 5 files changed, 14 insertions(+), 16 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index e40c1271da..4a350e365e 100644 --- a/diff.c +++ b/diff.c @@ -2540,6 +2540,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) void diff_setup(struct diff_options *options) { memset(options, 0, sizeof(*options)); + memset(&diff_queued_diff, 0, sizeof(diff_queued_diff)); options->file = stdout; @@ -3457,8 +3458,7 @@ int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1) diff_free_filepair(q->queue[i]); free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); return result; } @@ -3586,8 +3586,7 @@ void diff_flush(struct diff_options *options) diff_free_filepair(q->queue[i]); free_queue: free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; + DIFF_QUEUE_CLEAR(q); if (options->close_file) fclose(options->file); @@ -3609,8 +3608,7 @@ 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; + DIFF_QUEUE_CLEAR(&outq); if (!filter) return; @@ -3678,8 +3676,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) int i; struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; diff --git a/diffcore-break.c b/diffcore-break.c index 3a7b60a037..44f8678d22 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -162,8 +162,7 @@ void diffcore_break(int break_score) if (!merge_score) merge_score = DEFAULT_MERGE_SCORE; - outq.nr = outq.alloc = 0; - outq.queue = NULL; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -256,8 +255,7 @@ void diffcore_merge_broken(void) struct diff_queue_struct outq; int i, j; - outq.nr = outq.alloc = 0; - outq.queue = NULL; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index d0ef839700..929de15aa9 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -55,8 +55,7 @@ void diffcore_pickaxe(const char *needle, int opts) int i, has_changes; regex_t regex, *regexp = NULL; struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); if (opts & DIFF_PICKAXE_REGEX) { int err; diff --git a/diffcore-rename.c b/diffcore-rename.c index d6fd3cacd6..df41be56de 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -569,8 +569,7 @@ void diffcore_rename(struct diff_options *options) /* At this point, we have found some renames and copies and they * are recorded in rename_dst. The original list is still in *q. */ - outq.queue = NULL; - outq.nr = outq.alloc = 0; + DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; struct diff_filepair *pair_to_free = NULL; diff --git a/diffcore.h b/diffcore.h index fcd00bf27a..5d05deaf68 100644 --- a/diffcore.h +++ b/diffcore.h @@ -92,6 +92,11 @@ struct diff_queue_struct { int alloc; int nr; }; +#define DIFF_QUEUE_CLEAR(q) \ + do { \ + (q)->queue = NULL; \ + (q)->nr = (q)->alloc = 0; \ + } while(0); extern struct diff_queue_struct diff_queued_diff; extern struct diff_filepair *diff_queue(struct diff_queue_struct *, -- cgit v1.3 From 1da6175d438a9849db07a68326ee05f291510074 Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Thu, 6 May 2010 21:52:28 -0700 Subject: Make diffcore_std only can run once before a diff_flush When file renames/copies detection is turned on, the second diffcore_std will degrade a 'C' pair to a 'R' pair. And this may happen when we run 'git log --follow' with hard copies finding. That is, the try_to_follow_renames() will run diffcore_std to find the copies, and then 'git log' will issue another diffcore_std, which will reduce 'src->rename_used' and recognize this copy as a rename. This is not what we want. So, I think we really don't need to run diffcore_std more than one time. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 8 ++++++++ diffcore.h | 2 ++ 2 files changed, 10 insertions(+) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 4a350e365e..f0985bc76a 100644 --- a/diff.c +++ b/diff.c @@ -3737,6 +3737,12 @@ void diffcore_fix_diff_index(struct diff_options *options) void diffcore_std(struct diff_options *options) { + /* We never run this function more than one time, because the + * rename/copy detection logic can only run once. + */ + if (diff_queued_diff.run) + return; + if (options->skip_stat_unmatch) diffcore_skip_stat_unmatch(options); if (options->break_opt != -1) @@ -3756,6 +3762,8 @@ void diffcore_std(struct diff_options *options) DIFF_OPT_SET(options, HAS_CHANGES); else DIFF_OPT_CLR(options, HAS_CHANGES); + + diff_queued_diff.run = 1; } int diff_result_code(struct diff_options *opt, int status) diff --git a/diffcore.h b/diffcore.h index 5d05deaf68..491bea0b44 100644 --- a/diffcore.h +++ b/diffcore.h @@ -91,11 +91,13 @@ struct diff_queue_struct { struct diff_filepair **queue; int alloc; int nr; + int run; }; #define DIFF_QUEUE_CLEAR(q) \ do { \ (q)->queue = NULL; \ (q)->nr = (q)->alloc = 0; \ + (q)->run = 0; \ } while(0); extern struct diff_queue_struct diff_queued_diff; -- cgit v1.3 From f89504ddb9392fa6b9407957bf26905fd1d59d0a Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Sun, 2 May 2010 19:03:41 -0700 Subject: diff: add configuration option for disabling diff prefixes. With new configuration "diff.noprefix", "git diff" does not show a source or destination prefix ala "git diff --no-prefix". Signed-off-by: Eli Collins Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 ++ diff.c | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'diff.c') diff --git a/Documentation/config.txt b/Documentation/config.txt index e5aa2cacfa..93ab49772c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -798,6 +798,8 @@ diff.mnemonicprefix:: standard "a/" and "b/" depending on what is being compared. When this configuration is in effect, reverse diff output also swaps the order of the prefixes: +diff.noprefix:: + If set, 'git diff' does not show any source or destination prefix. `git diff`;; compares the (i)ndex and the (w)ork tree; `git diff HEAD`;; diff --git a/diff.c b/diff.c index e49f14a924..6d9928cb96 100644 --- a/diff.c +++ b/diff.c @@ -30,6 +30,7 @@ static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; +static int diff_no_prefix; static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_RESET, @@ -101,6 +102,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_mnemonic_prefix = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.noprefix")) { + diff_no_prefix = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "diff.external")) return git_config_string(&external_diff_cmd_cfg, var, value); if (!strcmp(var, "diff.wordregex")) @@ -2538,7 +2543,9 @@ void diff_setup(struct diff_options *options) DIFF_OPT_SET(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; - if (!diff_mnemonic_prefix) { + if (diff_no_prefix) { + options->a_prefix = options->b_prefix = ""; + } else if (!diff_mnemonic_prefix) { options->a_prefix = "a/"; options->b_prefix = "b/"; } -- cgit v1.3 From 1c9eecff97beab2d425397dc624281dff5c0be5c Mon Sep 17 00:00:00 2001 From: Will Palmer Date: Thu, 13 May 2010 09:59:00 +0100 Subject: diff-options: make --patch a synonym for -p Here we simply make --patch a synonym for -p, whose mnemonic was "patch" all along. Signed-off-by: Will Palmer Reviewed-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 1 + diff.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'diff.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c9c6c2b1cb..4a968591cb 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -21,6 +21,7 @@ endif::git-format-patch[] ifndef::git-format-patch[] -p:: -u:: +--patch:: Generate patch (see section on generating patches). {git-diff? This is the default.} endif::git-format-patch[] diff --git a/diff.c b/diff.c index e49f14a924..43a313deba 100644 --- a/diff.c +++ b/diff.c @@ -2701,7 +2701,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) const char *arg = av[0]; /* Output format options */ - if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) + if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")) options->output_format |= DIFF_FORMAT_PATCH; else if (opt_arg(arg, 'U', "unified", &options->context)) options->output_format |= DIFF_FORMAT_PATCH; -- cgit v1.3 From 374664478f204ab45bbd494ab21492f331d8b1f0 Mon Sep 17 00:00:00 2001 From: Bert Wesarg Date: Tue, 4 May 2010 00:38:07 +0200 Subject: diff: fix coloring of extended diff headers Coloring the extended headers where done as a whole not per line. less with option -R (which is the default from git) does not support this coloring mode because of performance reasons. The -r option would be an alternative but has problems with lines that are longer than the screen. Therefore stick to the idiom to color each line separately. The problem is, that the result of ill_metainfo() will also be used as an parameter to an external diff driver, so we need to disable coloring in this case. Because coloring is now done inside fill_metainfo() we can simply add this string to the diff header and therefore keep the last newline in the extended header. This results also into the fact that the external diff driver now gets this last newline too. Which is a change in behavior but a good one. Signed-off-by: Bert Wesarg Signed-off-by: Junio C Hamano --- diff.c | 61 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 26 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index d0ecbc3540..7e508dd064 100644 --- a/diff.c +++ b/diff.c @@ -1650,21 +1650,21 @@ static void builtin_diff(const char *name_a, if (lbl[0][0] == '/') { /* /dev/null */ strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); } else if (lbl[1][0] == '/') { strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); } else { if (one->mode != two->mode) { strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); } - if (xfrm_msg && xfrm_msg[0]) - strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + if (xfrm_msg) + strbuf_addstr(&header, xfrm_msg); /* * we do not run diff between different kind @@ -2323,30 +2323,36 @@ static void fill_metainfo(struct strbuf *msg, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o, - struct diff_filepair *p) + struct diff_filepair *p, + int use_color) { + const char *set = diff_get_color(use_color, DIFF_METAINFO); + const char *reset = diff_get_color(use_color, DIFF_RESET); + strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\ncopy from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%scopy from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\ncopy to "); + strbuf_addf(msg, "%s\n%scopy to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_RENAMED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\nrename from "); + strbuf_addf(msg, "%ssimilarity index %d%%", + set, similarity_index(p)); + strbuf_addf(msg, "%s\n%srename from ", reset, set); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\nrename to "); + strbuf_addf(msg, "%s\n%srename to ", reset, set); quote_c_style(other, msg, NULL, 0); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); break; case DIFF_STATUS_MODIFIED: if (p->score) { - strbuf_addf(msg, "dissimilarity index %d%%\n", - similarity_index(p)); + strbuf_addf(msg, "%sdissimilarity index %d%%%s\n", + set, similarity_index(p), reset); break; } /* fallthru */ @@ -2363,15 +2369,13 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", + strbuf_addf(msg, "%sindex %.*s..%.*s", set, abbrev, sha1_to_hex(one->sha1), abbrev, sha1_to_hex(two->sha1)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); - strbuf_addch(msg, '\n'); + strbuf_addf(msg, "%s\n", reset); } - if (msg->len) - strbuf_setlen(msg, msg->len - 1); } static void run_diff_cmd(const char *pgm, @@ -2387,11 +2391,6 @@ static void run_diff_cmd(const char *pgm, const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; - if (msg) { - fill_metainfo(msg, name, other, one, two, o, p); - xfrm_msg = msg->len ? msg->buf : NULL; - } - if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; else { @@ -2400,6 +2399,16 @@ static void run_diff_cmd(const char *pgm, pgm = drv->external; } + if (msg) { + /* + * don't use colors when the header is intended for an + * external diff driver + */ + fill_metainfo(msg, name, other, one, two, o, p, + DIFF_OPT_TST(o, COLOR_DIFF) && !pgm); + xfrm_msg = msg->len ? msg->buf : NULL; + } + if (pgm) { run_external_diff(pgm, name, other, one, two, xfrm_msg, complete_rewrite); -- cgit v1.3 From 3e5a188f1d5b48dcc0bc73ad520925cdb846dfaf Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sun, 30 May 2010 15:37:17 +0200 Subject: diff.c: Ensure "index $from..$to" line contains unambiguous SHA1s In the metainfo section of git diffs there's an "index" line providing abbreviated (unless --full-index is used) blob SHA1s from the pre-/post-images used to generate the diff. These provide hints that can be used to reconstruct a 3-way merge when applying the patch (see the --3way option to 'git am' for more details). In order for this to work, however, the blob SHA1s must not be abbreviated into ambiguity. This patch eliminates the possible ambiguity by using find_unique_abbrev() to produce the abbreviated SHA1s (instead of blind abbreviation by way of "%.*s"). A testcase verifying the fix is also included. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- diff.c | 6 +++--- t/t4044-diff-index-unique-abbrev.sh | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100755 t/t4044-diff-index-unique-abbrev.sh (limited to 'diff.c') diff --git a/diff.c b/diff.c index 494f5601e9..1aefa66375 100644 --- a/diff.c +++ b/diff.c @@ -2419,9 +2419,9 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", - abbrev, sha1_to_hex(one->sha1), - abbrev, sha1_to_hex(two->sha1)); + strbuf_addf(msg, "index %s..", + find_unique_abbrev(one->sha1, abbrev)); + strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); strbuf_addch(msg, '\n'); diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh new file mode 100755 index 0000000000..d5ce72be63 --- /dev/null +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='test unique sha1 abbreviation on "index from..to" line' +. ./test-lib.sh + +cat >expect_initial <expect_update < foo && + git add foo && + git commit -m "initial" && + git cat-file -p HEAD: > actual && + test_cmp expect_initial actual && + echo 11742 > foo && + git commit -a -m "update" && + git cat-file -p HEAD: > actual && + test_cmp expect_update actual +' + +cat >expect < actual && + test_cmp expect actual +' + +test_done -- cgit v1.3 From a3c158d4a58b17c1e4a8d3f793344beee21d3a4c Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Wed, 26 May 2010 15:08:02 +0800 Subject: Add a prefix output callback to diff output The callback can be used to add some prefix string to each line of diff output. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 62 +++++++++++++++++++++++++++++++++++--------------------------- diff.h | 5 +++++ 2 files changed, 40 insertions(+), 27 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 494f5601e9..e2f910ad14 100644 --- a/diff.c +++ b/diff.c @@ -194,8 +194,8 @@ struct emit_callback { sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; + struct diff_options *opt; int *found_changesp; - FILE *file; struct strbuf *header; }; @@ -282,11 +282,19 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line_0(FILE *file, const char *set, const char *reset, +static void emit_line_0(struct diff_options *o, const char *set, const char *reset, int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; int nofirst; + FILE *file = o->file; + + if (o->output_prefix) { + struct strbuf *msg = NULL; + msg = o->output_prefix(o, o->output_prefix_data); + assert(msg); + fwrite(msg->buf, msg->len, 1, file); + } if (len == 0) { has_trailing_newline = (first == '\n'); @@ -316,10 +324,10 @@ static void emit_line_0(FILE *file, const char *set, const char *reset, fputc('\n', file); } -static void emit_line(FILE *file, const char *set, const char *reset, +static void emit_line(struct diff_options *o, const char *set, const char *reset, const char *line, int len) { - emit_line_0(file, set, reset, line[0], line+1, len-1); + emit_line_0(o, set, reset, line[0], line+1, len-1); } static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) @@ -341,15 +349,15 @@ static void emit_add_line(const char *reset, const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line_0(ecbdata->file, set, reset, '+', line, len); + emit_line_0(ecbdata->opt, set, reset, '+', line, len); else if (new_blank_line_at_eof(ecbdata, line, len)) /* Blank line at EOF - paint '+' as well */ - emit_line_0(ecbdata->file, ws, reset, '+', line, len); + emit_line_0(ecbdata->opt, ws, reset, '+', line, len); else { /* Emit just the prefix, then the rest. */ - emit_line_0(ecbdata->file, set, reset, '+', "", 0); + emit_line_0(ecbdata->opt, set, reset, '+', "", 0); ws_check_emit(line, len, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); + ecbdata->opt->file, set, reset, ws); } } @@ -370,23 +378,23 @@ static void emit_hunk_header(struct emit_callback *ecbdata, if (len < 10 || memcmp(line, atat, 2) || !(ep = memmem(line + 2, len - 2, atat, 2))) { - emit_line(ecbdata->file, plain, reset, line, len); + emit_line(ecbdata->opt, plain, reset, line, len); return; } ep += 2; /* skip over @@ */ /* The hunk header in fraginfo color */ - emit_line(ecbdata->file, frag, reset, line, ep - line); + emit_line(ecbdata->opt, frag, reset, line, ep - line); /* blank before the func header */ for (cp = ep; ep - line < len; ep++) if (*ep != ' ' && *ep != '\t') break; if (ep != cp) - emit_line(ecbdata->file, plain, reset, cp, ep - cp); + emit_line(ecbdata->opt, plain, reset, cp, ep - cp); if (ep < line + len) - emit_line(ecbdata->file, func, reset, ep, line + len - ep); + emit_line(ecbdata->opt, func, reset, ep, line + len - ep); } static struct diff_tempfile *claim_diff_tempfile(void) { @@ -446,7 +454,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, len = endp ? (endp - data + 1) : size; if (prefix != '+') { ecb->lno_in_preimage++; - emit_line_0(ecb->file, old, reset, '-', + emit_line_0(ecb->opt, old, reset, '-', data, len); } else { ecb->lno_in_postimage++; @@ -458,7 +466,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb, if (!endp) { const char *plain = diff_get_color(ecb->color_diff, DIFF_PLAIN); - emit_line_0(ecb->file, plain, reset, '\\', + emit_line_0(ecb->opt, plain, reset, '\\', nneof, strlen(nneof)); } } @@ -508,7 +516,7 @@ static void emit_rewrite_diff(const char *name_a, ecbdata.color_diff = color_diff; ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); - ecbdata.file = o->file; + ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; mf1.ptr = (char *)data_one; @@ -840,7 +848,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); if (ecbdata->header) { - fprintf(ecbdata->file, "%s", ecbdata->header->buf); + fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); strbuf_reset(ecbdata->header); ecbdata->header = NULL; } @@ -852,9 +860,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - fprintf(ecbdata->file, "%s--- %s%s%s\n", + fprintf(ecbdata->opt->file, "%s--- %s%s%s\n", meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(ecbdata->file, "%s+++ %s%s%s\n", + fprintf(ecbdata->opt->file, "%s+++ %s%s%s\n", meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -872,15 +880,15 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); if (line[len-1] != '\n') - putc('\n', ecbdata->file); + putc('\n', ecbdata->opt->file); return; } if (len < 1) { - emit_line(ecbdata->file, reset, reset, line, len); + emit_line(ecbdata->opt, reset, reset, line, len); if (ecbdata->diff_words && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) - fputs("~\n", ecbdata->file); + fputs("~\n", ecbdata->opt->file); return; } @@ -896,11 +904,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) } diff_words_flush(ecbdata); if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { - emit_line(ecbdata->file, plain, reset, line, len); - fputs("~\n", ecbdata->file); + emit_line(ecbdata->opt, plain, reset, line, len); + fputs("~\n", ecbdata->opt->file); } else { /* don't print the prefix character */ - emit_line(ecbdata->file, plain, reset, line+1, len-1); + emit_line(ecbdata->opt, plain, reset, line+1, len-1); } return; } @@ -912,7 +920,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->lno_in_preimage++; if (line[0] == ' ') ecbdata->lno_in_postimage++; - emit_line(ecbdata->file, color, reset, line, len); + emit_line(ecbdata->opt, color, reset, line, len); } else { ecbdata->lno_in_postimage++; emit_add_line(reset, ecbdata, line + 1, len - 1); @@ -1477,7 +1485,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) fprintf(data->o->file, "%s:%d: %s.\n", data->filename, data->lineno, err); free(err); - emit_line(data->o->file, set, reset, line, 1); + emit_line(data->o, set, reset, line, 1); ws_check_emit(line + 1, len - 1, data->ws_rule, data->o->file, set, reset, ws); } else if (line[0] == ' ') { @@ -1787,7 +1795,7 @@ static void builtin_diff(const char *name_a, ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); - ecbdata.file = o->file; + ecbdata.opt = o; ecbdata.header = header.len ? &header : NULL; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; diff --git a/diff.h b/diff.h index 9ace08cbae..b4eefa759d 100644 --- a/diff.h +++ b/diff.h @@ -9,6 +9,7 @@ struct rev_info; struct diff_options; struct diff_queue_struct; +struct strbuf; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -25,6 +26,8 @@ typedef void (*add_remove_fn_t)(struct diff_options *options, typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, struct diff_options *options, void *data); +typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data); + #define DIFF_FORMAT_RAW 0x0001 #define DIFF_FORMAT_DIFFSTAT 0x0002 #define DIFF_FORMAT_NUMSTAT 0x0004 @@ -130,6 +133,8 @@ struct diff_options { add_remove_fn_t add_remove; diff_format_fn_t format_callback; void *format_callback_data; + diff_prefix_fn_t output_prefix; + void *output_prefix_data; }; enum color_diff { -- cgit v1.3 From 7be5761073fde260d3aca10883e8688bd30cbccf Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Wed, 26 May 2010 15:23:54 +0800 Subject: diff.c: Output the text graph padding before each diff line Change output from diff with -p/--dirstat/--binary/--numstat/--stat/ --shortstat/--check/--summary options to align with graph paddings. Thanks Jeff King for reporting the '--summary' bug and his initial patch. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 147 insertions(+), 53 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index e2f910ad14..7f2538d339 100644 --- a/diff.c +++ b/diff.c @@ -490,6 +490,13 @@ static void emit_rewrite_diff(const char *name_a, char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; + char *line_prefix = ""; + struct strbuf *msgbuf; + + if (o && o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -531,9 +538,10 @@ static void emit_rewrite_diff(const char *name_a, lc_a = count_lines(data_one, size_one); lc_b = count_lines(data_two, size_two); fprintf(o->file, - "%s--- %s%s%s\n%s+++ %s%s%s\n%s@@ -", - metainfo, a_name.buf, name_a_tab, reset, - metainfo, b_name.buf, name_b_tab, reset, fraginfo); + "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -", + line_prefix, metainfo, a_name.buf, name_a_tab, reset, + line_prefix, metainfo, b_name.buf, name_b_tab, reset, + line_prefix, fraginfo); print_line_count(o->file, lc_a); fprintf(o->file, " +"); print_line_count(o->file, lc_b); @@ -846,6 +854,14 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); + struct diff_options *o = ecbdata->opt; + char *line_prefix = ""; + struct strbuf *msgbuf; + + if (o && o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (ecbdata->header) { fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf); @@ -860,10 +876,10 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - fprintf(ecbdata->opt->file, "%s--- %s%s%s\n", - meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(ecbdata->opt->file, "%s+++ %s%s%s\n", - meta, ecbdata->label_path[1], reset, name_b_tab); + fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n", + line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); + fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n", + line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -1100,10 +1116,17 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) int total_files = data->nr; int width, name_width; const char *reset, *set, *add_c, *del_c; + const char *line_prefix = ""; + struct strbuf *msg = NULL; if (data->nr == 0) return; + if (options->output_prefix) { + msg = options->output_prefix(options, options->output_prefix_data); + line_prefix = msg->buf; + } + width = options->stat_width ? options->stat_width : 80; name_width = options->stat_name_width ? options->stat_name_width : 50; @@ -1173,6 +1196,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) } if (data->files[i]->is_binary) { + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " Bin "); fprintf(options->file, "%s%"PRIuMAX"%s", @@ -1185,6 +1209,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) continue; } else if (data->files[i]->is_unmerged) { + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " Unmerged\n"); continue; @@ -1207,6 +1232,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) add = scale_linear(add, width, max_change); del = scale_linear(del, width, max_change); } + fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, "%5"PRIuMAX"%s", added + deleted, added + deleted ? " " : ""); @@ -1214,6 +1240,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) show_graph(options->file, '-', del, del_c, reset); fprintf(options->file, "\n"); } + fprintf(options->file, "%s", line_prefix); fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); @@ -1240,6 +1267,12 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option } } } + if (options->output_prefix) { + struct strbuf *msg = NULL; + msg = options->output_prefix(options, + options->output_prefix_data); + fprintf(options->file, "%s", msg->buf); + } fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -1254,6 +1287,13 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; + if (options->output_prefix) { + struct strbuf *msg = NULL; + msg = options->output_prefix(options, + options->output_prefix_data); + fprintf(options->file, "%s", msg->buf); + } + if (file->is_binary) fprintf(options->file, "-\t-\t"); else @@ -1289,10 +1329,18 @@ struct dirstat_dir { int alloc, nr, percent, cumulative; }; -static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long changed, const char *base, int baselen) +static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, + unsigned long changed, const char *base, int baselen) { unsigned long this_dir = 0; unsigned int sources = 0; + const char *line_prefix = ""; + struct strbuf *msg = NULL; + + if (opt->output_prefix) { + msg = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msg->buf; + } while (dir->nr) { struct dirstat_file *f = dir->files; @@ -1307,7 +1355,7 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch slash = strchr(f->name + baselen, '/'); if (slash) { int newbaselen = slash + 1 - f->name; - this = gather_dirstat(file, dir, changed, f->name, newbaselen); + this = gather_dirstat(opt, dir, changed, f->name, newbaselen); sources++; } else { this = f->changed; @@ -1329,7 +1377,8 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch if (permille) { int percent = permille / 10; if (percent >= dir->percent) { - fprintf(file, "%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base); + fprintf(opt->file, "%s%4d.%01d%% %.*s\n", line_prefix, + percent, permille % 10, baselen, base); if (!dir->cumulative) return 0; } @@ -1409,7 +1458,7 @@ static void show_dirstat(struct diff_options *options) /* Show all directories with more than x% of the changes */ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); - gather_dirstat(options->file, &dir, changed, "", 0); + gather_dirstat(options, &dir, changed, "", 0); } static void free_diffstat_info(struct diffstat_t *diffstat) @@ -1467,6 +1516,15 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) const char *reset = diff_get_color(color_diff, DIFF_RESET); const char *set = diff_get_color(color_diff, DIFF_FILE_NEW); char *err; + char *line_prefix = ""; + struct strbuf *msgbuf; + + assert(data->o); + if (data->o->output_prefix) { + msgbuf = data->o->output_prefix(data->o, + data->o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (line[0] == '+') { unsigned bad; @@ -1474,16 +1532,16 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (is_conflict_marker(line + 1, marker_size, len - 1)) { data->status |= 1; fprintf(data->o->file, - "%s:%d: leftover conflict marker\n", - data->filename, data->lineno); + "%s%s:%d: leftover conflict marker\n", + line_prefix, data->filename, data->lineno); } bad = ws_check(line + 1, len - 1, data->ws_rule); if (!bad) return; data->status |= bad; err = whitespace_error_string(bad); - fprintf(data->o->file, "%s:%d: %s.\n", - data->filename, data->lineno, err); + fprintf(data->o->file, "%s%s:%d: %s.\n", + line_prefix, data->filename, data->lineno, err); free(err); emit_line(data->o, set, reset, line, 1); ws_check_emit(line + 1, len - 1, data->ws_rule, @@ -1523,7 +1581,7 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) { void *cp; void *delta; @@ -1552,13 +1610,13 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) } if (delta && delta_size < deflate_size) { - fprintf(file, "delta %lu\n", orig_size); + fprintf(file, "%sdelta %lu\n", prefix, orig_size); free(deflated); data = delta; data_size = delta_size; } else { - fprintf(file, "literal %lu\n", two->size); + fprintf(file, "%sliteral %lu\n", prefix, two->size); free(delta); data = deflated; data_size = deflate_size; @@ -1576,18 +1634,19 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two) line[0] = bytes - 26 + 'a' - 1; encode_85(line + 1, cp, bytes); cp = (char *) cp + bytes; + fprintf(file, "%s", prefix); fputs(line, file); fputc('\n', file); } - fprintf(file, "\n"); + fprintf(file, "%s\n", prefix); free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two) +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix) { - fprintf(file, "GIT binary patch\n"); - emit_binary_diff_body(file, one, two); - emit_binary_diff_body(file, two, one); + fprintf(file, "%sGIT binary patch\n", prefix); + emit_binary_diff_body(file, one, two, prefix); + emit_binary_diff_body(file, two, one, prefix); } static void diff_filespec_load_driver(struct diff_filespec *one) @@ -1676,6 +1735,13 @@ static void builtin_diff(const char *name_a, struct userdiff_driver *textconv_one = NULL; struct userdiff_driver *textconv_two = NULL; struct strbuf header = STRBUF_INIT; + struct strbuf *msgbuf; + char *line_prefix = ""; + + if (o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } if (DIFF_OPT_TST(o, SUBMODULE_LOG) && (!one->mode || S_ISGITLINK(one->mode)) && @@ -1710,22 +1776,22 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); + strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else if (lbl[1][0] == '/') { - strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); + strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); } else { if (one->mode != two->mode) { - strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); - strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); + strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset); + strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset); } if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); @@ -1760,10 +1826,10 @@ static void builtin_diff(const char *name_a, fprintf(o->file, "%s", header.buf); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(o->file, &mf1, &mf2); + emit_binary_diff(o->file, &mf1, &mf2, line_prefix); else - fprintf(o->file, "Binary files %s and %s differ\n", - lbl[0], lbl[1]); + fprintf(o->file, "%sBinary files %s and %s differ\n", + line_prefix, lbl[0], lbl[1]); o->found_changes = 1; } else { @@ -2389,28 +2455,36 @@ static void fill_metainfo(struct strbuf *msg, struct diff_options *o, struct diff_filepair *p) { + struct strbuf *msgbuf; + char *line_prefix = ""; + + if (o->output_prefix) { + msgbuf = o->output_prefix(o, o->output_prefix_data); + line_prefix = msgbuf->buf; + } + strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\ncopy from "); + strbuf_addf(msg, "%ssimilarity index %d%%", line_prefix, similarity_index(p)); + strbuf_addf(msg, "\n%scopy from ", line_prefix); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\ncopy to "); + strbuf_addf(msg, "\n%scopy to ", line_prefix); quote_c_style(other, msg, NULL, 0); strbuf_addch(msg, '\n'); break; case DIFF_STATUS_RENAMED: - strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(msg, "\nrename from "); + strbuf_addf(msg, "%ssimilarity index %d%%", line_prefix, similarity_index(p)); + strbuf_addf(msg, "\n%srename from ", line_prefix); quote_c_style(name, msg, NULL, 0); - strbuf_addstr(msg, "\nrename to "); + strbuf_addf(msg, "\n%srename to ", line_prefix); quote_c_style(other, msg, NULL, 0); strbuf_addch(msg, '\n'); break; case DIFF_STATUS_MODIFIED: if (p->score) { - strbuf_addf(msg, "dissimilarity index %d%%\n", - similarity_index(p)); + strbuf_addf(msg, "%sdissimilarity index %d%%\n", + line_prefix, similarity_index(p)); break; } /* fallthru */ @@ -2427,8 +2501,8 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "index %.*s..%.*s", - abbrev, sha1_to_hex(one->sha1), + strbuf_addf(msg, "%sindex %.*s..%.*s", + line_prefix, abbrev, sha1_to_hex(one->sha1), abbrev, sha1_to_hex(two->sha1)); if (one->mode == two->mode) strbuf_addf(msg, " %06o", one->mode); @@ -3132,6 +3206,11 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt) { int line_termination = opt->line_termination; int inter_name_termination = line_termination ? '\t' : '\0'; + if (opt->output_prefix) { + struct strbuf *msg = NULL; + msg = opt->output_prefix(opt, opt->output_prefix_data); + fprintf(opt->file, "%s", msg->buf); + } if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) { fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode, @@ -3377,48 +3456,62 @@ static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_f } -static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name) +static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name, + const char *line_prefix) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { - fprintf(file, " mode change %06o => %06o%c", p->one->mode, p->two->mode, - show_name ? ' ' : '\n'); + fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode, + p->two->mode, show_name ? ' ' : '\n'); if (show_name) { write_name_quoted(p->two->path, file, '\n'); } } } -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p) +static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p, + const char *line_prefix) { char *names = pprint_rename(p->one->path, p->two->path); fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); free(names); - show_mode_change(file, p, 0); + show_mode_change(file, p, 0, line_prefix); } -static void diff_summary(FILE *file, struct diff_filepair *p) +static void diff_summary(struct diff_options *opt, struct diff_filepair *p) { + FILE *file = opt->file; + char *line_prefix = ""; + + if (opt->output_prefix) { + struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = buf->buf; + } + switch(p->status) { case DIFF_STATUS_DELETED: + fputs(line_prefix, file); show_file_mode_name(file, "delete", p->one); break; case DIFF_STATUS_ADDED: + fputs(line_prefix, file); show_file_mode_name(file, "create", p->two); break; case DIFF_STATUS_COPIED: - show_rename_copy(file, "copy", p); + fputs(line_prefix, file); + show_rename_copy(file, "copy", p, line_prefix); break; case DIFF_STATUS_RENAMED: - show_rename_copy(file, "rename", p); + fputs(line_prefix, file); + show_rename_copy(file, "rename", p, line_prefix); break; default: if (p->score) { - fputs(" rewrite ", file); + fprintf(file, "%s rewrite ", line_prefix); write_name_quoted(p->two->path, file, ' '); fprintf(file, "(%d%%)\n", similarity_index(p)); } - show_mode_change(file, p, !p->score); + show_mode_change(file, p, !p->score, line_prefix); break; } } @@ -3627,8 +3720,9 @@ void diff_flush(struct diff_options *options) show_dirstat(options); if (output_format & DIFF_FORMAT_SUMMARY && !is_summary_empty(q)) { - for (i = 0; i < q->nr; i++) - diff_summary(options->file, q->queue[i]); + for (i = 0; i < q->nr; i++) { + diff_summary(options, q->queue[i]); + } separator++; } -- cgit v1.3 From 2efcc977646320123c0d461664d25c4c93aaa9ee Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Sat, 29 May 2010 23:32:05 +0800 Subject: Emit a whole line in one go Since the graph prefix will be printed when calling emit_line, so the functions should be used to emit a complete line out once a time. No one should call emit_line to just output some strings instead of a complete line. Use a strbuf to compose the whole line, and then call emit_line to output it once. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 7f2538d339..2a1482aa90 100644 --- a/diff.c +++ b/diff.c @@ -370,6 +370,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); static const char atat[2] = { '@', '@' }; const char *cp, *ep; + struct strbuf msgbuf = STRBUF_INIT; + int org_len = len; + int i = 1; /* * As a hunk header must begin with "@@ -, + @@", @@ -384,17 +387,36 @@ static void emit_hunk_header(struct emit_callback *ecbdata, ep += 2; /* skip over @@ */ /* The hunk header in fraginfo color */ - emit_line(ecbdata->opt, frag, reset, line, ep - line); + strbuf_add(&msgbuf, frag, strlen(frag)); + strbuf_add(&msgbuf, line, ep - line); + strbuf_add(&msgbuf, reset, strlen(reset)); + + /* + * trailing "\r\n" + */ + for ( ; i < 3; i++) + if (line[len - i] == '\r' || line[len - i] == '\n') + len--; /* blank before the func header */ for (cp = ep; ep - line < len; ep++) if (*ep != ' ' && *ep != '\t') break; - if (ep != cp) - emit_line(ecbdata->opt, plain, reset, cp, ep - cp); + if (ep != cp) { + strbuf_add(&msgbuf, plain, strlen(plain)); + strbuf_add(&msgbuf, cp, ep - cp); + strbuf_add(&msgbuf, reset, strlen(reset)); + } + + if (ep < line + len) { + strbuf_add(&msgbuf, func, strlen(func)); + strbuf_add(&msgbuf, ep, line + len - ep); + strbuf_add(&msgbuf, reset, strlen(reset)); + } - if (ep < line + len) - emit_line(ecbdata->opt, func, reset, ep, line + len - ep); + strbuf_add(&msgbuf, line + len, org_len - len); + emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len); + strbuf_release(&msgbuf); } static struct diff_tempfile *claim_diff_tempfile(void) { -- cgit v1.3 From 4297c0aeb5cc6b9c1c87d770c91e09ac2a837320 Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Sat, 29 May 2010 23:32:06 +0800 Subject: Make --color-words work well with --graph '--color-words' algorithm can be described as: 1. collect a the minus/plus lines of a diff hunk, divided into minus-lines and plus-lines; 2. break both minus-lines and plus-lines into words and place them into two mmfile_t with one word for each line; 3. use xdiff to run diff on the two mmfile_t to get the words level diff; And for the common parts of the both file, we output the plus side text. diff_words->current_plus is used to trace the current position of the plus file which printed. diff_words->last_minus is used to trace the last minus word printed. For '--graph' to work with '--color-words', we need to output the graph prefix on each line of color words output. Generally, there are two conditions on which we should output the prefix. 1. diff_words->last_minus == 0 && diff_words->current_plus == diff_words->plus.text.ptr that is: the plus text must start as a new line, and if there is no minus word printed, a graph prefix must be printed. 2. diff_words->current_plus > diff_words->plus.text.ptr && *(diff_words->current_plus - 1) == '\n' that is: a graph prefix must be printed following a '\n' Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 17 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 2a1482aa90..974b6a997a 100644 --- a/diff.c +++ b/diff.c @@ -622,7 +622,8 @@ struct diff_words_style diff_words_styles[] = { struct diff_words_data { struct diff_words_buffer minus, plus; const char *current_plus; - FILE *file; + int last_minus; + struct diff_options *opt; regex_t *word_regex; enum diff_words_type type; struct diff_words_style *style; @@ -631,10 +632,15 @@ struct diff_words_data { static int fn_out_diff_words_write_helper(FILE *fp, struct diff_words_style_elem *st_el, const char *newline, - size_t count, const char *buf) + size_t count, const char *buf, + const char *line_prefix) { + int print = 0; + while (count) { char *p = memchr(buf, '\n', count); + if (print) + fputs(line_prefix, fp); if (p != buf) { if (st_el->color && fputs(st_el->color, fp) < 0) return -1; @@ -652,21 +658,74 @@ static int fn_out_diff_words_write_helper(FILE *fp, return -1; count -= p + 1 - buf; buf = p + 1; + print = 1; } return 0; } +/* + * '--color-words' algorithm can be described as: + * + * 1. collect a the minus/plus lines of a diff hunk, divided into + * minus-lines and plus-lines; + * + * 2. break both minus-lines and plus-lines into words and + * place them into two mmfile_t with one word for each line; + * + * 3. use xdiff to run diff on the two mmfile_t to get the words level diff; + * + * And for the common parts of the both file, we output the plus side text. + * diff_words->current_plus is used to trace the current position of the plus file + * which printed. diff_words->last_minus is used to trace the last minus word + * printed. + * + * For '--graph' to work with '--color-words', we need to output the graph prefix + * on each line of color words output. Generally, there are two conditions on + * which we should output the prefix. + * + * 1. diff_words->last_minus == 0 && + * diff_words->current_plus == diff_words->plus.text.ptr + * + * that is: the plus text must start as a new line, and if there is no minus + * word printed, a graph prefix must be printed. + * + * 2. diff_words->current_plus > diff_words->plus.text.ptr && + * *(diff_words->current_plus - 1) == '\n' + * + * that is: a graph prefix must be printed following a '\n' + */ +static int color_words_output_graph_prefix(struct diff_words_data *diff_words) +{ + if ((diff_words->last_minus == 0 && + diff_words->current_plus == diff_words->plus.text.ptr) || + (diff_words->current_plus > diff_words->plus.text.ptr && + *(diff_words->current_plus - 1) == '\n')) { + return 1; + } else { + return 0; + } +} + static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) { struct diff_words_data *diff_words = priv; struct diff_words_style *style = diff_words->style; int minus_first, minus_len, plus_first, plus_len; const char *minus_begin, *minus_end, *plus_begin, *plus_end; + struct diff_options *opt = diff_words->opt; + struct strbuf *msgbuf; + char *line_prefix = ""; if (line[0] != '@' || parse_hunk_header(line, len, &minus_first, &minus_len, &plus_first, &plus_len)) return; + assert(opt); + if (opt->output_prefix) { + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msgbuf->buf; + } + /* POSIX requires that first be decremented by one if len == 0... */ if (minus_len) { minus_begin = diff_words->minus.orig[minus_first].begin; @@ -682,21 +741,32 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) } else plus_begin = plus_end = diff_words->plus.orig[plus_first].end; - if (diff_words->current_plus != plus_begin) - fn_out_diff_words_write_helper(diff_words->file, + if (color_words_output_graph_prefix(diff_words)) { + fputs(line_prefix, diff_words->opt->file); + } + if (diff_words->current_plus != plus_begin) { + fn_out_diff_words_write_helper(diff_words->opt->file, &style->ctx, style->newline, plus_begin - diff_words->current_plus, - diff_words->current_plus); - if (minus_begin != minus_end) - fn_out_diff_words_write_helper(diff_words->file, + diff_words->current_plus, line_prefix); + if (*(plus_begin - 1) == '\n') + fputs(line_prefix, diff_words->opt->file); + } + if (minus_begin != minus_end) { + fn_out_diff_words_write_helper(diff_words->opt->file, &style->old, style->newline, - minus_end - minus_begin, minus_begin); - if (plus_begin != plus_end) - fn_out_diff_words_write_helper(diff_words->file, + minus_end - minus_begin, minus_begin, + line_prefix); + } + if (plus_begin != plus_end) { + fn_out_diff_words_write_helper(diff_words->opt->file, &style->new, style->newline, - plus_end - plus_begin, plus_begin); + plus_end - plus_begin, plus_begin, + line_prefix); + } diff_words->current_plus = plus_end; + diff_words->last_minus = minus_first; } /* This function starts looking at *begin, and returns 0 iff a word was found. */ @@ -777,16 +847,29 @@ static void diff_words_show(struct diff_words_data *diff_words) mmfile_t minus, plus; struct diff_words_style *style = diff_words->style; + struct diff_options *opt = diff_words->opt; + struct strbuf *msgbuf; + char *line_prefix = ""; + + assert(opt); + if (opt->output_prefix) { + msgbuf = opt->output_prefix(opt, opt->output_prefix_data); + line_prefix = msgbuf->buf; + } + /* special case: only removal */ if (!diff_words->plus.text.size) { - fn_out_diff_words_write_helper(diff_words->file, + fputs(line_prefix, diff_words->opt->file); + fn_out_diff_words_write_helper(diff_words->opt->file, &style->old, style->newline, - diff_words->minus.text.size, diff_words->minus.text.ptr); + diff_words->minus.text.size, + diff_words->minus.text.ptr, line_prefix); diff_words->minus.text.size = 0; return; } diff_words->current_plus = diff_words->plus.text.ptr; + diff_words->last_minus = 0; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); @@ -800,11 +883,15 @@ static void diff_words_show(struct diff_words_data *diff_words) free(minus.ptr); free(plus.ptr); if (diff_words->current_plus != diff_words->plus.text.ptr + - diff_words->plus.text.size) - fn_out_diff_words_write_helper(diff_words->file, + diff_words->plus.text.size) { + if (color_words_output_graph_prefix(diff_words)) + fputs(line_prefix, diff_words->opt->file); + fn_out_diff_words_write_helper(diff_words->opt->file, &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, diff_words->current_plus); + - diff_words->current_plus, diff_words->current_plus, + line_prefix); + } diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -1902,8 +1989,8 @@ static void builtin_diff(const char *name_a, ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); - ecbdata.diff_words->file = o->file; ecbdata.diff_words->type = o->word_diff; + ecbdata.diff_words->opt = o; if (!o->word_regex) o->word_regex = userdiff_word_regex(one); if (!o->word_regex) -- cgit v1.3 From 296c6bb21a6980f6e5b42f0790d6365c1e3f696f Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 26 May 2010 04:50:12 +0200 Subject: diff: fix "git show -C -C" output when renaming a binary file A bug was introduced in 3e97c7c6af2901cec63bf35fcd43ae3472e24af8 (No diff -b/-w output for all-whitespace changes, Nov 19 2009) that made the lines: diff --git a/bar b/sub/bar similarity index 100% rename from bar rename to sub/bar disappear from "git show -C -C" output when file bar is a binary file. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- diff.c | 23 ++++++++++++++++------ t/t4015-diff-whitespace.sh | 37 +++++++++++++++++++++++++++++++++++ t/t4043-diff-rename-binary.sh | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) create mode 100755 t/t4043-diff-rename-binary.sh (limited to 'diff.c') diff --git a/diff.c b/diff.c index a2d8c7f9a7..426fd04665 100644 --- a/diff.c +++ b/diff.c @@ -1596,6 +1596,7 @@ static void builtin_diff(const char *name_a, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, + int must_show_header, struct diff_options *o, int complete_rewrite) { @@ -1647,16 +1648,19 @@ static void builtin_diff(const char *name_a, strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset); if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + must_show_header = 1; } else if (lbl[1][0] == '/') { strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset); if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); + must_show_header = 1; } else { if (one->mode != two->mode) { strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset); strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset); + must_show_header = 1; } if (xfrm_msg && xfrm_msg[0]) strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset); @@ -1687,8 +1691,11 @@ static void builtin_diff(const char *name_a, (diff_filespec_is_binary(two) && !textconv_two) )) { /* Quite common confusing case */ if (mf1.size == mf2.size && - !memcmp(mf1.ptr, mf2.ptr, mf1.size)) + !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { + if (must_show_header) + fprintf(o->file, "%s", header.buf); goto free_ab_and_return; + } fprintf(o->file, "%s", header.buf); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) @@ -1706,7 +1713,7 @@ static void builtin_diff(const char *name_a, struct emit_callback ecbdata; const struct userdiff_funcname *pe; - if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) { + if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) { fprintf(o->file, "%s", header.buf); strbuf_reset(&header); } @@ -2315,8 +2322,10 @@ static void fill_metainfo(struct strbuf *msg, struct diff_filespec *one, struct diff_filespec *two, struct diff_options *o, - struct diff_filepair *p) + struct diff_filepair *p, + int *must_show_header) { + *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); switch (p->status) { case DIFF_STATUS_COPIED: @@ -2344,7 +2353,7 @@ static void fill_metainfo(struct strbuf *msg, /* fallthru */ default: /* nothing */ - ; + *must_show_header = 0; } if (one && two && hashcmp(one->sha1, two->sha1)) { int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; @@ -2378,9 +2387,10 @@ static void run_diff_cmd(const char *pgm, { const char *xfrm_msg = NULL; int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; + int must_show_header = 0; if (msg) { - fill_metainfo(msg, name, other, one, two, o, p); + fill_metainfo(msg, name, other, one, two, o, p, &must_show_header); xfrm_msg = msg->len ? msg->buf : NULL; } @@ -2399,7 +2409,8 @@ static void run_diff_cmd(const char *pgm, } if (one && two) builtin_diff(name, other ? other : name, - one, two, xfrm_msg, o, complete_rewrite); + one, two, xfrm_msg, must_show_header, + o, complete_rewrite); else fprintf(o->file, "* Unmerged path %s\n", name); } diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 90f3342373..7e78851a11 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -396,6 +396,43 @@ test_expect_success 'whitespace-only changes not reported' ' test_cmp expect actual ' +cat <expect +diff --git a/x b/z +similarity index NUM% +rename from x +rename to z +index 380c32a..a97b785 100644 +EOF +test_expect_success 'whitespace-only changes reported across renames' ' + git reset --hard && + for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x && + git add x && + git commit -m "base" && + sed -e "5s/^/ /" x >z && + git rm x && + git add z && + git diff -w -M --cached | + sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual && + test_cmp expect actual +' + +cat >expected <<\EOF +diff --git a/empty b/void +similarity index 100% +rename from empty +rename to void +EOF + +test_expect_success 'rename empty' ' + git reset --hard && + >empty && + git add empty && + git commit -m empty && + git mv empty void && + git diff -w --cached -M >current && + test_cmp expected current +' + test_expect_success 'combined diff with autocrlf conversion' ' git reset --hard && diff --git a/t/t4043-diff-rename-binary.sh b/t/t4043-diff-rename-binary.sh new file mode 100755 index 0000000000..06012811a1 --- /dev/null +++ b/t/t4043-diff-rename-binary.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright (c) 2010 Jakub Narebski, Christian Couder +# + +test_description='Move a binary file' + +. ./test-lib.sh + + +test_expect_success 'prepare repository' ' + git init && + echo foo > foo && + echo "barQ" | q_to_nul > bar && + git add . && + git commit -m "Initial commit" +' + +test_expect_success 'move the files into a "sub" directory' ' + mkdir sub && + git mv bar foo sub/ && + git commit -m "Moved to sub/" +' + +cat > expected <<\EOF + bar => sub/bar | Bin 5 -> 5 bytes + foo => sub/foo | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + +diff --git a/bar b/sub/bar +similarity index 100% +rename from bar +rename to sub/bar +diff --git a/foo b/sub/foo +similarity index 100% +rename from foo +rename to sub/foo +EOF + +test_expect_success 'git show -C -C report renames' ' + git show -C -C --raw --binary --stat | tail -n 12 > current && + test_cmp expected current +' + +test_done -- cgit v1.3 From a788d7d58bfbd2772b7ad5d788d8f7f885a651e1 Mon Sep 17 00:00:00 2001 From: Axel Bonnet Date: Mon, 7 Jun 2010 17:23:36 +0200 Subject: textconv: make the API public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The textconv functionality allows one to convert a file into text before running diff. But this functionality can be useful to other features such as blame. Signed-off-by: Axel Bonnet Signed-off-by: Clément Poulain Signed-off-by: Diane Gasselin Signed-off-by: Junio C Hamano --- diff.c | 12 ++++-------- diff.h | 8 ++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 494f5601e9..b4a830f88f 100644 --- a/diff.c +++ b/diff.c @@ -43,10 +43,6 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* FUNCINFO */ }; -static void diff_filespec_load_driver(struct diff_filespec *one); -static size_t fill_textconv(struct userdiff_driver *driver, - struct diff_filespec *df, char **outbuf); - static int parse_diff_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) @@ -1629,7 +1625,7 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const options->b_prefix = b; } -static struct userdiff_driver *get_textconv(struct diff_filespec *one) +struct userdiff_driver *get_textconv(struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) return NULL; @@ -4002,9 +3998,9 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, return strbuf_detach(&buf, outsize); } -static size_t fill_textconv(struct userdiff_driver *driver, - struct diff_filespec *df, - char **outbuf) +size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, + char **outbuf) { size_t size; diff --git a/diff.h b/diff.h index 9ace08cbae..2a0e36d7a5 100644 --- a/diff.h +++ b/diff.h @@ -9,6 +9,8 @@ struct rev_info; struct diff_options; struct diff_queue_struct; +struct diff_filespec; +struct userdiff_driver; typedef void (*change_fn_t)(struct diff_options *options, unsigned old_mode, unsigned new_mode, @@ -287,4 +289,10 @@ extern void diff_no_index(struct rev_info *, int, const char **, int, const char extern int index_differs_from(const char *def, int diff_flags); +extern size_t fill_textconv(struct userdiff_driver *driver, + struct diff_filespec *df, + char **outbuf); + +extern struct userdiff_driver *get_textconv(struct diff_filespec *one); + #endif /* DIFF_H */ -- cgit v1.3 From dd44d419d30afa52b863efa07aeec738c4531ea9 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Tue, 8 Jun 2010 18:31:51 +0200 Subject: Add optional parameters to the diff option "--ignore-submodules" In some use cases it is not desirable that the diff family considers submodules that only contain untracked content as dirty. This may happen e.g. when the submodule is not under the developers control and not all build generated files have been added to .gitignore by the upstream developers. Using the "untracked" parameter for the "--ignore-submodules" option disables checking for untracked content and lets git diff report them as changed only when they have new commits or modified content. Sometimes it is not wanted to have submodules show up as changed when they just contain changes to their work tree. An example for that are scripts which just want to check for submodule commits while ignoring any changes to the work tree. Also users having large submodules known not to change might want to use this option, as the - sometimes substantial - time it takes to scan the submodule work tree(s) is saved. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 10 ++++- diff-lib.c | 1 + diff.c | 11 +++++- diff.h | 1 + t/t4027-diff-submodule.sh | 20 ++++++++-- t/t4041-diff-submodule-option.sh | 81 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 6 deletions(-) (limited to 'diff.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c9c6c2b1cb..2dd91e930b 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -288,8 +288,14 @@ endif::git-format-patch[] --no-ext-diff:: Disallow external diff drivers. ---ignore-submodules:: - Ignore changes to submodules in the diff generation. +--ignore-submodules[=]:: + Ignore changes to submodules in the diff generation. can be + either "untracked", "dirty" or "all", which is the default. When + "untracked" is used submodules are not considered dirty when they only + contain untracked content (but they are still scanned for modified + content). Using "dirty" ignores all changes to the work tree of submodules, + only changes to the commits stored in the superproject are shown (this was + the behavior until 1.7.0). Using "all" hides all changes to submodules. --src-prefix=:: Show the given source prefix instead of "a/". diff --git a/diff-lib.c b/diff-lib.c index c9f6e05bad..8b8978ae6d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -70,6 +70,7 @@ static int match_stat_with_submodule(struct diff_options *diffopt, int changed = ce_match_stat(ce, st, ce_option); if (S_ISGITLINK(ce->ce_mode) && !DIFF_OPT_TST(diffopt, IGNORE_SUBMODULES) + && !DIFF_OPT_TST(diffopt, IGNORE_DIRTY_SUBMODULES) && (!changed || DIFF_OPT_TST(diffopt, DIRTY_SUBMODULES))) { *dirty_submodule = is_submodule_modified(ce->name, DIFF_OPT_TST(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES)); } diff --git a/diff.c b/diff.c index e40c1271da..804acf0b21 100644 --- a/diff.c +++ b/diff.c @@ -2867,7 +2867,16 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) DIFF_OPT_SET(options, IGNORE_SUBMODULES); - else if (!strcmp(arg, "--submodule")) + else if (!prefixcmp(arg, "--ignore-submodules=")) { + if (!strcmp(arg + 20, "all")) + DIFF_OPT_SET(options, IGNORE_SUBMODULES); + else if (!strcmp(arg + 20, "untracked")) + DIFF_OPT_SET(options, IGNORE_UNTRACKED_IN_SUBMODULES); + else if (!strcmp(arg + 20, "dirty")) + DIFF_OPT_SET(options, IGNORE_DIRTY_SUBMODULES); + else + die("bad --ignore-submodules argument: %s", arg + 20); + } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { if (!strcmp(arg + 12, "log")) diff --git a/diff.h b/diff.h index 6a71013dc6..53db218b30 100644 --- a/diff.h +++ b/diff.h @@ -71,6 +71,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_SUBMODULE_LOG (1 << 23) #define DIFF_OPT_DIRTY_SUBMODULES (1 << 24) #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25) +#define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26) #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 83c1914771..559b41eccd 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -103,9 +103,17 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules HEAD >actual2 && + echo -n "" | test_cmp - actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + sed -e "1,/^@@/d" actual3 >actual3.body && + expect_from_to >expect.body $subprev $subprev-dirty && + test_cmp expect.body actual3.body && + git diff --ignore-submodules=dirty HEAD >actual4 && + echo -n "" | test_cmp - actual4 ' - +test_done test_expect_success 'git diff HEAD with dirty submodule (index, refs match)' ' ( cd sub && @@ -129,7 +137,13 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' git diff HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body + test_cmp expect.body actual.body && + git diff --ignore-submodules=all HEAD >actual2 && + echo -n "" | test_cmp - actual2 && + git diff --ignore-submodules=untracked HEAD >actual3 && + echo -n "" | test_cmp - actual3 && + git diff --ignore-submodules=dirty HEAD >actual4 && + echo -n "" | test_cmp - actual4 ' test_expect_success 'git diff (empty submodule dir)' ' diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 019acb926d..f44b9064ea 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -205,6 +205,21 @@ Submodule sm1 contains untracked content EOF " +test_expect_success 'submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'submodule contains untracked and modifed content' " echo new > sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && @@ -214,6 +229,26 @@ Submodule sm1 contains modified content EOF " +test_expect_success 'submodule contains untracked and modifed content (untracked ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +EOF +" + +test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + +test_expect_success 'submodule contains untracked and modifed content (all ignored)' " + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && @@ -242,6 +277,27 @@ Submodule sm1 $head6..$head8: EOF " +test_expect_success 'modified submodule contains untracked content (untracked ignored)' " + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (dirty ignored)' " + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked content (all ignored)' " + git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'modified submodule contains untracked and modifed content' " echo modification >> sm1/foo6 && git diff-index -p --submodule=log HEAD >actual && @@ -253,6 +309,31 @@ Submodule sm1 $head6..$head8: EOF " +test_expect_success 'modified submodule contains untracked and modifed content (untracked ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 contains modified content +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (dirty ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && + diff actual - <<-EOF +Submodule sm1 $head6..$head8: + > change +EOF +" + +test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' " + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=log HEAD >actual && + echo -n '' | diff actual - +" + test_expect_success 'modified submodule contains modifed content' " rm -f sm1/new-file && git diff-index -p --submodule=log HEAD >actual && -- cgit v1.3 From 46a958b3daa1da336683ec82d7f321d0f51b39c8 Mon Sep 17 00:00:00 2001 From: Jens Lehmann Date: Fri, 25 Jun 2010 16:56:47 +0200 Subject: Add the option "--ignore-submodules" to "git status" In some use cases it is not desirable that "git status" considers submodules that only contain untracked content as dirty. This may happen e.g. when the submodule is not under the developers control and not all build generated files have been added to .gitignore by the upstream developers. Using the "untracked" parameter for the "--ignore-submodules" option disables checking for untracked content and lets git diff report them as changed only when they have new commits or modified content. Sometimes it is not wanted to have submodules show up as changed when they just contain changes to their work tree (this was the behavior before 1.7.0). An example for that are scripts which just want to check for submodule commits while ignoring any changes to the work tree. Also users having large submodules known not to change might want to use this option, as the - sometimes substantial - time it takes to scan the submodule work tree(s) is saved when using the "dirty" parameter. And if you want to ignore any changes to submodules, you can now do that by using this option without parameters or with "all" (when the config option status.submodulesummary is set, using "all" will also suppress the output of the submodule summary). A new function handle_ignore_submodules_arg() is introduced to parse this option new to "git status" in a single location, as "git diff" already knew it. Signed-off-by: Jens Lehmann Signed-off-by: Junio C Hamano --- Documentation/git-status.txt | 11 ++++ builtin/commit.c | 7 ++- diff.c | 15 ++--- submodule.c | 13 +++++ submodule.h | 3 + t/t7508-status.sh | 127 +++++++++++++++++++++++++++++++++++++++++++ wt-status.c | 10 +++- wt-status.h | 1 + 8 files changed, 174 insertions(+), 13 deletions(-) (limited to 'diff.c') diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 2d4bbfcaf4..a617ce746c 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -49,6 +49,17 @@ See linkgit:git-config[1] for configuration variable used to change the default for when the option is not specified. +--ignore-submodules[=]:: + Ignore changes to submodules when looking for changes. can be + either "untracked", "dirty" or "all", which is the default. When + "untracked" is used submodules are not considered dirty when they only + contain untracked content (but they are still scanned for modified + content). Using "dirty" ignores all changes to the work tree of submodules, + only changes to the commits stored in the superproject are shown (this was + the behavior before 1.7.0). Using "all" hides all changes to submodules + (and suppresses the output of submodule summaries when the config option + `status.submodulesummary` is set). + -z:: Terminate entries with NUL, instead of LF. This implies the `--porcelain` output format if no other format is given. diff --git a/builtin/commit.c b/builtin/commit.c index c5ab683d5b..0181750926 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -67,7 +67,7 @@ static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite; -static char *untracked_files_arg, *force_date; +static char *untracked_files_arg, *force_date, *ignore_submodule_arg; /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -1031,6 +1031,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", + "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", + PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_END(), }; @@ -1052,6 +1055,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.in_merge = in_merge; + s.ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(&s); if (s.relative_paths) @@ -1070,6 +1074,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) break; case STATUS_FORMAT_LONG: s.verbose = verbose; + s.ignore_submodule_arg = ignore_submodule_arg; wt_status_print(&s); break; } diff --git a/diff.c b/diff.c index 804acf0b21..36eae91a75 100644 --- a/diff.c +++ b/diff.c @@ -2866,17 +2866,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--no-textconv")) DIFF_OPT_CLR(options, ALLOW_TEXTCONV); else if (!strcmp(arg, "--ignore-submodules")) - DIFF_OPT_SET(options, IGNORE_SUBMODULES); - else if (!prefixcmp(arg, "--ignore-submodules=")) { - if (!strcmp(arg + 20, "all")) - DIFF_OPT_SET(options, IGNORE_SUBMODULES); - else if (!strcmp(arg + 20, "untracked")) - DIFF_OPT_SET(options, IGNORE_UNTRACKED_IN_SUBMODULES); - else if (!strcmp(arg + 20, "dirty")) - DIFF_OPT_SET(options, IGNORE_DIRTY_SUBMODULES); - else - die("bad --ignore-submodules argument: %s", arg + 20); - } else if (!strcmp(arg, "--submodule")) + handle_ignore_submodules_arg(options, "all"); + else if (!prefixcmp(arg, "--ignore-submodules=")) + handle_ignore_submodules_arg(options, arg + 20); + else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); else if (!prefixcmp(arg, "--submodule=")) { if (!strcmp(arg + 12, "log")) diff --git a/submodule.c b/submodule.c index 676d48fb33..61cb6e21dd 100644 --- a/submodule.c +++ b/submodule.c @@ -46,6 +46,19 @@ done: return ret; } +void handle_ignore_submodules_arg(struct diff_options *diffopt, + const char *arg) +{ + if (!strcmp(arg, "all")) + DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); + else if (!strcmp(arg, "untracked")) + DIFF_OPT_SET(diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); + else if (!strcmp(arg, "dirty")) + DIFF_OPT_SET(diffopt, IGNORE_DIRTY_SUBMODULES); + else + die("bad --ignore-submodules argument: %s", arg); +} + void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, diff --git a/submodule.h b/submodule.h index dbda270873..6fd3bb4070 100644 --- a/submodule.h +++ b/submodule.h @@ -1,6 +1,9 @@ #ifndef SUBMODULE_H #define SUBMODULE_H +struct diff_options; + +void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 556d0faa77..63d437e0e6 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -693,4 +693,131 @@ test_expect_success 'commit --dry-run submodule summary (--amend)' ' test_cmp expect output ' +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' + echo modified > sm/untracked && + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +test_expect_success '--ignore-submodules=dirty suppresses submodules with untracked content' ' + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +test_expect_success '--ignore-submodules=dirty suppresses submodules with modified content' ' + echo modified > sm/foo && + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# (commit or discard the untracked or modified content in submodules) +# +# modified: dir1/modified +# modified: sm (modified content) +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 --verify HEAD) + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: dir1/modified +# modified: sm (new commits) +# +# Submodules changed but not updated: +# +# * sm $head...$head2 (1): +# > 2nd commit +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' + git status --ignore-submodules=untracked > output && + test_cmp expect output +' + +test_expect_success "--ignore-submodules=dirty doesn't suppress submodule summary" ' + git status --ignore-submodules=dirty > output && + test_cmp expect output +' + +cat > expect << EOF +# On branch master +# Changed but not updated: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# expect +# output +# untracked +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success "--ignore-submodules=all suppresses submodule summary" ' + git status --ignore-submodules=all > output && + test_cmp expect output +' + test_done diff --git a/wt-status.c b/wt-status.c index 8ca59a2d2a..894d66f8fc 100644 --- a/wt-status.c +++ b/wt-status.c @@ -9,6 +9,7 @@ #include "quote.h" #include "run-command.h" #include "remote.h" +#include "submodule.h" static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ @@ -306,6 +307,8 @@ static void wt_status_collect_changes_worktree(struct wt_status *s) DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES); if (!s->show_untracked_files) DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES); + if (s->ignore_submodule_arg) + handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); rev.diffopt.format_callback = wt_status_collect_changed_cb; rev.diffopt.format_callback_data = s; rev.prune_data = s->pathspec; @@ -322,6 +325,9 @@ static void wt_status_collect_changes_index(struct wt_status *s) opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference; setup_revisions(0, NULL, &rev, &opt); + if (s->ignore_submodule_arg) + handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = wt_status_collect_updated_cb; rev.diffopt.format_callback_data = s; @@ -618,7 +624,9 @@ void wt_status_print(struct wt_status *s) wt_status_print_updated(s); wt_status_print_unmerged(s); wt_status_print_changed(s); - if (s->submodule_summary) { + if (s->submodule_summary && + (!s->ignore_submodule_arg || + strcmp(s->ignore_submodule_arg, "all"))) { wt_status_print_submodule_summary(s, 0); /* staged */ wt_status_print_submodule_summary(s, 1); /* unstaged */ } diff --git a/wt-status.h b/wt-status.h index 91206739f3..192909691e 100644 --- a/wt-status.h +++ b/wt-status.h @@ -42,6 +42,7 @@ struct wt_status { int relative_paths; int submodule_summary; enum untracked_status_type show_untracked_files; + const char *ignore_submodule_arg; char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN]; /* These are computed during processing of the individual sections */ -- cgit v1.3 From e13f38a33ed181b937c85fd9adf6bce755f69d00 Mon Sep 17 00:00:00 2001 From: Bo Yang Date: Thu, 8 Jul 2010 23:12:34 +0800 Subject: diff.c: fix a graph output bug When --graph is in effect, the line-prefix typically has colored graph line segments and ends with reset. The color sequence "set" given to this function is for showing the metainfo part of the patch text and (1) it should not be applied to the graph lines, and (2) it will be reset at the end of line_prefix so it won't be in effect anyway. Signed-off-by: Bo Yang Signed-off-by: Junio C Hamano --- diff.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 3aa695df62..17873f3d9e 100644 --- a/diff.c +++ b/diff.c @@ -2627,8 +2627,7 @@ static void fill_metainfo(struct strbuf *msg, (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) abbrev = 40; } - strbuf_addf(msg, "%s%sindex %s..", set, - line_prefix, + strbuf_addf(msg, "%s%sindex %s..", line_prefix, set, find_unique_abbrev(one->sha1, abbrev)); strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev)); if (one->mode == two->mode) -- cgit v1.3 From ee38d823f74bd8872c1e793f98a5b5523ee04646 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Aug 2010 08:29:42 -0700 Subject: Fix DIFF_QUEUE_CLEAR refactoring It introduced a macro to reduce repeated assignments to three fields, but an unrelated and incorrect change snuck in by mistake, which broke commands like "git diff-files -p --submodule". Noticed by Sven Verdoolaege. Signed-off-by: Junio C Hamano --- diff.c | 1 - 1 file changed, 1 deletion(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index 4a350e365e..882a9603bc 100644 --- a/diff.c +++ b/diff.c @@ -2540,7 +2540,6 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) void diff_setup(struct diff_options *options) { memset(options, 0, sizeof(*options)); - memset(&diff_queued_diff, 0, sizeof(diff_queued_diff)); options->file = stdout; -- cgit v1.3 From dea007fb4c8170ea007b577698c7b44df6c318b9 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Thu, 5 Aug 2010 10:22:52 +0200 Subject: diff: parse separate options like -S foo Change the option parsing logic in revision.c to accept separate forms like `-S foo' in addition to `-Sfoo'. The rest of git already accepted this form, but revision.c still used its own option parsing. Short options affected are -S, -l and -O, for which an empty string wouldn't make sense, hence -