From d4704b04f1fc048ee3fa13c50366dd71d5744934 Mon Sep 17 00:00:00 2001 From: Jochen Sprickerhof Date: Tue, 28 Aug 2018 10:58:58 +0200 Subject: [PATCH 0001/1015] add -p: coalesce hunks before testing applicability When a hunk was split before being edited manually, it does not apply anymore cleanly. Apply coalesce_overlapping_hunks() first to make it work. Enable test for it as well. Signed-off-by: Jochen Sprickerhof Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 8 ++++---- t/t3701-add-interactive.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index c1f52e457f6c5b..f96daa294cb350 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1188,10 +1188,10 @@ sub edit_hunk_loop { # delta from the original unedited hunk. $hunk->{OFS_DELTA} and $newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA}; - if (diff_applies($head, - @{$hunks}[0..$ix-1], - $newhunk, - @{$hunks}[$ix+1..$#{$hunks}])) { + my @hunk = @{$hunks}; + splice (@hunk, $ix, 1, $newhunk); + @hunk = coalesce_overlapping_hunks(@hunk); + if (diff_applies($head, @hunk)) { $newhunk->{DISPLAY} = [color_diff(@{$newtext})]; return $newhunk; } diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b170fb02b80356..b04806ad7009ea 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -348,7 +348,7 @@ test_expect_success 'split hunk "add -p (edit)"' ' ! grep "^+15" actual ' -test_expect_failure 'split hunk "add -p (no, yes, edit)"' ' +test_expect_success 'split hunk "add -p (no, yes, edit)"' ' test_write_lines 5 10 20 21 30 31 40 50 60 >test && git reset && # test sequence is s(plit), n(o), y(es), e(dit) From 5efde212fc85bd22813d9a4dfca61116adb5d5c0 Mon Sep 17 00:00:00 2001 From: Martin Koegler Date: Sun, 14 Oct 2018 03:16:36 +0100 Subject: [PATCH 0002/1015] zlib.c: use size_t for size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Koegler Signed-off-by: Junio C Hamano Signed-off-by: Torsten Bögershausen Helped-by: SZEDER Gábor Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- builtin/pack-objects.c | 11 ++++++----- cache.h | 10 +++++----- pack-check.c | 4 ++-- packfile.c | 4 ++-- packfile.h | 2 +- wrapper.c | 8 ++++---- zlib.c | 8 ++++---- 7 files changed, 24 insertions(+), 23 deletions(-) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index e6316d294dca1a..89fe1c5d460efe 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -269,12 +269,12 @@ static void copy_pack_data(struct hashfile *f, off_t len) { unsigned char *in; - unsigned long avail; + size_t avail; while (len) { in = use_pack(p, w_curs, offset, &avail); if (avail > len) - avail = (unsigned long)len; + avail = xsize_t(len); hashwrite(f, in, avail); offset += avail; len -= avail; @@ -1478,8 +1478,8 @@ static void check_object(struct object_entry *entry) struct pack_window *w_curs = NULL; const unsigned char *base_ref = NULL; struct object_entry *base_entry; - unsigned long used, used_0; - unsigned long avail; + size_t used, used_0; + size_t avail; off_t ofs; unsigned char *buf, c; enum object_type type; @@ -1966,7 +1966,8 @@ unsigned long oe_get_size_slow(struct packing_data *pack, struct pack_window *w_curs; unsigned char *buf; enum object_type type; - unsigned long used, avail, size; + unsigned long used, size; + size_t avail; if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { read_lock(); diff --git a/cache.h b/cache.h index d508f3d4f8837c..fce53fe620cc7c 100644 --- a/cache.h +++ b/cache.h @@ -20,10 +20,10 @@ #include typedef struct git_zstream { z_stream z; - unsigned long avail_in; - unsigned long avail_out; - unsigned long total_in; - unsigned long total_out; + size_t avail_in; + size_t avail_out; + size_t total_in; + size_t total_out; unsigned char *next_in; unsigned char *next_out; } git_zstream; @@ -40,7 +40,7 @@ void git_deflate_end(git_zstream *); int git_deflate_abort(git_zstream *); int git_deflate_end_gently(git_zstream *); int git_deflate(git_zstream *, int flush); -unsigned long git_deflate_bound(git_zstream *, unsigned long); +size_t git_deflate_bound(git_zstream *, size_t); /* The length in bytes and in hex digits of an object name (SHA-1 value). */ #define GIT_SHA1_RAWSZ 20 diff --git a/pack-check.c b/pack-check.c index fa5f0ff8fa5746..d1e7f554aef4a9 100644 --- a/pack-check.c +++ b/pack-check.c @@ -33,7 +33,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, uint32_t data_crc = crc32(0, NULL, 0); do { - unsigned long avail; + size_t avail; void *data = use_pack(p, w_curs, offset, &avail); if (avail > len) avail = len; @@ -68,7 +68,7 @@ static int verify_packfile(struct packed_git *p, the_hash_algo->init_fn(&ctx); do { - unsigned long remaining; + size_t remaining; unsigned char *in = use_pack(p, w_curs, offset, &remaining); offset += remaining; if (!pack_sig_ofs) diff --git a/packfile.c b/packfile.c index 841b36182fcd93..9f50411ad3555e 100644 --- a/packfile.c +++ b/packfile.c @@ -579,7 +579,7 @@ static int in_window(struct pack_window *win, off_t offset) unsigned char *use_pack(struct packed_git *p, struct pack_window **w_cursor, off_t offset, - unsigned long *left) + size_t *left) { struct pack_window *win = *w_cursor; @@ -1098,7 +1098,7 @@ int unpack_object_header(struct packed_git *p, unsigned long *sizep) { unsigned char *base; - unsigned long left; + size_t left; unsigned long used; enum object_type type; diff --git a/packfile.h b/packfile.h index 442625723dea4b..e2daf6342654c3 100644 --- a/packfile.h +++ b/packfile.h @@ -78,7 +78,7 @@ extern void close_pack_index(struct packed_git *); extern uint32_t get_pack_fanout(struct packed_git *p, uint32_t value); -extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); +extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, size_t *); extern void close_pack_windows(struct packed_git *); extern void close_pack(struct packed_git *); extern void close_all_packs(struct raw_object_store *o); diff --git a/wrapper.c b/wrapper.c index e4fa9d84cd0767..1a510bd6fc32b5 100644 --- a/wrapper.c +++ b/wrapper.c @@ -67,11 +67,11 @@ static void *do_xmalloc(size_t size, int gentle) ret = malloc(1); if (!ret) { if (!gentle) - die("Out of memory, malloc failed (tried to allocate %lu bytes)", - (unsigned long)size); + die("Out of memory, malloc failed (tried to allocate %" PRIuMAX " bytes)", + (uintmax_t)size); else { - error("Out of memory, malloc failed (tried to allocate %lu bytes)", - (unsigned long)size); + error("Out of memory, malloc failed (tried to allocate %" PRIuMAX " bytes)", + (uintmax_t)size); return NULL; } } diff --git a/zlib.c b/zlib.c index d594cba3fc9d82..197a1acc7b0db9 100644 --- a/zlib.c +++ b/zlib.c @@ -29,7 +29,7 @@ static const char *zerr_to_string(int status) */ /* #define ZLIB_BUF_MAX ((uInt)-1) */ #define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */ -static inline uInt zlib_buf_cap(unsigned long len) +static inline uInt zlib_buf_cap(size_t len) { return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; } @@ -46,8 +46,8 @@ static void zlib_pre_call(git_zstream *s) static void zlib_post_call(git_zstream *s) { - unsigned long bytes_consumed; - unsigned long bytes_produced; + size_t bytes_consumed; + size_t bytes_produced; bytes_consumed = s->z.next_in - s->next_in; bytes_produced = s->z.next_out - s->next_out; @@ -150,7 +150,7 @@ int git_inflate(git_zstream *strm, int flush) #define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) #endif -unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) +size_t git_deflate_bound(git_zstream *strm, size_t size) { return deflateBound(&strm->z, size); } From 0ecb1fc7269e15be890870937b8b639c987abd08 Mon Sep 17 00:00:00 2001 From: Daniels Umanovskis Date: Thu, 25 Oct 2018 21:04:21 +0200 Subject: [PATCH 0003/1015] branch: introduce --show-current display option When called with --show-current, git branch will print the current branch name and terminate. Only the actual name gets printed, without refs/heads. In detached HEAD state, nothing is output. Intended both for scripting and interactive/informative use. Unlike git branch --list, no filtering is needed to just get the branch name. Signed-off-by: Daniels Umanovskis Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 6 ++++- builtin/branch.c | 25 ++++++++++++++++++-- t/t3203-branch-output.sh | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index bf5316ffa929a8..0babb9b1be4db9 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git branch' [--color[=] | --no-color] [-r | -a] - [--list] [-v [--abbrev= | --no-abbrev]] + [--list] [--show-current] [-v [--abbrev= | --no-abbrev]] [--column[=] | --no-column] [--sort=] [(--merged | --no-merged) []] [--contains []] @@ -160,6 +160,10 @@ This option is only applicable in non-verbose mode. branch --list 'maint-*'`, list only the branches that match the pattern(s). +--show-current:: + Print the name of the current branch. In detached HEAD state, + nothing is printed. + -v:: -vv:: --verbose:: diff --git a/builtin/branch.c b/builtin/branch.c index c396c41533c386..46f91dc06dc64c 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -443,6 +443,21 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin free(to_free); } +static void print_current_branch_name(void) +{ + int flags; + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags); + const char *shortname; + if (!refname) + die(_("could not resolve HEAD")); + else if (!(flags & REF_ISSYMREF)) + return; + else if (skip_prefix(refname, "refs/heads/", &shortname)) + puts(shortname); + else + die(_("HEAD (%s) points outside of refs/heads/"), refname); +} + static void reject_rebase_or_bisect_branch(const char *target) { struct worktree **worktrees = get_worktrees(0); @@ -581,6 +596,7 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, copy = 0, force = 0, list = 0; + int show_current = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; @@ -620,6 +636,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1), OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2), OPT_BOOL('l', "list", &list, N_("list branch names")), + OPT_BOOL(0, "show-current", &show_current, N_("show current branch name")), OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")), OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), @@ -662,14 +679,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0) + if (!delete && !rename && !copy && !edit_description && !new_upstream && + !show_current && !unset_upstream && argc == 0) list = 1; if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || filter.no_commit) list = 1; - if (!!delete + !!rename + !!copy + !!new_upstream + + if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -697,6 +715,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!argc) die(_("branch name required")); return delete_branches(argc, argv, delete > 1, filter.kind, quiet); + } else if (show_current) { + print_current_branch_name(); + return 0; } else if (list) { /* git branch --local also shows HEAD when it is detached */ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index ee6787614c80ed..be551489303031 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -100,6 +100,50 @@ test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* ' +test_expect_success 'git branch `--show-current` shows current branch' ' + cat >expect <<-\EOF && + branch-two + EOF + git checkout branch-two && + git branch --show-current >actual && + test_cmp expect actual +' + +test_expect_success 'git branch `--show-current` is silent when detached HEAD' ' + git checkout HEAD^0 && + git branch --show-current >actual && + test_must_be_empty actual +' + +test_expect_success 'git branch `--show-current` works properly when tag exists' ' + cat >expect <<-\EOF && + branch-and-tag-name + EOF + test_when_finished " + git checkout branch-one + git branch -D branch-and-tag-name + " && + git checkout -b branch-and-tag-name && + test_when_finished "git tag -d branch-and-tag-name" && + git tag branch-and-tag-name && + git branch --show-current >actual && + test_cmp expect actual +' + +test_expect_success 'git branch `--show-current` works properly with worktrees' ' + cat >expect <<-\EOF && + branch-one + branch-two + EOF + git checkout branch-one && + git worktree add worktree branch-two && + { + git branch --show-current && + git -C worktree branch --show-current + } >actual && + test_cmp expect actual +' + test_expect_success 'git branch shows detached HEAD properly' ' cat >expect < Date: Mon, 19 Nov 2018 22:12:51 -0800 Subject: [PATCH 0004/1015] index: do not warn about unrecognized extensions Documentation/technical/index-format explains: 4-byte extension signature. If the first byte is 'A'..'Z' the extension is optional and can be ignored. This allows gracefully introducing a new index extension without having to rely on all readers having support for it. Mandatory extensions start with a lowercase letter and optional ones start with a capital. Thus the versions of Git acting on a shared local repository do not have to upgrade in lockstep. We almost obey that convention, but there is a problem: when encountering an unrecognized optional extension, we write ignoring FNCY extension to stderr, which alarms users. This means that in practice we have had to introduce index extensions in two steps: first add read support, and then a while later, start writing by default. This delays when users can benefit from improvements to the index format. We cannot change the past, but for index extensions of the future, there is a straightforward improvement: silence that message except when tracing. This way, the message is still available when debugging, but in everyday use it does not show up so (once most Git users have this patch) we can turn on new optional extensions right away without alarming people. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- read-cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/read-cache.c b/read-cache.c index 42de59a163c1f8..002ed2c1e44a56 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1726,7 +1726,7 @@ static int read_index_extension(struct index_state *istate, if (*ext < 'A' || 'Z' < *ext) return error("index uses %.4s extension, which we do not understand", ext); - fprintf(stderr, "ignoring %.4s extension\n", ext); + trace_printf("ignoring %.4s extension\n", ext); break; } return 0; From ee70c128203d743b5ef5c72bb089ec0ecf149d73 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 19 Nov 2018 22:15:44 -0800 Subject: [PATCH 0005/1015] index: offer advice for unknown index extensions It is not unusual for multiple distinct versions of Git to act on a single repository. For example, some IDEs bundle a particular version of Git, which can be a different version from the system copy of Git, or on a Mac, /usr/bin/git quickly goes out of sync with the Homebrew git in /usr/local/bin/git. When a newer version of Git writes an index file that an older version of Git does not know how to make full use of, this is a teaching opportunity. The user may not be aware of what version of Git they are using. Print an advice message to help the user to use the most full featured version of Git (e.g. by futzing with their PATH). warning: ignoring optional IEOT index extension hint: This is likely due to the file having been written by a newer hint: version of Git than is reading it. You can upgrade Git to hint: take advantage of performance improvements from the updated hint: file format. hint: hint: You can run "git config advice.unknownIndexExtension false" hint: to suppress this message. This replaces the message ignoring IEOT extension that existed previously and did not provide enough detail for a user to act on it or suppress it. Helped-by: Junio C Hamano Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- advice.c | 2 ++ advice.h | 1 + read-cache.c | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/advice.c b/advice.c index 5f35656409b1d5..91a55046fdeb3b 100644 --- a/advice.c +++ b/advice.c @@ -24,6 +24,7 @@ int advice_add_embedded_repo = 1; int advice_ignored_hook = 1; int advice_waiting_for_editor = 1; int advice_graft_file_deprecated = 1; +int advice_unknown_index_extension = 1; int advice_checkout_ambiguous_remote_branch_name = 1; static int advice_use_color = -1; @@ -78,6 +79,7 @@ static struct { { "ignoredHook", &advice_ignored_hook }, { "waitingForEditor", &advice_waiting_for_editor }, { "graftFileDeprecated", &advice_graft_file_deprecated }, + { "unknownIndexExtension", &advice_unknown_index_extension }, { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name }, /* make this an alias for backward compatibility */ diff --git a/advice.h b/advice.h index 696bf0e7d29ee1..8da0845cfc86e2 100644 --- a/advice.h +++ b/advice.h @@ -24,6 +24,7 @@ extern int advice_add_embedded_repo; extern int advice_ignored_hook; extern int advice_waiting_for_editor; extern int advice_graft_file_deprecated; +extern int advice_unknown_index_extension; extern int advice_checkout_ambiguous_remote_branch_name; int git_default_advice_config(const char *var, const char *value); diff --git a/read-cache.c b/read-cache.c index 002ed2c1e44a56..d1d903e5a127b2 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1727,6 +1727,17 @@ static int read_index_extension(struct index_state *istate, return error("index uses %.4s extension, which we do not understand", ext); trace_printf("ignoring %.4s extension\n", ext); + if (advice_unknown_index_extension) { + warning(_("ignoring optional %.4s index extension"), ext); + advise(_("This is likely due to the file having been written by a newer\n" + "version of Git than is reading it. You can upgrade Git to\n" + "take advantage of performance improvements from the updated\n" + "file format.\n" + "\n" + "Run \"%s\"\n" + "to suppress this message."), + "git config advice.unknownIndexExtension false"); + } break; } return 0; From 42617752d4b22d616e276528ba4d155e6fff1835 Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Sat, 8 Dec 2018 17:36:41 +0100 Subject: [PATCH 0006/1015] doc: group pretty-format.txt placeholders descriptions The placeholders can be grouped into three kinds: * literals * affecting formatting of later placeholders * expanding to information in commit Also change the list to a definition list (using '::') Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 235 ++++++++++++++++--------------- 1 file changed, 125 insertions(+), 110 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 417b638cd803e6..8740b43581abdd 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -102,118 +102,133 @@ The title was >>t4119: test autocomputing -p for traditional diff input.<< + The placeholders are: -- '%H': commit hash -- '%h': abbreviated commit hash -- '%T': tree hash -- '%t': abbreviated tree hash -- '%P': parent hashes -- '%p': abbreviated parent hashes -- '%an': author name -- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] - or linkgit:git-blame[1]) -- '%ae': author email -- '%aE': author email (respecting .mailmap, see - linkgit:git-shortlog[1] or linkgit:git-blame[1]) -- '%ad': author date (format respects --date= option) -- '%aD': author date, RFC2822 style -- '%ar': author date, relative -- '%at': author date, UNIX timestamp -- '%ai': author date, ISO 8601-like format -- '%aI': author date, strict ISO 8601 format -- '%cn': committer name -- '%cN': committer name (respecting .mailmap, see - linkgit:git-shortlog[1] or linkgit:git-blame[1]) -- '%ce': committer email -- '%cE': committer email (respecting .mailmap, see - linkgit:git-shortlog[1] or linkgit:git-blame[1]) -- '%cd': committer date (format respects --date= option) -- '%cD': committer date, RFC2822 style -- '%cr': committer date, relative -- '%ct': committer date, UNIX timestamp -- '%ci': committer date, ISO 8601-like format -- '%cI': committer date, strict ISO 8601 format -- '%d': ref names, like the --decorate option of linkgit:git-log[1] -- '%D': ref names without the " (", ")" wrapping. -- '%e': encoding -- '%s': subject -- '%f': sanitized subject line, suitable for a filename -- '%b': body -- '%B': raw body (unwrapped subject and body) +- Placeholders that expand to a single literal character: +'%n':: newline +'%%':: a raw '%' +'%x00':: print a byte from a hex code + +- Placeholders that affect formatting of later placeholders: +'%Cred':: switch color to red +'%Cgreen':: switch color to green +'%Cblue':: switch color to blue +'%Creset':: reset color +'%C(...)':: color specification, as described under Values in the + "CONFIGURATION FILE" section of linkgit:git-config[1]. By + default, colors are shown only when enabled for log output + (by `color.diff`, `color.ui`, or `--color`, and respecting + the `auto` settings of the former if we are going to a + terminal). `%C(auto,...)` is accepted as a historical + synonym for the default (e.g., `%C(auto,red)`). Specifying + `%C(always,...) will show the colors even when color is + not otherwise enabled (though consider just using + `--color=always` to enable color for the whole output, + including this format and anything else git might color). + `auto` alone (i.e. `%C(auto)`) will turn on auto coloring + on the next placeholders until the color is switched + again. +'%m':: left (`<`), right (`>`) or boundary (`-`) mark +'%w([[,[,]]])':: switch line wrapping, like the -w option of + linkgit:git-shortlog[1]. +'%<([,trunc|ltrunc|mtrunc])':: make the next placeholder take at + least N columns, padding spaces on + the right if necessary. Optionally + truncate at the beginning (ltrunc), + the middle (mtrunc) or the end + (trunc) if the output is longer than + N columns. Note that truncating + only works correctly with N >= 2. +'%<|()':: make the next placeholder take at least until Nth + columns, padding spaces on the right if necessary +'%>()', '%>|()':: similar to '%<()', '%<|()' respectively, + but padding spaces on the left +'%>>()', '%>>|()':: similar to '%>()', '%>|()' + respectively, except that if the next + placeholder takes more spaces than given and + there are spaces on its left, use those + spaces +'%><()', '%><|()':: similar to '%<()', '%<|()' + respectively, but padding both sides + (i.e. the text is centered) + +- Placeholders that expand to information extracted from the commit: +'%H':: commit hash +'%h':: abbreviated commit hash +'%T':: tree hash +'%t':: abbreviated tree hash +'%P':: parent hashes +'%p':: abbreviated parent hashes +'%an':: author name +'%aN':: author name (respecting .mailmap, see linkgit:git-shortlog[1] + or linkgit:git-blame[1]) +'%ae':: author email +'%aE':: author email (respecting .mailmap, see linkgit:git-shortlog[1] + or linkgit:git-blame[1]) +'%ad':: author date (format respects --date= option) +'%aD':: author date, RFC2822 style +'%ar':: author date, relative +'%at':: author date, UNIX timestamp +'%ai':: author date, ISO 8601-like format +'%aI':: author date, strict ISO 8601 format +'%cn':: committer name +'%cN':: committer name (respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%ce':: committer email +'%cE':: committer email (respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%cd':: committer date (format respects --date= option) +'%cD':: committer date, RFC2822 style +'%cr':: committer date, relative +'%ct':: committer date, UNIX timestamp +'%ci':: committer date, ISO 8601-like format +'%cI':: committer date, strict ISO 8601 format +'%d':: ref names, like the --decorate option of linkgit:git-log[1] +'%D':: ref names without the " (", ")" wrapping. +'%e':: encoding +'%s':: subject +'%f':: sanitized subject line, suitable for a filename +'%b':: body +'%B':: raw body (unwrapped subject and body) ifndef::git-rev-list[] -- '%N': commit notes +'%N':: commit notes endif::git-rev-list[] -- '%GG': raw verification message from GPG for a signed commit -- '%G?': show "G" for a good (valid) signature, - "B" for a bad signature, - "U" for a good signature with unknown validity, - "X" for a good signature that has expired, - "Y" for a good signature made by an expired key, - "R" for a good signature made by a revoked key, - "E" if the signature cannot be checked (e.g. missing key) - and "N" for no signature -- '%GS': show the name of the signer for a signed commit -- '%GK': show the key used to sign a signed commit -- '%GF': show the fingerprint of the key used to sign a signed commit -- '%GP': show the fingerprint of the primary key whose subkey was used - to sign a signed commit -- '%gD': reflog selector, e.g., `refs/stash@{1}` or - `refs/stash@{2 minutes ago`}; the format follows the rules described - for the `-g` option. The portion before the `@` is the refname as - given on the command line (so `git log -g refs/heads/master` would - yield `refs/heads/master@{0}`). -- '%gd': shortened reflog selector; same as `%gD`, but the refname - portion is shortened for human readability (so `refs/heads/master` - becomes just `master`). -- '%gn': reflog identity name -- '%gN': reflog identity name (respecting .mailmap, see - linkgit:git-shortlog[1] or linkgit:git-blame[1]) -- '%ge': reflog identity email -- '%gE': reflog identity email (respecting .mailmap, see - linkgit:git-shortlog[1] or linkgit:git-blame[1]) -- '%gs': reflog subject -- '%Cred': switch color to red -- '%Cgreen': switch color to green -- '%Cblue': switch color to blue -- '%Creset': reset color -- '%C(...)': color specification, as described under Values in the - "CONFIGURATION FILE" section of linkgit:git-config[1]. - By default, colors are shown only when enabled for log output (by - `color.diff`, `color.ui`, or `--color`, and respecting the `auto` - settings of the former if we are going to a terminal). `%C(auto,...)` - is accepted as a historical synonym for the default (e.g., - `%C(auto,red)`). Specifying `%C(always,...) will show the colors - even when color is not otherwise enabled (though consider - just using `--color=always` to enable color for the whole output, - including this format and anything else git might color). `auto` - alone (i.e. `%C(auto)`) will turn on auto coloring on the next - placeholders until the color is switched again. -- '%m': left (`<`), right (`>`) or boundary (`-`) mark -- '%n': newline -- '%%': a raw '%' -- '%x00': print a byte from a hex code -- '%w([[,[,]]])': switch line wrapping, like the -w option of - linkgit:git-shortlog[1]. -- '%<([,trunc|ltrunc|mtrunc])': make the next placeholder take at - least N columns, padding spaces on the right if necessary. - Optionally truncate at the beginning (ltrunc), the middle (mtrunc) - or the end (trunc) if the output is longer than N columns. - Note that truncating only works correctly with N >= 2. -- '%<|()': make the next placeholder take at least until Nth - columns, padding spaces on the right if necessary -- '%>()', '%>|()': similar to '%<()', '%<|()' - respectively, but padding spaces on the left -- '%>>()', '%>>|()': similar to '%>()', '%>|()' - respectively, except that if the next placeholder takes more spaces - than given and there are spaces on its left, use those spaces -- '%><()', '%><|()': similar to '%<()', '%<|()' - respectively, but padding both sides (i.e. the text is centered) -- %(trailers[:options]): display the trailers of the body as interpreted - by linkgit:git-interpret-trailers[1]. The `trailers` string may be - followed by a colon and zero or more comma-separated options. 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. +'%GG':: raw verification message from GPG for a signed commit +'%G?':: show "G" for a good (valid) signature, + "B" for a bad signature, + "U" for a good signature with unknown validity, + "X" for a good signature that has expired, + "Y" for a good signature made by an expired key, + "R" for a good signature made by a revoked key, + "E" if the signature cannot be checked (e.g. missing key) + and "N" for no signature +'%GS':: show the name of the signer for a signed commit +'%GK':: show the key used to sign a signed commit +'%GF':: show the fingerprint of the key used to sign a signed commit +'%GP':: show the fingerprint of the primary key whose subkey was used + to sign a signed commit +'%gD':: reflog selector, e.g., `refs/stash@{1}` or `refs/stash@{2 + minutes ago`}; the format follows the rules described for the + `-g` option. The portion before the `@` is the refname as + given on the command line (so `git log -g refs/heads/master` + would yield `refs/heads/master@{0}`). +'%gd':: shortened reflog selector; same as `%gD`, but the refname + portion is shortened for human readability (so + `refs/heads/master` becomes just `master`). +'%gn':: reflog identity name +'%gN':: reflog identity name (respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%ge':: reflog identity email +'%gE':: reflog identity email (respecting .mailmap, see + linkgit:git-shortlog[1] or linkgit:git-blame[1]) +'%gs':: reflog subject +'%(trailers[:options])':: display the trailers of the body as + interpreted by + linkgit:git-interpret-trailers[1]. The + `trailers` string may be followed by a colon + and zero or more comma-separated options: +** 'only': omit non-trailer lines from the trailer block. +** 'unfold': make it behave as if interpret-trailer's `--unfold` + option was given. E.g., `%(trailers:only,unfold)` unfolds and + shows all trailer lines. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will From f39a9c6547ad92c5e10735368d59ac357e6a0a94 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Mon, 10 Dec 2018 09:15:10 -0500 Subject: [PATCH 0007/1015] remote: add --save-to-push option to git remote set-url This adds the --save-to-push option to `git remote set-url` such that when executed, we move the remote.*.url to remote.*.pushurl and set remote.*.url to the given url argument. For example, if we have the following config: [remote "origin"] url = git@github.com:git/git.git `git remote set-url --save-to-push origin https://github.com/git/git.git` would change the config to the following: [remote "origin"] url = https://github.com/git/git.git pushurl = git@github.com:git/git.git Helped-by: Junio C Hamano Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 12 ++++++++++++ builtin/remote.c | 26 +++++++++++++++++++++----- t/t5505-remote.sh | 11 +++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 0cad37fb81d99c..47aaae22c1d56d 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -19,6 +19,7 @@ SYNOPSIS 'git remote set-url' [--push] [] 'git remote set-url --add' [--push] 'git remote set-url --delete' [--push] +'git remote set-url --save-to-push' 'git remote' [-v | --verbose] 'show' [-n] ... 'git remote prune' [-n | --dry-run] ... 'git remote' [-v | --verbose] 'update' [-p | --prune] [( | )...] @@ -155,6 +156,17 @@ With `--delete`, instead of changing existing URLs, all URLs matching regex are deleted for remote . Trying to delete all non-push URLs is an error. + +With `--save-to-push`, the current URL is saved into the push URL before +setting the URL to . Note that this command will not work if more than one +URL is defined because the behavior would be ambiguous. A use-case for this +feature is that you may have started your interaction with the repository with +a single authenticated URL that can be used for both fetching and pushing, but +over time you may have become sick of having to authenticate only to fetch. In +such a case, you can feed an unauthenticated/anonymous fetch URL to set-url +with this option, so that the authenticated URL that you have been using for +pushing becomes the pushURL, and the new, unauthenticated/anonymous URL will be +used for fetching. ++ Note that the push URL and the fetch URL, even though they can be set differently, must still refer to the same place. What you pushed to the push URL should be what you would see if you diff --git a/builtin/remote.c b/builtin/remote.c index f7edf7f2cb1f58..d683e67ba6fe41 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -24,8 +24,9 @@ static const char * const builtin_remote_usage[] = { N_("git remote set-branches [--add] ..."), N_("git remote get-url [--push] [--all] "), N_("git remote set-url [--push] []"), - N_("git remote set-url --add "), - N_("git remote set-url --delete "), + N_("git remote set-url --add [--push] "), + N_("git remote set-url --delete [--push] "), + N_("git remote set-url --save-to-push "), NULL }; @@ -77,8 +78,9 @@ static const char * const builtin_remote_geturl_usage[] = { static const char * const builtin_remote_seturl_usage[] = { N_("git remote set-url [--push] []"), - N_("git remote set-url --add "), - N_("git remote set-url --delete "), + N_("git remote set-url --add [--push] "), + N_("git remote set-url --delete [--push] "), + N_("git remote set-url --save-to-push "), NULL }; @@ -1519,7 +1521,7 @@ static int get_url(int argc, const char **argv) static int set_url(int argc, const char **argv) { - int i, push_mode = 0, add_mode = 0, delete_mode = 0; + int i, push_mode = 0, save_to_push = 0, add_mode = 0, delete_mode = 0; int matches = 0, negative_matches = 0; const char *remotename = NULL; const char *newurl = NULL; @@ -1532,6 +1534,8 @@ static int set_url(int argc, const char **argv) struct option options[] = { OPT_BOOL('\0', "push", &push_mode, N_("manipulate push URLs")), + OPT_BOOL('\0', "save-to-push", &save_to_push, + N_("change fetching URL behavior")), OPT_BOOL('\0', "add", &add_mode, N_("add URL")), OPT_BOOL('\0', "delete", &delete_mode, @@ -1543,6 +1547,8 @@ static int set_url(int argc, const char **argv) if (add_mode && delete_mode) die(_("--add --delete doesn't make sense")); + if (save_to_push && (push_mode || add_mode || delete_mode)) + die(_("--save-to-push cannot be used with other options")); if (argc < 3 || argc > 4 || ((add_mode || delete_mode) && argc != 3)) usage_with_options(builtin_remote_seturl_usage, options); @@ -1564,6 +1570,16 @@ static int set_url(int argc, const char **argv) urlset = remote->pushurl; urlset_nr = remote->pushurl_nr; } else { + if (save_to_push) { + if (remote->url_nr != 1) + die(_("--save-to-push can only be used when only one url is defined")); + + strbuf_addf(&name_buf, "remote.%s.pushurl", remotename); + git_config_set_multivar(name_buf.buf, + remote->url[0], "^$", 0); + strbuf_reset(&name_buf); + } + strbuf_addf(&name_buf, "remote.%s.url", remotename); urlset = remote->url; urlset_nr = remote->url_nr; diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index d2a2cdd453396b..434c1f828a1f58 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1194,6 +1194,17 @@ test_expect_success 'remote set-url --delete baz' ' cmp expect actual ' +test_expect_success 'remote set-url --save-to-push bbb' ' + git remote set-url --save-to-push someremote bbb && + echo bbb >expect && + echo "YYY" >>expect && + echo ccc >>expect && + git config --get-all remote.someremote.url >actual && + echo "YYY" >>actual && + git config --get-all remote.someremote.pushurl >>actual && + cmp expect actual +' + test_expect_success 'extra args: setup' ' # add a dummy origin so that this does not trigger failure git remote add origin . From 6da1f1a92046b3a8b15fda3ee6885335ff3c61ab Mon Sep 17 00:00:00 2001 From: Josh Steadmon Date: Thu, 20 Dec 2018 13:58:08 -0800 Subject: [PATCH 0008/1015] protocol: advertise multiple supported versions Currently the client advertises that it supports the wire protocol version set in the protocol.version config. However, not all services support the same set of protocol versions. For example, git-receive-pack supports v1 and v0, but not v2. If a client connects to git-receive-pack and requests v2, it will instead be downgraded to v0. Other services, such as git-upload-archive, do not do any version negotiation checks. This patch creates a protocol version registry. Individual client and server programs register all the protocol versions they support prior to communicating with a remote instance. Versions should be listed in preference order; the version specified in protocol.version will automatically be moved to the front of the registry. The protocol version registry is passed to remote helpers via the GIT_PROTOCOL environment variable. Clients now advertise the full list of registered versions. Servers select the first allowed version from this advertisement. Additionally, remove special cases around advertising version=0. Previously we avoided adding version advertisements to the client's initial connection request if the client wanted version=0. However, including these advertisements does not change the version negotiation behavior, so it's better to have simpler code. As a side effect, this means that client operations over SSH will always include a "SendEnv=GIT_PROTOCOL" option on the SSH command line. While we're at it, remove unnecessary externs from function declarations in protocol.h. Signed-off-by: Josh Steadmon Signed-off-by: Junio C Hamano --- builtin/archive.c | 3 + builtin/clone.c | 4 ++ builtin/fetch-pack.c | 4 ++ builtin/fetch.c | 5 ++ builtin/ls-remote.c | 5 ++ builtin/pull.c | 5 ++ builtin/push.c | 4 ++ builtin/receive-pack.c | 3 + builtin/send-pack.c | 3 + builtin/upload-archive.c | 3 + builtin/upload-pack.c | 4 ++ connect.c | 52 +++++++-------- protocol.c | 122 +++++++++++++++++++++++++++++++++--- protocol.h | 23 ++++++- remote-curl.c | 27 +++++--- t/t5551-http-fetch-smart.sh | 1 + t/t5570-git-daemon.sh | 2 +- t/t5601-clone.sh | 38 +++++------ t/t5700-protocol-v1.sh | 8 +-- t/t5702-protocol-v2.sh | 16 +++-- transport-helper.c | 6 ++ 21 files changed, 256 insertions(+), 82 deletions(-) diff --git a/builtin/archive.c b/builtin/archive.c index d2455237ce04d6..213895ce8ce5cf 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -8,6 +8,7 @@ #include "transport.h" #include "parse-options.h" #include "pkt-line.h" +#include "protocol.h" #include "sideband.h" static void create_output_file(const char *output_file) @@ -94,6 +95,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix) OPT_END() }; + register_allowed_protocol_version(protocol_v0); + argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); diff --git a/builtin/clone.c b/builtin/clone.c index 15b142d64640e2..d7826ae8f0836b 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -901,6 +901,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct refspec rs = REFSPEC_INIT_FETCH; struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + fetch_if_missing = 0; packet_trace_identity("clone"); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 63e69a58011a4d..8ba503b88752c7 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -58,6 +58,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) fetch_if_missing = 0; + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + packet_trace_identity("fetch-pack"); memset(&args, 0, sizeof(args)); diff --git a/builtin/fetch.c b/builtin/fetch.c index e0140327aab236..07f2d428f93778 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -21,6 +21,7 @@ #include "argv-array.h" #include "utf8.h" #include "packfile.h" +#include "protocol.h" #include "list-objects-filter-options.h" #include "commit-reach.h" @@ -1570,6 +1571,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) int prune_tags_ok = 1; struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + packet_trace_identity("fetch"); fetch_if_missing = 0; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 1d7f1f5ce27834..f4316326d819c8 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "protocol.h" #include "transport.h" #include "ref-filter.h" #include "remote.h" @@ -80,6 +81,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) memset(&ref_array, 0, sizeof(ref_array)); + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + argc = parse_options(argc, argv, prefix, options, ls_remote_usage, PARSE_OPT_STOP_AT_NON_OPTION); dest = argv[0]; diff --git a/builtin/pull.c b/builtin/pull.c index 1b90622b131118..21c1a231d0438a 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -9,6 +9,7 @@ #include "config.h" #include "builtin.h" #include "parse-options.h" +#include "protocol.h" #include "exec-cmd.h" #include "run-command.h" #include "sha1-array.h" @@ -863,6 +864,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix) struct object_id rebase_fork_point; int autostash; + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); diff --git a/builtin/push.c b/builtin/push.c index 8bb8a0849ba90a..bfac4722fc385f 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -10,6 +10,7 @@ #include "remote.h" #include "transport.h" #include "parse-options.h" +#include "protocol.h" #include "submodule.h" #include "submodule-config.h" #include "send-pack.h" @@ -587,6 +588,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + packet_trace_identity("push"); git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 33187bd8e90252..8f59e4d0c01b39 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1935,6 +1935,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) packet_trace_identity("receive-pack"); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); if (argc > 1) diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8e3c7490f70df7..f48bd1306be5c9 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -184,6 +184,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_END() }; + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + git_config(send_pack_config, NULL); argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0); if (argc > 0) { diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 018879737aeedc..f1773d72551680 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "archive.h" #include "pkt-line.h" +#include "protocol.h" #include "sideband.h" #include "run-command.h" #include "argv-array.h" @@ -82,6 +83,8 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage(upload_archive_usage); + register_allowed_protocol_version(protocol_v0); + /* * Set up sideband subprocess. * diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c index 42dc4da5a1fc04..293dd45b9e4284 100644 --- a/builtin/upload-pack.c +++ b/builtin/upload-pack.c @@ -33,6 +33,10 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix) packet_trace_identity("upload-pack"); read_replace_refs = 0; + register_allowed_protocol_version(protocol_v2); + register_allowed_protocol_version(protocol_v1); + register_allowed_protocol_version(protocol_v0); + argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0); if (argc != 1) diff --git a/connect.c b/connect.c index 24281b608284ee..3080fb1fd17cbc 100644 --- a/connect.c +++ b/connect.c @@ -1046,7 +1046,7 @@ static enum ssh_variant determine_ssh_variant(const char *ssh_command, */ static struct child_process *git_connect_git(int fd[2], char *hostandport, const char *path, const char *prog, - enum protocol_version version, + const struct strbuf *version_advert, int flags) { struct child_process *conn; @@ -1084,12 +1084,9 @@ static struct child_process *git_connect_git(int fd[2], char *hostandport, prog, path, 0, target_host, 0); - /* If using a new version put that stuff here after a second null byte */ - if (version > 0) { - strbuf_addch(&request, '\0'); - strbuf_addf(&request, "version=%d%c", - version, '\0'); - } + /* Add version fields after a second null byte */ + strbuf_addch(&request, '\0'); + strbuf_addf(&request, "%s%c", version_advert->buf, '\0'); packet_write(fd[1], request.buf, request.len); @@ -1104,14 +1101,13 @@ static struct child_process *git_connect_git(int fd[2], char *hostandport, */ static void push_ssh_options(struct argv_array *args, struct argv_array *env, enum ssh_variant variant, const char *port, - enum protocol_version version, int flags) + const struct strbuf *version_advert, int flags) { - if (variant == VARIANT_SSH && - version > 0) { + if (variant == VARIANT_SSH) { argv_array_push(args, "-o"); argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT); - argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d", - version); + argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s", + version_advert->buf); } if (flags & CONNECT_IPV4) { @@ -1164,7 +1160,7 @@ static void push_ssh_options(struct argv_array *args, struct argv_array *env, /* Prepare a child_process for use by Git's SSH-tunneled transport. */ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, - const char *port, enum protocol_version version, + const char *port, const struct strbuf *version_advert, int flags) { const char *ssh; @@ -1198,15 +1194,16 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host, argv_array_push(&detect.args, ssh); argv_array_push(&detect.args, "-G"); - push_ssh_options(&detect.args, &detect.env_array, - VARIANT_SSH, port, version, flags); + push_ssh_options(&detect.args, &detect.env_array, VARIANT_SSH, + port, version_advert, flags); argv_array_push(&detect.args, ssh_host); variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH; } argv_array_push(&conn->args, ssh); - push_ssh_options(&conn->args, &conn->env_array, variant, port, version, flags); + push_ssh_options(&conn->args, &conn->env_array, variant, port, + version_advert, flags); argv_array_push(&conn->args, ssh_host); } @@ -1226,16 +1223,10 @@ struct child_process *git_connect(int fd[2], const char *url, { char *hostandport, *path; struct child_process *conn; + struct strbuf version_advert = STRBUF_INIT; enum protocol protocol; - enum protocol_version version = get_protocol_version_config(); - /* - * NEEDSWORK: If we are trying to use protocol v2 and we are planning - * to perform a push, then fallback to v0 since the client doesn't know - * how to push yet using v2. - */ - if (version == protocol_v2 && !strcmp("git-receive-pack", prog)) - version = protocol_v0; + get_client_protocol_version_advertisement(&version_advert); /* Without this we cannot rely on waitpid() to tell * what happened to our children. @@ -1250,7 +1241,8 @@ struct child_process *git_connect(int fd[2], const char *url, printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; } else if (protocol == PROTO_GIT) { - conn = git_connect_git(fd, hostandport, path, prog, version, flags); + conn = git_connect_git(fd, hostandport, path, prog, + &version_advert, flags); } else { struct strbuf cmd = STRBUF_INIT; const char *const *var; @@ -1293,13 +1285,13 @@ struct child_process *git_connect(int fd[2], const char *url, strbuf_release(&cmd); return NULL; } - fill_ssh_args(conn, ssh_host, port, version, flags); + fill_ssh_args(conn, ssh_host, port, &version_advert, + flags); } else { transport_check_allowed("file"); - if (version > 0) { - argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", - version); - } + argv_array_pushf(&conn->env_array, + GIT_PROTOCOL_ENVIRONMENT "=%s", + version_advert.buf); } argv_array_push(&conn->args, cmd.buf); diff --git a/protocol.c b/protocol.c index 5e636785d14f8e..5664bd7a050a49 100644 --- a/protocol.c +++ b/protocol.c @@ -2,18 +2,43 @@ #include "config.h" #include "protocol.h" +static enum protocol_version *allowed_versions; +static int nr_allowed_versions; +static int alloc_allowed_versions; +static int version_registration_locked = 0; + +static const char protocol_v0_string[] = "0"; +static const char protocol_v1_string[] = "1"; +static const char protocol_v2_string[] = "2"; + static enum protocol_version parse_protocol_version(const char *value) { - if (!strcmp(value, "0")) + if (!strcmp(value, protocol_v0_string)) return protocol_v0; - else if (!strcmp(value, "1")) + else if (!strcmp(value, protocol_v1_string)) return protocol_v1; - else if (!strcmp(value, "2")) + else if (!strcmp(value, protocol_v2_string)) return protocol_v2; else return protocol_unknown_version; } +/* Return the text representation of a wire protocol version. */ +static const char *format_protocol_version(enum protocol_version version) +{ + switch (version) { + case protocol_v0: + return protocol_v0_string; + case protocol_v1: + return protocol_v1_string; + case protocol_v2: + return protocol_v2_string; + case protocol_unknown_version: + die(_("Unrecognized protocol version")); + } + die(_("Unrecognized protocol_version")); +} + enum protocol_version get_protocol_version_config(void) { const char *value; @@ -30,6 +55,85 @@ enum protocol_version get_protocol_version_config(void) return protocol_v0; } +void register_allowed_protocol_version(enum protocol_version version) +{ + if (version_registration_locked) + BUG("late attempt to register an allowed protocol version"); + + ALLOC_GROW(allowed_versions, nr_allowed_versions + 1, + alloc_allowed_versions); + allowed_versions[nr_allowed_versions++] = version; +} + +void register_allowed_protocol_versions_from_env(void) +{ + const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT); + const char *version_str; + struct string_list version_list = STRING_LIST_INIT_DUP; + struct string_list_item *version; + + if (!git_protocol) + return; + + string_list_split(&version_list, git_protocol, ':', -1); + for_each_string_list_item(version, &version_list) { + if (skip_prefix(version->string, "version=", &version_str)) + register_allowed_protocol_version( + parse_protocol_version(version_str)); + } + string_list_clear(&version_list, 0); +} + +static int is_allowed_protocol_version(enum protocol_version version) +{ + int i; + version_registration_locked = 1; + for (i = 0; i < nr_allowed_versions; i++) + if (version == allowed_versions[i]) + return 1; + return 0; +} + +void get_client_protocol_version_advertisement(struct strbuf *advert) +{ + int i, tmp_nr = nr_allowed_versions; + enum protocol_version *tmp_allowed_versions, config_version; + strbuf_reset(advert); + + version_registration_locked = 1; + + config_version = get_protocol_version_config(); + if (config_version == protocol_v0) { + strbuf_addstr(advert, "version=0"); + return; + } + + if (tmp_nr > 0) { + ALLOC_ARRAY(tmp_allowed_versions, tmp_nr); + copy_array(tmp_allowed_versions, allowed_versions, tmp_nr, + sizeof(enum protocol_version)); + } else { + ALLOC_ARRAY(tmp_allowed_versions, 1); + tmp_nr = 1; + tmp_allowed_versions[0] = config_version; + } + + if (tmp_allowed_versions[0] != config_version) + for (i = 1; i < nr_allowed_versions; i++) + if (tmp_allowed_versions[i] == config_version) { + SWAP(tmp_allowed_versions[0], + tmp_allowed_versions[i]); + } + + strbuf_addf(advert, "version=%s", + format_protocol_version(tmp_allowed_versions[0])); + for (i = 1; i < tmp_nr; i++) + strbuf_addf(advert, ":version=%s", + format_protocol_version(tmp_allowed_versions[i])); + + free(tmp_allowed_versions); +} + enum protocol_version determine_protocol_version_server(void) { const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT); @@ -38,9 +142,10 @@ enum protocol_version determine_protocol_version_server(void) /* * Determine which protocol version the client has requested. Since * multiple 'version' keys can be sent by the client, indicating that - * the client is okay to speak any of them, select the greatest version - * that the client has requested. This is due to the assumption that - * the most recent protocol version will be the most state-of-the-art. + * the client is okay to speak any of them, select the first + * recognizable version that the client has requested. This is due to + * the assumption that the protocol versions will be listed in + * preference order. */ if (git_protocol) { struct string_list list = STRING_LIST_INIT_DUP; @@ -53,8 +158,11 @@ enum protocol_version determine_protocol_version_server(void) if (skip_prefix(item->string, "version=", &value)) { v = parse_protocol_version(value); - if (v > version) + if (v != protocol_unknown_version && + is_allowed_protocol_version(v)) { version = v; + break; + } } } diff --git a/protocol.h b/protocol.h index 2ad35e433c1e6f..88330fd0ee4da5 100644 --- a/protocol.h +++ b/protocol.h @@ -14,7 +14,24 @@ enum protocol_version { * 'protocol.version' config. If unconfigured, a value of 'protocol_v0' is * returned. */ -extern enum protocol_version get_protocol_version_config(void); +enum protocol_version get_protocol_version_config(void); + +/* + * Register an allowable protocol version for a given operation. Registration + * must occur before attempting to advertise a version to a server process. + */ +void register_allowed_protocol_version(enum protocol_version version); + +/* + * Register allowable protocol versions from the GIT_PROTOCOL environment var. + */ +void register_allowed_protocol_versions_from_env(void); + +/* + * Fill a strbuf with a version advertisement string suitable for use in the + * GIT_PROTOCOL environment variable or similar version negotiation field. + */ +void get_client_protocol_version_advertisement(struct strbuf *advert); /* * Used by a server to determine which protocol version should be used based on @@ -23,12 +40,12 @@ extern enum protocol_version get_protocol_version_config(void); * request a particular protocol version, a default of 'protocol_v0' will be * used. */ -extern enum protocol_version determine_protocol_version_server(void); +enum protocol_version determine_protocol_version_server(void); /* * Used by a client to determine which protocol version the server is speaking * based on the server's initial response. */ -extern enum protocol_version determine_protocol_version_client(const char *server_response); +enum protocol_version determine_protocol_version_client(const char *server_response); #endif /* PROTOCOL_H */ diff --git a/remote-curl.c b/remote-curl.c index 1220dffcdc57a1..0d8fe19cc717c3 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -330,6 +330,19 @@ static int get_protocol_http_header(enum protocol_version version, return 0; } +static int get_client_protocol_http_header(const struct strbuf *version_advert, + struct strbuf *header) +{ + if (version_advert->len > 0) { + strbuf_addf(header, GIT_PROTOCOL_HEADER ": %s", + version_advert->buf); + + return 1; + } + + return 0; +} + static struct discovery *discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; @@ -339,11 +352,11 @@ static struct discovery *discover_refs(const char *service, int for_push) struct strbuf refs_url = STRBUF_INIT; struct strbuf effective_url = STRBUF_INIT; struct strbuf protocol_header = STRBUF_INIT; + struct strbuf version_advert = STRBUF_INIT; struct string_list extra_headers = STRING_LIST_INIT_DUP; struct discovery *last = last_discovery; int http_ret, maybe_smart = 0; struct http_get_options http_options; - enum protocol_version version = get_protocol_version_config(); if (last && !strcmp(service, last->service)) return last; @@ -360,16 +373,10 @@ static struct discovery *discover_refs(const char *service, int for_push) strbuf_addf(&refs_url, "service=%s", service); } - /* - * NEEDSWORK: If we are trying to use protocol v2 and we are planning - * to perform a push, then fallback to v0 since the client doesn't know - * how to push yet using v2. - */ - if (version == protocol_v2 && !strcmp("git-receive-pack", service)) - version = protocol_v0; + get_client_protocol_version_advertisement(&version_advert); /* Add the extra Git-Protocol header */ - if (get_protocol_http_header(version, &protocol_header)) + if (get_client_protocol_http_header(&version_advert, &protocol_header)) string_list_append(&extra_headers, protocol_header.buf); memset(&http_options, 0, sizeof(http_options)); @@ -1328,6 +1335,8 @@ int cmd_main(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; int nongit; + register_allowed_protocol_versions_from_env(); + setup_git_directory_gently(&nongit); if (argc < 2) { error("remote-curl: usage: git remote-curl []"); diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 8630b0cc39045f..a60dd907bdf806 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -29,6 +29,7 @@ test_expect_success 'clone http repository' ' > Accept: */* > Accept-Encoding: ENCODINGS > Pragma: no-cache + > Git-Protocol: version=0 < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 7466aad111fe4e..d528e91630697d 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -186,7 +186,7 @@ test_expect_success 'hostname cannot break out of directory' ' test_expect_success 'daemon log records all attributes' ' cat >expect <<-\EOF && Extended attribute "host": localhost - Extended attribute "protocol": version=1 + Extended attribute "protocol": version=1:version=2:version=0 EOF >daemon.log && GIT_OVERRIDE_VIRTUAL_HOST=localhost \ diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 8bbc7068acbd1e..d9caa1891ef48c 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -346,7 +346,7 @@ expect_ssh () { test_expect_success 'clone myhost:src uses ssh' ' git clone myhost:src ssh-clone && - expect_ssh myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL" myhost src ' test_expect_success !MINGW,!CYGWIN 'clone local path foo:bar' ' @@ -357,12 +357,12 @@ test_expect_success !MINGW,!CYGWIN 'clone local path foo:bar' ' test_expect_success 'bracketed hostnames are still ssh' ' git clone "[myhost:123]:src" ssh-bracket-clone && - expect_ssh "-p 123" myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src ' test_expect_success 'OpenSSH variant passes -4' ' git clone -4 "[myhost:123]:src" ssh-ipv4-clone && - expect_ssh "-4 -p 123" myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL -4 -p 123" myhost src ' test_expect_success 'variant can be overridden' ' @@ -406,7 +406,7 @@ test_expect_success 'OpenSSH-like uplink is treated as ssh' ' GIT_SSH="$TRASH_DIRECTORY/uplink" && test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" && git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink && - expect_ssh "-p 123" myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src ' test_expect_success 'plink is treated specially (as putty)' ' @@ -446,14 +446,14 @@ test_expect_success 'GIT_SSH_VARIANT overrides plink detection' ' copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && GIT_SSH_VARIANT=ssh \ git clone "[myhost:123]:src" ssh-bracket-clone-variant-1 && - expect_ssh "-p 123" myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src ' test_expect_success 'ssh.variant overrides plink detection' ' copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && git -c ssh.variant=ssh \ clone "[myhost:123]:src" ssh-bracket-clone-variant-2 && - expect_ssh "-p 123" myhost src + expect_ssh "-o SendEnv=GIT_PROTOCOL -p 123" myhost src ' test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' ' @@ -488,7 +488,7 @@ test_clone_url () { } test_expect_success !MINGW 'clone c:temp is ssl' ' - test_clone_url c:temp c temp + test_clone_url c:temp "-o SendEnv=GIT_PROTOCOL" c temp ' test_expect_success MINGW 'clone c:temp is dos drive' ' @@ -499,7 +499,7 @@ test_expect_success MINGW 'clone c:temp is dos drive' ' for repo in rep rep/home/project 123 do test_expect_success "clone host:$repo" ' - test_clone_url host:$repo host $repo + test_clone_url host:$repo "-o SendEnv=GIT_PROTOCOL" host $repo ' done @@ -507,16 +507,16 @@ done for repo in rep rep/home/project 123 do test_expect_success "clone [::1]:$repo" ' - test_clone_url [::1]:$repo ::1 "$repo" + test_clone_url [::1]:$repo "-o SendEnv=GIT_PROTOCOL" ::1 "$repo" ' done #home directory test_expect_success "clone host:/~repo" ' - test_clone_url host:/~repo host "~repo" + test_clone_url host:/~repo "-o SendEnv=GIT_PROTOCOL" host "~repo" ' test_expect_success "clone [::1]:/~repo" ' - test_clone_url [::1]:/~repo ::1 "~repo" + test_clone_url [::1]:/~repo "-o SendEnv=GIT_PROTOCOL" ::1 "~repo" ' # Corner cases @@ -532,22 +532,22 @@ done for tcol in "" : do test_expect_success "clone ssh://host.xz$tcol/home/user/repo" ' - test_clone_url "ssh://host.xz$tcol/home/user/repo" host.xz /home/user/repo + test_clone_url "ssh://host.xz$tcol/home/user/repo" "-o SendEnv=GIT_PROTOCOL" host.xz /home/user/repo ' # from home directory test_expect_success "clone ssh://host.xz$tcol/~repo" ' - test_clone_url "ssh://host.xz$tcol/~repo" host.xz "~repo" + test_clone_url "ssh://host.xz$tcol/~repo" "-o SendEnv=GIT_PROTOCOL" host.xz "~repo" ' done # with port number test_expect_success 'clone ssh://host.xz:22/home/user/repo' ' - test_clone_url "ssh://host.xz:22/home/user/repo" "-p 22 host.xz" "/home/user/repo" + test_clone_url "ssh://host.xz:22/home/user/repo" "-o SendEnv=GIT_PROTOCOL -p 22 host.xz" "/home/user/repo" ' # from home directory with port number test_expect_success 'clone ssh://host.xz:22/~repo' ' - test_clone_url "ssh://host.xz:22/~repo" "-p 22 host.xz" "~repo" + test_clone_url "ssh://host.xz:22/~repo" "-o SendEnv=GIT_PROTOCOL -p 22 host.xz" "~repo" ' #IPv6 @@ -555,7 +555,7 @@ for tuah in ::1 [::1] [::1]: user@::1 user@[::1] user@[::1]: [user@::1] [user@:: do ehost=$(echo $tuah | sed -e "s/1]:/1]/" | tr -d "[]") test_expect_success "clone ssh://$tuah/home/user/repo" " - test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo + test_clone_url ssh://$tuah/home/user/repo '-o SendEnv=GIT_PROTOCOL' $ehost /home/user/repo " done @@ -564,7 +564,7 @@ for tuah in ::1 [::1] user@::1 user@[::1] [user@::1] do euah=$(echo $tuah | tr -d "[]") test_expect_success "clone ssh://$tuah/~repo" " - test_clone_url ssh://$tuah/~repo $euah '~repo' + test_clone_url ssh://$tuah/~repo '-o SendEnv=GIT_PROTOCOL' $euah '~repo' " done @@ -573,7 +573,7 @@ for tuah in [::1] user@[::1] [user@::1] do euah=$(echo $tuah | tr -d "[]") test_expect_success "clone ssh://$tuah:22/home/user/repo" " - test_clone_url ssh://$tuah:22/home/user/repo '-p 22' $euah /home/user/repo + test_clone_url ssh://$tuah:22/home/user/repo '-o SendEnv=GIT_PROTOCOL -p 22' $euah /home/user/repo " done @@ -582,7 +582,7 @@ for tuah in [::1] user@[::1] [user@::1] do euah=$(echo $tuah | tr -d "[]") test_expect_success "clone ssh://$tuah:22/~repo" " - test_clone_url ssh://$tuah:22/~repo '-p 22' $euah '~repo' + test_clone_url ssh://$tuah:22/~repo '-o SendEnv=GIT_PROTOCOL -p 22' $euah '~repo' " done diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh index ba86a44eb18392..2e56c792331ecf 100755 --- a/t/t5700-protocol-v1.sh +++ b/t/t5700-protocol-v1.sh @@ -26,7 +26,7 @@ test_expect_success 'clone with git:// using protocol v1' ' test_cmp expect actual && # Client requested to use protocol v1 - grep "clone> .*\\\0\\\0version=1\\\0$" log && + grep "clone> .*\\\0\\\0version=1.*\\\0$" log && # Server responded using protocol v1 grep "clone< version 1" log ' @@ -42,7 +42,7 @@ test_expect_success 'fetch with git:// using protocol v1' ' test_cmp expect actual && # Client requested to use protocol v1 - grep "fetch> .*\\\0\\\0version=1\\\0$" log && + grep "fetch> .*\\\0\\\0version=1.*\\\0$" log && # Server responded using protocol v1 grep "fetch< version 1" log ' @@ -56,7 +56,7 @@ test_expect_success 'pull with git:// using protocol v1' ' test_cmp expect actual && # Client requested to use protocol v1 - grep "fetch> .*\\\0\\\0version=1\\\0$" log && + grep "fetch> .*\\\0\\\0version=1.*\\\0$" log && # Server responded using protocol v1 grep "fetch< version 1" log ' @@ -74,7 +74,7 @@ test_expect_success 'push with git:// using protocol v1' ' test_cmp expect actual && # Client requested to use protocol v1 - grep "push> .*\\\0\\\0version=1\\\0$" log && + grep "push> .*\\\0\\\0version=1.*\\\0$" log && # Server responded using protocol v1 grep "push< version 1" log ' diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 0f2b09ebb8d662..9ddcc9139f56f7 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -24,7 +24,7 @@ test_expect_success 'list refs with git:// using protocol v2' ' ls-remote --symref "$GIT_DAEMON_URL/parent" >actual && # Client requested to use protocol v2 - grep "git> .*\\\0\\\0version=2\\\0$" log && + grep "git> .*\\\0\\\0version=2.*\\\0$" log && # Server responded using protocol v2 grep "git< version 2" log && @@ -56,7 +56,7 @@ test_expect_success 'clone with git:// using protocol v2' ' test_cmp expect actual && # Client requested to use protocol v2 - grep "clone> .*\\\0\\\0version=2\\\0$" log && + grep "clone> .*\\\0\\\0version=2.*\\\0$" log && # Server responded using protocol v2 grep "clone< version 2" log ' @@ -74,7 +74,7 @@ test_expect_success 'fetch with git:// using protocol v2' ' test_cmp expect actual && # Client requested to use protocol v2 - grep "fetch> .*\\\0\\\0version=2\\\0$" log && + grep "fetch> .*\\\0\\\0version=2.*\\\0$" log && # Server responded using protocol v2 grep "fetch< version 2" log ' @@ -103,7 +103,7 @@ test_expect_success 'pull with git:// using protocol v2' ' test_cmp expect actual && # Client requested to use protocol v2 - grep "fetch> .*\\\0\\\0version=2\\\0$" log && + grep "fetch> .*\\\0\\\0version=2.*\\\0$" log && # Server responded using protocol v2 grep "fetch< version 2" log ' @@ -518,7 +518,7 @@ test_expect_success 'push with http:// and a config of v2 does not request v2' ' test_when_finished "rm -f log" && # Till v2 for push is designed, make sure that if a client has # protocol.version configured to use v2, that the client instead falls - # back and uses v0. + # back to previous versions. test_commit -C http_child three && @@ -531,10 +531,8 @@ test_expect_success 'push with http:// and a config of v2 does not request v2' ' git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect && test_cmp expect actual && - # Client didnt request to use protocol v2 - ! grep "Git-Protocol: version=2" log && - # Server didnt respond using protocol v2 - ! grep "git< version 2" log + # Server responded with version 1 + grep "git< version 1" log ' test_expect_success 'when server sends "ready", expect DELIM' ' diff --git a/transport-helper.c b/transport-helper.c index bf225c698fac81..9c84e4a67f2995 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -105,6 +105,7 @@ static struct child_process *get_helper(struct transport *transport) { struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; + struct strbuf version_advert = STRBUF_INIT; struct child_process *helper; int duped; int code; @@ -127,6 +128,11 @@ static struct child_process *get_helper(struct transport *transport) argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir()); + get_client_protocol_version_advertisement(&version_advert); + if (version_advert.len > 0) + argv_array_pushf(&helper->env_array, "%s=%s", + GIT_PROTOCOL_ENVIRONMENT, version_advert.buf); + code = start_command(helper); if (code < 0 && errno == ENOENT) die(_("unable to find remote helper for '%s'"), data->name); From 913368875262dedc4beeecb3f41fde5f2e040ab1 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 21 Dec 2018 08:28:37 -0800 Subject: [PATCH 0009/1015] repack: refactor pack deletion for future use The repack builtin deletes redundant pack-files and their associated .idx, .promisor, .bitmap, and .keep files. We will want to re-use this logic in the future for other types of repack, so pull the logic into 'unlink_pack_path()' in packfile.c. The 'ignore_keep' parameter is enabled for the use in repack, but will be important for a future caller. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/repack.c | 14 ++------------ packfile.c | 28 ++++++++++++++++++++++++++++ packfile.h | 7 +++++++ 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/builtin/repack.c b/builtin/repack.c index 45583683eef4d5..3d445b34b4c9f6 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -129,19 +129,9 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list, static void remove_redundant_pack(const char *dir_name, const char *base_name) { - const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"}; - int i; struct strbuf buf = STRBUF_INIT; - size_t plen; - - strbuf_addf(&buf, "%s/%s", dir_name, base_name); - plen = buf.len; - - for (i = 0; i < ARRAY_SIZE(exts); i++) { - strbuf_setlen(&buf, plen); - strbuf_addstr(&buf, exts[i]); - unlink(buf.buf); - } + strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name); + unlink_pack_path(buf.buf, 1); strbuf_release(&buf); } diff --git a/packfile.c b/packfile.c index d1e6683ffe877d..bacecb4d0debf0 100644 --- a/packfile.c +++ b/packfile.c @@ -352,6 +352,34 @@ void close_all_packs(struct raw_object_store *o) } } +void unlink_pack_path(const char *pack_name, int force_delete) +{ + static const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"}; + int i; + struct strbuf buf = STRBUF_INIT; + size_t plen; + + strbuf_addstr(&buf, pack_name); + strip_suffix_mem(buf.buf, &buf.len, ".pack"); + plen = buf.len; + + if (!force_delete) { + strbuf_addstr(&buf, ".keep"); + if (!access(buf.buf, F_OK)) { + strbuf_release(&buf); + return; + } + } + + for (i = 0; i < ARRAY_SIZE(exts); i++) { + strbuf_setlen(&buf, plen); + strbuf_addstr(&buf, exts[i]); + unlink(buf.buf); + } + + strbuf_release(&buf); +} + /* * The LRU pack is the one with the oldest MRU window, preferring packs * with no used windows, or the oldest mtime if it has no windows allocated. diff --git a/packfile.h b/packfile.h index 6c4037605d0dfe..5b7bcdb1dd1212 100644 --- a/packfile.h +++ b/packfile.h @@ -86,6 +86,13 @@ extern void unuse_pack(struct pack_window **); extern void clear_delta_base_cache(void); extern struct packed_git *add_packed_git(const char *path, size_t path_len, int local); +/* + * Unlink the .pack and associated extension files. + * Does not unlink if 'force_delete' is false and the pack-file is + * marked as ".keep". + */ +extern void unlink_pack_path(const char *pack_name, int force_delete); + /* * Make sure that a pointer access into an mmap'd index file is within bounds, * and can provide at least 8 bytes of data. From e2527ae9b6b357ad7182a98985e077744bc9e599 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 21 Dec 2018 08:28:38 -0800 Subject: [PATCH 0010/1015] Docs: rearrange subcommands for multi-pack-index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will add new subcommands to the multi-pack-index, and that will make the documentation a bit messier. Clean up the 'verb' descriptions by renaming the concept to 'subcommand' and removing the reference to the object directory. Helped-by: Stefan Beller Helped-by: Szeder Gábor Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- Documentation/git-multi-pack-index.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index f7778a2c85c1aa..1af406aca21922 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -9,7 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes SYNOPSIS -------- [verse] -'git multi-pack-index' [--object-dir=] +'git multi-pack-index' [--object-dir=] DESCRIPTION ----------- @@ -23,13 +23,13 @@ OPTIONS `/packs/multi-pack-index` for the current MIDX file, and `/packs` for the pack-files to index. +The following subcommands are available: + write:: - When given as the verb, write a new MIDX file to - `/packs/multi-pack-index`. + Write a new MIDX file. verify:: - When given as the verb, verify the contents of the MIDX file - at `/packs/multi-pack-index`. + Verify the contents of the MIDX file. EXAMPLES From a0cc58450a8ac81ba405f1e161599263d1678686 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:13 +0000 Subject: [PATCH 0011/1015] move worktree tests to t24* The 'git worktree' command used to be just another mode in 'git checkout', namely 'git checkout --to'. When the tests for the latter were retrofitted for the former, the test name was adjusted, but the test number was kept, even though the test is testing a different command now. t/README states: "Second digit tells the particular command we are testing.", so 'git worktree' should have a separate number just for itself. Move the worktree tests to t24* to adhere to that guideline. We're going to make use of the free'd up numbers in a subsequent commit. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- t/{t2025-worktree-add.sh => t2400-worktree-add.sh} | 0 t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} | 0 t/{t2027-worktree-list.sh => t2402-worktree-list.sh} | 0 t/{t2028-worktree-move.sh => t2403-worktree-move.sh} | 0 t/{t2029-worktree-config.sh => t2404-worktree-config.sh} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename t/{t2025-worktree-add.sh => t2400-worktree-add.sh} (100%) rename t/{t2026-worktree-prune.sh => t2401-worktree-prune.sh} (100%) rename t/{t2027-worktree-list.sh => t2402-worktree-list.sh} (100%) rename t/{t2028-worktree-move.sh => t2403-worktree-move.sh} (100%) rename t/{t2029-worktree-config.sh => t2404-worktree-config.sh} (100%) diff --git a/t/t2025-worktree-add.sh b/t/t2400-worktree-add.sh similarity index 100% rename from t/t2025-worktree-add.sh rename to t/t2400-worktree-add.sh diff --git a/t/t2026-worktree-prune.sh b/t/t2401-worktree-prune.sh similarity index 100% rename from t/t2026-worktree-prune.sh rename to t/t2401-worktree-prune.sh diff --git a/t/t2027-worktree-list.sh b/t/t2402-worktree-list.sh similarity index 100% rename from t/t2027-worktree-list.sh rename to t/t2402-worktree-list.sh diff --git a/t/t2028-worktree-move.sh b/t/t2403-worktree-move.sh similarity index 100% rename from t/t2028-worktree-move.sh rename to t/t2403-worktree-move.sh diff --git a/t/t2029-worktree-config.sh b/t/t2404-worktree-config.sh similarity index 100% rename from t/t2029-worktree-config.sh rename to t/t2404-worktree-config.sh From b702dd12d52816e192578c6206db5e6c332ba49b Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:14 +0000 Subject: [PATCH 0012/1015] entry: factor out unlink_entry function Factor out the 'unlink_entry()' function from unpack-trees.c to entry.c. It will be used in other places as well in subsequent steps. As it's no longer a static function, also move the documentation to the header file to make it more discoverable. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 5 +++++ entry.c | 15 +++++++++++++++ unpack-trees.c | 19 ------------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/cache.h b/cache.h index ca36b44ee0b585..c1c953e810abcc 100644 --- a/cache.h +++ b/cache.h @@ -1542,6 +1542,11 @@ struct checkout { extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); extern void enable_delayed_checkout(struct checkout *state); extern int finish_delayed_checkout(struct checkout *state); +/* + * Unlink the last component and schedule the leading directories for + * removal, such that empty directories get removed. + */ +extern void unlink_entry(const struct cache_entry *ce); struct cache_def { struct strbuf path; diff --git a/entry.c b/entry.c index 0a3c451f5f0f08..b9eef571171694 100644 --- a/entry.c +++ b/entry.c @@ -508,3 +508,18 @@ int checkout_entry(struct cache_entry *ce, create_directories(path.buf, path.len, state); return write_entry(ce, path.buf, state, 0); } + +void unlink_entry(const struct cache_entry *ce) +{ + const struct submodule *sub = submodule_from_ce(ce); + if (sub) { + /* 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; + if (remove_or_warn(ce->ce_mode, ce->name)) + return; + schedule_dir_for_removal(ce->name, ce_namelen(ce)); +} diff --git a/unpack-trees.c b/unpack-trees.c index 7570df481bf698..e8d1a6ac504ec0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -300,25 +300,6 @@ static void load_gitmodules_file(struct index_state *index, } } -/* - * Unlink the last component and schedule the leading directories for - * removal, such that empty directories get removed. - */ -static void unlink_entry(const struct cache_entry *ce) -{ - const struct submodule *sub = submodule_from_ce(ce); - if (sub) { - /* 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; - if (remove_or_warn(ce->ce_mode, ce->name)) - return; - schedule_dir_for_removal(ce->name, ce_namelen(ce)); -} - static struct progress *get_progress(struct unpack_trees_options *o) { unsigned cnt = 0, total = 0; From 536ec1839dbde8b9a6b38e6ccb5ab01b2b6311f9 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:15 +0000 Subject: [PATCH 0013/1015] entry: support CE_WT_REMOVE flag in checkout_entry 'checkout_entry()' currently only supports creating new entries in the working tree, but not deleting them. Add the ability to remove entries at the same time if the entry is marked with the CE_WT_REMOVE flag. Currently this doesn't have any effect, as the CE_WT_REMOVE flag is only used in unpack-tree, however we will make use of this in a subsequent step in the series. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- entry.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/entry.c b/entry.c index b9eef571171694..3d3701e7ae148b 100644 --- a/entry.c +++ b/entry.c @@ -441,6 +441,17 @@ int checkout_entry(struct cache_entry *ce, static struct strbuf path = STRBUF_INIT; struct stat st; + if (ce->ce_flags & CE_WT_REMOVE) { + if (topath) + /* + * No content and thus no path to create, so we have + * no pathname to return. + */ + BUG("Can't remove entry to a path"); + unlink_entry(ce); + return 0; + } + if (topath) return write_entry(ce, topath, state, 1); From 6fdc2057225ad1ae735ecaacdcace77c8b0b6b76 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:16 +0000 Subject: [PATCH 0014/1015] read-cache: add invalidate parameter to remove_marked_cache_entries When marking cache entries for removal, and later removing them all at once using 'remove_marked_cache_entries()', cache entries currently have to be invalidated manually in the cache tree and in the untracked cache. Add an invalidate flag to the function. With the flag set, the function will take care of invalidating the path in the cache tree and in the untracked cache. Note that the current callsites already do the invalidation properly in other places, so we're just passing 0 from there to keep the status quo. This will be useful in a subsequent commit. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 2 +- read-cache.c | 8 +++++++- split-index.c | 2 +- unpack-trees.c | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index c1c953e810abcc..1deee48f5bff74 100644 --- a/cache.h +++ b/cache.h @@ -751,7 +751,7 @@ extern void rename_index_entry_at(struct index_state *, int pos, const char *new /* Remove entry, return true if there are more entries to go. */ extern int remove_index_entry_at(struct index_state *, int pos); -extern void remove_marked_cache_entries(struct index_state *istate); +extern void remove_marked_cache_entries(struct index_state *istate, int invalidate); extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_VERBOSE 1 #define ADD_CACHE_PRETEND 2 diff --git a/read-cache.c b/read-cache.c index bd45dc3e24d7dc..978d43f6763999 100644 --- a/read-cache.c +++ b/read-cache.c @@ -590,13 +590,19 @@ int remove_index_entry_at(struct index_state *istate, int pos) * CE_REMOVE is set in ce_flags. This is much more effective than * calling remove_index_entry_at() for each entry to be removed. */ -void remove_marked_cache_entries(struct index_state *istate) +void remove_marked_cache_entries(struct index_state *istate, int invalidate) { struct cache_entry **ce_array = istate->cache; unsigned int i, j; for (i = j = 0; i < istate->cache_nr; i++) { if (ce_array[i]->ce_flags & CE_REMOVE) { + if (invalidate) { + cache_tree_invalidate_path(istate, + ce_array[i]->name); + untracked_cache_remove_from_index(istate, + ce_array[i]->name); + } remove_name_hash(istate, ce_array[i]); save_or_free_index_entry(istate, ce_array[i]); } diff --git a/split-index.c b/split-index.c index 5820412dc52030..8aebc3661bebf0 100644 --- a/split-index.c +++ b/split-index.c @@ -162,7 +162,7 @@ void merge_base_index(struct index_state *istate) ewah_each_bit(si->replace_bitmap, replace_entry, istate); ewah_each_bit(si->delete_bitmap, mark_entry_for_delete, istate); if (si->nr_deletions) - remove_marked_cache_entries(istate); + remove_marked_cache_entries(istate, 0); for (i = si->nr_replacements; i < si->saved_cache_nr; i++) { if (!ce_namelen(si->saved_cache[i])) diff --git a/unpack-trees.c b/unpack-trees.c index e8d1a6ac504ec0..8e6afa924d184c 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -392,7 +392,7 @@ static int check_updates(struct unpack_trees_options *o) unlink_entry(ce); } } - remove_marked_cache_entries(index); + remove_marked_cache_entries(index, 0); remove_scheduled_dirs(); if (should_update_submodules() && o->update && !o->dry_run) From 5160fa05623dc2e9e8348d48b91f1f0bf44d369e Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:17 +0000 Subject: [PATCH 0015/1015] checkout: clarify comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The key point for the if statement is that read_tree_some did not update the entry, because either it doesn't exist in tree-ish or doesn't match the pathspec. Clarify that. Suggested-by: Nguyễn Thái Ngọc Duy Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/checkout.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index acdafc6e4c4d10..cb166b2e078c60 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -304,10 +304,10 @@ static int checkout_paths(const struct checkout_opts *opts, continue; if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) /* - * "git checkout tree-ish -- path", but this entry - * is in the original index; it will not be checked - * out to the working tree and it does not matter - * if pathspec matched this entry. We will not do + * "git checkout tree-ish -- path" and this entry + * is in the original index, but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do * anything to this entry at all. */ continue; From b7033e73b7b671670160726f62ac16d88161e3d0 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Thu, 20 Dec 2018 13:48:18 +0000 Subject: [PATCH 0016/1015] checkout: factor out mark_cache_entry_for_checkout function Factor out the code that marks a cache entry as matched for checkout into a separate function. We are going to introduce a new mode in 'git checkout' in a subsequent commit, that is going to have a slightly different logic. This would make this code unnecessarily complex. Moving that complexity into separate functions will make the code in the subsequent step easier to follow. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/checkout.c | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index cb166b2e078c60..32c4b7f897f894 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -247,6 +247,40 @@ static int checkout_merged(int pos, const struct checkout *state) return status; } +static void mark_ce_for_checkout(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do + * anything to this entry at all. + */ + return; + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) + ce->ce_flags |= CE_MATCHED; +} + static int checkout_paths(const struct checkout_opts *opts, const char *revision) { @@ -297,37 +331,8 @@ static int checkout_paths(const struct checkout_opts *opts, * Make sure all pathspecs participated in locating the paths * to be checked out. */ - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - ce->ce_flags &= ~CE_MATCHED; - if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) - continue; - if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) - /* - * "git checkout tree-ish -- path" and this entry - * is in the original index, but is not in tree-ish - * or does not match the pathspec; it will not be - * checked out to the working tree. We will not do - * anything to this entry at all. - */ - continue; - /* - * Either this entry came from the tree-ish we are - * checking the paths out of, or we are checking out - * of the index. - * - * If it comes from the tree-ish, we already know it - * matches the pathspec and could just stamp - * CE_MATCHED to it from update_some(). But we still - * need ps_matched and read_tree_recursive (and - * eventually tree_entry_interesting) cannot fill - * ps_matched yet. Once it can, we can avoid calling - * match_pathspec() for _all_ entries when - * opts->source_tree != NULL. - */ - if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) - ce->ce_flags |= CE_MATCHED; - } + for (pos = 0; pos < active_nr; pos++) + mark_ce_for_checkout(active_cache[pos], ps_matched, opts); if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { free(ps_matched); From 2b71595d47a75031346d7bc0125da39a9bb10126 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 29 Dec 2018 17:03:58 +0100 Subject: [PATCH 0017/1015] sequencer: changes in parse_insn_buffer() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This clears the number of items of a todo_list before parsing it to allow to parse the same list multiple times without issues. As its items are not dynamically allocated, or don’t need to allocate memory, no additionnal memory management is required here. Furthermore, if a line is invalid, the type of the corresponding command is set to a garbage value, and its argument is defined properly. This will allow to recreate the text of a todo list from its commands, even if one of them is incorrect. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index b68bca0bef927f..d605199a54bece 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2141,6 +2141,8 @@ static int parse_insn_buffer(struct repository *r, char *buf, char *p = buf, *next_p; int i, res = 0, fixup_okay = file_exists(rebase_path_done()); + todo_list->current = todo_list->nr = 0; + for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -2154,7 +2156,10 @@ static int parse_insn_buffer(struct repository *r, char *buf, if (parse_insn_line(r, item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = TODO_NOOP; + item->command = TODO_COMMENT + 1; + item->arg = p; + item->arg_len = (int)(eol - p); + item->commit = NULL; } if (fixup_okay) From 5d94d54564fb0dea1f3caf2f1dacb7701f4be25c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 29 Dec 2018 17:03:59 +0100 Subject: [PATCH 0018/1015] sequencer: make the todo_list structure public This makes the structures todo_list and todo_item, and the functions todo_list_release() and parse_insn_buffer(), accessible outside of sequencer.c. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 69 ++++++++++------------------------------------------- sequencer.h | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/sequencer.c b/sequencer.c index d605199a54bece..25cc7a9a91af2e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1510,32 +1510,6 @@ static int allow_empty(struct repository *r, return 1; } -/* - * Note that ordering matters in this enum. Not only must it match the mapping - * below, it is also divided into several sections that matter. When adding - * new commands, make sure you add it in the right section. - */ -enum todo_command { - /* commands that handle commits */ - TODO_PICK = 0, - TODO_REVERT, - TODO_EDIT, - TODO_REWORD, - TODO_FIXUP, - TODO_SQUASH, - /* commands that do something else than handling a single commit */ - TODO_EXEC, - TODO_BREAK, - TODO_LABEL, - TODO_RESET, - TODO_MERGE, - /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP, - TODO_DROP, - /* comments (not counted for reporting progress) */ - TODO_COMMENT -}; - static struct { char c; const char *str; @@ -2012,26 +1986,7 @@ enum todo_item_flags { TODO_EDIT_MERGE_MSG = 1 }; -struct todo_item { - enum todo_command command; - struct commit *commit; - unsigned int flags; - const char *arg; - int arg_len; - size_t offset_in_buf; -}; - -struct todo_list { - struct strbuf buf; - struct todo_item *items; - int nr, alloc, current; - int done_nr, total_nr; - struct stat_data stat; -}; - -#define TODO_LIST_INIT { STRBUF_INIT } - -static void todo_list_release(struct todo_list *todo_list) +void todo_list_release(struct todo_list *todo_list) { strbuf_release(&todo_list->buf); FREE_AND_NULL(todo_list->items); @@ -2134,8 +2089,8 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return !item->commit; } -static int parse_insn_buffer(struct repository *r, char *buf, - struct todo_list *todo_list) +int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; @@ -2234,7 +2189,7 @@ static int read_populate_todo(struct repository *r, return error(_("could not stat '%s'"), todo_file); fill_stat_data(&todo_list->stat, &st); - res = parse_insn_buffer(r, todo_list->buf.buf, todo_list); + res = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list); if (res) { if (is_rebase_i(opts)) return error(_("please fix this using " @@ -2265,7 +2220,7 @@ static int read_populate_todo(struct repository *r, FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && - !parse_insn_buffer(r, done.buf.buf, &done)) + !todo_list_parse_insn_buffer(r, done.buf.buf, &done)) todo_list->done_nr = count_commands(&done); else todo_list->done_nr = 0; @@ -4556,7 +4511,7 @@ int sequencer_add_exec_commands(struct repository *r, if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4612,7 +4567,7 @@ int transform_todos(struct repository *r, unsigned flags) if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4698,7 +4653,7 @@ int check_todo_list(struct repository *r) goto leave_check; } advise_to_edit_todo = res = - parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) goto leave_check; @@ -4717,7 +4672,7 @@ int check_todo_list(struct repository *r) goto leave_check; } strbuf_release(&todo_file); - res = !!parse_insn_buffer(r, todo_list.buf.buf, &todo_list); + res = !!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); /* Find commits in git-rebase-todo.backup yet unseen */ for (i = todo_list.nr - 1; i >= 0; i--) { @@ -4799,7 +4754,7 @@ static int skip_unnecessary_picks(struct repository *r, struct object_id *output if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { todo_list_release(&todo_list); return -1; } @@ -4887,7 +4842,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla if (strbuf_read_file(buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); - if (parse_insn_buffer(r, buf->buf, &todo_list)) { + if (todo_list_parse_insn_buffer(r, buf->buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } @@ -4995,7 +4950,7 @@ int rearrange_squash(struct repository *r) if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; - if (parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list) < 0) { todo_list_release(&todo_list); return -1; } diff --git a/sequencer.h b/sequencer.h index 9d83f0f3e90313..c6360bac66b033 100644 --- a/sequencer.h +++ b/sequencer.h @@ -73,6 +73,56 @@ enum missing_commit_check_level { int write_message(const void *buf, size_t len, const char *filename, int append_eol); +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * of todo_command_info (in sequencer.c), it is also divided into several + * sections that matter. When adding new commands, make sure you add it in the + * right section. + */ +enum todo_command { + /* commands that handle commits */ + TODO_PICK = 0, + TODO_REVERT, + TODO_EDIT, + TODO_REWORD, + TODO_FIXUP, + TODO_SQUASH, + /* commands that do something else than handling a single commit */ + TODO_EXEC, + TODO_BREAK, + TODO_LABEL, + TODO_RESET, + TODO_MERGE, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP, + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT +}; + +struct todo_item { + enum todo_command command; + struct commit *commit; + unsigned int flags; + const char *arg; + int arg_len; + size_t offset_in_buf; +}; + +struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; + int done_nr, total_nr; + struct stat_data stat; +}; + +#define TODO_LIST_INIT { STRBUF_INIT } + +int todo_list_parse_insn_buffer(struct repository *r, char *buf, + struct todo_list *todo_list); +void todo_list_release(struct todo_list *todo_list); + /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); int sequencer_pick_revisions(struct repository *repo, From 091e04bc8cbb0c89c8112c4784f02a44decc257e Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Tue, 8 Jan 2019 21:52:24 +0000 Subject: [PATCH 0019/1015] checkout: introduce --{,no-}overlay option Currently 'git checkout' is defined as an overlay operation, which means that if in 'git checkout -- []' we have an entry in the index that matches , but that doesn't exist in , that entry will not be removed from the index or the working tree. Introduce a new --{,no-}overlay option, which allows using 'git checkout' in non-overlay mode, thus removing files from the working tree if they do not exist in but match . Note that 'git checkout -p -- []' already works this way, so no changes are needed for the patch mode. We disallow 'git checkout --overlay -p' to avoid confusing users who would expect to be able to force overlay mode in 'git checkout -p' this way. Untracked files are not affected by this change, so 'git checkout --no-overlay HEAD -- untracked' will not remove untracked from the working tree. This is so e.g. 'git checkout --no-overlay HEAD -- dir/' doesn't delete all untracked files in dir/, but rather just resets the state of files that are known to git. Suggested-by: Junio C Hamano Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 10 ++++++ builtin/checkout.c | 66 +++++++++++++++++++++++++++++----- t/t2025-checkout-no-overlay.sh | 47 ++++++++++++++++++++++++ t/t9902-completion.sh | 1 + 4 files changed, 116 insertions(+), 8 deletions(-) create mode 100755 t/t2025-checkout-no-overlay.sh diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 801de2f7645bf0..24e52b01e18975 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -260,6 +260,9 @@ the conflicted merge in the specified paths. This means that you can use `git checkout -p` to selectively discard edits from your current working tree. See the ``Interactive Mode'' section of linkgit:git-add[1] to learn how to operate the `--patch` mode. ++ +Note that this option uses the no overlay mode by default (see also +`--[no-]overlay`), and currently doesn't support overlay mode. --ignore-other-worktrees:: `git checkout` refuses when the wanted ref is already checked @@ -276,6 +279,13 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. Just like linkgit:git-submodule[1], this will detach the submodules HEAD. +--[no-]overlay:: + In the default overlay mode, `git checkout` never + removes files from the index or the working tree. When + specifying `--no-overlay`, files that appear in the index and + working tree, but not in are removed, to make them + match exactly. + :: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/builtin/checkout.c b/builtin/checkout.c index 32c4b7f897f894..0c5fe948ef5890 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -44,6 +44,7 @@ struct checkout_opts { int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + int overlay_mode; /* * If new checkout options are added, skip_merge_working_tree * should be updated accordingly. @@ -132,7 +133,8 @@ static int skip_same_name(const struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, const struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos, + int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -140,6 +142,8 @@ static int check_stage(int stage, const struct cache_entry *ce, int pos) return 0; pos++; } + if (!overlay_mode) + return 0; if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else @@ -165,7 +169,7 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) } static int checkout_stage(int stage, const struct cache_entry *ce, int pos, - const struct checkout *state) + const struct checkout *state, int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -173,6 +177,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, return checkout_entry(active_cache[pos], state, NULL); pos++; } + if (!overlay_mode) { + unlink_entry(ce); + return 0; + } if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else @@ -247,9 +255,9 @@ static int checkout_merged(int pos, const struct checkout *state) return status; } -static void mark_ce_for_checkout(struct cache_entry *ce, - char *ps_matched, - const struct checkout_opts *opts) +static void mark_ce_for_checkout_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) { ce->ce_flags &= ~CE_MATCHED; if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) @@ -281,6 +289,25 @@ static void mark_ce_for_checkout(struct cache_entry *ce, ce->ce_flags |= CE_MATCHED; } +static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) { + ce->ce_flags |= CE_MATCHED; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * In overlay mode, but the path is not in + * tree-ish, which means we should remove it + * from the index and the working tree. + */ + ce->ce_flags |= CE_REMOVE | CE_WT_REMOVE; + } +} + static int checkout_paths(const struct checkout_opts *opts, const char *revision) { @@ -332,7 +359,14 @@ static int checkout_paths(const struct checkout_opts *opts, * to be checked out. */ for (pos = 0; pos < active_nr; pos++) - mark_ce_for_checkout(active_cache[pos], ps_matched, opts); + if (opts->overlay_mode) + mark_ce_for_checkout_overlay(active_cache[pos], + ps_matched, + opts); + else + mark_ce_for_checkout_no_overlay(active_cache[pos], + ps_matched, + opts); if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { free(ps_matched); @@ -353,7 +387,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->force) { warning(_("path '%s' is unmerged"), ce->name); } else if (opts->writeout_stage) { - errs |= check_stage(opts->writeout_stage, ce, pos); + errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode); } else if (opts->merge) { errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { @@ -380,12 +414,14 @@ static int checkout_paths(const struct checkout_opts *opts, continue; } if (opts->writeout_stage) - errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); + errs |= checkout_stage(opts->writeout_stage, ce, pos, &state, opts->overlay_mode); else if (opts->merge) errs |= checkout_merged(pos, &state); pos = skip_same_name(ce, pos) - 1; } } + remove_marked_cache_entries(&the_index, 1); + remove_scheduled_dirs(); errs |= finish_delayed_checkout(&state); if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) @@ -547,6 +583,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts, * opts->show_progress only impacts output so doesn't require a merge */ + /* + * opts->overlay_mode cannot be used with switching branches so is + * not tested here + */ + /* * If we aren't creating a new branch any changes or updates will * happen in the existing branch. Since that could only be updating @@ -1183,6 +1224,10 @@ static int checkout_branch(struct checkout_opts *opts, die(_("'%s' cannot be used with switching branches"), "--patch"); + if (!opts->overlay_mode) + die(_("'%s' cannot be used with switching branches"), + "--no-overlay"); + if (opts->writeout_stage) die(_("'%s' cannot be used with switching branches"), "--ours/--theirs"); @@ -1271,6 +1316,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) "checkout", "control recursive updating of submodules", PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), + OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")), OPT_END(), }; @@ -1279,6 +1325,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.overwrite_ignore = 1; opts.prefix = prefix; opts.show_progress = -1; + opts.overlay_mode = -1; git_config(git_checkout_config, &opts); @@ -1302,6 +1349,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) die(_("-b, -B and --orphan are mutually exclusive")); + if (opts.overlay_mode == 1 && opts.patch_mode) + die(_("-p and --overlay are mutually exclusive")); + /* * From here on, new_branch will contain the branch to be checked out, * and new_branch_force and new_orphan_branch will tell us which one of diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh new file mode 100755 index 00000000000000..76330cb5ab79ab --- /dev/null +++ b/t/t2025-checkout-no-overlay.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +test_description='checkout --no-overlay -- ' + +. ./test-lib.sh + +test_expect_success 'setup' ' + git commit --allow-empty -m "initial" +' + +test_expect_success 'checkout --no-overlay deletes files not in ' ' + >file && + mkdir dir && + >dir/file1 && + git add file dir/file1 && + git checkout --no-overlay HEAD -- file && + test_path_is_missing file && + test_path_is_file dir/file1 +' + +test_expect_success 'checkout --no-overlay removing last file from directory' ' + git checkout --no-overlay HEAD -- dir/file1 && + test_path_is_missing dir +' + +test_expect_success 'checkout -p --overlay is disallowed' ' + test_must_fail git checkout -p --overlay HEAD 2>actual && + test_i18ngrep "fatal: -p and --overlay are mutually exclusive" actual +' + +test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' + test_commit file1 file1 && + test_commit file2 file2 && + git rm --cached file1 && + echo 1234 >file1 && + F1=$(git rev-parse HEAD:file1) && + F2=$(git rev-parse HEAD:file2) && + { + echo "100644 $F1 1 file1" && + echo "100644 $F2 2 file1" + } | git update-index --index-info && + test_path_is_file file1 && + git checkout --theirs --no-overlay -- file1 && + test_path_is_missing file1 +' + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index d01ad8eb2587ba..5758fffa0dd19c 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1436,6 +1436,7 @@ test_expect_success 'double dash "git checkout"' ' --progress Z --no-quiet Z --no-... Z + --overlay Z EOF ' From 1495ff7da526c61bff88e31fcdf419fb023a42c5 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Tue, 8 Jan 2019 21:52:25 +0000 Subject: [PATCH 0020/1015] checkout: introduce checkout.overlayMode config In the previous patch we introduced a new no-overlay mode for git checkout. Some users (such as the author of this commit) may want to have this mode turned on by default as it matches their mental model more closely. Make that possible by introducing a new config option to that extend. Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/config/checkout.txt | 7 +++++++ builtin/checkout.c | 8 +++++++- t/t2025-checkout-no-overlay.sh | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt index c4118fa1968711..73380a8d862d37 100644 --- a/Documentation/config/checkout.txt +++ b/Documentation/config/checkout.txt @@ -21,3 +21,10 @@ checkout.optimizeNewBranch:: will not update the skip-worktree bit in the index nor add/remove files in the working directory to reflect the current sparse checkout settings nor will it show the local changes. + +checkout.overlayMode:: + In the default overlay mode, `git checkout` never + removes files from the index or the working tree. When + setting `checkout.overlayMode` to false, files that appear in + the index and working tree, but not in are removed, + to make them match exactly. diff --git a/builtin/checkout.c b/builtin/checkout.c index 0c5fe948ef5890..b5dfc45736e458 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1019,13 +1019,19 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { + struct checkout_opts *opts = cb; + if (!strcmp(var, "checkout.optimizenewbranch")) { checkout_optimize_new_branch = git_config_bool(var, value); return 0; } + if (!strcmp(var, "checkout.overlaymode")) { + opts->overlay_mode = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "diff.ignoresubmodules")) { - struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh index 76330cb5ab79ab..a4912e35cbe3aa 100755 --- a/t/t2025-checkout-no-overlay.sh +++ b/t/t2025-checkout-no-overlay.sh @@ -44,4 +44,14 @@ test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' test_path_is_missing file1 ' +test_expect_success 'checkout with checkout.overlayMode=false deletes files not in ' ' + >file && + mkdir dir && + >dir/file1 && + git add file dir/file1 && + git -c checkout.overlayMode=false checkout HEAD -- file && + test_path_is_missing file && + test_path_is_file dir/file1 +' + test_done From 06800238c694aa0cd8330c0f8fb295a21c6b4e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:06 +0100 Subject: [PATCH 0021/1015] config.c: avoid git_path() in do_git_config_sequence() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This function has both $GIT_COMMON_DIR and $GIT_DIR in "opts". Use it to construct config.worktree path instead because git_pathdup() is tied to the current repository, but the given $GIT_DIR could be from another one. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- config.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/config.c b/config.c index ff521eb27ad243..79fbe65da8b02f 100644 --- a/config.c +++ b/config.c @@ -1665,6 +1665,7 @@ static int do_git_config_sequence(const struct config_options *opts, char *xdg_config = xdg_config_home("config"); char *user_config = expand_user_path("~/.gitconfig", 0); char *repo_config; + char *worktree_config; if (opts->commondir) repo_config = mkpathdup("%s/config", opts->commondir); @@ -1672,6 +1673,10 @@ static int do_git_config_sequence(const struct config_options *opts, BUG("git_dir without commondir"); else repo_config = NULL; + if (repository_format_worktree_config) + worktree_config = mkpathdup("%s/config.worktree", opts->git_dir); + else + worktree_config = NULL; current_parsing_scope = CONFIG_SCOPE_SYSTEM; if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) @@ -1693,12 +1698,8 @@ static int do_git_config_sequence(const struct config_options *opts, * Note: this should have a new scope, CONFIG_SCOPE_WORKTREE. * But let's not complicate things before it's actually needed. */ - if (repository_format_worktree_config) { - char *path = git_pathdup("config.worktree"); - if (!access_or_die(path, R_OK, 0)) - ret += git_config_from_file(fn, path, data); - free(path); - } + if (worktree_config && !access_or_die(worktree_config, R_OK, 0)) + ret += git_config_from_file(fn, worktree_config, data); current_parsing_scope = CONFIG_SCOPE_CMDLINE; if (git_config_from_parameters(fn, data) < 0) @@ -1708,6 +1709,7 @@ static int do_git_config_sequence(const struct config_options *opts, free(xdg_config); free(user_config); free(repo_config); + free(worktree_config); return ret; } From 2ec5633ff4e3863e1c1844e6fd6ec679056e2a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:07 +0100 Subject: [PATCH 0022/1015] worktree.c: add get_worktree_config() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "git config --worktree" can write to the right file whether extensions.worktreeConfig is enabled or not. In order to do the same using config API, we need to determine the right file to write to. Add this function for that purpose. This is the basis for the coming repo_config_set_worktree() Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/config.c | 9 ++------- worktree.c | 16 ++++++++++++++++ worktree.h | 7 +++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 84385ef165195e..771cfa54bd3a06 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -650,18 +650,13 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (use_local_config) given_config_source.file = git_pathdup("config"); else if (use_worktree_config) { - struct worktree **worktrees = get_worktrees(0); - if (repository_format_worktree_config) - given_config_source.file = git_pathdup("config.worktree"); - else if (worktrees[0] && worktrees[1]) + given_config_source.file = get_worktree_config(the_repository); + if (!given_config_source.file) die(_("--worktree cannot be used with multiple " "working trees unless the config\n" "extension worktreeConfig is enabled. " "Please read \"CONFIGURATION FILE\"\n" "section in \"git help worktree\" for details")); - else - given_config_source.file = git_pathdup("config"); - free_worktrees(worktrees); } else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = diff --git a/worktree.c b/worktree.c index d6a0ee7f730d96..d335bdf28acfac 100644 --- a/worktree.c +++ b/worktree.c @@ -581,3 +581,19 @@ int other_head_refs(each_ref_fn fn, void *cb_data) free_worktrees(worktrees); return ret; } + +char *get_worktree_config(struct repository *r) +{ + struct worktree **worktrees = get_worktrees(0); + char *path; + + if (repository_format_worktree_config) + path = repo_git_path(r, "config.worktree"); + else if (worktrees[0] && worktrees[1]) + path = NULL; + else + path = repo_git_path(r, "config"); + + free_worktrees(worktrees); + return path; +} diff --git a/worktree.h b/worktree.h index 9e3b0b7b6f9bd2..4c41002d311043 100644 --- a/worktree.h +++ b/worktree.h @@ -132,4 +132,11 @@ void strbuf_worktree_ref(const struct worktree *wt, const char *worktree_ref(const struct worktree *wt, const char *refname); +/* + * Return the path to config file that can contain worktree-specific + * config (or NULL in unsupported setups). The caller must free the + * return value. + */ +char *get_worktree_config(struct repository *r); + #endif From 8f7c7f55555818ff6216230b1a533144a3fdc719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:08 +0100 Subject: [PATCH 0023/1015] config.c: add repo_config_set_worktree_gently() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is C equivalent of "git config --set --worktree". In other words, it will - write to $GIT_DIR/config in single-worktree setup - write to $GIT_COMMON_DIR/worktrees//config.worktree or $GIT_COMMON_DIR/config.worktree (for main worktree) if extensions.worktreeConfig is enabled - return error in multiple-worktree setup if extensions.worktreeConfig is not enabled. While at there, also add repo_config_set*() for writing to $GIT_COMMON_DIR/config. Note, since git_config_set_multivar_in_file_gently() only invalidates the config set of the_repository, anybody who uses these functions on submodules must do additional invalidation if needed. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- config.c | 41 ++++++++++++++++++++++++++++++++++++++++- config.h | 3 +++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index 79fbe65da8b02f..151d28664e126b 100644 --- a/config.c +++ b/config.c @@ -19,6 +19,7 @@ #include "utf8.h" #include "dir.h" #include "color.h" +#include "worktree.h" struct config_source { struct config_source *prev; @@ -2137,6 +2138,39 @@ int repo_config_get_pathname(struct repository *repo, return ret; } +int repo_config_set_gently(struct repository *r, + const char *key, const char *value) +{ + char *path = repo_git_path(r, "config"); + int ret = git_config_set_multivar_in_file_gently(path, key, value, NULL, 0); + free(path); + return ret; +} + +void repo_config_set(struct repository *r, const char *key, const char *value) +{ + if (!repo_config_set_gently(r, key, value)) + return; + if (value) + die(_("could not set '%s' to '%s'"), key, value); + else + die(_("could not unset '%s'"), key); +} + +int repo_config_set_worktree_gently(struct repository *r, + const char *key, const char *value) +{ + char *path; + int ret; + + path = get_worktree_config(r); + if (!path) + return CONFIG_INVALID_FILE; + ret = git_config_set_multivar_in_file_gently(path, key, value, NULL, 0); + free(path); + return ret; +} + /* Functions used historically to read configuration from 'the_repository' */ void git_config(config_fn_t fn, void *data) { @@ -2912,7 +2946,12 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, ret = 0; - /* Invalidate the config cache */ + /* + * Invalidate the config cache + * + * NEEDSWORK: invalidate _all_ existing config caches, not + * just one from the_repository + */ git_config_clear(); out_free: diff --git a/config.h b/config.h index ee5d3fa7b4264f..62204dc252a8b8 100644 --- a/config.h +++ b/config.h @@ -103,6 +103,9 @@ extern int git_config_color(char *, const char *, const char *); extern int git_config_set_in_file_gently(const char *, const char *, const char *); extern void git_config_set_in_file(const char *, const char *, const char *); extern int git_config_set_gently(const char *, const char *); +extern int repo_config_set_gently(struct repository *, const char *, const char *); +extern void repo_config_set(struct repository *, const char *, const char *); +extern int repo_config_set_worktree_gently(struct repository *, const char *, const char *); extern void git_config_set(const char *, const char *); extern int git_config_parse_key(const char *, char **, int *); extern int git_config_key_is_valid(const char *key); From 27382d073811bf6aed272e7c6a5cb32b5ef544c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:09 +0100 Subject: [PATCH 0024/1015] config: use OPT_FILENAME() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not handle prefix directly. It's simpler to use OPT_FILENAME() instead. The other reason for doing this is because this code (where the deleted code is) will be factored out and called when "prefix" is not available. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/config.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 771cfa54bd3a06..c22d25de129f8b 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -126,7 +126,7 @@ static struct option builtin_config_options[] = { OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), - OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), + OPT_FILENAME('f', "file", &given_config_source.file, N_("use given config file")), OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), @@ -657,10 +657,6 @@ int cmd_config(int argc, const char **argv, const char *prefix) "extension worktreeConfig is enabled. " "Please read \"CONFIGURATION FILE\"\n" "section in \"git help worktree\" for details")); - } else if (given_config_source.file) { - if (!is_absolute_path(given_config_source.file) && prefix) - given_config_source.file = - prefix_filename(prefix, given_config_source.file); } if (respect_includes_opt == -1) From a12c1ff3a57b70116ea887cfac84b0d6f20c9e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:10 +0100 Subject: [PATCH 0025/1015] config: factor out set_config_source_file() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next patch, "config" is taught to move some config variables from one file to another. We need to specify two locations, source and destination, and the plan is reusing the same --global/--local/... option. Factor this code out for reuse later. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/config.c | 113 +++++++++++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index c22d25de129f8b..61a6a5a0e14052 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -71,6 +71,64 @@ static int show_origin; static NORETURN void usage_builtin_config(void); +static void set_config_source_file(void) +{ + int nongit = !startup_info->have_repository; + + if (use_global_config + use_system_config + use_local_config + + use_worktree_config + + !!given_config_source.file + !!given_config_source.blob > 1) + die(_("only one config file at a time")); + + if (use_local_config && nongit) + die(_("--local can only be used inside a git repository")); + + if (given_config_source.blob && nongit) + die(_("--blob can only be used inside a git repository")); + + if (given_config_source.file && + !strcmp(given_config_source.file, "-")) { + given_config_source.file = NULL; + given_config_source.use_stdin = 1; + } + + if (use_global_config) { + char *user_config = expand_user_path("~/.gitconfig", 0); + char *xdg_config = xdg_config_home("config"); + + if (!user_config) + /* + * It is unknown if HOME/.gitconfig exists, so + * we do not know if we should write to XDG + * location; error out even if XDG_CONFIG_HOME + * is set and points at a sane location. + */ + die(_("$HOME not set")); + + if (access_or_warn(user_config, R_OK, 0) && + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { + given_config_source.file = xdg_config; + free(user_config); + } else { + given_config_source.file = user_config; + free(xdg_config); + } + } + else if (use_system_config) + given_config_source.file = git_etc_gitconfig(); + else if (use_local_config) + given_config_source.file = git_pathdup("config"); + else if (use_worktree_config) { + given_config_source.file = get_worktree_config(the_repository); + if (!given_config_source.file) + die(_("--worktree cannot be used with multiple " + "working trees unless the config\n" + "extension worktreeConfig is enabled. " + "Please read \"CONFIGURATION FILE\"\n" + "section in \"git help worktree\" for details")); + } +} + static int option_parse_type(const struct option *opt, const char *arg, int unset) { @@ -604,60 +662,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (use_global_config + use_system_config + use_local_config + - use_worktree_config + - !!given_config_source.file + !!given_config_source.blob > 1) { - error(_("only one config file at a time")); - usage_builtin_config(); - } - - if (use_local_config && nongit) - die(_("--local can only be used inside a git repository")); - - if (given_config_source.blob && nongit) - die(_("--blob can only be used inside a git repository")); - - if (given_config_source.file && - !strcmp(given_config_source.file, "-")) { - given_config_source.file = NULL; - given_config_source.use_stdin = 1; - } - - if (use_global_config) { - char *user_config = expand_user_path("~/.gitconfig", 0); - char *xdg_config = xdg_config_home("config"); - - if (!user_config) - /* - * It is unknown if HOME/.gitconfig exists, so - * we do not know if we should write to XDG - * location; error out even if XDG_CONFIG_HOME - * is set and points at a sane location. - */ - die(_("$HOME not set")); - - if (access_or_warn(user_config, R_OK, 0) && - xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { - given_config_source.file = xdg_config; - free(user_config); - } else { - given_config_source.file = user_config; - free(xdg_config); - } - } - else if (use_system_config) - given_config_source.file = git_etc_gitconfig(); - else if (use_local_config) - given_config_source.file = git_pathdup("config"); - else if (use_worktree_config) { - given_config_source.file = get_worktree_config(the_repository); - if (!given_config_source.file) - die(_("--worktree cannot be used with multiple " - "working trees unless the config\n" - "extension worktreeConfig is enabled. " - "Please read \"CONFIGURATION FILE\"\n" - "section in \"git help worktree\" for details")); - } + set_config_source_file(); if (respect_includes_opt == -1) config_options.respect_includes = !given_config_source.file; From 6f11fd5edb6cd0ba2fc7bd9ce81587758c2a5700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 27 Dec 2018 16:56:11 +0100 Subject: [PATCH 0026/1015] config: add --move-to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This option can be used to move one or multiple variables from one place to another, e.g. to move some aliases from repo config to user config, or from repo config to per-worktree config. This will be useful for moving config variables around when extensions.worktreeConfig is enabled. E.g. git config --local --move-to --worktree core.worktree git config --local --move-glob-to --worktree 'submodule.*.*' The implementation is definitely not the best. We could for example lock both source and destination files before doing any update, and perhaps edit these files just once instead of once per key. But it adds a lot more complication to config update code. Let's stay with something simple for now. It's not worse than scripting using "git config". Optimization could be done later. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-config.txt | 12 ++++ Documentation/git-worktree.txt | 16 +++-- builtin/config.c | 120 +++++++++++++++++++++++++++++++++ t/t1300-config.sh | 54 +++++++++++++++ 4 files changed, 196 insertions(+), 6 deletions(-) diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 1bfe9f56a7b9b9..495bb574161ec9 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -20,6 +20,9 @@ SYNOPSIS 'git config' [] --unset-all name [value_regex] 'git config' [] --rename-section old_name new_name 'git config' [] --remove-section name +'git config' [] --move-to name +'git config' [] --move-regexp-to name-regexp +'git config' [] --move-glob-to name-glob 'git config' [] [--show-origin] [-z|--null] [--name-only] -l | --list 'git config' [] --get-color name [default] 'git config' [] --get-colorbool name [stdout-is-tty] @@ -161,6 +164,15 @@ See also <>. --unset-all:: Remove all lines matching the key from config file. +--move-to:: +--move-regexp-to:: +--move-glob-to:: + Move a config variable (or multiple variables matching the + given regular expression or glob pattern) to a new file. Any + option about the config file location after `--move-to` or + `--move-to-regexp` specifies the move destination. Existing + config of the same name remains. + -l:: --list:: List all variables set in config file, along with their values. diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index cb86318f3e1b34..aae8e1d8b2f3fa 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -252,13 +252,17 @@ rev-parse --git-path config.worktree`. You can add or update configuration in this file with `git config --worktree`. Older Git versions will refuse to access repositories with this extension. -Note that in this file, the exception for `core.bare` and `core.worktree` -is gone. If you have them in $GIT_DIR/config before, you must move -them to the `config.worktree` of the main working tree. You may also -take this opportunity to review and move other configuration that you -do not want to share to all working trees: +Note that in this file, the exception for `core.bare` and +`core.worktree` is gone. If you have them in $GIT_DIR/config before, +you must move them to the `config.worktree` of the main working tree. - - `core.worktree` and `core.bare` should never be shared +------------ +$ git config --local --move-to --worktree core.bare +$ git config --local --move-to --worktree core.worktree +------------ + +You may also take this opportunity to review and move other +configuration that you do not want to share to all working trees: - `core.sparseCheckout` is recommended per working tree, unless you are sure you always use sparse checkout for all working trees. diff --git a/builtin/config.c b/builtin/config.c index 61a6a5a0e14052..20266824f3892c 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -26,6 +26,7 @@ static char term = '\n'; static int use_global_config, use_system_config, use_local_config; static int use_worktree_config; +static struct git_config_source move_source; static struct git_config_source given_config_source; static int actions, type; static char *default_value; @@ -50,6 +51,9 @@ static int show_origin; #define ACTION_GET_COLOR (1<<13) #define ACTION_GET_COLORBOOL (1<<14) #define ACTION_GET_URLMATCH (1<<15) +#define ACTION_MOVE (1<<16) +#define ACTION_MOVE_REGEXP (1<<17) +#define ACTION_MOVE_GLOB (1<<18) /* * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than @@ -178,6 +182,25 @@ static int option_parse_type(const struct option *opt, const char *arg, return 0; } +static int option_move_cb(const struct option *opt, + const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + set_config_source_file(); + memcpy(&move_source, &given_config_source, sizeof(move_source)); + + memset(&given_config_source, 0, sizeof(given_config_source)); + use_global_config = 0; + use_system_config = 0; + use_local_config = 0; + use_worktree_config = 0; + + actions = opt->defval; + return 0; +} + static struct option builtin_config_options[] = { OPT_GROUP(N_("Config file location")), OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), @@ -197,6 +220,18 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL), OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), + { OPTION_CALLBACK, 0, "move-to", NULL, NULL, + N_("move a variable to a different config file"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + option_move_cb, ACTION_MOVE }, + { OPTION_CALLBACK, 0, "move-regexp-to", NULL, NULL, + N_("move matching variables to a different config file"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + option_move_cb, ACTION_MOVE_REGEXP }, + { OPTION_CALLBACK, 0, "move-glob-to", NULL, NULL, + N_("move matching variables to a different config file"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + option_move_cb, ACTION_MOVE_GLOB }, OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), @@ -426,6 +461,84 @@ static int get_value(const char *key_, const char *regex_) return ret; } +struct move_config_cb { + struct string_list keys; + const char *key; + regex_t key_re; +}; + +static int collect_move_config(const char *key, const char *value, void *cb) +{ + struct move_config_cb *data = cb; + + switch (actions) { + case ACTION_MOVE: + if (strcasecmp(data->key, key)) + return 0; + break; + case ACTION_MOVE_REGEXP: + if (regexec(&data->key_re, key, 0, NULL, 0)) + return 0; + break; + case ACTION_MOVE_GLOB: + if (wildmatch(data->key, key, WM_CASEFOLD)) + return 0; + break; + default: + BUG("action %d cannot get here", actions); + } + + string_list_append(&data->keys, key)->util = xstrdup(value); + return 0; +} + +static int move_config(const char *key) +{ + struct move_config_cb cb; + int i, ret = 0; + + config_options.respect_includes = 0; + if (!move_source.file && !move_source.use_stdin && !move_source.blob) + die(_("unknown config source")); + + string_list_init(&cb.keys, 1); + cb.key = key; + if (actions == ACTION_MOVE_REGEXP && + regcomp(&cb.key_re, key, REG_EXTENDED | REG_ICASE)) + die(_("invalid key pattern: %s"), key); + + config_with_options(collect_move_config, &cb, + &move_source, &config_options); + + for (i = 0; i < cb.keys.nr && !ret; i++) { + const char *key = cb.keys.items[i].string; + const char *value = cb.keys.items[i].util; + const char *dest = given_config_source.file; + + ret = git_config_set_multivar_in_file_gently( + dest, key, value, CONFIG_REGEX_NONE, 0); + } + + /* + * OK all keys have been copied successfully, time to delete + * old ones + */ + if (!ret && move_source.file) { + for (i = 0; i < cb.keys.nr; i++) { + const char *key = cb.keys.items[i].string; + const char *src = move_source.file; + + git_config_set_multivar_in_file_gently( + src, key, NULL, NULL, 1); + } + } + + string_list_clear(&cb.keys, 1); + if (actions == ACTION_MOVE_REGEXP) + regfree(&cb.key_re); + return ret; +} + static char *normalize_value(const char *key, const char *value) { if (!value) @@ -862,6 +975,13 @@ int cmd_config(int argc, const char **argv, const char *prefix) color_stdout_is_tty = git_config_bool("command line", argv[1]); return get_colorbool(argv[0], argc == 2); } + else if (actions == ACTION_MOVE || + actions == ACTION_MOVE_REGEXP || + actions == ACTION_MOVE_GLOB) { + check_write(); + check_argc(argc, 1, 1); + return move_config(argv[0]); + } return 0; } diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 9652b241c7cf18..6969e6092bff44 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -1844,4 +1844,58 @@ test_expect_success '--replace-all does not invent newlines' ' test_cmp expect .git/config ' +test_expect_success '--move-to moves keys' ' + test_when_finished rm -f dest && + git config single.foo bar && + git config multi.foo bar1 && + git config --add multi.foo bar2 && + git config --local --move-to -f dest SINGLE.foo && + ! git config single.foo && + test_cmp_config bar -f dest single.foo && + git config --local --move-to -f dest multi.FOO && + ! git config multi.foo && + git config -f dest --get-all multi.foo | sort >actual && + cat >expected <<-\EOF && + bar1 + bar2 + EOF + test_cmp expected actual +' + +test_expect_success '--move-regexp-to moves keys' ' + test_when_finished rm -f dest && + git config single.foo bar && + git config multi.foo bar1 && + git config --add multi.foo bar2 && + git config --local --move-regexp-to -f dest "S.*OO" && + ! git config single.foo && + test_cmp_config bar -f dest single.foo && + git config --local --move-regexp-to -f dest ^multi && + ! git config multi.foo && + git config -f dest --get-all multi.foo | sort >actual && + cat >expected <<-\EOF && + bar1 + bar2 + EOF + test_cmp expected actual +' + +test_expect_success '--move-glob-to moves keys' ' + test_when_finished rm -f dest && + git config single.foo bar && + git config multi.foo bar1 && + git config --add multi.foo bar2 && + git config --local --move-glob-to -f dest "SINGLE.*" && + ! git config single.foo && + test_cmp_config bar -f dest single.foo && + git config --local --move-glob-to -f dest "m*.foo" && + ! git config multi.foo && + git config -f dest --get-all multi.foo | sort >actual && + cat >expected <<-\EOF && + bar1 + bar2 + EOF + test_cmp expected actual +' + test_done From 59b12ae96a738c3ad7cb9c0bfb8cad10f8cc242c Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Sun, 13 Jan 2019 21:02:18 +0000 Subject: [PATCH 0027/1015] config.h: fix hdr-check warnings commit 8f7c7f5555 ("config.c: add repo_config_set_worktree_gently()", 2018-12-27) adds three function declarations that cause the hdr-check make target to complain. The hdr-check complaint is caused by placing the new declarations, which include 'struct repository *' parameters, before the declaration of 'struct repository'. Move the struct declaration to the top of the file. Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.h b/config.h index 62204dc252a8b8..77c5b128730693 100644 --- a/config.h +++ b/config.h @@ -5,6 +5,7 @@ #include "string-list.h" struct object_id; +struct repository; /* git_config_parse_key() returns these negated: */ #define CONFIG_INVALID_KEY 1 @@ -215,7 +216,6 @@ extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); /* Functions for reading a repository's config */ -struct repository; extern void repo_config(struct repository *repo, config_fn_t fn, void *data); extern int repo_config_get_value(struct repository *repo, const char *key, const char **value); From 1987b0b20f7b29a25260fd25f907121d22b18b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 17 Jan 2019 20:05:00 +0700 Subject: [PATCH 0028/1015] parse-options.h: remove extern on function prototypes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- parse-options.h | 58 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/parse-options.h b/parse-options.h index 14fe32428e57ae..f5e7ec7d235d39 100644 --- a/parse-options.h +++ b/parse-options.h @@ -174,18 +174,18 @@ struct option { * for translation with N_(). * Returns the number of arguments left in argv[]. */ -extern int parse_options(int argc, const char **argv, const char *prefix, - const struct option *options, - const char * const usagestr[], int flags); +int parse_options(int argc, const char **argv, const char *prefix, + const struct option *options, + const char * const usagestr[], int flags); -extern NORETURN void usage_with_options(const char * const *usagestr, - const struct option *options); +NORETURN void usage_with_options(const char * const *usagestr, + const struct option *options); -extern NORETURN void usage_msg_opt(const char *msg, - const char * const *usagestr, - const struct option *options); +NORETURN void usage_msg_opt(const char *msg, + const char * const *usagestr, + const struct option *options); -extern int optbug(const struct option *opt, const char *reason); +int optbug(const struct option *opt, const char *reason); const char *optname(const struct option *opt, int flags); /* @@ -227,31 +227,31 @@ struct parse_opt_ctx_t { const char *prefix; }; -extern void parse_options_start(struct parse_opt_ctx_t *ctx, - int argc, const char **argv, const char *prefix, - const struct option *options, int flags); +void parse_options_start(struct parse_opt_ctx_t *ctx, + int argc, const char **argv, const char *prefix, + const struct option *options, int flags); -extern int parse_options_step(struct parse_opt_ctx_t *ctx, - const struct option *options, - const char * const usagestr[]); +int parse_options_step(struct parse_opt_ctx_t *ctx, + const struct option *options, + const char * const usagestr[]); -extern int parse_options_end(struct parse_opt_ctx_t *ctx); +int parse_options_end(struct parse_opt_ctx_t *ctx); -extern struct option *parse_options_concat(struct option *a, struct option *b); +struct option *parse_options_concat(struct option *a, struct option *b); /*----- some often used options -----*/ -extern int parse_opt_abbrev_cb(const struct option *, const char *, int); -extern int parse_opt_expiry_date_cb(const struct option *, const char *, int); -extern int parse_opt_color_flag_cb(const struct option *, const char *, int); -extern int parse_opt_verbosity_cb(const struct option *, const char *, int); -extern int parse_opt_object_name(const struct option *, const char *, int); -extern int parse_opt_commits(const struct option *, const char *, int); -extern int parse_opt_tertiary(const struct option *, const char *, int); -extern int parse_opt_string_list(const struct option *, const char *, int); -extern int parse_opt_noop_cb(const struct option *, const char *, int); -extern int parse_opt_unknown_cb(const struct option *, const char *, int); -extern int parse_opt_passthru(const struct option *, const char *, int); -extern int parse_opt_passthru_argv(const struct option *, const char *, int); +int parse_opt_abbrev_cb(const struct option *, const char *, int); +int parse_opt_expiry_date_cb(const struct option *, const char *, int); +int parse_opt_color_flag_cb(const struct option *, const char *, int); +int parse_opt_verbosity_cb(const struct option *, const char *, int); +int parse_opt_object_name(const struct option *, const char *, int); +int parse_opt_commits(const struct option *, const char *, int); +int parse_opt_tertiary(const struct option *, const char *, int); +int parse_opt_string_list(const struct option *, const char *, int); +int parse_opt_noop_cb(const struct option *, const char *, int); +int parse_opt_unknown_cb(const struct option *, const char *, int); +int parse_opt_passthru(const struct option *, const char *, int); +int parse_opt_passthru_argv(const struct option *, const char *, int); #define OPT__VERBOSE(var, h) OPT_COUNTUP('v', "verbose", (var), (h)) #define OPT__QUIET(var, h) OPT_COUNTUP('q', "quiet", (var), (h)) From 13019979b811d26f4838d09331c7ddd8223d270d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C3=85gren?= Date: Tue, 22 Jan 2019 22:45:47 +0100 Subject: [PATCH 0029/1015] setup: free old value before setting `work_tree` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before assigning to `data->work_tree` in `read_worktree_config()`, free any value we might already have picked up, so that we do not leak it. Signed-off-by: Martin Ågren Signed-off-by: Junio C Hamano --- setup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.c b/setup.c index 1be5037f129646..bb633942bba11f 100644 --- a/setup.c +++ b/setup.c @@ -411,6 +411,7 @@ static int read_worktree_config(const char *var, const char *value, void *vdata) } else if (strcmp(var, "core.worktree") == 0) { if (!value) return config_error_nonbool(var); + free(data->work_tree); data->work_tree = xstrdup(value); } return 0; From 8e7e6c055e6754703145cb454b65582b15b059e5 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:15 -0800 Subject: [PATCH 0030/1015] commit-graph: return with errors during write The write_commit_graph() method uses die() to report failure and exit when confronted with an unexpected condition. This use of die() in a library function is incorrect and is now replaced by error() statements and an int return type. Now that we use 'goto cleanup' to jump to the terminal condition on an error, we have new paths that could lead to uninitialized values. New initializers are added to correct for this. The builtins 'commit-graph', 'gc', and 'commit' call these methods, so update them to check the return value. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/commit-graph.c | 19 +++++++------ builtin/commit.c | 5 ++-- builtin/gc.c | 7 ++--- commit-graph.c | 60 +++++++++++++++++++++++++++++------------- commit-graph.h | 10 +++---- 5 files changed, 62 insertions(+), 39 deletions(-) diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 4ae502754c292d..b12d46fdc88ca6 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -126,6 +126,7 @@ static int graph_write(int argc, const char **argv) struct string_list *pack_indexes = NULL; struct string_list *commit_hex = NULL; struct string_list lines; + int result; static struct option builtin_commit_graph_write_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, @@ -153,10 +154,8 @@ static int graph_write(int argc, const char **argv) read_replace_refs = 0; - if (opts.reachable) { - write_commit_graph_reachable(opts.obj_dir, opts.append, 1); - return 0; - } + if (opts.reachable) + return write_commit_graph_reachable(opts.obj_dir, opts.append, 1); string_list_init(&lines, 0); if (opts.stdin_packs || opts.stdin_commits) { @@ -173,14 +172,14 @@ static int graph_write(int argc, const char **argv) UNLEAK(buf); } - write_commit_graph(opts.obj_dir, - pack_indexes, - commit_hex, - opts.append, - 1); + result = write_commit_graph(opts.obj_dir, + pack_indexes, + commit_hex, + opts.append, + 1); UNLEAK(lines); - return 0; + return result; } int cmd_commit_graph(int argc, const char **argv, const char *prefix) diff --git a/builtin/commit.c b/builtin/commit.c index 004b816635bf16..04b0717b3551bc 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1667,8 +1667,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git reset HEAD\" to recover.")); - if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0)) - write_commit_graph_reachable(get_object_directory(), 0, 0); + if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) && + write_commit_graph_reachable(get_object_directory(), 0, 0)) + return 1; repo_rerere(the_repository, 0); run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); diff --git a/builtin/gc.c b/builtin/gc.c index 7696017cd4152e..9c6c9c90071d52 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -662,9 +662,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (pack_garbage.nr > 0) clean_pack_garbage(); - if (gc_write_commit_graph) - write_commit_graph_reachable(get_object_directory(), 0, - !quiet && !daemonized); + if (gc_write_commit_graph && + write_commit_graph_reachable(get_object_directory(), 0, + !quiet && !daemonized)) + return 1; if (auto_gc && too_many_loose_objects()) warning(_("There are too many unreachable loose objects; " diff --git a/commit-graph.c b/commit-graph.c index 3538ebff9c0186..80e7cc52749f9c 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -755,27 +755,30 @@ static int add_ref_to_list(const char *refname, return 0; } -void write_commit_graph_reachable(const char *obj_dir, int append, - int report_progress) +int write_commit_graph_reachable(const char *obj_dir, int append, + int report_progress) { struct string_list list = STRING_LIST_INIT_DUP; + int result; for_each_ref(add_ref_to_list, &list); - write_commit_graph(obj_dir, NULL, &list, append, report_progress); + result = write_commit_graph(obj_dir, NULL, &list, + append, report_progress); string_list_clear(&list, 0); + return result; } -void write_commit_graph(const char *obj_dir, - struct string_list *pack_indexes, - struct string_list *commit_hex, - int append, int report_progress) +int write_commit_graph(const char *obj_dir, + struct string_list *pack_indexes, + struct string_list *commit_hex, + int append, int report_progress) { struct packed_oid_list oids; struct packed_commit_list commits; struct hashfile *f; uint32_t i, count_distinct = 0; - char *graph_name; + char *graph_name = NULL; struct lock_file lk = LOCK_INIT; uint32_t chunk_ids[5]; uint64_t chunk_offsets[5]; @@ -787,15 +790,17 @@ void write_commit_graph(const char *obj_dir, struct strbuf progress_title = STRBUF_INIT; unsigned long approx_nr_objects; const unsigned hashsz = the_hash_algo->rawsz; + int res = 0; if (!commit_graph_compatible(the_repository)) - return; + return 0; oids.nr = 0; approx_nr_objects = approximate_object_count(); oids.alloc = approx_nr_objects / 32; oids.progress = NULL; oids.progress_done = 0; + commits.list = NULL; if (append) { prepare_commit_graph_one(the_repository, obj_dir); @@ -836,10 +841,16 @@ void write_commit_graph(const char *obj_dir, strbuf_setlen(&packname, dirlen); strbuf_addstr(&packname, pack_indexes->items[i].string); p = add_packed_git(packname.buf, packname.len, 1); - if (!p) - die(_("error adding pack %s"), packname.buf); - if (open_pack_index(p)) - die(_("error opening index for %s"), packname.buf); + if (!p) { + error(_("error adding pack %s"), packname.buf); + res = 1; + goto cleanup; + } + if (open_pack_index(p)) { + error(_("error opening index for %s"), packname.buf); + res = 1; + goto cleanup; + } for_each_object_in_pack(p, add_packed_commits, &oids, FOR_EACH_OBJECT_PACK_ORDER); close_pack(p); @@ -910,8 +921,11 @@ void write_commit_graph(const char *obj_dir, } stop_progress(&progress); - if (count_distinct >= GRAPH_PARENT_MISSING) - die(_("the commit graph format cannot write %d commits"), count_distinct); + if (count_distinct >= GRAPH_PARENT_MISSING) { + error(_("the commit graph format cannot write %d commits"), count_distinct); + res = 1; + goto cleanup; + } commits.nr = 0; commits.alloc = count_distinct; @@ -943,16 +957,21 @@ void write_commit_graph(const char *obj_dir, num_chunks = num_extra_edges ? 4 : 3; stop_progress(&progress); - if (commits.nr >= GRAPH_PARENT_MISSING) - die(_("too many commits to write graph")); + if (commits.nr >= GRAPH_PARENT_MISSING) { + error(_("too many commits to write graph")); + res = 1; + goto cleanup; + } compute_generation_numbers(&commits, report_progress); graph_name = get_commit_graph_filename(obj_dir); if (safe_create_leading_directories(graph_name)) { UNLEAK(graph_name); - die_errno(_("unable to create leading directories of %s"), - graph_name); + error(_("unable to create leading directories of %s"), + graph_name); + res = errno; + goto cleanup; } hold_lock_file_for_update(&lk, graph_name, LOCK_DIE_ON_ERROR); @@ -1011,9 +1030,12 @@ void write_commit_graph(const char *obj_dir, finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC); commit_lock_file(&lk); +cleanup: free(graph_name); free(commits.list); free(oids.list); + + return res; } #define VERIFY_COMMIT_GRAPH_ERROR_HASH 2 diff --git a/commit-graph.h b/commit-graph.h index e6aff2c2e1042f..cd333a0cd0c3ef 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -60,12 +60,12 @@ struct commit_graph *load_commit_graph_one(const char *graph_file); */ int generation_numbers_enabled(struct repository *r); -void write_commit_graph_reachable(const char *obj_dir, int append, +int write_commit_graph_reachable(const char *obj_dir, int append, int report_progress); -void write_commit_graph(const char *obj_dir, - struct string_list *pack_indexes, - struct string_list *commit_hex, - int append, int report_progress); +int write_commit_graph(const char *obj_dir, + struct string_list *pack_indexes, + struct string_list *commit_hex, + int append, int report_progress); int verify_commit_graph(struct repository *r, struct commit_graph *g); From 615151bf5c1cd15ba7efa19629d6016a9b4e1923 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:16 -0800 Subject: [PATCH 0031/1015] commit-graph: collapse parameters into flags The write_commit_graph() and write_commit_graph_reachable() methods currently take two boolean parameters: 'append' and 'report_progress'. We will soon expand the possible options to send to these methods, so instead of complicating the parameter list, first simplify it. Collapse these parameters into a 'flags' parameter, and adjust the callers to provide flags as necessary. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/commit-graph.c | 8 +++++--- builtin/commit.c | 2 +- builtin/gc.c | 4 ++-- commit-graph.c | 9 +++++---- commit-graph.h | 8 +++++--- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index b12d46fdc88ca6..0c92421f759347 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -127,6 +127,7 @@ static int graph_write(int argc, const char **argv) struct string_list *commit_hex = NULL; struct string_list lines; int result; + int flags = COMMIT_GRAPH_PROGRESS; static struct option builtin_commit_graph_write_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, @@ -151,11 +152,13 @@ static int graph_write(int argc, const char **argv) die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs")); if (!opts.obj_dir) opts.obj_dir = get_object_directory(); + if (opts.append) + flags |= COMMIT_GRAPH_APPEND; read_replace_refs = 0; if (opts.reachable) - return write_commit_graph_reachable(opts.obj_dir, opts.append, 1); + return write_commit_graph_reachable(opts.obj_dir, flags); string_list_init(&lines, 0); if (opts.stdin_packs || opts.stdin_commits) { @@ -175,8 +178,7 @@ static int graph_write(int argc, const char **argv) result = write_commit_graph(opts.obj_dir, pack_indexes, commit_hex, - opts.append, - 1); + flags); UNLEAK(lines); return result; diff --git a/builtin/commit.c b/builtin/commit.c index 04b0717b3551bc..3228de4e3c7f5c 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1668,7 +1668,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "not exceeded, and then \"git reset HEAD\" to recover.")); if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) && - write_commit_graph_reachable(get_object_directory(), 0, 0)) + write_commit_graph_reachable(get_object_directory(), 0)) return 1; repo_rerere(the_repository, 0); diff --git a/builtin/gc.c b/builtin/gc.c index 9c6c9c90071d52..198872206be8ca 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -663,8 +663,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) clean_pack_garbage(); if (gc_write_commit_graph && - write_commit_graph_reachable(get_object_directory(), 0, - !quiet && !daemonized)) + write_commit_graph_reachable(get_object_directory(), + !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0)) return 1; if (auto_gc && too_many_loose_objects()) diff --git a/commit-graph.c b/commit-graph.c index 80e7cc52749f9c..c7f3d75f01a95c 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -755,15 +755,14 @@ static int add_ref_to_list(const char *refname, return 0; } -int write_commit_graph_reachable(const char *obj_dir, int append, - int report_progress) +int write_commit_graph_reachable(const char *obj_dir, int flags) { struct string_list list = STRING_LIST_INIT_DUP; int result; for_each_ref(add_ref_to_list, &list); result = write_commit_graph(obj_dir, NULL, &list, - append, report_progress); + flags); string_list_clear(&list, 0); return result; @@ -772,7 +771,7 @@ int write_commit_graph_reachable(const char *obj_dir, int append, int write_commit_graph(const char *obj_dir, struct string_list *pack_indexes, struct string_list *commit_hex, - int append, int report_progress) + int flags) { struct packed_oid_list oids; struct packed_commit_list commits; @@ -791,6 +790,8 @@ int write_commit_graph(const char *obj_dir, unsigned long approx_nr_objects; const unsigned hashsz = the_hash_algo->rawsz; int res = 0; + int append = flags & COMMIT_GRAPH_APPEND; + int report_progress = flags & COMMIT_GRAPH_PROGRESS; if (!commit_graph_compatible(the_repository)) return 0; diff --git a/commit-graph.h b/commit-graph.h index cd333a0cd0c3ef..83fa5481380b32 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -60,12 +60,14 @@ struct commit_graph *load_commit_graph_one(const char *graph_file); */ int generation_numbers_enabled(struct repository *r); -int write_commit_graph_reachable(const char *obj_dir, int append, - int report_progress); +#define COMMIT_GRAPH_APPEND (1 << 0) +#define COMMIT_GRAPH_PROGRESS (1 << 1) + +int write_commit_graph_reachable(const char *obj_dir, int flags); int write_commit_graph(const char *obj_dir, struct string_list *pack_indexes, struct string_list *commit_hex, - int append, int report_progress); + int flags); int verify_commit_graph(struct repository *r, struct commit_graph *g); From b1beb0502e1bc5243236a177136de8929b42babb Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:17 -0800 Subject: [PATCH 0032/1015] commit-graph: create new version flags In anticipation of a new commit-graph file format version, create a flag for the write_commit_graph() and write_commit_graph_reachable() methods to take a version number. When there is no specified version, the implementation selects a default value. Currently, the only valid value is 1. The file format will change the header information, so place the existing header logic inside a switch statement with only one case. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- commit-graph.c | 58 +++++++++++++++++++++++++++++++++----------------- commit-graph.h | 1 + 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/commit-graph.c b/commit-graph.c index c7f3d75f01a95c..a087d7bda06aa9 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -25,9 +25,6 @@ #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16) -#define GRAPH_VERSION_1 0x1 -#define GRAPH_VERSION GRAPH_VERSION_1 - #define GRAPH_EXTRA_EDGES_NEEDED 0x80000000 #define GRAPH_PARENT_MISSING 0x7fffffff #define GRAPH_EDGE_LAST_MASK 0x7fffffff @@ -118,30 +115,35 @@ struct commit_graph *load_commit_graph_one(const char *graph_file) } graph_version = *(unsigned char*)(data + 4); - if (graph_version != GRAPH_VERSION) { + if (graph_version != 1) { error(_("graph version %X does not match version %X"), - graph_version, GRAPH_VERSION); - goto cleanup_fail; - } - - hash_version = *(unsigned char*)(data + 5); - if (hash_version != oid_version()) { - error(_("hash version %X does not match version %X"), - hash_version, oid_version()); + graph_version, 1); goto cleanup_fail; } graph = alloc_commit_graph(); + switch (graph_version) { + case 1: + hash_version = *(unsigned char*)(data + 5); + if (hash_version != oid_version()) { + error(_("hash version %X does not match version %X"), + hash_version, oid_version()); + goto cleanup_fail; + } + + graph->num_chunks = *(unsigned char*)(data + 6); + chunk_lookup = data + 8; + break; + } + graph->hash_len = the_hash_algo->rawsz; - graph->num_chunks = *(unsigned char*)(data + 6); graph->graph_fd = fd; graph->data = graph_map; graph->data_len = graph_size; last_chunk_id = 0; last_chunk_offset = 8; - chunk_lookup = data + 8; for (i = 0; i < graph->num_chunks; i++) { uint32_t chunk_id = get_be32(chunk_lookup + 0); uint64_t chunk_offset = get_be64(chunk_lookup + 4); @@ -792,10 +794,22 @@ int write_commit_graph(const char *obj_dir, int res = 0; int append = flags & COMMIT_GRAPH_APPEND; int report_progress = flags & COMMIT_GRAPH_PROGRESS; + int version = 0; + int header_size = 0; if (!commit_graph_compatible(the_repository)) return 0; + if (flags & COMMIT_GRAPH_VERSION_1) + version = 1; + if (!version) + version = 1; + if (version != 1) { + error(_("unsupported commit-graph version %d"), + version); + return 1; + } + oids.nr = 0; approx_nr_objects = approximate_object_count(); oids.alloc = approx_nr_objects / 32; @@ -980,10 +994,16 @@ int write_commit_graph(const char *obj_dir, hashwrite_be32(f, GRAPH_SIGNATURE); - hashwrite_u8(f, GRAPH_VERSION); - hashwrite_u8(f, oid_version()); - hashwrite_u8(f, num_chunks); - hashwrite_u8(f, 0); /* unused padding byte */ + hashwrite_u8(f, version); + + switch (version) { + case 1: + hashwrite_u8(f, oid_version()); + hashwrite_u8(f, num_chunks); + hashwrite_u8(f, 0); /* unused padding byte */ + header_size = 8; + break; + } chunk_ids[0] = GRAPH_CHUNKID_OIDFANOUT; chunk_ids[1] = GRAPH_CHUNKID_OIDLOOKUP; @@ -994,7 +1014,7 @@ int write_commit_graph(const char *obj_dir, chunk_ids[3] = 0; chunk_ids[4] = 0; - chunk_offsets[0] = 8 + (num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH; + chunk_offsets[0] = header_size + (num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH; chunk_offsets[1] = chunk_offsets[0] + GRAPH_FANOUT_SIZE; chunk_offsets[2] = chunk_offsets[1] + hashsz * commits.nr; chunk_offsets[3] = chunk_offsets[2] + (hashsz + 16) * commits.nr; diff --git a/commit-graph.h b/commit-graph.h index 83fa5481380b32..e03df54e33bf28 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -62,6 +62,7 @@ int generation_numbers_enabled(struct repository *r); #define COMMIT_GRAPH_APPEND (1 << 0) #define COMMIT_GRAPH_PROGRESS (1 << 1) +#define COMMIT_GRAPH_VERSION_1 (1 << 2) int write_commit_graph_reachable(const char *obj_dir, int flags); int write_commit_graph(const char *obj_dir, From 02fc77fc920dd917fd94f6d1bb849b8cb5987349 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:18 -0800 Subject: [PATCH 0033/1015] commit-graph: add --version= option Allo the commit-graph builtin to specify the file format version using the '--version=' option. Specify the version exactly in the verification tests as using a different version would change the offsets used in those tests. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- Documentation/git-commit-graph.txt | 3 +++ builtin/commit-graph.c | 13 +++++++++++-- t/t5318-commit-graph.sh | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/git-commit-graph.txt b/Documentation/git-commit-graph.txt index 624470e198202a..1d1cc70de40583 100644 --- a/Documentation/git-commit-graph.txt +++ b/Documentation/git-commit-graph.txt @@ -51,6 +51,9 @@ or `--stdin-packs`.) + With the `--append` option, include all commits that are present in the existing commit-graph file. ++ +With the `--version=` option, specify the file format version. Used +only for testing. 'read':: diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 0c92421f759347..b1bed842602b28 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -10,7 +10,7 @@ static char const * const builtin_commit_graph_usage[] = { N_("git commit-graph [--object-dir ]"), N_("git commit-graph read [--object-dir ]"), N_("git commit-graph verify [--object-dir ]"), - N_("git commit-graph write [--object-dir ] [--append] [--reachable|--stdin-packs|--stdin-commits]"), + N_("git commit-graph write [--object-dir ] [--append] [--reachable|--stdin-packs|--stdin-commits] [--version=]"), NULL }; @@ -25,7 +25,7 @@ static const char * const builtin_commit_graph_read_usage[] = { }; static const char * const builtin_commit_graph_write_usage[] = { - N_("git commit-graph write [--object-dir ] [--append] [--reachable|--stdin-packs|--stdin-commits]"), + N_("git commit-graph write [--object-dir ] [--append] [--reachable|--stdin-packs|--stdin-commits] [--version=]"), NULL }; @@ -35,6 +35,7 @@ static struct opts_commit_graph { int stdin_packs; int stdin_commits; int append; + int version; } opts; @@ -141,6 +142,8 @@ static int graph_write(int argc, const char **argv) N_("start walk at commits listed by stdin")), OPT_BOOL(0, "append", &opts.append, N_("include all commits already in the commit-graph file")), + OPT_INTEGER(0, "version", &opts.version, + N_("specify the file format version")), OPT_END(), }; @@ -155,6 +158,12 @@ static int graph_write(int argc, const char **argv) if (opts.append) flags |= COMMIT_GRAPH_APPEND; + switch (opts.version) { + case 1: + flags |= COMMIT_GRAPH_VERSION_1; + break; + } + read_replace_refs = 0; if (opts.reachable) diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index f4deb13b1da1ba..b79d6263e9cbb9 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -328,7 +328,7 @@ test_expect_success 'replace-objects invalidates commit-graph' ' test_expect_success 'git commit-graph verify' ' cd "$TRASH_DIRECTORY/full" && - git rev-parse commits/8 | git commit-graph write --stdin-commits && + git rev-parse commits/8 | git commit-graph write --stdin-commits --version=1 && git commit-graph verify >output ' From 1f39fd27b2eecabad73763cf20cb6c741ab8f97b Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:19 -0800 Subject: [PATCH 0034/1015] commit-graph: implement file format version 2 The commit-graph file format had some shortcomings which we now correct: 1. The hash algorithm was determined by a single byte, instead of the 4-byte format identifier. 2. There was no way to update the reachability index we used. We currently only support generation numbers, but that will change in the future. 3. Git did not fail with error if the unused eighth byte was non-zero, so we could not use that to indicate an incremental file format without breaking compatibility across versions. The new format modifies the header of the commit-graph to solve these problems. We use the 4-byte hash format id, freeing up a byte in our 32-bit alignment to introduce a reachability index version. We can also fail to read the commit-graph if the eighth byte is non-zero. The 'git commit-graph read' subcommand needs updating to show the new data. Set the default file format version to 2, and adjust the tests to expect the new 'git commit-graph read' output. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- .../technical/commit-graph-format.txt | 26 +++++++++- builtin/commit-graph.c | 9 ++++ commit-graph.c | 47 ++++++++++++++++--- commit-graph.h | 1 + t/t5318-commit-graph.sh | 9 +++- 5 files changed, 83 insertions(+), 9 deletions(-) diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index 16452a0504c8fa..e367aa94b15eed 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -31,13 +31,22 @@ and hash type. All 4-byte numbers are in network order. +There are two versions available, 1 and 2. These currently differ only in +the header. + HEADER: +All commit-graph files use the first five bytes for the same purpose. + 4-byte signature: The signature is: {'C', 'G', 'P', 'H'} 1-byte version number: - Currently, the only valid version is 1. + Currently, the valid version numbers are 1 and 2. + +The remainder of the header changes depending on the version. + +Version 1: 1-byte Hash Version (1 = SHA-1) We infer the hash length (H) from this value. @@ -47,6 +56,21 @@ HEADER: 1-byte (reserved for later use) Current clients should ignore this value. +Version 2: + + 1-byte number (C) of "chunks" + + 1-byte reachability index version number: + Currently, the only valid number is 1. + + 1-byte (reserved for later use) + Current clients expect this value to be zero, and will not + try to read the commit-graph file if it is non-zero. + + 4-byte format identifier for the hash algorithm: + If this identifier does not agree with the repository's current + hash algorithm, then the client will not read the commit graph. + CHUNK LOOKUP: (C + 1) * 12 bytes listing the table of contents for the chunks: diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index b1bed842602b28..28787d0c9cc457 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -102,6 +102,11 @@ static int graph_read(int argc, const char **argv) *(unsigned char*)(graph->data + 5), *(unsigned char*)(graph->data + 6), *(unsigned char*)(graph->data + 7)); + + if (*(unsigned char *)(graph->data + 4) == 2) + printf("hash algorithm: %X\n", + get_be32(graph->data + 8)); + printf("num_commits: %u\n", graph->num_commits); printf("chunks:"); @@ -162,6 +167,10 @@ static int graph_write(int argc, const char **argv) case 1: flags |= COMMIT_GRAPH_VERSION_1; break; + + case 2: + flags |= COMMIT_GRAPH_VERSION_2; + break; } read_replace_refs = 0; diff --git a/commit-graph.c b/commit-graph.c index a087d7bda06aa9..3c83263f11a1b8 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -90,7 +90,8 @@ struct commit_graph *load_commit_graph_one(const char *graph_file) uint64_t last_chunk_offset; uint32_t last_chunk_id; uint32_t graph_signature; - unsigned char graph_version, hash_version; + unsigned char graph_version, hash_version, reach_index_version; + uint32_t hash_id; if (fd < 0) return NULL; @@ -115,9 +116,9 @@ struct commit_graph *load_commit_graph_one(const char *graph_file) } graph_version = *(unsigned char*)(data + 4); - if (graph_version != 1) { - error(_("graph version %X does not match version %X"), - graph_version, 1); + if (!graph_version || graph_version > 2) { + error(_("unsupported graph version %X"), + graph_version); goto cleanup_fail; } @@ -135,6 +136,30 @@ struct commit_graph *load_commit_graph_one(const char *graph_file) graph->num_chunks = *(unsigned char*)(data + 6); chunk_lookup = data + 8; break; + + case 2: + graph->num_chunks = *(unsigned char *)(data + 5); + + reach_index_version = *(unsigned char *)(data + 6); + if (reach_index_version != 1) { + error(_("unsupported reachability index version %d"), + reach_index_version); + goto cleanup_fail; + } + + if (*(unsigned char*)(data + 7)) { + error(_("unsupported value in commit-graph header")); + goto cleanup_fail; + } + + hash_id = get_be32(data + 8); + if (hash_id != the_hash_algo->format_id) { + error(_("commit-graph hash algorithm does not match current algorithm")); + goto cleanup_fail; + } + + chunk_lookup = data + 12; + break; } graph->hash_len = the_hash_algo->rawsz; @@ -802,9 +827,11 @@ int write_commit_graph(const char *obj_dir, if (flags & COMMIT_GRAPH_VERSION_1) version = 1; + if (flags & COMMIT_GRAPH_VERSION_2) + version = 2; if (!version) - version = 1; - if (version != 1) { + version = 2; + if (version <= 0 || version > 2) { error(_("unsupported commit-graph version %d"), version); return 1; @@ -1003,6 +1030,14 @@ int write_commit_graph(const char *obj_dir, hashwrite_u8(f, 0); /* unused padding byte */ header_size = 8; break; + + case 2: + hashwrite_u8(f, num_chunks); + hashwrite_u8(f, 1); /* reachability index version */ + hashwrite_u8(f, 0); /* unused padding byte */ + hashwrite_be32(f, the_hash_algo->format_id); + header_size = 12; + break; } chunk_ids[0] = GRAPH_CHUNKID_OIDFANOUT; diff --git a/commit-graph.h b/commit-graph.h index e03df54e33bf28..050137063b4f0c 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -63,6 +63,7 @@ int generation_numbers_enabled(struct repository *r); #define COMMIT_GRAPH_APPEND (1 << 0) #define COMMIT_GRAPH_PROGRESS (1 << 1) #define COMMIT_GRAPH_VERSION_1 (1 << 2) +#define COMMIT_GRAPH_VERSION_2 (1 << 3) int write_commit_graph_reachable(const char *obj_dir, int flags); int write_commit_graph(const char *obj_dir, diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index b79d6263e9cbb9..3ff5e3b48d2024 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -65,7 +65,8 @@ graph_read_expect() { NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) fi cat >expect <<- EOF - header: 43475048 1 1 $NUM_CHUNKS 0 + header: 43475048 2 $NUM_CHUNKS 1 0 + hash algorithm: 73686131 num_commits: $1 chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL EOF @@ -390,10 +391,14 @@ test_expect_success 'detect bad signature' ' ' test_expect_success 'detect bad version' ' - corrupt_graph_and_verify $GRAPH_BYTE_VERSION "\02" \ + corrupt_graph_and_verify $GRAPH_BYTE_VERSION "\03" \ "graph version" ' +test_expect_success 'detect version 2 with version 1 data' ' + corrupt_graph_and_verify $GRAPH_BYTE_VERSION "\02" \ + "reachability index version" +' test_expect_success 'detect bad hash version' ' corrupt_graph_and_verify $GRAPH_BYTE_HASH "\02" \ "hash version" From f7777349d6ac9e27eff1edb0d821fcace1accb98 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 23 Jan 2019 13:59:20 -0800 Subject: [PATCH 0035/1015] commit-graph: test verifying a corrupt v2 header The commit-graph file format v2 changes the v1 data only in the header information. Add tests that check the 'verify' subcommand catches corruption in the v2 header. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- t/t5318-commit-graph.sh | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 3ff5e3b48d2024..be7bbf911a830c 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -497,6 +497,37 @@ test_expect_success 'git fsck (checks commit-graph)' ' test_must_fail git fsck ' +test_expect_success 'rewrite commmit-graph with version 2' ' + rm -f .git/objects/info/commit-graph && + git commit-graph write --reachable --version=2 && + git commit-graph verify +' + +GRAPH_BYTE_CHUNK_COUNT=5 +GRAPH_BYTE_REACH_INDEX=6 +GRAPH_BYTE_UNUSED=7 +GRAPH_BYTE_HASH=8 + +test_expect_success 'detect low chunk count (v2)' ' + corrupt_graph_and_verify $GRAPH_CHUNK_COUNT "\02" \ + "missing the .* chunk" +' + +test_expect_success 'detect incorrect reachability index' ' + corrupt_graph_and_verify $GRAPH_REACH_INDEX "\03" \ + "reachability index version" +' + +test_expect_success 'detect non-zero unused byte' ' + corrupt_graph_and_verify $GRAPH_BYTE_UNUSED "\01" \ + "unsupported value" +' + +test_expect_success 'detect bad hash version (v2)' ' + corrupt_graph_and_verify $GRAPH_BYTE_HASH "\00" \ + "hash algorithm" +' + test_expect_success 'setup non-the_repository tests' ' rm -rf repo && git init repo && From fa1fb081406f33f633f4a593d68249259d58ec8c Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:51:56 -0800 Subject: [PATCH 0036/1015] multi-pack-index: prepare for 'expire' subcommand The multi-pack-index tracks objects in a collection of pack-files. Only one copy of each object is indexed, using the modified time of the pack-files to determine tie-breakers. It is possible to have a pack-file with no referenced objects because all objects have a duplicate in a newer pack-file. Introduce a new 'expire' subcommand to the multi-pack-index builtin. This subcommand will delete these unused pack-files and rewrite the multi-pack-index to no longer refer to those files. More details about the specifics will follow as the method is implemented. Add a test that verifies the 'expire' subcommand is correctly wired, but will still be valid when the verb is implemented. Specifically, create a set of packs that should all have referenced objects and should not be removed during an 'expire' operation. The packs are created carefully to ensure they have a specific order when sorted by size. This will be important in a later test. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- Documentation/git-multi-pack-index.txt | 5 +++ builtin/multi-pack-index.c | 4 ++- midx.c | 5 +++ midx.h | 1 + t/t5319-multi-pack-index.sh | 49 ++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index 1af406aca21922..6186c4c9369a9c 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -31,6 +31,11 @@ write:: verify:: Verify the contents of the MIDX file. +expire:: + Delete the pack-files that are tracked by the MIDX file, but + have no objects referenced by the MIDX. Rewrite the MIDX file + afterward to remove all references to these pack-files. + EXAMPLES -------- diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index fca70f8e4fcca8..145de3a46c2418 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -5,7 +5,7 @@ #include "midx.h" static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [--object-dir=] (write|verify)"), + N_("git multi-pack-index [--object-dir=] (write|verify|expire)"), NULL }; @@ -44,6 +44,8 @@ int cmd_multi_pack_index(int argc, const char **argv, return write_midx_file(opts.object_dir); if (!strcmp(argv[0], "verify")) return verify_midx_file(opts.object_dir); + if (!strcmp(argv[0], "expire")) + return expire_midx_packs(opts.object_dir); die(_("unrecognized verb: %s"), argv[0]); } diff --git a/midx.c b/midx.c index 2a6a24fcd7eff1..0db5efd3562cc6 100644 --- a/midx.c +++ b/midx.c @@ -1025,3 +1025,8 @@ int verify_midx_file(const char *object_dir) return verify_midx_error; } + +int expire_midx_packs(const char *object_dir) +{ + return 0; +} diff --git a/midx.h b/midx.h index 774f652530c429..e3a2b740b59057 100644 --- a/midx.h +++ b/midx.h @@ -49,6 +49,7 @@ int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, i int write_midx_file(const char *object_dir); void clear_midx_file(struct repository *r); int verify_midx_file(const char *object_dir); +int expire_midx_packs(const char *object_dir); void close_midx(struct multi_pack_index *m); diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 70926b5bc04643..a8528f7da08c4f 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -348,4 +348,53 @@ test_expect_success 'verify incorrect 64-bit offset' ' "incorrect object offset" ' +test_expect_success 'setup expire tests' ' + mkdir dup && + ( + cd dup && + git init && + test-tool genrandom "data" 4096 >large_file.txt && + git update-index --add large_file.txt && + for i in $(test_seq 1 20) + do + test_commit $i + done && + git branch A HEAD && + git branch B HEAD~8 && + git branch C HEAD~13 && + git branch D HEAD~16 && + git branch E HEAD~18 && + git pack-objects --revs .git/objects/pack/pack-A <<-EOF && + refs/heads/A + ^refs/heads/B + EOF + git pack-objects --revs .git/objects/pack/pack-B <<-EOF && + refs/heads/B + ^refs/heads/C + EOF + git pack-objects --revs .git/objects/pack/pack-C <<-EOF && + refs/heads/C + ^refs/heads/D + EOF + git pack-objects --revs .git/objects/pack/pack-D <<-EOF && + refs/heads/D + ^refs/heads/E + EOF + git pack-objects --revs .git/objects/pack/pack-E <<-EOF && + refs/heads/E + EOF + git multi-pack-index write + ) +' + +test_expect_success 'expire does not remove any packs' ' + ( + cd dup && + ls .git/objects/pack >expect && + git multi-pack-index expire && + ls .git/objects/pack >actual && + test_cmp expect actual + ) +' + test_done From c33c399197ce170288b55c2c770aefcf056b66ef Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:51:57 -0800 Subject: [PATCH 0037/1015] midx: simplify computation of pack name lengths Before writing the multi-pack-index, we compute the length of the pack-index names concatenated together. This forms the data in the pack name chunk, and we precompute it to compute chunk offsets. The value is also modified to fit alignment needs. Previously, this computation was coupled with adding packs from the existing multi-pack-index and the remaining packs in the object dir not already covered by the multi-pack-index. In anticipation of this becoming more complicated with the 'expire' subcommand, simplify the computation by centralizing it to a single loop before writing the file. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- midx.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/midx.c b/midx.c index 0db5efd3562cc6..d0277ac1055f77 100644 --- a/midx.c +++ b/midx.c @@ -383,7 +383,6 @@ struct pack_list { uint32_t nr; uint32_t alloc_list; uint32_t alloc_names; - size_t pack_name_concat_len; struct multi_pack_index *m; }; @@ -418,7 +417,6 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, } packs->names[packs->nr] = xstrdup(file_name); - packs->pack_name_concat_len += strlen(file_name) + 1; packs->nr++; } } @@ -762,6 +760,7 @@ int write_midx_file(const char *object_dir) uint32_t nr_entries, num_large_offsets = 0; struct pack_midx_entry *entries = NULL; int large_offsets_needed = 0; + int pack_name_concat_len = 0; midx_name = get_midx_filename(object_dir); if (safe_create_leading_directories(midx_name)) { @@ -777,7 +776,6 @@ int write_midx_file(const char *object_dir) packs.alloc_names = packs.alloc_list; packs.list = NULL; packs.names = NULL; - packs.pack_name_concat_len = 0; ALLOC_ARRAY(packs.list, packs.alloc_list); ALLOC_ARRAY(packs.names, packs.alloc_names); @@ -788,7 +786,6 @@ int write_midx_file(const char *object_dir) packs.list[packs.nr] = NULL; packs.names[packs.nr] = xstrdup(packs.m->pack_names[i]); - packs.pack_name_concat_len += strlen(packs.names[packs.nr]) + 1; packs.nr++; } } @@ -798,10 +795,6 @@ int write_midx_file(const char *object_dir) if (packs.m && packs.nr == packs.m->num_packs) goto cleanup; - if (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) - packs.pack_name_concat_len += MIDX_CHUNK_ALIGNMENT - - (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT); - ALLOC_ARRAY(pack_perm, packs.nr); sort_packs_by_name(packs.names, packs.nr, pack_perm); @@ -814,6 +807,13 @@ int write_midx_file(const char *object_dir) large_offsets_needed = 1; } + for (i = 0; i < packs.nr; i++) + pack_name_concat_len += strlen(packs.names[i]) + 1; + + if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) + pack_name_concat_len += MIDX_CHUNK_ALIGNMENT - + (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT); + hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR); f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf); FREE_AND_NULL(midx_name); @@ -831,7 +831,7 @@ int write_midx_file(const char *object_dir) cur_chunk++; chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDFANOUT; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + packs.pack_name_concat_len; + chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len; cur_chunk++; chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP; From 5bf52fbccea44cc518b3bee9894f75618ed10a8d Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:51:57 -0800 Subject: [PATCH 0038/1015] midx: refactor permutation logic and pack sorting In anticipation of the expire subcommand, refactor the way we sort the packfiles by name. This will greatly simplify our approach to dropping expired packs from the list. First, create 'struct pack_info' to replace 'struct pack_pair'. This struct contains the necessary information about a pack, including its name, a pointer to its packfile struct (if not already in the multi-pack-index), and the original pack-int-id. Second, track the pack information using an array of pack_info structs in the pack_list struct. This simplifies the logic around the multiple arrays we were tracking in that struct. Finally, update get_sorted_entries() to not permute the pack-int-id and instead supply the permutation to write_midx_object_offsets(). This requires sorting the packs after get_sorted_entries(). Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- midx.c | 156 +++++++++++++++++++++++++-------------------------------- 1 file changed, 69 insertions(+), 87 deletions(-) diff --git a/midx.c b/midx.c index d0277ac1055f77..04499773f7965f 100644 --- a/midx.c +++ b/midx.c @@ -377,12 +377,23 @@ static size_t write_midx_header(struct hashfile *f, return MIDX_HEADER_SIZE; } +struct pack_info { + uint32_t orig_pack_int_id; + char *pack_name; + struct packed_git *p; +}; + +static int pack_info_compare(const void *_a, const void *_b) +{ + struct pack_info *a = (struct pack_info *)_a; + struct pack_info *b = (struct pack_info *)_b; + return strcmp(a->pack_name, b->pack_name); +} + struct pack_list { - struct packed_git **list; - char **names; + struct pack_info *info; uint32_t nr; - uint32_t alloc_list; - uint32_t alloc_names; + uint32_t alloc; struct multi_pack_index *m; }; @@ -395,66 +406,32 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, if (packs->m && midx_contains_pack(packs->m, file_name)) return; - ALLOC_GROW(packs->list, packs->nr + 1, packs->alloc_list); - ALLOC_GROW(packs->names, packs->nr + 1, packs->alloc_names); + ALLOC_GROW(packs->info, packs->nr + 1, packs->alloc); - packs->list[packs->nr] = add_packed_git(full_path, - full_path_len, - 0); + packs->info[packs->nr].p = add_packed_git(full_path, + full_path_len, + 0); - if (!packs->list[packs->nr]) { + if (!packs->info[packs->nr].p) { warning(_("failed to add packfile '%s'"), full_path); return; } - if (open_pack_index(packs->list[packs->nr])) { + if (open_pack_index(packs->info[packs->nr].p)) { warning(_("failed to open pack-index '%s'"), full_path); - close_pack(packs->list[packs->nr]); - FREE_AND_NULL(packs->list[packs->nr]); + close_pack(packs->info[packs->nr].p); + FREE_AND_NULL(packs->info[packs->nr].p); return; } - packs->names[packs->nr] = xstrdup(file_name); + packs->info[packs->nr].pack_name = xstrdup(file_name); + packs->info[packs->nr].orig_pack_int_id = packs->nr; packs->nr++; } } -struct pack_pair { - uint32_t pack_int_id; - char *pack_name; -}; - -static int pack_pair_compare(const void *_a, const void *_b) -{ - struct pack_pair *a = (struct pack_pair *)_a; - struct pack_pair *b = (struct pack_pair *)_b; - return strcmp(a->pack_name, b->pack_name); -} - -static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *perm) -{ - uint32_t i; - struct pack_pair *pairs; - - ALLOC_ARRAY(pairs, nr_packs); - - for (i = 0; i < nr_packs; i++) { - pairs[i].pack_int_id = i; - pairs[i].pack_name = pack_names[i]; - } - - QSORT(pairs, nr_packs, pack_pair_compare); - - for (i = 0; i < nr_packs; i++) { - pack_names[i] = pairs[i].pack_name; - perm[pairs[i].pack_int_id] = i; - } - - free(pairs); -} - struct pack_midx_entry { struct object_id oid; uint32_t pack_int_id; @@ -480,7 +457,6 @@ static int midx_oid_compare(const void *_a, const void *_b) } static int nth_midxed_pack_midx_entry(struct multi_pack_index *m, - uint32_t *pack_perm, struct pack_midx_entry *e, uint32_t pos) { @@ -488,7 +464,7 @@ static int nth_midxed_pack_midx_entry(struct multi_pack_index *m, return 1; nth_midxed_object_oid(&e->oid, m, pos); - e->pack_int_id = pack_perm[nth_midxed_pack_int_id(m, pos)]; + e->pack_int_id = nth_midxed_pack_int_id(m, pos); e->offset = nth_midxed_offset(m, pos); /* consider objects in midx to be from "old" packs */ @@ -522,8 +498,7 @@ static void fill_pack_entry(uint32_t pack_int_id, * of a packfile containing the object). */ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, - struct packed_git **p, - uint32_t *perm, + struct pack_info *info, uint32_t nr_packs, uint32_t *nr_objects) { @@ -534,7 +509,7 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, uint32_t start_pack = m ? m->num_packs : 0; for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) - total_objects += p[cur_pack]->num_objects; + total_objects += info[cur_pack].p->num_objects; /* * As we de-duplicate by fanout value, we expect the fanout @@ -559,7 +534,7 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, for (cur_object = start; cur_object < end; cur_object++) { ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout); - nth_midxed_pack_midx_entry(m, perm, + nth_midxed_pack_midx_entry(m, &entries_by_fanout[nr_fanout], cur_object); nr_fanout++; @@ -570,12 +545,12 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, uint32_t start = 0, end; if (cur_fanout) - start = get_pack_fanout(p[cur_pack], cur_fanout - 1); - end = get_pack_fanout(p[cur_pack], cur_fanout); + start = get_pack_fanout(info[cur_pack].p, cur_fanout - 1); + end = get_pack_fanout(info[cur_pack].p, cur_fanout); for (cur_object = start; cur_object < end; cur_object++) { ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout); - fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]); + fill_pack_entry(cur_pack, info[cur_pack].p, cur_object, &entries_by_fanout[nr_fanout]); nr_fanout++; } } @@ -604,7 +579,7 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, } static size_t write_midx_pack_names(struct hashfile *f, - char **pack_names, + struct pack_info *info, uint32_t num_packs) { uint32_t i; @@ -612,14 +587,14 @@ static size_t write_midx_pack_names(struct hashfile *f, size_t written = 0; for (i = 0; i < num_packs; i++) { - size_t writelen = strlen(pack_names[i]) + 1; + size_t writelen = strlen(info[i].pack_name) + 1; - if (i && strcmp(pack_names[i], pack_names[i - 1]) <= 0) + if (i && strcmp(info[i].pack_name, info[i - 1].pack_name) <= 0) BUG("incorrect pack-file order: %s before %s", - pack_names[i - 1], - pack_names[i]); + info[i - 1].pack_name, + info[i].pack_name); - hashwrite(f, pack_names[i], writelen); + hashwrite(f, info[i].pack_name, writelen); written += writelen; } @@ -690,6 +665,7 @@ static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len, } static size_t write_midx_object_offsets(struct hashfile *f, int large_offset_needed, + uint32_t *perm, struct pack_midx_entry *objects, uint32_t nr_objects) { struct pack_midx_entry *list = objects; @@ -699,7 +675,7 @@ static size_t write_midx_object_offsets(struct hashfile *f, int large_offset_nee for (i = 0; i < nr_objects; i++) { struct pack_midx_entry *obj = list++; - hashwrite_be32(f, obj->pack_int_id); + hashwrite_be32(f, perm[obj->pack_int_id]); if (large_offset_needed && obj->offset >> 31) hashwrite_be32(f, MIDX_LARGE_OFFSET_NEEDED | nr_large_offset++); @@ -772,20 +748,17 @@ int write_midx_file(const char *object_dir) packs.m = load_multi_pack_index(object_dir, 1); packs.nr = 0; - packs.alloc_list = packs.m ? packs.m->num_packs : 16; - packs.alloc_names = packs.alloc_list; - packs.list = NULL; - packs.names = NULL; - ALLOC_ARRAY(packs.list, packs.alloc_list); - ALLOC_ARRAY(packs.names, packs.alloc_names); + packs.alloc = packs.m ? packs.m->num_packs : 16; + packs.info = NULL; + ALLOC_ARRAY(packs.info, packs.alloc); if (packs.m) { for (i = 0; i < packs.m->num_packs; i++) { - ALLOC_GROW(packs.list, packs.nr + 1, packs.alloc_list); - ALLOC_GROW(packs.names, packs.nr + 1, packs.alloc_names); + ALLOC_GROW(packs.info, packs.nr + 1, packs.alloc); - packs.list[packs.nr] = NULL; - packs.names[packs.nr] = xstrdup(packs.m->pack_names[i]); + packs.info[packs.nr].orig_pack_int_id = i; + packs.info[packs.nr].pack_name = xstrdup(packs.m->pack_names[i]); + packs.info[packs.nr].p = NULL; packs.nr++; } } @@ -795,10 +768,7 @@ int write_midx_file(const char *object_dir) if (packs.m && packs.nr == packs.m->num_packs) goto cleanup; - ALLOC_ARRAY(pack_perm, packs.nr); - sort_packs_by_name(packs.names, packs.nr, pack_perm); - - entries = get_sorted_entries(packs.m, packs.list, pack_perm, packs.nr, &nr_entries); + entries = get_sorted_entries(packs.m, packs.info, packs.nr, &nr_entries); for (i = 0; i < nr_entries; i++) { if (entries[i].offset > 0x7fffffff) @@ -807,8 +777,21 @@ int write_midx_file(const char *object_dir) large_offsets_needed = 1; } + QSORT(packs.info, packs.nr, pack_info_compare); + + /* + * pack_perm stores a permutation between pack-int-ids from the + * previous multi-pack-index to the new one we are writing: + * + * pack_perm[old_id] = new_id + */ + ALLOC_ARRAY(pack_perm, packs.nr); + for (i = 0; i < packs.nr; i++) { + pack_perm[packs.info[i].orig_pack_int_id] = i; + } + for (i = 0; i < packs.nr; i++) - pack_name_concat_len += strlen(packs.names[i]) + 1; + pack_name_concat_len += strlen(packs.info[i].pack_name) + 1; if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) pack_name_concat_len += MIDX_CHUNK_ALIGNMENT - @@ -879,7 +862,7 @@ int write_midx_file(const char *object_dir) switch (chunk_ids[i]) { case MIDX_CHUNKID_PACKNAMES: - written += write_midx_pack_names(f, packs.names, packs.nr); + written += write_midx_pack_names(f, packs.info, packs.nr); break; case MIDX_CHUNKID_OIDFANOUT: @@ -891,7 +874,7 @@ int write_midx_file(const char *object_dir) break; case MIDX_CHUNKID_OBJECTOFFSETS: - written += write_midx_object_offsets(f, large_offsets_needed, entries, nr_entries); + written += write_midx_object_offsets(f, large_offsets_needed, pack_perm, entries, nr_entries); break; case MIDX_CHUNKID_LARGEOFFSETS: @@ -914,15 +897,14 @@ int write_midx_file(const char *object_dir) cleanup: for (i = 0; i < packs.nr; i++) { - if (packs.list[i]) { - close_pack(packs.list[i]); - free(packs.list[i]); + if (packs.info[i].p) { + close_pack(packs.info[i].p); + free(packs.info[i].p); } - free(packs.names[i]); + free(packs.info[i].pack_name); } - free(packs.list); - free(packs.names); + free(packs.info); free(entries); free(pack_perm); free(midx_name); From 3c9e7185936330e33ac203aab8d215de15bee765 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:51:59 -0800 Subject: [PATCH 0039/1015] multi-pack-index: implement 'expire' subcommand The 'git multi-pack-index expire' subcommand looks at the existing mult-pack-index, counts the number of objects referenced in each pack-file, deletes the pack-fils with no referenced objects, and rewrites the multi-pack-index to no longer reference those packs. Refactor the write_midx_file() method to call write_midx_internal() which now takes an existing 'struct multi_pack_index' and a list of pack-files to drop (as specified by the names of their pack- indexes). As we write the new multi-pack-index, we drop those file names from the list of known pack-files. The expire_midx_packs() method removes the unreferenced pack-files after carefully closing the packs to avoid open handles. Test that a new pack-file that covers the contents of two other pack-files leads to those pack-files being deleted during the expire subcommand. Be sure to read the multi-pack-index to ensure it no longer references those packs. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- midx.c | 120 +++++++++++++++++++++++++++++++++--- t/t5319-multi-pack-index.sh | 20 ++++++ 2 files changed, 130 insertions(+), 10 deletions(-) diff --git a/midx.c b/midx.c index 04499773f7965f..d6fa0253c204da 100644 --- a/midx.c +++ b/midx.c @@ -33,6 +33,8 @@ #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t)) #define MIDX_LARGE_OFFSET_NEEDED 0x80000000 +#define PACK_EXPIRED UINT_MAX + static char *get_midx_filename(const char *object_dir) { return xstrfmt("%s/pack/multi-pack-index", object_dir); @@ -381,6 +383,7 @@ struct pack_info { uint32_t orig_pack_int_id; char *pack_name; struct packed_git *p; + unsigned expired : 1; }; static int pack_info_compare(const void *_a, const void *_b) @@ -428,6 +431,7 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len, packs->info[packs->nr].pack_name = xstrdup(file_name); packs->info[packs->nr].orig_pack_int_id = packs->nr; + packs->info[packs->nr].expired = 0; packs->nr++; } } @@ -587,13 +591,17 @@ static size_t write_midx_pack_names(struct hashfile *f, size_t written = 0; for (i = 0; i < num_packs; i++) { - size_t writelen = strlen(info[i].pack_name) + 1; + size_t writelen; + + if (info[i].expired) + continue; if (i && strcmp(info[i].pack_name, info[i - 1].pack_name) <= 0) BUG("incorrect pack-file order: %s before %s", info[i - 1].pack_name, info[i].pack_name); + writelen = strlen(info[i].pack_name) + 1; hashwrite(f, info[i].pack_name, writelen); written += writelen; } @@ -675,6 +683,11 @@ static size_t write_midx_object_offsets(struct hashfile *f, int large_offset_nee for (i = 0; i < nr_objects; i++) { struct pack_midx_entry *obj = list++; + if (perm[obj->pack_int_id] == PACK_EXPIRED) + BUG("object %s is in an expired pack with int-id %d", + oid_to_hex(&obj->oid), + obj->pack_int_id); + hashwrite_be32(f, perm[obj->pack_int_id]); if (large_offset_needed && obj->offset >> 31) @@ -721,7 +734,8 @@ static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_off return written; } -int write_midx_file(const char *object_dir) +static int write_midx_internal(const char *object_dir, struct multi_pack_index *m, + struct string_list *packs_to_drop) { unsigned char cur_chunk, num_chunks = 0; char *midx_name; @@ -737,6 +751,8 @@ int write_midx_file(const char *object_dir) struct pack_midx_entry *entries = NULL; int large_offsets_needed = 0; int pack_name_concat_len = 0; + int dropped_packs = 0; + int result = 0; midx_name = get_midx_filename(object_dir); if (safe_create_leading_directories(midx_name)) { @@ -745,7 +761,10 @@ int write_midx_file(const char *object_dir) midx_name); } - packs.m = load_multi_pack_index(object_dir, 1); + if (m) + packs.m = m; + else + packs.m = load_multi_pack_index(object_dir, 1); packs.nr = 0; packs.alloc = packs.m ? packs.m->num_packs : 16; @@ -759,13 +778,14 @@ int write_midx_file(const char *object_dir) packs.info[packs.nr].orig_pack_int_id = i; packs.info[packs.nr].pack_name = xstrdup(packs.m->pack_names[i]); packs.info[packs.nr].p = NULL; + packs.info[packs.nr].expired = 0; packs.nr++; } } for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs); - if (packs.m && packs.nr == packs.m->num_packs) + if (packs.m && packs.nr == packs.m->num_packs && !packs_to_drop) goto cleanup; entries = get_sorted_entries(packs.m, packs.info, packs.nr, &nr_entries); @@ -779,6 +799,34 @@ int write_midx_file(const char *object_dir) QSORT(packs.info, packs.nr, pack_info_compare); + if (packs_to_drop && packs_to_drop->nr) { + int drop_index = 0; + int missing_drops = 0; + + for (i = 0; i < packs.nr && drop_index < packs_to_drop->nr; i++) { + int cmp = strcmp(packs.info[i].pack_name, + packs_to_drop->items[drop_index].string); + + if (!cmp) { + drop_index++; + packs.info[i].expired = 1; + } else if (cmp > 0) { + error(_("did not see pack-file %s to drop"), + packs_to_drop->items[drop_index].string); + drop_index++; + missing_drops++; + i--; + } else { + packs.info[i].expired = 0; + } + } + + if (missing_drops) { + result = 1; + goto cleanup; + } + } + /* * pack_perm stores a permutation between pack-int-ids from the * previous multi-pack-index to the new one we are writing: @@ -787,11 +835,18 @@ int write_midx_file(const char *object_dir) */ ALLOC_ARRAY(pack_perm, packs.nr); for (i = 0; i < packs.nr; i++) { - pack_perm[packs.info[i].orig_pack_int_id] = i; + if (packs.info[i].expired) { + dropped_packs++; + pack_perm[packs.info[i].orig_pack_int_id] = PACK_EXPIRED; + } else { + pack_perm[packs.info[i].orig_pack_int_id] = i - dropped_packs; + } } - for (i = 0; i < packs.nr; i++) - pack_name_concat_len += strlen(packs.info[i].pack_name) + 1; + for (i = 0; i < packs.nr; i++) { + if (!packs.info[i].expired) + pack_name_concat_len += strlen(packs.info[i].pack_name) + 1; + } if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) pack_name_concat_len += MIDX_CHUNK_ALIGNMENT - @@ -807,7 +862,7 @@ int write_midx_file(const char *object_dir) cur_chunk = 0; num_chunks = large_offsets_needed ? 5 : 4; - written = write_midx_header(f, num_chunks, packs.nr); + written = write_midx_header(f, num_chunks, packs.nr - dropped_packs); chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES; chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH; @@ -908,7 +963,12 @@ int write_midx_file(const char *object_dir) free(entries); free(pack_perm); free(midx_name); - return 0; + return result; +} + +int write_midx_file(const char *object_dir) +{ + return write_midx_internal(object_dir, NULL, NULL); } void clear_midx_file(struct repository *r) @@ -1010,5 +1070,45 @@ int verify_midx_file(const char *object_dir) int expire_midx_packs(const char *object_dir) { - return 0; + uint32_t i, *count, result = 0; + struct string_list packs_to_drop = STRING_LIST_INIT_DUP; + struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + + if (!m) + return 0; + + count = xcalloc(m->num_packs, sizeof(uint32_t)); + for (i = 0; i < m->num_objects; i++) { + int pack_int_id = nth_midxed_pack_int_id(m, i); + count[pack_int_id]++; + } + + for (i = 0; i < m->num_packs; i++) { + char *pack_name; + + if (count[i]) + continue; + + if (prepare_midx_pack(m, i)) + continue; + + if (m->packs[i]->pack_keep) + continue; + + pack_name = xstrdup(m->packs[i]->pack_name); + close_pack(m->packs[i]); + FREE_AND_NULL(m->packs[i]); + + string_list_insert(&packs_to_drop, m->pack_names[i]); + unlink_pack_path(pack_name, 0); + free(pack_name); + } + + free(count); + + if (packs_to_drop.nr) + result = write_midx_internal(object_dir, m, &packs_to_drop); + + string_list_clear(&packs_to_drop, 0); + return result; } diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index a8528f7da08c4f..65e85debec6734 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -397,4 +397,24 @@ test_expect_success 'expire does not remove any packs' ' ) ' +test_expect_success 'expire removes unreferenced packs' ' + ( + cd dup && + git pack-objects --revs .git/objects/pack/pack-combined <<-EOF && + refs/heads/A + ^refs/heads/C + EOF + git multi-pack-index write && + ls .git/objects/pack | grep -v -e pack-[AB] >expect && + git multi-pack-index expire && + ls .git/objects/pack >actual && + test_cmp expect actual && + ls .git/objects/pack/ | grep idx >expect-idx && + test-tool read-midx .git/objects | grep idx >actual-midx && + test_cmp expect-idx actual-midx && + git multi-pack-index verify && + git fsck + ) +' + test_done From 467ae6f9f331580a97fc05cfdc33b960cbcea848 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:51:59 -0800 Subject: [PATCH 0040/1015] multi-pack-index: prepare 'repack' subcommand In an environment where the multi-pack-index is useful, it is due to many pack-files and an inability to repack the object store into a single pack-file. However, it is likely that many of these pack-files are rather small, and could be repacked into a slightly larger pack-file without too much effort. It may also be important to ensure the object store is highly available and the repack operation does not interrupt concurrent git commands. Introduce a 'repack' subcommand to 'git multi-pack-index' that takes a '--batch-size' option. The subcommand will inspect the multi-pack-index for referenced pack-files whose size is smaller than the batch size, until collecting a list of pack-files whose sizes sum to larger than the batch size. Then, a new pack-file will be created containing the objects from those pack-files that are referenced by the multi-pack-index. The resulting pack is likely to actually be smaller than the batch size due to compression and the fact that there may be objects in the pack- files that have duplicate copies in other pack-files. The current change introduces the command-line arguments, and we add a test that ensures we parse these options properly. Since we specify a small batch size, we will guarantee that future implementations do not change the list of pack-files. In addition, we hard-code the modified times of the packs in the pack directory to ensure the list of packs sorted by modified time matches the order if sorted by size (ascending). This will be important in a future test. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- Documentation/git-multi-pack-index.txt | 11 +++++++++++ builtin/multi-pack-index.c | 12 ++++++++++-- midx.c | 5 +++++ midx.h | 1 + t/t5319-multi-pack-index.sh | 20 +++++++++++++++++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index 6186c4c9369a9c..de345c2400c6ab 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -36,6 +36,17 @@ expire:: have no objects referenced by the MIDX. Rewrite the MIDX file afterward to remove all references to these pack-files. +repack:: + Create a new pack-file containing objects in small pack-files + referenced by the multi-pack-index. Select the pack-files by + examining packs from oldest-to-newest, adding a pack if its + size is below the batch size. Stop adding packs when the sum + of sizes of the added packs is above the batch size. If the + total size does not reach the batch size, then do nothing. + Rewrite the multi-pack-index to reference the new pack-file. + A later run of 'git multi-pack-index expire' will delete the + pack-files that were part of this batch. + EXAMPLES -------- diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 145de3a46c2418..c66239de338d57 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -5,12 +5,13 @@ #include "midx.h" static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [--object-dir=] (write|verify|expire)"), + N_("git multi-pack-index [--object-dir=] (write|verify|expire|repack --batch-size=)"), NULL }; static struct opts_multi_pack_index { const char *object_dir; + unsigned long batch_size; } opts; int cmd_multi_pack_index(int argc, const char **argv, @@ -19,6 +20,8 @@ int cmd_multi_pack_index(int argc, const char **argv, static struct option builtin_multi_pack_index_options[] = { OPT_FILENAME(0, "object-dir", &opts.object_dir, N_("object directory containing set of packfile and pack-index pairs")), + OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, + N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_END(), }; @@ -40,6 +43,11 @@ int cmd_multi_pack_index(int argc, const char **argv, return 1; } + if (!strcmp(argv[0], "repack")) + return midx_repack(opts.object_dir, (size_t)opts.batch_size); + if (opts.batch_size) + die(_("--batch-size option is only for 'repack' subcommand")); + if (!strcmp(argv[0], "write")) return write_midx_file(opts.object_dir); if (!strcmp(argv[0], "verify")) @@ -47,5 +55,5 @@ int cmd_multi_pack_index(int argc, const char **argv, if (!strcmp(argv[0], "expire")) return expire_midx_packs(opts.object_dir); - die(_("unrecognized verb: %s"), argv[0]); + die(_("unrecognized subcommand: %s"), argv[0]); } diff --git a/midx.c b/midx.c index d6fa0253c204da..3bad45be1a1c24 100644 --- a/midx.c +++ b/midx.c @@ -1112,3 +1112,8 @@ int expire_midx_packs(const char *object_dir) string_list_clear(&packs_to_drop, 0); return result; } + +int midx_repack(const char *object_dir, size_t batch_size) +{ + return 0; +} diff --git a/midx.h b/midx.h index e3a2b740b59057..394a21ee96f0fd 100644 --- a/midx.h +++ b/midx.h @@ -50,6 +50,7 @@ int write_midx_file(const char *object_dir); void clear_midx_file(struct repository *r); int verify_midx_file(const char *object_dir); int expire_midx_packs(const char *object_dir); +int midx_repack(const char *object_dir, size_t batch_size); void close_midx(struct multi_pack_index *m); diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 65e85debec6734..26ae8b3f62e9a5 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -383,7 +383,8 @@ test_expect_success 'setup expire tests' ' git pack-objects --revs .git/objects/pack/pack-E <<-EOF && refs/heads/E EOF - git multi-pack-index write + git multi-pack-index write && + cp -r .git/objects/pack .git/objects/pack-backup ) ' @@ -417,4 +418,21 @@ test_expect_success 'expire removes unreferenced packs' ' ) ' +test_expect_success 'repack with minimum size does not alter existing packs' ' + ( + cd dup && + rm -rf .git/objects/pack && + mv .git/objects/pack-backup .git/objects/pack && + touch -m -t 201901010000 .git/objects/pack/pack-D* && + touch -m -t 201901010001 .git/objects/pack/pack-C* && + touch -m -t 201901010002 .git/objects/pack/pack-B* && + touch -m -t 201901010003 .git/objects/pack/pack-A* && + ls .git/objects/pack >expect && + MINSIZE=$(ls -l .git/objects/pack/*pack | awk "{print \$5;}" | sort -n | head -n 1) && + git multi-pack-index repack --batch-size=$MINSIZE && + ls .git/objects/pack >actual && + test_cmp expect actual + ) +' + test_done From 19c239d4726924835b8fc97776165f10d68bf534 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:52:00 -0800 Subject: [PATCH 0041/1015] midx: implement midx_repack() To repack using a multi-pack-index, first sort all pack-files by their modified time. Second, walk those pack-files from oldest to newest, adding the packs to a list if they are smaller than the given pack-size. Finally, collect the objects from the multi-pack- index that are in those packs and send them to 'git pack-objects'. While first designing a 'git multi-pack-index repack' operation, I started by collecting the batches based on the size of the objects instead of the size of the pack-files. This allows repacking a large pack-file that has very few referencd objects. However, this came at a significant cost of parsing pack-files instead of simply reading the multi-pack-index and getting the file information for the pack-files. This object-size idea could be a direction for future expansion in this area. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- midx.c | 109 +++++++++++++++++++++++++++++++++++- t/t5319-multi-pack-index.sh | 28 +++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/midx.c b/midx.c index 3bad45be1a1c24..5c875737a59579 100644 --- a/midx.c +++ b/midx.c @@ -8,6 +8,7 @@ #include "sha1-lookup.h" #include "midx.h" #include "progress.h" +#include "run-command.h" #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ #define MIDX_VERSION 1 @@ -1113,7 +1114,113 @@ int expire_midx_packs(const char *object_dir) return result; } -int midx_repack(const char *object_dir, size_t batch_size) +struct time_and_id { + timestamp_t mtime; + uint32_t pack_int_id; +}; + +static int compare_by_mtime(const void *a_, const void *b_) { + const struct time_and_id *a, *b; + + a = (const struct time_and_id *)a_; + b = (const struct time_and_id *)b_; + + if (a->mtime < b->mtime) + return -1; + if (a->mtime > b->mtime) + return 1; return 0; } + +int midx_repack(const char *object_dir, size_t batch_size) +{ + int result = 0; + uint32_t i, packs_to_repack; + size_t total_size; + struct time_and_id *pack_ti; + unsigned char *include_pack; + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf base_name = STRBUF_INIT; + struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + + if (!m) + return 0; + + include_pack = xcalloc(m->num_packs, sizeof(unsigned char)); + pack_ti = xcalloc(m->num_packs, sizeof(struct time_and_id)); + + for (i = 0; i < m->num_packs; i++) { + pack_ti[i].pack_int_id = i; + + if (prepare_midx_pack(m, i)) + continue; + + pack_ti[i].mtime = m->packs[i]->mtime; + } + QSORT(pack_ti, m->num_packs, compare_by_mtime); + + total_size = 0; + packs_to_repack = 0; + for (i = 0; total_size < batch_size && i < m->num_packs; i++) { + int pack_int_id = pack_ti[i].pack_int_id; + struct packed_git *p = m->packs[pack_int_id]; + + if (!p) + continue; + if (p->pack_size >= batch_size) + continue; + + packs_to_repack++; + total_size += p->pack_size; + include_pack[pack_int_id] = 1; + } + + if (total_size < batch_size || packs_to_repack < 2) + goto cleanup; + + argv_array_push(&cmd.args, "pack-objects"); + + strbuf_addstr(&base_name, object_dir); + strbuf_addstr(&base_name, "/pack/pack"); + argv_array_push(&cmd.args, base_name.buf); + strbuf_release(&base_name); + + cmd.git_cmd = 1; + cmd.in = cmd.out = -1; + + if (start_command(&cmd)) { + error(_("could not start pack-objects")); + result = 1; + goto cleanup; + } + + for (i = 0; i < m->num_objects; i++) { + struct object_id oid; + uint32_t pack_int_id = nth_midxed_pack_int_id(m, i); + + if (!include_pack[pack_int_id]) + continue; + + nth_midxed_object_oid(&oid, m, i); + xwrite(cmd.in, oid_to_hex(&oid), the_hash_algo->hexsz); + xwrite(cmd.in, "\n", 1); + } + close(cmd.in); + + if (finish_command(&cmd)) { + error(_("could not finish pack-objects")); + result = 1; + goto cleanup; + } + + result = write_midx_internal(object_dir, m, NULL); + m = NULL; + +cleanup: + if (m) + close_midx(m); + free(include_pack); + free(pack_ti); + return result; +} diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 26ae8b3f62e9a5..d6c13535149359 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -435,4 +435,32 @@ test_expect_success 'repack with minimum size does not alter existing packs' ' ) ' +test_expect_success 'repack creates a new pack' ' + ( + cd dup && + ls .git/objects/pack/*idx >idx-list && + test_line_count = 5 idx-list && + THIRD_SMALLEST_SIZE=$(ls -l .git/objects/pack/*pack | awk "{print \$5;}" | sort -n | head -n 3 | tail -n 1) && + BATCH_SIZE=$(($THIRD_SMALLEST_SIZE + 1)) && + git multi-pack-index repack --batch-size=$BATCH_SIZE && + ls .git/objects/pack/*idx >idx-list && + test_line_count = 6 idx-list && + test-tool read-midx .git/objects | grep idx >midx-list && + test_line_count = 6 midx-list + ) +' + +test_expect_success 'expire removes repacked packs' ' + ( + cd dup && + ls -al .git/objects/pack/*pack && + ls -S .git/objects/pack/*pack | head -n 4 >expect && + git multi-pack-index expire && + ls -S .git/objects/pack/*pack >actual && + test_cmp expect actual && + test-tool read-midx .git/objects | grep idx >midx-list && + test_line_count = 4 midx-list + ) +' + test_done From 411792887cff1d0e22b82847c942970992856660 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:52:01 -0800 Subject: [PATCH 0042/1015] multi-pack-index: test expire while adding packs During development of the multi-pack-index expire subcommand, a version went out that improperly computed the pack order if a new pack was introduced while other packs were being removed. Part of the subtlety of the bug involved the new pack being placed before other packs that already existed in the multi-pack-index. Add a test to t5319-multi-pack-index.sh that catches this issue. The test adds new packs that cause another pack to be expired, and creates new packs that are lexicographically sorted before and after the existing packs. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- t/t5319-multi-pack-index.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index d6c13535149359..19b769eea0c295 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -463,4 +463,36 @@ test_expect_success 'expire removes repacked packs' ' ) ' +test_expect_success 'expire works when adding new packs' ' + ( + cd dup && + git pack-objects --revs .git/objects/pack/pack-combined <<-EOF && + refs/heads/A + ^refs/heads/B + EOF + git pack-objects --revs .git/objects/pack/pack-combined <<-EOF && + refs/heads/B + ^refs/heads/C + EOF + git pack-objects --revs .git/objects/pack/pack-combined <<-EOF && + refs/heads/C + ^refs/heads/D + EOF + git multi-pack-index write && + git pack-objects --revs .git/objects/pack/a-pack <<-EOF && + refs/heads/D + ^refs/heads/E + EOF + git multi-pack-index write && + git pack-objects --revs .git/objects/pack/z-pack <<-EOF && + refs/heads/E + EOF + git multi-pack-index expire && + ls .git/objects/pack/ | grep idx >expect && + test-tool read-midx .git/objects | grep idx >actual && + test_cmp expect actual && + git multi-pack-index verify + ) +' + test_done From e3042fd8820edea624e5f44e0f8ad46ca050d8b2 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Thu, 24 Jan 2019 13:52:02 -0800 Subject: [PATCH 0043/1015] midx: add test that 'expire' respects .keep files The 'git multi-pack-index expire' subcommand may delete packs that are not needed from the perspective of the multi-pack-index. If a pack has a .keep file, then we should not delete that pack. Add a test that ensures we preserve a pack that would otherwise be expired. First, create a new pack that contains every object in the repo, then add it to the multi-pack-index. Then create a .keep file for a pack starting with "a-pack" that was added in the previous test. Finally, expire and verify that the pack remains and the other packs were expired. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- t/t5319-multi-pack-index.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 19b769eea0c295..bcfa52040124d9 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -495,4 +495,22 @@ test_expect_success 'expire works when adding new packs' ' ) ' +test_expect_success 'expire respects .keep files' ' + ( + cd dup && + git pack-objects --revs .git/objects/pack/pack-all <<-EOF && + refs/heads/A + EOF + git multi-pack-index write && + PACKA=$(ls .git/objects/pack/a-pack*\.pack | sed s/\.pack\$//) && + touch $PACKA.keep && + git multi-pack-index expire && + ls -S .git/objects/pack/a-pack* | grep $PACKA >a-pack-files && + test_line_count = 3 a-pack-files && + test-tool read-midx .git/objects | grep idx >midx-list && + test_line_count = 2 midx-list + ) +' + + test_done From 202fbb33156287feeabc39c46db0e9857cb97152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:23 +0700 Subject: [PATCH 0044/1015] parse-options: add one-shot mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to help reimplement diff_opt_parse() using parse_options(). The behavior of parse_options() is changed to be the same as the other: - no argv0 in argv[], everything can be processed - argv[] must not be updated, it's the caller's job to do that - return the number of arguments processed - leave all unknown options / non-options alone (this one can already be achieved with PARSE_OPT_KEEP_UNKNOWN and PARSE_OPT_STOP_AT_NON_OPTION) This mode is NOT supposed to stay here for long. It's to help converting diff/rev option parsing. Once that work is over and we can just use parse_options() throughout the code base, this will be deleted. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- parse-options.c | 26 ++++++++++++++++++++++---- parse-options.h | 17 +++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/parse-options.c b/parse-options.c index 9f84bacce64e72..740ae5438f93d6 100644 --- a/parse-options.c +++ b/parse-options.c @@ -416,15 +416,24 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, const struct option *options, int flags) { memset(ctx, 0, sizeof(*ctx)); - ctx->argc = ctx->total = argc - 1; - ctx->argv = argv + 1; - ctx->out = argv; + ctx->argc = argc; + ctx->argv = argv; + if (!(flags & PARSE_OPT_ONE_SHOT)) { + ctx->argc--; + ctx->argv++; + } + ctx->total = ctx->argc; + ctx->out = argv; ctx->prefix = prefix; ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0); ctx->flags = flags; if ((flags & PARSE_OPT_KEEP_UNKNOWN) && - (flags & PARSE_OPT_STOP_AT_NON_OPTION)) + (flags & PARSE_OPT_STOP_AT_NON_OPTION) && + !(flags & PARSE_OPT_ONE_SHOT)) BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); + if ((flags & PARSE_OPT_ONE_SHOT) && + (flags & PARSE_OPT_KEEP_ARGV0)) + BUG("Can't keep argv0 if you don't have it"); parse_options_check(options); } @@ -536,6 +545,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, for (; ctx->argc; ctx->argc--, ctx->argv++) { const char *arg = ctx->argv[0]; + if (ctx->flags & PARSE_OPT_ONE_SHOT && + ctx->argc != ctx->total) + break; + if (*arg != '-' || !arg[1]) { if (parse_nodash_opt(ctx, arg, options) == 0) continue; @@ -610,6 +623,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, } continue; unknown: + if (ctx->flags & PARSE_OPT_ONE_SHOT) + break; if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN)) return PARSE_OPT_UNKNOWN; ctx->out[ctx->cpidx++] = ctx->argv[0]; @@ -623,6 +638,9 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, int parse_options_end(struct parse_opt_ctx_t *ctx) { + if (ctx->flags & PARSE_OPT_ONE_SHOT) + return ctx->total - ctx->argc; + MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc); ctx->out[ctx->cpidx + ctx->argc] = NULL; return ctx->cpidx + ctx->argc; diff --git a/parse-options.h b/parse-options.h index f5e7ec7d235d39..d663b839734771 100644 --- a/parse-options.h +++ b/parse-options.h @@ -27,7 +27,8 @@ enum parse_opt_flags { PARSE_OPT_STOP_AT_NON_OPTION = 2, PARSE_OPT_KEEP_ARGV0 = 4, PARSE_OPT_KEEP_UNKNOWN = 8, - PARSE_OPT_NO_INTERNAL_HELP = 16 + PARSE_OPT_NO_INTERNAL_HELP = 16, + PARSE_OPT_ONE_SHOT = 32 }; enum parse_opt_option_flags { @@ -169,10 +170,18 @@ struct option { N_("no-op (backward compatibility)"), \ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb } -/* parse_options() will filter out the processed options and leave the - * non-option arguments in argv[]. usagestr strings should be marked - * for translation with N_(). +/* + * parse_options() will filter out the processed options and leave the + * non-option arguments in argv[]. argv0 is assumed program name and + * skipped. + * + * usagestr strings should be marked for translation with N_(). + * * Returns the number of arguments left in argv[]. + * + * In one-shot mode, argv0 is not a program name, argv[] is left + * untouched and parse_options() returns the number of options + * processed. */ int parse_options(int argc, const char **argv, const char *prefix, const struct option *options, From baa4adc66aefe2c3cc15b573b8a19dec786a1641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:24 +0700 Subject: [PATCH 0045/1015] parse-options: disable option abbreviation with PARSE_OPT_KEEP_UNKNOWN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse-options can unambiguously find an abbreviation only if it sees all available options. This is usually the case when you use parse_options(). But there are other callers like blame or shortlog which uses parse_options_start() in combination with a custom option parser, like rev-list. parse-options cannot see all options in this case and will get abbrev detection wrong. Disable it. t7800 needs update because --symlink no longer expands to --symlinks and will be passed down to git-diff, which will not recognize it. I still think this is the correct thing to do. But if --symlink has been actually used in the wild, we would just add an option alias for it. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- parse-options.c | 3 ++- t/t7800-difftool.sh | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/parse-options.c b/parse-options.c index 740ae5438f93d6..779034e1fd86ad 100644 --- a/parse-options.c +++ b/parse-options.c @@ -266,7 +266,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, } if (!rest) { /* abbreviated? */ - if (!strncmp(long_name, arg, arg_end - arg)) { + if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && + !strncmp(long_name, arg, arg_end - arg)) { is_abbreviated: if (abbrev_option) { /* diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 22b9199d599284..bb9a7f4ff91120 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -546,7 +546,7 @@ do done >actual EOF -test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlinks without unstaged changes' ' cat >expect <<-EOF && file $PWD/file @@ -555,7 +555,7 @@ test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged cha sub/sub $PWD/sub/sub EOF - git difftool --dir-diff --symlink \ + git difftool --dir-diff --symlinks \ --extcmd "./.git/CHECK_SYMLINKS" branch HEAD && test_cmp expect actual ' From f62470c650e0db9536f32b9e1ecbe7d25f759031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:25 +0700 Subject: [PATCH 0046/1015] parse-options: add OPT_BITOP() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed for diff_opt_parse() where we do value = (value & ~mask) | some_more; Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- parse-options.c | 7 +++++++ parse-options.h | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/parse-options.c b/parse-options.c index 779034e1fd86ad..62d94ca2e0323c 100644 --- a/parse-options.c +++ b/parse-options.c @@ -109,6 +109,13 @@ static int get_value(struct parse_opt_ctx_t *p, *(int *)opt->value &= ~opt->defval; return 0; + case OPTION_BITOP: + if (unset) + BUG("BITOP can't have unset form"); + *(int *)opt->value &= ~opt->extra; + *(int *)opt->value |= opt->defval; + return 0; + case OPTION_COUNTUP: if (*(int *)opt->value < 0) *(int *)opt->value = 0; diff --git a/parse-options.h b/parse-options.h index d663b839734771..c97324f576a01e 100644 --- a/parse-options.h +++ b/parse-options.h @@ -10,6 +10,7 @@ enum parse_opt_type { /* options with no arguments */ OPTION_BIT, OPTION_NEGBIT, + OPTION_BITOP, OPTION_COUNTUP, OPTION_SET_INT, OPTION_CMDMODE, @@ -118,6 +119,7 @@ struct option { int flags; parse_opt_cb *callback; intptr_t defval; + intptr_t extra; }; #define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \ @@ -133,6 +135,9 @@ struct option { (h), PARSE_OPT_NOARG} #define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } #define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0) +#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \ + PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \ + (set), (clear) } #define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG, NULL, (b) } #define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0) From bf3ff338a25b7353ec6d39d31e14d081be9e3471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:26 +0700 Subject: [PATCH 0047/1015] parse-options: stop abusing 'callback' for lowlevel callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lowlevel callbacks have different function signatures. Add a new field in 'struct option' with the right type for lowlevel callbacks. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/blame.c | 2 +- builtin/merge.c | 2 +- builtin/update-index.c | 11 ++++++----- parse-options-cb.c | 3 ++- parse-options.c | 15 ++++++++++++++- parse-options.h | 12 ++++++++---- 6 files changed, 32 insertions(+), 13 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index 6d798f99392e54..8dcc55dffa9f2d 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -814,7 +814,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) * and are only included here to get included in the "-h" * output: */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, + { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb }, OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from instead of calling git-rev-list")), diff --git a/builtin/merge.c b/builtin/merge.c index dc0b7cc521c26f..07839b0bb8f922 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -261,7 +261,7 @@ static struct option builtin_merge_options[] = { option_parse_message), { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), N_("read message from file"), PARSE_OPT_NONEG, - (parse_opt_cb *) option_read_message }, + NULL, 0, option_read_message }, OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), diff --git a/builtin/update-index.c b/builtin/update-index.c index e19da77edcaa02..727a8118b88953 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -985,7 +985,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("add the specified entry to the index"), PARSE_OPT_NOARG | /* disallow --cacheinfo= form */ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, - (parse_opt_cb *) cacheinfo_callback}, + NULL, 0, + cacheinfo_callback}, {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x", N_("override the executable bit of the listed files"), PARSE_OPT_NONEG, @@ -1011,19 +1012,19 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, N_("read list of paths to be updated from standard input"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) stdin_callback}, + NULL, 0, stdin_callback}, {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, N_("add entries from standard input to the index"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) stdin_cacheinfo_callback}, + NULL, 0, stdin_cacheinfo_callback}, {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, N_("repopulate stages #2 and #3 for the listed paths"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) unresolve_callback}, + NULL, 0, unresolve_callback}, {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, N_("only update entries that differ from HEAD"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) reupdate_callback}, + NULL, 0, reupdate_callback}, OPT_BIT(0, "ignore-missing", &refresh_args.flags, N_("ignore files missing from worktree"), REFRESH_IGNORE_MISSING), diff --git a/parse-options-cb.c b/parse-options-cb.c index e2f3eaed072f77..e05bcea8096b3e 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -170,7 +170,8 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) * "-h" output even if it's not being handled directly by * parse_options(). */ -int parse_opt_unknown_cb(const struct option *opt, const char *arg, int unset) +int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) { return -2; } diff --git a/parse-options.c b/parse-options.c index 62d94ca2e0323c..37a56d079a36f0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -93,7 +93,7 @@ static int get_value(struct parse_opt_ctx_t *p, switch (opt->type) { case OPTION_LOWLEVEL_CALLBACK: - return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset); + return opt->ll_callback(p, opt, unset); case OPTION_BIT: if (unset) @@ -408,6 +408,19 @@ static void parse_options_check(const struct option *opts) if ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG)) err |= optbug(opts, "should not accept an argument"); + break; + case OPTION_CALLBACK: + if (!opts->callback) + BUG("OPTION_CALLBACK needs a callback"); + if (opts->ll_callback) + BUG("OPTION_CALLBACK needs no ll_callback"); + break; + case OPTION_LOWLEVEL_CALLBACK: + if (!opts->ll_callback) + BUG("OPTION_LOWLEVEL_CALLBACK needs a callback"); + if (opts->callback) + BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback"); + break; default: ; /* ok. (usually accepts an argument) */ } diff --git a/parse-options.h b/parse-options.h index c97324f576a01e..f1f246387cae57 100644 --- a/parse-options.h +++ b/parse-options.h @@ -100,13 +100,16 @@ typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * the option takes optional argument. * * `callback`:: - * pointer to the callback to use for OPTION_CALLBACK or - * OPTION_LOWLEVEL_CALLBACK. + * pointer to the callback to use for OPTION_CALLBACK * * `defval`:: * default value to fill (*->value) with for PARSE_OPT_OPTARG. * OPTION_{BIT,SET_INT} store the {mask,integer} to put in the value when met. * CALLBACKS can use it like they want. + * + * `ll_callback`:: + * pointer to the callback to use for OPTION_LOWLEVEL_CALLBACK + * */ struct option { enum parse_opt_type type; @@ -119,6 +122,7 @@ struct option { int flags; parse_opt_cb *callback; intptr_t defval; + parse_opt_ll_cb *ll_callback; intptr_t extra; }; @@ -137,7 +141,7 @@ struct option { #define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0) #define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \ PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \ - (set), (clear) } + (set), NULL, (clear) } #define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG, NULL, (b) } #define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0) @@ -263,7 +267,7 @@ int parse_opt_commits(const struct option *, const char *, int); int parse_opt_tertiary(const struct option *, const char *, int); int parse_opt_string_list(const struct option *, const char *, int); int parse_opt_noop_cb(const struct option *, const char *, int); -int parse_opt_unknown_cb(const struct option *, const char *, int); +int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, const struct option *, int); int parse_opt_passthru(const struct option *, const char *, int); int parse_opt_passthru_argv(const struct option *, const char *, int); From f41179f16ba2fc16e31be81518536008afe2e278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:27 +0700 Subject: [PATCH 0048/1015] parse-options: avoid magic return codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Give names to these magic negative numbers. Make parse_opt_ll_cb return an enum to make clear it can actually control parse_options() with different return values (parse_opt_cb can too, but nobody needs it). Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/merge.c | 5 +-- builtin/update-index.c | 20 ++++++------ parse-options-cb.c | 6 ++-- parse-options.c | 69 +++++++++++++++++++++++++++--------------- parse-options.h | 14 ++++----- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index 07839b0bb8f922..de64d7850e99ef 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -112,8 +112,9 @@ static int option_parse_message(const struct option *opt, return 0; } -static int option_read_message(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result option_read_message(struct parse_opt_ctx_t *ctx, + const struct option *opt, + int unset) { struct strbuf *buf = opt->value; const char *arg; diff --git a/builtin/update-index.c b/builtin/update-index.c index 727a8118b88953..21c84e55906521 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -847,8 +847,8 @@ static int parse_new_style_cacheinfo(const char *arg, return 0; } -static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result cacheinfo_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { struct object_id oid; unsigned int mode; @@ -873,8 +873,8 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result stdin_cacheinfo_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { int *nul_term_line = opt->value; @@ -887,8 +887,8 @@ static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int stdin_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result stdin_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { int *read_from_stdin = opt->value; @@ -900,8 +900,8 @@ static int stdin_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int unresolve_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result unresolve_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; @@ -919,8 +919,8 @@ static int unresolve_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int reupdate_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result reupdate_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; diff --git a/parse-options-cb.c b/parse-options-cb.c index e05bcea8096b3e..ec01ef722bdc95 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -170,10 +170,10 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) * "-h" output even if it's not being handled directly by * parse_options(). */ -int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset) { - return -2; + return PARSE_OPT_UNKNOWN; } /** diff --git a/parse-options.c b/parse-options.c index 37a56d079a36f0..50c340474c7664 100644 --- a/parse-options.c +++ b/parse-options.c @@ -20,8 +20,9 @@ int optbug(const struct option *opt, const char *reason) return error("BUG: switch '%c' %s", opt->short_name, reason); } -static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, - int flags, const char **arg) +static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p, + const struct option *opt, + int flags, const char **arg) { if (p->opt) { *arg = p->opt; @@ -44,9 +45,10 @@ static void fix_filename(const char *prefix, const char **file) *file = prefix_filename(prefix, *file); } -static int opt_command_mode_error(const struct option *opt, - const struct option *all_opts, - int flags) +static enum parse_opt_result opt_command_mode_error( + const struct option *opt, + const struct option *all_opts, + int flags) { const struct option *that; struct strbuf that_name = STRBUF_INIT; @@ -69,16 +71,16 @@ static int opt_command_mode_error(const struct option *opt, error(_("%s is incompatible with %s"), optname(opt, flags), that_name.buf); strbuf_release(&that_name); - return -1; + return PARSE_OPT_ERROR; } return error(_("%s : incompatible with something else"), optname(opt, flags)); } -static int get_value(struct parse_opt_ctx_t *p, - const struct option *opt, - const struct option *all_opts, - int flags) +static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, + const struct option *opt, + const struct option *all_opts, + int flags) { const char *s, *arg; const int unset = flags & OPT_UNSET; @@ -208,7 +210,8 @@ static int get_value(struct parse_opt_ctx_t *p, } } -static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options) +static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p, + const struct option *options) { const struct option *all_opts = options; const struct option *numopt = NULL; @@ -239,11 +242,12 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio free(arg); return rc; } - return -2; + return PARSE_OPT_UNKNOWN; } -static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, - const struct option *options) +static enum parse_opt_result parse_long_opt( + struct parse_opt_ctx_t *p, const char *arg, + const struct option *options) { const struct option *all_opts = options; const char *arg_end = strchrnul(arg, '='); @@ -269,7 +273,7 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, if (*rest) continue; p->out[p->cpidx++] = arg - 2; - return 0; + return PARSE_OPT_DONE; } if (!rest) { /* abbreviated? */ @@ -334,11 +338,11 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, ambiguous_option->long_name, (abbrev_flags & OPT_UNSET) ? "no-" : "", abbrev_option->long_name); - return -3; + return PARSE_OPT_HELP; } if (abbrev_option) return get_value(p, abbrev_option, all_opts, abbrev_flags); - return -2; + return PARSE_OPT_UNKNOWN; } static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, @@ -590,22 +594,28 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (arg[1] != '-') { ctx->opt = arg + 1; switch (parse_short_opt(ctx, options)) { - case -1: + case PARSE_OPT_ERROR: return PARSE_OPT_ERROR; - case -2: + case PARSE_OPT_UNKNOWN: if (ctx->opt) check_typos(arg + 1, options); if (internal_help && *ctx->opt == 'h') goto show_usage; goto unknown; + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_HELP: + case PARSE_OPT_COMPLETE: + BUG("parse_short_opt() cannot return these"); + case PARSE_OPT_DONE: + break; } if (ctx->opt) check_typos(arg + 1, options); while (ctx->opt) { switch (parse_short_opt(ctx, options)) { - case -1: + case PARSE_OPT_ERROR: return PARSE_OPT_ERROR; - case -2: + case PARSE_OPT_UNKNOWN: if (internal_help && *ctx->opt == 'h') goto show_usage; @@ -617,6 +627,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, ctx->argv[0] = xstrdup(ctx->opt - 1); *(char *)ctx->argv[0] = '-'; goto unknown; + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_COMPLETE: + case PARSE_OPT_HELP: + BUG("parse_short_opt() cannot return these"); + case PARSE_OPT_DONE: + break; } } continue; @@ -635,12 +651,17 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (internal_help && !strcmp(arg + 2, "help")) goto show_usage; switch (parse_long_opt(ctx, arg + 2, options)) { - case -1: + case PARSE_OPT_ERROR: return PARSE_OPT_ERROR; - case -2: + case PARSE_OPT_UNKNOWN: goto unknown; - case -3: + case PARSE_OPT_HELP: goto show_usage; + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_COMPLETE: + BUG("parse_long_opt() cannot return these"); + case PARSE_OPT_DONE: + break; } continue; unknown: diff --git a/parse-options.h b/parse-options.h index f1f246387cae57..4e49185027a049 100644 --- a/parse-options.h +++ b/parse-options.h @@ -49,8 +49,8 @@ struct option; typedef int parse_opt_cb(const struct option *, const char *arg, int unset); struct parse_opt_ctx_t; -typedef int parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset); +typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, + const struct option *opt, int unset); /* * `type`:: @@ -222,12 +222,12 @@ const char *optname(const struct option *opt, int flags); /*----- incremental advanced APIs -----*/ -enum { - PARSE_OPT_COMPLETE = -2, - PARSE_OPT_HELP = -1, - PARSE_OPT_DONE, +enum parse_opt_result { + PARSE_OPT_COMPLETE = -3, + PARSE_OPT_HELP = -2, + PARSE_OPT_ERROR = -1, /* must be the same as error() */ + PARSE_OPT_DONE = 0, /* fixed so that "return 0" works */ PARSE_OPT_NON_OPTION, - PARSE_OPT_ERROR, PARSE_OPT_UNKNOWN }; From 3ebbe289896a698b99c91b797440563272dcd716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:28 +0700 Subject: [PATCH 0049/1015] parse-options: allow ll_callback with OPTION_CALLBACK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OPTION_CALLBACK is much simpler/safer to use, but parse_opt_cb does not allow access to parse_opt_ctx_t, which sometimes is useful (e.g. to obtain the prefix). Extending parse_opt_cb to take parse_opt_cb could result in a lot of changes. Instead let's just allow ll_callback to be used with OPTION_CALLBACK. The user will have to be careful, not to change anything in ctx, or return wrong result code. But that's the price for ll_callback. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/merge.c | 2 ++ builtin/update-index.c | 20 +++++++++++++++----- parse-options-cb.c | 4 +++- parse-options.c | 42 ++++++++++++++++++++++++++++-------------- parse-options.h | 5 +++-- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/builtin/merge.c b/builtin/merge.c index de64d7850e99ef..563a16f38a50e1 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -114,11 +114,13 @@ static int option_parse_message(const struct option *opt, static enum parse_opt_result option_read_message(struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg_not_used, int unset) { struct strbuf *buf = opt->value; const char *arg; + BUG_ON_OPT_ARG(arg_not_used); if (unset) BUG("-F cannot be negated"); diff --git a/builtin/update-index.c b/builtin/update-index.c index 21c84e55906521..7abde20169ac12 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -848,13 +848,15 @@ static int parse_new_style_cacheinfo(const char *arg, } static enum parse_opt_result cacheinfo_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { struct object_id oid; unsigned int mode; const char *path; BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) { if (add_cacheinfo(mode, &oid, path, 0)) @@ -874,11 +876,13 @@ static enum parse_opt_result cacheinfo_callback( } static enum parse_opt_result stdin_cacheinfo_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *nul_term_line = opt->value; BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); if (ctx->argc != 1) return error("option '%s' must be the last argument", opt->long_name); @@ -888,11 +892,13 @@ static enum parse_opt_result stdin_cacheinfo_callback( } static enum parse_opt_result stdin_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *read_from_stdin = opt->value; BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); if (ctx->argc != 1) return error("option '%s' must be the last argument", opt->long_name); @@ -901,12 +907,14 @@ static enum parse_opt_result stdin_callback( } static enum parse_opt_result unresolve_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); /* consume remaining arguments. */ *has_errors = do_unresolve(ctx->argc, ctx->argv, @@ -920,12 +928,14 @@ static enum parse_opt_result unresolve_callback( } static enum parse_opt_result reupdate_callback( - struct parse_opt_ctx_t *ctx, const struct option *opt, int unset) + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); /* consume remaining arguments. */ setup_work_tree(); diff --git a/parse-options-cb.c b/parse-options-cb.c index ec01ef722bdc95..2733393546d4d4 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -171,8 +171,10 @@ int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset) * parse_options(). */ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) + const struct option *opt, + const char *arg, int unset) { + BUG_ON_OPT_ARG(arg); return PARSE_OPT_UNKNOWN; } diff --git a/parse-options.c b/parse-options.c index 50c340474c7664..cec74522e56b08 100644 --- a/parse-options.c +++ b/parse-options.c @@ -95,7 +95,7 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, switch (opt->type) { case OPTION_LOWLEVEL_CALLBACK: - return opt->ll_callback(p, opt, unset); + return opt->ll_callback(p, opt, NULL, unset); case OPTION_BIT: if (unset) @@ -161,16 +161,27 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, return err; case OPTION_CALLBACK: + { + const char *p_arg = NULL; + int p_unset; + if (unset) - return (*opt->callback)(opt, NULL, 1) ? (-1) : 0; - if (opt->flags & PARSE_OPT_NOARG) - return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; - if (opt->flags & PARSE_OPT_OPTARG && !p->opt) - return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; - if (get_arg(p, opt, flags, &arg)) + p_unset = 1; + else if (opt->flags & PARSE_OPT_NOARG) + p_unset = 0; + else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) + p_unset = 0; + else if (get_arg(p, opt, flags, &arg)) return -1; - return (*opt->callback)(opt, arg, 0) ? (-1) : 0; - + else { + p_unset = 0; + p_arg = arg; + } + if (opt->callback) + return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0; + else + return (*opt->ll_callback)(p, opt, p_arg, p_unset); + } case OPTION_INTEGER: if (unset) { *(int *)opt->value = 0; @@ -238,7 +249,10 @@ static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p, len++; arg = xmemdupz(p->opt, len); p->opt = p->opt[len] ? p->opt + len : NULL; - rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0; + if (numopt->callback) + rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0; + else + rc = (*numopt->ll_callback)(p, numopt, arg, 0); free(arg); return rc; } @@ -414,10 +428,10 @@ static void parse_options_check(const struct option *opts) err |= optbug(opts, "should not accept an argument"); break; case OPTION_CALLBACK: - if (!opts->callback) - BUG("OPTION_CALLBACK needs a callback"); - if (opts->ll_callback) - BUG("OPTION_CALLBACK needs no ll_callback"); + if (!opts->callback && !opts->ll_callback) + BUG("OPTION_CALLBACK needs one callback"); + if (opts->callback && opts->ll_callback) + BUG("OPTION_CALLBACK can't have two callbacks"); break; case OPTION_LOWLEVEL_CALLBACK: if (!opts->ll_callback) diff --git a/parse-options.h b/parse-options.h index 4e49185027a049..ce75278804fcf0 100644 --- a/parse-options.h +++ b/parse-options.h @@ -50,7 +50,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); struct parse_opt_ctx_t; typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset); + const struct option *opt, + const char *arg, int unset); /* * `type`:: @@ -267,7 +268,7 @@ int parse_opt_commits(const struct option *, const char *, int); int parse_opt_tertiary(const struct option *, const char *, int); int parse_opt_string_list(const struct option *, const char *, int); int parse_opt_noop_cb(const struct option *, const char *, int); -int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, const struct option *, int); +int parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx, const struct option *, const char *, int); int parse_opt_passthru(const struct option *, const char *, int); int parse_opt_passthru_argv(const struct option *, const char *, int); From 4a1b13a3972efb0cf90143ebba88e309af2f41be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:29 +0700 Subject: [PATCH 0050/1015] diff.h: keep forward struct declarations sorted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/diff.h b/diff.h index b512d0477ac3a4..c872a41344c6d7 100644 --- a/diff.h +++ b/diff.h @@ -9,16 +9,16 @@ #include "object.h" #include "oidset.h" -struct rev_info; +struct combine_diff_path; +struct commit; +struct diff_filespec; struct diff_options; struct diff_queue_struct; -struct strbuf; -struct diff_filespec; -struct userdiff_driver; struct oid_array; -struct commit; -struct combine_diff_path; struct repository; +struct rev_info; +struct strbuf; +struct userdiff_driver; typedef int (*pathchange_fn_t)(struct diff_options *options, struct combine_diff_path *path); From 2b393ef357d2fe466ba7097c9cc3ec92798ec05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:30 +0700 Subject: [PATCH 0051/1015] diff.h: avoid bit fields in struct diff_flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bitfield addresses cannot be passed around in a pointer. This makes it hard to use parse-options to set/unset them. Turn this struct to normal integers. This of course increases the size of this struct multiple times, but since we only have a handful of diff_options variables around, memory consumption is not at all a concern. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.h | 66 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/diff.h b/diff.h index c872a41344c6d7..8abe1649d031df 100644 --- a/diff.h +++ b/diff.h @@ -64,39 +64,39 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_FLAGS_INIT { 0 } struct diff_flags { - unsigned recursive:1; - unsigned tree_in_recursive:1; - unsigned binary:1; - unsigned text:1; - unsigned full_index:1; - unsigned silent_on_remove:1; - unsigned find_copies_harder:1; - unsigned follow_renames:1; - unsigned rename_empty:1; - unsigned has_changes:1; - unsigned quick:1; - unsigned no_index:1; - unsigned allow_external:1; - unsigned exit_with_status:1; - unsigned reverse_diff:1; - unsigned check_failed:1; - unsigned relative_name:1; - unsigned ignore_submodules:1; - unsigned dirstat_cumulative:1; - unsigned dirstat_by_file:1; - unsigned allow_textconv:1; - unsigned textconv_set_via_cmdline:1; - unsigned diff_from_contents:1; - unsigned dirty_submodules:1; - unsigned ignore_untracked_in_submodules:1; - unsigned ignore_dirty_submodules:1; - unsigned override_submodule_config:1; - unsigned dirstat_by_line:1; - unsigned funccontext:1; - unsigned default_follow_renames:1; - unsigned stat_with_summary:1; - unsigned suppress_diff_headers:1; - unsigned dual_color_diffed_diffs:1; + unsigned recursive; + unsigned tree_in_recursive; + unsigned binary; + unsigned text; + unsigned full_index; + unsigned silent_on_remove; + unsigned find_copies_harder; + unsigned follow_renames; + unsigned rename_empty; + unsigned has_changes; + unsigned quick; + unsigned no_index; + unsigned allow_external; + unsigned exit_with_status; + unsigned reverse_diff; + unsigned check_failed; + unsigned relative_name; + unsigned ignore_submodules; + unsigned dirstat_cumulative; + unsigned dirstat_by_file; + unsigned allow_textconv; + unsigned textconv_set_via_cmdline; + unsigned diff_from_contents; + unsigned dirty_submodules; + unsigned ignore_untracked_in_submodules; + unsigned ignore_dirty_submodules; + unsigned override_submodule_config; + unsigned dirstat_by_line; + unsigned funccontext; + unsigned default_follow_renames; + unsigned stat_with_summary; + unsigned suppress_diff_headers; + unsigned dual_color_diffed_diffs; }; static inline void diff_flags_or(struct diff_flags *a, From 4a2884783949d1791ee5d720d8440d5536ebdc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:31 +0700 Subject: [PATCH 0052/1015] diff.c: prepare to use parse_options() for parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a preparation step to start using parse_options() to parse diff/revision options instead of what we have now. There are a couple of good things from using parse_options(): - better help usage - easier to add new options - better completion support - help usage generation - better integration with main command option parser. We can just concat the main command's option array and diffopt's together and parse all in one go. - detect colidding options (e.g. --reverse is used by revision code, so diff code can't use it as long name for -R) - consistent syntax, e.g. option that takes mandatory argument will now accept both "--option=value" and "--option value". The plan is migrate all diff/rev options to parse_options(). Then we could get rid of diff_opt_parse() and expose parseopts[] directly to the caller. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 27 +++++++++++++++++++++++++++ diff.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/diff.c b/diff.c index 1b5f27636061fe..80b4af23d71808 100644 --- a/diff.c +++ b/diff.c @@ -23,6 +23,7 @@ #include "argv-array.h" #include "graph.h" #include "packfile.h" +#include "parse-options.h" #include "help.h" #ifdef NO_FAST_WORKING_DIRECTORY @@ -4425,6 +4426,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) builtin_checkdiff(name, other, attr_path, p->one, p->two, o); } +static void prep_parse_options(struct diff_options *options); + void repo_diff_setup(struct repository *r, struct diff_options *options) { memcpy(options, &default_diff_options, sizeof(*options)); @@ -4466,6 +4469,8 @@ void repo_diff_setup(struct repository *r, struct diff_options *options) options->color_moved = diff_color_moved_default; options->color_moved_ws_handling = diff_color_moved_ws_default; + + prep_parse_options(options); } void diff_setup_done(struct diff_options *options) @@ -4569,6 +4574,8 @@ void diff_setup_done(struct diff_options *options) if (!options->use_color || external_diff()) options->color_moved = 0; + + FREE_AND_NULL(options->parseopts); } static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val) @@ -4860,6 +4867,16 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static void prep_parse_options(struct diff_options *options) +{ + struct option parseopts[] = { + OPT_END() + }; + + ALLOC_ARRAY(options->parseopts, ARRAY_SIZE(parseopts)); + memcpy(options->parseopts, parseopts, sizeof(parseopts)); +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac, const char *prefix) { @@ -4870,6 +4887,16 @@ int diff_opt_parse(struct diff_options *options, if (!prefix) prefix = ""; + ac = parse_options(ac, av, prefix, options->parseopts, NULL, + PARSE_OPT_KEEP_DASHDASH | + PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_NO_INTERNAL_HELP | + PARSE_OPT_ONE_SHOT | + PARSE_OPT_STOP_AT_NON_OPTION); + + if (ac) + return ac; + /* Output format options */ if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch") || opt_arg(arg, 'U', "unified", &options->context)) diff --git a/diff.h b/diff.h index 8abe1649d031df..d9ad73f0e171e3 100644 --- a/diff.h +++ b/diff.h @@ -15,6 +15,7 @@ struct diff_filespec; struct diff_options; struct diff_queue_struct; struct oid_array; +struct option; struct repository; struct rev_info; struct strbuf; @@ -229,6 +230,7 @@ struct diff_options { unsigned color_moved_ws_handling; struct repository *repo; + struct option *parseopts; }; void diff_emit_submodule_del(struct diff_options *o, const char *line); From cc013c224cfc73cf7d29ce4d336749f00c3bf1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:32 +0700 Subject: [PATCH 0053/1015] diff.c: convert -u|-p|--patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index 80b4af23d71808..a4a40e4aa8b38e 100644 --- a/diff.c +++ b/diff.c @@ -4870,6 +4870,13 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) static void prep_parse_options(struct diff_options *options) { struct option parseopts[] = { + OPT_GROUP(N_("Diff output format options")), + OPT_BITOP('p', "patch", &options->output_format, + N_("generate patch"), + DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), + OPT_BITOP('u', NULL, &options->output_format, + N_("generate patch"), + DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), OPT_END() }; @@ -4898,8 +4905,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch") - || opt_arg(arg, 'U', "unified", &options->context)) + if (opt_arg(arg, 'U', "unified", &options->context)) enable_patch_output(&options->output_format); else if (!strcmp(arg, "--raw")) options->output_format |= DIFF_FORMAT_RAW; From d473e2e0e8eedf2ab07ddc545e00bff7c8f450e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:33 +0700 Subject: [PATCH 0054/1015] diff.c: convert -U|--unified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 2 +- diff.c | 23 ++++++++++++++++++++--- parse-options.h | 5 +++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b94d332f71b0ff..0711734b125f92 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -36,7 +36,7 @@ endif::git-format-patch[] -U:: --unified=:: Generate diffs with lines of context instead of - the usual three. + the usual three. Implies `--patch`. ifndef::git-format-patch[] Implies `-p`. endif::git-format-patch[] diff --git a/diff.c b/diff.c index a4a40e4aa8b38e..093158244eca22 100644 --- a/diff.c +++ b/diff.c @@ -4867,6 +4867,22 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static int diff_opt_unified(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + char *s; + + BUG_ON_OPT_NEG(unset); + + options->context = strtol(arg, &s, 10); + if (*s) + return error(_("%s expects a numerical value"), "--unified"); + enable_patch_output(&options->output_format); + + return 0; +} + static void prep_parse_options(struct diff_options *options) { struct option parseopts[] = { @@ -4877,6 +4893,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BITOP('u', NULL, &options->output_format, N_("generate patch"), DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), + OPT_CALLBACK_F('U', "unified", options, N_(""), + N_("generate diffs with lines context"), + PARSE_OPT_NONEG, diff_opt_unified), OPT_END() }; @@ -4905,9 +4924,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (opt_arg(arg, 'U', "unified", &options->context)) - enable_patch_output(&options->output_format); - else if (!strcmp(arg, "--raw")) + if (!strcmp(arg, "--raw")) options->output_format |= DIFF_FORMAT_RAW; else if (!strcmp(arg, "--patch-with-raw")) { enable_patch_output(&options->output_format); diff --git a/parse-options.h b/parse-options.h index ce75278804fcf0..7d83e2971d9afa 100644 --- a/parse-options.h +++ b/parse-options.h @@ -134,6 +134,8 @@ struct option { #define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG | (f), NULL, (i) } #define OPT_BOOL_F(s, l, v, h, f) OPT_SET_INT_F(s, l, v, h, 1, f) +#define OPT_CALLBACK_F(s, l, v, a, h, f, cb) \ + { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), (cb) } #define OPT_END() { OPTION_END } #define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, \ @@ -164,8 +166,7 @@ struct option { #define OPT_EXPIRY_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0, \ parse_opt_expiry_date_cb } -#define OPT_CALLBACK(s, l, v, a, h, f) \ - { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) } +#define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f) #define OPT_NUMBER_CALLBACK(v, h, f) \ { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } From 7fd9a1ba039b683f596d10bacbf892fa5e8f202f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:34 +0700 Subject: [PATCH 0055/1015] diff.c: convert -W|--[no-]function-context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/diff.c b/diff.c index 093158244eca22..8f701013625796 100644 --- a/diff.c +++ b/diff.c @@ -4896,6 +4896,8 @@ static void prep_parse_options(struct diff_options *options) OPT_CALLBACK_F('U', "unified", options, N_(""), N_("generate diffs with lines context"), PARSE_OPT_NONEG, diff_opt_unified), + OPT_BOOL('W', "function-context", &options->flags.funccontext, + N_("generate diffs with lines context")), OPT_END() }; @@ -5212,12 +5214,6 @@ int diff_opt_parse(struct diff_options *options, else if (opt_arg(arg, '\0', "inter-hunk-context", &options->interhunkcontext)) ; - else if (!strcmp(arg, "-W")) - options->flags.funccontext = 1; - else if (!strcmp(arg, "--function-context")) - options->flags.funccontext = 1; - else if (!strcmp(arg, "--no-function-context")) - options->flags.funccontext = 0; else if ((argcount = parse_long_opt("output", av, &optarg))) { char *path = prefix_filename(prefix, optarg); options->file = xfopen(path, "w"); From ed88148674502893790bf13d70154c157a489a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 27 Jan 2019 07:35:35 +0700 Subject: [PATCH 0056/1015] diff.c: convert --raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 8f701013625796..4bc9df73623606 100644 --- a/diff.c +++ b/diff.c @@ -4898,6 +4898,9 @@ static void prep_parse_options(struct diff_options *options) PARSE_OPT_NONEG, diff_opt_unified), OPT_BOOL('W', "function-context", &options->flags.funccontext, N_("generate diffs with lines context")), + OPT_BIT_F(0, "raw", &options->output_format, + N_("generate the diff in raw format"), + DIFF_FORMAT_RAW, PARSE_OPT_NONEG), OPT_END() }; @@ -4926,9 +4929,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--raw")) - options->output_format |= DIFF_FORMAT_RAW; - else if (!strcmp(arg, "--patch-with-raw")) { + if (!strcmp(arg, "--patch-with-raw")) { enable_patch_output(&options->output_format); options->output_format |= DIFF_FORMAT_RAW; } else if (!strcmp(arg, "--numstat")) From 4f732e0fd7a524759424c817b92848949db1e2db Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Tue, 29 Jan 2019 07:49:00 +0100 Subject: [PATCH 0057/1015] pretty: allow %(trailers) options with explicit value In addition to old %(trailers:only) it is now allowed to write %(trailers:only=yes) By itself this only gives (the not quite so useful) possibility to have users change their mind in the middle of a formatting string (%(trailers:only=true,only=false)). However, it gives users the opportunity to override defaults from future options. Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 14 ++++++--- pretty.c | 52 +++++++++++++++++++++++++++----- t/t4205-log-pretty-formats.sh | 18 +++++++++++ 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 8740b43581abdd..dcb686e88f134a 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -225,10 +225,16 @@ endif::git-rev-list[] linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by a colon and zero or more comma-separated options: -** 'only': omit non-trailer lines from the trailer block. -** 'unfold': make it behave as if interpret-trailer's `--unfold` - option was given. E.g., `%(trailers:only,unfold)` unfolds and - shows all trailer lines. +** 'only[=val]': select whether non-trailer lines from the trailer + block should be included. The `only` keyword may optionally be + followed by an equal sign and one of `true`, `on`, `yes` to omit or + `false`, `off`, `no` to show the non-trailer lines. If option is + given without value it is enabled. If given multiple times the last + value is used. +** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold` + option was given. In same way as to for `only` it can be followed + by an equal sign and explicit value. E.g., + `%(trailers:only,unfold=true)` unfolds and shows all trailer lines. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/pretty.c b/pretty.c index b83a3ecd2331af..4dfbd38cf643bd 100644 --- a/pretty.c +++ b/pretty.c @@ -1056,13 +1056,26 @@ static size_t parse_padding_placeholder(struct strbuf *sb, return 0; } -static int match_placeholder_arg(const char *to_parse, const char *candidate, - const char **end) +static int match_placeholder_arg_value(const char *to_parse, const char *candidate, + const char **end, const char **valuestart, + size_t *valuelen) { const char *p; if (!(skip_prefix(to_parse, candidate, &p))) return 0; + if (valuestart) { + if (*p == '=') { + *valuestart = p + 1; + *valuelen = strcspn(*valuestart, ",)"); + p = *valuestart + *valuelen; + } else { + if (*p != ',' && *p != ')') + return 0; + *valuestart = NULL; + *valuelen = 0; + } + } if (*p == ',') { *end = p + 1; return 1; @@ -1074,6 +1087,34 @@ static int match_placeholder_arg(const char *to_parse, const char *candidate, return 0; } +static int match_placeholder_bool_arg(const char *to_parse, const char *candidate, + const char **end, int *val) +{ + const char *argval; + char *strval; + size_t arglen; + int v; + + if (!match_placeholder_arg_value(to_parse, candidate, end, &argval, &arglen)) + return 0; + + if (!argval) { + *val = 1; + return 1; + } + + strval = xstrndup(argval, arglen); + v = git_parse_maybe_bool(strval); + free(strval); + + if (v == -1) + return 0; + + *val = v; + + return 1; +} + static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const char *placeholder, void *context) @@ -1318,11 +1359,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (*arg == ':') { arg++; for (;;) { - if (match_placeholder_arg(arg, "only", &arg)) - opts.only_trailers = 1; - else if (match_placeholder_arg(arg, "unfold", &arg)) - opts.unfold = 1; - else + if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && + !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold)) break; } } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 978a8a66ff0505..63730a4ec0fc79 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -578,6 +578,24 @@ test_expect_success '%(trailers:only) shows only "key: value" trailers' ' test_cmp expect actual ' +test_expect_success '%(trailers:only=yes) shows only "key: value" trailers' ' + git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual && + grep -v patch.description expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:only=no) shows all trailers' ' + git log --no-walk --pretty=format:"%(trailers:only=no)" >actual && + cat trailers >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:only=no,only=true) shows only "key: value" trailers' ' + git log --no-walk --pretty=format:"%(trailers:only=yes)" >actual && + grep -v patch.description expect && + test_cmp expect actual +' + test_expect_success '%(trailers:unfold) unfolds trailers' ' git log --no-walk --pretty="%(trailers:unfold)" >actual && { From 3e3f34781964c225ca199ac50527f3ce6feddb84 Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Mon, 28 Jan 2019 22:33:33 +0100 Subject: [PATCH 0058/1015] pretty: single return path in %(trailers) handling No functional change intended. This change may not seem useful on its own, but upcoming commits will do memory allocation in there, and a single return path makes deallocation easier. Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- pretty.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pretty.c b/pretty.c index 4dfbd38cf643bd..610837e43949f7 100644 --- a/pretty.c +++ b/pretty.c @@ -1353,6 +1353,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (skip_prefix(placeholder, "(trailers", &arg)) { struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + size_t ret = 0; opts.no_divider = 1; @@ -1366,8 +1367,9 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ } if (*arg == ')') { format_trailers_from_commit(sb, msg + c->subject_off, &opts); - return arg - placeholder + 1; + ret = arg - placeholder + 1; } + return ret; } return 0; /* unknown placeholder */ From 250bea0c1652ad546cd0455852bd734e4820ec46 Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Mon, 28 Jan 2019 22:33:34 +0100 Subject: [PATCH 0059/1015] pretty: allow showing specific trailers Adds a new "key=X" option to "%(trailers)" which will cause it to only print trailer lines which match any of the specified keys. Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 8 +++++ pretty.c | 36 ++++++++++++++++++-- t/t4205-log-pretty-formats.sh | 57 ++++++++++++++++++++++++++++++++ trailer.c | 10 +++--- trailer.h | 2 ++ 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index dcb686e88f134a..abfb2493394fff 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -225,6 +225,14 @@ endif::git-rev-list[] linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by a colon and zero or more comma-separated options: +** 'key=': only show trailers with specified key. Matching is done + case-insensitively and trailing colon is optional. If option is + given multiple times trailer lines matching any of the keys are + shown. This option automatically enables the `only` option so that + non-trailer lines in the trailer block are hidden. If that is not + desired it can be disabled with `only=false`. E.g., + `%(trailers:key=Reviewed-by)` shows trailer lines with key + `Reviewed-by`. ** 'only[=val]': select whether non-trailer lines from the trailer block should be included. The `only` keyword may optionally be followed by an equal sign and one of `true`, `on`, `yes` to omit or diff --git a/pretty.c b/pretty.c index 610837e43949f7..5bf05cde56a649 100644 --- a/pretty.c +++ b/pretty.c @@ -1115,6 +1115,19 @@ static int match_placeholder_bool_arg(const char *to_parse, const char *candidat return 1; } +static int format_trailer_match_cb(const struct strbuf *key, void *ud) +{ + const struct string_list *list = ud; + const struct string_list_item *item; + + for_each_string_list_item (item, list) { + if (key->len == (uintptr_t)item->util && + !strncasecmp(item->string, key->buf, key->len)) + return 1; + } + return 0; +} + static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const char *placeholder, void *context) @@ -1353,6 +1366,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (skip_prefix(placeholder, "(trailers", &arg)) { struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + struct string_list filter_list = STRING_LIST_INIT_NODUP; size_t ret = 0; opts.no_divider = 1; @@ -1360,8 +1374,24 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (*arg == ':') { arg++; for (;;) { - if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && - !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold)) + const char *argval; + size_t arglen; + + if (match_placeholder_arg_value(arg, "key", &arg, &argval, &arglen)) { + uintptr_t len = arglen; + + if (!argval) + goto trailer_out; + + if (len && argval[len - 1] == ':') + len--; + string_list_append(&filter_list, argval)->util = (char *)len; + + opts.filter = format_trailer_match_cb; + opts.filter_data = &filter_list; + opts.only_trailers = 1; + } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && + !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold)) break; } } @@ -1369,6 +1399,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ format_trailers_from_commit(sb, msg + c->subject_off, &opts); ret = arg - placeholder + 1; } + trailer_out: + string_list_clear(&filter_list, 0); return ret; } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 63730a4ec0fc79..d87201afbec213 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -616,6 +616,63 @@ test_expect_success ':only and :unfold work together' ' test_cmp expect actual ' +test_expect_success 'pretty format %(trailers:key=foo) shows that trailer' ' + git log --no-walk --pretty="format:%(trailers:key=Acked-by)" >actual && + echo "Acked-by: A U Thor " >expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers:key=foo) is case insensitive' ' + git log --no-walk --pretty="format:%(trailers:key=AcKed-bY)" >actual && + echo "Acked-by: A U Thor " >expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers:key=foo:) trailing colon also works' ' + git log --no-walk --pretty="format:%(trailers:key=Acked-by:)" >actual && + echo "Acked-by: A U Thor " >expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers:key=foo) multiple keys' ' + git log --no-walk --pretty="format:%(trailers:key=Acked-by:,key=Signed-off-By)" >actual && + grep -v patch.description expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:key=nonexistant) becomes empty' ' + git log --no-walk --pretty="x%(trailers:key=Nacked-by)x" >actual && + echo "xx" >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:key=foo) handles multiple lines even if folded' ' + git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by)" >actual && + grep -v patch.description expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:key=foo,unfold) properly unfolds' ' + git log --no-walk --pretty="format:%(trailers:key=Signed-Off-by,unfold)" >actual && + unfold expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers:key=foo,only=no) also includes nontrailer lines' ' + git log --no-walk --pretty="format:%(trailers:key=Acked-by,only=no)" >actual && + { + echo "Acked-by: A U Thor " && + grep patch.description expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:key) without value is error' ' + git log --no-walk --pretty="tformat:%(trailers:key)" >actual && + echo "%(trailers:key)" >expect && + test_cmp expect actual +' + test_expect_success 'trailer parsing not fooled by --- line' ' git commit --allow-empty -F - <<-\EOF && this is the subject diff --git a/trailer.c b/trailer.c index 0796f326b36bac..d6da555cd71bed 100644 --- a/trailer.c +++ b/trailer.c @@ -1132,7 +1132,7 @@ static void format_trailer_info(struct strbuf *out, size_t i; /* If we want the whole block untouched, we can take the fast path. */ - if (!opts->only_trailers && !opts->unfold) { + if (!opts->only_trailers && !opts->unfold && !opts->filter) { strbuf_add(out, info->trailer_start, info->trailer_end - info->trailer_start); return; @@ -1147,10 +1147,12 @@ static void format_trailer_info(struct strbuf *out, struct strbuf val = STRBUF_INIT; parse_trailer(&tok, &val, NULL, trailer, separator_pos); - if (opts->unfold) - unfold_value(&val); + if (!opts->filter || opts->filter(&tok, opts->filter_data)) { + if (opts->unfold) + unfold_value(&val); - strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); + strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); + } strbuf_release(&tok); strbuf_release(&val); diff --git a/trailer.h b/trailer.h index b997739649a37e..5255b676de2400 100644 --- a/trailer.h +++ b/trailer.h @@ -72,6 +72,8 @@ struct process_trailer_options { int only_input; int unfold; int no_divider; + int (*filter)(const struct strbuf *, void *); + void *filter_data; }; #define PROCESS_TRAILER_OPTIONS_INIT {0} From d9b936db5226ed4b87d42a2c91324adc50c768b6 Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Mon, 28 Jan 2019 22:33:35 +0100 Subject: [PATCH 0060/1015] pretty: add support for "valueonly" option in %(trailers) With the new "key=" option to %(trailers) it often makes little sense to show the key, as it by definition already is knows which trailer is printed there. This new "valueonly" option makes it omit the key when printing trailers. E.g.: $ git show -s --pretty='%s%n%(trailers:key=Signed-off-by,valueonly)' aaaa88182 will show: > upload-pack: fix broken if/else chain in config callback > Jeff King > Junio C Hamano Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 2 ++ pretty.c | 3 ++- t/t4205-log-pretty-formats.sh | 6 ++++++ trailer.c | 6 ++++-- trailer.h | 1 + 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index abfb2493394fff..76e2dbdb7128ed 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -243,6 +243,8 @@ endif::git-rev-list[] option was given. In same way as to for `only` it can be followed by an equal sign and explicit value. E.g., `%(trailers:only,unfold=true)` unfolds and shows all trailer lines. +** 'valueonly[=val]': skip over the key part of the trailer line and only + show the value part. Also this optionally allows explicit value. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/pretty.c b/pretty.c index 5bf05cde56a649..fe73916adc8888 100644 --- a/pretty.c +++ b/pretty.c @@ -1391,7 +1391,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ opts.filter_data = &filter_list; opts.only_trailers = 1; } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && - !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold)) + !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) && + !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only)) break; } } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index d87201afbec213..1ad68347815286 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -673,6 +673,12 @@ test_expect_success '%(trailers:key) without value is error' ' test_cmp expect actual ' +test_expect_success '%(trailers:key=foo,valueonly) shows only value' ' + git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual && + echo "A U Thor " >expect && + test_cmp expect actual +' + test_expect_success 'trailer parsing not fooled by --- line' ' git commit --allow-empty -F - <<-\EOF && this is the subject diff --git a/trailer.c b/trailer.c index d6da555cd71bed..d0d9e91631ca4f 100644 --- a/trailer.c +++ b/trailer.c @@ -1150,8 +1150,10 @@ static void format_trailer_info(struct strbuf *out, if (!opts->filter || opts->filter(&tok, opts->filter_data)) { if (opts->unfold) unfold_value(&val); - - strbuf_addf(out, "%s: %s\n", tok.buf, val.buf); + if (!opts->value_only) + strbuf_addf(out, "%s: ", tok.buf); + strbuf_addbuf(out, &val); + strbuf_addch(out, '\n'); } strbuf_release(&tok); strbuf_release(&val); diff --git a/trailer.h b/trailer.h index 5255b676de2400..06d417fe93c563 100644 --- a/trailer.h +++ b/trailer.h @@ -72,6 +72,7 @@ struct process_trailer_options { int only_input; int unfold; int no_divider; + int value_only; int (*filter)(const struct strbuf *, void *); void *filter_data; }; From fd2015b323d283c73346d70d2285a927650bb60a Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Mon, 28 Jan 2019 22:33:36 +0100 Subject: [PATCH 0061/1015] strbuf: separate callback for strbuf_expand:ing literals Expanding '%n' and '%xNN' is generic functionality, so extract that from the pretty.c formatter into a callback that can be reused. No functional change intended Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- pretty.c | 16 +++++----------- strbuf.c | 21 +++++++++++++++++++++ strbuf.h | 8 ++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/pretty.c b/pretty.c index fe73916adc8888..2c66698b8969f4 100644 --- a/pretty.c +++ b/pretty.c @@ -1137,9 +1137,13 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ const char *msg = c->message; struct commit_list *p; const char *arg; - int ch; + size_t res; /* these are independent of the commit */ + res = strbuf_expand_literal_cb(sb, placeholder, NULL); + if (res) + return res; + switch (placeholder[0]) { case 'C': if (starts_with(placeholder + 1, "(auto)")) { @@ -1158,16 +1162,6 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ */ return ret; } - case 'n': /* newline */ - strbuf_addch(sb, '\n'); - return 1; - case 'x': - /* %x00 == NUL, %x0a == LF, etc. */ - ch = hex2chr(placeholder + 1); - if (ch < 0) - return 0; - strbuf_addch(sb, ch); - return 3; case 'w': if (placeholder[1] == '(') { unsigned long width = 0, indent1 = 0, indent2 = 0; diff --git a/strbuf.c b/strbuf.c index f6a6cf78b9426a..78eecd29f7e641 100644 --- a/strbuf.c +++ b/strbuf.c @@ -380,6 +380,27 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, } } +size_t strbuf_expand_literal_cb(struct strbuf *sb, + const char *placeholder, + void *context) +{ + int ch; + + switch (placeholder[0]) { + case 'n': /* newline */ + strbuf_addch(sb, '\n'); + return 1; + case 'x': + /* %x00 == NUL, %x0a == LF, etc. */ + ch = hex2chr(placeholder + 1); + if (ch < 0) + return 0; + strbuf_addch(sb, ch); + return 3; + } + return 0; +} + size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context) { diff --git a/strbuf.h b/strbuf.h index fc40873b65124f..52e44c9ab83b34 100644 --- a/strbuf.h +++ b/strbuf.h @@ -320,6 +320,14 @@ void strbuf_expand(struct strbuf *sb, expand_fn_t fn, void *context); +/** + * Used as callback for `strbuf_expand` to only expand literals + * (i.e. %n and %xNN). The context argument is ignored. + */ +size_t strbuf_expand_literal_cb(struct strbuf *sb, + const char *placeholder, + void *context); + /** * Used as callback for `strbuf_expand()`, expects an array of * struct strbuf_expand_dict_entry as context, i.e. pairs of From 0b691d8685131c2c10e1a2cf2acc9b8920c5365f Mon Sep 17 00:00:00 2001 From: Anders Waldenborg Date: Mon, 28 Jan 2019 22:33:37 +0100 Subject: [PATCH 0062/1015] pretty: add support for separator option in %(trailers) By default trailer lines are terminated by linebreaks ('\n'). By specifying the new 'separator' option they will instead be separated by user provided string and have separator semantics rather than terminator semantics. The separator string can contain the literal formatting codes %n and %xNN allowing it to be things that are otherwise hard to type such as %x00, or comma and end-parenthesis which would break parsing. E.g: $ git log --pretty='%(trailers:key=Reviewed-by,valueonly,separator=%x00)' Signed-off-by: Anders Waldenborg Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 9 ++++++++ pretty.c | 10 +++++++++ t/t4205-log-pretty-formats.sh | 36 ++++++++++++++++++++++++++++++++ trailer.c | 15 +++++++++++-- trailer.h | 1 + 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 76e2dbdb7128ed..4cfea4c9d69f23 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -239,6 +239,15 @@ endif::git-rev-list[] `false`, `off`, `no` to show the non-trailer lines. If option is given without value it is enabled. If given multiple times the last value is used. +** 'separator=': specify a separator inserted between trailer + lines. When this option is not given each trailer line is + terminated with a line feed character. The string SEP may contain + the literal formatting codes described above. To use comma as + separator one must use `%x2C` as it would otherwise be parsed as + next option. If separator option is given multiple times only the + last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )` + shows all trailer lines whose key is "Ticket" separated by a comma + and a space. ** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold` option was given. In same way as to for `only` it can be followed by an equal sign and explicit value. E.g., diff --git a/pretty.c b/pretty.c index 2c66698b8969f4..6d58b21affd3b7 100644 --- a/pretty.c +++ b/pretty.c @@ -1361,6 +1361,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (skip_prefix(placeholder, "(trailers", &arg)) { struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; struct string_list filter_list = STRING_LIST_INIT_NODUP; + struct strbuf sepbuf = STRBUF_INIT; size_t ret = 0; opts.no_divider = 1; @@ -1384,6 +1385,14 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ opts.filter = format_trailer_match_cb; opts.filter_data = &filter_list; opts.only_trailers = 1; + } else if (match_placeholder_arg_value(arg, "separator", &arg, &argval, &arglen)) { + char *fmt; + + strbuf_reset(&sepbuf); + fmt = xstrndup(argval, arglen); + strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL); + free(fmt); + opts.separator = &sepbuf; } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) && !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) && !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only)) @@ -1396,6 +1405,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ } trailer_out: string_list_clear(&filter_list, 0); + strbuf_release(&sepbuf); return ret; } diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 1ad68347815286..99f50fa401cbdd 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -679,6 +679,42 @@ test_expect_success '%(trailers:key=foo,valueonly) shows only value' ' test_cmp expect actual ' +test_expect_success 'pretty format %(trailers:separator) changes separator' ' + git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual && + printf "XSigned-off-by: A U Thor \0Acked-by: A U Thor \0[ v2 updated patch description ]\0Signed-off-by: A U Thor X" >expect && + test_cmp expect actual +' + +test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' ' + git commit --allow-empty -F - <<-\EOF && + Important fix + + The fix is explained here + + Closes: #1234 + EOF + + git commit --allow-empty -F - <<-\EOF && + Another fix + + The fix is explained here + + Closes: #567 + Closes: #890 + EOF + + git commit --allow-empty -F - <<-\EOF && + Does not close any tickets + EOF + + git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,valueonly)" HEAD~3.. >actual && + test_write_lines \ + "Does not close any tickets" \ + "Another fix #567, #890" \ + "Important fix #1234" >expect && + test_cmp expect actual +' + test_expect_success 'trailer parsing not fooled by --- line' ' git commit --allow-empty -F - <<-\EOF && this is the subject diff --git a/trailer.c b/trailer.c index d0d9e91631ca4f..0c414f2fed2b57 100644 --- a/trailer.c +++ b/trailer.c @@ -1129,10 +1129,11 @@ static void format_trailer_info(struct strbuf *out, const struct trailer_info *info, const struct process_trailer_options *opts) { + size_t origlen = out->len; size_t i; /* If we want the whole block untouched, we can take the fast path. */ - if (!opts->only_trailers && !opts->unfold && !opts->filter) { + if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) { strbuf_add(out, info->trailer_start, info->trailer_end - info->trailer_start); return; @@ -1150,16 +1151,26 @@ static void format_trailer_info(struct strbuf *out, if (!opts->filter || opts->filter(&tok, opts->filter_data)) { if (opts->unfold) unfold_value(&val); + + if (opts->separator && out->len != origlen) + strbuf_addbuf(out, opts->separator); if (!opts->value_only) strbuf_addf(out, "%s: ", tok.buf); strbuf_addbuf(out, &val); - strbuf_addch(out, '\n'); + if (!opts->separator) + strbuf_addch(out, '\n'); } strbuf_release(&tok); strbuf_release(&val); } else if (!opts->only_trailers) { + if (opts->separator && out->len != origlen) { + strbuf_addbuf(out, opts->separator); + } strbuf_addstr(out, trailer); + if (opts->separator) { + strbuf_rtrim(out); + } } } diff --git a/trailer.h b/trailer.h index 06d417fe93c563..203acf4ee1db99 100644 --- a/trailer.h +++ b/trailer.h @@ -73,6 +73,7 @@ struct process_trailer_options { int unfold; int no_divider; int value_only; + const struct strbuf *separator; int (*filter)(const struct strbuf *, void *); void *filter_data; }; From f851b4583c2ab45bbf5b2e7f8a49c53911527f46 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 29 Jan 2019 11:47:06 -0800 Subject: [PATCH 0063/1015] SQUASH : misnamed variables and style fix The need for this fix may indicate that the tests weren't even run before submission, which is not a good sign... --- t/t5318-commit-graph.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index be7bbf911a830c..4230df199b543b 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -33,13 +33,13 @@ test_expect_success 'create commits and repack' ' git repack ' -graph_git_two_modes() { +graph_git_two_modes () { git -c core.commitGraph=true $1 >output git -c core.commitGraph=false $1 >expect test_cmp expect output } -graph_git_behavior() { +graph_git_behavior () { MSG=$1 DIR=$2 BRANCH=$3 @@ -56,7 +56,7 @@ graph_git_behavior() { graph_git_behavior 'no graph' full commits/3 commits/1 -graph_read_expect() { +graph_read_expect () { OPTIONAL="" NUM_CHUNKS=3 if test ! -z $2 @@ -372,7 +372,7 @@ GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES)) # by inserting the data, then runs 'git commit-graph verify' # and places the output in the file 'err'. Test 'err' for # the given string. -corrupt_graph_and_verify() { +corrupt_graph_and_verify () { pos=$1 data="${2:-\0}" grepstr=$3 @@ -509,12 +509,12 @@ GRAPH_BYTE_UNUSED=7 GRAPH_BYTE_HASH=8 test_expect_success 'detect low chunk count (v2)' ' - corrupt_graph_and_verify $GRAPH_CHUNK_COUNT "\02" \ + corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\02" \ "missing the .* chunk" ' test_expect_success 'detect incorrect reachability index' ' - corrupt_graph_and_verify $GRAPH_REACH_INDEX "\03" \ + corrupt_graph_and_verify $GRAPH_BYTE_REACH_INDEX "\03" \ "reachability index version" ' From 6ad656db9b2d2426a0a884b431e8adc9877101bc Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:46 +0100 Subject: [PATCH 0064/1015] sequencer: remove the 'arg' field from todo_item The 'arg' field of todo_item used to store the address of the first byte of the parameter of a command in a todo list. It was associated with the length of the parameter (the 'arg_len' field). This replaces the 'arg' field by 'arg_offset'. This new field does not store the address of the parameter, but the position of the first character of the parameter in the buffer. todo_item_get_arg() is added to return the address of the parameter of an item. This will prevent todo_list_add_exec_commands() from having to do awful pointer arithmetics when growing the todo list buffer. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 67 ++++++++++++++++++++++++++++++----------------------- sequencer.h | 6 +++-- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/sequencer.c b/sequencer.c index 25cc7a9a91af2e..c844a9b7f30c8a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1999,8 +1999,14 @@ static struct todo_item *append_new_todo(struct todo_list *todo_list) return todo_list->items + todo_list->nr++; } +const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item) +{ + return todo_list->buf.buf + item->arg_offset; +} + static int parse_insn_line(struct repository *r, struct todo_item *item, - const char *bol, char *eol) + const char *buf, const char *bol, char *eol) { struct object_id commit_oid; char *end_of_object_name; @@ -2014,7 +2020,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, if (bol == eol || *bol == '\r' || *bol == comment_line_char) { item->command = TODO_COMMENT; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@ -2041,7 +2047,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, return error(_("%s does not accept arguments: '%s'"), command_to_string(item->command), bol); item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = eol - bol; return 0; } @@ -2053,7 +2059,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, if (item->command == TODO_EXEC || item->command == TODO_LABEL || item->command == TODO_RESET) { item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@ -2067,7 +2073,7 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, } else { item->flags |= TODO_EDIT_MERGE_MSG; item->commit = NULL; - item->arg = bol; + item->arg_offset = bol - buf; item->arg_len = (int)(eol - bol); return 0; } @@ -2079,8 +2085,9 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, status = get_oid(bol, &commit_oid); *end_of_object_name = saved; - item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); - item->arg_len = (int)(eol - item->arg); + bol = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_offset = bol - buf; + item->arg_len = (int)(eol - bol); if (status < 0) return -1; @@ -2108,11 +2115,11 @@ int todo_list_parse_insn_buffer(struct repository *r, char *buf, item = append_new_todo(todo_list); item->offset_in_buf = p - todo_list->buf.buf; - if (parse_insn_line(r, item, p, eol)) { + if (parse_insn_line(r, item, buf, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); item->command = TODO_COMMENT + 1; - item->arg = p; + item->arg_offset = p - buf; item->arg_len = (int)(eol - p); item->commit = NULL; } @@ -2452,7 +2459,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->command = command; item->commit = commit; - item->arg = NULL; + item->arg_offset = 0; item->arg_len = 0; item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); @@ -3491,6 +3498,8 @@ static int pick_commits(struct repository *r, while (todo_list->current < todo_list->nr) { struct todo_item *item = todo_list->items + todo_list->current; + const char *arg = todo_item_get_arg(todo_list, item); + if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { @@ -3542,10 +3551,9 @@ static int pick_commits(struct repository *r, fprintf(stderr, _("Stopped at %s... %.*s\n"), short_commit_name(commit), - item->arg_len, item->arg); + item->arg_len, arg); return error_with_patch(r, commit, - item->arg, item->arg_len, opts, res, - !res); + arg, item->arg_len, opts, res, !res); } if (is_rebase_i(opts) && !res) record_in_rewritten(&item->commit->object.oid, @@ -3554,7 +3562,7 @@ static int pick_commits(struct repository *r, if (res == 1) intend_to_amend(); return error_failed_squash(r, item->commit, opts, - item->arg_len, item->arg); + item->arg_len, arg); } else if (res && is_rebase_i(opts) && item->commit) { int to_amend = 0; struct object_id oid; @@ -3573,16 +3581,16 @@ static int pick_commits(struct repository *r, to_amend = 1; return res | error_with_patch(r, item->commit, - item->arg, item->arg_len, opts, + arg, item->arg_len, opts, res, to_amend); } } else if (item->command == TODO_EXEC) { - char *end_of_arg = (char *)(item->arg + item->arg_len); + char *end_of_arg = (char *)(arg + item->arg_len); int saved = *end_of_arg; struct stat st; *end_of_arg = '\0'; - res = do_exec(r, item->arg); + res = do_exec(r, arg); *end_of_arg = saved; /* Reread the todo file if it has changed. */ @@ -3599,14 +3607,14 @@ static int pick_commits(struct repository *r, todo_list->current = -1; } } else if (item->command == TODO_LABEL) { - if ((res = do_label(r, item->arg, item->arg_len))) + if ((res = do_label(r, arg, item->arg_len))) reschedule = 1; } else if (item->command == TODO_RESET) { - if ((res = do_reset(r, item->arg, item->arg_len, opts))) + if ((res = do_reset(r, arg, item->arg_len, opts))) reschedule = 1; } else if (item->command == TODO_MERGE) { if ((res = do_merge(r, item->commit, - item->arg, item->arg_len, + arg, item->arg_len, item->flags, opts)) < 0) reschedule = 1; else if (item->commit) @@ -3615,9 +3623,8 @@ static int pick_commits(struct repository *r, if (res > 0) /* failed with merge conflicts */ return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); @@ -3632,9 +3639,8 @@ static int pick_commits(struct repository *r, if (item->commit) return error_with_patch(r, item->commit, - item->arg, - item->arg_len, opts, - res, 0); + arg, item->arg_len, + opts, res, 0); } todo_list->current++; @@ -4575,7 +4581,8 @@ int transform_todos(struct repository *r, unsigned flags) for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { - strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg); + strbuf_addf(&buf, "%.*s\n", item->arg_len, + todo_item_get_arg(&todo_list, item)); continue; } @@ -4605,7 +4612,8 @@ int transform_todos(struct repository *r, unsigned flags) if (!item->arg_len) strbuf_addch(&buf, '\n'); else - strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg); + strbuf_addf(&buf, " %.*s\n", item->arg_len, + todo_item_get_arg(&todo_list, item)); } i = write_message(buf.buf, buf.len, todo_file, 0); @@ -4681,7 +4689,8 @@ int check_todo_list(struct repository *r) if (commit && !*commit_seen_at(&commit_seen, commit)) { strbuf_addf(&missing, " - %s %.*s\n", short_commit_name(commit), - item->arg_len, item->arg); + item->arg_len, + todo_item_get_arg(&todo_list, item)); *commit_seen_at(&commit_seen, commit) = 1; } } diff --git a/sequencer.h b/sequencer.h index c6360bac66b033..50d552429c476c 100644 --- a/sequencer.h +++ b/sequencer.h @@ -104,9 +104,9 @@ struct todo_item { enum todo_command command; struct commit *commit; unsigned int flags; - const char *arg; int arg_len; - size_t offset_in_buf; + /* The offset of the command and its argument in the strbuf */ + size_t offset_in_buf, arg_offset; }; struct todo_list { @@ -122,6 +122,8 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); void todo_list_release(struct todo_list *todo_list); +const char *todo_item_get_arg(struct todo_list *todo_list, + struct todo_item *item); /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); From cbef27d61cd6ae4f1ecae054eb9df06e898148d8 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:47 +0100 Subject: [PATCH 0065/1015] sequencer: refactor transform_todos() to work on a todo_list This refactors transform_todos() to work on a todo_list. The function is renamed todo_list_transform(). As rebase -p still need to check the todo list from the disk, a new function is introduced, transform_todo_file(). It is still used by complete_action() and edit_todo_list() for now, but they will be replaced in a future commit. todo_list_transform() is not a static function, because it will be used by edit_todo_list() from rebase-interactive.c in a future commit. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 2 +- rebase-interactive.c | 4 +-- sequencer.c | 54 +++++++++++++++++++++++------------ sequencer.h | 4 ++- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index dd2a55ab1d956e..0898eb4c59aa5d 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -253,7 +253,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) } case SHORTEN_OIDS: case EXPAND_OIDS: - ret = transform_todos(the_repository, flags); + ret = transform_todo_file(the_repository, flags); break; case CHECK_TODO_LIST: ret = check_todo_list(the_repository); diff --git a/rebase-interactive.c b/rebase-interactive.c index 68aff1dac28878..842fa07e7e2b80 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -69,7 +69,7 @@ int edit_todo_list(struct repository *r, unsigned flags) strbuf_release(&buf); - transform_todos(r, flags | TODO_LIST_SHORTEN_IDS); + transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS); if (strbuf_read_file(&buf, todo_file, 0) < 0) return error_errno(_("could not read '%s'."), todo_file); @@ -85,7 +85,7 @@ int edit_todo_list(struct repository *r, unsigned flags) if (launch_sequence_editor(todo_file, NULL, NULL)) return -1; - transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS)); + transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS)); return 0; } diff --git a/sequencer.c b/sequencer.c index c844a9b7f30c8a..346706029cb2a6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4562,27 +4562,18 @@ int sequencer_add_exec_commands(struct repository *r, return i; } -int transform_todos(struct repository *r, unsigned flags) +void todo_list_transform(struct repository *r, struct todo_list *todo_list, + unsigned flags) { - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; struct strbuf buf = STRBUF_INIT; struct todo_item *item; int i; - if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) - return error(_("could not read '%s'."), todo_file); - - if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { - todo_list_release(&todo_list); - return error(_("unusable todo list: '%s'"), todo_file); - } - - for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + for (item = todo_list->items, i = 0; i < todo_list->nr; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { strbuf_addf(&buf, "%.*s\n", item->arg_len, - todo_item_get_arg(&todo_list, item)); + todo_item_get_arg(todo_list, item)); continue; } @@ -4613,12 +4604,39 @@ int transform_todos(struct repository *r, unsigned flags) strbuf_addch(&buf, '\n'); else strbuf_addf(&buf, " %.*s\n", item->arg_len, - todo_item_get_arg(&todo_list, item)); + todo_item_get_arg(todo_list, item)); } - i = write_message(buf.buf, buf.len, todo_file, 0); + strbuf_reset(&todo_list->buf); + strbuf_add(&todo_list->buf, buf.buf, buf.len); + strbuf_release(&buf); + + if (todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list)) + BUG("unusable todo list"); +} + +int transform_todo_file(struct repository *r, unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_transform(r, &todo_list, flags); + + res = write_message(todo_list.buf.buf, todo_list.buf.len, todo_file, 0); todo_list_release(&todo_list); - return i; + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; } enum missing_commit_check_level get_missing_commit_check_level(void) @@ -4880,7 +4898,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return error(_("could not copy '%s' to '%s'."), todo_file, rebase_path_todo_backup()); - if (transform_todos(r, flags | TODO_LIST_SHORTEN_IDS)) + if (transform_todo_file(r, flags | TODO_LIST_SHORTEN_IDS)) return error(_("could not transform the todo list")); strbuf_reset(buf); @@ -4909,7 +4927,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return -1; } - if (transform_todos(r, flags & ~(TODO_LIST_SHORTEN_IDS))) + if (transform_todo_file(r, flags & ~(TODO_LIST_SHORTEN_IDS))) return error(_("could not transform the todo list")); if (opts->allow_ff && skip_unnecessary_picks(r, &oid)) diff --git a/sequencer.h b/sequencer.h index 50d552429c476c..2ddb20cbc1e5bd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -121,6 +121,8 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); +void todo_list_transform(struct repository *r, struct todo_list *todo_list, + unsigned flags); void todo_list_release(struct todo_list *todo_list); const char *todo_item_get_arg(struct todo_list *todo_list, struct todo_item *item); @@ -148,7 +150,7 @@ int sequencer_make_script(struct repository *repo, FILE *out, unsigned flags); int sequencer_add_exec_commands(struct repository *r, const char *command); -int transform_todos(struct repository *r, unsigned flags); +int transform_todo_file(struct repository *r, unsigned flags); enum missing_commit_check_level get_missing_commit_check_level(void); int check_todo_list(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, From 616d7740cfbe65533af8ff1dabcf4e56f6baad5a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:48 +0100 Subject: [PATCH 0066/1015] sequencer: introduce todo_list_write_to_file() This introduces a new function to recreate the text of a todo list from its commands and write it to a file. This will be useful as the next few commits will change the use of the buffer in struct todo_list so it will no longer be a mirror of the file on disk. This functionality already exists in todo_list_transform(), but this function was made to replace the buffer of a todo list, which is not what we want here. Thus, the part of todo_list_transform() that replaces the buffer is dropped, and the function is renamed todo_list_to_strbuf(). It is called by todo_list_write_to_file() to fill the buffer to write to the disk. todo_list_write_to_file() can also take care of appending the help text to the buffer before writing it to the disk, or to write only the first n items of the list. This feature will be used by skip_unnecessary_picks(), which has to write done commands in a file. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 61 +++++++++++++++++++++++++++++++++++------------------ sequencer.h | 11 ++++++---- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/sequencer.c b/sequencer.c index 346706029cb2a6..4809b22ce4663c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4562,26 +4562,28 @@ int sequencer_add_exec_commands(struct repository *r, return i; } -void todo_list_transform(struct repository *r, struct todo_list *todo_list, - unsigned flags) +static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_list, + struct strbuf *buf, int num, unsigned flags) { - struct strbuf buf = STRBUF_INIT; struct todo_item *item; - int i; + int i, max = todo_list->nr; - for (item = todo_list->items, i = 0; i < todo_list->nr; i++, item++) { + if (num > 0 && num < max) + max = num; + + for (item = todo_list->items, i = 0; i < max; i++, item++) { /* if the item is not a command write it and continue */ if (item->command >= TODO_COMMENT) { - strbuf_addf(&buf, "%.*s\n", item->arg_len, + strbuf_addf(buf, "%.*s\n", item->arg_len, todo_item_get_arg(todo_list, item)); continue; } /* add command to the buffer */ if (flags & TODO_LIST_ABBREVIATE_CMDS) - strbuf_addch(&buf, command_to_char(item->command)); + strbuf_addch(buf, command_to_char(item->command)); else - strbuf_addstr(&buf, command_to_string(item->command)); + strbuf_addstr(buf, command_to_string(item->command)); /* add commit id */ if (item->commit) { @@ -4591,28 +4593,48 @@ void todo_list_transform(struct repository *r, struct todo_list *todo_list, if (item->command == TODO_MERGE) { if (item->flags & TODO_EDIT_MERGE_MSG) - strbuf_addstr(&buf, " -c"); + strbuf_addstr(buf, " -c"); else - strbuf_addstr(&buf, " -C"); + strbuf_addstr(buf, " -C"); } - strbuf_addf(&buf, " %s", oid); + strbuf_addf(buf, " %s", oid); } /* add all the rest */ if (!item->arg_len) - strbuf_addch(&buf, '\n'); + strbuf_addch(buf, '\n'); else - strbuf_addf(&buf, " %.*s\n", item->arg_len, + strbuf_addf(buf, " %.*s\n", item->arg_len, todo_item_get_arg(todo_list, item)); } +} - strbuf_reset(&todo_list->buf); - strbuf_add(&todo_list->buf, buf.buf, buf.len); +int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags) +{ + int edit_todo = !(shortrevisions && shortonto), res; + struct strbuf buf = STRBUF_INIT; + + todo_list_to_strbuf(r, todo_list, &buf, num, flags); + + if (flags & TODO_LIST_APPEND_TODO_HELP) { + int command_count = count_commands(todo_list); + if (!edit_todo) { + strbuf_addch(&buf, '\n'); + strbuf_commented_addf(&buf, Q_("Rebase %s onto %s (%d command)", + "Rebase %s onto %s (%d commands)", + command_count), + shortrevisions, shortonto, command_count); + } + append_todo_help(edit_todo, flags & TODO_LIST_KEEP_EMPTY, &buf); + } + + res = write_message(buf.buf, buf.len, file, 0); strbuf_release(&buf); - if (todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list)) - BUG("unusable todo list"); + return res; } int transform_todo_file(struct repository *r, unsigned flags) @@ -4629,9 +4651,8 @@ int transform_todo_file(struct repository *r, unsigned flags) return error(_("unusable todo list: '%s'"), todo_file); } - todo_list_transform(r, &todo_list, flags); - - res = write_message(todo_list.buf.buf, todo_list.buf.len, todo_file, 0); + res = todo_list_write_to_file(r, &todo_list, todo_file, + NULL, NULL, -1, flags); todo_list_release(&todo_list); if (res) diff --git a/sequencer.h b/sequencer.h index 2ddb20cbc1e5bd..7278f9675bbb62 100644 --- a/sequencer.h +++ b/sequencer.h @@ -121,8 +121,9 @@ struct todo_list { int todo_list_parse_insn_buffer(struct repository *r, char *buf, struct todo_list *todo_list); -void todo_list_transform(struct repository *r, struct todo_list *todo_list, - unsigned flags); +int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list, + const char *file, const char *shortrevisions, + const char *shortonto, int num, unsigned flags); void todo_list_release(struct todo_list *todo_list); const char *todo_item_get_arg(struct todo_list *todo_list, struct todo_item *item); @@ -145,8 +146,10 @@ int sequencer_remove_state(struct replay_opts *opts); * commits should be rebased onto the new base, this flag needs to be passed. */ #define TODO_LIST_REBASE_COUSINS (1U << 4) -int sequencer_make_script(struct repository *repo, FILE *out, - int argc, const char **argv, +#define TODO_LIST_APPEND_TODO_HELP (1U << 5) + +int sequencer_make_script(struct repository *r, FILE *out, int argc, + const char **argv, unsigned flags); int sequencer_add_exec_commands(struct repository *r, const char *command); From 6ca89c6f399b86983c7e93a3c5b918cad8292b47 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 29 Jan 2019 16:01:49 +0100 Subject: [PATCH 0067/1015] sequencer: refactor check_todo_list() to work on a todo_list This refactors check_todo_list() to work on a todo_list to avoid redundant reads and writes to the disk. The function is renamed todo_list_check(). The parsing of the two todo lists is left to the caller. As rebase -p still need to check the todo list from the disk, a new function is introduced, check_todo_list_from_file(). It reads the file from the disk, parses it, pass the todo_list to todo_list_check(), and writes it back to the disk. As get_missing_commit_check_level() and the enum missing_commit_check_level are no longer needed inside of sequencer.c, they are moved to rebase-interactive.c, and made static again. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- builtin/rebase--interactive.c | 2 +- rebase-interactive.c | 91 ++++++++++++++++++++++++- rebase-interactive.h | 2 + sequencer.c | 121 +++++++--------------------------- sequencer.h | 9 +-- 5 files changed, 117 insertions(+), 108 deletions(-) diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 0898eb4c59aa5d..df19ccaeb9e8a9 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -256,7 +256,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = transform_todo_file(the_repository, flags); break; case CHECK_TODO_LIST: - ret = check_todo_list(the_repository); + ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: ret = rearrange_squash(the_repository); diff --git a/rebase-interactive.c b/rebase-interactive.c index 842fa07e7e2b80..dfa6dd530f3dd2 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -1,8 +1,32 @@ #include "cache.h" #include "commit.h" -#include "rebase-interactive.h" #include "sequencer.h" +#include "rebase-interactive.h" #include "strbuf.h" +#include "commit-slab.h" +#include "config.h" + +enum missing_commit_check_level { + MISSING_COMMIT_CHECK_IGNORE = 0, + MISSING_COMMIT_CHECK_WARN, + MISSING_COMMIT_CHECK_ERROR +}; + +static enum missing_commit_check_level get_missing_commit_check_level(void) +{ + const char *value; + + if (git_config_get_value("rebase.missingcommitscheck", &value) || + !strcasecmp("ignore", value)) + return MISSING_COMMIT_CHECK_IGNORE; + if (!strcasecmp("warn", value)) + return MISSING_COMMIT_CHECK_WARN; + if (!strcasecmp("error", value)) + return MISSING_COMMIT_CHECK_ERROR; + warning(_("unrecognized setting %s for option " + "rebase.missingCommitsCheck. Ignoring."), value); + return MISSING_COMMIT_CHECK_IGNORE; +} void append_todo_help(unsigned edit_todo, unsigned keep_empty, struct strbuf *buf) @@ -89,3 +113,68 @@ int edit_todo_list(struct repository *r, unsigned flags) return 0; } + +define_commit_slab(commit_seen, unsigned char); +/* + * Check if the user dropped some commits by mistake + * Behaviour determined by rebase.missingCommitsCheck. + * Check if there is an unrecognized command or a + * bad SHA-1 in a command. + */ +int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo) +{ + enum missing_commit_check_level check_level = get_missing_commit_check_level(); + struct strbuf missing = STRBUF_INIT; + int res = 0, i; + struct commit_seen commit_seen; + + init_commit_seen(&commit_seen); + + if (check_level == MISSING_COMMIT_CHECK_IGNORE) + goto leave_check; + + /* Mark the commits in git-rebase-todo as seen */ + for (i = 0; i < new_todo->nr; i++) { + struct commit *commit = new_todo->items[i].commit; + if (commit) + *commit_seen_at(&commit_seen, commit) = 1; + } + + /* Find commits in git-rebase-todo.backup yet unseen */ + for (i = old_todo->nr - 1; i >= 0; i--) { + struct todo_item *item = old_todo->items + i; + struct commit *commit = item->commit; + if (commit && !*commit_seen_at(&commit_seen, commit)) { + strbuf_addf(&missing, " - %s %.*s\n", + find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV), + item->arg_len, + todo_item_get_arg(old_todo, item)); + *commit_seen_at(&commit_seen, commit) = 1; + } + } + + /* Warn about missing commits */ + if (!missing.len) + goto leave_check; + + if (check_level == MISSING_COMMIT_CHECK_ERROR) + res = 1; + + fprintf(stderr, + _("Warning: some commits may have been dropped accidentally.\n" + "Dropped commits (newer to older):\n")); + + /* Make the list user-friendly and display */ + fputs(missing.buf, stderr); + strbuf_release(&missing); + + fprintf(stderr, _("To avoid this message, use \"drop\" to " + "explicitly remove a commit.\n\n" + "Use 'git config rebase.missingCommitsCheck' to change " + "the level of warnings.\n" + "The possible behaviours are: ignore, warn, error.\n\n")); + +leave_check: + clear_commit_seen(&commit_seen); + return res; +} diff --git a/rebase-interactive.h b/rebase-interactive.h index 17b6c9f6d05ea2..187b5032d67fc3 100644 --- a/rebase-interactive.h +++ b/rebase-interactive.h @@ -3,9 +3,11 @@ struct strbuf; struct repository; +struct todo_list; void append_todo_help(unsigned edit_todo, unsigned keep_empty, struct strbuf *buf); int edit_todo_list(struct repository *r, unsigned flags); +int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo); #endif diff --git a/sequencer.c b/sequencer.c index 4809b22ce4663c..99e12c751ed48e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -4660,112 +4660,37 @@ int transform_todo_file(struct repository *r, unsigned flags) return 0; } -enum missing_commit_check_level get_missing_commit_check_level(void) -{ - const char *value; - - if (git_config_get_value("rebase.missingcommitscheck", &value) || - !strcasecmp("ignore", value)) - return MISSING_COMMIT_CHECK_IGNORE; - if (!strcasecmp("warn", value)) - return MISSING_COMMIT_CHECK_WARN; - if (!strcasecmp("error", value)) - return MISSING_COMMIT_CHECK_ERROR; - warning(_("unrecognized setting %s for option " - "rebase.missingCommitsCheck. Ignoring."), value); - return MISSING_COMMIT_CHECK_IGNORE; -} +static const char edit_todo_list_advice[] = +N_("You can fix this with 'git rebase --edit-todo' " +"and then run 'git rebase --continue'.\n" +"Or you can abort the rebase with 'git rebase" +" --abort'.\n"); -define_commit_slab(commit_seen, unsigned char); -/* - * Check if the user dropped some commits by mistake - * Behaviour determined by rebase.missingCommitsCheck. - * Check if there is an unrecognized command or a - * bad SHA-1 in a command. - */ -int check_todo_list(struct repository *r) +int check_todo_list_from_file(struct repository *r) { - enum missing_commit_check_level check_level = get_missing_commit_check_level(); - struct strbuf todo_file = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; - struct strbuf missing = STRBUF_INIT; - int advise_to_edit_todo = 0, res = 0, i; - struct commit_seen commit_seen; - - init_commit_seen(&commit_seen); + struct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT; + int res = 0; - strbuf_addstr(&todo_file, rebase_path_todo()); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { + if (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) { res = -1; - goto leave_check; - } - advise_to_edit_todo = res = - todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - - if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) - goto leave_check; - - /* Mark the commits in git-rebase-todo as seen */ - for (i = 0; i < todo_list.nr; i++) { - struct commit *commit = todo_list.items[i].commit; - if (commit) - *commit_seen_at(&commit_seen, commit) = 1; + goto out; } - todo_list_release(&todo_list); - strbuf_addstr(&todo_file, ".backup"); - if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { + if (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) { res = -1; - goto leave_check; - } - strbuf_release(&todo_file); - res = !!todo_list_parse_insn_buffer(r, todo_list.buf.buf, &todo_list); - - /* Find commits in git-rebase-todo.backup yet unseen */ - for (i = todo_list.nr - 1; i >= 0; i--) { - struct todo_item *item = todo_list.items + i; - struct commit *commit = item->commit; - if (commit && !*commit_seen_at(&commit_seen, commit)) { - strbuf_addf(&missing, " - %s %.*s\n", - short_commit_name(commit), - item->arg_len, - todo_item_get_arg(&todo_list, item)); - *commit_seen_at(&commit_seen, commit) = 1; - } + goto out; } - /* Warn about missing commits */ - if (!missing.len) - goto leave_check; - - if (check_level == MISSING_COMMIT_CHECK_ERROR) - advise_to_edit_todo = res = 1; - - fprintf(stderr, - _("Warning: some commits may have been dropped accidentally.\n" - "Dropped commits (newer to older):\n")); - - /* Make the list user-friendly and display */ - fputs(missing.buf, stderr); - strbuf_release(&missing); - - fprintf(stderr, _("To avoid this message, use \"drop\" to " - "explicitly remove a commit.\n\n" - "Use 'git config rebase.missingCommitsCheck' to change " - "the level of warnings.\n" - "The possible behaviours are: ignore, warn, error.\n\n")); - -leave_check: - clear_commit_seen(&commit_seen); - strbuf_release(&todo_file); - todo_list_release(&todo_list); - - if (advise_to_edit_todo) - fprintf(stderr, - _("You can fix this with 'git rebase --edit-todo' " - "and then run 'git rebase --continue'.\n" - "Or you can abort the rebase with 'git rebase" - " --abort'.\n")); + res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo); + if (!res) + res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo); + if (!res) + res = todo_list_check(&old_todo, &new_todo); + if (res) + fprintf(stderr, _(edit_todo_list_advice)); +out: + todo_list_release(&old_todo); + todo_list_release(&new_todo); return res; } @@ -4943,7 +4868,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla todo_list_release(&todo_list); - if (check_todo_list(r)) { + if (check_todo_list_from_file(r)) { checkout_onto(opts, onto_name, onto, orig_head); return -1; } diff --git a/sequencer.h b/sequencer.h index 7278f9675bbb62..217353e9f0b199 100644 --- a/sequencer.h +++ b/sequencer.h @@ -64,12 +64,6 @@ struct replay_opts { }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } -enum missing_commit_check_level { - MISSING_COMMIT_CHECK_IGNORE = 0, - MISSING_COMMIT_CHECK_WARN, - MISSING_COMMIT_CHECK_ERROR -}; - int write_message(const void *buf, size_t len, const char *filename, int append_eol); @@ -154,8 +148,7 @@ int sequencer_make_script(struct repository *r, FILE *out, int argc, int sequencer_add_exec_commands(struct repository *r, const char *command); int transform_todo_file(struct repository *r, unsigned flags); -enum missing_commit_check_level get_missing_commit_check_level(void); -int check_todo_list(struct repository *r); +int check_todo_list_from_file(struct repository *r); int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags, const char *shortrevisions, const char *onto_name, const char *onto, const char *orig_head, const char *cmd, From ba81921a94cc8c79b80cc4abc5c62cb8f7412ad5 Mon Sep 17 00:00:00 2001 From: Dan McGregor Date: Fri, 1 Feb 2019 13:30:04 -0600 Subject: [PATCH 0068/1015] http: cast result to FILE * Commit 8dd2e88a92 ("http: support file handles for HTTP_KEEP_ERROR", 2019-01-10) introduced an implicit assumption that rewind, fileno, and fflush are functions. At least on FreeBSD fileno is not, and as such passing a void * failed. Explicitly cast result to a FILE * when using standard functions that may ultimately be macros. Signed-off-by: Dan McGregor Signed-off-by: Junio C Hamano --- http.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http.c b/http.c index 954bebf6842510..8b9476b15175f0 100644 --- a/http.c +++ b/http.c @@ -1996,12 +1996,12 @@ static int http_request_reauth(const char *url, strbuf_reset(result); break; case HTTP_REQUEST_FILE: - if (fflush(result)) { + if (fflush((FILE *)result)) { error_errno("unable to flush a file"); return HTTP_START_FAILED; } - rewind(result); - if (ftruncate(fileno(result), 0) < 0) { + rewind((FILE *)result); + if (ftruncate(fileno((FILE *)result), 0) < 0) { error_errno("unable to truncate a file"); return HTTP_START_FAILED; } From 39ab4d0951ba64edcfae7809740715991b44fa6d Mon Sep 17 00:00:00 2001 From: William Hubbs Date: Mon, 4 Feb 2019 12:48:50 -0600 Subject: [PATCH 0069/1015] config: allow giving separate author and committer idents The author.email, author.name, committer.email and committer.name settings are analogous to the GIT_AUTHOR_* and GIT_COMMITTER_* environment variables, but for the git config system. This allows them to be set separately for each repository. Git supports setting different authorship and committer information with environment variables. However, environment variables are set in the shell, so if different authorship and committer information is needed for different repositories an external tool is required. This adds support to git config for author.email, author.name, committer.email and committer.name settings so this information can be set per repository. Also, it generalizes the fmt_ident function so it can handle author vs committer identification. Signed-off-by: William Hubbs Signed-off-by: Junio C Hamano --- Documentation/config/user.txt | 23 ++++++--- blame.c | 3 +- builtin/am.c | 1 + builtin/commit.c | 3 +- cache.h | 13 ++++- config.c | 4 +- ident.c | 92 ++++++++++++++++++++++++++++++++--- log-tree.c | 3 +- sequencer.c | 5 +- t/t7517-per-repo-email.sh | 74 ++++++++++++++++++++++++++++ 10 files changed, 197 insertions(+), 24 deletions(-) diff --git a/Documentation/config/user.txt b/Documentation/config/user.txt index b5b2ba1199c98a..0557cbbceb8159 100644 --- a/Documentation/config/user.txt +++ b/Documentation/config/user.txt @@ -1,12 +1,19 @@ -user.email:: - Your email address to be recorded in any newly created commits. - Can be overridden by the `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_EMAIL`, and - `EMAIL` environment variables. See linkgit:git-commit-tree[1]. - user.name:: - Your full name to be recorded in any newly created commits. - Can be overridden by the `GIT_AUTHOR_NAME` and `GIT_COMMITTER_NAME` - environment variables. See linkgit:git-commit-tree[1]. +user.email:: +author.name:: +author.email:: +committer.name:: +committer.email:: + The `user.name` and `user.email` variables determine what ends + up in the `author` and `committer` field of commit + objects. + If you need the `author` or `committer` to be different, the + `author.name`, `author.email`, `committer.name` or + `committer.email` variables can be set. + Also, all of these can be overridden by the `GIT_AUTHOR_NAME`, + `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME`, + `GIT_COMMITTER_EMAIL` and `EMAIL` environment variables. + See linkgit:git-commit-tree[1] for more information. user.useConfigOnly:: Instruct Git to avoid trying to guess defaults for `user.email` diff --git a/blame.c b/blame.c index 43861437f74984..c9c351eb3676ad 100644 --- a/blame.c +++ b/blame.c @@ -204,7 +204,8 @@ static struct commit *fake_working_tree_commit(struct repository *r, origin = make_origin(commit, path); - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", + WANT_BLANK_IDENT, NULL, 0); strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); for (parent = commit->parents; parent; parent = parent->next) strbuf_addf(&msg, "parent %s\n", diff --git a/builtin/am.c b/builtin/am.c index 95370313b66daa..3727d4d267655e 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1594,6 +1594,7 @@ static void do_commit(const struct am_state *state) } author = fmt_ident(state->author_name, state->author_email, + WANT_AUTHOR_IDENT, state->ignore_date ? NULL : state->author_date, IDENT_STRICT); diff --git a/builtin/commit.c b/builtin/commit.c index 004b816635bf16..f96b90daeb5390 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -607,7 +607,8 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + IDENT_STRICT)); assert_split_ident(&author, author_ident); export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); diff --git a/cache.h b/cache.h index 49713cc5a5a61b..bb78eb9a3aefd3 100644 --- a/cache.h +++ b/cache.h @@ -1479,10 +1479,19 @@ int date_overflows(timestamp_t date); #define IDENT_STRICT 1 #define IDENT_NO_DATE 2 #define IDENT_NO_NAME 4 + +enum want_ident { + WANT_BLANK_IDENT, + WANT_AUTHOR_IDENT, + WANT_COMMITTER_IDENT +}; + extern const char *git_author_info(int); extern const char *git_committer_info(int); -extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); -extern const char *fmt_name(const char *name, const char *email); +extern const char *fmt_ident(const char *name, const char *email, + enum want_ident whose_ident, + const char *date_str, int); +extern const char *fmt_name(enum want_ident); extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); diff --git a/config.c b/config.c index ff521eb27ad243..fd36db77901cca 100644 --- a/config.c +++ b/config.c @@ -1445,7 +1445,9 @@ int git_default_config(const char *var, const char *value, void *cb) if (starts_with(var, "core.")) return git_default_core_config(var, value, cb); - if (starts_with(var, "user.")) + if (starts_with(var, "user.") || + starts_with(var, "author.") || + starts_with(var, "committer.")) return git_ident_config(var, value, cb); if (starts_with(var, "i18n.")) diff --git a/ident.c b/ident.c index 33bcf40644cdf2..9c2eb0a2d02ac8 100644 --- a/ident.c +++ b/ident.c @@ -11,6 +11,10 @@ static struct strbuf git_default_name = STRBUF_INIT; static struct strbuf git_default_email = STRBUF_INIT; static struct strbuf git_default_date = STRBUF_INIT; +static struct strbuf git_author_name = STRBUF_INIT; +static struct strbuf git_author_email = STRBUF_INIT; +static struct strbuf git_committer_name = STRBUF_INIT; +static struct strbuf git_committer_email = STRBUF_INIT; static int default_email_is_bogus; static int default_name_is_bogus; @@ -355,13 +359,19 @@ N_("\n" "\n"); const char *fmt_ident(const char *name, const char *email, - const char *date_str, int flag) + enum want_ident whose_ident, const char *date_str, int flag) { static struct strbuf ident = STRBUF_INIT; int strict = (flag & IDENT_STRICT); int want_date = !(flag & IDENT_NO_DATE); int want_name = !(flag & IDENT_NO_NAME); + if (!email) { + if (whose_ident == WANT_AUTHOR_IDENT && git_author_email.len) + email = git_author_email.buf; + else if (whose_ident == WANT_COMMITTER_IDENT && git_committer_email.len) + email = git_committer_email.buf; + } if (!email) { if (strict && ident_use_config_only && !(ident_config_given & IDENT_MAIL_GIVEN)) { @@ -377,6 +387,13 @@ const char *fmt_ident(const char *name, const char *email, if (want_name) { int using_default = 0; + if (!name) { + if (whose_ident == WANT_AUTHOR_IDENT && git_author_name.len) + name = git_author_name.buf; + else if (whose_ident == WANT_COMMITTER_IDENT && + git_committer_name.len) + name = git_committer_name.buf; + } if (!name) { if (strict && ident_use_config_only && !(ident_config_given & IDENT_NAME_GIVEN)) { @@ -425,9 +442,25 @@ const char *fmt_ident(const char *name, const char *email, return ident.buf; } -const char *fmt_name(const char *name, const char *email) +const char *fmt_name(enum want_ident whose_ident) { - return fmt_ident(name, email, NULL, IDENT_STRICT | IDENT_NO_DATE); + char *name = NULL; + char *email = NULL; + + switch (whose_ident) { + case WANT_BLANK_IDENT: + break; + case WANT_AUTHOR_IDENT: + name = getenv("GIT_AUTHOR_NAME"); + email = getenv("GIT_AUTHOR_EMAIL"); + break; + case WANT_COMMITTER_IDENT: + name = getenv("GIT_COMMITTER_NAME"); + email = getenv("GIT_COMMITTER_EMAIL"); + break; + } + return fmt_ident(name, email, whose_ident, NULL, + IDENT_STRICT | IDENT_NO_DATE); } const char *git_author_info(int flag) @@ -438,6 +471,7 @@ const char *git_author_info(int flag) author_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), + WANT_AUTHOR_IDENT, getenv("GIT_AUTHOR_DATE"), flag); } @@ -450,6 +484,7 @@ const char *git_committer_info(int flag) committer_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), + WANT_COMMITTER_IDENT, getenv("GIT_COMMITTER_DATE"), flag); } @@ -473,10 +508,45 @@ int author_ident_sufficiently_given(void) return ident_is_sufficient(author_ident_explicitly_given); } -int git_ident_config(const char *var, const char *value, void *data) +static int set_ident(const char *var, const char *value) { - if (!strcmp(var, "user.useconfigonly")) { - ident_use_config_only = git_config_bool(var, value); + if (!strcmp(var, "author.name")) { + if (!value) + return config_error_nonbool(var); + strbuf_reset(&git_author_name); + strbuf_addstr(&git_author_name, value); + author_ident_explicitly_given |= IDENT_NAME_GIVEN; + ident_config_given |= IDENT_NAME_GIVEN; + return 0; + } + + if (!strcmp(var, "author.email")) { + if (!value) + return config_error_nonbool(var); + strbuf_reset(&git_author_email); + strbuf_addstr(&git_author_email, value); + author_ident_explicitly_given |= IDENT_MAIL_GIVEN; + ident_config_given |= IDENT_MAIL_GIVEN; + return 0; + } + + if (!strcmp(var, "committer.name")) { + if (!value) + return config_error_nonbool(var); + strbuf_reset(&git_committer_name); + strbuf_addstr(&git_committer_name, value); + committer_ident_explicitly_given |= IDENT_NAME_GIVEN; + ident_config_given |= IDENT_NAME_GIVEN; + return 0; + } + + if (!strcmp(var, "committer.email")) { + if (!value) + return config_error_nonbool(var); + strbuf_reset(&git_committer_email); + strbuf_addstr(&git_committer_email, value); + committer_ident_explicitly_given |= IDENT_MAIL_GIVEN; + ident_config_given |= IDENT_MAIL_GIVEN; return 0; } @@ -505,6 +575,16 @@ int git_ident_config(const char *var, const char *value, void *data) return 0; } +int git_ident_config(const char *var, const char *value, void *data) +{ + if (!strcmp(var, "user.useconfigonly")) { + ident_use_config_only = git_config_bool(var, value); + return 0; + } + + return set_ident(var, value); +} + static int buf_cmp(const char *a_begin, const char *a_end, const char *b_begin, const char *b_end) { diff --git a/log-tree.c b/log-tree.c index 10680c139eeb53..43ef4f430099c6 100644 --- a/log-tree.c +++ b/log-tree.c @@ -687,8 +687,7 @@ void show_log(struct rev_info *opt) */ if (ctx.need_8bit_cte >= 0 && opt->add_signoff) ctx.need_8bit_cte = - has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); + has_non_ascii(fmt_name(WANT_COMMITTER_IDENT)); ctx.date_mode = opt->date_mode; ctx.date_mode_explicit = opt->date_mode_explicit; ctx.abbrev = opt->diffopt.abbrev; diff --git a/sequencer.c b/sequencer.c index f5370f49659a3d..3505d52bb99a64 100644 --- a/sequencer.c +++ b/sequencer.c @@ -836,7 +836,7 @@ static const char *read_author_ident(struct strbuf *buf) } strbuf_reset(&out); - strbuf_addstr(&out, fmt_ident(name, email, date, 0)); + strbuf_addstr(&out, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, 0)); strbuf_swap(buf, &out); strbuf_release(&out); free(name); @@ -4087,8 +4087,7 @@ void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag) int has_footer; strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); + strbuf_addstr(&sob, fmt_name(WANT_COMMITTER_IDENT)); strbuf_addch(&sob, '\n'); if (!ignore_footer) diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh index 231b8cc19d6342..b2401cec3e3be0 100755 --- a/t/t7517-per-repo-email.sh +++ b/t/t7517-per-repo-email.sh @@ -85,4 +85,78 @@ test_expect_success REBASE_P \ test_must_fail git rebase -p master ' +test_expect_success 'author.name overrides user.name' ' + test_config user.name user && + test_config user.email user@example.com && + test_config author.name author && + test_commit author-name-override-user && + echo author user@example.com > expected-author && + echo user user@example.com > expected-committer && + git log --format="%an %ae" -1 > actual-author && + git log --format="%cn %ce" -1 > actual-committer && + test_cmp expected-author actual-author && + test_cmp expected-committer actual-committer +' + +test_expect_success 'author.email overrides user.email' ' + test_config user.name user && + test_config user.email user@example.com && + test_config author.email author@example.com && + test_commit author-email-override-user && + echo user author@example.com > expected-author && + echo user user@example.com > expected-committer && + git log --format="%an %ae" -1 > actual-author && + git log --format="%cn %ce" -1 > actual-committer && + test_cmp expected-author actual-author && + test_cmp expected-committer actual-committer +' + +test_expect_success 'committer.name overrides user.name' ' + test_config user.name user && + test_config user.email user@example.com && + test_config committer.name committer && + test_commit committer-name-override-user && + echo user user@example.com > expected-author && + echo committer user@example.com > expected-committer && + git log --format="%an %ae" -1 > actual-author && + git log --format="%cn %ce" -1 > actual-committer && + test_cmp expected-author actual-author && + test_cmp expected-committer actual-committer +' + +test_expect_success 'committer.email overrides user.email' ' + test_config user.name user && + test_config user.email user@example.com && + test_config committer.email committer@example.com && + test_commit committer-email-override-user && + echo user user@example.com > expected-author && + echo user committer@example.com > expected-committer && + git log --format="%an %ae" -1 > actual-author && + git log --format="%cn %ce" -1 > actual-committer && + test_cmp expected-author actual-author && + test_cmp expected-committer actual-committer +' + +test_expect_success 'author and committer environment variables override config settings' ' + test_config user.name user && + test_config user.email user@example.com && + test_config author.name author && + test_config author.email author@example.com && + test_config committer.name committer && + test_config committer.email committer@example.com && + GIT_AUTHOR_NAME=env_author && export GIT_AUTHOR_NAME && + GIT_AUTHOR_EMAIL=env_author@example.com && export GIT_AUTHOR_EMAIL && + GIT_COMMITTER_NAME=env_commit && export GIT_COMMITTER_NAME && + GIT_COMMITTER_EMAIL=env_commit@example.com && export GIT_COMMITTER_EMAIL && + test_commit env-override-conf && + echo env_author env_author@example.com > expected-author && + echo env_commit env_commit@example.com > expected-committer && + git log --format="%an %ae" -1 > actual-author && + git log --format="%cn %ce" -1 > actual-committer && + sane_unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && + sane_unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL && + test_cmp expected-author actual-author && + test_cmp expected-committer actual-committer +' + test_done From e92aa0e4ef5a91781530449f9466a45c16c91f7f Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Mon, 4 Feb 2019 21:13:16 +0000 Subject: [PATCH 0070/1015] revert "checkout: introduce checkout.overlayMode config" This reverts 1495ff7da5 ("checkout: introduce checkout.overlayMode config", 2019-01-08) and thus removes the checkout.overlayMode config option. The option was originally introduced to give users the option to make the new no-overlay behaviour the default. However users may be using 'git checkout' in scripts, even though it is porcelain. Users setting the option to false may actually end up accidentally breaking scripts. With the introduction of a new subcommand that will make the behaviour the default, the config option will not be needed anymore anyway. Revert the commit and remove the config option, so we don't risk breaking scripts. Suggested-by: Jonathan Nieder Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/config/checkout.txt | 7 ------- builtin/checkout.c | 8 +------- t/t2025-checkout-no-overlay.sh | 10 ---------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Documentation/config/checkout.txt b/Documentation/config/checkout.txt index 73380a8d862d37..c4118fa1968711 100644 --- a/Documentation/config/checkout.txt +++ b/Documentation/config/checkout.txt @@ -21,10 +21,3 @@ checkout.optimizeNewBranch:: will not update the skip-worktree bit in the index nor add/remove files in the working directory to reflect the current sparse checkout settings nor will it show the local changes. - -checkout.overlayMode:: - In the default overlay mode, `git checkout` never - removes files from the index or the working tree. When - setting `checkout.overlayMode` to false, files that appear in - the index and working tree, but not in are removed, - to make them match exactly. diff --git a/builtin/checkout.c b/builtin/checkout.c index b5dfc45736e458..0c5fe948ef5890 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1019,19 +1019,13 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { - struct checkout_opts *opts = cb; - if (!strcmp(var, "checkout.optimizenewbranch")) { checkout_optimize_new_branch = git_config_bool(var, value); return 0; } - if (!strcmp(var, "checkout.overlaymode")) { - opts->overlay_mode = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "diff.ignoresubmodules")) { + struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } diff --git a/t/t2025-checkout-no-overlay.sh b/t/t2025-checkout-no-overlay.sh index a4912e35cbe3aa..76330cb5ab79ab 100755 --- a/t/t2025-checkout-no-overlay.sh +++ b/t/t2025-checkout-no-overlay.sh @@ -44,14 +44,4 @@ test_expect_success '--no-overlay --theirs with D/F conflict deletes file' ' test_path_is_missing file1 ' -test_expect_success 'checkout with checkout.overlayMode=false deletes files not in ' ' - >file && - mkdir dir && - >dir/file1 && - git add file dir/file1 && - git -c checkout.overlayMode=false checkout HEAD -- file && - test_path_is_missing file && - test_path_is_file dir/file1 -' - test_done From 3173a94d577e14501039e85f333e9c98cbd92667 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Sat, 2 Feb 2019 21:30:12 +0800 Subject: [PATCH 0071/1015] t5323: test cases for git-pack-redundant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test cases for git pack-redundant to validate new algorithm for git pack-redundant. Signed-off-by: Jiang Xin Reviewed-by: SZEDER Gábor Helped-by: Eric Sunshine Reviewed-by: Sun Chao Signed-off-by: Junio C Hamano --- t/t5323-pack-redundant.sh | 467 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100755 t/t5323-pack-redundant.sh diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh new file mode 100755 index 00000000000000..5929c1da49ec84 --- /dev/null +++ b/t/t5323-pack-redundant.sh @@ -0,0 +1,467 @@ +#!/bin/sh +# +# Copyright (c) 2018 Jiang Xin +# + +test_description='Test git pack-redundant + +In order to test git-pack-redundant, we will create a number of objects and +packs in the repository `master.git`. The relationship between packs (P1-P8) +and objects (T, A-R) is showed in the following chart. Objects of a pack will +be marked with letter x, while objects of redundant packs will be marked with +exclamation point, and redundant pack itself will be marked with asterisk. + + | T A B C D E F G H I J K L M N O P Q R + ----+-------------------------------------- + P1 | x x x x x x x x + P2* | ! ! ! ! ! ! ! + P3 | x x x x x x + P4* | ! ! ! ! ! + P5 | x x x x + P6* | ! ! ! + P7 | x x + P8* | ! + ----+-------------------------------------- + ALL | x x x x x x x x x x x x x x x x x x x + +Another repository `shared.git` has unique objects (X-Z), while other objects +(marked with letter s) are shared through alt-odb (of `master.git`). The +relationship between packs and objects is as follows: + + | T A B C D E F G H I J K L M N O P Q R X Y Z + ----+---------------------------------------------- + Px1 | s s s x x x + Px2 | s s s x x x +' + +. ./test-lib.sh + +master_repo=master.git +shared_repo=shared.git + +# Create commits in and assign each commit's oid to shell variables +# given in the arguments (A, B, and C). E.g.: +# +# create_commits_in A B C +# +# NOTE: Avoid calling this function from a subshell since variable +# assignments will disappear when subshell exits. +create_commits_in () { + repo="$1" && + if ! parent=$(git -C "$repo" rev-parse HEAD^{} 2>/dev/null) + then + parent= + fi && + T=$(git -C "$repo" write-tree) && + shift && + while test $# -gt 0 + do + name=$1 && + test_tick && + if test -z "$parent" + then + oid=$(echo $name | git -C "$repo" commit-tree $T) + else + oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T) + fi && + eval $name=$oid && + parent=$oid && + shift || + return 1 + done && + git -C "$repo" update-ref refs/heads/master $oid +} + +# Create pack in and assign pack id to variable given in the 2nd argument +# (). Commits in the pack will be read from stdin. E.g.: +# +# create_pack_in <<-EOF +# ... +# EOF +# +# NOTE: commits from stdin should be given using heredoc, not using pipe, and +# avoid calling this function from a subshell since variable assignments will +# disappear when subshell exits. +create_pack_in () { + repo="$1" && + name="$2" && + pack=$(git -C "$repo/objects/pack" pack-objects -q pack) && + eval $name=$pack && + eval P$pack=$name:$pack +} + +format_packfiles () { + sed \ + -e "s#.*/pack-\(.*\)\.idx#\1#" \ + -e "s#.*/pack-\(.*\)\.pack#\1#" | + sort -u | + while read p + do + if test -z "$(eval echo \${P$p})" + then + echo $p + else + eval echo "\${P$p}" + fi + done | + sort +} + +test_expect_success 'setup master repo' ' + git init --bare "$master_repo" && + create_commits_in "$master_repo" A B C D E F G H I J K L M N O P Q R +' + +############################################################################# +# Chart of packs and objects for this test case +# +# | T A B C D E F G H I J K L M N O P Q R +# ----+-------------------------------------- +# P1 | x x x x x x x x +# P2 | x x x x x x x +# P3 | x x x x x x +# ----+-------------------------------------- +# ALL | x x x x x x x x x x x x x x x +# +############################################################################# +test_expect_success 'master: no redundant for pack 1, 2, 3' ' + create_pack_in "$master_repo" P1 <<-EOF && + $T + $A + $B + $C + $D + $E + $F + $R + EOF + create_pack_in "$master_repo" P2 <<-EOF && + $B + $C + $D + $E + $G + $H + $I + EOF + create_pack_in "$master_repo" P3 <<-EOF && + $F + $I + $J + $K + $L + $M + EOF + ( + cd "$master_repo" && + git pack-redundant --all >out && + test_must_be_empty out + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# | T A B C D E F G H I J K L M N O P Q R +# ----+-------------------------------------- +# P1 | x x x x x x x x +# P2* | ! ! ! ! ! ! ! +# P3 | x x x x x x +# P4 | x x x x x +# P5 | x x x x +# ----+-------------------------------------- +# ALL | x x x x x x x x x x x x x x x x x x +# +############################################################################# +test_expect_success 'master: one of pack-2/pack-3 is redundant' ' + create_pack_in "$master_repo" P4 <<-EOF && + $J + $K + $L + $M + $P + EOF + create_pack_in "$master_repo" P5 <<-EOF && + $G + $H + $N + $O + EOF + ( + cd "$master_repo" && + cat >expect <<-EOF && + P2:$P2 + EOF + git pack-redundant --all >out && + format_packfiles actual && + test_cmp expect actual + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# | T A B C D E F G H I J K L M N O P Q R +# ----+-------------------------------------- +# P1 | x x x x x x x x +# P2* | ! ! ! ! ! ! ! +# P3 | x x x x x x +# P4* | ! ! ! ! ! +# P5 | x x x x +# P6* | ! ! ! +# P7 | x x +# ----+-------------------------------------- +# ALL | x x x x x x x x x x x x x x x x x x x +# +############################################################################# +test_expect_success 'master: pack 2, 4, and 6 are redundant' ' + create_pack_in "$master_repo" P6 <<-EOF && + $N + $O + $Q + EOF + create_pack_in "$master_repo" P7 <<-EOF && + $P + $Q + EOF + ( + cd "$master_repo" && + cat >expect <<-EOF && + P2:$P2 + P4:$P4 + P6:$P6 + EOF + git pack-redundant --all >out && + format_packfiles actual && + test_cmp expect actual + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# | T A B C D E F G H I J K L M N O P Q R +# ----+-------------------------------------- +# P1 | x x x x x x x x +# P2* | ! ! ! ! ! ! ! +# P3 | x x x x x x +# P4* | ! ! ! ! ! +# P5 | x x x x +# P6* | ! ! ! +# P7 | x x +# P8* | ! +# ----+-------------------------------------- +# ALL | x x x x x x x x x x x x x x x x x x x +# +############################################################################# +test_expect_success 'master: pack-8 (subset of pack-1) is also redundant' ' + create_pack_in "$master_repo" P8 <<-EOF && + $A + EOF + ( + cd "$master_repo" && + cat >expect <<-EOF && + P2:$P2 + P4:$P4 + P6:$P6 + P8:$P8 + EOF + git pack-redundant --all >out && + format_packfiles actual && + test_cmp expect actual + ) +' + +test_expect_success 'master: clean loose objects' ' + ( + cd "$master_repo" && + git prune-packed && + find objects -type f | sed -e "/objects\/pack\//d" >out && + test_must_be_empty out + ) +' + +test_expect_success 'master: remove redundant packs and pass fsck' ' + ( + cd "$master_repo" && + git pack-redundant --all | xargs rm && + git fsck && + git pack-redundant --all >out && + test_must_be_empty out + ) +' + +# The following test cases will execute inside `shared.git`, instead of +# inside `master.git`. +test_expect_success 'setup shared.git' ' + git clone --mirror "$master_repo" "$shared_repo" && + ( + cd "$shared_repo" && + printf "../../$master_repo/objects\n" >objects/info/alternates + ) +' + +test_expect_success 'shared: all packs are redundant, but no output without --alt-odb' ' + ( + cd "$shared_repo" && + git pack-redundant --all >out && + test_must_be_empty out + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# ================ master.git =============== +# | T A B C D E F G H I J K L M N O P Q R <----------+ +# ----+-------------------------------------- | +# P1 | x x x x x x x x | +# P3 | x x x x x x | +# P5 | x x x x | +# P7 | x x | +# ----+-------------------------------------- | +# ALL | x x x x x x x x x x x x x x x x x x x | +# | +# | +# ================ shared.git =============== | +# | T A B C D E F G H I J K L M N O P Q R +# ----+-------------------------------------- +# P1* | s s s s s s s s +# P3* | s s s s s s +# P5* | s s s s +# P7* | s s +# ----+-------------------------------------- +# ALL | x x x x x x x x x x x x x x x x x x x +# +############################################################################# +test_expect_success 'shared: show redundant packs in stderr for verbose mode' ' + ( + cd "$shared_repo" && + cat >expect <<-EOF && + P1:$P1 + P3:$P3 + P5:$P5 + P7:$P7 + EOF + git pack-redundant --all --verbose >out 2>out.err && + test_must_be_empty out && + grep "pack$" out.err | format_packfiles >actual && + test_cmp expect actual + ) +' + +test_expect_success 'shared: remove redundant packs, no packs left' ' + ( + cd "$shared_repo" && + cat >expect <<-EOF && + fatal: Zero packs found! + EOF + git pack-redundant --all --alt-odb | xargs rm && + git fsck && + test_must_fail git pack-redundant --all --alt-odb >actual 2>&1 && + test_cmp expect actual + ) +' + +test_expect_success 'shared: create new objects and packs' ' + create_commits_in "$shared_repo" X Y Z && + create_pack_in "$shared_repo" Px1 <<-EOF && + $X + $Y + $Z + $A + $B + $C + EOF + create_pack_in "$shared_repo" Px2 <<-EOF + $X + $Y + $Z + $D + $E + $F + EOF +' + +test_expect_success 'shared: no redundant without --alt-odb' ' + ( + cd "$shared_repo" && + git pack-redundant --all >out && + test_must_be_empty out + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# ================ master.git =============== +# | T A B C D E F G H I J K L M N O P Q R <----------------+ +# ----+-------------------------------------- | +# P1 | x x x x x x x x | +# P3 | x x x x x x | +# P5 | x x x x | +# P7 | x x | +# ----+-------------------------------------- | +# ALL | x x x x x x x x x x x x x x x x x x x | +# | +# | +# ================ shared.git ======================= | +# | T A B C D E F G H I J K L M N O P Q R X Y Z +# ----+---------------------------------------------- +# Px1 | s s s x x x +# Px2*| s s s ! ! ! +# ----+---------------------------------------------- +# ALL | s s s s s s s s s s s s s s s s s s s x x x +# +############################################################################# +test_expect_success 'shared: one pack is redundant with --alt-odb' ' + ( + cd "$shared_repo" && + git pack-redundant --all --alt-odb >out && + format_packfiles actual && + test_line_count = 1 actual + ) +' + +############################################################################# +# Chart of packs and objects for this test case +# +# ================ master.git =============== +# | T A B C D E F G H I J K L M N O P Q R <----------------+ +# ----+-------------------------------------- | +# P1 | x x x x x x x x | +# P3 | x x x x x x | +# P5 | x x x x | +# P7 | x x | +# ----+-------------------------------------- | +# ALL | x x x x x x x x x x x x x x x x x x x | +# | +# | +# ================ shared.git ======================= | +# | T A B C D E F G H I J K L M N O P Q R X Y Z +# ----+---------------------------------------------- +# Px1*| s s s i i i +# Px2*| s s s i i i +# ----+---------------------------------------------- +# ALL | s s s s s s s s s s s s s s s s s s s i i i +# (ignored objects, marked with i) +# +############################################################################# +test_expect_success 'shared: ignore unique objects and all two packs are redundant' ' + ( + cd "$shared_repo" && + cat >expect <<-EOF && + Px1:$Px1 + Px2:$Px2 + EOF + git pack-redundant --all --alt-odb >out <<-EOF && + $X + $Y + $Z + EOF + format_packfiles actual && + test_cmp expect actual + ) +' + +test_done From 301117764041101ae228e8e33c12c0b232b06408 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Sat, 2 Feb 2019 21:30:13 +0800 Subject: [PATCH 0072/1015] pack-redundant: delay creation of unique_objects Instead of initializing unique_objects in `add_pack()`, copy from all_objects in `cmp_two_packs()`, when unwanted objects are removed from all_objects. This will save memory (no allocate memory for alt-odb packs), and run `llist_sorted_difference_inplace()` only once when removing ignored objects and removing objects in alt-odb in `scan_alt_odb_packs()`. Signed-off-by: Jiang Xin Signed-off-by: Junio C Hamano --- builtin/pack-redundant.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index cf9a9aabd4eb2e..f7dab0ec60008d 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -254,6 +254,11 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) struct llist_item *p1_hint = NULL, *p2_hint = NULL; const unsigned int hashsz = the_hash_algo->rawsz; + if (!p1->unique_objects) + p1->unique_objects = llist_copy(p1->all_objects); + if (!p2->unique_objects) + p2->unique_objects = llist_copy(p2->all_objects); + p1_base = p1->pack->index_data; p2_base = p2->pack->index_data; p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8); @@ -536,7 +541,7 @@ static void scan_alt_odb_packs(void) while (alt) { local = local_packs; while (local) { - llist_sorted_difference_inplace(local->unique_objects, + llist_sorted_difference_inplace(local->all_objects, alt->all_objects); local = local->next; } @@ -567,8 +572,7 @@ static struct pack_list * add_pack(struct packed_git *p) llist_insert_back(l.all_objects, (const struct object_id *)(base + off)); off += step; } - /* this list will be pruned in cmp_two_packs later */ - l.unique_objects = llist_copy(l.all_objects); + l.unique_objects = NULL; if (p->pack_local) return pack_list_insert(&local_packs, &l); else @@ -646,7 +650,6 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) load_all_objects(); - cmp_local_packs(); if (alt_odb) scan_alt_odb_packs(); @@ -663,10 +666,12 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) llist_sorted_difference_inplace(all_objects, ignore); pl = local_packs; while (pl) { - llist_sorted_difference_inplace(pl->unique_objects, ignore); + llist_sorted_difference_inplace(pl->all_objects, ignore); pl = pl->next; } + cmp_local_packs(); + minimize(&min); if (verbose) { From 8822859363a86ade287c7deb07224af345a699f4 Mon Sep 17 00:00:00 2001 From: Sun Chao Date: Sat, 2 Feb 2019 21:30:14 +0800 Subject: [PATCH 0073/1015] pack-redundant: delete redundant code The objects in alt-odb are removed from `all_objects` twice in `load_all_objects` and `scan_alt_odb_packs`, remove it from the later function. Signed-off-by: Sun Chao Signed-off-by: Jiang Xin Signed-off-by: Junio C Hamano --- builtin/pack-redundant.c | 1 - 1 file changed, 1 deletion(-) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index f7dab0ec60008d..4a06f057ddf3f5 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -545,7 +545,6 @@ static void scan_alt_odb_packs(void) alt->all_objects); local = local->next; } - llist_sorted_difference_inplace(all_objects, alt->all_objects); alt = alt->next; } } From 3084a01e5efefc57d8f52562c82fd41799379697 Mon Sep 17 00:00:00 2001 From: Sun Chao Date: Sat, 2 Feb 2019 21:30:15 +0800 Subject: [PATCH 0074/1015] pack-redundant: new algorithm to find min packs When calling `git pack-redundant --all`, if there are too many local packs and too many redundant objects within them, the too deep iteration of `get_permutations` will exhaust all the resources, and the process of `git pack-redundant` will be killed. The following script could create a repository with too many redundant packs, and running `git pack-redundant --all` in the `test.git` repo will die soon. #!/bin/sh repo="$(pwd)/test.git" work="$(pwd)/test" i=1 max=199 if test -d "$repo" || test -d "$work"; then echo >&2 "ERROR: '$repo' or '$work' already exist" exit 1 fi git init -q --bare "$repo" git --git-dir="$repo" config gc.auto 0 git --git-dir="$repo" config transfer.unpackLimit 0 git clone -q "$repo" "$work" 2>/dev/null while :; do cd "$work" echo "loop $i: $(date +%s)" >$i git add $i git commit -q -sm "loop $i" git push -q origin HEAD:master printf "\rCreate pack %4d/%d\t" $i $max if test $i -ge $max; then break; fi cd "$repo" git repack -q if test $(($i % 2)) -eq 0; then git repack -aq pack=$(ls -t $repo/objects/pack/*.pack | head -1) touch "${pack%.pack}.keep" fi i=$((i+1)) done printf "\ndone\n" To get the `min` unique pack list, we can replace the iteration in `minimize` function with a new algorithm, and this could solve this issue: 1. Get the unique and non_uniqe packs, add the unique packs to the `min` list. 2. Remove the objects of unique packs from non_unique packs, then each object left in the non_unique packs will have at least two copies. 3. Sort the non_unique packs by the objects' size, more objects first, and add the first non_unique pack to `min` list. 4. Drop the duplicated objects from other packs in the ordered non_unique pack list, and repeat step 3. Some test cases will fail on Mac OS X. Mark them and will resolve in later commit. Original PR and discussions: https://github.com/jiangxin/git/pull/25 Signed-off-by: Sun Chao Signed-off-by: Jiang Xin Signed-off-by: Junio C Hamano --- builtin/pack-redundant.c | 194 +++++++++++++------------------------- t/t5323-pack-redundant.sh | 12 +-- 2 files changed, 73 insertions(+), 133 deletions(-) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 4a06f057ddf3f5..d6d9a66e46cdc2 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -35,11 +35,6 @@ static struct pack_list { struct llist *all_objects; } *local_packs = NULL, *altodb_packs = NULL; -struct pll { - struct pll *next; - struct pack_list *pl; -}; - static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) @@ -63,15 +58,6 @@ static inline struct llist_item *llist_item_get(void) return new_item; } -static void llist_free(struct llist *list) -{ - while ((list->back = list->front)) { - list->front = list->front->next; - llist_item_put(list->back); - } - free(list); -} - static inline void llist_init(struct llist **list) { *list = xmalloc(sizeof(struct llist)); @@ -290,78 +276,6 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) } } -static void pll_free(struct pll *l) -{ - struct pll *old; - struct pack_list *opl; - - while (l) { - old = l; - while (l->pl) { - opl = l->pl; - l->pl = opl->next; - free(opl); - } - l = l->next; - free(old); - } -} - -/* all the permutations have to be free()d at the same time, - * since they refer to each other - */ -static struct pll * get_permutations(struct pack_list *list, int n) -{ - struct pll *subset, *ret = NULL, *new_pll = NULL; - - if (list == NULL || pack_list_size(list) < n || n == 0) - return NULL; - - if (n == 1) { - while (list) { - new_pll = xmalloc(sizeof(*new_pll)); - new_pll->pl = NULL; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - list = list->next; - } - return ret; - } - - while (list->next) { - subset = get_permutations(list->next, n - 1); - while (subset) { - new_pll = xmalloc(sizeof(*new_pll)); - new_pll->pl = subset->pl; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - subset = subset->next; - } - list = list->next; - } - return ret; -} - -static int is_superset(struct pack_list *pl, struct llist *list) -{ - struct llist *diff; - - diff = llist_copy(list); - - while (pl) { - llist_sorted_difference_inplace(diff, pl->all_objects); - if (diff->size == 0) { /* we're done */ - llist_free(diff); - return 1; - } - pl = pl->next; - } - llist_free(diff); - return 0; -} - static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; @@ -426,14 +340,52 @@ static inline off_t pack_set_bytecount(struct pack_list *pl) return ret; } +static int cmp_pack_list_reverse(const void *a, const void *b) +{ + struct pack_list *pl_a = *((struct pack_list **)a); + struct pack_list *pl_b = *((struct pack_list **)b); + size_t sz_a = pl_a->all_objects->size; + size_t sz_b = pl_b->all_objects->size; + + if (sz_a == sz_b) + return 0; + else if (sz_a < sz_b) + return 1; + else + return -1; +} + +/* Sort pack_list, greater size of all_objects first */ +static void sort_pack_list(struct pack_list **pl) +{ + struct pack_list **ary, *p; + int i; + size_t n = pack_list_size(*pl); + + if (n < 2) + return; + + /* prepare an array of packed_list for easier sorting */ + ary = xcalloc(n, sizeof(struct pack_list *)); + for (n = 0, p = *pl; p; p = p->next) + ary[n++] = p; + + QSORT(ary, n, cmp_pack_list_reverse); + + /* link them back again */ + for (i = 0; i < n - 1; i++) + ary[i]->next = ary[i + 1]; + ary[n - 1]->next = NULL; + *pl = ary[0]; + + free(ary); +} + + static void minimize(struct pack_list **min) { - struct pack_list *pl, *unique = NULL, - *non_unique = NULL, *min_perm = NULL; - struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm; - struct llist *missing; - off_t min_perm_size = 0, perm_size; - int n; + struct pack_list *pl, *unique = NULL, *non_unique = NULL; + struct llist *missing, *unique_pack_objects; pl = local_packs; while (pl) { @@ -451,49 +403,37 @@ static void minimize(struct pack_list **min) pl = pl->next; } + *min = unique; + /* return if there are no objects missing from the unique set */ if (missing->size == 0) { - *min = unique; free(missing); return; } - /* find the permutations which contain all missing objects */ - for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) { - perm_all = perm = get_permutations(non_unique, n); - while (perm) { - if (is_superset(perm->pl, missing)) { - new_perm = xmalloc(sizeof(struct pll)); - memcpy(new_perm, perm, sizeof(struct pll)); - new_perm->next = perm_ok; - perm_ok = new_perm; - } - perm = perm->next; - } - if (perm_ok) - break; - pll_free(perm_all); - } - if (perm_ok == NULL) - die("Internal error: No complete sets found!"); - - /* find the permutation with the smallest size */ - perm = perm_ok; - while (perm) { - perm_size = pack_set_bytecount(perm->pl); - if (!min_perm_size || min_perm_size > perm_size) { - min_perm_size = perm_size; - min_perm = perm->pl; - } - perm = perm->next; - } - *min = min_perm; - /* add the unique packs to the list */ - pl = unique; + unique_pack_objects = llist_copy(all_objects); + llist_sorted_difference_inplace(unique_pack_objects, missing); + + /* remove unique pack objects from the non_unique packs */ + pl = non_unique; while (pl) { - pack_list_insert(min, pl); + llist_sorted_difference_inplace(pl->all_objects, unique_pack_objects); pl = pl->next; } + + while (non_unique) { + /* sort the non_unique packs, greater size of all_objects first */ + sort_pack_list(&non_unique); + if (non_unique->all_objects->size == 0) + break; + + pack_list_insert(min, non_unique); + + for (pl = non_unique->next; pl && pl->all_objects->size > 0; pl = pl->next) + llist_sorted_difference_inplace(pl->all_objects, non_unique->all_objects); + + non_unique = non_unique->next; + } } static void load_all_objects(void) @@ -606,7 +546,7 @@ static void load_all(void) int cmd_pack_redundant(int argc, const char **argv, const char *prefix) { int i; - struct pack_list *min, *red, *pl; + struct pack_list *min = NULL, *red, *pl; struct llist *ignore; struct object_id *oid; char buf[GIT_MAX_HEXSZ + 2]; /* hex hash + \n + \0 */ diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh index 5929c1da49ec84..e859965489c4d8 100755 --- a/t/t5323-pack-redundant.sh +++ b/t/t5323-pack-redundant.sh @@ -173,7 +173,7 @@ test_expect_success 'master: no redundant for pack 1, 2, 3' ' # ALL | x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_success 'master: one of pack-2/pack-3 is redundant' ' +test_expect_failure 'master: one of pack-2/pack-3 is redundant (failed on Mac)' ' create_pack_in "$master_repo" P4 <<-EOF && $J $K @@ -214,7 +214,7 @@ test_expect_success 'master: one of pack-2/pack-3 is redundant' ' # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_success 'master: pack 2, 4, and 6 are redundant' ' +test_expect_failure 'master: pack 2, 4, and 6 are redundant (failed on Mac)' ' create_pack_in "$master_repo" P6 <<-EOF && $N $O @@ -254,7 +254,7 @@ test_expect_success 'master: pack 2, 4, and 6 are redundant' ' # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_success 'master: pack-8 (subset of pack-1) is also redundant' ' +test_expect_failure 'master: pack-8 (subset of pack-1) is also redundant (failed on Mac)' ' create_pack_in "$master_repo" P8 <<-EOF && $A EOF @@ -281,7 +281,7 @@ test_expect_success 'master: clean loose objects' ' ) ' -test_expect_success 'master: remove redundant packs and pass fsck' ' +test_expect_failure 'master: remove redundant packs and pass fsck (failed on Mac)' ' ( cd "$master_repo" && git pack-redundant --all | xargs rm && @@ -301,7 +301,7 @@ test_expect_success 'setup shared.git' ' ) ' -test_expect_success 'shared: all packs are redundant, but no output without --alt-odb' ' +test_expect_failure 'shared: all packs are redundant, but no output without --alt-odb (failed on Mac)' ' ( cd "$shared_repo" && git pack-redundant --all >out && @@ -334,7 +334,7 @@ test_expect_success 'shared: all packs are redundant, but no output without --al # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_success 'shared: show redundant packs in stderr for verbose mode' ' +test_expect_failure 'shared: show redundant packs in stderr for verbose mode (failed on Mac)' ' ( cd "$shared_repo" && cat >expect <<-EOF && From 4bc0cc12c19f00380052d0cfaf4e71e0a4630f41 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Sat, 2 Feb 2019 21:30:16 +0800 Subject: [PATCH 0075/1015] pack-redundant: rename pack_list.all_objects New algorithm uses `pack_list.all_objects` to track remaining objects, so rename it to `pack_list.remaining_objects`. Signed-off-by: Jiang Xin Signed-off-by: Junio C Hamano --- builtin/pack-redundant.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index d6d9a66e46cdc2..15cdf233c4e812 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -32,7 +32,7 @@ static struct pack_list { struct pack_list *next; struct packed_git *pack; struct llist *unique_objects; - struct llist *all_objects; + struct llist *remaining_objects; } *local_packs = NULL, *altodb_packs = NULL; static struct llist_item *free_nodes; @@ -241,9 +241,9 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) const unsigned int hashsz = the_hash_algo->rawsz; if (!p1->unique_objects) - p1->unique_objects = llist_copy(p1->all_objects); + p1->unique_objects = llist_copy(p1->remaining_objects); if (!p2->unique_objects) - p2->unique_objects = llist_copy(p2->all_objects); + p2->unique_objects = llist_copy(p2->remaining_objects); p1_base = p1->pack->index_data; p2_base = p2->pack->index_data; @@ -344,8 +344,8 @@ static int cmp_pack_list_reverse(const void *a, const void *b) { struct pack_list *pl_a = *((struct pack_list **)a); struct pack_list *pl_b = *((struct pack_list **)b); - size_t sz_a = pl_a->all_objects->size; - size_t sz_b = pl_b->all_objects->size; + size_t sz_a = pl_a->remaining_objects->size; + size_t sz_b = pl_b->remaining_objects->size; if (sz_a == sz_b) return 0; @@ -355,7 +355,7 @@ static int cmp_pack_list_reverse(const void *a, const void *b) return -1; } -/* Sort pack_list, greater size of all_objects first */ +/* Sort pack_list, greater size of remaining_objects first */ static void sort_pack_list(struct pack_list **pl) { struct pack_list **ary, *p; @@ -399,7 +399,7 @@ static void minimize(struct pack_list **min) missing = llist_copy(all_objects); pl = unique; while (pl) { - llist_sorted_difference_inplace(missing, pl->all_objects); + llist_sorted_difference_inplace(missing, pl->remaining_objects); pl = pl->next; } @@ -417,20 +417,20 @@ static void minimize(struct pack_list **min) /* remove unique pack objects from the non_unique packs */ pl = non_unique; while (pl) { - llist_sorted_difference_inplace(pl->all_objects, unique_pack_objects); + llist_sorted_difference_inplace(pl->remaining_objects, unique_pack_objects); pl = pl->next; } while (non_unique) { - /* sort the non_unique packs, greater size of all_objects first */ + /* sort the non_unique packs, greater size of remaining_objects first */ sort_pack_list(&non_unique); - if (non_unique->all_objects->size == 0) + if (non_unique->remaining_objects->size == 0) break; pack_list_insert(min, non_unique); - for (pl = non_unique->next; pl && pl->all_objects->size > 0; pl = pl->next) - llist_sorted_difference_inplace(pl->all_objects, non_unique->all_objects); + for (pl = non_unique->next; pl && pl->remaining_objects->size > 0; pl = pl->next) + llist_sorted_difference_inplace(pl->remaining_objects, non_unique->remaining_objects); non_unique = non_unique->next; } @@ -445,7 +445,7 @@ static void load_all_objects(void) while (pl) { hint = NULL; - l = pl->all_objects->front; + l = pl->remaining_objects->front; while (l) { hint = llist_insert_sorted_unique(all_objects, l->oid, hint); @@ -456,7 +456,7 @@ static void load_all_objects(void) /* remove objects present in remote packs */ pl = altodb_packs; while (pl) { - llist_sorted_difference_inplace(all_objects, pl->all_objects); + llist_sorted_difference_inplace(all_objects, pl->remaining_objects); pl = pl->next; } } @@ -481,8 +481,8 @@ static void scan_alt_odb_packs(void) while (alt) { local = local_packs; while (local) { - llist_sorted_difference_inplace(local->all_objects, - alt->all_objects); + llist_sorted_difference_inplace(local->remaining_objects, + alt->remaining_objects); local = local->next; } alt = alt->next; @@ -499,7 +499,7 @@ static struct pack_list * add_pack(struct packed_git *p) return NULL; l.pack = p; - llist_init(&l.all_objects); + llist_init(&l.remaining_objects); if (open_pack_index(p)) return NULL; @@ -508,7 +508,7 @@ static struct pack_list * add_pack(struct packed_git *p) base += 256 * 4 + ((p->index_version < 2) ? 4 : 8); step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0); while (off < p->num_objects * step) { - llist_insert_back(l.all_objects, (const struct object_id *)(base + off)); + llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off)); off += step; } l.unique_objects = NULL; @@ -605,7 +605,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) llist_sorted_difference_inplace(all_objects, ignore); pl = local_packs; while (pl) { - llist_sorted_difference_inplace(pl->all_objects, ignore); + llist_sorted_difference_inplace(pl->remaining_objects, ignore); pl = pl->next; } From 0e37abd2e89d475500e93a6368f1f054ca2fec55 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Sat, 2 Feb 2019 21:30:17 +0800 Subject: [PATCH 0076/1015] pack-redundant: consistent sort method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SZEDER reported that test case t5323 has different test result on MacOS. This is because `cmp_pack_list_reverse` cannot give identical result when two pack being sorted has the same size of remaining_objects. Changes to the sorting function will make consistent test result for t5323. The new algorithm to find redundant packs is a trade-off to save memory resources, and the result of it may be different with old one, and may be not the best result sometimes. Update t5323 for the new algorithm. Reported-by: SZEDER Gábor Signed-off-by: Jiang Xin Signed-off-by: Junio C Hamano --- builtin/pack-redundant.c | 24 ++++++++++++++++-------- t/t5323-pack-redundant.sh | 18 +++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 15cdf233c4e812..29ff5e99cbb5ed 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -33,6 +33,7 @@ static struct pack_list { struct packed_git *pack; struct llist *unique_objects; struct llist *remaining_objects; + size_t all_objects_size; } *local_packs = NULL, *altodb_packs = NULL; static struct llist_item *free_nodes; @@ -340,19 +341,25 @@ static inline off_t pack_set_bytecount(struct pack_list *pl) return ret; } -static int cmp_pack_list_reverse(const void *a, const void *b) +static int cmp_remaining_objects(const void *a, const void *b) { struct pack_list *pl_a = *((struct pack_list **)a); struct pack_list *pl_b = *((struct pack_list **)b); - size_t sz_a = pl_a->remaining_objects->size; - size_t sz_b = pl_b->remaining_objects->size; - if (sz_a == sz_b) - return 0; - else if (sz_a < sz_b) + if (pl_a->remaining_objects->size == pl_b->remaining_objects->size) { + /* have the same remaining_objects, big pack first */ + if (pl_a->all_objects_size == pl_b->all_objects_size) + return 0; + else if (pl_a->all_objects_size < pl_b->all_objects_size) + return 1; + else + return -1; + } else if (pl_a->remaining_objects->size < pl_b->remaining_objects->size) { + /* sort by remaining objects, more objects first */ return 1; - else + } else { return -1; + } } /* Sort pack_list, greater size of remaining_objects first */ @@ -370,7 +377,7 @@ static void sort_pack_list(struct pack_list **pl) for (n = 0, p = *pl; p; p = p->next) ary[n++] = p; - QSORT(ary, n, cmp_pack_list_reverse); + QSORT(ary, n, cmp_remaining_objects); /* link them back again */ for (i = 0; i < n - 1; i++) @@ -511,6 +518,7 @@ static struct pack_list * add_pack(struct packed_git *p) llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off)); off += step; } + l.all_objects_size = l.remaining_objects->size; l.unique_objects = NULL; if (p->pack_local) return pack_list_insert(&local_packs, &l); diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh index e859965489c4d8..6b4d1ca3536acc 100755 --- a/t/t5323-pack-redundant.sh +++ b/t/t5323-pack-redundant.sh @@ -165,15 +165,15 @@ test_expect_success 'master: no redundant for pack 1, 2, 3' ' # | T A B C D E F G H I J K L M N O P Q R # ----+-------------------------------------- # P1 | x x x x x x x x -# P2* | ! ! ! ! ! ! ! -# P3 | x x x x x x +# P2 | x x x x x x x +# P3* | ! ! ! ! ! ! # P4 | x x x x x # P5 | x x x x # ----+-------------------------------------- # ALL | x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_failure 'master: one of pack-2/pack-3 is redundant (failed on Mac)' ' +test_expect_success 'master: one of pack-2/pack-3 is redundant' ' create_pack_in "$master_repo" P4 <<-EOF && $J $K @@ -190,7 +190,7 @@ test_expect_failure 'master: one of pack-2/pack-3 is redundant (failed on Mac)' ( cd "$master_repo" && cat >expect <<-EOF && - P2:$P2 + P3:$P3 EOF git pack-redundant --all >out && format_packfiles actual && @@ -214,7 +214,7 @@ test_expect_failure 'master: one of pack-2/pack-3 is redundant (failed on Mac)' # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_failure 'master: pack 2, 4, and 6 are redundant (failed on Mac)' ' +test_expect_success 'master: pack 2, 4, and 6 are redundant' ' create_pack_in "$master_repo" P6 <<-EOF && $N $O @@ -254,7 +254,7 @@ test_expect_failure 'master: pack 2, 4, and 6 are redundant (failed on Mac)' ' # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_failure 'master: pack-8 (subset of pack-1) is also redundant (failed on Mac)' ' +test_expect_success 'master: pack-8 (subset of pack-1) is also redundant' ' create_pack_in "$master_repo" P8 <<-EOF && $A EOF @@ -281,7 +281,7 @@ test_expect_success 'master: clean loose objects' ' ) ' -test_expect_failure 'master: remove redundant packs and pass fsck (failed on Mac)' ' +test_expect_success 'master: remove redundant packs and pass fsck' ' ( cd "$master_repo" && git pack-redundant --all | xargs rm && @@ -301,7 +301,7 @@ test_expect_success 'setup shared.git' ' ) ' -test_expect_failure 'shared: all packs are redundant, but no output without --alt-odb (failed on Mac)' ' +test_expect_success 'shared: all packs are redundant, but no output without --alt-odb' ' ( cd "$shared_repo" && git pack-redundant --all >out && @@ -334,7 +334,7 @@ test_expect_failure 'shared: all packs are redundant, but no output without --al # ALL | x x x x x x x x x x x x x x x x x x x # ############################################################################# -test_expect_failure 'shared: show redundant packs in stderr for verbose mode (failed on Mac)' ' +test_expect_success 'shared: show redundant packs in stderr for verbose mode' ' ( cd "$shared_repo" && cat >expect <<-EOF && From db7750cfbec60ca7d5863b25fc4ef32f042e4baf Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Wed, 6 Feb 2019 01:42:46 -0800 Subject: [PATCH 0077/1015] completion: complete git submodule absorbgitdirs Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9e8ec95c3c7adb..ed41ddeb8d6604 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2580,7 +2580,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init deinit update summary foreach sync" + local subcommands="add status init deinit update summary foreach sync absorbgitdirs" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in From 7a4bb55f3a47756b5cd9ca2d120c7fe119439e81 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Thu, 7 Feb 2019 02:18:55 -0800 Subject: [PATCH 0078/1015] git-submodule.txt: "--branch " option defaults to 'master' This behavior is mentioned in gitmodules.txt but not in git-submodule.txt so we copy the information over so that it is not missed. Also, add the missed argument to the -b/--branch option. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index ba3c4df550acfe..4150148fa3ff5d 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -255,13 +255,14 @@ OPTIONS This option is only valid for the deinit command. Unregister all submodules in the working tree. --b:: ---branch:: +-b :: +--branch :: Branch of repository to add as submodule. The name of the branch is recorded as `submodule..branch` in `.gitmodules` for `update --remote`. A special value of `.` is used to indicate that the name of the branch in the submodule should be the - same name as the current branch in the current repository. + same name as the current branch in the current repository. If the + option is not specified, it defaults to 'master'. -f:: --force:: From d76ce4f734634f47b467b7f6eea11d6bf8c81f22 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Thu, 7 Feb 2019 17:12:46 -0800 Subject: [PATCH 0079/1015] log,diff-tree: add --combined-all-paths option The combined diff format for merges will only list one filename, even if rename or copy detection is active. For example, with raw format one might see: ::100644 100644 100644 fabadb8 cc95eb0 4866510 MM describe.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh ::100644 100644 100644 e07d6c5 9042e82 ee91881 RR phooey.c This doesn't let us know what the original name of bar.sh was in the first parent, and doesn't let us know what either of the original names of phooey.c were in either of the parents. In contrast, for non-merge commits, raw format does provide original filenames (and a rename score to boot). In order to also provide original filenames for merge commits, add a --combined-all-paths option (which must be used with either -c or --cc, and is likely only useful with rename or copy detection active) so that we can print tab-separated filenames when renames are involved. This transforms the above output to: ::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c desc.c desc.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM foo.sh bar.sh bar.sh ::100644 100644 100644 e07d6c5 9042e82 ee91881 RR fooey.c fuey.c phooey.c Further, in patch format, this changes the from/to headers so that instead of just having one "from" header, we get one for each parent. For example, instead of having --- a/phooey.c +++ b/phooey.c we would see --- a/fooey.c --- a/fuey.c +++ b/phooey.c Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- Documentation/diff-format.txt | 20 +++++- Documentation/diff-generate-patch.txt | 13 ++++ Documentation/git-diff-tree.txt | 11 +++- Documentation/rev-list-options.txt | 7 +++ builtin/diff-tree.c | 6 +- combine-diff.c | 76 +++++++++++++++++++---- diff.h | 1 + revision.c | 6 ++ revision.h | 1 + t/t4038-diff-combined.sh | 88 +++++++++++++++++++++++++++ 10 files changed, 212 insertions(+), 17 deletions(-) diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index cdcc17f0ad574b..4d846d73463c52 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -95,12 +95,26 @@ from the format described above in the following way: . there are more "src" modes and "src" sha1 . status is concatenated status characters for each parent . no optional "score" number -. single path, only for "dst" +. tab-separated pathname(s) of the file -Example: +For `-c` and `--cc`, only the destination or final path is shown even +if the file was renamed on any side of history. With +`--combined-all-paths`, the name of the path in each parent is shown +followed by the name of the path in the merge commit. + +Examples for `-c` and `--cc` without `--combined-all-paths`: +------------------------------------------------ +::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c +::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh +::100644 100644 100644 e07d6c5 9042e82 ee91881 RR phooey.c +------------------------------------------------ + +Examples when `--combined-all-paths` added to either `-c` or `--cc`: ------------------------------------------------ -::100644 100644 100644 fabadb8 cc95eb0 4866510 MM describe.c +::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c desc.c desc.c +::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM foo.sh bar.sh bar.sh +::100644 100644 100644 e07d6c5 9042e82 ee91881 RR fooey.c fuey.c phooey.c ------------------------------------------------ Note that 'combined diff' lists only files which were modified from diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index 231105cff48d9c..f10ca410ad8a93 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -143,6 +143,19 @@ copying detection) are designed to work with diff of two Similar to two-line header for traditional 'unified' diff format, `/dev/null` is used to signal created or deleted files. ++ +However, if the --combined-all-paths option is provided, instead of a +two-line from-file/to-file you get a N+1 line from-file/to-file header, +where N is the number of parents in the merge commit + + --- a/file + --- a/file + --- a/file + +++ b/file ++ +This extended format can be useful if rename or copy detection is +active, to allow you to see the original name of the file in different +parents. 4. Chunk header format is modified to prevent people from accidentally feeding it to `patch -p1`. Combined diff format diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index 2319b2b1920946..28b93ecd54fd63 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -10,8 +10,8 @@ SYNOPSIS -------- [verse] 'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty] - [-t] [-r] [-c | --cc] [--root] [] - [] [...] + [-t] [-r] [-c | --cc] [--combined-all-paths] [--root] + [] [] [...] DESCRIPTION ----------- @@ -108,6 +108,13 @@ include::pretty-options.txt[] itself and the commit log message is not shown, just like in any other "empty diff" case. +--combined-all-paths:: + This flag causes combined diffs (used for merge commits) to + list the name of the file from all parents. It thus only has + effect when -c or --cc are specified, and is likely only + useful if filename changes are detected (i.e. when either + rename or copy detection have been requested). + --always:: Show the commit itself and the commit log message even if the diff itself is empty. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 98b538bc779635..642ce943c17962 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -948,6 +948,13 @@ options may be given. See linkgit:git-diff-files[1] for more options. the parents have only two variants and the merge result picks one of them without modification. +--combined-all-paths:: + This flag causes combined diffs (used for merge commits) to + list the name of the file from all parents. It thus only has + effect when -c or --cc are specified, and is likely only + useful if filename changes are detected (i.e. when either + rename or copy detection have been requested). + -m:: This flag makes the merge commits show the full diff like regular commits; for each merge parent, a separate log entry diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index ef996126d7b521..ebe48f60c159bf 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -82,9 +82,13 @@ static int diff_tree_stdin(char *line) } static const char diff_tree_usage[] = -"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " +"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " "[] [] [...]\n" " -r diff recursively\n" +" -c show combined diff for merge commits\n" +" --cc show combined diff for merge commits removing uninteresting hunks\n" +" --combined-all-paths\n" +" show name of file in all parents for combined diffs\n" " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; diff --git a/combine-diff.c b/combine-diff.c index a143c006341170..54cb892ae5c3ef 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -23,11 +23,20 @@ static int compare_paths(const struct combine_diff_path *one, two->path, strlen(two->path), two->mode); } -static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) +static int filename_changed(char status) +{ + return status == 'R' || status == 'C'; +} + +static struct combine_diff_path *intersect_paths( + struct combine_diff_path *curr, + int n, + int num_parent, + int combined_all_paths) { struct diff_queue_struct *q = &diff_queued_diff; struct combine_diff_path *p, **tail = &curr; - int i, cmp; + int i, j, cmp; if (!n) { for (i = 0; i < q->nr; i++) { @@ -50,6 +59,13 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; + + if (combined_all_paths && + filename_changed(p->parent[n].status)) { + strbuf_init(&p->parent[n].path, 0); + strbuf_addstr(&p->parent[n].path, + q->queue[i]->one->path); + } *tail = p; tail = &p->next; } @@ -68,6 +84,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, if (cmp < 0) { /* p->path not in q->queue[]; drop it */ *tail = p->next; + for (j = 0; j < num_parent; j++) + if (combined_all_paths && + filename_changed(p->parent[j].status)) + strbuf_release(&p->parent[j].path); free(p); continue; } @@ -81,6 +101,10 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, oidcpy(&p->parent[n].oid, &q->queue[i]->one->oid); p->parent[n].mode = q->queue[i]->one->mode; p->parent[n].status = q->queue[i]->status; + if (combined_all_paths && + filename_changed(p->parent[n].status)) + strbuf_addstr(&p->parent[n].path, + q->queue[i]->one->path); tail = &p->next; i++; @@ -960,12 +984,25 @@ static void show_combined_header(struct combine_diff_path *elem, if (!show_file_header) return; - if (added) - dump_quoted_path("--- ", "", "/dev/null", - line_prefix, c_meta, c_reset); - else - dump_quoted_path("--- ", a_prefix, elem->path, - line_prefix, c_meta, c_reset); + if (rev->combined_all_paths) { + for (i = 0; i < num_parent; i++) { + char *path = filename_changed(elem->parent[i].status) + ? elem->parent[i].path.buf : elem->path; + if (elem->parent[i].status == DIFF_STATUS_ADDED) + dump_quoted_path("--- ", "", "/dev/null", + line_prefix, c_meta, c_reset); + else + dump_quoted_path("--- ", a_prefix, path, + line_prefix, c_meta, c_reset); + } + } else { + if (added) + dump_quoted_path("--- ", "", "/dev/null", + line_prefix, c_meta, c_reset); + else + dump_quoted_path("--- ", a_prefix, elem->path, + line_prefix, c_meta, c_reset); + } if (deleted) dump_quoted_path("+++ ", "", "/dev/null", line_prefix, c_meta, c_reset); @@ -1227,6 +1264,15 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re putchar(inter_name_termination); } + for (i = 0; i < num_parent; i++) + if (rev->combined_all_paths) { + if (filename_changed(p->parent[i].status)) + write_name_quoted(p->parent[i].path.buf, stdout, + inter_name_termination); + else + write_name_quoted(p->path, stdout, + inter_name_termination); + } write_name_quoted(p->path, stdout, line_termination); } @@ -1324,7 +1370,9 @@ static const char *path_path(void *obj) /* find set of paths that every parent touches */ static struct combine_diff_path *find_paths_generic(const struct object_id *oid, - const struct oid_array *parents, struct diff_options *opt) + const struct oid_array *parents, + struct diff_options *opt, + int combined_all_paths) { struct combine_diff_path *paths = NULL; int i, num_parent = parents->nr; @@ -1350,7 +1398,8 @@ static struct combine_diff_path *find_paths_generic(const struct object_id *oid, opt->output_format = DIFF_FORMAT_NO_OUTPUT; diff_tree_oid(&parents->oid[i], oid, "", opt); diffcore_std(opt); - paths = intersect_paths(paths, i, num_parent); + paths = intersect_paths(paths, i, num_parent, + combined_all_paths); /* if showing diff, show it in requested order */ if (opt->output_format != DIFF_FORMAT_NO_OUTPUT && @@ -1460,7 +1509,8 @@ void diff_tree_combined(const struct object_id *oid, * diff(sha1,parent_i) for all i to do the job, specifically * for parent0. */ - paths = find_paths_generic(oid, parents, &diffopts); + paths = find_paths_generic(oid, parents, &diffopts, + rev->combined_all_paths); } else { int stat_opt; @@ -1535,6 +1585,10 @@ void diff_tree_combined(const struct object_id *oid, while (paths) { struct combine_diff_path *tmp = paths; paths = paths->next; + for (i = 0; i < num_parent; i++) + if (rev->combined_all_paths && + filename_changed(tmp->parent[i].status)) + strbuf_release(&tmp->parent[i].path); free(tmp); } diff --git a/diff.h b/diff.h index b512d0477ac3a4..90ea0256a584bb 100644 --- a/diff.h +++ b/diff.h @@ -294,6 +294,7 @@ struct combine_diff_path { char status; unsigned int mode; struct object_id oid; + struct strbuf path; } parent[FLEX_ARRAY]; }; #define combine_diff_path_size(n, l) \ diff --git a/revision.c b/revision.c index 119947ced0ba74..384fa69aed1d1b 100644 --- a/revision.c +++ b/revision.c @@ -1995,6 +1995,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->diff = 1; revs->dense_combined_merges = 0; revs->combine_merges = 1; + } else if (!strcmp(arg, "--combined-all-paths")) { + revs->diff = 1; + revs->combined_all_paths = 1; } else if (!strcmp(arg, "--cc")) { revs->diff = 1; revs->dense_combined_merges = 1; @@ -2491,6 +2494,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s } if (revs->combine_merges) revs->ignore_merges = 0; + if (revs->combined_all_paths && !revs->combine_merges) + die("--combined-all-paths makes no sense without -c or --cc"); + revs->diffopt.abbrev = revs->abbrev; if (revs->line_level_traverse) { diff --git a/revision.h b/revision.h index 52e5a88ff57258..253c12c29a5a1d 100644 --- a/revision.h +++ b/revision.h @@ -171,6 +171,7 @@ struct rev_info { verbose_header:1, ignore_merges:1, combine_merges:1, + combined_all_paths:1, dense_combined_merges:1, always_show_header:1; diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index e2824d34378343..07b49f6d6d9ef9 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -435,4 +435,92 @@ test_expect_success 'combine diff gets tree sorting right' ' test_cmp expect actual ' +test_expect_success 'setup for --combined-all-paths' ' + git branch side1c && + git branch side2c && + git checkout side1c && + test_seq 1 10 >filename-side1c && + git add filename-side1c && + git commit -m with && + git checkout side2c && + test_seq 1 9 >filename-side2c && + echo ten >>filename-side2c && + git add filename-side2c && + git commit -m iam && + git checkout -b mergery side1c && + git merge --no-commit side2c && + git rm filename-side1c && + echo eleven >>filename-side2c && + git mv filename-side2c filename-merged && + git add filename-merged && + git commit +' + +test_expect_success '--combined-all-paths and --raw' ' + cat <<-\EOF >expect && + ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR filename-side1c filename-side2c filename-merged + EOF + git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp && + sed 1d actual && + test_cmp expect actual +' + +test_expect_success '--combined-all-paths and --cc' ' + cat <<-\EOF >expect && + --- a/filename-side1c + --- a/filename-side2c + +++ b/filename-merged + EOF + git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp && + grep ^[-+][-+][-+] actual && + test_cmp expect actual +' + +test_expect_success FUNNYNAMES 'setup for --combined-all-paths with funny names' ' + git branch side1d && + git branch side2d && + git checkout side1d && + test_seq 1 10 >$(printf "file\twith\ttabs") && + git add file* && + git commit -m with && + git checkout side2d && + test_seq 1 9 >$(printf "i\tam\ttabbed") && + echo ten >>$(printf "i\tam\ttabbed") && + git add *tabbed && + git commit -m iam && + git checkout -b funny-names-mergery side1d && + git merge --no-commit side2d && + git rm *tabs && + echo eleven >>$(printf "i\tam\ttabbed") && + git mv "$(printf "i\tam\ttabbed")" "$(printf "fickle\tnaming")" && + git add fickle* && + git commit +' + +test_expect_success FUNNYNAMES '--combined-all-paths and --raw and funny names' ' + cat <<-\EOF >expect && + ::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR "file\twith\ttabs" "i\tam\ttabbed" "fickle\tnaming" + EOF + git diff-tree -c -M --raw --combined-all-paths HEAD >actual.tmp && + sed 1d actual && + test_cmp expect actual +' + +test_expect_success FUNNYNAMES '--combined-all-paths and --raw -and -z and funny names' ' + printf "aaf8087c3cbd4db8e185a2d074cf27c53cfb75d7\0::100644 100644 100644 f00c965d8307308469e537302baa73048488f162 088bd5d92c2a8e0203ca8e7e4c2a5c692f6ae3f7 333b9c62519f285e1854830ade0fe1ef1d40ee1b RR\0file\twith\ttabs\0i\tam\ttabbed\0fickle\tnaming\0" >expect && + git diff-tree -c -M --raw --combined-all-paths -z HEAD >actual && + test_cmp -a expect actual +' + +test_expect_success FUNNYNAMES '--combined-all-paths and --cc and funny names' ' + cat <<-\EOF >expect && + --- "a/file\twith\ttabs" + --- "a/i\tam\ttabbed" + +++ "b/fickle\tnaming" + EOF + git diff-tree --cc -M --combined-all-paths HEAD >actual.tmp && + grep ^[-+][-+][-+] actual && + test_cmp expect actual +' + test_done From c89c494240a516b082f397130ae44df2e757866b Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Fri, 8 Feb 2019 03:21:31 -0800 Subject: [PATCH 0080/1015] submodule--helper: teach config subcommand --unset This teaches submodule--helper config the --unset option, which removes the specified configuration key from the .gitmodule file. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 17 ++++++++++++----- t/t7411-submodule-config.sh | 9 +++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index b80fc4ba3d88b5..33d9f826616743 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -2148,17 +2148,22 @@ static int check_name(int argc, const char **argv, const char *prefix) static int module_config(int argc, const char **argv, const char *prefix) { enum { - CHECK_WRITEABLE = 1 + CHECK_WRITEABLE = 1, + DO_UNSET = 2 } command = 0; struct option module_config_options[] = { OPT_CMDMODE(0, "check-writeable", &command, N_("check if it is safe to write to the .gitmodules file"), CHECK_WRITEABLE), + OPT_CMDMODE(0, "unset", &command, + N_("unset the config in the .gitmodules file"), + DO_UNSET), OPT_END() }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper config name [value]"), + N_("git submodule--helper config []"), + N_("git submodule--helper config --unset "), N_("git submodule--helper config --check-writeable"), NULL }; @@ -2170,15 +2175,17 @@ static int module_config(int argc, const char **argv, const char *prefix) return is_writing_gitmodules_ok() ? 0 : -1; /* Equivalent to ACTION_GET in builtin/config.c */ - if (argc == 2) + if (argc == 2 && command != DO_UNSET) return print_config_from_gitmodules(the_repository, argv[1]); /* Equivalent to ACTION_SET in builtin/config.c */ - if (argc == 3) { + if (argc == 3 || (argc == 2 && command == DO_UNSET)) { + const char *value = (argc == 3) ? argv[2] : NULL; + if (!is_writing_gitmodules_ok()) die(_("please make sure that the .gitmodules file is in the working tree")); - return config_set_in_gitmodules_file_gently(argv[1], argv[2]); + return config_set_in_gitmodules_file_gently(argv[1], value); } usage_with_options(git_submodule_helper_usage, module_config_options); diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index 89690b7adb85a9..fcc0fb82d8adb1 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -142,6 +142,15 @@ test_expect_success 'reading submodules config from the working tree with "submo ) ' +test_expect_success 'unsetting submodules config from the working tree with "submodule--helper config --unset"' ' + (cd super && + git submodule--helper config --unset submodule.submodule.url && + git submodule--helper config submodule.submodule.url >actual && + test_must_be_empty actual + ) +' + + test_expect_success 'writing submodules config with "submodule--helper config"' ' (cd super && echo "new_url" >expect && From 11f470aee7ccd43bc2be159e69e121c35a72f91d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 9 Feb 2019 10:25:26 -0800 Subject: [PATCH 0081/1015] test: caution on our version of 'yes' During a review of a patch, we noticed that we use our own imitation of 'yes' with the limit of 99 lines. It is very tempting to lift this arbitrary limit, but the limit is there for a reason. Add an in-code comment to prevent future developers from wasting their time. Signed-off-by: Junio C Hamano --- t/README | 9 +++++++++ t/test-lib.sh | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/t/README b/t/README index 1326fd7505fde3..f4e1a82657e056 100644 --- a/t/README +++ b/t/README @@ -927,6 +927,15 @@ library for your script to use. test_oid_init or test_oid_cache. Providing an unknown key is an error. + - yes [] + + This is often seen in modern UNIX but some platforms lack it, so + the test harness overrides the platform implementation with a + more limited one. Use this only when feeding a handful lines of + output to the downstream---unlike the real version, it generates + only up to 99 lines. + + Prerequisites ------------- diff --git a/t/test-lib.sh b/t/test-lib.sh index 42b1a0aa7f0b8f..541a37f4c0a92b 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1313,7 +1313,11 @@ then fi fi -# Provide an implementation of the 'yes' utility +# Provide an implementation of the 'yes' utility; the upper bound +# limit is there to help Windows that cannot stop this loop from +# wasting cycles when the downstream stops reading, so do not be +# tempted to turn it into an infinite loop. cf. 6129c930 ("test-lib: +# limit the output of the yes utility", 2016-02-02) yes () { if test $# = 0 then From 9b0bd87ed2ae444fa0ecf55ba497e666b3911fbb Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Mon, 11 Feb 2019 17:23:40 +0000 Subject: [PATCH 0082/1015] prune-packed: check for too many arguments Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- builtin/prune-packed.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 4ff525e50fc701..b22b1a0539de20 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -62,6 +62,11 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, prune_packed_options, prune_packed_usage, 0); + if (argc > 0) + usage_msg_opt(_("too many arguments"), + prune_packed_usage, + prune_packed_options); + prune_packed_objects(opts); return 0; } From 3486d8d4b46684b8562fac9cd121fe3107c1a909 Mon Sep 17 00:00:00 2001 From: Barret Rhoden Date: Tue, 12 Feb 2019 17:27:18 -0500 Subject: [PATCH 0083/1015] Move init_skiplist() outside of fsck init_skiplist() took a file consisting of SHA-1s and comments and added the objects to an oidset. This functionality is useful for other commands. Signed-off-by: Barret Rhoden Signed-off-by: Junio C Hamano --- fsck.c | 37 +-------------------------------- oidset.c | 35 +++++++++++++++++++++++++++++++ oidset.h | 8 +++++++ t/t5504-fetch-receive-strict.sh | 14 ++++++------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/fsck.c b/fsck.c index 68502ce85b11bf..80b53e6f496813 100644 --- a/fsck.c +++ b/fsck.c @@ -181,41 +181,6 @@ static int fsck_msg_type(enum fsck_msg_id msg_id, return msg_type; } -static void init_skiplist(struct fsck_options *options, const char *path) -{ - FILE *fp; - struct strbuf sb = STRBUF_INIT; - struct object_id oid; - - fp = fopen(path, "r"); - if (!fp) - die("Could not open skip list: %s", path); - while (!strbuf_getline(&sb, fp)) { - const char *p; - const char *hash; - - /* - * Allow trailing comments, leading whitespace - * (including before commits), and empty or whitespace - * only lines. - */ - hash = strchr(sb.buf, '#'); - if (hash) - strbuf_setlen(&sb, hash - sb.buf); - strbuf_trim(&sb); - if (!sb.len) - continue; - - if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0') - die("Invalid SHA-1: %s", sb.buf); - oidset_insert(&options->skiplist, &oid); - } - if (ferror(fp)) - die_errno("Could not read '%s'", path); - fclose(fp); - strbuf_release(&sb); -} - static int parse_msg_type(const char *str) { if (!strcmp(str, "error")) @@ -284,7 +249,7 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values) if (!strcmp(buf, "skiplist")) { if (equal == len) die("skiplist requires a path"); - init_skiplist(options, buf + equal + 1); + oidset_parse_file(&options->skiplist, buf + equal + 1); buf += len + 1; continue; } diff --git a/oidset.c b/oidset.c index fe4eb921df81bb..878a1b56af1c8e 100644 --- a/oidset.c +++ b/oidset.c @@ -35,3 +35,38 @@ void oidset_clear(struct oidset *set) kh_release_oid(&set->set); oidset_init(set, 0); } + +void oidset_parse_file(struct oidset *set, const char *path) +{ + FILE *fp; + struct strbuf sb = STRBUF_INIT; + struct object_id oid; + + fp = fopen(path, "r"); + if (!fp) + die("Could not open object name list: %s", path); + while (!strbuf_getline(&sb, fp)) { + const char *p; + const char *name; + + /* + * Allow trailing comments, leading whitespace + * (including before commits), and empty or whitespace + * only lines. + */ + name = strchr(sb.buf, '#'); + if (name) + strbuf_setlen(&sb, name - sb.buf); + strbuf_trim(&sb); + if (!sb.len) + continue; + + if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0') + die("Invalid object name: %s", sb.buf); + oidset_insert(set, &oid); + } + if (ferror(fp)) + die_errno("Could not read '%s'", path); + fclose(fp); + strbuf_release(&sb); +} diff --git a/oidset.h b/oidset.h index c9d0f6d3cc8b99..c4807749df8d16 100644 --- a/oidset.h +++ b/oidset.h @@ -73,6 +73,14 @@ int oidset_remove(struct oidset *set, const struct object_id *oid); */ void oidset_clear(struct oidset *set); +/** + * Add the contents of the file 'path' to an initialized oidset. Each line is + * an unabbreviated object name. Comments begin with '#', and trailing comments + * are allowed. Leading whitespace and empty or white-space only lines are + * ignored. + */ +void oidset_parse_file(struct oidset *set, const char *path); + struct oidset_iter { kh_oid_t *set; khiter_t iter; diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 7bc706873c5b23..7184f1d07f90b9 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -164,9 +164,9 @@ test_expect_success 'fsck with unsorted skipList' ' test_expect_success 'fsck with invalid or bogus skipList input' ' git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck && test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err && - test_i18ngrep "Could not open skip list: does-not-exist" err && + test_i18ngrep "Could not open object name list: does-not-exist" err && test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err && - test_i18ngrep "Invalid SHA-1: \[core\]" err + test_i18ngrep "Invalid object name: \[core\]" err ' test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' ' @@ -193,7 +193,7 @@ test_expect_success 'fsck no garbage output from comments & empty lines errors' test_expect_success 'fsck with invalid abbreviated skipList input' ' echo $commit | test_copy_bytes 20 >SKIP.abbreviated && test_must_fail git -c fsck.skipList=SKIP.abbreviated fsck 2>err-abbreviated && - test_i18ngrep "^fatal: Invalid SHA-1: " err-abbreviated + test_i18ngrep "^fatal: Invalid object name: " err-abbreviated ' test_expect_success 'fsck with exhaustive accepted skipList input (various types of comments etc.)' ' @@ -226,10 +226,10 @@ test_expect_success 'push with receive.fsck.skipList' ' test_must_fail git push --porcelain dst bogus && git --git-dir=dst/.git config receive.fsck.skipList does-not-exist && test_must_fail git push --porcelain dst bogus 2>err && - test_i18ngrep "Could not open skip list: does-not-exist" err && + test_i18ngrep "Could not open object name list: does-not-exist" err && git --git-dir=dst/.git config receive.fsck.skipList config && test_must_fail git push --porcelain dst bogus 2>err && - test_i18ngrep "Invalid SHA-1: \[core\]" err && + test_i18ngrep "Invalid object name: \[core\]" err && git --git-dir=dst/.git config receive.fsck.skipList SKIP && git push --porcelain dst bogus @@ -255,10 +255,10 @@ test_expect_success 'fetch with fetch.fsck.skipList' ' test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && git --git-dir=dst/.git config fetch.fsck.skipList does-not-exist && test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && - test_i18ngrep "Could not open skip list: does-not-exist" err && + test_i18ngrep "Could not open object name list: does-not-exist" err && git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config && test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && - test_i18ngrep "Invalid SHA-1: \[core\]" err && + test_i18ngrep "Invalid object name: \[core\]" err && git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP && git --git-dir=dst/.git fetch "file://$(pwd)" $refspec From 6e37c8ed3c899385651f5beac1f1588fe3c1f5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Wed, 13 Feb 2019 16:51:29 +0700 Subject: [PATCH 0084/1015] read-cache.c: fix writing "link" index ext with null base oid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 7db118303a (unpack_trees: fix breakage when o->src_index != o->dst_index - 2018-04-23) and changes in merge code to use separate index_state for source and destination, when doing a merge with split index activated, we may run into this line in unpack_trees(): o->result.split_index = init_split_index(&o->result); This is by itself not wrong. But this split index information is not fully populated (and it's only so when move_cache_to_base_index() is called, aka force splitting the index, or loading index_state from a file). Both "base_oid" and "base" in this case remain null. So when writing the main index down, we link to this index with null oid (default value after init_split_index()), which also means "no split index" internally. This triggers an incorrect base index refresh: warning: could not freshen shared index '.../sharedindex.0{40}' This patch makes sure we will not refresh null base_oid (because the file is never there). It also makes sure not to write "link" extension with null base_oid in the first place (no point having it at all). Read code already has protection against null base_oid. There is also another side fix in remove_split_index() that causes a crash when doing "git update-index --no-split-index" when base_oid in the index file is null. In this case we will not load istate->split_index->base but we dereference it anyway and are rewarded with a segfault. This should not happen anymore, but it's still wrong to dereference a potential NULL pointer, especially when we do check for NULL pointer in the next code. Reported-by: Luke Diamand Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- read-cache.c | 5 +++-- split-index.c | 34 ++++++++++++++++++---------------- t/t1700-split-index.sh | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/read-cache.c b/read-cache.c index 8f644f68b432e1..d140b44f8f29ad 100644 --- a/read-cache.c +++ b/read-cache.c @@ -2520,7 +2520,8 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, return err; /* Write extension data here */ - if (!strip_extensions && istate->split_index) { + if (!strip_extensions && istate->split_index && + !is_null_oid(&istate->split_index->base_oid)) { struct strbuf sb = STRBUF_INIT; err = write_link_extension(&sb, istate) < 0 || @@ -2794,7 +2795,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, ret = write_split_index(istate, lock, flags); /* Freshen the shared index only if the split-index was written */ - if (!ret && !new_shared_index) { + if (!ret && !new_shared_index && !is_null_oid(&si->base_oid)) { const char *shared_index = git_path("sharedindex.%s", oid_to_hex(&si->base_oid)); freshen_shared_index(shared_index, 1); diff --git a/split-index.c b/split-index.c index 5820412dc52030..a9d13611a432d7 100644 --- a/split-index.c +++ b/split-index.c @@ -440,24 +440,26 @@ void add_split_index(struct index_state *istate) void remove_split_index(struct index_state *istate) { if (istate->split_index) { - /* - * When removing the split index, we need to move - * ownership of the mem_pool associated with the - * base index to the main index. There may be cache entries - * allocated from the base's memory pool that are shared with - * the_index.cache[]. - */ - mem_pool_combine(istate->ce_mem_pool, istate->split_index->base->ce_mem_pool); + if (istate->split_index->base) { + /* + * When removing the split index, we need to move + * ownership of the mem_pool associated with the + * base index to the main index. There may be cache entries + * allocated from the base's memory pool that are shared with + * the_index.cache[]. + */ + mem_pool_combine(istate->ce_mem_pool, + istate->split_index->base->ce_mem_pool); - /* - * The split index no longer owns the mem_pool backing - * its cache array. As we are discarding this index, - * mark the index as having no cache entries, so it - * will not attempt to clean up the cache entries or - * validate them. - */ - if (istate->split_index->base) + /* + * The split index no longer owns the mem_pool backing + * its cache array. As we are discarding this index, + * mark the index as having no cache entries, so it + * will not attempt to clean up the cache entries or + * validate them. + */ istate->split_index->base->cache_nr = 0; + } /* * We can discard the split index because its diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index f053bf83eb9f71..ea5181aff964e8 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -447,4 +447,22 @@ test_expect_success 'writing split index with null sha1 does not write cache tre test_line_count = 0 cache-tree.out ' +test_expect_success 'do not refresh null base index' ' + test_create_repo merge && + ( + cd merge && + test_commit initial && + git checkout -b side-branch && + test_commit extra && + git checkout master && + git update-index --split-index && + test_commit more && + # must not write a new shareindex, or we wont catch the problem + git -c splitIndex.maxPercentChange=100 merge --no-edit side-branch 2>err && + # i.e. do not expect warnings like + # could not freshen shared index .../shareindex.00000... + test_must_be_empty err + ) +' + test_done From 7d50d34fc7d9764766c13eb4f86171f241664b98 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 14 Feb 2019 11:06:35 -0800 Subject: [PATCH 0085/1015] remote-curl: reduce scope of rpc_state.argv The argv field in struct rpc_state is only used in rpc_service(), and not in any functions it directly or indirectly calls. Refactor it to become an argument of rpc_service() instead. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- remote-curl.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index bb7421023ba584..8b7baf6298c9a1 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -505,7 +505,6 @@ static void output_refs(struct ref *refs) struct rpc_state { const char *service_name; - const char **argv; struct strbuf *stdin_preamble; char *service_url; char *hdr_content_type; @@ -829,7 +828,8 @@ static int post_rpc(struct rpc_state *rpc) return err; } -static int rpc_service(struct rpc_state *rpc, struct discovery *heads) +static int rpc_service(struct rpc_state *rpc, struct discovery *heads, + const char **client_argv) { const char *svc = rpc->service_name; struct strbuf buf = STRBUF_INIT; @@ -840,7 +840,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) client.in = -1; client.out = -1; client.git_cmd = 1; - client.argv = rpc->argv; + client.argv = client_argv; if (start_command(&client)) exit(1); if (preamble) @@ -978,11 +978,10 @@ static int fetch_git(struct discovery *heads, memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-upload-pack", - rpc.argv = args.argv; rpc.stdin_preamble = &preamble; rpc.gzip_request = 1; - err = rpc_service(&rpc, heads); + err = rpc_service(&rpc, heads, args.argv); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); @@ -1112,10 +1111,9 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - rpc.argv = args.argv; rpc.stdin_preamble = &preamble; - err = rpc_service(&rpc, heads); + err = rpc_service(&rpc, heads, args.argv); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); From 5d91669309d23383651c352abd00254982e31902 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 14 Feb 2019 11:06:36 -0800 Subject: [PATCH 0086/1015] remote-curl: reduce scope of rpc_state.stdin_preamble The stdin_preamble field in struct rpc_state is only used in rpc_service(), and not in any functions it directly or indirectly calls. Refactor it to become an argument of rpc_service() instead. An observation of all callers of rpc_service() shows that the preamble is always set, so we no longer need the "if (preamble)" check. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- remote-curl.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 8b7baf6298c9a1..d33d5bbfa8409a 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -505,7 +505,6 @@ static void output_refs(struct ref *refs) struct rpc_state { const char *service_name; - struct strbuf *stdin_preamble; char *service_url; char *hdr_content_type; char *hdr_accept; @@ -829,11 +828,10 @@ static int post_rpc(struct rpc_state *rpc) } static int rpc_service(struct rpc_state *rpc, struct discovery *heads, - const char **client_argv) + const char **client_argv, const struct strbuf *preamble) { const char *svc = rpc->service_name; struct strbuf buf = STRBUF_INIT; - struct strbuf *preamble = rpc->stdin_preamble; struct child_process client = CHILD_PROCESS_INIT; int err = 0; @@ -843,8 +841,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads, client.argv = client_argv; if (start_command(&client)) exit(1); - if (preamble) - write_or_die(client.in, preamble->buf, preamble->len); + write_or_die(client.in, preamble->buf, preamble->len); if (heads) write_or_die(client.in, heads->buf, heads->len); @@ -978,10 +975,9 @@ static int fetch_git(struct discovery *heads, memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-upload-pack", - rpc.stdin_preamble = &preamble; rpc.gzip_request = 1; - err = rpc_service(&rpc, heads, args.argv); + err = rpc_service(&rpc, heads, args.argv, &preamble); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); @@ -1111,9 +1107,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - rpc.stdin_preamble = &preamble; - err = rpc_service(&rpc, heads, args.argv); + err = rpc_service(&rpc, heads, args.argv, &preamble); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); From b35903092e35cf249777ff14c1135c0b50f4e1f3 Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 14 Feb 2019 11:06:37 -0800 Subject: [PATCH 0087/1015] remote-curl: reduce scope of rpc_state.result The result field in struct rpc_state is only used in rpc_service(), and not in any functions it directly or indirectly calls. Refactor it to become an argument of rpc_service() instead. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- remote-curl.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index d33d5bbfa8409a..8e0e37ed3d5ef1 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -516,7 +516,6 @@ struct rpc_state { int in; int out; int any_written; - struct strbuf result; unsigned gzip_request : 1; unsigned initial_buffer : 1; }; @@ -828,7 +827,8 @@ static int post_rpc(struct rpc_state *rpc) } static int rpc_service(struct rpc_state *rpc, struct discovery *heads, - const char **client_argv, const struct strbuf *preamble) + const char **client_argv, const struct strbuf *preamble, + struct strbuf *rpc_result) { const char *svc = rpc->service_name; struct strbuf buf = STRBUF_INIT; @@ -849,7 +849,6 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads, rpc->buf = xmalloc(rpc->alloc); rpc->in = client.in; rpc->out = client.out; - strbuf_init(&rpc->result, 0); strbuf_addf(&buf, "%s%s", url.buf, svc); rpc->service_url = strbuf_detach(&buf, NULL); @@ -877,7 +876,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads, close(client.in); client.in = -1; if (!err) { - strbuf_read(&rpc->result, client.out, 0); + strbuf_read(rpc_result, client.out, 0); } else { char buf[4096]; for (;;) @@ -930,6 +929,7 @@ static int fetch_git(struct discovery *heads, struct strbuf preamble = STRBUF_INIT; int i, err; struct argv_array args = ARGV_ARRAY_INIT; + struct strbuf rpc_result = STRBUF_INIT; argv_array_pushl(&args, "fetch-pack", "--stateless-rpc", "--stdin", "--lock-pack", NULL); @@ -977,10 +977,10 @@ static int fetch_git(struct discovery *heads, rpc.service_name = "git-upload-pack", rpc.gzip_request = 1; - err = rpc_service(&rpc, heads, args.argv, &preamble); - if (rpc.result.len) - write_or_die(1, rpc.result.buf, rpc.result.len); - strbuf_release(&rpc.result); + err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result); + if (rpc_result.len) + write_or_die(1, rpc_result.buf, rpc_result.len); + strbuf_release(&rpc_result); strbuf_release(&preamble); argv_array_clear(&args); return err; @@ -1075,6 +1075,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) struct argv_array args; struct string_list_item *cas_option; struct strbuf preamble = STRBUF_INIT; + struct strbuf rpc_result = STRBUF_INIT; argv_array_init(&args); argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status", @@ -1108,10 +1109,10 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", - err = rpc_service(&rpc, heads, args.argv, &preamble); - if (rpc.result.len) - write_or_die(1, rpc.result.buf, rpc.result.len); - strbuf_release(&rpc.result); + err = rpc_service(&rpc, heads, args.argv, &preamble, &rpc_result); + if (rpc_result.len) + write_or_die(1, rpc_result.buf, rpc_result.len); + strbuf_release(&rpc_result); strbuf_release(&preamble); argv_array_clear(&args); return err; From d55a30bb1db565751f5bba5c5f9c344b08771ab2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Feb 2019 23:35:22 -0500 Subject: [PATCH 0088/1015] prune: lazily perform reachability traversal The general strategy of "git prune" is to do a full reachability walk, then for each loose object see if we found it in our walk. But if we don't have any loose objects, we don't need to do the expensive walk in the first place. This patch postpones that walk until the first time we need to see its results. Note that this is really a specific case of a more general optimization, which is that we could traverse only far enough to find the object under consideration (i.e., stop the traversal when we find it, then pick up again when asked about the next object, etc). That could save us in some instances from having to do a full walk. But it's actually a bit tricky to do with our traversal code, and you'd need to do a full walk anyway if you have even a single unreachable object (which you generally do, if any objects are actually left after running git-repack). So in practice this lazy-load of the full walk catches one easy but common case (i.e., you've just repacked via git-gc, and there's nothing unreachable). The perf script is fairly contrived, but it does show off the improvement: Test HEAD^ HEAD ------------------------------------------------------------------------- 5304.4: prune with no objects 3.66(3.60+0.05) 0.00(0.00+0.00) -100.0% and would let us know if we accidentally regress this optimization. Note also that we need to take special care with prune_shallow(), which relies on us having performed the traversal. So this optimization can only kick in for a non-shallow repository. Since this is easy to get wrong and is not covered by existing tests, let's add an extra test to t5304 that covers this case explicitly. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/prune.c | 43 ++++++++++++++++++++++++++++++++----------- t/perf/p5304-prune.sh | 24 ++++++++++++++++++++++++ t/t5304-prune.sh | 12 ++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) create mode 100755 t/perf/p5304-prune.sh diff --git a/builtin/prune.c b/builtin/prune.c index 1ec9ddd751df66..04b65739457516 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -31,16 +31,40 @@ static int prune_tmp_file(const char *fullpath) return 0; } -static int prune_object(const struct object_id *oid, const char *fullpath, - void *data) +static void perform_reachability_traversal(struct rev_info *revs) { - struct stat st; + static int initialized; + struct progress *progress = NULL; + + if (initialized) + return; + + if (show_progress) + progress = start_delayed_progress(_("Checking connectivity"), 0); + mark_reachable_objects(revs, 1, expire, progress); + stop_progress(&progress); + initialized = 1; +} + +static int is_object_reachable(const struct object_id *oid, + struct rev_info *revs) +{ + perform_reachability_traversal(revs); /* * Do we know about this object? * It must have been reachable */ - if (lookup_object(the_repository, oid->hash)) + return !!lookup_object(the_repository, oid->hash); +} + +static int prune_object(const struct object_id *oid, const char *fullpath, + void *data) +{ + struct rev_info *revs = data; + struct stat st; + + if (is_object_reachable(oid, revs)) return 0; if (lstat(fullpath, &st)) { @@ -102,7 +126,6 @@ static void remove_temporary_files(const char *path) int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct progress *progress = NULL; int exclude_promisor_objects = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), @@ -142,17 +165,13 @@ int cmd_prune(int argc, const char **argv, const char *prefix) if (show_progress == -1) show_progress = isatty(2); - if (show_progress) - progress = start_delayed_progress(_("Checking connectivity"), 0); if (exclude_promisor_objects) { fetch_if_missing = 0; revs.exclude_promisor_objects = 1; } - mark_reachable_objects(&revs, 1, expire, progress); - stop_progress(&progress); for_each_loose_file_in_objdir(get_object_directory(), prune_object, - prune_cruft, prune_subdir, NULL); + prune_cruft, prune_subdir, &revs); prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); remove_temporary_files(get_object_directory()); @@ -160,8 +179,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) remove_temporary_files(s); free(s); - if (is_repository_shallow(the_repository)) + if (is_repository_shallow(the_repository)) { + perform_reachability_traversal(&revs); prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0); + } return 0; } diff --git a/t/perf/p5304-prune.sh b/t/perf/p5304-prune.sh new file mode 100755 index 00000000000000..3c852084eb4330 --- /dev/null +++ b/t/perf/p5304-prune.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='performance tests of prune' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'remove reachable loose objects' ' + git repack -ad +' + +test_expect_success 'remove unreachable loose objects' ' + git prune +' + +test_expect_success 'confirm there are no loose objects' ' + git count-objects | grep ^0 +' + +test_perf 'prune with no objects' ' + git prune +' + +test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 270da21ac3e293..2c19a790c1be2e 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -274,6 +274,18 @@ test_expect_success 'prune .git/shallow' ' test_path_is_missing .git/shallow ' +test_expect_success 'prune .git/shallow when there are no loose objects' ' + SHA1=$(echo hi|git commit-tree HEAD^{tree}) && + echo $SHA1 >.git/shallow && + git update-ref refs/heads/shallow-tip $SHA1 && + git repack -ad && + # verify assumption that all loose objects are gone + git count-objects | grep ^0 && + git prune && + echo $SHA1 >expect && + test_cmp expect .git/shallow +' + test_expect_success 'prune: handle alternate object database' ' test_create_repo A && git -C A commit --allow-empty -m "initial commit" && From fde67d68966e2acc4f2790d0a69991ab1f89a042 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Feb 2019 23:37:43 -0500 Subject: [PATCH 0089/1015] prune: use bitmaps for reachability traversal Pruning generally has to traverse the whole commit graph in order to see which objects are reachable. This is the exact problem that reachability bitmaps were meant to solve, so let's use them (if they're available, of course). Here are timings on git.git: Test HEAD^ HEAD ------------------------------------------------------------------------ 5304.6: prune with bitmaps 3.65(3.56+0.09) 1.01(0.92+0.08) -72.3% And on linux.git: Test HEAD^ HEAD -------------------------------------------------------------------------- 5304.6: prune with bitmaps 35.05(34.79+0.23) 3.00(2.78+0.21) -91.4% The tests show a pretty optimal case, as we'll have just repacked and should have pretty good coverage of all refs with our bitmaps. But that's actually pretty realistic: normally prune is run via "gc" right after repacking. A few notes on the implementation: - the change is actually in reachable.c, so it would improve reachability traversals by "reflog expire --stale-fix", as well. Those aren't performed regularly, though (a normal "git gc" doesn't use --stale-fix), so they're not really worth measuring. There's a low chance of regressing that caller, since the use of bitmaps is totally transparent from the caller's perspective. - The bitmap case could actually get away without creating a "struct object", and instead the caller could just look up each object id in the bitmap result. However, this would be a marginal improvement in runtime, and it would make the callers much more complicated. They'd have to handle both the bitmap and non-bitmap cases separately, and in the case of git-prune, we'd also have to tweak prune_shallow(), which relies on our SEEN flags. - Because we do create real object structs, we go through a few contortions to create ones of the right type. This isn't strictly necessary (lookup_unknown_object() would suffice), but it's more memory efficient to use the correct types, since we already know them. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- reachable.c | 42 ++++++++++++++++++++++++++++++++++++++++++ t/perf/p5304-prune.sh | 11 +++++++++++ 2 files changed, 53 insertions(+) diff --git a/reachable.c b/reachable.c index 6e9b810d2a5e03..0d00a91de4c848 100644 --- a/reachable.c +++ b/reachable.c @@ -12,6 +12,7 @@ #include "packfile.h" #include "worktree.h" #include "object-store.h" +#include "pack-bitmap.h" struct connectivity_progress { struct progress *progress; @@ -158,10 +159,44 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs, FOR_EACH_OBJECT_LOCAL_ONLY); } +static void *lookup_object_by_type(struct repository *r, + const struct object_id *oid, + enum object_type type) +{ + switch (type) { + case OBJ_COMMIT: + return lookup_commit(r, oid); + case OBJ_TREE: + return lookup_tree(r, oid); + case OBJ_TAG: + return lookup_tag(r, oid); + case OBJ_BLOB: + return lookup_blob(r, oid); + default: + die("BUG: unknown object type %d", type); + } +} + +static int mark_object_seen(const struct object_id *oid, + enum object_type type, + int exclude, + uint32_t name_hash, + struct packed_git *found_pack, + off_t found_offset) +{ + struct object *obj = lookup_object_by_type(the_repository, oid, type); + if (!obj) + die("unable to create object '%s'", oid_to_hex(oid)); + + obj->flags |= SEEN; + return 0; +} + void mark_reachable_objects(struct rev_info *revs, int mark_reflog, timestamp_t mark_recent, struct progress *progress) { struct connectivity_progress cp; + struct bitmap_index *bitmap_git; /* * Set up revision parsing, and mark us as being interested @@ -188,6 +223,13 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog, cp.progress = progress; cp.count = 0; + bitmap_git = prepare_bitmap_walk(revs); + if (bitmap_git) { + traverse_bitmap_commit_list(bitmap_git, mark_object_seen); + free_bitmap_index(bitmap_git); + return; + } + /* * Set up the revision walk - this will move all commits * from the pending list to the commit walking list. diff --git a/t/perf/p5304-prune.sh b/t/perf/p5304-prune.sh index 3c852084eb4330..83baedb8a49280 100755 --- a/t/perf/p5304-prune.sh +++ b/t/perf/p5304-prune.sh @@ -21,4 +21,15 @@ test_perf 'prune with no objects' ' git prune ' +test_expect_success 'repack with bitmaps' ' + git repack -adb +' + +# We have to create the object in each trial run, since otherwise +# runs after the first see no object and just skip the traversal entirely! +test_perf 'prune with bitmaps' ' + echo "probably not present in repo" | git hash-object -w --stdin && + git prune +' + test_done From c2bf473d0d60f44a22a724871ada7981e51606f9 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Feb 2019 23:38:21 -0500 Subject: [PATCH 0090/1015] prune: check SEEN flag for reachability The git-prune command checks reachability by doing a traversal, and then checking whether a given object exists in the global object hash. This can yield false positives if any other part of the code had to create an object struct for some reason. It's not clear whether this is even possible, but it's more robust to rely on something a little more concrete: the SEEN flag set by our traversal. Note that there is a slight possibility of regression here, as we're relying on mark_reachable_objects() to consistently set the flag. However, it has always done so, and we're already relying on that fact in prune_shallow(), which is called as part of git-prune. So this is making these two parts of the prune operation more consistent. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/prune.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/builtin/prune.c b/builtin/prune.c index 04b65739457516..97613eccb54dcb 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -49,13 +49,12 @@ static void perform_reachability_traversal(struct rev_info *revs) static int is_object_reachable(const struct object_id *oid, struct rev_info *revs) { + struct object *obj; + perform_reachability_traversal(revs); - /* - * Do we know about this object? - * It must have been reachable - */ - return !!lookup_object(the_repository, oid->hash); + obj = lookup_object(the_repository, oid->hash); + return obj && (obj->flags & SEEN); } static int prune_object(const struct object_id *oid, const char *fullpath, From cc80c95f42289f6288cda5c0e62f618f585e142c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 06:07:36 -0500 Subject: [PATCH 0091/1015] t5304: rename "sha1" variables to "oid" Let's make the script less jarring to read in a post-sha1 world by using more hash-agnostic variable names. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/t5304-prune.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 2c19a790c1be2e..1eeb828a15146e 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -118,10 +118,10 @@ test_expect_success 'prune: do not prune detached HEAD with no reflog' ' test_expect_success 'prune: prune former HEAD after checking out branch' ' - head_sha1=$(git rev-parse HEAD) && + head_oid=$(git rev-parse HEAD) && git checkout --quiet master && git prune -v >prune_actual && - grep "$head_sha1" prune_actual + grep "$head_oid" prune_actual ' @@ -265,24 +265,24 @@ EOF ' test_expect_success 'prune .git/shallow' ' - SHA1=$(echo hi|git commit-tree HEAD^{tree}) && - echo $SHA1 >.git/shallow && + oid=$(echo hi|git commit-tree HEAD^{tree}) && + echo $oid >.git/shallow && git prune --dry-run >out && - grep $SHA1 .git/shallow && - grep $SHA1 out && + grep $oid .git/shallow && + grep $oid out && git prune && test_path_is_missing .git/shallow ' test_expect_success 'prune .git/shallow when there are no loose objects' ' - SHA1=$(echo hi|git commit-tree HEAD^{tree}) && - echo $SHA1 >.git/shallow && - git update-ref refs/heads/shallow-tip $SHA1 && + oid=$(echo hi|git commit-tree HEAD^{tree}) && + echo $oid >.git/shallow && + git update-ref refs/heads/shallow-tip $oid && git repack -ad && # verify assumption that all loose objects are gone git count-objects | grep ^0 && git prune && - echo $SHA1 >expect && + echo $oid >expect && test_cmp expect .git/shallow ' @@ -326,8 +326,8 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' ' git reset --hard HEAD^ ) && git prune --expire=now && - SHA1=`git hash-object expected` && - git -C third-worktree show "$SHA1" >actual && + oid=`git hash-object expected` && + git -C third-worktree show "$oid" >actual && test_cmp expected actual ' From 784c0daed5435a7ea0630e64e424837e49e30148 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:48:03 -0500 Subject: [PATCH 0092/1015] diff: drop options parameter from diffcore_fix_diff_index() The sole purpose of this function is to fix the sorting order of the queued diff entries. It doesn't need to know about any diff options, so we can drop the unused parameter. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff-lib.c | 2 +- diff.c | 2 +- diff.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/diff-lib.c b/diff-lib.c index 23c8d351b3aa59..a838c219ec044b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -531,7 +531,7 @@ int run_diff_index(struct rev_info *revs, int cached) exit(128); diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); - diffcore_fix_diff_index(&revs->diffopt); + diffcore_fix_diff_index(); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); trace_performance_leave("diff-index"); diff --git a/diff.c b/diff.c index 5306c48652db59..3e7d11fb704fee 100644 --- a/diff.c +++ b/diff.c @@ -6224,7 +6224,7 @@ static int diffnamecmp(const void *a_, const void *b_) return strcmp(name_a, name_b); } -void diffcore_fix_diff_index(struct diff_options *options) +void diffcore_fix_diff_index(void) { struct diff_queue_struct *q = &diff_queued_diff; QSORT(q->queue, q->nr, diffnamecmp); diff --git a/diff.h b/diff.h index b512d0477ac3a4..8f55aad5ba14c9 100644 --- a/diff.h +++ b/diff.h @@ -367,7 +367,7 @@ int git_config_rename(const char *var, const char *value); #define DIFF_PICKAXE_IGNORE_CASE 32 void diffcore_std(struct diff_options *); -void diffcore_fix_diff_index(struct diff_options *); +void diffcore_fix_diff_index(void); #define COMMON_DIFF_OPTIONS_HELP \ "\ncommon diff options:\n" \ From e04df61256a40e860dfcc5518fd4f0558c8c2f03 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:48:13 -0500 Subject: [PATCH 0093/1015] diff: drop unused color reset parameters Several of the emit_* functions take a "reset" color parameter, but never actually look at it (instead, they call into emit_diff_symbol, which handles the colors itself). Let's drop these unused parameters. Note that emit_line() does still take a color/reset pair, and actually uses it. It cannot be refactored to match these other functions because it's the thing that emit_diff_symbol eventually calls into (i.e., it does not by itself know which colors to use, and must be told by the caller). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/diff.c b/diff.c index 3e7d11fb704fee..de45bbd76ea51b 100644 --- a/diff.c +++ b/diff.c @@ -1613,8 +1613,7 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line return ws_blank_line(line, len, ecbdata->ws_rule); } -static void emit_add_line(const char *reset, - struct emit_callback *ecbdata, +static void emit_add_line(struct emit_callback *ecbdata, const char *line, int len) { unsigned flags = WSEH_NEW | ecbdata->ws_rule; @@ -1624,16 +1623,14 @@ static void emit_add_line(const char *reset, emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags); } -static void emit_del_line(const char *reset, - struct emit_callback *ecbdata, +static void emit_del_line(struct emit_callback *ecbdata, const char *line, int len) { 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, +static void emit_context_line(struct emit_callback *ecbdata, const char *line, int len) { unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule; @@ -1742,7 +1739,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb, int prefix, const char *data, int size) { const char *endp = NULL; - const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); while (0 < size) { int len; @@ -1751,10 +1747,10 @@ static void emit_rewrite_lines(struct emit_callback *ecb, len = endp ? (endp - data + 1) : size; if (prefix != '+') { ecb->lno_in_preimage++; - emit_del_line(reset, ecb, data, len); + emit_del_line(ecb, data, len); } else { ecb->lno_in_postimage++; - emit_add_line(reset, ecb, data, len); + emit_add_line(ecb, data, len); } size -= len; data += len; @@ -2325,7 +2321,6 @@ 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 *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); struct diff_options *o = ecbdata->opt; o->found_changes = 1; @@ -2392,16 +2387,16 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) switch (line[0]) { case '+': ecbdata->lno_in_postimage++; - emit_add_line(reset, ecbdata, line + 1, len - 1); + emit_add_line(ecbdata, line + 1, len - 1); break; case '-': ecbdata->lno_in_preimage++; - emit_del_line(reset, ecbdata, line + 1, len - 1); + emit_del_line(ecbdata, line + 1, len - 1); break; case ' ': ecbdata->lno_in_postimage++; ecbdata->lno_in_preimage++; - emit_context_line(reset, ecbdata, line + 1, len - 1); + emit_context_line(ecbdata, line + 1, len - 1); break; default: /* incomplete line at the end */ From 19b9046eedef2c8b39a1192bfcb0541f59acde09 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:48:27 -0500 Subject: [PATCH 0094/1015] diff: drop unused emit data parameter from sane_truncate_line() We pass the "struct emit_callback" (which contains all of the context for our diff) into sane_truncate_line(), but that function doesn't actually use it. In theory we might eventually develop a diff option that impacts this, but in the meantime let's not mislead anybody reading the code. Since the function is static, it would be easy to pass it again if it should ever become useful. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index de45bbd76ea51b..d24843b5310421 100644 --- a/diff.c +++ b/diff.c @@ -2287,7 +2287,7 @@ const char *diff_line_prefix(struct diff_options *opt) return msgbuf->buf; } -static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) +static unsigned long sane_truncate_line(char *line, unsigned long len) { const char *cp; unsigned long allot; @@ -2351,7 +2351,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { if (ecbdata->diff_words) diff_words_flush(ecbdata); - len = sane_truncate_line(ecbdata, line, len); + len = sane_truncate_line(line, len); find_lno(line, ecbdata); emit_hunk_header(ecbdata, line, len); return; From 4bc1792750ede6b716c0c099c690bbf77ddea6bc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:49:42 -0500 Subject: [PATCH 0095/1015] diff: drop complete_rewrite parameter from run_external_diff() Our builtin_diff() wants to know whether break-detection found a complete rewrite, because it changes how the diff is shown. However, when calling out to an external diff, we don't pass this information along (and doing so would require designing a new interface to the user-provided program). Let's drop the unused parameter to make this fact more clear. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- diff.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/diff.c b/diff.c index d24843b5310421..e3f263769b4272 100644 --- a/diff.c +++ b/diff.c @@ -4178,7 +4178,6 @@ static void run_external_diff(const char *pgm, struct diff_filespec *one, struct diff_filespec *two, const char *xfrm_msg, - int complete_rewrite, struct diff_options *o) { struct argv_array argv = ARGV_ARRAY_INIT; @@ -4336,8 +4335,7 @@ static void run_diff_cmd(const char *pgm, } if (pgm) { - run_external_diff(pgm, name, other, one, two, xfrm_msg, - complete_rewrite, o); + run_external_diff(pgm, name, other, one, two, xfrm_msg, o); return; } if (one && two) From b53c502807d760ec55db7e7520a360ee35ab7646 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:50:02 -0500 Subject: [PATCH 0096/1015] merge-recursive: drop several unused parameters There are a few functions related to directory renames that have unused parameters. After consulting with the author in [1], these seem to be leftover cruft from the development process, and not signs of any bug. Let's drop them. [1] https://public-inbox.org/git/CABPp-BHobf8wbBsXF97scNQCzkxQukziODRXq6JOOWq61cAd9g@mail.gmail.com/ Helped-by: Elijah Newren Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- merge-recursive.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 4851825aebf29d..f270fa66f3b54c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1402,8 +1402,7 @@ static int merge_mode_and_contents(struct merge_options *o, static int handle_rename_via_dir(struct merge_options *o, struct diff_filepair *pair, - const char *rename_branch, - const char *other_branch) + const char *rename_branch) { /* * Handle file adds that need to be renamed due to directory rename @@ -2213,8 +2212,7 @@ static void handle_directory_level_conflicts(struct merge_options *o, remove_hashmap_entries(dir_re_merge, &remove_from_merge); } -static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs, - struct tree *tree) +static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs) { struct hashmap *dir_renames; struct hashmap_iter iter; @@ -2460,8 +2458,7 @@ static void apply_directory_rename_modifications(struct merge_options *o, struct tree *o_tree, struct tree *a_tree, struct tree *b_tree, - struct string_list *entries, - int *clean) + struct string_list *entries) { struct string_list_item *item; int stage = (tree == a_tree ? 2 : 3); @@ -2632,8 +2629,7 @@ static struct string_list *get_renames(struct merge_options *o, apply_directory_rename_modifications(o, pair, new_path, re, tree, o_tree, a_tree, b_tree, - entries, - clean_merge); + entries); } hashmap_iter_init(&collisions, &iter); @@ -2944,8 +2940,8 @@ static int detect_and_process_renames(struct merge_options *o, merge_pairs = get_diffpairs(o, common, merge); if (o->detect_directory_renames) { - dir_re_head = get_directory_renames(head_pairs, head); - dir_re_merge = get_directory_renames(merge_pairs, merge); + dir_re_head = get_directory_renames(head_pairs); + dir_re_merge = get_directory_renames(merge_pairs); handle_directory_level_conflicts(o, dir_re_head, head, @@ -3268,8 +3264,7 @@ static int process_entry(struct merge_options *o, clean_merge = 1; if (handle_rename_via_dir(o, conflict_info->pair1, - conflict_info->branch1, - conflict_info->branch2)) + conflict_info->branch1)) clean_merge = -1; break; case RENAME_ADD: From c409d108b857799ae699654d2fc33b063c9aef9d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:50:32 -0500 Subject: [PATCH 0097/1015] pack-objects: drop unused parameter from oe_map_new_pack() Since 43fa44fa3b (pack-objects: move in_pack out of struct object_entry, 2018-04-14), we store the source pack for each object as a small index rather than as a pointer. When we see a new pack that has no allocated index, we fall back to generating an array of pointers by calling oe_map_new_pack(). Perhaps counter-intuitively, that function does not need to actually see our new index-less pack. It only allocates and populates the array with the existing packs, after which oe_set_in_pack() actually adds the new pack to the array. Let's drop the unused "struct packed_git" argument to oe_map_new_pack() to avoid confusion. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pack-objects.c | 3 +-- pack-objects.h | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pack-objects.c b/pack-objects.c index e7cd337bee53cb..ce33b8906e5c58 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -119,8 +119,7 @@ static void prepare_in_pack_by_idx(struct packing_data *pdata) * this fall back code, just stay simple and fall back to using * in_pack[] array. */ -void oe_map_new_pack(struct packing_data *pack, - struct packed_git *p) +void oe_map_new_pack(struct packing_data *pack) { uint32_t i; diff --git a/pack-objects.h b/pack-objects.h index 6bfacc7d2cedd7..6fde7ce27cbc15 100644 --- a/pack-objects.h +++ b/pack-objects.h @@ -247,14 +247,14 @@ static inline struct packed_git *oe_in_pack(const struct packing_data *pack, return pack->in_pack[e - pack->objects]; } -void oe_map_new_pack(struct packing_data *pack, - struct packed_git *p); +void oe_map_new_pack(struct packing_data *pack); + static inline void oe_set_in_pack(struct packing_data *pack, struct object_entry *e, struct packed_git *p) { if (!p->index) - oe_map_new_pack(pack, p); + oe_map_new_pack(pack); if (pack->in_pack_by_idx) e->in_pack_idx = p->index; else From 10dee40ed3f6f091be271b45191a40aa560f33bf Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:50:46 -0500 Subject: [PATCH 0098/1015] files-backend: drop refs parameter from split_symref_update() This parameter was added in fcc42ea0c9 (split_symref_update(): add a files_ref_store argument, 2016-09-04) without comment, but never used. The splitting is purely mechanical, and doesn't depend on the particular ref-store. Let's drop this parameter in the name of simplicity. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- refs/files-backend.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/refs/files-backend.c b/refs/files-backend.c index dd8abe91850802..ef053f716c300e 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2254,8 +2254,7 @@ static int split_head_update(struct ref_update *update, * Note that the new update will itself be subject to splitting when * the iteration gets to it. */ -static int split_symref_update(struct files_ref_store *refs, - struct ref_update *update, +static int split_symref_update(struct ref_update *update, const char *referent, struct ref_transaction *transaction, struct string_list *affected_refnames, @@ -2449,7 +2448,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, * of processing the split-off update, so we * don't have to do it here. */ - ret = split_symref_update(refs, update, + ret = split_symref_update(update, referent.buf, transaction, affected_refnames, err); if (ret) From e0329199a7b40372dec50a1bc1c497f5fbb2ddbc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:50:54 -0500 Subject: [PATCH 0099/1015] ref-filter: drop unused buf/sz pairs The grab_tag_values() and grab_commit_values() functions take both the "struct object" as well as the buf/sz pair for the actual object bytes. However, neither function uses the latter, as they pull the data directly from the parsed object struct. Let's get rid of these misleading parameters. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ref-filter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index 422a9c9ae3fd2c..c9333b31817348 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -913,7 +913,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_ } /* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +static void grab_tag_values(struct atom_value *val, int deref, struct object *obj) { int i; struct tag *tag = (struct tag *) obj; @@ -935,7 +935,7 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob } /* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +static void grab_commit_values(struct atom_value *val, int deref, struct object *obj) { int i; struct commit *commit = (struct commit *) obj; @@ -1269,12 +1269,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v { switch (obj->type) { case OBJ_TAG: - grab_tag_values(val, deref, obj, buf, sz); + grab_tag_values(val, deref, obj); grab_sub_body_contents(val, deref, obj, buf, sz); grab_person("tagger", val, deref, obj, buf, sz); break; case OBJ_COMMIT: - grab_commit_values(val, deref, obj, buf, sz); + grab_commit_values(val, deref, obj); grab_sub_body_contents(val, deref, obj, buf, sz); grab_person("author", val, deref, obj, buf, sz); grab_person("committer", val, deref, obj, buf, sz); From 25051cac802b01a588f8170e96f97bf17869177a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:50:58 -0500 Subject: [PATCH 0100/1015] ref-filter: drop unused "obj" parameters The grab_person() and grab_sub_body_contents() functions take both an object struct and a buf/sz pair of the object bytes. However, they use only the latter, since "struct object" does not contain the parsed ident (nor the whole commit message, of course). Let's get rid of these misleading "struct object" parameters. It's possible we may want them in the future (e.g., to generate error messages that mention the object id), but since these are static functions, we can easily add them back in later (and if we do want that information, it's likely we'd pass it through a more generalized "parsing context" struct anyway). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ref-filter.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index c9333b31817348..9ba18b220ceb16 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1064,7 +1064,7 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam } /* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +static void grab_person(const char *who, struct atom_value *val, int deref, void *buf, unsigned long sz) { int i; int wholen = strlen(who); @@ -1192,7 +1192,7 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size } /* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf, unsigned long sz) { int i; const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; @@ -1270,14 +1270,14 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v switch (obj->type) { case OBJ_TAG: grab_tag_values(val, deref, obj); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("tagger", val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, buf, sz); + grab_person("tagger", val, deref, buf, sz); break; case OBJ_COMMIT: grab_commit_values(val, deref, obj); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("author", val, deref, obj, buf, sz); - grab_person("committer", val, deref, obj, buf, sz); + grab_sub_body_contents(val, deref, buf, sz); + grab_person("author", val, deref, buf, sz); + grab_person("committer", val, deref, buf, sz); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ From 5c326d12524e4395b0ff184123e71738db6c9f65 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 14 Feb 2019 00:51:03 -0500 Subject: [PATCH 0101/1015] ref-filter: drop unused "sz" parameters Many of our grab_* functions, which parse the object content, take a buf/sz pair of the object bytes. However, the functions which actually parse the buffers (like find_wholine() and find_subpos()) never look at "sz", and instead use functions like strchr() and strchrnul() that assume the result is NUL-terminated. This is OK in practice (and common for Git's parsing code), since we always allocate an extra NUL when loading an object into memory (and likewise, we are OK with stopping parsing if a commit or tag contains an embedded NUL). Let's drop these extra "sz" parameters, as they are misleading about how the functions intend to access the buffer. We can drop from both the functions mentioned above, which in turn lets us drop from their callers, cascading all the way up to the top-level grab_values(). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- ref-filter.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ref-filter.c b/ref-filter.c index 9ba18b220ceb16..09b5fb439f472f 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -968,7 +968,7 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object } } -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) +static const char *find_wholine(const char *who, int wholen, const char *buf) { const char *eol; while (*buf) { @@ -1064,7 +1064,7 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam } /* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, void *buf, unsigned long sz) +static void grab_person(const char *who, struct atom_value *val, int deref, void *buf) { int i; int wholen = strlen(who); @@ -1085,7 +1085,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void !starts_with(name + wholen, "date")) continue; if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); + wholine = find_wholine(who, wholen, buf); if (!wholine) return; /* no point looking for it */ if (name[wholen] == 0) @@ -1105,7 +1105,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void if (strcmp(who, "tagger") && strcmp(who, "committer")) return; /* "author" for commit object is not wanted */ if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); + wholine = find_wholine(who, wholen, buf); if (!wholine) return; for (i = 0; i < used_atom_cnt; i++) { @@ -1123,7 +1123,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void } } -static void find_subpos(const char *buf, unsigned long sz, +static void find_subpos(const char *buf, const char **sub, unsigned long *sublen, const char **body, unsigned long *bodylen, unsigned long *nonsiglen, @@ -1192,7 +1192,7 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size } /* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf, unsigned long sz) +static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) { int i; const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; @@ -1212,7 +1212,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf, !starts_with(name, "contents")) continue; if (!subpos) - find_subpos(buf, sz, + find_subpos(buf, &subpos, &sublen, &bodypos, &bodylen, &nonsiglen, &sigpos, &siglen); @@ -1265,19 +1265,19 @@ static void fill_missing_values(struct atom_value *val) * pointed at by the ref itself; otherwise it is the object the * ref (which is a tag) refers to. */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) +static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf) { switch (obj->type) { case OBJ_TAG: grab_tag_values(val, deref, obj); - grab_sub_body_contents(val, deref, buf, sz); - grab_person("tagger", val, deref, buf, sz); + grab_sub_body_contents(val, deref, buf); + grab_person("tagger", val, deref, buf); break; case OBJ_COMMIT: grab_commit_values(val, deref, obj); - grab_sub_body_contents(val, deref, buf, sz); - grab_person("author", val, deref, buf, sz); - grab_person("committer", val, deref, buf, sz); + grab_sub_body_contents(val, deref, buf); + grab_person("author", val, deref, buf); + grab_person("committer", val, deref, buf); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ @@ -1516,7 +1516,7 @@ static int get_object(struct ref_array_item *ref, int deref, struct object **obj return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"), oid_to_hex(&oi->oid), ref->refname); } - grab_values(ref->value, deref, *obj, oi->content, oi->size); + grab_values(ref->value, deref, *obj, oi->content); } grab_common_values(ref->value, deref, oi); From 68cabbfda36b2e515802e13753637a987c4cf9a9 Mon Sep 17 00:00:00 2001 From: Denton Liu Date: Fri, 15 Feb 2019 01:26:41 -0800 Subject: [PATCH 0102/1015] submodule: document default behavior submodule's default behavior wasn't documented in both git-submodule.txt and in the usage text of git-submodule. Document the default behavior similar to how git-remote does it. Signed-off-by: Denton Liu Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 4 ++++ git-submodule.sh | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index ba3c4df550acfe..2794e2978021c0 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,6 +9,7 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] +'git submodule' [--quiet] [--cached] 'git submodule' [--quiet] add [] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] @@ -28,6 +29,9 @@ For more information about submodules, see linkgit:gitsubmodules[7]. COMMANDS -------- +With no arguments, shows the status of existing submodules. Several +subcommands are available to perform operations on the submodules. + add [-b ] [-f|--force] [--name ] [--reference ] [--depth ] [--] []:: Add the given repository as a submodule at the given path to the changeset to be committed next to the current diff --git a/git-submodule.sh b/git-submodule.sh index b5f2beee60ab59..514ede259660d3 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,8 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] +USAGE="[--quiet] [--cached] + or: $dashless [--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] or: $dashless [--quiet] init [--] [...] or: $dashless [--quiet] deinit [-f|--force] (--all| [--] ...) From 1956ecd0ab26dea9c3ed6b9afe334101d9d12f60 Mon Sep 17 00:00:00 2001 From: Ben Peart Date: Fri, 15 Feb 2019 12:59:21 -0500 Subject: [PATCH 0103/1015] read-cache: add post-index-change hook Add a post-index-change hook that is invoked after the index is written in do_write_locked_index(). This hook is meant primarily for notification, and cannot affect the outcome of git commands that trigger the index write. The hook is passed a flag to indicate whether the working directory was updated or not and a flag indicating if a skip-worktree bit could have changed. These flags enable the hook to optimize its response to the index change notification. Signed-off-by: Ben Peart Signed-off-by: Junio C Hamano --- Documentation/githooks.txt | 18 ++++ builtin/reset.c | 1 + builtin/update-index.c | 2 + cache.h | 4 +- read-cache.c | 14 ++- t/t7113-post-index-change-hook.sh | 144 ++++++++++++++++++++++++++++++ unpack-trees.c | 2 + 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100755 t/t7113-post-index-change-hook.sh diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 959044347e5ee0..bfb0be36599d84 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -492,6 +492,24 @@ This hook is invoked by `git-p4 submit`. It takes no parameters and nothing from standard input. Exiting with non-zero status from this script prevent `git-p4 submit` from launching. Run `git-p4 submit --help` for details. +post-index-change +~~~~~~~~~~~~~~~~~ + +This hook is invoked when the index is written in read-cache.c +do_write_locked_index. + +The first parameter passed to the hook is the indicator for the +working directory being updated. "1" meaning working directory +was updated or "0" when the working directory was not updated. + +The second parameter passed to the hook is the indicator for whether +or not the index was updated and the skip-worktree bit could have +changed. "1" meaning skip-worktree bits could have been updated +and "0" meaning they were not. + +Only one parameter should be set to "1" when the hook runs. The hook +running passing "1", "1" should not be possible. + GIT --- Part of the linkgit:git[1] suite diff --git a/builtin/reset.c b/builtin/reset.c index 4d18a461fab631..e173afcaacd541 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -380,6 +380,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; + the_index.updated_skipworktree = 1; if (!quiet && get_git_work_tree()) { uint64_t t_begin, t_delta_in_ms; diff --git a/builtin/update-index.c b/builtin/update-index.c index 02ace602b9ae23..cf731640fa032e 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -1071,6 +1071,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); + the_index.updated_skipworktree = 1; + /* * Custom copy of parse_options() because we want to handle * filename arguments as they come. diff --git a/cache.h b/cache.h index 27fe635f622c49..46eb862d3e8441 100644 --- a/cache.h +++ b/cache.h @@ -338,7 +338,9 @@ struct index_state { struct cache_time timestamp; unsigned name_hash_initialized : 1, initialized : 1, - drop_cache_tree : 1; + drop_cache_tree : 1, + updated_workdir : 1, + updated_skipworktree : 1; struct hashmap name_hash; struct hashmap dir_hash; struct object_id oid; diff --git a/read-cache.c b/read-cache.c index 0e0c93edc9be5a..862bdf383dba78 100644 --- a/read-cache.c +++ b/read-cache.c @@ -17,6 +17,7 @@ #include "commit.h" #include "blob.h" #include "resolve-undo.h" +#include "run-command.h" #include "strbuf.h" #include "varint.h" #include "split-index.h" @@ -2999,8 +3000,17 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l if (ret) return ret; if (flags & COMMIT_LOCK) - return commit_locked_index(lock); - return close_lock_file_gently(lock); + ret = commit_locked_index(lock); + else + ret = close_lock_file_gently(lock); + + run_hook_le(NULL, "post-index-change", + istate->updated_workdir ? "1" : "0", + istate->updated_skipworktree ? "1" : "0", NULL); + istate->updated_workdir = 0; + istate->updated_skipworktree = 0; + + return ret; } static int write_split_index(struct index_state *istate, diff --git a/t/t7113-post-index-change-hook.sh b/t/t7113-post-index-change-hook.sh new file mode 100755 index 00000000000000..f011ad7eece890 --- /dev/null +++ b/t/t7113-post-index-change-hook.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +test_description='post index change hook' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir -p dir1 && + touch dir1/file1.txt && + echo testing >dir1/file2.txt && + git add . && + git commit -m "initial" +' + +test_expect_success 'test status, add, commit, others trigger hook without flags set' ' + mkdir -p .git/hooks && + write_script .git/hooks/post-index-change <<-\EOF && + if test "$1" -eq 1; then + echo "Invalid combination of flags passed to hook; updated_workdir is set." >testfailure + exit 1 + fi + if test "$2" -eq 1; then + echo "Invalid combination of flags passed to hook; updated_skipworktree is set." >testfailure + exit 1 + fi + if test -f ".git/index.lock"; then + echo ".git/index.lock exists" >testfailure + exit 3 + fi + if ! test -f ".git/index"; then + echo ".git/index does not exist" >testfailure + exit 3 + fi + echo "success" >testsuccess + EOF + mkdir -p dir2 && + touch dir2/file1.txt && + touch dir2/file2.txt && + : force index to be dirty && + test-tool chmtime +60 dir1/file1.txt && + git status && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git add . && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git commit -m "second" && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git checkout -- dir1/file1.txt && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git update-index && + test_path_is_missing testsuccess && + test_path_is_missing testfailure && + git reset --soft && + test_path_is_missing testsuccess && + test_path_is_missing testfailure +' + +test_expect_success 'test checkout and reset trigger the hook' ' + write_script .git/hooks/post-index-change <<-\EOF && + if test "$1" -eq 1 && test "$2" -eq 1; then + echo "Invalid combination of flags passed to hook; updated_workdir and updated_skipworktree are both set." >testfailure + exit 1 + fi + if test "$1" -eq 0 && test "$2" -eq 0; then + echo "Invalid combination of flags passed to hook; neither updated_workdir or updated_skipworktree are set." >testfailure + exit 2 + fi + if test "$1" -eq 1; then + if test -f ".git/index.lock"; then + echo "updated_workdir set but .git/index.lock exists" >testfailure + exit 3 + fi + if ! test -f ".git/index"; then + echo "updated_workdir set but .git/index does not exist" >testfailure + exit 3 + fi + else + echo "update_workdir should be set for checkout" >testfailure + exit 4 + fi + echo "success" >testsuccess + EOF + : force index to be dirty && + test-tool chmtime +60 dir1/file1.txt && + git checkout master && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + test-tool chmtime +60 dir1/file1.txt && + git checkout HEAD && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + test-tool chmtime +60 dir1/file1.txt && + git reset --hard && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git checkout -B test && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure +' + +test_expect_success 'test reset --mixed and update-index triggers the hook' ' + write_script .git/hooks/post-index-change <<-\EOF && + if test "$1" -eq 1 && test "$2" -eq 1; then + echo "Invalid combination of flags passed to hook; updated_workdir and updated_skipworktree are both set." >testfailure + exit 1 + fi + if test "$1" -eq 0 && test "$2" -eq 0; then + echo "Invalid combination of flags passed to hook; neither updated_workdir or updated_skipworktree are set." >testfailure + exit 2 + fi + if test "$2" -eq 1; then + if test -f ".git/index.lock"; then + echo "updated_skipworktree set but .git/index.lock exists" >testfailure + exit 3 + fi + if ! test -f ".git/index"; then + echo "updated_skipworktree set but .git/index does not exist" >testfailure + exit 3 + fi + else + echo "updated_skipworktree should be set for reset --mixed and update-index" >testfailure + exit 4 + fi + echo "success" >testsuccess + EOF + : force index to be dirty && + test-tool chmtime +60 dir1/file1.txt && + git reset --mixed --quiet HEAD~1 && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git hash-object -w --stdin expect && + git update-index --cacheinfo 100644 "$(cat expect)" dir1/file1.txt && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure && + git update-index --skip-worktree dir1/file2.txt && + git update-index --remove dir1/file2.txt && + test_path_is_file testsuccess && rm -f testsuccess && + test_path_is_missing testfailure +' + +test_done diff --git a/unpack-trees.c b/unpack-trees.c index 3563daae1aa6bc..8665a4a7c0baa6 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1637,6 +1637,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options WRITE_TREE_SILENT | WRITE_TREE_REPAIR); } + + o->result.updated_workdir = 1; discard_index(o->dst_index); *o->dst_index = o->result; } else { From 5a59a2301f6ec9bcf1b101edb9ca33beb465842f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 16 Feb 2019 18:24:41 +0700 Subject: [PATCH 0104/1015] completion: add more parameter value completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds value completion for a couple more paramters. To make it easier to maintain these hard coded lists, add a comment at the original list/code to remind people to update git-completion.bash too. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- apply.c | 4 +++ builtin/am.c | 4 +++ builtin/commit.c | 8 +++++ builtin/help.c | 4 +++ builtin/log.c | 8 +++++ builtin/pull.c | 4 +++ builtin/replace.c | 4 +++ contrib/completion/git-completion.bash | 43 ++++++++++++++++++++++++-- date.c | 4 +++ diff.c | 8 +++++ git-send-email.perl | 6 ++++ list-objects-filter-options.c | 4 +++ merge-recursive.c | 4 +++ pretty.c | 4 +++ ref-filter.c | 4 +++ submodule-config.c | 9 +++++- submodule.c | 4 +++ xdiff-interface.c | 4 +++ 18 files changed, 126 insertions(+), 4 deletions(-) diff --git a/apply.c b/apply.c index 892ede5a318f75..f15afa9f6af98a 100644 --- a/apply.c +++ b/apply.c @@ -56,6 +56,10 @@ static int parse_whitespace_option(struct apply_state *state, const char *option state->ws_error_action = correct_ws_error; return 0; } + /* + * Please update $__git_whitespacelist in git-completion.bash + * when you add new options. + */ return error(_("unrecognized whitespace option '%s'"), option); } diff --git a/builtin/am.c b/builtin/am.c index 58a2aef28bbcd7..e8522450cb3c6e 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2119,6 +2119,10 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int *opt_value = PATCH_FORMAT_HG; else if (!strcmp(arg, "mboxrd")) *opt_value = PATCH_FORMAT_MBOXRD; + /* + * Please update $__git_patchformat in git-completion.bash + * when you add new options + */ else return error(_("Invalid value for --patch-format: %s"), arg); return 0; diff --git a/builtin/commit.c b/builtin/commit.c index 2986553d5ffb97..b9e396f5e3c3be 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1038,6 +1038,10 @@ static void handle_untracked_files_arg(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + /* + * Please update $__git_untracked_file_modes in + * git-completion.bash when you add new options + */ else die(_("Invalid untracked files mode '%s'"), untracked_files_arg); } @@ -1179,6 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[], else if (!strcmp(cleanup_arg, "scissors")) cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS : COMMIT_MSG_CLEANUP_SPACE; + /* + * Please update _git_commit() in git-completion.bash when you + * add new options. + */ else die(_("Invalid cleanup mode %s"), cleanup_arg); diff --git a/builtin/help.c b/builtin/help.c index 7739a5c1551426..e5590d7787c50a 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -70,6 +70,10 @@ static enum help_format parse_help_format(const char *format) return HELP_FORMAT_INFO; if (!strcmp(format, "web") || !strcmp(format, "html")) return HELP_FORMAT_WEB; + /* + * Please update _git_config() in git-completion.bash when you + * add new help formats. + */ die(_("unrecognized help format '%s'"), format); } diff --git a/builtin/log.c b/builtin/log.c index 57869267d8d75e..ab859f5904191f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -84,6 +84,10 @@ static int parse_decoration_style(const char *value) return DECORATE_SHORT_REFS; else if (!strcmp(value, "auto")) return auto_decoration_style(); + /* + * Please update _git_log() in git-completion.bash when you + * add new decoration styles. + */ return -1; } @@ -1228,6 +1232,10 @@ static int thread_callback(const struct option *opt, const char *arg, int unset) *thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) *thread = THREAD_DEEP; + /* + * Please update _git_formatpatch() in git-completion.bash + * when you add new options. + */ else return 1; return 0; diff --git a/builtin/pull.c b/builtin/pull.c index 701d1473dc59e0..33db8899554021 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -56,6 +56,10 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value, return REBASE_MERGES; else if (!strcmp(value, "interactive") || !strcmp(value, "i")) return REBASE_INTERACTIVE; + /* + * Please update _git_config() in git-completion.bash when you + * add new rebase modes. + */ if (fatal) die(_("Invalid value for %s: %s"), key, value); diff --git a/builtin/replace.c b/builtin/replace.c index 5b80b7f21141d6..f5701629a8a569 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -82,6 +82,10 @@ static int list_replace_refs(const char *pattern, const char *format) data.format = REPLACE_FORMAT_MEDIUM; else if (!strcmp(format, "long")) data.format = REPLACE_FORMAT_LONG; + /* + * Please update _git_replace() in git-completion.bash when + * you add new format + */ else return error(_("invalid replace format '%s'\n" "valid formats are 'short', 'medium' and 'long'"), diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 499e56f83d0a05..907855184c0ca1 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -853,6 +853,11 @@ __git_compute_merge_strategies () __git_merge_strategies=$(__git_list_merge_strategies) } +__git_merge_strategy_options="ours theirs subtree subtree= patience + histogram diff-algorithm= ignore-space-change ignore-all-space + ignore-space-at-eol renormalize no-renormalize no-renames + find-renames find-renames= rename-threshold=" + __git_complete_revlist_file () { local dequoted_word pfx ls ref cur_="$cur" @@ -996,12 +1001,21 @@ __git_complete_strategy () -s|--strategy) __gitcomp "$__git_merge_strategies" return 0 + ;; + -X) + __gitcomp "$__git_merge_strategy_options" + return 0 + ;; esac case "$cur" in --strategy=*) __gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}" return 0 ;; + --strategy-option=*) + __gitcomp "$__git_merge_strategy_options" "" "${cur##--strategy-option=}" + return 0 + ;; esac return 1 } @@ -1163,6 +1177,7 @@ __git_count_arguments () } __git_whitespacelist="nowarn warn error error-all fix" +__git_patchformat="mbox stgit stgit-series hg mboxrd" __git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch" _git_am () @@ -1177,6 +1192,10 @@ _git_am () __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; + --patch-format=*) + __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}" + return + ;; --*) __gitcomp_builtin am "" \ "$__git_am_inprogress_options" @@ -1200,6 +1219,10 @@ _git_apply () _git_add () { case "$cur" in + --chmod=*) + __gitcomp "+x -x" "" "${cur##--chmod=}" + return + ;; --*) __gitcomp_builtin add return @@ -1260,6 +1283,8 @@ _git_bisect () esac } +__git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD symref" + _git_branch () { local i c=1 only_local_ref="n" has_r="n" @@ -1343,6 +1368,9 @@ _git_cherry_pick () __gitcomp "$__git_cherry_pick_inprogress_options" return fi + + __git_complete_strategy && return + case "$cur" in --*) __gitcomp_builtin cherry-pick "" \ @@ -1506,6 +1534,10 @@ _git_fetch () __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}" return ;; + --filter=*) + __gitcomp "blob:none blob:limit= sparse:oid= sparse:path=" "" "${cur##--filter=}" + return + ;; --*) __gitcomp_builtin fetch return @@ -1702,8 +1734,8 @@ __git_log_shortlog_options=" --all-match --invert-grep " -__git_log_pretty_formats="oneline short medium full fuller email raw format:" -__git_log_date_formats="relative iso8601 rfc2822 short local default raw" +__git_log_pretty_formats="oneline short medium full fuller email raw format: mboxrd" +__git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:" _git_log () { @@ -2221,7 +2253,7 @@ _git_config () return ;; diff.submodule) - __gitcomp "log short" + __gitcomp "$__git_diff_submodule_formats" return ;; help.format) @@ -2388,6 +2420,10 @@ _git_remote () _git_replace () { case "$cur" in + --format=*) + __gitcomp "short medium long" "" "${cur##--format=}" + return + ;; --*) __gitcomp_builtin replace return @@ -2429,6 +2465,7 @@ _git_revert () __gitcomp "$__git_revert_inprogress_options" return fi + __git_complete_strategy && return case "$cur" in --*) __gitcomp_builtin revert "" \ diff --git a/date.c b/date.c index 9c5870e102951e..8126146c5069c9 100644 --- a/date.c +++ b/date.c @@ -921,6 +921,10 @@ static enum date_mode_type parse_date_type(const char *format, const char **end) return DATE_UNIX; if (skip_prefix(format, "format", end)) return DATE_STRFTIME; + /* + * Please update $__git_log_date_formats in + * git-completion.bash when you add new formats. + */ die("unknown date format %s", format); } diff --git a/diff.c b/diff.c index 5306c48652db59..a1f301b66ce391 100644 --- a/diff.c +++ b/diff.c @@ -178,6 +178,10 @@ static int parse_submodule_params(struct diff_options *options, const char *valu options->submodule_format = DIFF_SUBMODULE_SHORT; else if (!strcmp(value, "diff")) options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF; + /* + * Please update $__git_diff_submodule_formats in + * git-completion.bash when you add new formats. + */ else return -1; return 0; @@ -204,6 +208,10 @@ long parse_algorithm_value(const char *value) return XDF_PATIENCE_DIFF; else if (!strcasecmp(value, "histogram")) return XDF_HISTOGRAM_DIFF; + /* + * Please update $__git_diff_algorithms in git-completion.bash + * when you add new algorithms. + */ return -1; } diff --git a/git-send-email.perl b/git-send-email.perl index 8eb63b5a2f8d07..8200d58cdc62b6 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -465,6 +465,8 @@ sub read_config { my(%suppress_cc); if (@suppress_cc) { foreach my $entry (@suppress_cc) { + # Please update $__git_send_email_suppresscc_options + # in git-completion.bash when you add new options. die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry) unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc|misc-by)$/; $suppress_cc{$entry} = 1; @@ -494,6 +496,8 @@ sub read_config { if ($confirm_unconfigured) { $confirm = scalar %suppress_cc ? 'compose' : 'auto'; }; +# Please update $__git_send_email_confirm_options in +# git-completion.bash when you add new options. die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm) unless $confirm =~ /^(?:auto|cc|compose|always|never)/; @@ -587,6 +591,8 @@ sub parse_sendmail_aliases { if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { $aliases{$1} = [ $2 ]; }}} + # Please update _git_config() in git-completion.bash when you + # add new MUAs. ); if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index b71bd1fb65bc6b..c0036f73789d1a 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -82,6 +82,10 @@ static int gently_parse_list_objects_filter( filter_options->sparse_path_value = strdup(v0); return 0; } + /* + * Please update _git_fetch() in git-completion.bash when you + * add new filters + */ if (errbuf) strbuf_addf(errbuf, "invalid filter-spec '%s'", arg); diff --git a/merge-recursive.c b/merge-recursive.c index 4851825aebf29d..28b36c08f2a8df 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3764,6 +3764,10 @@ int parse_merge_opt(struct merge_options *o, const char *s) return -1; o->merge_detect_rename = 1; } + /* + * Please update $__git_merge_strategy_options in + * git-completion.bash when you add new options + */ else return -1; return 0; diff --git a/pretty.c b/pretty.c index 0ab45d10d70237..55739a03374322 100644 --- a/pretty.c +++ b/pretty.c @@ -98,6 +98,10 @@ static void setup_commit_formats(void) { "fuller", CMIT_FMT_FULLER, 0, 8 }, { "full", CMIT_FMT_FULL, 0, 8 }, { "oneline", CMIT_FMT_ONELINE, 1, 0 } + /* + * Please update $__git_log_pretty_formats in + * git-completion.bash when you add new formats. + */ }; commit_formats_len = ARRAY_SIZE(builtin_formats); builtin_formats_len = commit_formats_len; diff --git a/ref-filter.c b/ref-filter.c index 422a9c9ae3fd2c..cf80d4d69b75d7 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -485,6 +485,10 @@ static struct { { "if", SOURCE_NONE, FIELD_STR, if_atom_parser }, { "then", SOURCE_NONE }, { "else", SOURCE_NONE }, + /* + * Please update $__git_ref_fieldlist in git-completion.bash + * when you add new atoms + */ }; #define REF_FORMATTING_STATE_INIT { 0, NULL } diff --git a/submodule-config.c b/submodule-config.c index 52702c62d9e3a2..66653e86b9f87d 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -281,7 +281,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg, default: if (!strcmp(arg, "on-demand")) return RECURSE_SUBMODULES_ON_DEMAND; - + /* + * Please update $__git_fetch_recurse_submodules in + * git-completion.bash when you add new options. + */ if (die_on_error) die("bad %s argument: %s", opt, arg); else @@ -362,6 +365,10 @@ static int parse_push_recurse(const char *opt, const char *arg, return RECURSE_SUBMODULES_CHECK; else if (!strcmp(arg, "only")) return RECURSE_SUBMODULES_ONLY; + /* + * Please update $__git_push_recurse_submodules in + * git-completion.bash when you add new modes. + */ else if (die_on_error) die("bad %s argument: %s", opt, arg); else diff --git a/submodule.c b/submodule.c index 934ecfa2943bcb..b251a81f8efa3f 100644 --- a/submodule.c +++ b/submodule.c @@ -432,6 +432,10 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, diffopt->flags.ignore_dirty_submodules = 1; else if (strcmp(arg, "none")) die("bad --ignore-submodules argument: %s", arg); + /* + * Please update _git_status() in git-completion.bash when you + * add new options + */ } static int prepare_submodule_summary(struct rev_info *rev, const char *path, diff --git a/xdiff-interface.c b/xdiff-interface.c index 80f060d2782e22..8509f9ea223a12 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -306,6 +306,10 @@ int git_xmerge_config(const char *var, const char *value, void *cb) git_xmerge_style = XDL_MERGE_DIFF3; else if (!strcmp(value, "merge")) git_xmerge_style = 0; + /* + * Please update _git_checkout() in + * git-completion.bash when you add new merge config + */ else die("unknown style '%s' given for '%s'", value, var); From c659f30398fecdc3dbea1faeaf41e94c30d5da6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 16 Feb 2019 18:36:35 +0700 Subject: [PATCH 0105/1015] diff-parseopt: convert --patch-with-raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 4bc9df73623606..12e333c67f5105 100644 --- a/diff.c +++ b/diff.c @@ -4901,6 +4901,10 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "raw", &options->output_format, N_("generate the diff in raw format"), DIFF_FORMAT_RAW, PARSE_OPT_NONEG), + OPT_BITOP(0, "patch-with-raw", &options->output_format, + N_("synonym for '-p --raw'"), + DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW, + DIFF_FORMAT_NO_OUTPUT), OPT_END() }; @@ -4929,10 +4933,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--patch-with-raw")) { - enable_patch_output(&options->output_format); - options->output_format |= DIFF_FORMAT_RAW; - } else if (!strcmp(arg, "--numstat")) + if (!strcmp(arg, "--numstat")) options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; From e56736203eb4076979358130c299e10eecc93d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 16 Feb 2019 18:36:36 +0700 Subject: [PATCH 0106/1015] diff-parseopt: convert --numstat and --shortstat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/diff.c b/diff.c index 12e333c67f5105..419b6ac4aea84e 100644 --- a/diff.c +++ b/diff.c @@ -4905,6 +4905,12 @@ static void prep_parse_options(struct diff_options *options) N_("synonym for '-p --raw'"), DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW, DIFF_FORMAT_NO_OUTPUT), + OPT_BIT_F(0, "numstat", &options->output_format, + N_("machine friendly --stat"), + DIFF_FORMAT_NUMSTAT, PARSE_OPT_NONEG), + OPT_BIT_F(0, "shortstat", &options->output_format, + N_("output only the last line of --stat"), + DIFF_FORMAT_SHORTSTAT, PARSE_OPT_NONEG), OPT_END() }; @@ -4933,11 +4939,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--numstat")) - options->output_format |= DIFF_FORMAT_NUMSTAT; - else if (!strcmp(arg, "--shortstat")) - options->output_format |= DIFF_FORMAT_SHORTSTAT; - else if (skip_prefix(arg, "-X", &arg) || + if (skip_prefix(arg, "-X", &arg) || skip_to_optional_arg(arg, "--dirstat", &arg)) return parse_dirstat_opt(options, arg); else if (!strcmp(arg, "--cumulative")) From 9903623761e6882a1c885ca4245dd6951052ecc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 20 Feb 2019 01:00:33 +0100 Subject: [PATCH 0107/1015] receive-pack: fix use-after-free bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resolve_ref_unsafe() function can, and sometimes will in the case of this codepath, return the char * passed to it to the caller. In this case we construct a strbuf, free it, and then continue using the dst_name after that free(). The code being fixed dates back to da3efdb17b ("receive-pack: detect aliased updates which can occur with symrefs", 2010-04-19). When it was originally added it didn't have this bug, it was introduced when it was subsequently modified to use strbuf in 6b01ecfe22 ("ref namespaces: Support remote repositories via upload-pack and receive-pack", 2011-07-08). This is theoretically a security issue, the C standard makes no guarantees that a value you use after free() hasn't been poked at or changed by something else on the system, but in practice modern OSs will have mapped the relevant page to this process, so nothing else would have used it. We do no further allocations between the free() and use-after-free, so we ourselves didn't corrupt or change the value. Jeff investigated that and found: "It probably would be an issue if the allocation were larger. glibc at least will use mmap()/munmap() after some cutoff[1], in which case we'd get a segfault from hitting the unmapped page. But for small allocations, it just bumps brk() and the memory is still available for further allocations after free(). [...] If you had a sufficiently large refname you might be able to trigger the bug [...]. I tried to push such a ref. I had to manually make a packed-refs file with the long name to avoid filesystem limits (though probably you could have a long a/b/c/ name on ext4). But the result can't actually be pushed, because it all has to fit into a 64k pkt-line as part of the push protocol.". An a alternative and more succinct way of implementing this would have been to do the strbuf_release() at the end of check_aliased_update() and use "goto out" instead of the early "return" statements. Hopefully this approach of using a helper instead makes it easier to follow. 1. Jeff: "Weirdly, the mmap() cutoff on my glibc system is 135168 bytes. Which is...2^17 + 2^12? 33 pages? I'm sure there's a good reason for that, but I didn't dig into it." Reported-by: 王健强 Helped-by: Jeff King Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/receive-pack.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 68d36e0a56c3c7..3b133ce41399c8 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1204,17 +1204,12 @@ static void run_update_post_hook(struct command *commands) } } -static void check_aliased_update(struct command *cmd, struct string_list *list) +static void check_aliased_update_internal(struct command *cmd, + struct string_list *list, + const char *dst_name, int flag) { - struct strbuf buf = STRBUF_INIT; - const char *dst_name; struct string_list_item *item; struct command *dst_cmd; - int flag; - - strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); - strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) return; @@ -1253,6 +1248,18 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) "inconsistent aliased update"; } +static void check_aliased_update(struct command *cmd, struct string_list *list) +{ + struct strbuf buf = STRBUF_INIT; + const char *dst_name; + int flag; + + strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); + dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); + check_aliased_update_internal(cmd, list, dst_name, flag); + strbuf_release(&buf); +} + static void check_aliased_updates(struct command *commands) { struct command *cmd; From 3e14dd2c8e9bc3b4bd2fb8197db6fe47133815cb Mon Sep 17 00:00:00 2001 From: "Robert P. J. Day" Date: Wed, 20 Feb 2019 02:53:54 -0500 Subject: [PATCH 0108/1015] mention use of "hooks.allownonascii" in "man githooks" The default pre-commit script checks the config variable "hooks.allownonascii" to determine whether to allow non-ASCII file names -- mention this in "man githooks", just as the section on "update" mentions the use of "hooks.allowunannotated". Signed-off-by: Robert P. J. Day Signed-off-by: Junio C Hamano --- Documentation/githooks.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 959044347e5ee0..5bf653c111d07e 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -99,6 +99,10 @@ All the `git commit` hooks are invoked with the environment variable `GIT_EDITOR=:` if the command will not bring up an editor to modify the commit message. +The default 'pre-commit' hook, when enabled--and with the +`hooks.allownonascii` config option unset or set to false--prevents +the use of non-ASCII filenames. + prepare-commit-msg ~~~~~~~~~~~~~~~~~~ From 1ede45e44b3b3bef8598ca077a60a6483ede99ae Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Thu, 21 Feb 2019 09:50:29 -0800 Subject: [PATCH 0109/1015] merge-options.txt: correct wording of --no-commit option The former wording implied that --no-commit would always cause the merge operation to "pause" and allow the user to make further changes and/or provide a special commit message for the merge commit. This is not the case for fast-forward merges, as there is no merge commit to create. Without a merge commit, there is no place where it makes sense to "stop the merge and allow the user to tweak changes"; doing that would require a full rebase of some sort. Since users may be unaware of whether their branches have diverged or not, modify the wording to correctly address fast-forward cases as well and suggest using --no-ff with --no-commit if the point is to ensure that the merge stops before completing. Reported-by: Ulrich Windl Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- Documentation/merge-options.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 63a3fc09548abe..92a7d936c1a8fb 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -3,9 +3,14 @@ Perform the merge and commit the result. This option can be used to override --no-commit. + -With --no-commit perform the merge but pretend the merge -failed and do not autocommit, to give the user a chance to -inspect and further tweak the merge result before committing. +With --no-commit perform the merge and stop just before creating +a merge commit, to give the user a chance to inspect and further +tweak the merge result before committing. ++ +Note that fast-forward updates do not create a merge commit and +therefore there is no way to stop those merges with --no-commit. +Thus, if you want to ensure your branch is not changed or updated +by the merge command, use --no-ff with --no-commit. --edit:: -e:: From 90503a240bc126adc8cddb7b860e19c30b0dfa6f Mon Sep 17 00:00:00 2001 From: Josh Steadmon Date: Tue, 19 Feb 2019 16:32:26 -0800 Subject: [PATCH 0110/1015] protocol-capabilities.txt: document symref In 7171d8c15f ("upload-pack: send symbolic ref information as capability"), we added a symref capability to the pack protocol, but it was never documented. Adapt the patch notes from that commit and add them to the capabilities documentation. While we're at it, add a disclaimer to the top of protocol-capabilities.txt noting that the doc only applies to v0/v1 of the wire protocol. Signed-off-by: Josh Steadmon Signed-off-by: Junio C Hamano --- .../technical/protocol-capabilities.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index 332d209b58ca42..2b267c0da6b2d8 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -1,6 +1,10 @@ Git Protocol Capabilities ========================= +NOTE: this document describes capabilities for versions 0 and 1 of the pack +protocol. For version 2, please refer to the link:protocol-v2.html[protocol-v2] +doc. + Servers SHOULD support all capabilities defined in this document. On the very first line of the initial server response of either @@ -172,6 +176,20 @@ agent strings are purely informative for statistics and debugging purposes, and MUST NOT be used to programmatically assume the presence or absence of particular features. +symref +------ + +This parameterized capability is used to inform the receiver which symbolic ref +points to which ref; for example, "symref=HEAD:refs/heads/master" tells the +receiver that HEAD points to master. This capability can be repeated to +represent multiple symrefs. + +Servers SHOULD include this capability for the HEAD symref if it is one of the +refs being sent. + +Clients MAY use the parameters from this capability to select the proper initial +branch when cloning a repository. + shallow ------- From 4ce7aab5a56394914bd05b9e5fa4ca66404ec5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:03 +0700 Subject: [PATCH 0111/1015] diff-parseopt: convert --dirstat and friends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 7 ++++++ diff.c | 39 +++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 0711734b125f92..7b81b852cab8aa 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -148,6 +148,7 @@ These parameters can also be set individually with `--stat-width=`, number of modified files, as well as number of added and deleted lines. +-X[]:: --dirstat[=]:: Output the distribution of relative amount of changes for each sub-directory. The behavior of `--dirstat` can be customized by @@ -192,6 +193,12 @@ directories with less than 10% of the total amount of changed files, and accumulating child directory counts in the parent directories: `--dirstat=files,10,cumulative`. +--cumulative:: + Synonym for --dirstat=cumulative + +--dirstat-by-file[=...]:: + Synonym for --dirstat=files,param1,param2... + --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. diff --git a/diff.c b/diff.c index 419b6ac4aea84e..1cdbe8e6884eb2 100644 --- a/diff.c +++ b/diff.c @@ -4867,6 +4867,22 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static int diff_opt_dirstat(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!strcmp(opt->long_name, "cumulative")) { + if (arg) + BUG("how come --cumulative take a value?"); + arg = "cumulative"; + } else if (!strcmp(opt->long_name, "dirstat-by-file")) + parse_dirstat_opt(options, "files"); + parse_dirstat_opt(options, arg ? arg : ""); + return 0; +} + static int diff_opt_unified(const struct option *opt, const char *arg, int unset) { @@ -4911,6 +4927,18 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "shortstat", &options->output_format, N_("output only the last line of --stat"), DIFF_FORMAT_SHORTSTAT, PARSE_OPT_NONEG), + OPT_CALLBACK_F('X', "dirstat", options, N_("..."), + N_("output the distribution of relative amount of changes for each sub-directory"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_dirstat), + OPT_CALLBACK_F(0, "cumulative", options, NULL, + N_("synonym for --dirstat=cumulative"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + diff_opt_dirstat), + OPT_CALLBACK_F(0, "dirstat-by-file", options, N_("..."), + N_("synonym for --dirstat=files,param1,param2..."), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_dirstat), OPT_END() }; @@ -4939,16 +4967,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (skip_prefix(arg, "-X", &arg) || - skip_to_optional_arg(arg, "--dirstat", &arg)) - return parse_dirstat_opt(options, arg); - else if (!strcmp(arg, "--cumulative")) - return parse_dirstat_opt(options, "cumulative"); - else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) { - parse_dirstat_opt(options, "files"); - return parse_dirstat_opt(options, arg); - } - else if (!strcmp(arg, "--check")) + if (!strcmp(arg, "--check")) options->output_format |= DIFF_FORMAT_CHECKDIFF; else if (!strcmp(arg, "--summary")) options->output_format |= DIFF_FORMAT_SUMMARY; From fc6af3e92afb24fd657c1381a8aab475b033b15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:04 +0700 Subject: [PATCH 0112/1015] diff-parseopt: convert --check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 1cdbe8e6884eb2..5e16082091c204 100644 --- a/diff.c +++ b/diff.c @@ -4939,6 +4939,9 @@ static void prep_parse_options(struct diff_options *options) N_("synonym for --dirstat=files,param1,param2..."), PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_dirstat), + OPT_BIT_F(0, "check", &options->output_format, + N_("warn if changes introduce conflict markers or whitespace errors"), + DIFF_FORMAT_CHECKDIFF, PARSE_OPT_NONEG), OPT_END() }; @@ -4967,9 +4970,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--check")) - options->output_format |= DIFF_FORMAT_CHECKDIFF; - else if (!strcmp(arg, "--summary")) + if (!strcmp(arg, "--summary")) options->output_format |= DIFF_FORMAT_SUMMARY; else if (!strcmp(arg, "--patch-with-stat")) { enable_patch_output(&options->output_format); From 70a304179a518c8890cc937486bc3f806c557c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:05 +0700 Subject: [PATCH 0113/1015] diff-parseopt: convert --summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 5e16082091c204..0276f25200f4d2 100644 --- a/diff.c +++ b/diff.c @@ -4942,6 +4942,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "check", &options->output_format, N_("warn if changes introduce conflict markers or whitespace errors"), DIFF_FORMAT_CHECKDIFF, PARSE_OPT_NONEG), + OPT_BIT_F(0, "summary", &options->output_format, + N_("condensed summary such as creations, renames and mode changes"), + DIFF_FORMAT_SUMMARY, PARSE_OPT_NONEG), OPT_END() }; @@ -4970,9 +4973,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--summary")) - options->output_format |= DIFF_FORMAT_SUMMARY; - else if (!strcmp(arg, "--patch-with-stat")) { + if (!strcmp(arg, "--patch-with-stat")) { enable_patch_output(&options->output_format); options->output_format |= DIFF_FORMAT_DIFFSTAT; } else if (!strcmp(arg, "--name-only")) From e550f585518221bc6e946be2c3ce634dd71df3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:06 +0700 Subject: [PATCH 0114/1015] diff-parseopt: convert --patch-with-stat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 0276f25200f4d2..b9811aefef1644 100644 --- a/diff.c +++ b/diff.c @@ -4921,6 +4921,10 @@ static void prep_parse_options(struct diff_options *options) N_("synonym for '-p --raw'"), DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW, DIFF_FORMAT_NO_OUTPUT), + OPT_BITOP(0, "patch-with-stat", &options->output_format, + N_("synonym for '-p --stat'"), + DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT, + DIFF_FORMAT_NO_OUTPUT), OPT_BIT_F(0, "numstat", &options->output_format, N_("machine friendly --stat"), DIFF_FORMAT_NUMSTAT, PARSE_OPT_NONEG), @@ -4973,10 +4977,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--patch-with-stat")) { - enable_patch_output(&options->output_format); - options->output_format |= DIFF_FORMAT_DIFFSTAT; - } else if (!strcmp(arg, "--name-only")) + if (!strcmp(arg, "--name-only")) options->output_format |= DIFF_FORMAT_NAME; else if (!strcmp(arg, "--name-status")) options->output_format |= DIFF_FORMAT_NAME_STATUS; From 0e840e2af4f2c5dc7da4c8778491be8296ef0fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:07 +0700 Subject: [PATCH 0115/1015] diff-parseopt: convert --name-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index b9811aefef1644..7ea308814f7afa 100644 --- a/diff.c +++ b/diff.c @@ -4949,6 +4949,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "summary", &options->output_format, N_("condensed summary such as creations, renames and mode changes"), DIFF_FORMAT_SUMMARY, PARSE_OPT_NONEG), + OPT_BIT_F(0, "name-only", &options->output_format, + N_("show only names of changed files"), + DIFF_FORMAT_NAME, PARSE_OPT_NONEG), OPT_END() }; @@ -4977,9 +4980,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--name-only")) - options->output_format |= DIFF_FORMAT_NAME; - else if (!strcmp(arg, "--name-status")) + if (!strcmp(arg, "--name-status")) options->output_format |= DIFF_FORMAT_NAME_STATUS; else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) options->output_format |= DIFF_FORMAT_NO_OUTPUT; From a23874726b077c8ebb19a32855665bce04875b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:08 +0700 Subject: [PATCH 0116/1015] diff-parseopt: convert --name-status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 7ea308814f7afa..99047fb5fec8d0 100644 --- a/diff.c +++ b/diff.c @@ -4952,6 +4952,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "name-only", &options->output_format, N_("show only names of changed files"), DIFF_FORMAT_NAME, PARSE_OPT_NONEG), + OPT_BIT_F(0, "name-status", &options->output_format, + N_("show only names and status of changed files"), + DIFF_FORMAT_NAME_STATUS, PARSE_OPT_NONEG), OPT_END() }; @@ -4980,9 +4983,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--name-status")) - options->output_format |= DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) + if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) options->output_format |= DIFF_FORMAT_NO_OUTPUT; else if (starts_with(arg, "--stat")) /* --stat, --stat-width, --stat-name-width, or --stat-count */ From e01df7a33d368e2bd2ce7b72b007bc18c51875d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:09 +0700 Subject: [PATCH 0117/1015] diff-parseopt: convert -s|--no-patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/diff.c b/diff.c index 99047fb5fec8d0..9c8f5336bc3ee0 100644 --- a/diff.c +++ b/diff.c @@ -4906,6 +4906,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BITOP('p', "patch", &options->output_format, N_("generate patch"), DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), + OPT_BIT_F('s', "no-patch", &options->output_format, + N_("suppress diff output"), + DIFF_FORMAT_NO_OUTPUT, PARSE_OPT_NONEG), OPT_BITOP('u', NULL, &options->output_format, N_("generate patch"), DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), @@ -4983,9 +4986,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch")) - options->output_format |= DIFF_FORMAT_NO_OUTPUT; - else if (starts_with(arg, "--stat")) + if (starts_with(arg, "--stat")) /* --stat, --stat-width, --stat-name-width, or --stat-count */ return stat_opt(options, av); else if (!strcmp(arg, "--compact-summary")) { From 84b5089e41c74ac39c96f49f584541e5e9a94ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:10 +0700 Subject: [PATCH 0118/1015] diff-parseopt: convert --stat* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 118 +++++++++++++++++++++++++-------------------------------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/diff.c b/diff.c index 9c8f5336bc3ee0..1feb13deb3adef 100644 --- a/diff.c +++ b/diff.c @@ -104,11 +104,6 @@ static const char *color_diff_slots[] = { [DIFF_FILE_NEW_BOLD] = "newBold", }; -static NORETURN void die_want_option(const char *option_name) -{ - die(_("option '%s' requires a value"), option_name); -} - define_list_config_array_extra(color_diff_slots, {"plain"}); static int parse_diff_color_slot(const char *var) @@ -4661,77 +4656,56 @@ int parse_long_opt(const char *opt, const char **argv, return 2; } -static int stat_opt(struct diff_options *options, const char **av) +static int diff_opt_stat(const struct option *opt, const char *value, int unset) { - const char *arg = av[0]; - char *end; + struct diff_options *options = opt->value; int width = options->stat_width; int name_width = options->stat_name_width; int graph_width = options->stat_graph_width; int count = options->stat_count; - int argcount = 1; + char *end; - if (!skip_prefix(arg, "--stat", &arg)) - BUG("stat option does not begin with --stat: %s", arg); - end = (char *)arg; + BUG_ON_OPT_NEG(unset); - switch (*arg) { - case '-': - if (skip_prefix(arg, "-width", &arg)) { - if (*arg == '=') - width = strtoul(arg + 1, &end, 10); - else if (!*arg && !av[1]) - die_want_option("--stat-width"); - else if (!*arg) { - width = strtoul(av[1], &end, 10); - argcount = 2; - } - } else if (skip_prefix(arg, "-name-width", &arg)) { - if (*arg == '=') - name_width = strtoul(arg + 1, &end, 10); - else if (!*arg && !av[1]) - die_want_option("--stat-name-width"); - else if (!*arg) { - name_width = strtoul(av[1], &end, 10); - argcount = 2; - } - } else if (skip_prefix(arg, "-graph-width", &arg)) { - if (*arg == '=') - graph_width = strtoul(arg + 1, &end, 10); - else if (!*arg && !av[1]) - die_want_option("--stat-graph-width"); - else if (!*arg) { - graph_width = strtoul(av[1], &end, 10); - argcount = 2; - } - } else if (skip_prefix(arg, "-count", &arg)) { - if (*arg == '=') - count = strtoul(arg + 1, &end, 10); - else if (!*arg && !av[1]) - die_want_option("--stat-count"); - else if (!*arg) { - count = strtoul(av[1], &end, 10); - argcount = 2; - } + if (!strcmp(opt->long_name, "stat")) { + if (value) { + width = strtoul(value, &end, 10); + if (*end == ',') + name_width = strtoul(end+1, &end, 10); + if (*end == ',') + count = strtoul(end+1, &end, 10); + if (*end) + return error(_("invalid --stat value: %s"), value); } - break; - case '=': - width = strtoul(arg+1, &end, 10); - if (*end == ',') - name_width = strtoul(end+1, &end, 10); - if (*end == ',') - count = strtoul(end+1, &end, 10); - } + } else if (!strcmp(opt->long_name, "stat-width")) { + width = strtoul(value, &end, 10); + if (*end) + return error(_("%s expects a numerical value"), + opt->long_name); + } else if (!strcmp(opt->long_name, "stat-name-width")) { + name_width = strtoul(value, &end, 10); + if (*end) + return error(_("%s expects a numerical value"), + opt->long_name); + } else if (!strcmp(opt->long_name, "stat-graph-width")) { + graph_width = strtoul(value, &end, 10); + if (*end) + return error(_("%s expects a numerical value"), + opt->long_name); + } else if (!strcmp(opt->long_name, "stat-count")) { + count = strtoul(value, &end, 10); + if (*end) + return error(_("%s expects a numerical value"), + opt->long_name); + } else + BUG("%s should not get here", opt->long_name); - /* Important! This checks all the error cases! */ - if (*end) - return 0; options->output_format |= DIFF_FORMAT_DIFFSTAT; options->stat_name_width = name_width; options->stat_graph_width = graph_width; options->stat_width = width; options->stat_count = count; - return argcount; + return 0; } static int parse_dirstat_opt(struct diff_options *options, const char *params) @@ -4958,6 +4932,21 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "name-status", &options->output_format, N_("show only names and status of changed files"), DIFF_FORMAT_NAME_STATUS, PARSE_OPT_NONEG), + OPT_CALLBACK_F(0, "stat", options, N_("[,[,]]"), + N_("generate diffstat"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_stat), + OPT_CALLBACK_F(0, "stat-width", options, N_(""), + N_("generate diffstat with a given width"), + PARSE_OPT_NONEG, diff_opt_stat), + OPT_CALLBACK_F(0, "stat-name-width", options, N_(""), + N_("generate diffstat with a given name width"), + PARSE_OPT_NONEG, diff_opt_stat), + OPT_CALLBACK_F(0, "stat-graph-width", options, N_(""), + N_("generate diffstat with a given graph width"), + PARSE_OPT_NONEG, diff_opt_stat), + OPT_CALLBACK_F(0, "stat-count", options, N_(""), + N_("generate diffstat with limited lines"), + PARSE_OPT_NONEG, diff_opt_stat), OPT_END() }; @@ -4986,10 +4975,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (starts_with(arg, "--stat")) - /* --stat, --stat-width, --stat-name-width, or --stat-count */ - return stat_opt(options, av); - else if (!strcmp(arg, "--compact-summary")) { + if (!strcmp(arg, "--compact-summary")) { options->flags.stat_with_summary = 1; options->output_format |= DIFF_FORMAT_DIFFSTAT; } else if (!strcmp(arg, "--no-compact-summary")) From 7d7942b79678904c7f07a2d5cbb76dd991926373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:11 +0700 Subject: [PATCH 0119/1015] diff-parseopt: convert --[no-]compact-summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/diff.c b/diff.c index 1feb13deb3adef..b24f6825a13275 100644 --- a/diff.c +++ b/diff.c @@ -4841,6 +4841,21 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static int diff_opt_compact_summary(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_ARG(arg); + if (unset) { + options->flags.stat_with_summary = 0; + } else { + options->flags.stat_with_summary = 1; + options->output_format |= DIFF_FORMAT_DIFFSTAT; + } + return 0; +} + static int diff_opt_dirstat(const struct option *opt, const char *arg, int unset) { @@ -4947,6 +4962,9 @@ static void prep_parse_options(struct diff_options *options) OPT_CALLBACK_F(0, "stat-count", options, N_(""), N_("generate diffstat with limited lines"), PARSE_OPT_NONEG, diff_opt_stat), + OPT_CALLBACK_F(0, "compact-summary", options, NULL, + N_("generate compact summary in diffstat"), + PARSE_OPT_NOARG, diff_opt_compact_summary), OPT_END() }; @@ -4975,12 +4993,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* Output format options */ - if (!strcmp(arg, "--compact-summary")) { - options->flags.stat_with_summary = 1; - options->output_format |= DIFF_FORMAT_DIFFSTAT; - } else if (!strcmp(arg, "--no-compact-summary")) - options->flags.stat_with_summary = 0; - else if (skip_prefix(arg, "--output-indicator-new=", &arg)) + if (skip_prefix(arg, "--output-indicator-new=", &arg)) options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0]; else if (skip_prefix(arg, "--output-indicator-old=", &arg)) options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0]; From af2f36809182c24ee92d3b13ed3cc5a1fb6e10df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:12 +0700 Subject: [PATCH 0120/1015] diff-parseopt: convert --output-* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also validates that the user specifies a single character in --output-indicator-*, not a string. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 10 +++++ diff.c | 71 +++++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 7b81b852cab8aa..56b731eae54410 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -41,6 +41,16 @@ ifndef::git-format-patch[] Implies `-p`. endif::git-format-patch[] +--output=:: + Output to a specific file instead of stdout. + +--output-indicator-new=:: +--output-indicator-old=:: +--output-indicator-context=:: + Specify the character used to indicate new, old or context + lines in the generated patch. Normally they are '+', '-' and + ' ' respectively. + ifndef::git-format-patch[] --raw:: ifndef::git-log[] diff --git a/diff.c b/diff.c index b24f6825a13275..8df396cb9a36d8 100644 --- a/diff.c +++ b/diff.c @@ -4841,6 +4841,19 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static int diff_opt_char(const struct option *opt, + const char *arg, int unset) +{ + char *value = opt->value; + + BUG_ON_OPT_NEG(unset); + if (arg[1]) + return error(_("%s expects a character, got '%s'"), + opt->long_name, arg); + *value = arg[0]; + return 0; +} + static int diff_opt_compact_summary(const struct option *opt, const char *arg, int unset) { @@ -4872,6 +4885,23 @@ static int diff_opt_dirstat(const struct option *opt, return 0; } +static enum parse_opt_result diff_opt_output(struct parse_opt_ctx_t *ctx, + const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + char *path; + + BUG_ON_OPT_NEG(unset); + path = prefix_filename(ctx->prefix, arg); + options->file = xfopen(path, "w"); + options->close_file = 1; + if (options->use_color != GIT_COLOR_ALWAYS) + options->use_color = GIT_COLOR_NEVER; + free(path); + return 0; +} + static int diff_opt_unified(const struct option *opt, const char *arg, int unset) { @@ -4965,6 +4995,27 @@ static void prep_parse_options(struct diff_options *options) OPT_CALLBACK_F(0, "compact-summary", options, NULL, N_("generate compact summary in diffstat"), PARSE_OPT_NOARG, diff_opt_compact_summary), + OPT_CALLBACK_F(0, "output-indicator-new", + &options->output_indicators[OUTPUT_INDICATOR_NEW], + N_(""), + N_("specify the character to indicate a new line instead of '+'"), + PARSE_OPT_NONEG, diff_opt_char), + OPT_CALLBACK_F(0, "output-indicator-old", + &options->output_indicators[OUTPUT_INDICATOR_OLD], + N_(""), + N_("specify the character to indicate an old line instead of '-'"), + PARSE_OPT_NONEG, diff_opt_char), + OPT_CALLBACK_F(0, "output-indicator-context", + &options->output_indicators[OUTPUT_INDICATOR_CONTEXT], + N_(""), + N_("specify the character to indicate a context instead of ' '"), + PARSE_OPT_NONEG, diff_opt_char), + + OPT_GROUP(N_("Diff other options")), + { OPTION_CALLBACK, 0, "output", options, N_(""), + N_("Output to a specific file"), + PARSE_OPT_NONEG, NULL, 0, diff_opt_output }, + OPT_END() }; @@ -4992,16 +5043,8 @@ int diff_opt_parse(struct diff_options *options, if (ac) return ac; - /* Output format options */ - if (skip_prefix(arg, "--output-indicator-new=", &arg)) - options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0]; - else if (skip_prefix(arg, "--output-indicator-old=", &arg)) - options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0]; - else if (skip_prefix(arg, "--output-indicator-context=", &arg)) - options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = arg[0]; - /* renames options */ - else if (starts_with(arg, "-B") || + if (starts_with(arg, "-B") || skip_to_optional_arg(arg, "--break-rewrites", NULL)) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -B: %s", arg+2); @@ -5242,15 +5285,7 @@ int diff_opt_parse(struct diff_options *options, else if (opt_arg(arg, '\0', "inter-hunk-context", &options->interhunkcontext)) ; - else if ((argcount = parse_long_opt("output", av, &optarg))) { - char *path = prefix_filename(prefix, optarg); - options->file = xfopen(path, "w"); - options->close_file = 1; - if (options->use_color != GIT_COLOR_ALWAYS) - options->use_color = GIT_COLOR_NEVER; - free(path); - return argcount; - } else + else return 0; return 1; } From ced4e179feaadd2c0b43fa963acedc7243d09a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:13 +0700 Subject: [PATCH 0121/1015] diff-parseopt: convert -B|--break-rewrites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 62 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/diff.c b/diff.c index 8df396cb9a36d8..d2139082b76743 100644 --- a/diff.c +++ b/diff.c @@ -4841,6 +4841,30 @@ static int parse_objfind_opt(struct diff_options *opt, const char *arg) return 1; } +static int diff_opt_break_rewrites(const struct option *opt, + const char *arg, int unset) +{ + int *break_opt = opt->value; + int opt1, opt2; + + BUG_ON_OPT_NEG(unset); + if (!arg) + arg = ""; + opt1 = parse_rename_score(&arg); + if (*arg == 0) + opt2 = 0; + else if (*arg != '/') + return error(_("%s expects / form"), opt->long_name); + else { + arg++; + opt2 = parse_rename_score(&arg); + } + if (*arg != 0) + return error(_("%s expects / form"), opt->long_name); + *break_opt = opt1 | (opt2 << 16); + return 0; +} + static int diff_opt_char(const struct option *opt, const char *arg, int unset) { @@ -5011,6 +5035,12 @@ static void prep_parse_options(struct diff_options *options) N_("specify the character to indicate a context instead of ' '"), PARSE_OPT_NONEG, diff_opt_char), + OPT_GROUP(N_("Diff rename options")), + OPT_CALLBACK_F('B', "break-rewrites", &options->break_opt, N_("[/]"), + N_("break complete rewrite changes into pairs of delete and create"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_break_rewrites), + OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), N_("Output to a specific file"), @@ -5044,12 +5074,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* renames options */ - if (starts_with(arg, "-B") || - skip_to_optional_arg(arg, "--break-rewrites", NULL)) { - if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) - return error("invalid argument to -B: %s", arg+2); - } - else if (starts_with(arg, "-M") || + if (starts_with(arg, "-M") || skip_to_optional_arg(arg, "--find-renames", NULL)) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -M: %s", arg+2); @@ -5328,17 +5353,14 @@ int parse_rename_score(const char **cp_p) static int diff_scoreopt_parse(const char *opt) { - int opt1, opt2, cmd; + int opt1, cmd; if (*opt++ != '-') return -1; cmd = *opt++; if (cmd == '-') { /* convert the long-form arguments into short-form versions */ - if (skip_prefix(opt, "break-rewrites", &opt)) { - if (*opt == 0 || *opt++ == '=') - cmd = 'B'; - } else if (skip_prefix(opt, "find-copies", &opt)) { + if (skip_prefix(opt, "find-copies", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'C'; } else if (skip_prefix(opt, "find-renames", &opt)) { @@ -5346,25 +5368,13 @@ static int diff_scoreopt_parse(const char *opt) cmd = 'M'; } } - if (cmd != 'M' && cmd != 'C' && cmd != 'B') - return -1; /* that is not a -M, -C, or -B option */ + if (cmd != 'M' && cmd != 'C') + return -1; /* that is not a -M, or -C option */ opt1 = parse_rename_score(&opt); - if (cmd != 'B') - opt2 = 0; - else { - if (*opt == 0) - opt2 = 0; - else if (*opt != '/') - return -1; /* we expect -B80/99 or -B80 */ - else { - opt++; - opt2 = parse_rename_score(&opt); - } - } if (*opt != 0) return -1; - return opt1 | (opt2 << 16); + return opt1; } struct diff_queue_struct diff_queued_diff; From f476308b273bdc78f9a8921b2bafffd39ea1e00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:14 +0700 Subject: [PATCH 0122/1015] diff-parseopt: convert -M|--find-renames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/diff.c b/diff.c index d2139082b76743..2c904e0526d524 100644 --- a/diff.c +++ b/diff.c @@ -4909,6 +4909,22 @@ static int diff_opt_dirstat(const struct option *opt, return 0; } +static int diff_opt_find_renames(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!arg) + arg = ""; + options->rename_score = parse_rename_score(&arg); + if (*arg != 0) + return error(_("invalid argument to %s"), opt->long_name); + + options->detect_rename = DIFF_DETECT_RENAME; + return 0; +} + static enum parse_opt_result diff_opt_output(struct parse_opt_ctx_t *ctx, const struct option *opt, const char *arg, int unset) @@ -5040,6 +5056,10 @@ static void prep_parse_options(struct diff_options *options) N_("break complete rewrite changes into pairs of delete and create"), PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_break_rewrites), + OPT_CALLBACK_F('M', "find-renames", options, N_(""), + N_("detect renames"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_find_renames), OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), @@ -5074,13 +5094,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* renames options */ - if (starts_with(arg, "-M") || - skip_to_optional_arg(arg, "--find-renames", NULL)) { - if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) - return error("invalid argument to -M: %s", arg+2); - options->detect_rename = DIFF_DETECT_RENAME; - } - else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { + if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { options->irreversible_delete = 1; } else if (starts_with(arg, "-C") || @@ -5363,13 +5377,10 @@ static int diff_scoreopt_parse(const char *opt) if (skip_prefix(opt, "find-copies", &opt)) { if (*opt == 0 || *opt++ == '=') cmd = 'C'; - } else if (skip_prefix(opt, "find-renames", &opt)) { - if (*opt == 0 || *opt++ == '=') - cmd = 'M'; } } - if (cmd != 'M' && cmd != 'C') - return -1; /* that is not a -M, or -C option */ + if (cmd != 'C') + return -1; /* that is not a -M option */ opt1 = parse_rename_score(&opt); if (*opt != 0) From 1e5332968a0f46978d457ec580bb1c00c48fd13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:15 +0700 Subject: [PATCH 0123/1015] diff-parseopt: convert -D|--irreversible-delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 2c904e0526d524..e51f6b3005cfe4 100644 --- a/diff.c +++ b/diff.c @@ -5060,6 +5060,9 @@ static void prep_parse_options(struct diff_options *options) N_("detect renames"), PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_find_renames), + OPT_SET_INT_F('D', "irreversible-delete", &options->irreversible_delete, + N_("omit the preimage for deletes"), + 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), @@ -5094,10 +5097,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* renames options */ - if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { - options->irreversible_delete = 1; - } - else if (starts_with(arg, "-C") || + if (starts_with(arg, "-C") || skip_to_optional_arg(arg, "--find-copies", NULL)) { if (options->detect_rename == DIFF_DETECT_COPY) options->flags.find_copies_harder = 1; From 7f64850d364b471f27f3123c4334397f9e03b09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:16 +0700 Subject: [PATCH 0124/1015] diff-parseopt: convert -C|--find-copies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 59 +++++++++++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/diff.c b/diff.c index e51f6b3005cfe4..35bac115cc29e7 100644 --- a/diff.c +++ b/diff.c @@ -4617,8 +4617,6 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va return 1; } -static int diff_scoreopt_parse(const char *opt); - static inline int short_opt(char opt, const char **argv, const char **optarg) { @@ -4909,6 +4907,26 @@ static int diff_opt_dirstat(const struct option *opt, return 0; } +static int diff_opt_find_copies(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!arg) + arg = ""; + options->rename_score = parse_rename_score(&arg); + if (*arg != 0) + return error(_("invalid argument to %s"), opt->long_name); + + if (options->detect_rename == DIFF_DETECT_COPY) + options->flags.find_copies_harder = 1; + else + options->detect_rename = DIFF_DETECT_COPY; + + return 0; +} + static int diff_opt_find_renames(const struct option *opt, const char *arg, int unset) { @@ -5063,6 +5081,10 @@ static void prep_parse_options(struct diff_options *options) OPT_SET_INT_F('D', "irreversible-delete", &options->irreversible_delete, N_("omit the preimage for deletes"), 1, PARSE_OPT_NONEG), + OPT_CALLBACK_F('C', "find-copies", options, N_(""), + N_("detect copies"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_find_copies), OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), @@ -5097,15 +5119,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* renames options */ - if (starts_with(arg, "-C") || - skip_to_optional_arg(arg, "--find-copies", NULL)) { - if (options->detect_rename == DIFF_DETECT_COPY) - options->flags.find_copies_harder = 1; - if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) - return error("invalid argument to -C: %s", arg+2); - options->detect_rename = DIFF_DETECT_COPY; - } - else if (!strcmp(arg, "--no-renames")) + if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else if (!strcmp(arg, "--rename-empty")) options->flags.rename_empty = 1; @@ -5365,29 +5379,6 @@ int parse_rename_score(const char **cp_p) return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale)); } -static int diff_scoreopt_parse(const char *opt) -{ - int opt1, cmd; - - if (*opt++ != '-') - return -1; - cmd = *opt++; - if (cmd == '-') { - /* convert the long-form arguments into short-form versions */ - if (skip_prefix(opt, "find-copies", &opt)) { - if (*opt == 0 || *opt++ == '=') - cmd = 'C'; - } - } - if (cmd != 'C') - return -1; /* that is not a -M option */ - - opt1 = parse_rename_score(&opt); - if (*opt != 0) - return -1; - return opt1; -} - struct diff_queue_struct diff_queued_diff; void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) From bdd4741bfd380ca70fe3d185e7bd7f8ff54eafc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:17 +0700 Subject: [PATCH 0125/1015] diff-parseopt: convert --find-copies-harder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --no-find-copies-harder is also added on purpose (because I don't see why we should not have the --no- version for this) Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index 35bac115cc29e7..abb1566f957113 100644 --- a/diff.c +++ b/diff.c @@ -5085,6 +5085,8 @@ static void prep_parse_options(struct diff_options *options) N_("detect copies"), PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_find_copies), + OPT_BOOL(0, "find-copies-harder", &options->flags.find_copies_harder, + N_("use unmodified files as source to find copies")), OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), @@ -5191,8 +5193,6 @@ int diff_opt_parse(struct diff_options *options, options->flags.text = 1; else if (!strcmp(arg, "-R")) options->flags.reverse_diff = 1; - else if (!strcmp(arg, "--find-copies-harder")) - options->flags.find_copies_harder = 1; else if (!strcmp(arg, "--follow")) options->flags.follow_renames = 1; else if (!strcmp(arg, "--no-follow")) { From cdc43eb0b871992b42cff02ea9fe818c812c6dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:18 +0700 Subject: [PATCH 0126/1015] diff-parseopt: convert --no-renames|--[no--rename-empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For --rename-empty, see 90d43b0768 (teach diffcore-rename to optionally ignore empty content - 2012-03-22) for more information. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 3 +++ diff.c | 13 ++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 56b731eae54410..915f2fec8b1d4c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -392,6 +392,9 @@ endif::git-format-patch[] Turn off rename detection, even when the configuration file gives the default to do so. +--[no-]rename-empty:: + Whether to use empty blobs as rename source. + ifndef::git-format-patch[] --check:: Warn if changes introduce conflict markers or whitespace errors. diff --git a/diff.c b/diff.c index abb1566f957113..d423a06b41b1c3 100644 --- a/diff.c +++ b/diff.c @@ -5087,6 +5087,11 @@ static void prep_parse_options(struct diff_options *options) diff_opt_find_copies), OPT_BOOL(0, "find-copies-harder", &options->flags.find_copies_harder, N_("use unmodified files as source to find copies")), + OPT_SET_INT_F(0, "no-renames", &options->detect_rename, + N_("disable rename detection"), + 0, PARSE_OPT_NONEG), + OPT_BOOL(0, "rename-empty", &options->flags.rename_empty, + N_("use empty blobs as rename source")), OPT_GROUP(N_("Diff other options")), { OPTION_CALLBACK, 0, "output", options, N_(""), @@ -5121,13 +5126,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* renames options */ - if (!strcmp(arg, "--no-renames")) - options->detect_rename = 0; - else if (!strcmp(arg, "--rename-empty")) - options->flags.rename_empty = 1; - else if (!strcmp(arg, "--no-rename-empty")) - options->flags.rename_empty = 0; - else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) { + if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) { options->flags.relative_name = 1; if (arg) options->prefix = arg; From 0b1c5b59f0507e428485c7d843c11d6085555db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:19 +0700 Subject: [PATCH 0127/1015] diff-parseopt: convert --relative MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/diff.c b/diff.c index d423a06b41b1c3..b9c267a1997706 100644 --- a/diff.c +++ b/diff.c @@ -4960,6 +4960,18 @@ static enum parse_opt_result diff_opt_output(struct parse_opt_ctx_t *ctx, return 0; } +static int diff_opt_relative(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + options->flags.relative_name = 1; + if (arg) + options->prefix = arg; + return 0; +} + static int diff_opt_unified(const struct option *opt, const char *arg, int unset) { @@ -5094,6 +5106,10 @@ static void prep_parse_options(struct diff_options *options) N_("use empty blobs as rename source")), OPT_GROUP(N_("Diff other options")), + OPT_CALLBACK_F(0, "relative", options, N_(""), + N_("when run from subdir, exclude changes outside and show relative paths"), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, + diff_opt_relative), { OPTION_CALLBACK, 0, "output", options, N_(""), N_("Output to a specific file"), PARSE_OPT_NONEG, NULL, 0, diff_opt_output }, @@ -5125,15 +5141,8 @@ int diff_opt_parse(struct diff_options *options, if (ac) return ac; - /* renames options */ - if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) { - options->flags.relative_name = 1; - if (arg) - options->prefix = arg; - } - /* xdiff options */ - else if (!strcmp(arg, "--minimal")) + if (!strcmp(arg, "--minimal")) DIFF_XDL_SET(options, NEED_MINIMAL); else if (!strcmp(arg, "--no-minimal")) DIFF_XDL_CLR(options, NEED_MINIMAL); From 2e75f922f3ee73a1ff9c232c11c82c6006ebeb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:20 +0700 Subject: [PATCH 0128/1015] diff-parseopt: convert --[no-]minimal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/diff.c b/diff.c index b9c267a1997706..33492e754fc5b2 100644 --- a/diff.c +++ b/diff.c @@ -5105,6 +5105,11 @@ static void prep_parse_options(struct diff_options *options) OPT_BOOL(0, "rename-empty", &options->flags.rename_empty, N_("use empty blobs as rename source")), + OPT_GROUP(N_("Diff algorithm options")), + OPT_BIT(0, "minimal", &options->xdl_opts, + N_("produce the smallest possible diff"), + XDF_NEED_MINIMAL), + OPT_GROUP(N_("Diff other options")), OPT_CALLBACK_F(0, "relative", options, N_(""), N_("when run from subdir, exclude changes outside and show relative paths"), @@ -5142,11 +5147,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* xdiff options */ - if (!strcmp(arg, "--minimal")) - DIFF_XDL_SET(options, NEED_MINIMAL); - else if (!strcmp(arg, "--no-minimal")) - DIFF_XDL_CLR(options, NEED_MINIMAL); - else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) + if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) DIFF_XDL_SET(options, IGNORE_WHITESPACE); else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); From 87649a1674a80a0c66b9c17977e7b54c08dd684a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 21 Feb 2019 18:16:21 +0700 Subject: [PATCH 0129/1015] diff-parseopt: convert --ignore-some-changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- diff.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/diff.c b/diff.c index 33492e754fc5b2..a63ee4a44d7cfa 100644 --- a/diff.c +++ b/diff.c @@ -5109,6 +5109,21 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT(0, "minimal", &options->xdl_opts, N_("produce the smallest possible diff"), XDF_NEED_MINIMAL), + OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts, + N_("ignore whitespace when comparing lines"), + XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG), + OPT_BIT_F('b', "ignore-space-change", &options->xdl_opts, + N_("ignore changes in amount of whitespace"), + XDF_IGNORE_WHITESPACE_CHANGE, PARSE_OPT_NONEG), + OPT_BIT_F(0, "ignore-space-at-eol", &options->xdl_opts, + N_("ignore changes in whitespace at EOL"), + XDF_IGNORE_WHITESPACE_AT_EOL, PARSE_OPT_NONEG), + OPT_BIT_F(0, "ignore-cr-at-eol", &options->xdl_opts, + N_("ignore carrier-return at the end of line"), + XDF_IGNORE_CR_AT_EOL, PARSE_OPT_NONEG), + OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts, + N_("ignore changes whose lines are all blank"), + XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG), OPT_GROUP(N_("Diff other options")), OPT_CALLBACK_F(0, "relative", options, N_(""), @@ -5147,17 +5162,7 @@ int diff_opt_parse(struct diff_options *options, return ac; /* xdiff options */ - if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) - DIFF_XDL_SET(options, IGNORE_WHITESPACE); - else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) - DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); - else if (!strcmp(arg, "--ignore-space-at-eol")) - DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); - else if (!strcmp(arg, "--ignore-cr-at-eol")) - DIFF_XDL_SET(options, IGNORE_CR_AT_EOL); - else if (!strcmp(arg, "--ignore-blank-lines")) - DIFF_XDL_SET(options, IGNORE_BLANK_LINES); - else if (!strcmp(arg, "--indent-heuristic")) + if (!strcmp(arg, "--indent-heuristic")) DIFF_XDL_SET(options, INDENT_HEURISTIC); else if (!strcmp(arg, "--no-indent-heuristic")) DIFF_XDL_CLR(options, INDENT_HEURISTIC); From bc208ae31417ea4fecad14edcdb8b6c7decb0c38 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Feb 2019 10:24:07 -0800 Subject: [PATCH 0130/1015] builtin/log: downcase the beginning of error messages Also drop full-stop at the end of error messages, per Documentation/CodingGuidelines. Signed-off-by: Junio C Hamano --- builtin/log.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/builtin/log.c b/builtin/log.c index 57869267d8d75e..2e4218cb57bbe1 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -513,7 +513,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c if (get_oid_with_context(the_repository, obj_name, GET_OID_RECORD_PATH, &oidc, &obj_context)) - die(_("Not a valid object name %s"), obj_name); + die(_("not a valid object name %s"), obj_name); if (!obj_context.path || !textconv_object(the_repository, obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) { @@ -537,7 +537,7 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) int offset = 0; if (!buf) - return error(_("Could not read object %s"), oid_to_hex(oid)); + return error(_("could not read object %s"), oid_to_hex(oid)); assert(type == OBJ_TAG); while (offset < size && buf[offset] != '\n') { @@ -631,7 +631,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) break; o = parse_object(the_repository, &t->tagged->oid); if (!o) - ret = error(_("Could not read object %s"), + ret = error(_("could not read object %s"), oid_to_hex(&t->tagged->oid)); objects[i].item = o; i--; @@ -656,7 +656,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) ret = cmd_log_walk(&rev); break; default: - ret = error(_("Unknown type: %d"), o->type); + ret = error(_("unknown type: %d"), o->type); } } free(objects); @@ -894,7 +894,7 @@ static int open_next_file(struct commit *commit, const char *subject, printf("%s\n", filename.buf + outdir_offset); if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { - error_errno(_("Cannot open patch file %s"), filename.buf); + error_errno(_("cannot open patch file %s"), filename.buf); strbuf_release(&filename); return -1; } @@ -911,7 +911,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) unsigned flags1, flags2; if (rev->pending.nr != 2) - die(_("Need exactly one range.")); + die(_("need exactly one range")); o1 = rev->pending.objects[0].item; o2 = rev->pending.objects[1].item; @@ -921,7 +921,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) c2 = lookup_commit_reference(the_repository, &o2->oid); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die(_("Not a range.")); + die(_("not a range")); init_patch_ids(the_repository, ids); @@ -1044,7 +1044,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *head = list[0]; if (!cmit_fmt_is_mail(rev->commit_format)) - die(_("Cover letter needs email format")); + die(_("cover letter needs email format")); committer = git_committer_info(0); @@ -1214,7 +1214,7 @@ static int output_directory_callback(const struct option *opt, const char *arg, const char **dir = (const char **)opt->value; BUG_ON_OPT_NEG(unset); if (*dir) - die(_("Two output directories?")); + die(_("two output directories?")); *dir = arg; return 0; } @@ -1321,7 +1321,7 @@ static struct commit *get_base_commit(const char *base_commit, if (base_commit && strcmp(base_commit, "auto")) { base = lookup_commit_reference_by_name(base_commit); if (!base) - die(_("Unknown commit %s"), base_commit); + die(_("unknown commit %s"), base_commit); } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); @@ -1331,18 +1331,18 @@ static struct commit *get_base_commit(const char *base_commit, struct object_id oid; if (get_oid(upstream, &oid)) - die(_("Failed to resolve '%s' as a valid ref."), upstream); + die(_("failed to resolve '%s' as a valid ref"), upstream); commit = lookup_commit_or_die(&oid, "upstream base"); base_list = get_merge_bases_many(commit, total, list); /* There should be one and only one merge base. */ if (!base_list || base_list->next) - die(_("Could not find exact merge base.")); + die(_("could not find exact merge base")); base = base_list->item; free_commit_list(base_list); } else { - die(_("Failed to get upstream, if you want to record base commit automatically,\n" + die(_("failed to get upstream, if you want to record base commit automatically,\n" "please use git branch --set-upstream-to to track a remote branch.\n" - "Or you could specify base commit by --base= manually.")); + "Or you could specify base commit by --base= manually")); } } @@ -1360,7 +1360,7 @@ static struct commit *get_base_commit(const char *base_commit, struct commit_list *merge_base; merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); if (!merge_base || merge_base->next) - die(_("Failed to find exact merge base")); + die(_("failed to find exact merge base")); rev[i] = merge_base->item; } @@ -1739,7 +1739,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (use_stdout) die(_("standard output, or directory, which one?")); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno(_("Could not create directory '%s'"), + die_errno(_("could not create directory '%s'"), output_directory); } @@ -1941,7 +1941,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout && open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) - die(_("Failed to create output files")); + die(_("failed to create output files")); shown = log_tree_commit(&rev, commit); free_commit_buffer(the_repository->parsed_objects, commit); @@ -2065,9 +2065,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) revs.max_parents = 1; if (add_pending_commit(head, &revs, 0)) - die(_("Unknown commit %s"), head); + die(_("unknown commit %s"), head); if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die(_("Unknown commit %s"), upstream); + die(_("unknown commit %s"), upstream); /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { @@ -2079,7 +2079,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) get_patch_ids(&revs, &ids); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die(_("Unknown commit %s"), limit); + die(_("unknown commit %s"), limit); /* reverse the list of commits */ if (prepare_revision_walk(&revs)) From 2fe95f494cde6cb3c3e6894bb687961b28b1764b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Feb 2019 11:26:43 -0800 Subject: [PATCH 0131/1015] format-patch: notice failure to open cover letter for writing The make_cover_letter() function is supposed to open a new file for writing, and let the caller write into it via FILE *rev->diffopt.file but because the function does not return anything, the caller does not bother checking the return value. Make sure it dies, instead of keep going with a NULL output filestream and relying on it to cause a crash, when it fails to open the file. Signed-off-by: Junio C Hamano --- builtin/log.c | 2 +- t/t4014-format-patch.sh | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/builtin/log.c b/builtin/log.c index 2e4218cb57bbe1..4fc424f1d9da84 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1050,7 +1050,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (!use_stdout && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) - return; + die(_("failed to create cover-letter file")); log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 0); diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 909c743c134c5e..b6e2fdbc4410f1 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -589,6 +589,12 @@ test_expect_success 'excessive subject' ' ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch ' +test_expect_success 'failure to write cover-letter aborts gracefully' ' + test_when_finished "rmdir 0000-cover-letter.patch" && + mkdir 0000-cover-letter.patch && + test_must_fail git format-patch --no-renames --cover-letter -1 +' + test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && From 78ad91728d859a3ddb6e86218e86f16cd3489d0a Mon Sep 17 00:00:00 2001 From: Jonathan Tan Date: Thu, 21 Feb 2019 12:24:40 -0800 Subject: [PATCH 0132/1015] remote-curl: refactor reading into rpc_state's buf Currently, whenever remote-curl reads pkt-lines from its response file descriptor, only the payload is written to its buf, not the 4 characters denoting the length. A future patch will require the ability to also write those 4 characters, so in preparation for that, refactor this read into its own function. Signed-off-by: Jonathan Tan Signed-off-by: Junio C Hamano --- remote-curl.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 8e0e37ed3d5ef1..1f0161475deef7 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -520,6 +520,25 @@ struct rpc_state { unsigned initial_buffer : 1; }; +/* + * Appends the result of reading from rpc->out to the string represented by + * rpc->buf and rpc->len if there is enough space. Returns 1 if there was + * enough space, 0 otherwise. + * + * Writes the number of bytes appended into appended. + */ +static int rpc_read_from_out(struct rpc_state *rpc, size_t *appended) { + size_t left = rpc->alloc - rpc->len; + char *buf = rpc->buf + rpc->len; + + if (left < LARGE_PACKET_MAX) + return 0; + + *appended = packet_read(rpc->out, NULL, NULL, buf, left, 0); + rpc->len += *appended; + return 1; +} + static size_t rpc_out(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { @@ -529,11 +548,12 @@ static size_t rpc_out(void *ptr, size_t eltsize, if (!avail) { rpc->initial_buffer = 0; - avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); + rpc->len = 0; + if (!rpc_read_from_out(rpc, &avail)) + BUG("The entire rpc->buf should be larger than LARGE_PACKET_DATA_MAX"); if (!avail) return 0; rpc->pos = 0; - rpc->len = avail; } if (max < avail) @@ -677,20 +697,15 @@ static int post_rpc(struct rpc_state *rpc) * chunked encoding mess. */ while (1) { - size_t left = rpc->alloc - rpc->len; - char *buf = rpc->buf + rpc->len; - int n; + size_t n; - if (left < LARGE_PACKET_MAX) { + if (!rpc_read_from_out(rpc, &n)) { large_request = 1; use_gzip = 0; break; } - - n = packet_read(rpc->out, NULL, NULL, buf, left, 0); if (!n) break; - rpc->len += n; } if (large_request) { From f6761faaa17a98c025b34fffeeec71f0ec223b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Thu, 21 Feb 2019 23:37:46 +0100 Subject: [PATCH 0133/1015] commit-graph tests: split up corrupt_graph_and_verify() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split up the corrupt_graph_and_verify() function added in d9b9f8a6fd ("commit-graph: verify catches corrupt signature", 2018-06-27) into its logical components of setting up the test itself, doing the corruption in a particular way with "dd", and then finally testing that stderr is what we expect. This allows for re-using everything except the now slimmer corrupt_graph_and_verify() to corrupt the graph in a way that doesn't involve inserting a given byte sequence at a given position, e.g. truncating it entirely to a custom value. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- t/t5318-commit-graph.sh | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index d4bd1522fe2e6e..725b7ce51f06d7 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -366,6 +366,19 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \ GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4)) GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES)) +corrupt_graph_setup() { + cd "$TRASH_DIRECTORY/full" && + test_when_finished mv commit-graph-backup $objdir/info/commit-graph && + cp $objdir/info/commit-graph commit-graph-backup +} + +corrupt_graph_verify() { + grepstr=$1 + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err && + test_i18ngrep "$grepstr" err +} + # usage: corrupt_graph_and_verify [] # Manipulates the commit-graph file at the position # by inserting the data, optionally zeroing the file @@ -376,17 +389,14 @@ corrupt_graph_and_verify() { pos=$1 data="${2:-\0}" grepstr=$3 - cd "$TRASH_DIRECTORY/full" && + corrupt_graph_setup && orig_size=$(wc -c < $objdir/info/commit-graph) && zero_pos=${4:-${orig_size}} && - test_when_finished mv commit-graph-backup $objdir/info/commit-graph && - cp $objdir/info/commit-graph commit-graph-backup && printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc && dd of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=0 && generate_zero_bytes $(($orig_size - $zero_pos)) >>"$objdir/info/commit-graph" && - test_must_fail git commit-graph verify 2>test_err && - grep -v "^+" test_err >err && - test_i18ngrep "$grepstr" err + corrupt_graph_verify "$grepstr" + } test_expect_success 'detect bad signature' ' From 945944ca70cba5a1e0b54be94d53030b4a3ad7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Thu, 21 Feb 2019 23:37:47 +0100 Subject: [PATCH 0134/1015] commit-graph tests: test a graph that's too small MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the recently split-up components of the corrupt_graph_and_verify() function to assert that we error on graphs that are too small. The error was added in 2a2e32bdc5 ("commit-graph: implement git commit-graph read", 2018-04-10), but there was no test for it. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- t/t5318-commit-graph.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 725b7ce51f06d7..733be2ed30c2ea 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -399,6 +399,12 @@ corrupt_graph_and_verify() { } +test_expect_success 'detect too small' ' + corrupt_graph_setup && + echo "a small graph" >$objdir/info/commit-graph && + corrupt_graph_verify "too small" +' + test_expect_success 'detect bad signature' ' corrupt_graph_and_verify 0 "\0" \ "graph signature" From c271dc28fd718655be8609c931b92833cc8f8c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 22 Feb 2019 18:27:57 +0700 Subject: [PATCH 0135/1015] Delete check-racy.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is git-checy-racy command, added a long time ago [1] and was never part of the default build. Naturally after some makefile changes [2], git-check-racy was no longer recognized as a build target. Even if it compiles to day, it will not link after the introduction of common-main.c [3]. Racy index has not been a problem for a long time [4]. It's time to let this code go. I briefly consider if check-racy should be part of test-tool. But I don't think it's worth the effort. [1] 42f774063d (Add check program "git-check-racy" - 2006-08-15) [2] c373991375 (Makefile: list generated object files in OBJECTS - 2010-01-26) [3] 3f2e2297b9 (add an extra level of indirection to main() - 2016-07-01) [4] I pretend I don't remember anything about the recent split-index's racy problem Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- check-racy.c | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 check-racy.c diff --git a/check-racy.c b/check-racy.c deleted file mode 100644 index 24b6542352a600..00000000000000 --- a/check-racy.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "cache.h" - -int main(int ac, char **av) -{ - int i; - int dirty, clean, racy; - - dirty = clean = racy = 0; - read_cache(); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - struct stat st; - - if (lstat(ce->name, &st)) { - error_errno("lstat(%s)", ce->name); - continue; - } - - if (ce_match_stat(ce, &st, 0)) - dirty++; - else if (ce_match_stat(ce, &st, CE_MATCH_RACY_IS_DIRTY)) - racy++; - else - clean++; - } - printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy); - return 0; -} From e544221d97a57a9be7ba18a0df52a15b4cc76f97 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 22 Feb 2019 14:25:00 -0800 Subject: [PATCH 0136/1015] trace2: Documentation/technical/api-trace2.txt Created design document for Trace2 feature. Signed-off-by: Jeff Hostetler Signed-off-by: Junio C Hamano --- Documentation/technical/api-trace2.txt | 1349 ++++++++++++++++++++++++ 1 file changed, 1349 insertions(+) create mode 100644 Documentation/technical/api-trace2.txt diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt new file mode 100644 index 00000000000000..2de565fa3d5a15 --- /dev/null +++ b/Documentation/technical/api-trace2.txt @@ -0,0 +1,1349 @@ += Trace2 API + +The Trace2 API can be used to print debug, performance, and telemetry +information to stderr or a file. The Trace2 feature is inactive unless +explicitly enabled by enabling one or more Trace2 Targets. + +The Trace2 API is intended to replace the existing (Trace1) +printf-style tracing provided by the existing `GIT_TRACE` and +`GIT_TRACE_PERFORMANCE` facilities. During initial implementation, +Trace2 and Trace1 may operate in parallel. + +The Trace2 API defines a set of high-level messages with known fields, +such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}). + +Trace2 instrumentation throughout the Git code base sends Trace2 +messages to the enabled Trace2 Targets. Targets transform these +messages content into purpose-specific formats and write events to +their data streams. In this manner, the Trace2 API can drive +many different types of analysis. + +Targets are defined using a VTable allowing easy extension to other +formats in the future. This might be used to define a binary format, +for example. + +== Trace2 Targets + +Trace2 defines the following set of Trace2 Targets. +Format details are given in a later section. + +`GIT_TR2` (NORMAL):: + + a simple printf format like GIT_TRACE. ++ +------------ +$ export GIT_TR2=~/log.normal +$ git version +git version 2.20.1.155.g426c96fcdb +------------ ++ +------------ +$ cat ~/log.normal +12:28:42.620009 common-main.c:38 version 2.20.1.155.g426c96fcdb +12:28:42.620989 common-main.c:39 start git version +12:28:42.621101 git.c:432 cmd_name version (version) +12:28:42.621215 git.c:662 exit elapsed:0.001227 code:0 +12:28:42.621250 trace2/tr2_tgt_normal.c:124 atexit elapsed:0.001265 code:0 +------------ + +`GIT_TR2_PERF` (PERF):: + + a column-based format to replace GIT_TRACE_PERFORMANCE suitable for + development and testing, possibly to complement tools like gprof. ++ +------------ +$ export GIT_TR2_PERF=~/log.perf +$ git version +git version 2.20.1.155.g426c96fcdb +------------ ++ +------------ +$ cat ~/log.perf +12:28:42.620675 common-main.c:38 | d0 | main | version | | | | | 2.20.1.155.g426c96fcdb +12:28:42.621001 common-main.c:39 | d0 | main | start | | | | | git version +12:28:42.621111 git.c:432 | d0 | main | cmd_name | | | | | version (version) +12:28:42.621225 git.c:662 | d0 | main | exit | | 0.001227 | | | code:0 +12:28:42.621259 trace2/tr2_tgt_perf.c:211 | d0 | main | atexit | | 0.001265 | | | code:0 +------------ + +`GIT_TR2_EVENT` (EVENT):: + + a JSON-based format of event data suitable for telemetry analysis. ++ +------------ +$ export GIT_TR2_EVENT=~/log.event +$ git version +git version 2.20.1.155.g426c96fcdb +------------ ++ +------------ +$ cat ~/log.event +{"event":"version","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.620713","file":"common-main.c","line":38,"evt":"1","exe":"2.20.1.155.g426c96fcdb"} +{"event":"start","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621027","file":"common-main.c","line":39,"argv":["git","version"]} +{"event":"cmd_name","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621122","file":"git.c","line":432,"name":"version","hierarchy":"version"} +{"event":"exit","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621236","file":"git.c","line":662,"t_abs":0.001227,"code":0} +{"event":"atexit","sid":"1547659722619736-11614","thread":"main","time":"2019-01-16 17:28:42.621268","file":"trace2/tr2_tgt_event.c","line":163,"t_abs":0.001265,"code":0} +------------ + +== Enabling a Target + +A Trace2 Target is enabled when the corresponding environment variable +(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set. The following +values are recognized. + +`0`:: +`false`:: + + Disables the target. + +`1`:: +`true`:: + + Enables the target and writes stream to `STDERR`. + +`[2-9]`:: + + Enables the target and writes to the already opened file descriptor. + +``:: + + Enables the target, opens and writes to the file in append mode. + +`af_unix:[:]`:: + + Enables the target, opens and writes to a Unix Domain Socket + (on platforms that support them). ++ +Socket type can be either `stream` or `dgram`. If the socket type is +omitted, Git will try both. + +== Trace2 API + +All public Trace2 functions and macros are defined in `trace2.h` and +`trace2.c`. All public symbols are prefixed with `trace2_`. + +There are no public Trace2 data structures. + +The Trace2 code also defines a set of private functions and data types +in the `trace2/` directory. These symbols are prefixed with `tr2_` +and should only be used by functions in `trace2.c`. + +== Conventions for Public Functions and Macros + +The functions defined by the Trace2 API are declared and documented +in `trace2.h`. It defines the API functions and wrapper macros for +Trace2. + +Some functions have a `_fl()` suffix to indicate that they take `file` +and `line-number` arguments. + +Some functions have a `_va_fl()` suffix to indicate that they also +take a `va_list` argument. + +Some functions have a `_printf_fl()` suffix to indicate that they also +take a varargs argument. + +There are CPP wrapper macros and ifdefs to hide most of these details. +See `trace2.h` for more details. The following discussion will only +describe the simplified forms. + +== Public API + +All Trace2 API functions send a messsage to all of the active +Trace2 Targets. This section describes the set of available +messages. + +It helps to divide these functions into groups for discussion +purposes. + +=== Basic Command Messages + +These are concerned with the lifetime of the overall git process. + +`void trace2_initialize()`:: + + Determines if any Trace2 Targets should be enabled and + initializes the Trace2 facility. This includes starting the + elapsed time clocks and thread local storage (TLS). ++ +This function emits a "version" message containing the version of git +and the Trace2 protocol. ++ +This function should be called from `main()` as early as possible in +the life of the process. + +`int trace2_is_enabled()`:: + + Returns 1 if Trace2 is enabled (at least one target is + active). + +`void trace2_cmd_start(int argc, const char **argv)`:: + + Emits a "start" message containing the process command line + arguments. + +`int trace2_cmd_exit(int exit_code)`:: + + Emits an "exit" message containing the process exit-code and + elapsed time. ++ +Returns the exit-code. + +`void trace2_cmd_error(const char *fmt, va_list ap)`:: + + Emits an "error" message containing a formatted error message. + +`void trace2_cmd_path(const char *pathname)`:: + + Emits a "cmd_path" message with the full pathname of the + current process. + +=== Command Detail Messages + +These are concerned with describing the specific Git command +after the command line, config, and environment are inspected. + +`void trace2_cmd_name(const char *name)`:: + + Emits a "cmd_name" message with the canonical name of the + command, for example "status" or "checkout". + +`void trace2_cmd_mode(const char *mode)`:: + + Emits a "cmd_mode" message with a qualifier name to further + describe the current git command. ++ +This message is intended to be used with git commands having multiple +major modes. For example, a "checkout" command can checkout a new +branch or it can checkout a single file, so the checkout code could +emit a cmd_mode message of "branch" or "file". + +`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`:: + + Emits an "alias" message containing the alias used and the + argument expansion. + +`void trace2_def_param(const char *parameter, const char *value)`:: + + Emits a "def_param" message containing a key/value pair. ++ +This message is intended to report some global aspect of the current +command, such as a configuration setting or command line switch that +significantly affects program performance or behavior, such as +`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`. + +`void trace2_cmd_list_config()`:: + + Emits a "def_param" messages for "important" configuration + settings. ++ +The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a +list of patterns of important configuration settings, for example: +`core.*,remote.*.url`. This function will iterate over all config +settings and emit a "def_param" message for each match. + +`void trace2_cmd_set_config(const char *key, const char *value)`:: + + Emits a "def_param" message for a specific configuration + setting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern. ++ +This is used to hook into `git_config_set()` and catch any +configuration changes and update a value previously reported by +`trace2_cmd_list_config()`. + +`void trace2_def_repo(struct repository *repo)`:: + + Registers a repository with the Trace2 layer. Assigns a + unique "repo-id" to `repo->trace2_repo_id`. ++ +Emits a "worktree" messages containing the repo-id and the worktree +pathname. ++ +Region and data messages (described later) may refer to this repo-id. ++ +The main/top-level repository will have repo-id value 1 (aka "r1"). ++ +The repo-id field is in anticipation of future in-proc submodule +repositories. + +=== Child Process Messages + +These are concerned with the various spawned child processes, +including shell scripts, git commands, editors, pagers, and hooks. + +`void trace2_child_start(struct child_process *cmd)`:: + + Emits a "child_start" message containing the "child-id", + "child-argv", and "child-classification". ++ +Before calling this, set `cmd->trace2_child_class` to a name +describing the type of child process, for example "editor". ++ +This function assigns a unique "child-id" to `cmd->trace2_child_id`. +This field is used later during the "child_exit" message to associate +it with the "child_start" message. ++ +This function should be called before spawning the child process. + +`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`:: + + Emits a "child_exit" message containing the "child-id", + the child's elapsed time and exit-code. ++ +The reported elapsed time includes the process creation overhead and +time spend waiting for it to exit, so it may be slightly longer than +the time reported by the child itself. ++ +This function should be called after reaping the child process. + +`int trace2_exec(const char *exe, const char **argv)`:: + + Emits a "exec" message containing the "exec-id" and the + argv of the new process. ++ +This function should be called before calling one of the `exec()` +variants, such as `execvp()`. ++ +This function returns a unique "exec-id". This value is used later +if the exec() fails and a "exec-result" message is necessary. + +`void trace2_exec_result(int exec_id, int error_code)`:: + + Emits a "exec_result" message containing the "exec-id" + and the error code. ++ +On Unix-based systems, `exec()` does not return if successful. +This message is used to indicate that the `exec()` failed and +that the current program is continuing. + +=== Git Thread Messages + +These messages are concerned with Git thread usage. + +`void trace2_thread_start(const char *thread_name)`:: + + Emits a "thread_start" message. ++ +The `thread_name` field should be a descriptive name, such as the +unique name of the thread-proc. A unique "thread-id" will be added +to the name to uniquely identify thread instances. ++ +Region and data messages (described later) may refer to this thread +name. ++ +This function must be called by the thread-proc of the new thread +(so that TLS data is properly initialized) and not by the caller +of `pthread_create()`. + +`void trace2_thread_exit()`:: + + Emits a "thread_exit" message containing the thread name + and the thread elapsed time. ++ +This function must be called by the thread-proc before it returns +(so that the coorect TLS data is used and cleaned up. It should +not be called by the caller of `pthread_join()`. + +=== Region and Data Messages + +These are concerned with recording performance data +over regions or spans of code. + +`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`:: + +`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`:: + +`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`:: + + Emits a thread-relative "region_enter" message with optional + printf string. ++ +This function pushes a new region nesting stack level on the current +thread and starts a clock for the new stack frame. ++ +The `category` field is an arbitrary category name used to classify +regions by feature area, such as "status" or "index". At this time +it is only just printed along with the rest of the message. It may +be used in the future to filter messages. ++ +The `label` field is an arbitrary label used to describe the activity +being started, such as "read_recursive" or "do_read_index". ++ +The `repo` field, if set, will be used to get the "repo-id", so that +recursive oerations can be attributed to the correct repository. + +`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`:: + +`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`:: + +`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`:: + + Emits a thread-relative "region_leave" message with optional + printf string. ++ +This function pops the region nesting stack on the current thread +and reports the elapsed time of the stack frame. ++ +The `category`, `label`, and `repo` fields are the same as above. +The `category` and `label` do not need to match the correpsonding +"region_enter" message, but it makes the data stream easier to +understand. + +`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`:: + +`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`:: + +`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`:: + + Emits a region- and thread-relative "data" or "data_json" message. ++ +This is a key/value pair message containing information about the +current thread, region stack, and repository. This could be used +to print the number of files in a directory during a multi-threaded +recursive tree walk. + +`void trace2_printf(const char *fmt, ...)`:: + +`void trace2_printf_va(const char *fmt, va_list ap)`:: + + Emits a region- and thread-relative "printf" message. + +== Trace2 Target Formats + +=== NORMAL Format + +NORMAL format is enabled when the `GIT_TR2` environment variable is +set. + +Events are written as lines of the form: + +------------ +[