diff options
90 files changed, 3117 insertions, 1081 deletions
diff --git a/Documentation/RelNotes/2.15.0.txt b/Documentation/RelNotes/2.15.0.txt index 2fd49978d7..ad429931ed 100644 --- a/Documentation/RelNotes/2.15.0.txt +++ b/Documentation/RelNotes/2.15.0.txt @@ -33,13 +33,11 @@ UI, Workflows & Features * The "rerere-train" script (in contrib/) learned the "--overwrite" option to allow overwriting existing recorded resolutions. - (merge ad53bf79aa rg/rerere-train-overwrite later to maint). * "git contacts" (in contrib/) now lists the address on the "Reported-by:" trailer to its output, in addition to those on S-o-b: and other trailers, to make it easier to notify (and thank) the original bug reporter. - (merge 09ac673788 eb/contacts-reported-by later to maint). * "git rebase", especially when it is run by mistake and ends up trying to replay many changes, spent long time in silence. The @@ -47,6 +45,9 @@ UI, Workflows & Features long time preparing these many changes to replay (which would give the user a chance to abort with ^C). + * "git merge" learned a "--signoff" option to add the Signed-off-by: + trailer with the committer's name. + Performance, Internal Implementation, Development Support etc. @@ -69,7 +70,6 @@ Performance, Internal Implementation, Development Support etc. * Because recent Git for Windows do come with a real msgfmt, the build procedure for git-gui has been updated to use it instead of a hand-rolled substitute. - (merge 90dbf226ba js/git-gui-msgfmt-on-windows later to maint). * "git grep --recurse-submodules" has been reworked to give a more consistent output across submodule boundary (and do its thing @@ -87,6 +87,15 @@ Performance, Internal Implementation, Development Support etc. has been optimized out when we know we do not run the pre-commit hook. (merge 680ee550d7 kw/commit-keep-index-when-pre-commit-is-not-run later to maint). + * Updates to the HTTP layer we made recently unconditionally used + features of libCurl without checking the existence of them, causing + compilation errors, which has been fixed. Also migrate the code to + check feature macros, not version numbers, to cope better with + libCurl that vendor ships with backported features. + + * The API to start showing progress meter after a short delay has + been simplified. + (merge 8aade107dd jc/simplify-progress later to maint). Also contains various documentation updates and code clean-ups. @@ -98,25 +107,20 @@ Fixes since v2.14 color escape codes, which was an early design mistake. They now honor the configuration (e.g. "color.ui = never") and also tty-ness of the output medium. - (merge 11b087adfd jk/ref-filter-colors later to maint). * The http.{sslkey,sslCert} configuration variables are to be interpreted as a pathname that honors "~[username]/" prefix, but weren't, which has been fixed. - (merge 8d1549643e jc/http-sslkey-and-ssl-cert-are-paths later to maint). * Numerous bugs in walking of reflogs via "log -g" and friends have been fixed. - (merge de239446b6 jk/reflog-walk later to maint). * "git commit" when seeing an totally empty message said "you did not edit the message", which is clearly wrong. The message has been corrected. - (merge bc17f35f8c ks/commit-abort-on-empty-message-fix later to maint). * When a directory is not readable, "gitweb" fails to build the project list. Work this around by skipping such a directory. - (merge 46a13857fc hb/gitweb-project-list later to maint). * Some versions of GnuPG fails to kill gpg-agent it auto-spawned and such a left-over agent can interfere with a test. Work it @@ -127,7 +131,6 @@ Fixes since v2.14 that EOF detection done around the time the connection to the cache daemon is torn down were flaky. This was fixed by reacting to ECONNRESET and behaving as if we got an EOF. - (merge 1f180e5eb9 dl/credential-cache-socket-in-xdg-cache later to maint). * "git log --tag=no-such-tag" showed log starting from HEAD, which has been fixed---it now shows nothing. @@ -188,19 +191,12 @@ Fixes since v2.14 codes; this has been corrected. (merge e1f68c66d5 as/grep-quiet-no-match-exit-code-fix later to maint). + * When handshake with a subprocess filter notices that the process + asked for an unknown capability, Git did not report what program + the offending subprocess was running. This has been corrected. + (merge d3ba566342 cc/subprocess-handshake-missing-capabilities later to maint). + * Other minor doc, test and build updates and code cleanups. - (merge 5b114f3bb0 rs/bswap-ubsan-fix later to maint). - (merge 168e63554c rs/move-array later to maint). - (merge 268ba20110 rs/stat-data-unaligned-reads-fix later to maint). - (merge 78e7b98f45 jt/fsck-code-cleanup later to maint). - (merge c7b0780545 rs/pack-objects-pbase-cleanup later to maint). - (merge c1e860f1dc js/run-process-parallel-api-fix later to maint). - (merge 7a40a95eb4 cc/ref-is-hidden-microcleanup later to maint). - (merge c0bb6d9cef ah/doc-wserrorhighlight later to maint). - (merge edd64ef4f7 dc/fmt-merge-msg-microcleanup later to maint). - (merge fa64a2fdbe jt/subprocess-handshake later to maint). - (merge 0ba9c9a0fb jb/t8008-cleanup later to maint). - (merge a7c28a2161 jt/t1450-fsck-corrupt-packfile later to maint). (merge dff2813391 ab/ref-filter-no-contains later to maint). (merge f094b89a4d ma/parse-maybe-bool later to maint). (merge 974ce8078c mf/no-dashed-subcommands later to maint). @@ -218,3 +214,5 @@ Fixes since v2.14 (merge 2aac933c62 hv/t5526-andand-chain-fix later to maint). (merge c8d0c4fe9b sb/submodule-parallel-update later to maint). (merge 794b7e1674 mg/format-ref-doc-fix later to maint). + (merge 24da8a26a9 rs/commit-h-single-parent-cleanup later to maint). + (merge 4e36907fa3 jk/doc-the-this later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 478b9431e0..dc4e3f58a2 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -776,6 +776,12 @@ core.commentChar:: If set to "auto", `git-commit` would select a character that is not the beginning character of any line in existing commit messages. +core.filesRefLockTimeout:: + The length of time, in milliseconds, to retry when trying to + lock an individual reference. Value 0 means not to retry at + all; -1 means to try indefinitely. Default is 100 (i.e., + retry for 100ms). + core.packedRefsTimeout:: The length of time, in milliseconds, to retry when trying to lock the `packed-refs` file. Value 0 means not to retry at @@ -1077,14 +1083,25 @@ This does not affect linkgit:git-format-patch[1] or the 'git-diff-{asterisk}' plumbing commands. Can be overridden on the command line with the `--color[=<when>]` option. +diff.colorMoved:: + If set to either a valid `<mode>` or a true value, moved lines + in a diff are colored differently, for details of valid modes + see '--color-moved' in linkgit:git-diff[1]. If simply set to + true the default color mode will be used. When set to false, + moved lines are not colored. + color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies which part of the patch to use the specified color, and is one of `context` (context text - `plain` is a historical synonym), `meta` (metainformation), `frag` (hunk header), 'func' (function in hunk header), `old` (removed lines), - `new` (added lines), `commit` (commit headers), or `whitespace` - (highlighting whitespace errors). + `new` (added lines), `commit` (commit headers), `whitespace` + (highlighting whitespace errors), `oldMoved` (deleted lines), + `newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`, + `oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative` + and `newMovedAlternativeDimmed` (See the '<mode>' + setting of '--color-moved' in linkgit:git-diff[1] for details). color.decorate.<slot>:: Use customized color for 'git log --decorate' output. `<slot>` is one @@ -1553,11 +1570,13 @@ gc.<pattern>.reflogExpireUnreachable:: gc.rerereResolved:: Records of conflicted merge you resolved earlier are kept for this many days when 'git rerere gc' is run. + You can also use more human-readable "1.month.ago", etc. The default is 60 days. See linkgit:git-rerere[1]. gc.rerereUnresolved:: Records of conflicted merge you have not resolved are kept for this many days when 'git rerere gc' is run. + You can also use more human-readable "1.month.ago", etc. The default is 15 days. See linkgit:git-rerere[1]. gitcvs.commitMsgAnnotation:: @@ -2912,8 +2931,8 @@ sendemail.smtpsslcertpath:: sendemail.<identity>.*:: Identity-specific versions of the 'sendemail.*' parameters - found below, taking precedence over those when the this - identity is selected, through command-line or + found below, taking precedence over those when this + identity is selected, through either the command-line or `sendemail.identity`. sendemail.aliasesFile:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 56dedafcd4..a88c76741e 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -231,6 +231,40 @@ ifdef::git-diff[] endif::git-diff[] It is the same as `--color=never`. +--color-moved[=<mode>]:: + Moved lines of code are colored differently. +ifdef::git-diff[] + It can be changed by the `diff.colorMoved` configuration setting. +endif::git-diff[] + The <mode> defaults to 'no' if the option is not given + and to 'zebra' if the option with no mode is given. + The mode must be one of: ++ +-- +no:: + Moved lines are not highlighted. +default:: + Is a synonym for `zebra`. This may change to a more sensible mode + in the future. +plain:: + Any line that is added in one location and was removed + in another location will be colored with 'color.diff.newMoved'. + Similarly 'color.diff.oldMoved' will be used for removed lines + that are added somewhere else in the diff. This mode picks up any + moved line, but it is not very useful in a review to determine + if a block of code was moved without permutation. +zebra:: + Blocks of moved text of at least 20 alphanumeric characters + are detected greedily. The detected blocks are + painted using either the 'color.diff.{old,new}Moved' color or + 'color.diff.{old,new}MovedAlternative'. The change between + the two colors indicates that a new block was detected. +dimmed_zebra:: + Similar to 'zebra', but additional dimming of uninteresting parts + of moved code is performed. The bordering lines of two adjacent + blocks are considered interesting, the rest is uninteresting. +-- + --word-diff[=<mode>]:: Show a word diff, using the <mode> to delimit changed words. By default, words are delimited by whitespace; see diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 31cdeaecdf..9dd19a1dd9 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -3,24 +3,27 @@ git-interpret-trailers(1) NAME ---- -git-interpret-trailers - help add structured information into commit messages +git-interpret-trailers - add or parse structured information in commit messages SYNOPSIS -------- [verse] -'git interpret-trailers' [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...] +'git interpret-trailers' [options] [(--trailer <token>[(=|:)<value>])...] [<file>...] +'git interpret-trailers' [options] [--parse] [<file>...] DESCRIPTION ----------- -Help adding 'trailers' lines, that look similar to RFC 822 e-mail +Help parsing or adding 'trailers' lines, that look similar to RFC 822 e-mail headers, at the end of the otherwise free-form part of a commit message. This command reads some patches or commit messages from either the -<file> arguments or the standard input if no <file> is specified. Then -this command applies the arguments passed using the `--trailer` -option, if any, to the commit message part of each input file. The -result is emitted on the standard output. +<file> arguments or the standard input if no <file> is specified. If +`--parse` is specified, the output consists of the parsed trailers. + +Otherwise, this command applies the arguments passed using the +`--trailer` option, if any, to the commit message part of each input +file. The result is emitted on the standard output. Some configuration variables control the way the `--trailer` arguments are applied to each commit message and the way any existing trailer in @@ -80,6 +83,45 @@ OPTIONS trailer to the input messages. See the description of this command. +--where <placement>:: +--no-where:: + Specify where all new trailers will be added. A setting + provided with '--where' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--where' or '--no-where'. + +--if-exists <action>:: +--no-if-exists:: + Specify what action will be performed when there is already at + least one trailer with the same <token> in the message. A setting + provided with '--if-exists' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--if-exists' or '--no-if-exists'. + +--if-missing <action>:: +--no-if-missing:: + Specify what action will be performed when there is no other + trailer with the same <token> in the message. A setting + provided with '--if-missing' overrides all configuration variables + and applies to all '--trailer' options until the next occurrence of + '--if-missing' or '--no-if-missing'. + +--only-trailers:: + Output only the trailers, not any other parts of the input. + +--only-input:: + Output only trailers that exist in the input; do not add any + from the command-line or by following configured `trailer.*` + rules. + +--unfold:: + Remove any whitespace-continuation in trailers, so that each + trailer appears on a line by itself with its full content. + +--parse:: + A convenience alias for `--only-trailers --only-input + --unfold`. + CONFIGURATION VARIABLES ----------------------- @@ -170,8 +212,8 @@ trailer.<token>.where:: configuration variable and it overrides what is specified by that option for trailers with the specified <token>. -trailer.<token>.ifexist:: - This option takes the same values as the 'trailer.ifexist' +trailer.<token>.ifexists:: + This option takes the same values as the 'trailer.ifexists' configuration variable and it overrides what is specified by that option for trailers with the specified <token>. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 04fdd8cf08..6b308ab6d0 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -64,6 +64,14 @@ OPTIONS ------- include::merge-options.txt[] +--signoff:: + Add Signed-off-by line by the committer at the end of the commit + log message. The meaning of a signoff depends on the project, + but it typically certifies that committer has + the rights to submit this work under the same license and + agrees to a Developer Certificate of Origin + (see http://developercertificate.org/ for more information). + -S[<keyid>]:: --gpg-sign[=<keyid>]:: GPG-sign the resulting merge commit. The `keyid` argument is diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 8973510a41..473a16135a 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -18,8 +18,9 @@ SYNOPSIS DESCRIPTION ----------- -Reads list of objects from the standard input, and writes a packed -archive with specified base-name, or to the standard output. +Reads list of objects from the standard input, and writes either one or +more packed archives with the specified base-name to disk, or a packed +archive to the standard output. A packed archive is an efficient way to transfer a set of objects between two repositories as well as an access efficient archival @@ -47,9 +48,9 @@ transport by their peers. OPTIONS ------- base-name:: - Write into a pair of files (.pack and .idx), using + Write into pairs of files (.pack and .idx), using <base-name> to determine the name of the created file. - When this option is used, the two files are written in + When this option is used, the two files in a pair are written in <base-name>-<SHA-1>.{pack,idx} files. <SHA-1> is a hash based on the pack content and is written to the standard output of the command. @@ -108,9 +109,13 @@ base-name:: is taken from the `pack.windowMemory` configuration variable. --max-pack-size=<n>:: - Maximum size of each output pack file. The size can be suffixed with + In unusual scenarios, you may not be able to create files + larger than a certain size on your filesystem, and this option + can be used to tell the command to split the output packfile + into multiple independent packfiles, each not larger than the + given size. The size can be suffixed with "k", "m", or "g". The minimum size allowed is limited to 1 MiB. - If specified, multiple packfiles may be created, which also + This option prevents the creation of a bitmap index. The default is unlimited, unless the config variable `pack.packSizeLimit` is set. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 973d19606b..d433d50f81 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -205,7 +205,10 @@ endif::git-rev-list[] - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)' respectively, but padding both sides (i.e. the text is centered) - %(trailers): display the trailers of the body as interpreted by - linkgit:git-interpret-trailers[1] + linkgit:git-interpret-trailers[1]. If the `:only` option is given, + omit non-trailer lines from the trailer block. If the `:unfold` + option is given, behave as if interpret-trailer's `--unfold` option + was given. E.g., `%(trailers:only:unfold)` to do both. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/Documentation/technical/api-tree-walking.txt b/Documentation/technical/api-tree-walking.txt index 14af37c3f1..bde18622a8 100644 --- a/Documentation/technical/api-tree-walking.txt +++ b/Documentation/technical/api-tree-walking.txt @@ -55,9 +55,9 @@ Initializing `fill_tree_descriptor`:: - Initialize a `tree_desc` and decode its first entry given the sha1 of - a tree. Returns the `buffer` member if the sha1 is a valid tree - identifier and NULL otherwise. + Initialize a `tree_desc` and decode its first entry given the + object ID of a tree. Returns the `buffer` member if the latter + is a valid tree identifier and NULL otherwise. `setup_traverse_info`:: @@ -655,6 +655,7 @@ TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils TEST_PROGRAMS_NEED_X += test-prio-queue TEST_PROGRAMS_NEED_X += test-read-cache +TEST_PROGRAMS_NEED_X += test-write-cache TEST_PROGRAMS_NEED_X += test-ref-store TEST_PROGRAMS_NEED_X += test-regex TEST_PROGRAMS_NEED_X += test-revision-walking @@ -2039,7 +2040,6 @@ XDIFF_OBJS += xdiff/xhistogram.o VCSSVN_OBJS += vcs-svn/line_buffer.o VCSSVN_OBJS += vcs-svn/sliding_window.o -VCSSVN_OBJS += vcs-svn/repo_tree.o VCSSVN_OBJS += vcs-svn/fast_export.o VCSSVN_OBJS += vcs-svn/svndiff.o VCSSVN_OBJS += vcs-svn/svndump.o @@ -219,6 +219,7 @@ struct patch { unsigned int recount:1; unsigned int conflicted_threeway:1; unsigned int direct_to_threeway:1; + unsigned int crlf_in_old:1; struct fragment *fragments; char *result; size_t resultsize; @@ -1662,6 +1663,19 @@ static void check_whitespace(struct apply_state *state, } /* + * Check if the patch has context lines with CRLF or + * the patch wants to remove lines with CRLF. + */ +static void check_old_for_crlf(struct patch *patch, const char *line, int len) +{ + if (len >= 2 && line[len-1] == '\n' && line[len-2] == '\r') { + patch->ws_rule |= WS_CR_AT_EOL; + patch->crlf_in_old = 1; + } +} + + +/* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference * between a "---" that is part of a patch, and a "---" that starts @@ -1711,11 +1725,14 @@ static int parse_fragment(struct apply_state *state, if (!deleted && !added) leading++; trailing++; + check_old_for_crlf(patch, line, len); if (!state->apply_in_reverse && state->ws_error_action == correct_ws_error) check_whitespace(state, line, len, patch->ws_rule); break; case '-': + if (!state->apply_in_reverse) + check_old_for_crlf(patch, line, len); if (state->apply_in_reverse && state->ws_error_action != nowarn_ws_error) check_whitespace(state, line, len, patch->ws_rule); @@ -1724,6 +1741,8 @@ static int parse_fragment(struct apply_state *state, trailing = 0; break; case '+': + if (state->apply_in_reverse) + check_old_for_crlf(patch, line, len); if (!state->apply_in_reverse && state->ws_error_action != nowarn_ws_error) check_whitespace(state, line, len, patch->ws_rule); @@ -2266,8 +2285,11 @@ static void show_stats(struct apply_state *state, struct patch *patch) add, pluses, del, minuses); } -static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) +static int read_old_data(struct stat *st, struct patch *patch, + const char *path, struct strbuf *buf) { + enum safe_crlf safe_crlf = patch->crlf_in_old ? + SAFE_CRLF_KEEP_CRLF : SAFE_CRLF_RENORMALIZE; switch (st->st_mode & S_IFMT) { case S_IFLNK: if (strbuf_readlink(buf, path, st->st_size) < 0) @@ -2276,7 +2298,15 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) case S_IFREG: if (strbuf_read_file(buf, path, st->st_size) != st->st_size) return error(_("unable to open or read %s"), path); - convert_to_git(&the_index, path, buf->buf, buf->len, buf, 0); + /* + * "git apply" without "--index/--cached" should never look + * at the index; the target file may not have been added to + * the index yet, and we may not even be in any Git repository. + * Pass NULL to convert_to_git() to stress this; the function + * should never look at the index when explicit crlf option + * is given. + */ + convert_to_git(NULL, path, buf->buf, buf->len, buf, safe_crlf); return 0; default: return -1; @@ -3379,6 +3409,7 @@ static int load_patch_target(struct apply_state *state, struct strbuf *buf, const struct cache_entry *ce, struct stat *st, + struct patch *patch, const char *name, unsigned expected_mode) { @@ -3394,7 +3425,7 @@ static int load_patch_target(struct apply_state *state, } else if (has_symlink_leading_path(name, strlen(name))) { return error(_("reading from '%s' beyond a symbolic link"), name); } else { - if (read_old_data(st, name, buf)) + if (read_old_data(st, patch, name, buf)) return error(_("failed to read %s"), name); } } @@ -3427,7 +3458,7 @@ static int load_preimage(struct apply_state *state, /* We have a patched copy in memory; use that. */ strbuf_add(&buf, previous->result, previous->resultsize); } else { - status = load_patch_target(state, &buf, ce, st, + status = load_patch_target(state, &buf, ce, st, patch, patch->old_name, patch->old_mode); if (status < 0) return status; @@ -3515,7 +3546,7 @@ static int load_current(struct apply_state *state, if (verify_index_match(ce, &st)) return error(_("%s: does not match index"), name); - status = load_patch_target(state, &buf, ce, &st, name, mode); + status = load_patch_target(state, &buf, ce, &st, patch, name, mode); if (status < 0) return status; else if (status) diff --git a/builtin/add.c b/builtin/add.c index 5d5773d5cd..c20548e4f5 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -116,6 +116,7 @@ int add_files_to_cache(const char *prefix, rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &data; + rev.diffopt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); return !!data.add_errors; diff --git a/builtin/blame.c b/builtin/blame.c index bda1a78726..e0daf17548 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -925,8 +925,7 @@ parse_done: sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π if (show_progress) - pi.progress = start_progress_delay(_("Blaming lines"), - sb.num_lines, 50, 1); + pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines); assign_blame(&sb, opt); diff --git a/builtin/checkout.c b/builtin/checkout.c index 2d75ac66c7..5c202b7af5 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -861,7 +861,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb) } if (starts_with(var, "submodule.")) - return submodule_config(var, value, NULL); + return git_default_submodule_config(var, value, NULL); return git_xmerge_config(var, value, NULL); } @@ -1182,7 +1182,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.prefix = prefix; opts.show_progress = -1; - gitmodules_config(); git_config(git_checkout_config, &opts); opts.track = BRANCH_TRACK_UNSPECIFIED; diff --git a/builtin/commit.c b/builtin/commit.c index b79bcfd5b9..b3b04f5dd3 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -195,7 +195,6 @@ static void determine_whence(struct wt_status *s) static void status_init_config(struct wt_status *s, config_fn_t fn) { wt_status_prepare(s); - gitmodules_config(); git_config(fn, s); determine_whence(s); init_diff_ui_defaults(); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 17bf84d18f..e88493ffe5 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -26,7 +26,6 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(&rev, prefix); - gitmodules_config(); rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 185e6f9b58..9d772f8f27 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -23,7 +23,6 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(&rev, prefix); - gitmodules_config(); rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 31d2cb4107..d66499909e 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -110,7 +110,6 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(opt, prefix); - gitmodules_config(); opt->abbrev = 0; opt->diff = 1; opt->disable_stdin = 1; diff --git a/builtin/diff.c b/builtin/diff.c index 7cde6abbcf..7e3ebcea38 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -315,8 +315,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix) no_index = DIFF_NO_INDEX_IMPLICIT; } - if (!no_index) - gitmodules_config(); init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); precompose_argv(argc, argv); diff --git a/builtin/difftool.c b/builtin/difftool.c index 8864d846f8..b2d3ba7539 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -111,7 +111,7 @@ static int use_wt_file(const char *workdir, const char *name, int fd = open(buf.buf, O_RDONLY); if (fd >= 0 && - !index_fd(wt_oid.hash, fd, &st, OBJ_BLOB, name, 0)) { + !index_fd(&wt_oid, fd, &st, OBJ_BLOB, name, 0)) { if (is_null_oid(oid)) { oidcpy(oid, &wt_oid); use = 1; diff --git a/builtin/fetch.c b/builtin/fetch.c index 08e094bf12..225c734924 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1361,11 +1361,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (recurse_submodules != RECURSE_SUBMODULES_OFF) { - gitmodules_config(); - git_config(submodule_config, NULL); - } - if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument")); diff --git a/builtin/fsck.c b/builtin/fsck.c index 338f3ce20b..1e4c471b41 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -180,7 +180,7 @@ static int traverse_reachable(void) unsigned int nr = 0; int result = 0; if (show_progress) - progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2); + progress = start_delayed_progress(_("Checking connectivity"), 0); while (pending.nr) { struct object_array_entry *entry; struct object *obj; diff --git a/builtin/grep.c b/builtin/grep.c index a70d8e2fba..19e23946ac 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -1048,10 +1048,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } #endif - if (recurse_submodules) { - gitmodules_config(); - } - if (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); diff --git a/builtin/hash-object.c b/builtin/hash-object.c index d04baf999a..c532ff9320 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -16,7 +16,7 @@ * needs to bypass the data conversion performed by, and the type * limitation imposed by, index_fd() and its callees. */ -static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigned flags) +static int hash_literally(struct object_id *oid, int fd, const char *type, unsigned flags) { struct strbuf buf = STRBUF_INIT; int ret; @@ -24,7 +24,7 @@ static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigne if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; else - ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags); + ret = hash_sha1_file_literally(buf.buf, buf.len, type, oid, flags); strbuf_release(&buf); return ret; } @@ -33,16 +33,16 @@ static void hash_fd(int fd, const char *type, const char *path, unsigned flags, int literally) { struct stat st; - unsigned char sha1[20]; + struct object_id oid; if (fstat(fd, &st) < 0 || (literally - ? hash_literally(sha1, fd, type, flags) - : index_fd(sha1, fd, &st, type_from_string(type), path, flags))) + ? hash_literally(&oid, fd, type, flags) + : index_fd(&oid, fd, &st, type_from_string(type), path, flags))) die((flags & HASH_WRITE_OBJECT) ? "Unable to add %s to database" : "Unable to hash %s", path); - printf("%s\n", sha1_to_hex(sha1)); + printf("%s\n", oid_to_hex(&oid)); maybe_flush_or_die(stdout, "hash to stdout"); } diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 175f14797b..b742539d4d 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -16,34 +16,119 @@ static const char * const git_interpret_trailers_usage[] = { NULL }; +static enum trailer_where where; +static enum trailer_if_exists if_exists; +static enum trailer_if_missing if_missing; + +static int option_parse_where(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_where(&where, arg); +} + +static int option_parse_if_exists(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_exists(&if_exists, arg); +} + +static int option_parse_if_missing(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_missing(&if_missing, arg); +} + +static void new_trailers_clear(struct list_head *trailers) +{ + struct list_head *pos, *tmp; + struct new_trailer_item *item; + + list_for_each_safe(pos, tmp, trailers) { + item = list_entry(pos, struct new_trailer_item, list); + list_del(pos); + free(item); + } +} + +static int option_parse_trailer(const struct option *opt, + const char *arg, int unset) +{ + struct list_head *trailers = opt->value; + struct new_trailer_item *item; + + if (unset) { + new_trailers_clear(trailers); + return 0; + } + + if (!arg) + return -1; + + item = xmalloc(sizeof(*item)); + item->text = arg; + item->where = where; + item->if_exists = if_exists; + item->if_missing = if_missing; + list_add_tail(&item->list, trailers); + return 0; +} + +static int parse_opt_parse(const struct option *opt, const char *arg, + int unset) +{ + struct process_trailer_options *v = opt->value; + v->only_trailers = 1; + v->only_input = 1; + v->unfold = 1; + return 0; +} + int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) { - int in_place = 0; - int trim_empty = 0; - struct string_list trailers = STRING_LIST_INIT_NODUP; + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + LIST_HEAD(trailers); struct option options[] = { - OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), - OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), - OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), - N_("trailer(s) to add")), + OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")), + OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")), + + OPT_CALLBACK(0, "where", NULL, N_("action"), + N_("where to place the new trailer"), option_parse_where), + OPT_CALLBACK(0, "if-exists", NULL, N_("action"), + N_("action if trailer already exists"), option_parse_if_exists), + OPT_CALLBACK(0, "if-missing", NULL, N_("action"), + N_("action if trailer is missing"), option_parse_if_missing), + + OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), + OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")), + OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")), + { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse }, + OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), + N_("trailer(s) to add"), option_parse_trailer), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_interpret_trailers_usage, 0); + if (opts.only_input && !list_empty(&trailers)) + usage_msg_opt( + _("--trailer with --only-input does not make sense"), + git_interpret_trailers_usage, + options); + if (argc) { int i; for (i = 0; i < argc; i++) - process_trailers(argv[i], in_place, trim_empty, &trailers); + process_trailers(argv[i], &opts, &trailers); } else { - if (in_place) + if (opts.in_place) die(_("no input file given for in-place editing")); - process_trailers(NULL, in_place, trim_empty, &trailers); + process_trailers(NULL, &opts, &trailers); } - string_list_clear(&trailers, 0); + new_trailers_clear(&trailers); return 0; } diff --git a/builtin/log.c b/builtin/log.c index 25c0808409..f8cccbc964 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1759,7 +1759,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.add_signoff = do_signoff; if (show_progress) - progress = start_progress_delay(_("Generating patches"), total, 0, 2); + progress = start_delayed_progress(_("Generating patches"), total); while (0 <= --nr) { int shown; display_progress(progress, total - nr); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c6126eae55..e1339e6d17 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -19,6 +19,7 @@ #include "pathspec.h" #include "run-command.h" #include "submodule.h" +#include "submodule-config.h" static int abbrev; static int show_deleted; @@ -210,8 +211,6 @@ static void show_submodule(struct repository *superproject, if (repo_read_index(&submodule) < 0) die("index file corrupt"); - repo_read_gitmodules(&submodule); - show_files(&submodule, dir); repo_clear(&submodule); @@ -609,9 +608,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); - if (recurse_submodules) - repo_read_gitmodules(the_repository); - if (recurse_submodules && (show_stage || show_deleted || show_others || show_unmerged || show_killed || show_modified || show_resolve_undo || with_tree)) diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index f12da292cf..d01ddecf66 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -213,11 +213,11 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid->hash : NULL) - buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); - buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); - buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); -#undef ENTRY_SHA1 +#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid : NULL) + buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0)); + buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1)); + buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2)); +#undef ENTRY_OID merge_trees(t, newbase); @@ -352,7 +352,7 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) if (get_oid(rev, &oid)) die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, oid.hash); + buf = fill_tree_descriptor(desc, &oid); if (!buf) die("%s is not a tree", rev); return buf; diff --git a/builtin/merge.c b/builtin/merge.c index dfd6830602..7b7320dede 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -71,6 +71,7 @@ static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; +static int signoff; static const char *sign_commit; static struct strategy all_strategy[] = { @@ -234,6 +235,7 @@ static struct option builtin_merge_options[] = { { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), + OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), OPT_END() }; @@ -764,6 +766,8 @@ static void prepare_to_commit(struct commit_list *remoteheads) strbuf_addch(&msg, '\n'); if (0 < option_edit) strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); + if (signoff) + append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); write_file_buf(git_path_merge_msg(), msg.buf, msg.len); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", git_path_merge_msg(), "merge", NULL)) diff --git a/builtin/mv.c b/builtin/mv.c index 94fbaaa5da..ffdd5f01a1 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -131,7 +131,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; - gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_mv_options, diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 97bfde24ba..419238171d 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -38,8 +38,7 @@ static int prune_object(const struct object_id *oid, const char *path, void prune_packed_objects(int opts) { if (opts & PRUNE_PACKED_VERBOSE) - progress = start_progress_delay(_("Removing duplicate objects"), - 256, 95, 2); + progress = start_delayed_progress(_("Removing duplicate objects"), 256); for_each_loose_file_in_objdir(get_object_directory(), prune_object, NULL, prune_subdir, &opts); diff --git a/builtin/prune.c b/builtin/prune.c index c378690545..cddabf26a9 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -138,7 +138,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) if (show_progress == -1) show_progress = isatty(2); if (show_progress) - progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2); + progress = start_delayed_progress(_("Checking connectivity"), 0); mark_reachable_objects(&revs, 1, expire, progress); stop_progress(&progress); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index d5f618d086..bf87a2710b 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -164,8 +164,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - load_submodule_cache(); - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); prefix_set = opts.prefix ? 1 : 0; diff --git a/builtin/replace.c b/builtin/replace.c index f4a85a165b..3e71a77152 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -269,7 +269,7 @@ static void import_object(struct object_id *oid, enum object_type type, if (fstat(fd, &st) < 0) die_errno("unable to fstat %s", filename); - if (index_fd(oid->hash, fd, &st, type, NULL, flags) < 0) + if (index_fd(oid, fd, &st, type, NULL, flags) < 0) die("unable to write object to database"); /* index_fd close()s fd for us */ } diff --git a/builtin/reset.c b/builtin/reset.c index 046403ed68..d72c7d1c96 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -75,13 +75,13 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) struct object_id head_oid; if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc, head_oid.hash)) + if (!fill_tree_descriptor(desc, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr - 1, oid->hash)) + if (!fill_tree_descriptor(desc + nr - 1, oid)) return error(_("Failed to find tree of %s."), oid_to_hex(oid)); if (unpack_trees(nr, desc, &opts)) return -1; @@ -156,6 +156,7 @@ static int read_from_tree(const struct pathspec *pathspec, opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; + opt.flags |= DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG; if (do_diff_cache(tree_oid, &opt)) return 1; @@ -308,8 +309,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); - load_submodule_cache(); - unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 95b4128250..c1c74d4a79 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -367,7 +367,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.limited = 1; if (show_progress) - progress = start_progress_delay(show_progress, 0, 0, 2); + progress = start_delayed_progress(show_progress, 0); if (use_bitmap_index && !revs.prune) { if (revs.count && !revs.left_right && !revs.cherry_mark) { diff --git a/builtin/rm.c b/builtin/rm.c index 4057e73fa0..d91451fea1 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -255,7 +255,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) struct pathspec pathspec; char *seen; - gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_rm_options, diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 0ff9dd0b85..818fe74f0a 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -275,8 +275,6 @@ static void module_list_active(struct module_list *list) int i; struct module_list active_modules = MODULE_LIST_INIT; - gitmodules_config(); - for (i = 0; i < list->nr; i++) { const struct cache_entry *ce = list->entries[i]; @@ -337,9 +335,6 @@ static void init_submodule(const char *path, const char *prefix, int quiet) struct strbuf sb = STRBUF_INIT; char *upd = NULL, *url = NULL, *displaypath; - /* Only loads from .gitmodules, no overlay with .git/config */ - gitmodules_config(); - if (prefix && get_super_prefix()) die("BUG: cannot have prefix and superprefix"); else if (prefix) @@ -475,7 +470,6 @@ static int module_name(int argc, const char **argv, const char *prefix) if (argc != 2) usage(_("git submodule--helper name <path>")); - gitmodules_config(); sub = submodule_from_path(&null_oid, argv[1]); if (!sub) @@ -780,6 +774,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, struct strbuf *out) { const struct submodule *sub = NULL; + const char *url = NULL; + const char *update_string; + enum submodule_update_type update_type; + char *key; struct strbuf displaypath_sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; const char *displaypath = NULL; @@ -808,9 +806,17 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, goto cleanup; } + key = xstrfmt("submodule.%s.update", sub->name); + if (!repo_config_get_string_const(the_repository, key, &update_string)) { + update_type = parse_submodule_update_type(update_string); + } else { + update_type = sub->update_strategy.type; + } + free(key); + if (suc->update.type == SM_UPDATE_NONE || (suc->update.type == SM_UPDATE_UNSPECIFIED - && sub->update_strategy.type == SM_UPDATE_NONE)) { + && update_type == SM_UPDATE_NONE)) { strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); strbuf_addch(out, '\n'); goto cleanup; @@ -823,6 +829,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, } strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + if (repo_config_get_string_const(the_repository, sb.buf, &url)) + url = sub->url; + + strbuf_reset(&sb); strbuf_addf(&sb, "%s/.git", ce->name); needs_cloning = !file_exists(sb.buf); @@ -851,7 +862,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_push(&child->args, "--depth=1"); argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); - argv_array_pushl(&child->args, "--url", sub->url, NULL); + argv_array_pushl(&child->args, "--url", url, NULL); if (suc->references.nr) { struct string_list_item *item; for_each_string_list_item(item, &suc->references) @@ -1025,10 +1036,6 @@ static int update_clone(int argc, const char **argv, const char *prefix) if (pathspec.nr) suc.warn_if_uninitialized = 1; - /* Overlay the parsed .gitmodules file with .git/config */ - gitmodules_config(); - git_config(submodule_config, NULL); - run_processes_parallel(max_jobs, update_clone_get_next_task, update_clone_start_failure, @@ -1066,17 +1073,22 @@ static int resolve_relative_path(int argc, const char **argv, const char *prefix static const char *remote_submodule_branch(const char *path) { const struct submodule *sub; - gitmodules_config(); - git_config(submodule_config, NULL); + const char *branch = NULL; + char *key; sub = submodule_from_path(&null_oid, path); if (!sub) return NULL; - if (!sub->branch) + key = xstrfmt("submodule.%s.branch", sub->name); + if (repo_config_get_string_const(the_repository, key, &branch)) + branch = sub->branch; + free(key); + + if (!branch) return "master"; - if (!strcmp(sub->branch, ".")) { + if (!strcmp(branch, ".")) { unsigned char sha1[20]; const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); @@ -1094,7 +1106,7 @@ static const char *remote_submodule_branch(const char *path) return refname; } - return sub->branch; + return branch; } static int resolve_remote_submodule_branch(int argc, const char **argv, @@ -1213,9 +1225,6 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, embed_gitdir_options, git_submodule_helper_usage, 0); - gitmodules_config(); - git_config(submodule_config, NULL); - if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) return 1; @@ -1231,8 +1240,6 @@ static int is_active(int argc, const char **argv, const char *prefix) if (argc != 2) die("submodule--helper is-active takes exactly 1 argument"); - gitmodules_config(); - return !is_submodule_active(the_repository, argv[1]); } diff --git a/builtin/update-index.c b/builtin/update-index.c index 56721cf03d..d562f2ec69 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -280,7 +280,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->oid.hash, path, st, + if (index_path(&ce->oid, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { free(ce); return -1; @@ -684,8 +684,8 @@ extern int ie_modified(const struct index_state *, const struct cache_entry *, s #define HASH_WRITE_OBJECT 1 #define HASH_FORMAT_CHECK 2 -extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); -extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags); +extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); +extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags); /* * Record to sd the data from st that we use to check whether a file @@ -1178,7 +1178,7 @@ static inline const unsigned char *lookup_replace_object(const unsigned char *sh extern int sha1_object_info(const unsigned char *, unsigned long *); extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1); -extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags); +extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, struct object_id *oid, unsigned flags); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); extern int git_open_cloexec(const char *name, int flags); @@ -1835,6 +1835,8 @@ void shift_tree_by(const struct object_id *, const struct object_id *, struct ob #define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8) #define WS_TAB_WIDTH_MASK 077 +/* All WS_* -- when extended, adapt diff.c emit_symbol */ +#define WS_RULE_MASK 07777 extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); @@ -42,6 +42,8 @@ struct strbuf; #define GIT_COLOR_BG_BLUE "\033[44m" #define GIT_COLOR_BG_MAGENTA "\033[45m" #define GIT_COLOR_BG_CYAN "\033[46m" +#define GIT_COLOR_FAINT "\033[2m" +#define GIT_COLOR_FAINT_ITALIC "\033[2;3m" /* A special value meaning "no color selected" */ #define GIT_COLOR_NIL "NIL" @@ -313,11 +313,6 @@ extern int interactive_add(int argc, const char **argv, const char *prefix, int extern int run_add_interactive(const char *revision, const char *patch_mode, const struct pathspec *pathspec); -static inline int single_parent(struct commit *commit) -{ - return commit->parents && !commit->parents->next; -} - struct commit_list *reduce_heads(struct commit_list *heads); struct commit_extra_header { @@ -2094,6 +2094,28 @@ int git_config_get_expiry(const char *key, const char **output) return ret; } +int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now) +{ + char *expiry_string; + intmax_t days; + timestamp_t when; + + if (git_config_get_string(key, &expiry_string)) + return 1; /* no such thing */ + + if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) { + const int scale = 86400; + *expiry = now - days * scale; + return 0; + } + + if (!parse_expiry_date(expiry_string, &when)) { + *expiry = when; + return 0; + } + return -1; /* thing exists but cannot be parsed */ +} + int git_config_get_untracked_cache(void) { int val = -1; @@ -215,6 +215,9 @@ extern int git_config_get_max_percent_split_change(void); /* This dies if the configured or default date is in the future */ extern int git_config_get_expiry(const char *key, const char **output); +/* parse either "this many days" integer, or "5.days.ago" approxidate */ +extern int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now); + struct key_value_info { const char *filename; int linenr; @@ -1132,10 +1132,12 @@ int convert_to_git(const struct index_state *istate, src = dst->buf; len = dst->len; } - ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe); - if (ret && dst) { - src = dst->buf; - len = dst->len; + if (checksafe != SAFE_CRLF_KEEP_CRLF) { + ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe); + if (ret && dst) { + src = dst->buf; + len = dst->len; + } } return ret | ident_to_git(path, src, len, dst, ca.ident); } @@ -12,7 +12,8 @@ enum safe_crlf { SAFE_CRLF_FALSE = 0, SAFE_CRLF_FAIL = 1, SAFE_CRLF_WARN = 2, - SAFE_CRLF_RENORMALIZE = 3 + SAFE_CRLF_RENORMALIZE = 3, + SAFE_CRLF_KEEP_CRLF = 4 }; extern enum safe_crlf safe_crlf; @@ -16,6 +16,7 @@ #include "userdiff.h" #include "submodule-config.h" #include "submodule.h" +#include "hashmap.h" #include "ll-merge.h" #include "string-list.h" #include "argv-array.h" @@ -33,6 +34,7 @@ static int diff_indent_heuristic = 1; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; +static int diff_color_moved_default; static int diff_context_default = 3; static int diff_interhunk_context_default; static const char *diff_word_regex_cfg; @@ -57,6 +59,14 @@ static char diff_colors[][COLOR_MAXLEN] = { GIT_COLOR_YELLOW, /* COMMIT */ GIT_COLOR_BG_RED, /* WHITESPACE */ GIT_COLOR_NORMAL, /* FUNCINFO */ + GIT_COLOR_BOLD_MAGENTA, /* OLD_MOVED */ + GIT_COLOR_BOLD_BLUE, /* OLD_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* OLD_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* OLD_MOVED_ALTERNATIVE_DIM */ + GIT_COLOR_BOLD_CYAN, /* NEW_MOVED */ + GIT_COLOR_BOLD_YELLOW, /* NEW_MOVED ALTERNATIVE */ + GIT_COLOR_FAINT, /* NEW_MOVED_DIM */ + GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */ }; static NORETURN void die_want_option(const char *option_name) @@ -82,6 +92,22 @@ static int parse_diff_color_slot(const char *var) return DIFF_WHITESPACE; if (!strcasecmp(var, "func")) return DIFF_FUNCINFO; + if (!strcasecmp(var, "oldmoved")) + return DIFF_FILE_OLD_MOVED; + if (!strcasecmp(var, "oldmovedalternative")) + return DIFF_FILE_OLD_MOVED_ALT; + if (!strcasecmp(var, "oldmoveddimmed")) + return DIFF_FILE_OLD_MOVED_DIM; + if (!strcasecmp(var, "oldmovedalternativedimmed")) + return DIFF_FILE_OLD_MOVED_ALT_DIM; + if (!strcasecmp(var, "newmoved")) + return DIFF_FILE_NEW_MOVED; + if (!strcasecmp(var, "newmovedalternative")) + return DIFF_FILE_NEW_MOVED_ALT; + if (!strcasecmp(var, "newmoveddimmed")) + return DIFF_FILE_NEW_MOVED_DIM; + if (!strcasecmp(var, "newmovedalternativedimmed")) + return DIFF_FILE_NEW_MOVED_ALT_DIM; return -1; } @@ -230,12 +256,44 @@ int git_diff_heuristic_config(const char *var, const char *value, void *cb) return 0; } +static int parse_color_moved(const char *arg) +{ + switch (git_parse_maybe_bool(arg)) { + case 0: + return COLOR_MOVED_NO; + case 1: + return COLOR_MOVED_DEFAULT; + default: + break; + } + + if (!strcmp(arg, "no")) + return COLOR_MOVED_NO; + else if (!strcmp(arg, "plain")) + return COLOR_MOVED_PLAIN; + else if (!strcmp(arg, "zebra")) + return COLOR_MOVED_ZEBRA; + else if (!strcmp(arg, "default")) + return COLOR_MOVED_DEFAULT; + else if (!strcmp(arg, "dimmed_zebra")) + return COLOR_MOVED_ZEBRA_DIM; + else + return error(_("color moved setting must be one of 'no', 'default', 'zebra', 'dimmed_zebra', 'plain'")); +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff_use_color_default = git_config_colorbool(var, value); return 0; } + if (!strcmp(var, "diff.colormoved")) { + int cm = parse_color_moved(value); + if (cm < 0) + return -1; + diff_color_moved_default = cm; + return 0; + } if (!strcmp(var, "diff.context")) { diff_context_default = git_config_int(var, value); if (diff_context_default < 0) @@ -344,9 +402,6 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } - if (starts_with(var, "submodule.")) - return parse_submodule_config_option(var, value); - if (git_diff_heuristic_config(var, value, cb) < 0) return -1; @@ -555,68 +610,735 @@ static void emit_line(struct diff_options *o, const char *set, const char *reset 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) +enum diff_symbol { + DIFF_SYMBOL_BINARY_DIFF_HEADER, + DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA, + DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL, + DIFF_SYMBOL_BINARY_DIFF_BODY, + DIFF_SYMBOL_BINARY_DIFF_FOOTER, + DIFF_SYMBOL_STATS_SUMMARY_NO_FILES, + DIFF_SYMBOL_STATS_SUMMARY_ABBREV, + DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES, + DIFF_SYMBOL_STATS_LINE, + DIFF_SYMBOL_WORD_DIFF, + DIFF_SYMBOL_STAT_SEP, + DIFF_SYMBOL_SUMMARY, + DIFF_SYMBOL_SUBMODULE_ADD, + DIFF_SYMBOL_SUBMODULE_DEL, + DIFF_SYMBOL_SUBMODULE_UNTRACKED, + DIFF_SYMBOL_SUBMODULE_MODIFIED, + DIFF_SYMBOL_SUBMODULE_HEADER, + DIFF_SYMBOL_SUBMODULE_ERROR, + DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, + DIFF_SYMBOL_REWRITE_DIFF, + DIFF_SYMBOL_BINARY_FILES, + DIFF_SYMBOL_HEADER, + DIFF_SYMBOL_FILEPAIR_PLUS, + DIFF_SYMBOL_FILEPAIR_MINUS, + DIFF_SYMBOL_WORDS_PORCELAIN, + DIFF_SYMBOL_WORDS, + DIFF_SYMBOL_CONTEXT, + DIFF_SYMBOL_CONTEXT_INCOMPLETE, + DIFF_SYMBOL_PLUS, + DIFF_SYMBOL_MINUS, + DIFF_SYMBOL_NO_LF_EOF, + DIFF_SYMBOL_CONTEXT_FRAGINFO, + DIFF_SYMBOL_CONTEXT_MARKER, + DIFF_SYMBOL_SEPARATOR +}; +/* + * Flags for content lines: + * 0..12 are whitespace rules + * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT + * 16 is marking if the line is blank at EOF + */ +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16) +#define DIFF_SYMBOL_MOVED_LINE (1<<17) +#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18) +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19) +#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK) + +/* + * This struct is used when we need to buffer the output of the diff output. + * + * NEEDSWORK: Instead of storing a copy of the line, add an offset pointer + * into the pre/post image file. This pointer could be a union with the + * line pointer. By storing an offset into the file instead of the literal line, + * we can decrease the memory footprint for the buffered output. At first we + * may want to only have indirection for the content lines, but we could also + * enhance the state for emitting prefabricated lines, e.g. the similarity + * score line or hunk/file headers would only need to store a number or path + * and then the output can be constructed later on depending on state. + */ +struct emitted_diff_symbol { + const char *line; + int len; + int flags; + enum diff_symbol s; +}; +#define EMITTED_DIFF_SYMBOL_INIT {NULL} + +struct emitted_diff_symbols { + struct emitted_diff_symbol *buf; + int nr, alloc; +}; +#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0} + +static void append_emitted_diff_symbol(struct diff_options *o, + struct emitted_diff_symbol *e) { - if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof_in_preimage && - ecbdata->blank_at_eof_in_postimage && - ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && - ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) - return 0; - return ws_blank_line(line, len, ecbdata->ws_rule); + struct emitted_diff_symbol *f; + + ALLOC_GROW(o->emitted_symbols->buf, + o->emitted_symbols->nr + 1, + o->emitted_symbols->alloc); + f = &o->emitted_symbols->buf[o->emitted_symbols->nr++]; + + memcpy(f, e, sizeof(struct emitted_diff_symbol)); + f->line = e->line ? xmemdupz(e->line, e->len) : NULL; } -static void emit_line_checked(const char *reset, - struct emit_callback *ecbdata, - const char *line, int len, - enum color_diff color, - unsigned ws_error_highlight, - char sign) +struct moved_entry { + struct hashmap_entry ent; + const struct emitted_diff_symbol *es; + struct moved_entry *next_line; +}; + +static int next_byte(const char **cp, const char **endp, + const struct diff_options *diffopt) +{ + int retval; + + if (*cp > *endp) + return -1; + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* + * After skipping a couple of whitespaces, we still have to + * account for one space. + */ + return (int)' '; + } + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) { + while (*cp < *endp && isspace(**cp)) + (*cp)++; + /* return the first non-ws character via the usual below */ + } + + retval = (unsigned char)(**cp); + (*cp)++; + return retval; +} + +static int moved_entry_cmp(const struct diff_options *diffopt, + const struct moved_entry *a, + const struct moved_entry *b, + const void *keydata) +{ + const char *ap = a->es->line, *ae = a->es->line + a->es->len; + const char *bp = b->es->line, *be = b->es->line + b->es->len; + + if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS)) + return a->es->len != b->es->len || memcmp(ap, bp, a->es->len); + + if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) { + while (ae > ap && isspace(*ae)) + ae--; + while (be > bp && isspace(*be)) + be--; + } + + while (1) { + int ca, cb; + ca = next_byte(&ap, &ae, diffopt); + cb = next_byte(&bp, &be, diffopt); + if (ca != cb) + return 1; + if (ca < 0) + return 0; + } +} + +static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o) +{ + if (o->xdl_opts & XDF_WHITESPACE_FLAGS) { + static struct strbuf sb = STRBUF_INIT; + const char *ap = es->line, *ae = es->line + es->len; + int c; + + strbuf_reset(&sb); + while (ae > ap && isspace(*ae)) + ae--; + while ((c = next_byte(&ap, &ae, o)) > 0) + strbuf_addch(&sb, c); + + return memhash(sb.buf, sb.len); + } else { + return memhash(es->line, es->len); + } +} + +static struct moved_entry *prepare_entry(struct diff_options *o, + int line_no) +{ + struct moved_entry *ret = xmalloc(sizeof(*ret)); + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no]; + + ret->ent.hash = get_string_hash(l, o); + ret->es = l; + ret->next_line = NULL; + + return ret; +} + +static void add_lines_to_move_detection(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry *prev_line = NULL; + + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm; + struct moved_entry *key; + + switch (o->emitted_symbols->buf[n].s) { + case DIFF_SYMBOL_PLUS: + hm = add_lines; + break; + case DIFF_SYMBOL_MINUS: + hm = del_lines; + break; + default: + prev_line = NULL; + continue; + } + + key = prepare_entry(o, n); + if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s) + prev_line->next_line = key; + + hashmap_add(hm, key); + prev_line = key; + } +} + +static int shrink_potential_moved_blocks(struct moved_entry **pmb, + int pmb_nr) +{ + int lp, rp; + + /* Shrink the set of potential block to the remaining running */ + for (lp = 0, rp = pmb_nr - 1; lp <= rp;) { + while (lp < pmb_nr && pmb[lp]) + lp++; + /* lp points at the first NULL now */ + + while (rp > -1 && !pmb[rp]) + rp--; + /* rp points at the last non-NULL */ + + if (lp < pmb_nr && rp > -1 && lp < rp) { + pmb[lp] = pmb[rp]; + pmb[rp] = NULL; + rp--; + lp++; + } + } + + /* Remember the number of running sets */ + return rp + 1; +} + +/* + * If o->color_moved is COLOR_MOVED_PLAIN, this function does nothing. + * + * Otherwise, if the last block has fewer alphanumeric characters than + * COLOR_MOVED_MIN_ALNUM_COUNT, unset DIFF_SYMBOL_MOVED_LINE on all lines in + * that block. + * + * The last block consists of the (n - block_length)'th line up to but not + * including the nth line. + * + * NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c. + * Think of a way to unify them. + */ +static void adjust_last_block(struct diff_options *o, int n, int block_length) +{ + int i, alnum_count = 0; + if (o->color_moved == COLOR_MOVED_PLAIN) + return; + for (i = 1; i < block_length + 1; i++) { + const char *c = o->emitted_symbols->buf[n - i].line; + for (; *c; c++) { + if (!isalnum(*c)) + continue; + alnum_count++; + if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT) + return; + } + } + for (i = 1; i < block_length + 1; i++) + o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE; +} + +/* Find blocks of moved code, delegate actual coloring decision to helper */ +static void mark_color_as_moved(struct diff_options *o, + struct hashmap *add_lines, + struct hashmap *del_lines) +{ + struct moved_entry **pmb = NULL; /* potentially moved blocks */ + int pmb_nr = 0, pmb_alloc = 0; + int n, flipped_block = 1, block_length = 0; + + + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct hashmap *hm = NULL; + struct moved_entry *key; + struct moved_entry *match = NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + int i; + + switch (l->s) { + case DIFF_SYMBOL_PLUS: + hm = del_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + case DIFF_SYMBOL_MINUS: + hm = add_lines; + key = prepare_entry(o, n); + match = hashmap_get(hm, key, o); + free(key); + break; + default: + flipped_block = 1; + } + + if (!match) { + adjust_last_block(o, n, block_length); + pmb_nr = 0; + block_length = 0; + continue; + } + + l->flags |= DIFF_SYMBOL_MOVED_LINE; + + if (o->color_moved == COLOR_MOVED_PLAIN) + continue; + + /* Check any potential block runs, advance each or nullify */ + for (i = 0; i < pmb_nr; i++) { + struct moved_entry *p = pmb[i]; + struct moved_entry *pnext = (p && p->next_line) ? + p->next_line : NULL; + if (pnext && !hm->cmpfn(o, pnext, match, NULL)) { + pmb[i] = p->next_line; + } else { + pmb[i] = NULL; + } + } + + pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr); + + if (pmb_nr == 0) { + /* + * The current line is the start of a new block. + * Setup the set of potential blocks. + */ + for (; match; match = hashmap_get_next(hm, match)) { + ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc); + pmb[pmb_nr++] = match; + } + + flipped_block = (flipped_block + 1) % 2; + + adjust_last_block(o, n, block_length); + block_length = 0; + } + + block_length++; + + if (flipped_block) + l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT; + } + adjust_last_block(o, n, block_length); + + free(pmb); +} + +#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \ + (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT) +static void dim_moved_lines(struct diff_options *o) +{ + int n; + for (n = 0; n < o->emitted_symbols->nr; n++) { + struct emitted_diff_symbol *prev = (n != 0) ? + &o->emitted_symbols->buf[n - 1] : NULL; + struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n]; + struct emitted_diff_symbol *next = + (n < o->emitted_symbols->nr - 1) ? + &o->emitted_symbols->buf[n + 1] : NULL; + + /* Not a plus or minus line? */ + if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS) + continue; + + /* Not a moved line? */ + if (!(l->flags & DIFF_SYMBOL_MOVED_LINE)) + continue; + + /* + * If prev or next are not a plus or minus line, + * pretend they don't exist + */ + if (prev && prev->s != DIFF_SYMBOL_PLUS && + prev->s != DIFF_SYMBOL_MINUS) + prev = NULL; + if (next && next->s != DIFF_SYMBOL_PLUS && + next->s != DIFF_SYMBOL_MINUS) + next = NULL; + + /* Inside a block? */ + if ((prev && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) && + (next && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) == + (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) { + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + continue; + } + + /* Check if we are at an interesting bound: */ + if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) && + (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) && + (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) != + (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT)) + continue; + + /* + * The boundary to prev and next are not interesting, + * so this line is not interesting as a whole + */ + l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING; + } +} + +static void emit_line_ws_markup(struct diff_options *o, + const char *set, const char *reset, + const char *line, int len, char sign, + unsigned ws_rule, int blank_at_eof) { - const char *set = diff_get_color(ecbdata->color_diff, color); const char *ws = NULL; - if (ecbdata->opt->ws_error_highlight & ws_error_highlight) { - ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + if (o->ws_error_highlight & ws_rule) { + ws = diff_get_color_opt(o, DIFF_WHITESPACE); if (!*ws) ws = NULL; } if (!ws) - emit_line_0(ecbdata->opt, set, reset, sign, line, len); - else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len)) + emit_line_0(o, set, reset, sign, line, len); + else if (blank_at_eof) /* Blank line at EOF - paint '+' as well */ - emit_line_0(ecbdata->opt, ws, reset, sign, line, len); + emit_line_0(o, ws, reset, sign, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line_0(ecbdata->opt, set, reset, sign, "", 0); - ws_check_emit(line, len, ecbdata->ws_rule, - ecbdata->opt->file, set, reset, ws); + emit_line_0(o, set, reset, sign, "", 0); + ws_check_emit(line, len, ws_rule, + o->file, set, reset, ws); + } +} + +static void emit_diff_symbol_from_struct(struct diff_options *o, + struct emitted_diff_symbol *eds) +{ + static const char *nneof = " No newline at end of file\n"; + const char *context, *reset, *set, *meta, *fraginfo; + struct strbuf sb = STRBUF_INIT; + + enum diff_symbol s = eds->s; + const char *line = eds->line; + int len = eds->len; + unsigned flags = eds->flags; + + switch (s) { + case DIFF_SYMBOL_NO_LF_EOF: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + putc('\n', o->file); + emit_line_0(o, context, reset, '\\', + nneof, strlen(nneof)); + break; + case DIFF_SYMBOL_SUBMODULE_HEADER: + case DIFF_SYMBOL_SUBMODULE_ERROR: + case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH: + case DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES: + case DIFF_SYMBOL_SUMMARY: + case DIFF_SYMBOL_STATS_LINE: + case DIFF_SYMBOL_BINARY_DIFF_BODY: + case DIFF_SYMBOL_CONTEXT_FRAGINFO: + emit_line(o, "", "", line, len); + break; + case DIFF_SYMBOL_CONTEXT_INCOMPLETE: + case DIFF_SYMBOL_CONTEXT_MARKER: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_SEPARATOR: + fprintf(o->file, "%s%c", + diff_line_prefix(o), + o->line_termination); + break; + case DIFF_SYMBOL_CONTEXT: + set = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, ' ', + flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0); + break; + case DIFF_SYMBOL_PLUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '+', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, + flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF); + break; + case DIFF_SYMBOL_MINUS: + switch (flags & (DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) { + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_ALT: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT); + break; + case DIFF_SYMBOL_MOVED_LINE | + DIFF_SYMBOL_MOVED_LINE_UNINTERESTING: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_DIM); + break; + case DIFF_SYMBOL_MOVED_LINE: + set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED); + break; + default: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + } + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line_ws_markup(o, set, reset, line, len, '-', + flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0); + break; + case DIFF_SYMBOL_WORDS_PORCELAIN: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, context, reset, line, len); + fputs("~\n", o->file); + break; + case DIFF_SYMBOL_WORDS: + context = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + /* + * Skip the prefix character, if any. With + * diff_suppress_blank_empty, there may be + * none. + */ + if (line[0] != '\n') { + line++; + len--; + } + emit_line(o, context, reset, line, len); + break; + case DIFF_SYMBOL_FILEPAIR_PLUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_FILEPAIR_MINUS: + meta = diff_get_color_opt(o, DIFF_METAINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + fprintf(o->file, "%s%s--- %s%s%s\n", diff_line_prefix(o), meta, + line, reset, + strchr(line, ' ') ? "\t" : ""); + break; + case DIFF_SYMBOL_BINARY_FILES: + case DIFF_SYMBOL_HEADER: + fprintf(o->file, "%s", line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER: + fprintf(o->file, "%sGIT binary patch\n", diff_line_prefix(o)); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA: + fprintf(o->file, "%sdelta %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL: + fprintf(o->file, "%sliteral %s\n", diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_BINARY_DIFF_FOOTER: + fputs(diff_line_prefix(o), o->file); + fputc('\n', o->file); + break; + case DIFF_SYMBOL_REWRITE_DIFF: + fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, fraginfo, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_ADD: + set = diff_get_color_opt(o, DIFF_FILE_NEW); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_DEL: + set = diff_get_color_opt(o, DIFF_FILE_OLD); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; + case DIFF_SYMBOL_SUBMODULE_UNTRACKED: + fprintf(o->file, "%sSubmodule %s contains untracked content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_SUBMODULE_MODIFIED: + fprintf(o->file, "%sSubmodule %s contains modified content\n", + diff_line_prefix(o), line); + break; + case DIFF_SYMBOL_STATS_SUMMARY_NO_FILES: + emit_line(o, "", "", " 0 files changed\n", + strlen(" 0 files changed\n")); + break; + case DIFF_SYMBOL_STATS_SUMMARY_ABBREV: + emit_line(o, "", "", " ...\n", strlen(" ...\n")); + break; + case DIFF_SYMBOL_WORD_DIFF: + fprintf(o->file, "%.*s", len, line); + break; + case DIFF_SYMBOL_STAT_SEP: + fputs(o->stat_sep, o->file); + break; + default: + die("BUG: unknown diff symbol"); } + strbuf_release(&sb); +} + +static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s, + const char *line, int len, unsigned flags) +{ + struct emitted_diff_symbol e = {line, len, flags, s}; + + if (o->emitted_symbols) + append_emitted_diff_symbol(o, &e); + else + emit_diff_symbol_from_struct(o, &e); +} + +void diff_emit_submodule_del(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_DEL, line, strlen(line), 0); +} + +void diff_emit_submodule_add(struct diff_options *o, const char *line) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ADD, line, strlen(line), 0); +} + +void diff_emit_submodule_untracked(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_UNTRACKED, + path, strlen(path), 0); +} + +void diff_emit_submodule_modified(struct diff_options *o, const char *path) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_MODIFIED, + path, strlen(path), 0); +} + +void diff_emit_submodule_header(struct diff_options *o, const char *header) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_HEADER, + header, strlen(header), 0); +} + +void diff_emit_submodule_error(struct diff_options *o, const char *err) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ERROR, err, strlen(err), 0); +} + +void diff_emit_submodule_pipethrough(struct diff_options *o, + const char *line, int len) +{ + emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, line, len, 0); +} + +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line, len, ecbdata->ws_rule); } static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_FILE_NEW, WSEH_NEW, '+'); + unsigned flags = WSEH_NEW | ecbdata->ws_rule; + if (new_blank_line_at_eof(ecbdata, line, len)) + flags |= DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF; + + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags); } static void emit_del_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_FILE_OLD, WSEH_OLD, '-'); + unsigned flags = WSEH_OLD | ecbdata->ws_rule; + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags); } static void emit_context_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { - emit_line_checked(reset, ecbdata, line, len, - DIFF_CONTEXT, WSEH_CONTEXT, ' '); + unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule; + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags); } static void emit_hunk_header(struct emit_callback *ecbdata, @@ -639,7 +1361,8 @@ 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->opt, context, reset, line, len); + emit_diff_symbol(ecbdata->opt, + DIFF_SYMBOL_CONTEXT_MARKER, line, len, 0); return; } ep += 2; /* skip over @@ */ @@ -673,7 +1396,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata, } strbuf_add(&msgbuf, line + len, org_len - len); - emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len); + strbuf_complete_line(&msgbuf); + emit_diff_symbol(ecbdata->opt, + DIFF_SYMBOL_CONTEXT_FRAGINFO, msgbuf.buf, msgbuf.len, 0); strbuf_release(&msgbuf); } @@ -695,17 +1420,17 @@ static void remove_tempfile(void) } } -static void print_line_count(FILE *file, int count) +static void add_line_count(struct strbuf *out, int count) { switch (count) { case 0: - fprintf(file, "0,0"); + strbuf_addstr(out, "0,0"); break; case 1: - fprintf(file, "1"); + strbuf_addstr(out, "1"); break; default: - fprintf(file, "1,%d", count); + strbuf_addf(out, "1,%d", count); break; } } @@ -714,7 +1439,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb, int prefix, const char *data, int size) { const char *endp = NULL; - static const char *nneof = " No newline at end of file\n"; const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); while (0 < size) { @@ -732,13 +1456,8 @@ static void emit_rewrite_lines(struct emit_callback *ecb, size -= len; data += len; } - if (!endp) { - const char *context = diff_get_color(ecb->color_diff, - DIFF_CONTEXT); - putc('\n', ecb->opt->file); - emit_line_0(ecb->opt, context, reset, '\\', - nneof, strlen(nneof)); - } + if (!endp) + emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0); } static void emit_rewrite_diff(const char *name_a, @@ -750,16 +1469,12 @@ static void emit_rewrite_diff(const char *name_a, struct diff_options *o) { int lc_a, lc_b; - const char *name_a_tab, *name_b_tab; - const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO); - const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO); - const char *reset = diff_get_color(o->use_color, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; const char *a_prefix, *b_prefix; char *data_one, *data_two; size_t size_one, size_two; struct emit_callback ecbdata; - const char *line_prefix = diff_line_prefix(o); + struct strbuf out = STRBUF_INIT; if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { a_prefix = o->b_prefix; @@ -771,8 +1486,6 @@ static void emit_rewrite_diff(const char *name_a, name_a += (*name_a == '/'); name_b += (*name_b == '/'); - name_a_tab = strchr(name_a, ' ') ? "\t" : ""; - name_b_tab = strchr(name_b, ' ') ? "\t" : ""; strbuf_reset(&a_name); strbuf_reset(&b_name); @@ -799,18 +1512,23 @@ 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%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); + + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS, + a_name.buf, a_name.len, 0); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS, + b_name.buf, b_name.len, 0); + + strbuf_addstr(&out, "@@ -"); if (!o->irreversible_delete) - print_line_count(o->file, lc_a); + add_line_count(&out, lc_a); else - fprintf(o->file, "?,?"); - fprintf(o->file, " +"); - print_line_count(o->file, lc_b); - fprintf(o->file, " @@%s\n", reset); + strbuf_addstr(&out, "?,?"); + strbuf_addstr(&out, " +"); + add_line_count(&out, lc_b); + strbuf_addstr(&out, " @@\n"); + emit_diff_symbol(o, DIFF_SYMBOL_REWRITE_DIFF, out.buf, out.len, 0); + strbuf_release(&out); + if (lc_a && !o->irreversible_delete) emit_rewrite_lines(&ecbdata, '-', data_one, size_one); if (lc_b) @@ -870,37 +1588,49 @@ struct diff_words_data { struct diff_words_style *style; }; -static int fn_out_diff_words_write_helper(FILE *fp, +static int fn_out_diff_words_write_helper(struct diff_options *o, struct diff_words_style_elem *st_el, const char *newline, - size_t count, const char *buf, - const char *line_prefix) + size_t count, const char *buf) { int print = 0; + struct strbuf sb = STRBUF_INIT; while (count) { char *p = memchr(buf, '\n', count); if (print) - fputs(line_prefix, fp); + strbuf_addstr(&sb, diff_line_prefix(o)); + 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; + const char *reset = st_el->color && *st_el->color ? + GIT_COLOR_RESET : NULL; + if (st_el->color && *st_el->color) + strbuf_addstr(&sb, st_el->color); + strbuf_addstr(&sb, st_el->prefix); + strbuf_add(&sb, buf, p ? p - buf : count); + strbuf_addstr(&sb, st_el->suffix); + if (reset) + strbuf_addstr(&sb, reset); } if (!p) - return 0; - if (fputs(newline, fp) < 0) - return -1; + goto out; + + strbuf_addstr(&sb, newline); count -= p + 1 - buf; buf = p + 1; print = 1; + if (count) { + emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF, + sb.buf, sb.len, 0); + strbuf_reset(&sb); + } } + +out: + if (sb.len) + emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF, + sb.buf, sb.len, 0); + strbuf_release(&sb); return 0; } @@ -982,24 +1712,20 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) fputs(line_prefix, diff_words->opt->file); } if (diff_words->current_plus != plus_begin) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->ctx, style->newline, plus_begin - diff_words->current_plus, - diff_words->current_plus, line_prefix); - if (*(plus_begin - 1) == '\n') - fputs(line_prefix, diff_words->opt->file); + diff_words->current_plus); } if (minus_begin != minus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, - minus_end - minus_begin, minus_begin, - line_prefix); + minus_end - minus_begin, minus_begin); } if (plus_begin != plus_end) { - fn_out_diff_words_write_helper(diff_words->opt->file, + fn_out_diff_words_write_helper(diff_words->opt, &style->new, style->newline, - plus_end - plus_begin, plus_begin, - line_prefix); + plus_end - plus_begin, plus_begin); } diff_words->current_plus = plus_end; @@ -1093,11 +1819,12 @@ static void diff_words_show(struct diff_words_data *diff_words) /* special case: only removal */ if (!diff_words->plus.text.size) { - fputs(line_prefix, diff_words->opt->file); - fn_out_diff_words_write_helper(diff_words->opt->file, + emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF, + line_prefix, strlen(line_prefix), 0); + fn_out_diff_words_write_helper(diff_words->opt, &style->old, style->newline, diff_words->minus.text.size, - diff_words->minus.text.ptr, line_prefix); + diff_words->minus.text.ptr); diff_words->minus.text.size = 0; return; } @@ -1120,12 +1847,12 @@ static void diff_words_show(struct diff_words_data *diff_words) if (diff_words->current_plus != diff_words->plus.text.ptr + 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, + emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF, + line_prefix, strlen(line_prefix), 0); + fn_out_diff_words_write_helper(diff_words->opt, &style->ctx, style->newline, diff_words->plus.text.ptr + diff_words->plus.text.size - - diff_words->current_plus, diff_words->current_plus, - line_prefix); + - diff_words->current_plus, diff_words->current_plus); } diff_words->minus.text.size = diff_words->plus.text.size = 0; } @@ -1133,9 +1860,29 @@ static void diff_words_show(struct diff_words_data *diff_words) /* In "color-words" mode, show word-diff of words accumulated in the buffer */ static void diff_words_flush(struct emit_callback *ecbdata) { + struct diff_options *wo = ecbdata->diff_words->opt; + if (ecbdata->diff_words->minus.text.size || ecbdata->diff_words->plus.text.size) diff_words_show(ecbdata->diff_words); + + if (wo->emitted_symbols) { + struct diff_options *o = ecbdata->opt; + struct emitted_diff_symbols *wol = wo->emitted_symbols; + int i; + + /* + * NEEDSWORK: + * Instead of appending each, concat all words to a line? + */ + for (i = 0; i < wol->nr; i++) + append_emitted_diff_symbol(o, &wol->buf[i]); + + for (i = 0; i < wol->nr; i++) + free((void *)wol->buf[i].line); + + wol->nr = 0; + } } static void diff_filespec_load_driver(struct diff_filespec *one) @@ -1171,6 +1918,11 @@ static void init_diff_words_data(struct emit_callback *ecbdata, xcalloc(1, sizeof(struct diff_words_data)); ecbdata->diff_words->type = o->word_diff; ecbdata->diff_words->opt = o; + + if (orig_opts->emitted_symbols) + o->emitted_symbols = + xcalloc(1, sizeof(struct emitted_diff_symbols)); + if (!o->word_regex) o->word_regex = userdiff_word_regex(one); if (!o->word_regex) @@ -1205,6 +1957,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { diff_words_flush(ecbdata); + free (ecbdata->diff_words->opt->emitted_symbols); free (ecbdata->diff_words->opt); free (ecbdata->diff_words->minus.text.ptr); free (ecbdata->diff_words->minus.orig); @@ -1269,30 +2022,25 @@ static void find_lno(const char *line, struct emit_callback *ecbdata) static void fn_out_consume(void *priv, char *line, unsigned long len) { struct emit_callback *ecbdata = priv; - const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); - const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); struct diff_options *o = ecbdata->opt; - const char *line_prefix = diff_line_prefix(o); o->found_changes = 1; if (ecbdata->header) { - fprintf(o->file, "%s", ecbdata->header->buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + ecbdata->header->buf, ecbdata->header->len, 0); strbuf_reset(ecbdata->header); ecbdata->header = NULL; } if (ecbdata->label_path[0]) { - const char *name_a_tab, *name_b_tab; - - name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : ""; - name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : ""; - - fprintf(o->file, "%s%s--- %s%s%s\n", - line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab); - fprintf(o->file, "%s%s+++ %s%s%s\n", - line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS, + ecbdata->label_path[0], + strlen(ecbdata->label_path[0]), 0); + emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS, + ecbdata->label_path[1], + strlen(ecbdata->label_path[1]), 0); ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } @@ -1308,12 +2056,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = sane_truncate_line(ecbdata, line, len); find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); - if (line[len-1] != '\n') - putc('\n', o->file); return; } if (ecbdata->diff_words) { + enum diff_symbol s = + ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN ? + DIFF_SYMBOL_WORDS_PORCELAIN : DIFF_SYMBOL_WORDS; if (line[0] == '-') { diff_words_append(line, len, &ecbdata->diff_words->minus); @@ -1333,21 +2082,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } diff_words_flush(ecbdata); - if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) { - emit_line(o, context, reset, line, len); - fputs("~\n", o->file); - } else { - /* - * Skip the prefix character, if any. With - * diff_suppress_blank_empty, there may be - * none. - */ - if (line[0] != '\n') { - line++; - len--; - } - emit_line(o, context, reset, line, len); - } + emit_diff_symbol(o, s, line, len, 0); return; } @@ -1368,8 +2103,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) default: /* incomplete line at the end */ ecbdata->lno_in_preimage++; - emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT), - reset, line, len); + emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE, + line, len, 0); break; } } @@ -1514,20 +2249,14 @@ static int scale_linear(int it, int width, int max_change) return 1 + (it * (width - 1) / max_change); } -static void show_name(FILE *file, - const char *prefix, const char *name, int len) -{ - fprintf(file, " %s%-*s |", prefix, len, name); -} - -static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset) +static void show_graph(struct strbuf *out, char ch, int cnt, + const char *set, const char *reset) { if (cnt <= 0) return; - fprintf(file, "%s", set); - while (cnt--) - putc(ch, file); - fprintf(file, "%s", reset); + strbuf_addstr(out, set); + strbuf_addchars(out, ch, cnt); + strbuf_addstr(out, reset); } static void fill_print_name(struct diffstat_file *file) @@ -1551,14 +2280,16 @@ static void fill_print_name(struct diffstat_file *file) file->print_name = pname; } -int print_stat_summary(FILE *fp, int files, int insertions, int deletions) +static void print_stat_summary_inserts_deletes(struct diff_options *options, + int files, int insertions, int deletions) { struct strbuf sb = STRBUF_INIT; - int ret; if (!files) { assert(insertions == 0 && deletions == 0); - return fprintf(fp, "%s\n", " 0 files changed"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_NO_FILES, + NULL, 0, 0); + return; } strbuf_addf(&sb, @@ -1585,9 +2316,19 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions) deletions); } strbuf_addch(&sb, '\n'); - ret = fputs(sb.buf, fp); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES, + sb.buf, sb.len, 0); strbuf_release(&sb); - return ret; +} + +void print_stat_summary(FILE *fp, int files, + int insertions, int deletions) +{ + struct diff_options o; + memset(&o, 0, sizeof(o)); + o.file = fp; + + print_stat_summary_inserts_deletes(&o, files, insertions, deletions); } static void show_stats(struct diffstat_t *data, struct diff_options *options) @@ -1597,13 +2338,13 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) int total_files = data->nr, count; int width, name_width, graph_width, number_width = 0, bin_width = 0; const char *reset, *add_c, *del_c; - const char *line_prefix = ""; int extra_shown = 0; + const char *line_prefix = diff_line_prefix(options); + struct strbuf out = STRBUF_INIT; if (data->nr == 0) return; - line_prefix = diff_line_prefix(options); count = options->stat_count ? options->stat_count : data->nr; reset = diff_get_color_opt(options, DIFF_RESET); @@ -1757,26 +2498,32 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) } if (file->is_binary) { - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " %*s", number_width, "Bin"); + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*s", number_width, "Bin"); if (!added && !deleted) { - putc('\n', options->file); + strbuf_addch(&out, '\n'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } - fprintf(options->file, " %s%"PRIuMAX"%s", + strbuf_addf(&out, " %s%"PRIuMAX"%s", del_c, deleted, reset); - fprintf(options->file, " -> "); - fprintf(options->file, "%s%"PRIuMAX"%s", + strbuf_addstr(&out, " -> "); + strbuf_addf(&out, "%s%"PRIuMAX"%s", add_c, added, reset); - fprintf(options->file, " bytes"); - fprintf(options->file, "\n"); + strbuf_addstr(&out, " bytes\n"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } else if (file->is_unmerged) { - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " Unmerged\n"); + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addstr(&out, " Unmerged\n"); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); continue; } @@ -1799,14 +2546,16 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) add = total - del; } } - fprintf(options->file, "%s", line_prefix); - show_name(options->file, prefix, name, len); - fprintf(options->file, " %*"PRIuMAX"%s", + strbuf_addf(&out, " %s%-*s |", prefix, len, name); + strbuf_addf(&out, " %*"PRIuMAX"%s", number_width, added + deleted, added + deleted ? " " : ""); - show_graph(options->file, '+', add, add_c, reset); - show_graph(options->file, '-', del, del_c, reset); - fprintf(options->file, "\n"); + show_graph(&out, '+', add, add_c, reset); + show_graph(&out, '-', del, del_c, reset); + strbuf_addch(&out, '\n'); + emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE, + out.buf, out.len, 0); + strbuf_reset(&out); } for (i = 0; i < data->nr; i++) { @@ -1827,11 +2576,13 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (i < count) continue; if (!extra_shown) - fprintf(options->file, "%s ...\n", line_prefix); + emit_diff_symbol(options, + DIFF_SYMBOL_STATS_SUMMARY_ABBREV, + NULL, 0, 0); extra_shown = 1; } - fprintf(options->file, "%s", line_prefix); - print_stat_summary(options->file, total_files, adds, dels); + + print_stat_summary_inserts_deletes(options, total_files, adds, dels); } static void show_shortstats(struct diffstat_t *data, struct diff_options *options) @@ -1843,7 +2594,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option for (i = 0; i < data->nr; i++) { int added = data->files[i]->added; - int deleted= data->files[i]->deleted; + int deleted = data->files[i]->deleted; if (data->files[i]->is_unmerged || (!data->files[i]->is_interesting && (added + deleted == 0))) { @@ -1853,8 +2604,7 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option dels += deleted; } } - fprintf(options->file, "%s", diff_line_prefix(options)); - print_stat_summary(options->file, total_files, adds, dels); + print_stat_summary_inserts_deletes(options, total_files, adds, dels); } static void show_numstat(struct diffstat_t *data, struct diff_options *options) @@ -2218,8 +2968,8 @@ static unsigned char *deflate_it(char *data, return deflated; } -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, - const char *prefix) +static void emit_binary_diff_body(struct diff_options *o, + mmfile_t *one, mmfile_t *two) { void *cp; void *delta; @@ -2248,13 +2998,18 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, } if (delta && delta_size < deflate_size) { - fprintf(file, "%sdelta %lu\n", prefix, orig_size); + char *s = xstrfmt("%lu", orig_size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA, + s, strlen(s), 0); + free(s); free(deflated); data = delta; data_size = delta_size; - } - else { - fprintf(file, "%sliteral %lu\n", prefix, two->size); + } else { + char *s = xstrfmt("%lu", two->size); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL, + s, strlen(s), 0); + free(s); free(delta); data = deflated; data_size = deflate_size; @@ -2263,8 +3018,9 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, /* emit data encoded in base85 */ cp = data; while (data_size) { + int len; int bytes = (52 < data_size) ? 52 : data_size; - char line[70]; + char line[71]; data_size -= bytes; if (bytes <= 26) line[0] = bytes + 'A' - 1; @@ -2272,20 +3028,24 @@ 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); + + len = strlen(line); + line[len++] = '\n'; + line[len] = '\0'; + + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_BODY, + line, len, 0); } - fprintf(file, "%s\n", prefix); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_FOOTER, NULL, 0, 0); free(data); } -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, - const char *prefix) +static void emit_binary_diff(struct diff_options *o, + mmfile_t *one, mmfile_t *two) { - fprintf(file, "%sGIT binary patch\n", prefix); - emit_binary_diff_body(file, one, two, prefix); - emit_binary_diff_body(file, two, one, prefix); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER, NULL, 0, 0); + emit_binary_diff_body(o, one, two); + emit_binary_diff_body(o, two, one); } int diff_filespec_is_binary(struct diff_filespec *one) @@ -2362,24 +3122,16 @@ static void builtin_diff(const char *name_a, if (o->submodule_format == DIFF_SUBMODULE_LOG && (!one->mode || S_ISGITLINK(one->mode)) && (!two->mode || S_ISGITLINK(two->mode))) { - const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); - const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); - show_submodule_summary(o->file, one->path ? one->path : two->path, - line_prefix, + show_submodule_summary(o, one->path ? one->path : two->path, &one->oid, &two->oid, - two->dirty_submodule, - meta, del, add, reset); + two->dirty_submodule); return; } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF && (!one->mode || S_ISGITLINK(one->mode)) && (!two->mode || S_ISGITLINK(two->mode))) { - const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); - const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); - show_submodule_inline_diff(o->file, one->path ? one->path : two->path, - line_prefix, + show_submodule_inline_diff(o, one->path ? one->path : two->path, &one->oid, &two->oid, - two->dirty_submodule, - meta, del, add, reset, o); + two->dirty_submodule); return; } @@ -2428,7 +3180,8 @@ static void builtin_diff(const char *name_a, if (complete_rewrite && (textconv_one || !diff_filespec_is_binary(one)) && (textconv_two || !diff_filespec_is_binary(two))) { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); emit_rewrite_diff(name_a, name_b, one, two, textconv_one, textconv_two, o); @@ -2438,23 +3191,31 @@ static void builtin_diff(const char *name_a, } if (o->irreversible_delete && lbl[1][0] == '/') { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, + header.len, 0); strbuf_reset(&header); goto free_ab_and_return; } else if (!DIFF_OPT_TST(o, TEXT) && ( (!textconv_one && diff_filespec_is_binary(one)) || (!textconv_two && diff_filespec_is_binary(two)) )) { + struct strbuf sb = STRBUF_INIT; if (!one->data && !two->data && S_ISREG(one->mode) && S_ISREG(two->mode) && !DIFF_OPT_TST(o, BINARY)) { if (!oidcmp(&one->oid, &two->oid)) { if (must_show_header) - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, + 0); goto free_ab_and_return; } - fprintf(o->file, "%s", header.buf); - fprintf(o->file, "%sBinary files %s and %s differ\n", - line_prefix, lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); + strbuf_addf(&sb, "%sBinary files %s and %s differ\n", + diff_line_prefix(o), lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES, + sb.buf, sb.len, 0); + strbuf_release(&sb); goto free_ab_and_return; } if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) @@ -2463,16 +3224,21 @@ static void builtin_diff(const char *name_a, if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) { if (must_show_header) - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); goto free_ab_and_return; } - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0); strbuf_reset(&header); if (DIFF_OPT_TST(o, BINARY)) - emit_binary_diff(o->file, &mf1, &mf2, line_prefix); - else - fprintf(o->file, "%sBinary files %s and %s differ\n", - line_prefix, lbl[0], lbl[1]); + emit_binary_diff(o, &mf1, &mf2); + else { + strbuf_addf(&sb, "%sBinary files %s and %s differ\n", + diff_line_prefix(o), lbl[0], lbl[1]); + emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES, + sb.buf, sb.len, 0); + strbuf_release(&sb); + } o->found_changes = 1; } else { /* Crazy xdl interfaces.. */ @@ -2484,7 +3250,8 @@ static void builtin_diff(const char *name_a, const struct userdiff_funcname *pe; if (must_show_header) { - fprintf(o->file, "%s", header.buf); + emit_diff_symbol(o, DIFF_SYMBOL_HEADER, + header.buf, header.len, 0); strbuf_reset(&header); } @@ -3242,7 +4009,7 @@ static void diff_fill_oid_info(struct diff_filespec *one) } if (lstat(one->path, &st) < 0) die_errno("stat '%s'", one->path); - if (index_path(one->oid.hash, one->path, &st, 0)) + if (index_path(&one->oid, one->path, &st, 0)) die("cannot hash %s", one->path); } } @@ -3275,8 +4042,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) const char *other; const char *attr_path; - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); + name = one->path; + other = (strcmp(name, two->path) ? two->path : NULL); attr_path = name; if (o->prefix_length) strip_prefix(o->prefix_length, &name, &other); @@ -3399,6 +4166,8 @@ void diff_setup(struct diff_options *options) options->a_prefix = "a/"; options->b_prefix = "b/"; } + + options->color_moved = diff_color_moved_default; } void diff_setup_done(struct diff_options *options) @@ -3508,6 +4277,9 @@ void diff_setup_done(struct diff_options *options) if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1) die(_("--follow requires exactly one pathspec")); + + if (!options->use_color || external_diff()) + options->color_moved = 0; } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -3932,7 +4704,19 @@ int diff_opt_parse(struct diff_options *options, } else if (!strcmp(arg, "--no-color")) options->use_color = 0; - else if (!strcmp(arg, "--color-words")) { + else if (!strcmp(arg, "--color-moved")) { + if (diff_color_moved_default) + options->color_moved = diff_color_moved_default; + if (options->color_moved == COLOR_MOVED_NO) + options->color_moved = COLOR_MOVED_DEFAULT; + } else if (!strcmp(arg, "--no-color-moved")) + options->color_moved = COLOR_MOVED_NO; + else if (skip_prefix(arg, "--color-moved=", &arg)) { + int cm = parse_color_moved(arg); + if (cm < 0) + die("bad --color-moved argument: %s", arg); + options->color_moved = cm; + } else if (!strcmp(arg, "--color-words")) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } @@ -4462,67 +5246,76 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt) } } -static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_filespec *fs) +static void show_file_mode_name(struct diff_options *opt, const char *newdelete, struct diff_filespec *fs) { + struct strbuf sb = STRBUF_INIT; if (fs->mode) - fprintf(file, " %s mode %06o ", newdelete, fs->mode); + strbuf_addf(&sb, " %s mode %06o ", newdelete, fs->mode); else - fprintf(file, " %s ", newdelete); - write_name_quoted(fs->path, file, '\n'); -} + strbuf_addf(&sb, " %s ", newdelete); + quote_c_style(fs->path, &sb, NULL, 0); + strbuf_addch(&sb, '\n'); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); +} -static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name, - const char *line_prefix) +static void show_mode_change(struct diff_options *opt, struct diff_filepair *p, + int show_name) { if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) { - fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode, - p->two->mode, show_name ? ' ' : '\n'); + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, " mode change %06o => %06o", + p->one->mode, p->two->mode); if (show_name) { - write_name_quoted(p->two->path, file, '\n'); + strbuf_addch(&sb, ' '); + quote_c_style(p->two->path, &sb, NULL, 0); } + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + strbuf_release(&sb); } } -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p, - const char *line_prefix) +static void show_rename_copy(struct diff_options *opt, const char *renamecopy, + struct diff_filepair *p) { + struct strbuf sb = STRBUF_INIT; char *names = pprint_rename(p->one->path, p->two->path); - - fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p)); + strbuf_addf(&sb, " %s %s (%d%%)\n", + renamecopy, names, similarity_index(p)); free(names); - show_mode_change(file, p, 0, line_prefix); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); + show_mode_change(opt, p, 0); } static void diff_summary(struct diff_options *opt, struct diff_filepair *p) { - FILE *file = opt->file; - const char *line_prefix = diff_line_prefix(opt); - switch(p->status) { case DIFF_STATUS_DELETED: - fputs(line_prefix, file); - show_file_mode_name(file, "delete", p->one); + show_file_mode_name(opt, "delete", p->one); break; case DIFF_STATUS_ADDED: - fputs(line_prefix, file); - show_file_mode_name(file, "create", p->two); + show_file_mode_name(opt, "create", p->two); break; case DIFF_STATUS_COPIED: - fputs(line_prefix, file); - show_rename_copy(file, "copy", p, line_prefix); + show_rename_copy(opt, "copy", p); break; case DIFF_STATUS_RENAMED: - fputs(line_prefix, file); - show_rename_copy(file, "rename", p, line_prefix); + show_rename_copy(opt, "rename", p); break; default: if (p->score) { - fprintf(file, "%s rewrite ", line_prefix); - write_name_quoted(p->two->path, file, ' '); - fprintf(file, "(%d%%)\n", similarity_index(p)); + struct strbuf sb = STRBUF_INIT; + strbuf_addstr(&sb, " rewrite "); + quote_c_style(p->two->path, &sb, NULL, 0); + strbuf_addf(&sb, " (%d%%)\n", similarity_index(p)); + emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY, + sb.buf, sb.len, 0); } - show_mode_change(file, p, !p->score, line_prefix); + show_mode_change(opt, p, !p->score); break; } } @@ -4727,6 +5520,51 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) warning(_(rename_limit_advice), varname, needed); } +static void diff_flush_patch_all_file_pairs(struct diff_options *o) +{ + int i; + static struct emitted_diff_symbols esm = EMITTED_DIFF_SYMBOLS_INIT; + struct diff_queue_struct *q = &diff_queued_diff; + + if (WSEH_NEW & WS_RULE_MASK) + die("BUG: WS rules bit mask overlaps with diff symbol flags"); + + if (o->color_moved) + o->emitted_symbols = &esm; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, o); + } + + if (o->emitted_symbols) { + if (o->color_moved) { + struct hashmap add_lines, del_lines; + + hashmap_init(&del_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + hashmap_init(&add_lines, + (hashmap_cmp_fn)moved_entry_cmp, o, 0); + + add_lines_to_move_detection(o, &add_lines, &del_lines); + mark_color_as_moved(o, &add_lines, &del_lines); + if (o->color_moved == COLOR_MOVED_ZEBRA_DIM) + dim_moved_lines(o); + + hashmap_free(&add_lines, 0); + hashmap_free(&del_lines, 0); + } + + for (i = 0; i < esm.nr; i++) + emit_diff_symbol_from_struct(o, &esm.buf[i]); + + for (i = 0; i < esm.nr; i++) + free((void *)esm.buf[i].line); + } + esm.nr = 0; +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; @@ -4799,6 +5637,7 @@ void diff_flush(struct diff_options *options) fclose(options->file); options->file = xfopen("/dev/null", "w"); options->close_file = 1; + options->color_moved = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) @@ -4810,20 +5649,14 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_PATCH) { if (separator) { - fprintf(options->file, "%s%c", - diff_line_prefix(options), - options->line_termination); - if (options->stat_sep) { + emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0); + if (options->stat_sep) /* attach patch instead of inline */ - fputs(options->stat_sep, options->file); - } + emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP, + NULL, 0, 0); } - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - diff_flush_patch(p, options); - } + diff_flush_patch_all_file_pairs(options); } if (output_format & DIFF_FORMAT_CALLBACK) @@ -148,9 +148,9 @@ struct diff_options { int abbrev; int ita_invisible_in_index; /* white-space error highlighting */ -#define WSEH_NEW 1 -#define WSEH_CONTEXT 2 -#define WSEH_OLD 4 +#define WSEH_NEW (1<<12) +#define WSEH_CONTEXT (1<<13) +#define WSEH_OLD (1<<14) unsigned ws_error_highlight; const char *prefix; int prefix_length; @@ -186,8 +186,27 @@ struct diff_options { void *output_prefix_data; int diff_path_counter; + + struct emitted_diff_symbols *emitted_symbols; + enum { + COLOR_MOVED_NO = 0, + COLOR_MOVED_PLAIN = 1, + COLOR_MOVED_ZEBRA = 2, + COLOR_MOVED_ZEBRA_DIM = 3, + } color_moved; + #define COLOR_MOVED_DEFAULT COLOR_MOVED_ZEBRA + #define COLOR_MOVED_MIN_ALNUM_COUNT 20 }; +void diff_emit_submodule_del(struct diff_options *o, const char *line); +void diff_emit_submodule_add(struct diff_options *o, const char *line); +void diff_emit_submodule_untracked(struct diff_options *o, const char *path); +void diff_emit_submodule_modified(struct diff_options *o, const char *path); +void diff_emit_submodule_header(struct diff_options *o, const char *header); +void diff_emit_submodule_error(struct diff_options *o, const char *err); +void diff_emit_submodule_pipethrough(struct diff_options *o, + const char *line, int len); + enum color_diff { DIFF_RESET = 0, DIFF_CONTEXT = 1, @@ -197,7 +216,15 @@ enum color_diff { DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, DIFF_WHITESPACE = 7, - DIFF_FUNCINFO = 8 + DIFF_FUNCINFO = 8, + DIFF_FILE_OLD_MOVED = 9, + DIFF_FILE_OLD_MOVED_ALT = 10, + DIFF_FILE_OLD_MOVED_DIM = 11, + DIFF_FILE_OLD_MOVED_ALT_DIM = 12, + DIFF_FILE_NEW_MOVED = 13, + DIFF_FILE_NEW_MOVED_ALT = 14, + DIFF_FILE_NEW_MOVED_DIM = 15, + DIFF_FILE_NEW_MOVED_ALT_DIM = 16 }; const char *diff_get_color(int diff_use_color, enum color_diff ix); #define diff_get_color_opt(o, ix) \ @@ -396,8 +423,8 @@ extern int parse_rename_score(const char **cp_p); extern long parse_algorithm_value(const char *value); -extern int print_stat_summary(FILE *fp, int files, - int insertions, int deletions); +extern void print_stat_summary(FILE *fp, int files, + int insertions, int deletions); extern void setup_diff_pager(struct diff_options *); #endif /* DIFF_H */ diff --git a/diffcore-rename.c b/diffcore-rename.c index 786f389498..0d8c3d2ee4 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -532,9 +532,9 @@ void diffcore_rename(struct diff_options *options) } if (options->show_rename_progress) { - progress = start_progress_delay( + progress = start_delayed_progress( _("Performing inexact rename detection"), - rename_dst_nr * rename_src_nr, 50, 1); + rename_dst_nr * rename_src_nr); } mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_create), sizeof(*mx)); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 9208f42ed1..959f04b494 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -5967,6 +5967,9 @@ sub git_history_body { $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); if ($ftype eq 'blob') { + print " | " . + $cgi->a({-href => href(action=>"blob_plain", hash_base=>$commit, file_name=>$file_name)}, "raw"); + my $blob_current = $file_hash; my $blob_parent = git_get_hash_by_path($commit, $file_name); if (defined $blob_current && defined $blob_parent && @@ -92,7 +92,7 @@ static struct { * here, too */ }; -#if LIBCURL_VERSION_NUM >= 0x071600 +#ifdef CURLGSSAPI_DELEGATION_FLAG static const char *curl_deleg; static struct { const char *name; @@ -353,7 +353,7 @@ static int http_options(const char *var, const char *value, void *cb) } if (!strcmp("http.delegation", var)) { -#if LIBCURL_VERSION_NUM >= 0x071600 +#ifdef CURLGSSAPI_DELEGATION_FLAG return git_config_string(&curl_deleg, var, value); #else warning(_("Delegation control is not supported with cURL < 7.22.0")); @@ -678,6 +678,7 @@ void setup_curl_trace(CURL *handle) curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL); } +#ifdef CURLPROTO_HTTP static long get_curl_allowed_protocols(int from_user) { long allowed_protocols = 0; @@ -693,6 +694,7 @@ static long get_curl_allowed_protocols(int from_user) return allowed_protocols; } +#endif static CURL *get_curl_handle(void) { @@ -718,7 +720,7 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY); #endif -#if LIBCURL_VERSION_NUM >= 0x071600 +#ifdef CURLGSSAPI_DELEGATION_FLAG if (curl_deleg) { int i; for (i = 0; i < ARRAY_SIZE(curl_deleg_levels); i++) { @@ -791,7 +793,7 @@ static CURL *get_curl_handle(void) #elif LIBCURL_VERSION_NUM >= 0x071101 curl_easy_setopt(result, CURLOPT_POST301, 1); #endif -#if LIBCURL_VERSION_NUM >= 0x071304 +#ifdef CURLPROTO_HTTP curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, get_curl_allowed_protocols(0)); curl_easy_setopt(result, CURLOPT_PROTOCOLS, diff --git a/notes-merge.c b/notes-merge.c index c12b354f10..744c685576 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -709,7 +709,7 @@ int notes_merge_commit(struct notes_merge_options *o, /* write file as blob, and add to partial_tree */ if (stat(path.buf, &st)) die_errno("Failed to stat '%s'", path.buf); - if (index_path(blob_oid.hash, path.buf, &st, HASH_WRITE_OBJECT)) + if (index_path(&blob_oid, path.buf, &st, HASH_WRITE_OBJECT)) die("Failed to write blob object from '%s'", path.buf); if (add_note(partial_tree, &obj_oid, &blob_oid, NULL)) die("Failed to add resolved note '%s' to notes tree", @@ -425,7 +425,7 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, unsigned char type; struct leaf_node *l; - buf = fill_tree_descriptor(&desc, subtree->val_oid.hash); + buf = fill_tree_descriptor(&desc, &subtree->val_oid); if (!buf) die("Could not read %s for notes-index", oid_to_hex(&subtree->val_oid)); @@ -871,16 +871,6 @@ const char *format_subject(struct strbuf *sb, const char *msg, return msg; } -static void format_trailers(struct strbuf *sb, const char *msg) -{ - struct trailer_info info; - - trailer_info_get(&info, msg); - strbuf_add(sb, info.trailer_start, - info.trailer_end - info.trailer_start); - trailer_info_release(&info); -} - static void parse_commit_message(struct format_commit_context *c) { const char *msg = c->message + c->message_off; @@ -1074,6 +1064,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const struct commit *commit = c->commit; const char *msg = c->message; struct commit_list *p; + const char *arg; int ch; /* these are independent of the commit */ @@ -1292,9 +1283,18 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ return 1; } - if (starts_with(placeholder, "(trailers)")) { - format_trailers(sb, msg + c->subject_off); - return strlen("(trailers)"); + if (skip_prefix(placeholder, "(trailers", &arg)) { + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + while (*arg == ':') { + if (skip_prefix(arg, ":only", &arg)) + opts.only_trailers = 1; + else if (skip_prefix(arg, ":unfold", &arg)) + opts.unfold = 1; + } + if (*arg == ')') { + format_trailers_from_commit(sb, msg + c->subject_off, &opts); + return arg - placeholder + 1; + } } return 0; /* unknown placeholder */ diff --git a/progress.c b/progress.c index 73e36d4a42..289678d43d 100644 --- a/progress.c +++ b/progress.c @@ -34,7 +34,7 @@ struct progress { unsigned total; unsigned last_percent; unsigned delay; - unsigned delayed_percent_treshold; + unsigned delayed_percent_threshold; struct throughput *throughput; uint64_t start_ns; }; @@ -88,7 +88,7 @@ static int display(struct progress *progress, unsigned n, const char *done) return 0; if (progress->total) { unsigned percent = n * 100 / progress->total; - if (percent > progress->delayed_percent_treshold) { + if (percent > progress->delayed_percent_threshold) { /* inhibit this progress report entirely */ clear_progress_signal(); progress->delay = -1; @@ -205,8 +205,8 @@ int display_progress(struct progress *progress, unsigned n) return progress ? display(progress, n, NULL) : 0; } -struct progress *start_progress_delay(const char *title, unsigned total, - unsigned percent_treshold, unsigned delay) +static struct progress *start_progress_delay(const char *title, unsigned total, + unsigned percent_threshold, unsigned delay) { struct progress *progress = malloc(sizeof(*progress)); if (!progress) { @@ -219,7 +219,7 @@ struct progress *start_progress_delay(const char *title, unsigned total, progress->total = total; progress->last_value = -1; progress->last_percent = -1; - progress->delayed_percent_treshold = percent_treshold; + progress->delayed_percent_threshold = percent_threshold; progress->delay = delay; progress->throughput = NULL; progress->start_ns = getnanotime(); @@ -227,6 +227,11 @@ struct progress *start_progress_delay(const char *title, unsigned total, return progress; } +struct progress *start_delayed_progress(const char *title, unsigned total) +{ + return start_progress_delay(title, total, 0, 2); +} + struct progress *start_progress(const char *title, unsigned total) { return start_progress_delay(title, total, 0, 0); diff --git a/progress.h b/progress.h index 611e4c4d42..6392b63371 100644 --- a/progress.h +++ b/progress.h @@ -6,8 +6,7 @@ struct progress; void display_throughput(struct progress *progress, off_t total); int display_progress(struct progress *progress, unsigned n); struct progress *start_progress(const char *title, unsigned total); -struct progress *start_progress_delay(const char *title, unsigned total, - unsigned percent_treshold, unsigned delay); +struct progress *start_delayed_progress(const char *title, unsigned total); void stop_progress(struct progress **progress); void stop_progress_msg(struct progress **progress, const char *msg); diff --git a/read-cache.c b/read-cache.c index acfb028f48..40da87ea71 100644 --- a/read-cache.c +++ b/read-cache.c @@ -160,9 +160,9 @@ static int ce_compare_data(const struct cache_entry *ce, struct stat *st) int fd = git_open_cloexec(ce->name, O_RDONLY); if (fd >= 0) { - unsigned char sha1[20]; - if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0)) - match = hashcmp(sha1, ce->oid.hash); + struct object_id oid; + if (!index_fd(&oid, fd, st, OBJ_BLOB, ce->name, 0)) + match = oidcmp(&oid, &ce->oid); /* index_fd() closed the file descriptor already */ } return match; @@ -689,7 +689,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, return 0; } if (!intent_only) { - if (index_path(ce->oid.hash, path, st, HASH_WRITE_OBJECT)) { + if (index_path(&ce->oid, path, st, HASH_WRITE_OBJECT)) { free(ce); return error("unable to index file %s", path); } @@ -1499,6 +1499,7 @@ struct ondisk_cache_entry_extended { }; /* These are only used for v3 or lower */ +#define align_padding_size(size, len) ((size + (len) + 8) & ~7) - (size + len) #define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7) #define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len) #define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len) @@ -2032,7 +2033,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) } /* Copy miscellaneous fields but not the name */ -static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, +static void copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce) { short flags; @@ -2056,32 +2057,35 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, struct ondisk_cache_entry_extended *ondisk2; ondisk2 = (struct ondisk_cache_entry_extended *)ondisk; ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16); - return ondisk2->name; - } - else { - return ondisk->name; } } static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, - struct strbuf *previous_name) + struct strbuf *previous_name, struct ondisk_cache_entry *ondisk) { int size; - struct ondisk_cache_entry *ondisk; int saved_namelen = saved_namelen; /* compiler workaround */ - char *name; int result; + static unsigned char padding[8] = { 0x00 }; if (ce->ce_flags & CE_STRIP_NAME) { saved_namelen = ce_namelen(ce); ce->ce_namelen = 0; } + if (ce->ce_flags & CE_EXTENDED) + size = offsetof(struct ondisk_cache_entry_extended, name); + else + size = offsetof(struct ondisk_cache_entry, name); + if (!previous_name) { - size = ondisk_ce_size(ce); - ondisk = xcalloc(1, size); - name = copy_cache_entry_to_ondisk(ondisk, ce); - memcpy(name, ce->name, ce_namelen(ce)); + int len = ce_namelen(ce); + copy_cache_entry_to_ondisk(ondisk, ce); + result = ce_write(c, fd, ondisk, size); + if (!result) + result = ce_write(c, fd, ce->name, len); + if (!result) + result = ce_write(c, fd, padding, align_padding_size(size, len)); } else { int common, to_remove, prefix_size; unsigned char to_remove_vi[16]; @@ -2094,16 +2098,12 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, to_remove = previous_name->len - common; prefix_size = encode_varint(to_remove, to_remove_vi); - if (ce->ce_flags & CE_EXTENDED) - size = offsetof(struct ondisk_cache_entry_extended, name); - else - size = offsetof(struct ondisk_cache_entry, name); - size += prefix_size + (ce_namelen(ce) - common + 1); - - ondisk = xcalloc(1, size); - name = copy_cache_entry_to_ondisk(ondisk, ce); - memcpy(name, to_remove_vi, prefix_size); - memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common); + copy_cache_entry_to_ondisk(ondisk, ce); + result = ce_write(c, fd, ondisk, size); + if (!result) + result = ce_write(c, fd, to_remove_vi, prefix_size); + if (!result) + result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common + 1); strbuf_splice(previous_name, common, to_remove, ce->name + common, ce_namelen(ce) - common); @@ -2113,8 +2113,6 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce, ce->ce_flags &= ~CE_STRIP_NAME; } - result = ce_write(c, fd, ondisk, size); - free(ondisk); return result; } @@ -2192,10 +2190,11 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, int newfd = tempfile->fd; git_SHA_CTX c; struct cache_header hdr; - int i, err, removed, extended, hdr_version; + int i, err = 0, removed, extended, hdr_version; struct cache_entry **cache = istate->cache; int entries = istate->cache_nr; struct stat st; + struct ondisk_cache_entry_extended ondisk; struct strbuf previous_name_buf = STRBUF_INIT, *previous_name; int drop_cache_tree = 0; @@ -2232,6 +2231,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, return -1; previous_name = (hdr_version == 4) ? &previous_name_buf : NULL; + for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; if (ce->ce_flags & CE_REMOVE) @@ -2247,15 +2247,21 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (allow) warning(msg, ce->name); else - return error(msg, ce->name); + err = error(msg, ce->name); drop_cache_tree = 1; } - if (ce_write_entry(&c, newfd, ce, previous_name) < 0) - return -1; + if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0) + err = -1; + + if (err) + break; } strbuf_release(&previous_name_buf); + if (err) + return err; + /* Write extension data here */ if (!strip_extensions && istate->split_index) { struct strbuf sb = STRBUF_INIT; @@ -579,6 +579,21 @@ enum ref_type ref_type(const char *refname) return REF_TYPE_NORMAL; } +long get_files_ref_lock_timeout_ms(void) +{ + static int configured = 0; + + /* The default timeout is 100 ms: */ + static int timeout_ms = 100; + + if (!configured) { + git_config_get_int("core.filesreflocktimeout", &timeout_ms); + configured = 1; + } + + return timeout_ms; +} + static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, const unsigned char *old_sha1, struct strbuf *err) { @@ -591,7 +606,9 @@ static int write_pseudoref(const char *pseudoref, const unsigned char *sha1, strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); filename = git_path("%s", pseudoref); - fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update_timeout(&lock, filename, + LOCK_DIE_ON_ERROR, + get_files_ref_lock_timeout_ms()); if (fd < 0) { strbuf_addf(err, "could not open '%s' for writing: %s", filename, strerror(errno)); @@ -634,8 +651,9 @@ static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1 int fd; unsigned char actual_old_sha1[20]; - fd = hold_lock_file_for_update(&lock, filename, - LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update_timeout( + &lock, filename, LOCK_DIE_ON_ERROR, + get_files_ref_lock_timeout_ms()); if (fd < 0) die_errno(_("Could not open '%s' for writing"), filename); if (read_ref(pseudoref, actual_old_sha1)) diff --git a/refs/files-backend.c b/refs/files-backend.c index 5cca55510b..fccbc24ac4 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -537,7 +537,9 @@ retry: if (!lock->lk) lock->lk = xcalloc(1, sizeof(struct lock_file)); - if (hold_lock_file_for_update(lock->lk, ref_file.buf, LOCK_NO_DEREF) < 0) { + if (hold_lock_file_for_update_timeout( + lock->lk, ref_file.buf, LOCK_NO_DEREF, + get_files_ref_lock_timeout_ms()) < 0) { if (errno == ENOENT && --attempts_remaining > 0) { /* * Maybe somebody just deleted one of the @@ -865,7 +867,9 @@ static int create_reflock(const char *path, void *cb) { struct lock_file *lk = cb; - return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0; + return hold_lock_file_for_update_timeout( + lk, path, LOCK_NO_DEREF, + get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0; } /* diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 4789106fc0..b02dc5a7e3 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -62,6 +62,12 @@ #define REF_DELETED_LOOSE 0x200 /* + * Return the length of time to retry acquiring a loose reference lock + * before giving up, in milliseconds: + */ +long get_files_ref_lock_timeout_ms(void); + +/* * Return true iff refname is minimally safe. "Safe" here means that * deleting a loose reference by this name will not do any damage, for * example by causing a file that is not a reference to be deleted. @@ -1133,14 +1133,14 @@ int rerere_forget(struct pathspec *pathspec) * Garbage collection support */ -static time_t rerere_created_at(struct rerere_id *id) +static timestamp_t rerere_created_at(struct rerere_id *id) { struct stat st; return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } -static time_t rerere_last_used_at(struct rerere_id *id) +static timestamp_t rerere_last_used_at(struct rerere_id *id) { struct stat st; @@ -1157,11 +1157,11 @@ static void unlink_rr_item(struct rerere_id *id) id->collection->status[id->variant] = 0; } -static void prune_one(struct rerere_id *id, time_t now, - int cutoff_resolve, int cutoff_noresolve) +static void prune_one(struct rerere_id *id, + timestamp_t cutoff_resolve, timestamp_t cutoff_noresolve) { - time_t then; - int cutoff; + timestamp_t then; + timestamp_t cutoff; then = rerere_last_used_at(id); if (then) @@ -1172,7 +1172,7 @@ static void prune_one(struct rerere_id *id, time_t now, return; cutoff = cutoff_noresolve; } - if (then < now - cutoff * 86400) + if (then < cutoff) unlink_rr_item(id); } @@ -1182,15 +1182,15 @@ void rerere_gc(struct string_list *rr) DIR *dir; struct dirent *e; int i; - time_t now = time(NULL); - int cutoff_noresolve = 15; - int cutoff_resolve = 60; + timestamp_t now = time(NULL); + timestamp_t cutoff_noresolve = now - 15 * 86400; + timestamp_t cutoff_resolve = now - 60 * 86400; if (setup_rerere(rr, 0) < 0) return; - git_config_get_int("gc.rerereresolved", &cutoff_resolve); - git_config_get_int("gc.rerereunresolved", &cutoff_noresolve); + git_config_get_expiry_in_days("gc.rerereresolved", &cutoff_resolve, now); + git_config_get_expiry_in_days("gc.rerereunresolved", &cutoff_noresolve, now); git_config(git_default_config, NULL); dir = opendir(git_path("rr-cache")); if (!dir) @@ -1211,7 +1211,7 @@ void rerere_gc(struct string_list *rr) for (id.variant = 0, id.collection = rr_dir; id.variant < id.collection->status_nr; id.variant++) { - prune_one(&id, now, cutoff_resolve, cutoff_noresolve); + prune_one(&id, cutoff_resolve, cutoff_noresolve); if (id.collection->status[id.variant]) now_empty = 0; } diff --git a/sha1_file.c b/sha1_file.c index bb0831b4c8..f56bb5cae7 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1581,7 +1581,7 @@ int write_sha1_file(const void *buf, unsigned long len, const char *type, unsign } int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, - unsigned char *sha1, unsigned flags) + struct object_id *oid, unsigned flags) { char *header; int hdrlen, status = 0; @@ -1589,13 +1589,13 @@ int hash_sha1_file_literally(const void *buf, unsigned long len, const char *typ /* type string, SP, %lu of the length plus NUL must fit this */ hdrlen = strlen(type) + 32; header = xmalloc(hdrlen); - write_sha1_file_prepare(buf, len, type, sha1, header, &hdrlen); + write_sha1_file_prepare(buf, len, type, oid->hash, header, &hdrlen); if (!(flags & HASH_WRITE_OBJECT)) goto cleanup; - if (freshen_packed_object(sha1) || freshen_loose_object(sha1)) + if (freshen_packed_object(oid->hash) || freshen_loose_object(oid->hash)) goto cleanup; - status = write_loose_object(sha1, header, hdrlen, buf, len, 0); + status = write_loose_object(oid->hash, header, hdrlen, buf, len, 0); cleanup: free(header); @@ -1785,14 +1785,14 @@ static int index_core(unsigned char *sha1, int fd, size_t size, * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -static int index_stream(unsigned char *sha1, int fd, size_t size, +static int index_stream(struct object_id *oid, int fd, size_t size, enum object_type type, const char *path, unsigned flags) { - return index_bulk_checkin(sha1, fd, size, type, path, flags); + return index_bulk_checkin(oid->hash, fd, size, type, path, flags); } -int index_fd(unsigned char *sha1, int fd, struct stat *st, +int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags) { int ret; @@ -1802,21 +1802,21 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, * die() for large files. */ if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(path)) - ret = index_stream_convert_blob(sha1, fd, path, flags); + ret = index_stream_convert_blob(oid->hash, fd, path, flags); else if (!S_ISREG(st->st_mode)) - ret = index_pipe(sha1, fd, type, path, flags); + ret = index_pipe(oid->hash, fd, type, path, flags); else if (st->st_size <= big_file_threshold || type != OBJ_BLOB || (path && would_convert_to_git(&the_index, path))) - ret = index_core(sha1, fd, xsize_t(st->st_size), type, path, + ret = index_core(oid->hash, fd, xsize_t(st->st_size), type, path, flags); else - ret = index_stream(sha1, fd, xsize_t(st->st_size), type, path, + ret = index_stream(oid, fd, xsize_t(st->st_size), type, path, flags); close(fd); return ret; } -int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags) +int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags) { int fd; struct strbuf sb = STRBUF_INIT; @@ -1826,7 +1826,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned fd = open(path, O_RDONLY); if (fd < 0) return error_errno("open(\"%s\")", path); - if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0) + if (index_fd(oid, fd, st, OBJ_BLOB, path, flags) < 0) return error("%s: failed to insert into database", path); break; @@ -1834,14 +1834,14 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned if (strbuf_readlink(&sb, path, st->st_size)) return error_errno("readlink(\"%s\")", path); if (!(flags & HASH_WRITE_OBJECT)) - hash_sha1_file(sb.buf, sb.len, blob_type, sha1); - else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1)) + hash_sha1_file(sb.buf, sb.len, blob_type, oid->hash); + else if (write_sha1_file(sb.buf, sb.len, blob_type, oid->hash)) return error("%s: failed to insert into database", path); strbuf_release(&sb); break; case S_IFDIR: - return resolve_gitlink_ref(path, "HEAD", sha1); + return resolve_gitlink_ref(path, "HEAD", oid->hash); default: return error("%s: unsupported file type", path); } diff --git a/sub-process.c b/sub-process.c index 6edb97c1c6..6ccfaaba99 100644 --- a/sub-process.c +++ b/sub-process.c @@ -184,8 +184,8 @@ static int handshake_capabilities(struct child_process *process, if (supported_capabilities) *supported_capabilities |= capabilities[i].flag; } else { - warning("external filter requested unsupported filter capability '%s'", - p); + warning("subprocess '%s' requested unsupported capability '%s'", + process->argv[0], p); } } diff --git a/submodule-config.c b/submodule-config.c index 0c839019e1..2aa8a1747f 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -18,6 +18,7 @@ struct submodule_cache { struct hashmap for_path; struct hashmap for_name; unsigned initialized:1; + unsigned gitmodules_read:1; }; /* @@ -99,6 +100,7 @@ static void submodule_cache_clear(struct submodule_cache *cache) hashmap_free(&cache->for_path, 1); hashmap_free(&cache->for_name, 1); cache->initialized = 0; + cache->gitmodules_read = 0; } void submodule_cache_free(struct submodule_cache *cache) @@ -455,9 +457,9 @@ static int parse_config(const char *var, const char *value, void *data) return ret; } -int gitmodule_oid_from_commit(const struct object_id *treeish_name, - struct object_id *gitmodules_oid, - struct strbuf *rev) +static int gitmodule_oid_from_commit(const struct object_id *treeish_name, + struct object_id *gitmodules_oid, + struct strbuf *rev) { int ret = 0; @@ -558,13 +560,11 @@ static void submodule_cache_check_init(struct repository *repo) submodule_cache_init(repo->submodule_cache); } -int submodule_config_option(struct repository *repo, - const char *var, const char *value) +static int gitmodules_cb(const char *var, const char *value, void *data) { + struct repository *repo = data; struct parse_config_parameter parameter; - submodule_cache_check_init(repo); - parameter.cache = repo->submodule_cache; parameter.treeish_name = NULL; parameter.gitmodules_sha1 = null_sha1; @@ -573,22 +573,63 @@ int submodule_config_option(struct repository *repo, return parse_config(var, value, ¶meter); } -int parse_submodule_config_option(const char *var, const char *value) +void repo_read_gitmodules(struct repository *repo) { - return submodule_config_option(the_repository, var, value); + submodule_cache_check_init(repo); + + if (repo->worktree) { + char *gitmodules; + + if (repo_read_index(repo) < 0) + return; + + gitmodules = repo_worktree_path(repo, GITMODULES_FILE); + + if (!is_gitmodules_unmerged(repo->index)) + git_config_from_file(gitmodules_cb, gitmodules, repo); + + free(gitmodules); + } + + repo->submodule_cache->gitmodules_read = 1; +} + +void gitmodules_config_oid(const struct object_id *commit_oid) +{ + struct strbuf rev = STRBUF_INIT; + struct object_id oid; + + submodule_cache_check_init(the_repository); + + if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) { + git_config_from_blob_oid(gitmodules_cb, rev.buf, + &oid, the_repository); + } + strbuf_release(&rev); + + the_repository->submodule_cache->gitmodules_read = 1; +} + +static void gitmodules_read_check(struct repository *repo) +{ + submodule_cache_check_init(repo); + + /* read the repo's .gitmodules file if it hasn't been already */ + if (!repo->submodule_cache->gitmodules_read) + repo_read_gitmodules(repo); } const struct submodule *submodule_from_name(const struct object_id *treeish_name, const char *name) { - submodule_cache_check_init(the_repository); + gitmodules_read_check(the_repository); return config_from(the_repository->submodule_cache, treeish_name, name, lookup_name); } const struct submodule *submodule_from_path(const struct object_id *treeish_name, const char *path) { - submodule_cache_check_init(the_repository); + gitmodules_read_check(the_repository); return config_from(the_repository->submodule_cache, treeish_name, path, lookup_path); } @@ -596,7 +637,7 @@ const struct submodule *submodule_from_cache(struct repository *repo, const struct object_id *treeish_name, const char *key) { - submodule_cache_check_init(repo); + gitmodules_read_check(repo); return config_from(repo->submodule_cache, treeish_name, key, lookup_path); } diff --git a/submodule-config.h b/submodule-config.h index cccd34b929..e3845831f6 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -34,9 +34,8 @@ extern int option_fetch_parse_recurse_submodules(const struct option *opt, const char *arg, int unset); extern int parse_update_recurse_submodules_arg(const char *opt, const char *arg); extern int parse_push_recurse_submodules_arg(const char *opt, const char *arg); -extern int parse_submodule_config_option(const char *var, const char *value); -extern int submodule_config_option(struct repository *repo, - const char *var, const char *value); +extern void repo_read_gitmodules(struct repository *repo); +extern void gitmodules_config_oid(const struct object_id *commit_oid); extern const struct submodule *submodule_from_name( const struct object_id *commit_or_tree, const char *name); extern const struct submodule *submodule_from_path( @@ -44,9 +43,6 @@ extern const struct submodule *submodule_from_path( extern const struct submodule *submodule_from_cache(struct repository *repo, const struct object_id *treeish_name, const char *key); -extern int gitmodule_oid_from_commit(const struct object_id *commit_oid, - struct object_id *gitmodules_oid, - struct strbuf *rev); extern void submodule_free(void); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index e072036e79..3cea8221e0 100644 --- a/submodule.c +++ b/submodule.c @@ -165,31 +165,18 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, { const struct submodule *submodule = submodule_from_path(&null_oid, path); if (submodule) { - if (submodule->ignore) - handle_ignore_submodules_arg(diffopt, submodule->ignore); - else if (is_gitmodules_unmerged(&the_index)) - DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); - } -} + const char *ignore; + char *key; -/* For loading from the .gitmodules file. */ -static int git_modules_config(const char *var, const char *value, void *cb) -{ - if (starts_with(var, "submodule.")) - return parse_submodule_config_option(var, value); - return 0; -} + key = xstrfmt("submodule.%s.ignore", submodule->name); + if (repo_config_get_string_const(the_repository, key, &ignore)) + ignore = submodule->ignore; + free(key); -/* Loads all submodule settings from the config. */ -int submodule_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "submodule.recurse")) { - int v = git_config_bool(var, value) ? - RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; - config_update_recurse_submodules = v; - return 0; - } else { - return git_modules_config(var, value, cb); + if (ignore) + handle_ignore_submodules_arg(diffopt, ignore); + else if (is_gitmodules_unmerged(&the_index)) + DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); } } @@ -221,55 +208,6 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt, return 0; } -void load_submodule_cache(void) -{ - if (config_update_recurse_submodules == RECURSE_SUBMODULES_OFF) - return; - - gitmodules_config(); - git_config(submodule_config, NULL); -} - -static int gitmodules_cb(const char *var, const char *value, void *data) -{ - struct repository *repo = data; - return submodule_config_option(repo, var, value); -} - -void repo_read_gitmodules(struct repository *repo) -{ - if (repo->worktree) { - char *gitmodules; - - if (repo_read_index(repo) < 0) - return; - - gitmodules = repo_worktree_path(repo, GITMODULES_FILE); - - if (!is_gitmodules_unmerged(repo->index)) - git_config_from_file(gitmodules_cb, gitmodules, repo); - - free(gitmodules); - } -} - -void gitmodules_config(void) -{ - repo_read_gitmodules(the_repository); -} - -void gitmodules_config_oid(const struct object_id *commit_oid) -{ - struct strbuf rev = STRBUF_INIT; - struct object_id oid; - - if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) { - git_config_from_blob_oid(submodule_config, rev.buf, - &oid, NULL); - } - strbuf_release(&rev); -} - /* * Determine if a submodule has been initialized at a given 'path' */ @@ -398,24 +336,38 @@ void die_path_inside_submodule(const struct index_state *istate, } } -int parse_submodule_update_strategy(const char *value, - struct submodule_update_strategy *dst) +enum submodule_update_type parse_submodule_update_type(const char *value) { - free((void*)dst->command); - dst->command = NULL; if (!strcmp(value, "none")) - dst->type = SM_UPDATE_NONE; + return SM_UPDATE_NONE; else if (!strcmp(value, "checkout")) - dst->type = SM_UPDATE_CHECKOUT; + return SM_UPDATE_CHECKOUT; else if (!strcmp(value, "rebase")) - dst->type = SM_UPDATE_REBASE; + return SM_UPDATE_REBASE; else if (!strcmp(value, "merge")) - dst->type = SM_UPDATE_MERGE; - else if (skip_prefix(value, "!", &value)) { - dst->type = SM_UPDATE_COMMAND; - dst->command = xstrdup(value); - } else + return SM_UPDATE_MERGE; + else if (*value == '!') + return SM_UPDATE_COMMAND; + else + return SM_UPDATE_UNSPECIFIED; +} + +int parse_submodule_update_strategy(const char *value, + struct submodule_update_strategy *dst) +{ + enum submodule_update_type type; + + free((void*)dst->command); + dst->command = NULL; + + type = parse_submodule_update_type(value); + if (type == SM_UPDATE_UNSPECIFIED) return -1; + + dst->type = type; + if (type == SM_UPDATE_COMMAND) + dst->command = xstrdup(value + 1); + return 0; } @@ -478,9 +430,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, return prepare_revision_walk(rev); } -static void print_submodule_summary(struct rev_info *rev, FILE *f, - const char *line_prefix, - const char *del, const char *add, const char *reset) +static void print_submodule_summary(struct rev_info *rev, struct diff_options *o) { static const char format[] = " %m %s"; struct strbuf sb = STRBUF_INIT; @@ -491,18 +441,12 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, ctx.date_mode = rev->date_mode; ctx.output_encoding = get_log_output_encoding(); strbuf_setlen(&sb, 0); - strbuf_addstr(&sb, line_prefix); - if (commit->object.flags & SYMMETRIC_LEFT) { - if (del) - strbuf_addstr(&sb, del); - } - else if (add) - strbuf_addstr(&sb, add); format_commit_message(commit, format, &sb, &ctx); - if (reset) - strbuf_addstr(&sb, reset); strbuf_addch(&sb, '\n'); - fprintf(f, "%s", sb.buf); + if (commit->object.flags & SYMMETRIC_LEFT) + diff_emit_submodule_del(o, sb.buf); + else + diff_emit_submodule_add(o, sb.buf); } strbuf_release(&sb); } @@ -529,11 +473,9 @@ void prepare_submodule_repo_env(struct argv_array *out) * attempt to lookup both the left and right commits and put them into the * left and right pointers. */ -static void show_submodule_header(FILE *f, const char *path, - const char *line_prefix, +static void show_submodule_header(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *reset, + unsigned dirty_submodule, struct commit **left, struct commit **right, struct commit_list **merge_bases) { @@ -542,11 +484,10 @@ static void show_submodule_header(FILE *f, const char *path, int fast_forward = 0, fast_backward = 0; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - fprintf(f, "%sSubmodule %s contains untracked content\n", - line_prefix, path); + diff_emit_submodule_untracked(o, path); + if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - fprintf(f, "%sSubmodule %s contains modified content\n", - line_prefix, path); + diff_emit_submodule_modified(o, path); if (is_null_oid(one)) message = "(new submodule)"; @@ -588,31 +529,29 @@ static void show_submodule_header(FILE *f, const char *path, } output_header: - strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path); + strbuf_addf(&sb, "Submodule %s ", path); strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV); strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "..."); strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV); if (message) - strbuf_addf(&sb, " %s%s\n", message, reset); + strbuf_addf(&sb, " %s\n", message); else - strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); - fwrite(sb.buf, sb.len, 1, f); + strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); + diff_emit_submodule_header(o, sb.buf); strbuf_release(&sb); } -void show_submodule_summary(FILE *f, const char *path, - const char *line_prefix, +void show_submodule_summary(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset) + unsigned dirty_submodule) { struct rev_info rev; struct commit *left = NULL, *right = NULL; struct commit_list *merge_bases = NULL; - show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, - meta, reset, &left, &right, &merge_bases); + show_submodule_header(o, path, one, two, dirty_submodule, + &left, &right, &merge_bases); /* * If we don't have both a left and a right pointer, there is no @@ -624,11 +563,11 @@ void show_submodule_summary(FILE *f, const char *path, /* Treat revision walker failure the same as missing commits */ if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) { - fprintf(f, "%s(revision walker failed)\n", line_prefix); + diff_emit_submodule_error(o, "(revision walker failed)\n"); goto out; } - print_submodule_summary(&rev, f, line_prefix, del, add, reset); + print_submodule_summary(&rev, o); out: if (merge_bases) @@ -637,21 +576,18 @@ out: clear_commit_marks(right, ~0); } -void show_submodule_inline_diff(FILE *f, const char *path, - const char *line_prefix, +void show_submodule_inline_diff(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset, - const struct diff_options *o) + unsigned dirty_submodule) { const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid; struct commit *left = NULL, *right = NULL; struct commit_list *merge_bases = NULL; - struct strbuf submodule_dir = STRBUF_INIT; struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf sb = STRBUF_INIT; - show_submodule_header(f, path, line_prefix, one, two, dirty_submodule, - meta, reset, &left, &right, &merge_bases); + show_submodule_header(o, path, one, two, dirty_submodule, + &left, &right, &merge_bases); /* We need a valid left and right commit to display a difference */ if (!(left || is_null_oid(one)) || @@ -663,16 +599,16 @@ void show_submodule_inline_diff(FILE *f, const char *path, if (right) new = two; - fflush(f); cp.git_cmd = 1; cp.dir = path; - cp.out = dup(fileno(f)); + cp.out = -1; cp.no_stdin = 1; /* TODO: other options may need to be passed here. */ argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL); + argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ? + "always" : "never"); - argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix); if (DIFF_OPT_TST(o, REVERSE_DIFF)) { argv_array_pushf(&cp.args, "--src-prefix=%s%s/", o->b_prefix, path); @@ -695,11 +631,17 @@ void show_submodule_inline_diff(FILE *f, const char *path, argv_array_push(&cp.args, oid_to_hex(new)); prepare_submodule_repo_env(&cp.env_array); - if (run_command(&cp)) - fprintf(f, "(diff failed)\n"); + if (start_command(&cp)) + diff_emit_submodule_error(o, "(diff failed)\n"); + + while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF) + diff_emit_submodule_pipethrough(o, sb.buf, sb.len); + + if (finish_command(&cp)) + diff_emit_submodule_error(o, "(diff failed)\n"); done: - strbuf_release(&submodule_dir); + strbuf_release(&sb); if (merge_bases) free_commit_list(merge_bases); if (left) @@ -1140,7 +1082,6 @@ int submodule_touches_in_range(struct object_id *excl_oid, struct argv_array args = ARGV_ARRAY_INIT; int ret; - gitmodules_config(); /* No need to check if there are no submodules configured */ if (!submodule_from_path(NULL, NULL)) return 0; @@ -1189,19 +1130,27 @@ static int get_next_submodule(struct child_process *cp, continue; submodule = submodule_from_path(&null_oid, ce->name); - if (!submodule) - submodule = submodule_from_name(&null_oid, ce->name); default_argv = "yes"; if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) { - if (submodule && - submodule->fetch_recurse != - RECURSE_SUBMODULES_NONE) { - if (submodule->fetch_recurse == - RECURSE_SUBMODULES_OFF) + int fetch_recurse = RECURSE_SUBMODULES_NONE; + + if (submodule) { + char *key; + const char *value; + + fetch_recurse = submodule->fetch_recurse; + key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name); + if (!repo_config_get_string_const(the_repository, key, &value)) { + fetch_recurse = parse_fetch_recurse_submodules_arg(key, value); + } + free(key); + } + + if (fetch_recurse != RECURSE_SUBMODULES_NONE) { + if (fetch_recurse == RECURSE_SUBMODULES_OFF) continue; - if (submodule->fetch_recurse == - RECURSE_SUBMODULES_ON_DEMAND) { + if (fetch_recurse == RECURSE_SUBMODULES_ON_DEMAND) { if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) continue; default_argv = "on-demand"; @@ -2039,7 +1988,6 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule) strbuf_addstr(buf, git_dir); } if (!is_git_directory(buf->buf)) { - gitmodules_config(); sub = submodule_from_path(&null_oid, submodule); if (!sub) { ret = -1; diff --git a/submodule.h b/submodule.h index e402b004ff..6b52133c88 100644 --- a/submodule.h +++ b/submodule.h @@ -40,16 +40,11 @@ extern int remove_path_from_gitmodules(const char *path); extern void stage_updated_gitmodules(void); extern void set_diffopt_flags_from_submodule_config(struct diff_options *, const char *path); -extern int submodule_config(const char *var, const char *value, void *cb); extern int git_default_submodule_config(const char *var, const char *value, void *cb); struct option; int option_parse_recurse_submodules_worktree_updater(const struct option *opt, const char *arg, int unset); -void load_submodule_cache(void); -extern void gitmodules_config(void); -extern void repo_read_gitmodules(struct repository *repo); -extern void gitmodules_config_oid(const struct object_id *commit_oid); extern int is_submodule_active(struct repository *repo, const char *path); /* * Determine if a submodule has been populated at a given 'path' by checking if @@ -62,21 +57,17 @@ extern void die_in_unpopulated_submodule(const struct index_state *istate, const char *prefix); extern void die_path_inside_submodule(const struct index_state *istate, const struct pathspec *ps); +extern enum submodule_update_type parse_submodule_update_type(const char *value); extern int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); extern void handle_ignore_submodules_arg(struct diff_options *, const char *); -extern void show_submodule_summary(FILE *f, const char *path, - const char *line_prefix, +extern void show_submodule_summary(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset); -extern void show_submodule_inline_diff(FILE *f, const char *path, - const char *line_prefix, + unsigned dirty_submodule); +extern void show_submodule_inline_diff(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, - unsigned dirty_submodule, const char *meta, - const char *del, const char *add, const char *reset, - const struct diff_options *opt); + unsigned dirty_submodule); /* Check if we want to update any submodule.*/ extern int should_update_submodules(void); /* diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index e13fbcc1b5..f23db3b19a 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -10,11 +10,6 @@ static void die_usage(int argc, const char **argv, const char *msg) exit(1); } -static int git_test_config(const char *var, const char *value, void *cb) -{ - return parse_submodule_config_option(var, value); -} - int cmd_main(int argc, const char **argv) { const char **arg = argv; @@ -37,8 +32,6 @@ int cmd_main(int argc, const char **argv) die_usage(argc, argv, "Wrong number of arguments."); setup_git_directory(); - gitmodules_config(); - git_config(git_test_config, NULL); while (*arg) { struct object_id commit_oid; diff --git a/t/helper/test-write-cache.c b/t/helper/test-write-cache.c new file mode 100644 index 0000000000..b7ee039669 --- /dev/null +++ b/t/helper/test-write-cache.c @@ -0,0 +1,23 @@ +#include "cache.h" +#include "lockfile.h" + +static struct lock_file index_lock; + +int cmd_main(int argc, const char **argv) +{ + int i, cnt = 1, lockfd; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + read_cache(); + for (i = 0; i < cnt; i++) { + lockfd = hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (0 <= lockfd) { + write_locked_index(&the_index, &index_lock, COMMIT_LOCK); + } else { + rollback_lock_file(&index_lock); + } + } + + return 0; +} diff --git a/t/perf/p0007-write-cache.sh b/t/perf/p0007-write-cache.sh new file mode 100755 index 0000000000..261fe92fd9 --- /dev/null +++ b/t/perf/p0007-write-cache.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description="Tests performance of writing the index" + +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success "setup repo" ' + if git rev-parse --verify refs/heads/p0006-ballast^{commit} + then + echo Assuming synthetic repo from many-files.sh + git config --local core.sparsecheckout 1 + cat >.git/info/sparse-checkout <<-EOF + /* + !ballast/* + EOF + else + echo Assuming non-synthetic repo... + fi && + nr_files=$(git ls-files | wc -l) +' + +count=3 +test_perf "write_locked_index $count times ($nr_files files)" " + test-write-cache $count +" + +test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 4046817d70..3b1ac1971a 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -444,6 +444,14 @@ test_expect_failure 'stash file to directory' ' test foo = "$(cat file/file)" ' +test_expect_success 'stash create - no changes' ' + git stash clear && + test_when_finished "git reset --hard HEAD" && + git reset --hard && + git stash create >actual && + test_must_be_empty actual +' + test_expect_success 'stash branch - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && @@ -648,6 +656,20 @@ test_expect_success 'stash branch should not drop the stash if the branch exists git rev-parse stash@{0} -- ' +test_expect_success 'stash branch should not drop the stash if the apply fails' ' + git stash clear && + git reset HEAD~1 --hard && + echo foo >file && + git add file && + git commit -m initial && + echo bar >file && + git stash && + echo baz >file && + test_when_finished "git checkout master" && + test_must_fail git stash branch new_branch stash@{0} && + git rev-parse stash@{0} -- +' + test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' git stash clear && echo 1 >subdir/subfile1 && @@ -800,6 +822,18 @@ test_expect_success 'create with multiple arguments for the message' ' test_cmp expect actual ' +test_expect_success 'create in a detached state' ' + test_when_finished "git checkout master" && + git checkout HEAD~1 && + >foo && + git add foo && + STASH_ID=$(git stash create) && + HEAD_ID=$(git rev-parse --short HEAD) && + echo "WIP on (no branch): ${HEAD_ID} initial" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + test_expect_success 'stash -- <pathspec> stashes and restores the file' ' >foo && >bar && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 289806d0c7..12d182dc1b 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -972,4 +972,563 @@ test_expect_success 'option overrides diff.wsErrorHighlight' ' ' +test_expect_success 'detect moved code, complete file' ' + git reset --hard && + cat <<-\EOF >test.c && + #include<stdio.h> + main() + { + printf("Hello World"); + } + EOF + git add test.c && + git commit -m "add main function" && + git mv test.c main.c && + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + git diff HEAD --color-moved=zebra --no-renames | test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>new file mode 100644<RESET> + <BOLD>index 0000000..a986c57<RESET> + <BOLD>--- /dev/null<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -0,0 +1,5 @@<RESET> + <BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET> + <BGREEN>+<RESET><BGREEN>main()<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>printf("Hello World");<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>deleted file mode 100644<RESET> + <BOLD>index a986c57..0000000<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ /dev/null<RESET> + <CYAN>@@ -1,5 +0,0 @@<RESET> + <BRED>-#include<stdio.h><RESET> + <BRED>-main()<RESET> + <BRED>-{<RESET> + <BRED>-printf("Hello World");<RESET> + <BRED>-}<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect malicious moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + git reset --hard && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int secure_foo(struct user *u) + { + if (!u->is_allowed_foo) + return; + foo(u); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int another_function() + { + bar(); + } + EOF + git add main.c test.c && + git commit -m "add main and test file" && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int secure_foo(struct user *u) + { + foo(u); + if (!u->is_allowed_foo) + return; + } + + int another_function() + { + bar(); + } + EOF + git diff HEAD --no-renames --color-moved=zebra| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BLUE>-if (!u->is_allowed_foo)<RESET> + <BLUE>-return;<RESET> + <RED>-foo(u);<RESET> + <RED>-}<RESET> + <RED>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <GREEN>+<RESET><GREEN>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <GREEN>+<RESET><GREEN>}<RESET> + <GREEN>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'plain moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + # needs previous test as setup + git diff HEAD --no-renames --color-moved=plain| test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BRED>-if (!u->is_allowed_foo)<RESET> + <BRED>-return;<RESET> + <BRED>-foo(u);<RESET> + <BRED>-}<RESET> + <BRED>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BGREEN>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect permutations inside moved code -- dimmed_zebra' ' + git reset --hard && + cat <<-\EOF >lines.txt && + long line 1 + long line 2 + long line 3 + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + line 10 + line 11 + line 12 + line 13 + long line 14 + long line 15 + long line 16 + EOF + git add lines.txt && + git commit -m "add poetry" && + cat <<-\EOF >lines.txt && + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + long line 1 + long line 2 + long line 3 + long line 14 + long line 15 + long line 16 + line 10 + line 11 + line 12 + line 13 + EOF + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + git diff HEAD --no-renames --color-moved=dimmed_zebra | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <BMAGENTA>-long line 1<RESET> + <BMAGENTA>-long line 2<RESET> + <BMAGENTA>-long line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <BCYAN>+<RESET><BCYAN>long line 1<RESET> + <BCYAN>+<RESET><BCYAN>long line 2<RESET> + <CYAN>+<RESET><CYAN>long line 3<RESET> + <YELLOW>+<RESET><YELLOW>long line 14<RESET> + <BYELLOW>+<RESET><BYELLOW>long line 15<RESET> + <BYELLOW>+<RESET><BYELLOW>long line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <BMAGENTA>-long line 14<RESET> + <BMAGENTA>-long line 15<RESET> + <BMAGENTA>-long line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'cmd option assumes configured colored-moved' ' + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + test_config diff.colorMoved zebra && + git diff HEAD --no-renames --color-moved | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <MAGENTA>-long line 1<RESET> + <MAGENTA>-long line 2<RESET> + <MAGENTA>-long line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <CYAN>+<RESET><CYAN>long line 1<RESET> + <CYAN>+<RESET><CYAN>long line 2<RESET> + <CYAN>+<RESET><CYAN>long line 3<RESET> + <YELLOW>+<RESET><YELLOW>long line 14<RESET> + <YELLOW>+<RESET><YELLOW>long line 15<RESET> + <YELLOW>+<RESET><YELLOW>long line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <MAGENTA>-long line 14<RESET> + <MAGENTA>-long line 15<RESET> + <MAGENTA>-long line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'no effect from --color-moved with --word-diff' ' + cat <<-\EOF >text.txt && + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + EOF + git add text.txt && + git commit -a -m "clean state" && + cat <<-\EOF >text.txt && + simply Lorem Ipsum dummy is text of the typesetting and printing industry. + EOF + git diff --color-moved --word-diff >actual && + git diff --word-diff >expect && + test_cmp expect actual +' + +test_expect_success 'move detection ignoring whitespace ' ' + git reset --hard && + cat <<\EOF >lines.txt && +line 1 +line 2 +line 3 +line 4 +long line 5 +long line 6 +long line 7 +EOF + git add lines.txt && + git commit -m "add poetry" && + cat <<\EOF >lines.txt && + long line 5 + long line 6 + long line 7 +line 1 +line 2 +line 3 +line 4 +EOF + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + git diff HEAD --no-renames --color-moved | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,7 +1,7 @@<RESET> + <GREEN>+<RESET> <GREEN>long line 5<RESET> + <GREEN>+<RESET> <GREEN>long line 6<RESET> + <GREEN>+<RESET> <GREEN>long line 7<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + <RED>-long line 5<RESET> + <RED>-long line 6<RESET> + <RED>-long line 7<RESET> + EOF + test_cmp expected actual && + + git diff HEAD --no-renames -w --color-moved | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,7 +1,7 @@<RESET> + <CYAN>+<RESET> <CYAN>long line 5<RESET> + <CYAN>+<RESET> <CYAN>long line 6<RESET> + <CYAN>+<RESET> <CYAN>long line 7<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + <MAGENTA>-long line 5<RESET> + <MAGENTA>-long line 6<RESET> + <MAGENTA>-long line 7<RESET> + EOF + test_cmp expected actual +' + +test_expect_success '--color-moved block at end of diff output respects MIN_ALNUM_COUNT' ' + git reset --hard && + >bar && + cat <<-\EOF >foo && + irrelevant_line + line1 + EOF + git add foo bar && + git commit -m x && + + cat <<-\EOF >bar && + line1 + EOF + cat <<-\EOF >foo && + irrelevant_line + EOF + + git diff HEAD --color-moved=zebra --no-renames | + grep -v "index" | + test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1 @@<RESET> + <GREEN>+<RESET><GREEN>line1<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,2 +1 @@<RESET> + irrelevant_line<RESET> + <RED>-line1<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success '--color-moved respects MIN_ALNUM_COUNT' ' + git reset --hard && + cat <<-\EOF >foo && + nineteen chars 456789 + irrelevant_line + twenty chars 234567890 + EOF + >bar && + git add foo bar && + git commit -m x && + + cat <<-\EOF >foo && + irrelevant_line + EOF + cat <<-\EOF >bar && + twenty chars 234567890 + nineteen chars 456789 + EOF + + git diff HEAD --color-moved=zebra --no-renames | + grep -v "index" | + test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1,2 @@<RESET> + <BOLD;CYAN>+<RESET><BOLD;CYAN>twenty chars 234567890<RESET> + <GREEN>+<RESET><GREEN>nineteen chars 456789<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,3 +1 @@<RESET> + <RED>-nineteen chars 456789<RESET> + irrelevant_line<RESET> + <BOLD;MAGENTA>-twenty chars 234567890<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success '--color-moved treats adjacent blocks as separate for MIN_ALNUM_COUNT' ' + git reset --hard && + cat <<-\EOF >foo && + 7charsA + irrelevant_line + 7charsB + 7charsC + EOF + >bar && + git add foo bar && + git commit -m x && + + cat <<-\EOF >foo && + irrelevant_line + EOF + cat <<-\EOF >bar && + 7charsB + 7charsC + 7charsA + EOF + + git diff HEAD --color-moved=zebra --no-renames | grep -v "index" | test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1,3 @@<RESET> + <GREEN>+<RESET><GREEN>7charsB<RESET> + <GREEN>+<RESET><GREEN>7charsC<RESET> + <GREEN>+<RESET><GREEN>7charsA<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,4 +1 @@<RESET> + <RED>-7charsA<RESET> + irrelevant_line<RESET> + <RED>-7charsB<RESET> + <RED>-7charsC<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'move detection with submodules' ' + test_create_repo bananas && + echo ripe >bananas/recipe && + git -C bananas add recipe && + test_commit fruit && + test_commit -C bananas recipe && + git submodule add ./bananas && + git add bananas && + git commit -a -m "bananas are like a heavy library?" && + echo foul >bananas/recipe && + echo ripe >fruit.t && + + git diff --submodule=diff --color-moved >actual && + + # no move detection as the moved line is across repository boundaries. + test_decode_color <actual >decoded_actual && + ! grep BGREEN decoded_actual && + ! grep BRED decoded_actual && + + # nor did we mess with it another way + git diff --submodule=diff | test_decode_color >expect && + test_cmp expect decoded_actual +' + test_done diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 518bf9524e..2ffd11a142 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -113,35 +113,6 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' ! test -s actual4 ' -test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' ' - git config diff.ignoreSubmodules all && - git diff HEAD >actual && - ! test -s actual && - git config submodule.subname.ignore none && - git config submodule.subname.path sub && - 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 && - git config submodule.subname.ignore all && - git diff HEAD >actual2 && - ! test -s actual2 && - git config submodule.subname.ignore untracked && - git diff HEAD >actual3 && - sed -e "1,/^@@/d" actual3 >actual3.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual3.body && - git config submodule.subname.ignore dirty && - git diff HEAD >actual4 && - ! test -s actual4 && - git diff HEAD --ignore-submodules=none >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body && - git config --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules -' - test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' ' git config diff.ignoreSubmodules dirty && git diff HEAD >actual && @@ -208,24 +179,6 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' ! test -s actual4 ' -test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' ' - git config submodule.subname.ignore all && - git config submodule.subname.path sub && - git diff HEAD >actual2 && - ! test -s actual2 && - git config submodule.subname.ignore untracked && - git diff HEAD >actual3 && - ! test -s actual3 && - git config submodule.subname.ignore dirty && - git diff HEAD >actual4 && - ! test -s actual4 && - git diff --ignore-submodules=none HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body && - git config --remove-section submodule.subname -' - test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' ' git config --add -f .gitmodules submodule.subname.ignore all && git config --add -f .gitmodules submodule.subname.path sub && @@ -261,26 +214,6 @@ test_expect_success 'git diff between submodule commits' ' ! test -s actual ' -test_expect_success 'git diff between submodule commits [.git/config]' ' - git diff HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - test_cmp expect.body actual.body && - git config submodule.subname.ignore dirty && - git config submodule.subname.path sub && - git diff HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - test_cmp expect.body actual.body && - git config submodule.subname.ignore all && - git diff HEAD^..HEAD >actual && - ! test -s actual && - git diff --ignore-submodules=dirty HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - git config --remove-section submodule.subname -' - test_expect_success 'git diff between submodule commits [.gitmodules]' ' git diff HEAD^..HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index d350065f25..4fc27c51f7 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -467,21 +467,42 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' ' test_cmp one expect ' -test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' +test_expect_success 'CR-LF line endings && add line && text=auto' ' git config --unset core.whitespace && printf "a\r\n" >one && + cp one save-one && + git add one && printf "b\r\n" >>one && - printf "c\r\n" >>one && + cp one expect && + git diff -- one >patch && + mv save-one one && + echo "one text=auto" >.gitattributes && + git apply patch && + test_cmp one expect +' + +test_expect_success 'CR-LF line endings && change line && text=auto' ' + printf "a\r\n" >one && cp one save-one && - printf " \r\n" >>one && git add one && + printf "b\r\n" >one && cp one expect && - printf "d\r\n" >>one && git diff -- one >patch && mv save-one one && - echo d >>expect && + echo "one text=auto" >.gitattributes && + git apply patch && + test_cmp one expect +' - git apply --ignore-space-change --whitespace=fix patch && +test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' ' + printf "a\n" >one && + git add one && + printf "b\r\n" >one && + git diff -- one >patch && + printf "a\r\n" >one && + echo "one text=auto" >.gitattributes && + git -c core.eol=CRLF apply patch && + printf "b\r\n" >expect && test_cmp one expect ' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 1a080e7823..d97d2bebc9 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -239,6 +239,43 @@ test_expect_success 'old records rest in peace' ' ! test -f $rr2/preimage ' +rerere_gc_custom_expiry_test () { + five_days="$1" right_now="$2" + test_expect_success "rerere gc with custom expiry ($five_days, $right_now)" ' + rm -fr .git/rr-cache && + rr=.git/rr-cache/$_z40 && + mkdir -p "$rr" && + >"$rr/preimage" && + >"$rr/postimage" && + + two_days_ago=$((-2*86400)) && + test-chmtime =$two_days_ago "$rr/preimage" && + test-chmtime =$two_days_ago "$rr/postimage" && + + find .git/rr-cache -type f | sort >original && + + git -c "gc.rerereresolved=$five_days" \ + -c "gc.rerereunresolved=$five_days" rerere gc && + find .git/rr-cache -type f | sort >actual && + test_cmp original actual && + + git -c "gc.rerereresolved=$five_days" \ + -c "gc.rerereunresolved=$right_now" rerere gc && + find .git/rr-cache -type f | sort >actual && + test_cmp original actual && + + git -c "gc.rerereresolved=$right_now" \ + -c "gc.rerereunresolved=$right_now" rerere gc && + find .git/rr-cache -type f | sort >actual && + >expect && + test_cmp expect actual + ' +} + +rerere_gc_custom_expiry_test 5 0 + +rerere_gc_custom_expiry_test 5.days.ago now + test_expect_success 'setup: file2 added differently in two branches' ' git reset --hard && @@ -419,24 +456,6 @@ count_pre_post () { test_line_count = "$2" actual } -test_expect_success 'rerere gc' ' - find .git/rr-cache -type f >original && - xargs test-chmtime -172800 <original && - - git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && - find .git/rr-cache -type f >actual && - test_cmp original actual && - - git -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc && - find .git/rr-cache -type f >actual && - test_cmp original actual && - - git -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc && - find .git/rr-cache -type f >actual && - >expect && - test_cmp expect actual -' - merge_conflict_resolve () { git reset --hard && test_must_fail git merge six.1 && @@ -446,6 +465,8 @@ merge_conflict_resolve () { } test_expect_success 'multiple identical conflicts' ' + rm -fr .git/rr-cache && + mkdir .git/rr-cache && git reset --hard && test_seq 1 6 >early && diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 18aa1b5889..ec5f530102 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -539,25 +539,62 @@ cat >trailers <<EOF Signed-off-by: A U Thor <author@example.com> Acked-by: A U Thor <author@example.com> [ v2 updated patch description ] -Signed-off-by: A U Thor <author@example.com> +Signed-off-by: A U Thor + <author@example.com> EOF -test_expect_success 'pretty format %(trailers) shows trailers' ' +unfold () { + perl -0pe 's/\n\s+/ /' +} + +test_expect_success 'set up trailer tests' ' echo "Some contents" >trailerfile && git add trailerfile && - git commit -F - <<-EOF && + git commit -F - <<-EOF trailers: this commit message has trailers This commit is a test commit with trailers at the end. We parse this - message and display the trailers using %bT + message and display the trailers using %(trailers). $(cat trailers) EOF +' + +test_expect_success 'pretty format %(trailers) shows trailers' ' git log --no-walk --pretty="%(trailers)" >actual && - cat >expect <<-EOF && - $(cat trailers) + { + cat trailers && + echo + } >expect && + test_cmp expect actual +' - EOF +test_expect_success '%(trailers:only) shows only "key: value" trailers' ' + git log --no-walk --pretty="%(trailers:only)" >actual && + { + grep -v patch.description <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:unfold) unfolds trailers' ' + git log --no-walk --pretty="%(trailers:unfold)" >actual && + { + unfold <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success ':only and :unfold work together' ' + git log --no-walk --pretty="%(trailers:only:unfold)" >actual && + git log --no-walk --pretty="%(trailers:unfold:only)" >reverse && + test_cmp actual reverse && + { + grep -v patch.description <trailers | unfold && + echo + } >expect && test_cmp expect actual ' diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e9c3335b78..6f8337ffb5 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -46,16 +46,6 @@ test_expect_success 'submodule update aborts on missing gitmodules url' ' test_must_fail git submodule init ' -test_expect_success 'configuration parsing' ' - test_when_finished "rm -f .gitmodules" && - cat >.gitmodules <<-\EOF && - [submodule "s"] - path - ignore - EOF - test_must_fail git status -' - test_expect_success 'setup - repository in init subdirectory' ' mkdir init && ( diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index eea36f1dbe..46c09c7765 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -31,6 +31,21 @@ test_expect_success 'submodule config cache setup' ' ) ' +test_expect_success 'configuration parsing with error' ' + test_when_finished "rm -rf repo" && + test_create_repo repo && + cat >repo/.gitmodules <<-\EOF && + [submodule "s"] + path + ignore + EOF + ( + cd repo && + test_must_fail test-submodule-config "" s 2>actual && + test_i18ngrep "bad config" actual + ) +' + cat >super/expect <<EOF Submodule name: 'a' for path 'a' Submodule name: 'a' for path 'b' @@ -107,78 +122,6 @@ test_expect_success 'using different treeishs works' ' ) ' -cat >super/expect_url <<EOF -Submodule url: 'git@somewhere.else.net:a.git' for path 'b' -Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' -EOF - -cat >super/expect_local_path <<EOF -Submodule name: 'a' for path 'c' -Submodule name: 'submodule' for path 'submodule' -EOF - -test_expect_success 'reading of local configuration' ' - (cd super && - old_a=$(git config submodule.a.url) && - old_submodule=$(git config submodule.submodule.url) && - git config submodule.a.url git@somewhere.else.net:a.git && - git config submodule.submodule.url git@somewhere.else.net:submodule.git && - test-submodule-config --url \ - "" b \ - "" submodule \ - >actual && - test_cmp expect_url actual && - git config submodule.a.path c && - test-submodule-config \ - "" c \ - "" submodule \ - >actual && - test_cmp expect_local_path actual && - git config submodule.a.url "$old_a" && - git config submodule.submodule.url "$old_submodule" && - git config --unset submodule.a.path c - ) -' - -cat >super/expect_url <<EOF -Submodule url: '../submodule' for path 'b' -Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' -EOF - -test_expect_success 'reading of local configuration for uninitialized submodules' ' - ( - cd super && - git submodule deinit -f b && - old_submodule=$(git config submodule.submodule.url) && - git config submodule.submodule.url git@somewhere.else.net:submodule.git && - test-submodule-config --url \ - "" b \ - "" submodule \ - >actual && - test_cmp expect_url actual && - git config submodule.submodule.url "$old_submodule" && - git submodule init b - ) -' - -cat >super/expect_fetchrecurse_die.err <<EOF -fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla -EOF - -test_expect_success 'local error in fetchrecursesubmodule dies early' ' - (cd super && - git config submodule.submodule.fetchrecursesubmodules blabla && - test_must_fail test-submodule-config \ - "" b \ - "" submodule \ - >actual.out 2>actual.err && - touch expect_fetchrecurse_die.out && - test_cmp expect_fetchrecurse_die.out actual.out && - test_cmp expect_fetchrecurse_die.err actual.err && - git config --unset submodule.submodule.fetchrecursesubmodules - ) -' - test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' (cd super && git config -f .gitmodules \ diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 0c6f91c433..164719d1c9 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -681,6 +681,36 @@ test_expect_success 'using "where = before"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--where after"' ' + git config trailer.ack.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --trailer "ack: Peff" \ + complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before" with "--no-where"' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Peff + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --no-where --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "where = after"' ' git config trailer.ack.where "after" && cat complex_message_body >expected && @@ -947,6 +977,23 @@ test_expect_success 'using "ifExists = add" with "where = after"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-exists replace"' ' + git config trailer.fix.key "Fixes: " && + git config trailer.fix.ifExists "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Fixes: 22 + EOF + git interpret-trailers --if-exists replace --trailer "review:" \ + --trailer "fix=53" --trailer "fix=22" --trailer "bug: 42" \ + <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "ifExists = replace"' ' git config trailer.fix.key "Fixes: " && git config trailer.fix.ifExists "replace" && @@ -1026,6 +1073,25 @@ test_expect_success 'the default is "ifMissing = add"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-missing doNothing"' ' + git config trailer.ifmissing "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --if-missing doNothing \ + --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'when default "ifMissing" is "doNothing"' ' git config trailer.ifmissing "doNothing" && cat complex_message_body >expected && @@ -1275,4 +1341,80 @@ test_expect_success 'with cut line' ' test_cmp expected actual ' +test_expect_success 'only trailers' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + sign: config-value + added: added-value + EOF + git interpret-trailers \ + --trailer added:added-value \ + --only-trailers >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual +' + +test_expect_success 'only-trailers omits non-trailer in middle of block' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + Signed-off-by: nobody <nobody@nowhere> + Signed-off-by: somebody <somebody@somewhere> + sign: config-value + EOF + git interpret-trailers --only-trailers >actual <<-\EOF && + subject + + it is important that the trailers below are signed-off-by + so that they meet the "25% trailers Git knows about" heuristic + + Signed-off-by: nobody <nobody@nowhere> + this is not a trailer + Signed-off-by: somebody <somebody@somewhere> + EOF + test_cmp expected actual +' + +test_expect_success 'only input' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + EOF + git interpret-trailers \ + --only-trailers --only-input >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual +' + +test_expect_success 'unfold' ' + cat >expected <<-\EOF && + foo: continued across several lines + EOF + # pass through tr to make leading and trailing whitespace more obvious + tr _ " " <<-\EOF | + my subject + + my body + + foo:_ + __continued + ___across + ____several + _____lines + ___ + EOF + git interpret-trailers --only-trailers --only-input --unfold >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7614-merge-signoff.sh b/t/t7614-merge-signoff.sh new file mode 100755 index 0000000000..c1b8446f49 --- /dev/null +++ b/t/t7614-merge-signoff.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git merge --signoff + +This test runs git merge --signoff and makes sure that it works. +' + +. ./test-lib.sh + +# Setup test files +test_setup() { + # Expected commit message after merge --signoff + cat >expected-signed <<EOF && +Merge branch 'master' into other-branch + +Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") +EOF + + # Expected commit message after merge without --signoff (or with --no-signoff) + cat >expected-unsigned <<EOF && +Merge branch 'master' into other-branch +EOF + + # Initial commit and feature branch to merge master into it. + git commit --allow-empty -m "Initial empty commit" && + git checkout -b other-branch && + test_commit other-branch file1 1 +} + +# Setup repository, files & feature branch +# This step must be run if You want to test 2,3 or 4 +# Order of 2,3,4 is not important, but 1 must be run before +# For example `-r 1,4` or `-r 1,4,2 -v` etc +# But not `-r 2` or `-r 4,3,2,1` +test_expect_success 'setup' ' + test_setup +' + +# Test with --signoff flag +test_expect_success 'git merge --signoff adds a sign-off line' ' + git checkout master && + test_commit master-branch-2 file2 2 && + git checkout other-branch && + git merge master --signoff --no-edit && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' + +# Test without --signoff flag +test_expect_success 'git merge does not add a sign-off line' ' + git checkout master && + test_commit master-branch-3 file3 3 && + git checkout other-branch && + git merge master --no-edit && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-unsigned actual +' + +# Test for --no-signoff flag +test_expect_success 'git merge --no-signoff flag cancels --signoff flag' ' + git checkout master && + test_commit master-branch-4 file4 4 && + git checkout other-branch && + git merge master --no-edit --signoff --no-signoff && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-unsigned actual +' + +test_done @@ -10,18 +10,13 @@ * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> */ -enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START }; -enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT, - EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING }; -enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING }; - struct conf_info { char *name; char *key; char *command; - enum action_where where; - enum action_if_exists if_exists; - enum action_if_missing if_missing; + enum trailer_where where; + enum trailer_if_exists if_exists; + enum trailer_if_missing if_missing; }; static struct conf_info default_conf_info; @@ -63,7 +58,7 @@ static const char *git_generated_prefixes[] = { pos != (head); \ pos = is_reverse ? pos->prev : pos->next) -static int after_or_end(enum action_where where) +static int after_or_end(enum trailer_where where) { return (where == WHERE_AFTER) || (where == WHERE_END); } @@ -164,13 +159,15 @@ static void print_tok_val(FILE *outfile, const char *tok, const char *val) fprintf(outfile, "%s%c %s\n", tok, separators[0], val); } -static void print_all(FILE *outfile, struct list_head *head, int trim_empty) +static void print_all(FILE *outfile, struct list_head *head, + const struct process_trailer_options *opts) { struct list_head *pos; struct trailer_item *item; list_for_each(pos, head) { item = list_entry(pos, struct trailer_item, list); - if (!trim_empty || strlen(item->value) > 0) + if ((!opts->trim_empty || strlen(item->value) > 0) && + (!opts->only_trailers || item->token)) print_tok_val(outfile, item->token, item->value); } } @@ -201,7 +198,7 @@ static int check_if_different(struct trailer_item *in_tok, int check_all, struct list_head *head) { - enum action_where where = arg_tok->conf.where; + enum trailer_where where = arg_tok->conf.where; struct list_head *next_head; do { if (same_trailer(in_tok, arg_tok)) @@ -300,13 +297,16 @@ static void apply_arg_if_exists(struct trailer_item *in_tok, else free_arg_item(arg_tok); break; + default: + die("BUG: trailer.c: unhandled value %d", + arg_tok->conf.if_exists); } } static void apply_arg_if_missing(struct list_head *head, struct arg_item *arg_tok) { - enum action_where where; + enum trailer_where where; struct trailer_item *to_add; switch (arg_tok->conf.if_missing) { @@ -321,6 +321,10 @@ static void apply_arg_if_missing(struct list_head *head, list_add_tail(&to_add->list, head); else list_add(&to_add->list, head); + break; + default: + die("BUG: trailer.c: unhandled value %d", + arg_tok->conf.if_missing); } } @@ -331,7 +335,7 @@ static int find_same_and_apply_arg(struct list_head *head, struct trailer_item *in_tok; struct trailer_item *on_tok; - enum action_where where = arg_tok->conf.where; + enum trailer_where where = arg_tok->conf.where; int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE); int backwards = after_or_end(where); struct trailer_item *start_tok; @@ -373,44 +377,50 @@ static void process_trailers_lists(struct list_head *head, } } -static int set_where(struct conf_info *item, const char *value) +int trailer_set_where(enum trailer_where *item, const char *value) { - if (!strcasecmp("after", value)) - item->where = WHERE_AFTER; + if (!value) + *item = WHERE_DEFAULT; + else if (!strcasecmp("after", value)) + *item = WHERE_AFTER; else if (!strcasecmp("before", value)) - item->where = WHERE_BEFORE; + *item = WHERE_BEFORE; else if (!strcasecmp("end", value)) - item->where = WHERE_END; + *item = WHERE_END; else if (!strcasecmp("start", value)) - item->where = WHERE_START; + *item = WHERE_START; else return -1; return 0; } -static int set_if_exists(struct conf_info *item, const char *value) +int trailer_set_if_exists(enum trailer_if_exists *item, const char *value) { - if (!strcasecmp("addIfDifferent", value)) - item->if_exists = EXISTS_ADD_IF_DIFFERENT; + if (!value) + *item = EXISTS_DEFAULT; + else if (!strcasecmp("addIfDifferent", value)) + *item = EXISTS_ADD_IF_DIFFERENT; else if (!strcasecmp("addIfDifferentNeighbor", value)) - item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; + *item = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; else if (!strcasecmp("add", value)) - item->if_exists = EXISTS_ADD; + *item = EXISTS_ADD; else if (!strcasecmp("replace", value)) - item->if_exists = EXISTS_REPLACE; + *item = EXISTS_REPLACE; else if (!strcasecmp("doNothing", value)) - item->if_exists = EXISTS_DO_NOTHING; + *item = EXISTS_DO_NOTHING; else return -1; return 0; } -static int set_if_missing(struct conf_info *item, const char *value) +int trailer_set_if_missing(enum trailer_if_missing *item, const char *value) { - if (!strcasecmp("doNothing", value)) - item->if_missing = MISSING_DO_NOTHING; + if (!value) + *item = MISSING_DEFAULT; + else if (!strcasecmp("doNothing", value)) + *item = MISSING_DO_NOTHING; else if (!strcasecmp("add", value)) - item->if_missing = MISSING_ADD; + *item = MISSING_ADD; else return -1; return 0; @@ -470,15 +480,18 @@ static int git_trailer_default_config(const char *conf_key, const char *value, v variable_name = strrchr(trailer_item, '.'); if (!variable_name) { if (!strcmp(trailer_item, "where")) { - if (set_where(&default_conf_info, value) < 0) + if (trailer_set_where(&default_conf_info.where, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "ifexists")) { - if (set_if_exists(&default_conf_info, value) < 0) + if (trailer_set_if_exists(&default_conf_info.if_exists, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "ifmissing")) { - if (set_if_missing(&default_conf_info, value) < 0) + if (trailer_set_if_missing(&default_conf_info.if_missing, + value) < 0) warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "separators")) { @@ -532,15 +545,15 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb) conf->command = xstrdup(value); break; case TRAILER_WHERE: - if (set_where(conf, value)) + if (trailer_set_where(&conf->where, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; case TRAILER_IF_EXISTS: - if (set_if_exists(conf, value)) + if (trailer_set_if_exists(&conf->if_exists, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; case TRAILER_IF_MISSING: - if (set_if_missing(conf, value)) + if (trailer_set_if_missing(&conf->if_missing, value)) warning(_("unknown value '%s' for key '%s'"), value, conf_key); break; default: @@ -555,6 +568,9 @@ static void ensure_configured(void) return; /* Default config must be setup first */ + default_conf_info.where = WHERE_END; + default_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR; + default_conf_info.if_missing = MISSING_ADD; git_config(git_trailer_default_config, NULL); git_config(git_trailer_config, NULL); configured = 1; @@ -658,19 +674,27 @@ static struct trailer_item *add_trailer_item(struct list_head *head, char *tok, } static void add_arg_item(struct list_head *arg_head, char *tok, char *val, - const struct conf_info *conf) + const struct conf_info *conf, + const struct new_trailer_item *new_trailer_item) { struct arg_item *new = xcalloc(sizeof(*new), 1); new->token = tok; new->value = val; duplicate_conf(&new->conf, conf); + if (new_trailer_item) { + if (new_trailer_item->where != WHERE_DEFAULT) + new->conf.where = new_trailer_item->where; + if (new_trailer_item->if_exists != EXISTS_DEFAULT) + new->conf.if_exists = new_trailer_item->if_exists; + if (new_trailer_item->if_missing != MISSING_DEFAULT) + new->conf.if_missing = new_trailer_item->if_missing; + } list_add_tail(&new->list, arg_head); } static void process_command_line_args(struct list_head *arg_head, - struct string_list *trailers) + struct list_head *new_trailer_head) { - struct string_list_item *tr; struct arg_item *item; struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; @@ -690,26 +714,29 @@ static void process_command_line_args(struct list_head *arg_head, add_arg_item(arg_head, xstrdup(token_from_item(item, NULL)), xstrdup(""), - &item->conf); + &item->conf, NULL); } /* Add an arg item for each trailer on the command line */ - for_each_string_list_item(tr, trailers) { - int separator_pos = find_separator(tr->string, cl_separators); + list_for_each(pos, new_trailer_head) { + struct new_trailer_item *tr = + list_entry(pos, struct new_trailer_item, list); + int separator_pos = find_separator(tr->text, cl_separators); + if (separator_pos == 0) { struct strbuf sb = STRBUF_INIT; - strbuf_addstr(&sb, tr->string); + strbuf_addstr(&sb, tr->text); strbuf_trim(&sb); error(_("empty trailer token in trailer '%.*s'"), (int) sb.len, sb.buf); strbuf_release(&sb); } else { - parse_trailer(&tok, &val, &conf, tr->string, + parse_trailer(&tok, &val, &conf, tr->text, separator_pos); add_arg_item(arg_head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL), - conf); + conf, tr); } } @@ -885,9 +912,37 @@ static int ends_with_blank_line(const char *buf, size_t len) return is_blank_line(buf + ll); } +static void unfold_value(struct strbuf *val) +{ + struct strbuf out = STRBUF_INIT; + size_t i; + + strbuf_grow(&out, val->len); + i = 0; + while (i < val->len) { + char c = val->buf[i++]; + if (c == '\n') { + /* Collapse continuation down to a single space. */ + while (i < val->len && isspace(val->buf[i])) + i++; + strbuf_addch(&out, ' '); + } else { + strbuf_addch(&out, c); + } + } + + /* Empty lines may have left us with whitespace cruft at the edges */ + strbuf_trim(&out); + + /* output goes back to val as if we modified it in-place */ + strbuf_swap(&out, val); + strbuf_release(&out); +} + static int process_input_file(FILE *outfile, const char *str, - struct list_head *head) + struct list_head *head, + const struct process_trailer_options *opts) { struct trailer_info info; struct strbuf tok = STRBUF_INIT; @@ -897,9 +952,10 @@ static int process_input_file(FILE *outfile, trailer_info_get(&info, str); /* Print lines before the trailers as is */ - fwrite(str, 1, info.trailer_start - str, outfile); + if (!opts->only_trailers) + fwrite(str, 1, info.trailer_start - str, outfile); - if (!info.blank_line_before_trailer) + if (!opts->only_trailers && !info.blank_line_before_trailer) fprintf(outfile, "\n"); for (i = 0; i < info.trailer_nr; i++) { @@ -911,10 +967,12 @@ static int process_input_file(FILE *outfile, if (separator_pos >= 1) { parse_trailer(&tok, &val, NULL, trailer, separator_pos); + if (opts->unfold) + unfold_value(&val); add_trailer_item(head, strbuf_detach(&tok, NULL), strbuf_detach(&val, NULL)); - } else { + } else if (!opts->only_trailers) { strbuf_addstr(&val, trailer); strbuf_strip_suffix(&val, "\n"); add_trailer_item(head, @@ -968,10 +1026,11 @@ static FILE *create_in_place_tempfile(const char *file) return outfile; } -void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) +void process_trailers(const char *file, + const struct process_trailer_options *opts, + struct list_head *new_trailer_head) { LIST_HEAD(head); - LIST_HEAD(arg_head); struct strbuf sb = STRBUF_INIT; int trailer_end; FILE *outfile = stdout; @@ -980,24 +1039,27 @@ void process_trailers(const char *file, int in_place, int trim_empty, struct str read_input_file(&sb, file); - if (in_place) + if (opts->in_place) outfile = create_in_place_tempfile(file); /* Print the lines before the trailers */ - trailer_end = process_input_file(outfile, sb.buf, &head); - - process_command_line_args(&arg_head, trailers); + trailer_end = process_input_file(outfile, sb.buf, &head, opts); - process_trailers_lists(&head, &arg_head); + if (!opts->only_input) { + LIST_HEAD(arg_head); + process_command_line_args(&arg_head, new_trailer_head); + process_trailers_lists(&head, &arg_head); + } - print_all(outfile, &head, trim_empty); + print_all(outfile, &head, opts); free_all(&head); /* Print the lines after the trailers as is */ - fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); + if (!opts->only_trailers) + fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); - if (in_place) + if (opts->in_place) if (rename_tempfile(&trailers_tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); @@ -1054,3 +1116,49 @@ void trailer_info_release(struct trailer_info *info) free(info->trailers[i]); free(info->trailers); } + +static void format_trailer_info(struct strbuf *out, + const struct trailer_info *info, + const struct process_trailer_options *opts) +{ + int i; + + /* If we want the whole block untouched, we can take the fast path. */ + if (!opts->only_trailers && !opts->unfold) { + strbuf_add(out, info->trailer_start, + info->trailer_end - info->trailer_start); + return; + } + + for (i = 0; i < info->trailer_nr; i++) { + char *trailer = info->trailers[i]; + int separator_pos = find_separator(trailer, separators); + + if (separator_pos >= 1) { + struct strbuf tok = STRBUF_INIT; + struct strbuf val = STRBUF_INIT; + + parse_trailer(&tok, &val, NULL, trailer, separator_pos); + if (opts->unfold) + unfold_value(&val); + + strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); + strbuf_release(&tok); + strbuf_release(&val); + + } else if (!opts->only_trailers) { + strbuf_addstr(out, trailer); + } + } + +} + +void format_trailers_from_commit(struct strbuf *out, const char *msg, + const struct process_trailer_options *opts) +{ + struct trailer_info info; + + trailer_info_get(&info, msg); + format_trailer_info(out, &info, opts); + trailer_info_release(&info); +} @@ -1,6 +1,33 @@ #ifndef TRAILER_H #define TRAILER_H +#include "list.h" + +enum trailer_where { + WHERE_DEFAULT, + WHERE_END, + WHERE_AFTER, + WHERE_BEFORE, + WHERE_START +}; +enum trailer_if_exists { + EXISTS_DEFAULT, + EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, + EXISTS_ADD_IF_DIFFERENT, + EXISTS_ADD, + EXISTS_REPLACE, + EXISTS_DO_NOTHING +}; +enum trailer_if_missing { + MISSING_DEFAULT, + MISSING_ADD, + MISSING_DO_NOTHING +}; + +int trailer_set_where(enum trailer_where *item, const char *value); +int trailer_set_if_exists(enum trailer_if_exists *item, const char *value); +int trailer_set_if_missing(enum trailer_if_missing *item, const char *value); + struct trailer_info { /* * True if there is a blank line before the location pointed to by @@ -22,11 +49,50 @@ struct trailer_info { size_t trailer_nr; }; -void process_trailers(const char *file, int in_place, int trim_empty, - struct string_list *trailers); +/* + * A list that represents newly-added trailers, such as those provided + * with the --trailer command line option of git-interpret-trailers. + */ +struct new_trailer_item { + struct list_head list; + + const char *text; + + enum trailer_where where; + enum trailer_if_exists if_exists; + enum trailer_if_missing if_missing; +}; + +struct process_trailer_options { + int in_place; + int trim_empty; + int only_trailers; + int only_input; + int unfold; +}; + +#define PROCESS_TRAILER_OPTIONS_INIT {0} + +void process_trailers(const char *file, + const struct process_trailer_options *opts, + struct list_head *new_trailer_head); void trailer_info_get(struct trailer_info *info, const char *str); void trailer_info_release(struct trailer_info *info); +/* + * Format the trailers from the commit msg "msg" into the strbuf "out". + * Note two caveats about "opts": + * + * - this is primarily a helper for pretty.c, and not + * all of the flags are supported. + * + * - this differs from process_trailers slightly in that we always format + * only the trailer block itself, even if the "only_trailers" option is not + * set. + */ +void format_trailers_from_commit(struct strbuf *out, const char *msg, + const struct process_trailer_options *opts); + #endif /* TRAILER_H */ diff --git a/tree-diff.c b/tree-diff.c index 2357f72899..4bb93155bc 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -421,9 +421,8 @@ static struct combine_diff_path *ll_diff_tree_paths( * diff_tree_oid(parent, commit) ) */ for (i = 0; i < nparent; ++i) - tptree[i] = fill_tree_descriptor(&tp[i], - parents_oid[i] ? parents_oid[i]->hash : NULL); - ttree = fill_tree_descriptor(&t, oid ? oid->hash : NULL); + tptree[i] = fill_tree_descriptor(&tp[i], parents_oid[i]); + ttree = fill_tree_descriptor(&t, oid); /* Enable recursion indefinitely */ opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE); diff --git a/tree-walk.c b/tree-walk.c index 6a42e402b0..c99309069a 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -78,15 +78,16 @@ int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned l return result; } -void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1) +void *fill_tree_descriptor(struct tree_desc *desc, const struct object_id *oid) { unsigned long size = 0; void *buf = NULL; - if (sha1) { - buf = read_object_with_reference(sha1, tree_type, &size, NULL); + if (oid) { + buf = read_object_with_reference(oid->hash, tree_type, &size, + NULL); if (!buf) - die("unable to read tree %s", sha1_to_hex(sha1)); + die("unable to read tree %s", oid_to_hex(oid)); } init_tree_desc(desc, buf, size); return buf; diff --git a/tree-walk.h b/tree-walk.h index 68bb78b928..b6bd1b4ccf 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -42,7 +42,7 @@ int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long int tree_entry(struct tree_desc *, struct name_entry *); int tree_entry_gently(struct tree_desc *, struct name_entry *); -void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1); +void *fill_tree_descriptor(struct tree_desc *desc, const struct object_id *oid); struct traverse_info; typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); diff --git a/unpack-trees.c b/unpack-trees.c index 38000ac8fa..68d34259c6 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,5 +1,6 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "repository.h" #include "config.h" #include "dir.h" #include "tree.h" @@ -255,47 +256,41 @@ static int check_submodule_move_head(const struct cache_entry *ce, { unsigned flags = SUBMODULE_MOVE_HEAD_DRY_RUN; const struct submodule *sub = submodule_from_ce(ce); + if (!sub) return 0; if (o->reset) flags |= SUBMODULE_MOVE_HEAD_FORCE; - switch (sub->update_strategy.type) { - case SM_UPDATE_UNSPECIFIED: - case SM_UPDATE_CHECKOUT: - if (submodule_move_head(ce->name, old_id, new_id, flags)) - return o->gently ? -1 : - add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name); - return 0; - case SM_UPDATE_NONE: - return 0; - case SM_UPDATE_REBASE: - case SM_UPDATE_MERGE: - case SM_UPDATE_COMMAND: - default: - warning(_("submodule update strategy not supported for submodule '%s'"), ce->name); - return -1; - } + if (submodule_move_head(ce->name, old_id, new_id, flags)) + return o->gently ? -1 : + add_rejected_path(o, ERROR_WOULD_LOSE_SUBMODULE, ce->name); + return 0; } -static void reload_gitmodules_file(struct index_state *index, - struct checkout *state) +/* + * Preform the loading of the repository's gitmodules file. This function is + * used by 'check_update()' to perform loading of the gitmodules file in two + * differnt situations: + * (1) before removing entries from the working tree if the gitmodules file has + * been marked for removal. This situation is specified by 'state' == NULL. + * (2) before checking out entries to the working tree if the gitmodules file + * has been marked for update. This situation is specified by 'state' != NULL. + */ +static void load_gitmodules_file(struct index_state *index, + struct checkout *state) { - int i; - for (i = 0; i < index->cache_nr; i++) { - struct cache_entry *ce = index->cache[i]; - if (ce->ce_flags & CE_UPDATE) { - int r = strcmp(ce->name, GITMODULES_FILE); - if (r < 0) - continue; - else if (r == 0) { - submodule_free(); - checkout_entry(ce, state, NULL); - gitmodules_config(); - git_config(submodule_config, NULL); - } else - break; + int pos = index_name_pos(index, GITMODULES_FILE, strlen(GITMODULES_FILE)); + + if (pos >= 0) { + struct cache_entry *ce = index->cache[pos]; + if (!state && ce->ce_flags & CE_WT_REMOVE) { + repo_read_gitmodules(the_repository); + } else if (state && (ce->ce_flags & CE_UPDATE)) { + submodule_free(); + checkout_entry(ce, state, NULL); + repo_read_gitmodules(the_repository); } } } @@ -308,19 +303,9 @@ static void unlink_entry(const struct cache_entry *ce) { const struct submodule *sub = submodule_from_ce(ce); if (sub) { - switch (sub->update_strategy.type) { - case SM_UPDATE_UNSPECIFIED: - case SM_UPDATE_CHECKOUT: - case SM_UPDATE_REBASE: - case SM_UPDATE_MERGE: - /* state.force is set at the caller. */ - submodule_move_head(ce->name, "HEAD", NULL, - SUBMODULE_MOVE_HEAD_FORCE); - break; - case SM_UPDATE_NONE: - case SM_UPDATE_COMMAND: - return; /* Do not touch the submodule. */ - } + /* state.force is set at the caller. */ + submodule_move_head(ce->name, "HEAD", NULL, + SUBMODULE_MOVE_HEAD_FORCE); } if (!check_leading_path(ce->name, ce_namelen(ce))) return; @@ -343,8 +328,7 @@ static struct progress *get_progress(struct unpack_trees_options *o) total++; } - return start_progress_delay(_("Checking out files"), - total, 50, 1); + return start_delayed_progress(_("Checking out files"), total); } static int check_updates(struct unpack_trees_options *o) @@ -365,6 +349,10 @@ static int check_updates(struct unpack_trees_options *o) if (o->update) git_attr_set_direction(GIT_ATTR_CHECKOUT, index); + + if (should_update_submodules() && o->update && !o->dry_run) + load_gitmodules_file(index, NULL); + for (i = 0; i < index->cache_nr; i++) { const struct cache_entry *ce = index->cache[i]; @@ -378,7 +366,7 @@ static int check_updates(struct unpack_trees_options *o) remove_scheduled_dirs(); if (should_update_submodules() && o->update && !o->dry_run) - reload_gitmodules_file(index, &state); + load_gitmodules_file(index, &state); enable_delayed_checkout(&state); for (i = 0; i < index->cache_nr; i++) { @@ -662,10 +650,10 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, else if (i > 1 && are_same_oid(&names[i], &names[i - 2])) t[i] = t[i - 2]; else { - const unsigned char *sha1 = NULL; + const struct object_id *oid = NULL; if (dirmask & 1) - sha1 = names[i].oid->hash; - buf[nr_buf++] = fill_tree_descriptor(t+i, sha1); + oid = names[i].oid; + buf[nr_buf++] = fill_tree_descriptor(t + i, oid); } } diff --git a/vcs-svn/fast_export.c b/vcs-svn/fast_export.c index 5a89db30e3..3fd047a8b8 100644 --- a/vcs-svn/fast_export.c +++ b/vcs-svn/fast_export.c @@ -6,7 +6,6 @@ #include "cache.h" #include "quote.h" #include "fast_export.h" -#include "repo_tree.h" #include "strbuf.h" #include "svndiff.h" #include "sliding_window.h" @@ -210,7 +209,7 @@ static long apply_delta(off_t len, struct line_buffer *input, die("invalid cat-blob response: %s", response); check_preimage_overflow(preimage.max_off, 1); } - if (old_mode == REPO_MODE_LNK) { + if (old_mode == S_IFLNK) { strbuf_addstr(&preimage.buf, "link "); check_preimage_overflow(preimage.max_off, strlen("link ")); preimage.max_off += strlen("link "); @@ -244,7 +243,7 @@ void fast_export_buf_to_data(const struct strbuf *data) void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input) { assert(len >= 0); - if (mode == REPO_MODE_LNK) { + if (mode == S_IFLNK) { /* svn symlink blobs start with "link " */ if (len < 5) die("invalid dump: symlink too short for \"link\" prefix"); @@ -312,6 +311,40 @@ int fast_export_ls(const char *path, uint32_t *mode, struct strbuf *dataref) return parse_ls_response(get_response_line(), mode, dataref); } +const char *fast_export_read_path(const char *path, uint32_t *mode_out) +{ + int err; + static struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + err = fast_export_ls(path, mode_out, &buf); + if (err) { + if (errno != ENOENT) + die_errno("BUG: unexpected fast_export_ls error"); + /* Treat missing paths as directories. */ + *mode_out = S_IFDIR; + return NULL; + } + return buf.buf; +} + +void fast_export_copy(uint32_t revision, const char *src, const char *dst) +{ + int err; + uint32_t mode; + static struct strbuf data = STRBUF_INIT; + + strbuf_reset(&data); + err = fast_export_ls_rev(revision, src, &mode, &data); + if (err) { + if (errno != ENOENT) + die_errno("BUG: unexpected fast_export_ls_rev error"); + fast_export_delete(dst); + return; + } + fast_export_modify(dst, mode, data.buf); +} + void fast_export_blob_delta(uint32_t mode, uint32_t old_mode, const char *old_data, off_t len, struct line_buffer *input) @@ -320,7 +353,7 @@ void fast_export_blob_delta(uint32_t mode, assert(len >= 0); postimage_len = apply_delta(len, input, old_data, old_mode); - if (mode == REPO_MODE_LNK) { + if (mode == S_IFLNK) { buffer_skip_bytes(&postimage, strlen("link ")); postimage_len -= strlen("link "); } diff --git a/vcs-svn/fast_export.h b/vcs-svn/fast_export.h index b9a3b71c99..60b79c35b9 100644 --- a/vcs-svn/fast_export.h +++ b/vcs-svn/fast_export.h @@ -28,4 +28,7 @@ int fast_export_ls_rev(uint32_t rev, const char *path, int fast_export_ls(const char *path, uint32_t *mode_out, struct strbuf *dataref_out); +void fast_export_copy(uint32_t revision, const char *src, const char *dst); +const char *fast_export_read_path(const char *path, uint32_t *mode_out); + #endif diff --git a/vcs-svn/repo_tree.c b/vcs-svn/repo_tree.c deleted file mode 100644 index 67d27f0b6c..0000000000 --- a/vcs-svn/repo_tree.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed under a two-clause BSD-style license. - * See LICENSE for details. - */ - -#include "git-compat-util.h" -#include "strbuf.h" -#include "repo_tree.h" -#include "fast_export.h" - -const char *repo_read_path(const char *path, uint32_t *mode_out) -{ - int err; - static struct strbuf buf = STRBUF_INIT; - - strbuf_reset(&buf); - err = fast_export_ls(path, mode_out, &buf); - if (err) { - if (errno != ENOENT) - die_errno("BUG: unexpected fast_export_ls error"); - /* Treat missing paths as directories. */ - *mode_out = REPO_MODE_DIR; - return NULL; - } - return buf.buf; -} - -void repo_copy(uint32_t revision, const char *src, const char *dst) -{ - int err; - uint32_t mode; - static struct strbuf data = STRBUF_INIT; - - strbuf_reset(&data); - err = fast_export_ls_rev(revision, src, &mode, &data); - if (err) { - if (errno != ENOENT) - die_errno("BUG: unexpected fast_export_ls_rev error"); - fast_export_delete(dst); - return; - } - fast_export_modify(dst, mode, data.buf); -} - -void repo_delete(const char *path) -{ - fast_export_delete(path); -} diff --git a/vcs-svn/repo_tree.h b/vcs-svn/repo_tree.h deleted file mode 100644 index 889c6a3c95..0000000000 --- a/vcs-svn/repo_tree.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef REPO_TREE_H_ -#define REPO_TREE_H_ - -struct strbuf; - -#define REPO_MODE_DIR 0040000 -#define REPO_MODE_BLB 0100644 -#define REPO_MODE_EXE 0100755 -#define REPO_MODE_LNK 0120000 - -uint32_t next_blob_mark(void); -void repo_copy(uint32_t revision, const char *src, const char *dst); -void repo_add(const char *path, uint32_t mode, uint32_t blob_mark); -const char *repo_read_path(const char *path, uint32_t *mode_out); -void repo_delete(const char *path); -void repo_commit(uint32_t revision, const char *author, - const struct strbuf *log, const char *uuid, const char *url, - long unsigned timestamp); -void repo_diff(uint32_t r1, uint32_t r2); -void repo_init(void); -void repo_reset(void); - -#endif diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index 1846685a21..ec6b350611 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -8,7 +8,6 @@ */ #include "cache.h" -#include "repo_tree.h" #include "fast_export.h" #include "line_buffer.h" #include "strbuf.h" @@ -134,13 +133,13 @@ static void handle_property(const struct strbuf *key_buf, die("invalid dump: sets type twice"); } if (!val) { - node_ctx.type = REPO_MODE_BLB; + node_ctx.type = S_IFREG | 0644; return; } *type_set = 1; node_ctx.type = keylen == strlen("svn:executable") ? - REPO_MODE_EXE : - REPO_MODE_LNK; + (S_IFREG | 0755) : + S_IFLNK; } } @@ -219,45 +218,45 @@ static void handle_node(void) */ static const char *const empty_blob = "::empty::"; const char *old_data = NULL; - uint32_t old_mode = REPO_MODE_BLB; + uint32_t old_mode = S_IFREG | 0644; if (node_ctx.action == NODEACT_DELETE) { if (have_text || have_props || node_ctx.srcRev) die("invalid dump: deletion node has " "copyfrom info, text, or properties"); - repo_delete(node_ctx.dst.buf); + fast_export_delete(node_ctx.dst.buf); return; } if (node_ctx.action == NODEACT_REPLACE) { - repo_delete(node_ctx.dst.buf); + fast_export_delete(node_ctx.dst.buf); node_ctx.action = NODEACT_ADD; } if (node_ctx.srcRev) { - repo_copy(node_ctx.srcRev, node_ctx.src.buf, node_ctx.dst.buf); + fast_export_copy(node_ctx.srcRev, node_ctx.src.buf, node_ctx.dst.buf); if (node_ctx.action == NODEACT_ADD) node_ctx.action = NODEACT_CHANGE; } - if (have_text && type == REPO_MODE_DIR) + if (have_text && type == S_IFDIR) die("invalid dump: directories cannot have text attached"); /* * Find old content (old_data) and decide on the new mode. */ if (node_ctx.action == NODEACT_CHANGE && !*node_ctx.dst.buf) { - if (type != REPO_MODE_DIR) + if (type != S_IFDIR) die("invalid dump: root of tree is not a regular file"); old_data = NULL; } else if (node_ctx.action == NODEACT_CHANGE) { uint32_t mode; - old_data = repo_read_path(node_ctx.dst.buf, &mode); - if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR) + old_data = fast_export_read_path(node_ctx.dst.buf, &mode); + if (mode == S_IFDIR && type != S_IFDIR) die("invalid dump: cannot modify a directory into a file"); - if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR) + if (mode != S_IFDIR && type == S_IFDIR) die("invalid dump: cannot modify a file into a directory"); node_ctx.type = mode; old_mode = mode; } else if (node_ctx.action == NODEACT_ADD) { - if (type == REPO_MODE_DIR) + if (type == S_IFDIR) old_data = NULL; else if (have_text) old_data = empty_blob; @@ -280,7 +279,7 @@ static void handle_node(void) /* * Save the result. */ - if (type == REPO_MODE_DIR) /* directories are not tracked. */ + if (type == S_IFDIR) /* directories are not tracked. */ return; assert(old_data); if (old_data == empty_blob) @@ -385,9 +384,9 @@ void svndump_read(const char *url, const char *local_ref, const char *notes_ref) continue; strbuf_addf(&rev_ctx.note, "%s\n", t); if (!strcmp(val, "dir")) - node_ctx.type = REPO_MODE_DIR; + node_ctx.type = S_IFDIR; else if (!strcmp(val, "file")) - node_ctx.type = REPO_MODE_BLB; + node_ctx.type = S_IFREG | 0644; else fprintf(stderr, "Unknown node-kind: %s\n", val); break; |