From 9bc454df08ca2a27b51ac0ab9ff8f154e51b8698 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 19 Jan 2010 05:25:57 +0100 Subject: [PATCH 0001/3211] reset: add option "--keep" to "git reset" The purpose of this new option is to discard some of the last commits but to keep current changes in the work tree. The use case is when you work on something and commit that work. And then you work on something else that touches other files, but you don't commit it yet. Then you realize that what you commited when you worked on the first thing is not good or belongs to another branch. So you want to get rid of the previous commits (at least in the current branch) but you want to make sure that you keep the changes you have in the work tree. And you are pretty sure that your changes are independent from what you previously commited, so you don't want the reset to succeed if the previous commits changed a file that you also changed in your work tree. The table below shows what happens when running "git reset --keep target" to reset the HEAD to another commit (as a special case "target" could be the same as HEAD). working index HEAD target working index HEAD ---------------------------------------------------- A B C D --keep (disallowed) A B C C --keep A C C B B C D --keep (disallowed) B B C C --keep B C C In this table, A, B and C are some different states of a file. For example the last line of the table means that if a file is in state B in the working tree and the index, and in a different state C in HEAD and in the target, then "git reset --keep target" will put the file in state B in the working tree, and in state C in the index and in HEAD. The following table shows what happens on unmerged entries: working index HEAD target working index HEAD ---------------------------------------------------- X U A B --keep (disallowed) X U A A --keep X A A In this table X can be any state and U means an unmerged entry. Though the error message when "reset --keep" is disallowed on unmerged entries is something like: error: Entry 'file1' would be overwritten by merge. Cannot merge. fatal: Could not reset index file to revision 'HEAD^'. which is not very nice. A following patch will add some test cases for "--keep". The "--keep" option is implemented by doing a 2 way merge between HEAD and the reset target, and if this succeeds by doing a mixed reset to the target. The code comes from the sequencer GSoC project, where such an option was developed by Stephan Beyer: git://repo.or.cz/git/sbeyer.git (at commit 5a78908b70ceb5a4ea9fd4b82f07ceba1f019079) But in the sequencer project the "reset" flag was set in the "struct unpack_trees_options" passed to "unpack_trees()". With this flag the changes in the working tree were discarded if the file was different between HEAD and the reset target. Mentored-by: Daniel Barkalow Mentored-by: Christian Couder Signed-off-by: Stephan Beyer Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- builtin-reset.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/builtin-reset.c b/builtin-reset.c index 0f5022eed2..da61f20e87 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -22,13 +22,15 @@ #include "cache-tree.h" static const char * const git_reset_usage[] = { - "git reset [--mixed | --soft | --hard | --merge] [-q] []", + "git reset [--mixed | --soft | --hard | --merge | --keep] [-q] []", "git reset [--mixed] [--] ...", NULL }; -enum reset_type { MIXED, SOFT, HARD, MERGE, NONE }; -static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL }; +enum reset_type { MIXED, SOFT, HARD, MERGE, KEEP, NONE }; +static const char *reset_type_names[] = { + "mixed", "soft", "hard", "merge", "keep", NULL +}; static char *args_to_str(const char **argv) { @@ -71,6 +73,7 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet if (!quiet) opts.verbose_update = 1; switch (reset_type) { + case KEEP: case MERGE: opts.update = 1; break; @@ -85,6 +88,16 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet read_cache_unmerged(); + if (reset_type == KEEP) { + unsigned char head_sha1[20]; + if (get_sha1("HEAD", head_sha1)) + return error("You do not have a valid HEAD."); + if (!fill_tree_descriptor(desc, head_sha1)) + return error("Failed to find tree of HEAD."); + nr++; + opts.fn = twoway_merge; + } + if (!fill_tree_descriptor(desc + nr - 1, sha1)) return error("Failed to find tree of %s.", sha1_to_hex(sha1)); if (unpack_trees(nr, desc, &opts)) @@ -229,6 +242,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", HARD), OPT_SET_INT(0, "merge", &reset_type, "reset HEAD, index and working tree", MERGE), + OPT_SET_INT(0, "keep", &reset_type, + "reset HEAD but keep local changes", KEEP), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -317,9 +332,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == SOFT) { if (is_merge() || read_cache() < 0 || unmerged_cache()) die("Cannot do a soft reset in the middle of a merge."); + } else { + int err = reset_index_file(sha1, reset_type, quiet); + if (reset_type == KEEP) + err = err || reset_index_file(sha1, MIXED, quiet); + if (err) + die("Could not reset index file to revision '%s'.", rev); } - else if (reset_index_file(sha1, reset_type, quiet)) - die("Could not reset index file to revision '%s'.", rev); /* Any resets update HEAD to the head being switched to, * saving the previous head in ORIG_HEAD before. */ From ffbc5dc2d0cbdbd63a4ae04dc2cc1ebf385fcc25 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 19 Jan 2010 05:25:58 +0100 Subject: [PATCH 0002/3211] reset: add test cases for "--keep" option This shows that with the "--keep" option, changes that are both in the work tree and the index are kept in the work tree after the reset (but discarded in the index). In the case of unmerged entries, we can see that "git reset --keep" works only when the target state is the same as HEAD. And then the work tree is not reset. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- t/t7110-reset-merge.sh | 119 ++++++++++++++++++++++++++++++++++++++++- t/t7111-reset-table.sh | 8 +++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh index 8704d00196..1a9c1c7494 100755 --- a/t/t7110-reset-merge.sh +++ b/t/t7110-reset-merge.sh @@ -3,7 +3,7 @@ # Copyright (c) 2009 Christian Couder # -test_description='Tests for "git reset --merge"' +test_description='Tests for "git reset" with "--merge" and "--keep" options' . ./test-lib.sh @@ -43,6 +43,30 @@ test_expect_success 'reset --merge is ok when switching back' ' test -z "$(git diff --cached)" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: C C C D --keep D D D +# file2: C D D D --keep C D D +test_expect_success 'reset --keep is ok with changes in file it does not touch' ' + git reset --hard second && + cat file1 >file2 && + git reset --keep HEAD^ && + ! grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --keep is ok when switching back' ' + git reset --keep second && + grep 4 file1 && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + # The next test will test the following: # # working index HEAD target working index HEAD @@ -74,6 +98,18 @@ test_expect_success 'reset --merge is ok again when switching back (1)' ' test -z "$(git diff --cached)" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: B B C D --keep (disallowed) +test_expect_success 'reset --keep fails with changes in index in files it touches' ' + git reset --hard second && + echo "line 5" >> file1 && + git add file1 && + test_must_fail git reset --keep HEAD^ +' + # The next test will test the following: # # working index HEAD target working index HEAD @@ -100,6 +136,30 @@ test_expect_success 'reset --merge is ok again when switching back (2)' ' test -z "$(git diff --cached)" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: C C C D --keep D D D +# file2: C C D D --keep C D D +test_expect_success 'reset --keep keeps changes it does not touch' ' + git reset --hard second && + echo "line 4" >> file2 && + git add file2 && + git reset --keep HEAD^ && + grep 4 file2 && + test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" && + test -z "$(git diff --cached)" +' + +test_expect_success 'reset --keep keeps changes when switching back' ' + git reset --keep second && + grep 4 file2 && + grep 4 file1 && + test "$(git rev-parse HEAD)" = "$(git rev-parse second)" && + test -z "$(git diff --cached)" +' + # The next test will test the following: # # working index HEAD target working index HEAD @@ -116,6 +176,22 @@ test_expect_success 'reset --merge fails with changes in file it touches' ' grep file1 err.log | grep "not uptodate" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: A B B C --keep (disallowed) +test_expect_success 'reset --keep fails with changes in file it touches' ' + git reset --hard second && + echo "line 5" >> file1 && + test_tick && + git commit -m "add line 5" file1 && + sed -e "s/line 1/changed line 1/" file3 && + mv file3 file1 && + test_must_fail git reset --keep HEAD^ 2>err.log && + grep file1 err.log | grep "not uptodate" +' + test_expect_success 'setup 3 different branches' ' git reset --hard second && git branch branch1 && @@ -152,6 +228,18 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' ' test -z "$(git diff)" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: X U B C --keep (disallowed) +test_expect_success '"reset --keep HEAD^" fails with pending merge' ' + git reset --hard third && + test_must_fail git merge branch1 && + test_must_fail git reset --keep HEAD^ 2>err.log && + grep file1 err.log | grep "overwritten by merge" +' + # The next test will test the following: # # working index HEAD target working index HEAD @@ -166,6 +254,21 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' ' test -z "$(git diff)" ' +# The next test will test the following: +# +# working index HEAD target working index HEAD +# ---------------------------------------------------- +# file1: X U B B --keep X B B +test_expect_success '"reset --keep HEAD" is ok with pending merge' ' + git reset --hard third && + test_must_fail git merge branch1 && + cat file1 >orig_file1 && + git reset --keep HEAD && + test "$(git rev-parse HEAD)" = "$(git rev-parse third)" && + test -z "$(git diff --cached)" && + test_cmp file1 orig_file1 +' + test_expect_success '--merge with added/deleted' ' git reset --hard third && rm -f file2 && @@ -180,4 +283,18 @@ test_expect_success '--merge with added/deleted' ' git diff --exit-code --cached ' +test_expect_success '--keep with added/deleted' ' + git reset --hard third && + rm -f file2 && + test_must_fail git merge branch3 && + ! test -f file2 && + test -f file3 && + git diff --exit-code file3 && + git diff --exit-code branch3 file3 && + git reset --keep HEAD && + test -f file3 && + ! test -f file2 && + git diff --exit-code --cached +' + test_done diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh index de896c948d..2ebda97828 100755 --- a/t/t7111-reset-table.sh +++ b/t/t7111-reset-table.sh @@ -44,26 +44,32 @@ A B C D soft A B D A B C D mixed A D D A B C D hard D D D A B C D merge XXXXX +A B C D keep XXXXX A B C C soft A B C A B C C mixed A C C A B C C hard C C C A B C C merge XXXXX +A B C C keep A C C B B C D soft B B D B B C D mixed B D D B B C D hard D D D B B C D merge D D D +B B C D keep XXXXX B B C C soft B B C B B C C mixed B C C B B C C hard C C C B B C C merge C C C +B B C C keep B C C B C C D soft B C D B C C D mixed B D D B C C D hard D D D B C C D merge XXXXX +B C C D keep XXXXX B C C C soft B C C B C C C mixed B C C B C C C hard C C C B C C C merge B C C +B C C C keep B C C EOF test_expect_success 'setting up branches to test with unmerged entries' ' @@ -104,10 +110,12 @@ X U B C soft XXXXX X U B C mixed X C C X U B C hard C C C X U B C merge C C C +X U B C keep XXXXX X U B B soft XXXXX X U B B mixed X B B X U B B hard B B B X U B B merge B B B +X U B B keep X B B EOF test_done From 80235ba79ef43349f455cce869397b3e726f4058 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Jan 2010 20:09:06 -0800 Subject: [PATCH 0003/3211] "log --author=me --grep=it" should find intersection, not union Historically, any grep filter in "git log" family of commands were taken as restricting to commits with any of the words in the commit log message. However, the user almost always want to find commits "done by this person on that topic". With "--all-match" option, a series of grep patterns can be turned into a requirement that all of them must produce a match, but that makes it impossible to ask for "done by me, on either this or that" with: log --author=me --committer=him --grep=this --grep=that because it will require both "this" and "that" to appear. Change the "header" parser of grep library to treat the headers specially, and parse it as: (all-match-OR (HEADER-AUTHOR me) (HEADER-COMMITTER him) (OR (PATTERN this) (PATTERN that) ) ) Even though the "log" command line parser doesn't give direct access to the extended grep syntax to group terms with parentheses, this change will cover the majority of the case the users would want. This incidentally revealed that one test in t7002 was bogus. It ran: log --author=Thor --grep=Thu --format='%s' and expected (wrongly) "Thu" to match "Thursday" in the author/committer date, but that would never match, as the timestamp in raw commit buffer does not have the name of the day-of-the-week. Signed-off-by: Junio C Hamano --- builtin-grep.c | 1 + builtin-rev-list.c | 5 +++-- grep.c | 44 +++++++++++++++++++++++++++++++++++++++----- grep.h | 2 ++ revision.c | 3 ++- t/t7002-grep.sh | 10 +++++++++- 6 files changed, 56 insertions(+), 9 deletions(-) diff --git a/builtin-grep.c b/builtin-grep.c index 529461fb8f..d57c4d9bed 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -820,6 +820,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.relative = 1; opt.pathname = 1; opt.pattern_tail = &opt.pattern_list; + opt.header_tail = &opt.header_list; opt.regflags = REG_NEWLINE; opt.max_depth = -1; diff --git a/builtin-rev-list.c b/builtin-rev-list.c index cd97ded4d2..c53ad9fa08 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -371,8 +371,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header || - revs.grep_filter.pattern_list; + save_commit_buffer = (revs.verbose_header || + revs.grep_filter.pattern_list || + revs.grep_filter.header_list); if (bisect_list) revs.limited = 1; diff --git a/grep.c b/grep.c index bdadf2c0cc..eee99f31f1 100644 --- a/grep.c +++ b/grep.c @@ -11,8 +11,8 @@ void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field fie p->no = 0; p->token = GREP_PATTERN_HEAD; p->field = field; - *opt->pattern_tail = p; - opt->pattern_tail = &p->next; + *opt->header_tail = p; + opt->header_tail = &p->next; p->next = NULL; } @@ -172,9 +172,26 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; + struct grep_expr *header_expr = NULL; - if (opt->all_match) - opt->extended = 1; + if (opt->header_list) { + p = opt->header_list; + header_expr = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); + for (p = opt->header_list; p; p = p->next) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + compile_regexp(p, opt); + break; + default: + opt->extended = 1; + break; + } + } + } for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { @@ -189,7 +206,9 @@ void compile_grep_patterns(struct grep_opt *opt) } } - if (!opt->extended) + if (opt->all_match || header_expr) + opt->extended = 1; + else if (!opt->extended) return; /* Then bundle them up in an expression. @@ -200,6 +219,21 @@ void compile_grep_patterns(struct grep_opt *opt) opt->pattern_expression = compile_pattern_expr(&p); if (p) die("incomplete pattern expression: %s", p->pattern); + + if (!header_expr) + return; + + if (opt->pattern_expression) { + struct grep_expr *z; + z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_OR; + z->u.binary.left = opt->pattern_expression; + z->u.binary.right = header_expr; + opt->pattern_expression = z; + } else { + opt->pattern_expression = header_expr; + } + opt->all_match = 1; } static void free_pattern_expr(struct grep_expr *x) diff --git a/grep.h b/grep.h index 75370f60d7..e39e5146d8 100644 --- a/grep.h +++ b/grep.h @@ -59,6 +59,8 @@ struct grep_expr { struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; + struct grep_pat *header_list; + struct grep_pat **header_tail; struct grep_expr *pattern_expression; const char *prefix; int prefix_length; diff --git a/revision.c b/revision.c index 25fa14d93e..c84243d7bb 100644 --- a/revision.c +++ b/revision.c @@ -806,6 +806,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->grep_filter.status_only = 1; revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list); + revs->grep_filter.header_tail = &(revs->grep_filter.header_list); revs->grep_filter.regflags = REG_NEWLINE; diff_setup(&revs->diffopt); @@ -1751,7 +1752,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { - if (!opt->grep_filter.pattern_list) + if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list) return 1; return grep_buffer(&opt->grep_filter, NULL, /* we say nothing, not even filename */ diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 76c5e091b7..6baa4461f7 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -357,7 +357,7 @@ test_expect_success 'log grep (4)' ' ' test_expect_success 'log grep (5)' ' - git log --author=Thor -F --grep=Thu --pretty=tformat:%s >actual && + git log --author=Thor -F --pretty=tformat:%s >actual && ( echo third ; echo initial ) >expect && test_cmp expect actual ' @@ -368,6 +368,14 @@ test_expect_success 'log grep (6)' ' test_cmp expect actual ' +test_expect_success 'log --grep --author implicitly uses all-match' ' + # grep matches initial and second but not third + # author matches only initial and third + git log --author="A U Thor" --grep=s --grep=l --format=%s >actual && + echo initial >expect && + test_cmp expect actual +' + test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && From 21528abc3612e3dae0c195b81848dd9aa18d2a02 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:44:47 -0600 Subject: [PATCH 0004/3211] Makefile: add missing header file dependencies LIB_H is missing exec_cmd.h and color.h. cache.h includes SHA1_HEADER, and thus so does almost everything else, so add that to LIB_H, too. xdiff-interface.h is not included by any header files, but so many source files use xdiff that it is simplest to include it in LIB_H, too. xdiff-interface.o uses the xdiff library heavily; let it depend on all xdiff headers to avoid needing to keep track of which headers it uses. Signed-off-by: Jonathan Nieder --- Makefile | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index fd7f51e8b9..bad8f804c3 100644 --- a/Makefile +++ b/Makefile @@ -447,6 +447,7 @@ LIB_H += blob.h LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h +LIB_H += color.h LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h @@ -457,6 +458,7 @@ LIB_H += delta.h LIB_H += diffcore.h LIB_H += diff.h LIB_H += dir.h +LIB_H += exec_cmd.h LIB_H += fsck.h LIB_H += git-compat-util.h LIB_H += graph.h @@ -499,6 +501,8 @@ LIB_H += unpack-trees.h LIB_H += userdiff.h LIB_H += utf8.h LIB_H += wt-status.h +LIB_H += xdiff-interface.h +LIB_H += xdiff/xdiff.h LIB_OBJS += abspath.o LIB_OBJS += advice.o @@ -1281,10 +1285,12 @@ endif ifdef BLK_SHA1 SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o + LIB_H += block-sha1/sha1.h else ifdef PPC_SHA1 SHA1_HEADER = "ppc/sha1.h" LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o + LIB_H += ppc/sha1.h else SHA1_HEADER = EXTLIBS += $(LIB_4_CRYPTO) @@ -1620,9 +1626,9 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o: http.h +http.o http-walker.o http-push.o remote-curl.o: http.h -http.o http-walker.o: $(LIB_H) +http.o http-walker.o remote-curl.o: $(LIB_H) git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ @@ -1637,14 +1643,25 @@ git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) +builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o: branch.h +builtin-bundle.o bundle.o transport.o: bundle.h +builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h +builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h +builtin-send-pack.o transport.o: send-pack.h +builtin-log.o builtin-shortlog.o: shortlog.h +builtin-prune.o builtin-reflog.o reachable.o: reachable.h builtin-revert.o wt-status.o: wt-status.h +builtin-tar-tree.o archive-tar.o: tar.h +builtin-pack-objects.o: thread-utils.h +http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o -$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ +xdiff-interface.o $(XDIFF_OBJS): \ + xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h $(XDIFF_LIB): $(XDIFF_OBJS) From daa99a917293d962ce565a04a09b0de08e32b8ba Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:45:54 -0600 Subject: [PATCH 0005/3211] Makefile: make sure test helpers are rebuilt when headers change It is not worth the bother to maintain an up-to-date list of which headers each test helper uses, so depend on $(LIB_H) to catch them all. Signed-off-by: Jonathan Nieder --- Makefile | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index bad8f804c3..1670ee87c3 100644 --- a/Makefile +++ b/Makefile @@ -402,6 +402,18 @@ PROGRAMS += git-upload-pack$X PROGRAMS += git-var$X PROGRAMS += git-http-backend$X +TEST_PROGRAMS += test-chmtime$X +TEST_PROGRAMS += test-ctype$X +TEST_PROGRAMS += test-date$X +TEST_PROGRAMS += test-delta$X +TEST_PROGRAMS += test-dump-cache-tree$X +TEST_PROGRAMS += test-genrandom$X +TEST_PROGRAMS += test-match-trees$X +TEST_PROGRAMS += test-parse-options$X +TEST_PROGRAMS += test-path-utils$X +TEST_PROGRAMS += test-sha1$X +TEST_PROGRAMS += test-sigchain$X + # List built-in command $C whose implementation cmd_$C() is not in # builtin-$C.o but is linked in as part of some other command. BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) @@ -695,6 +707,8 @@ BUILTIN_OBJS += builtin-verify-pack.o BUILTIN_OBJS += builtin-verify-tag.o BUILTIN_OBJS += builtin-write-tree.o +TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) + GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = @@ -1642,7 +1656,7 @@ git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) -$(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) +$(patsubst git-%$X,%.o,$(PROGRAMS)) $(TEST_OBJS) git.o: $(LIB_H) $(wildcard */*.h) builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o: branch.h builtin-bundle.o bundle.o transport.o: bundle.h builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h @@ -1732,18 +1746,6 @@ endif ### Testing rules -TEST_PROGRAMS += test-chmtime$X -TEST_PROGRAMS += test-ctype$X -TEST_PROGRAMS += test-date$X -TEST_PROGRAMS += test-delta$X -TEST_PROGRAMS += test-dump-cache-tree$X -TEST_PROGRAMS += test-genrandom$X -TEST_PROGRAMS += test-match-trees$X -TEST_PROGRAMS += test-parse-options$X -TEST_PROGRAMS += test-path-utils$X -TEST_PROGRAMS += test-sha1$X -TEST_PROGRAMS += test-sigchain$X - all:: $(TEST_PROGRAMS) # GNU make supports exporting all variables by "export" without parameters. @@ -1763,9 +1765,7 @@ test-delta$X: diff-delta.o patch-delta.o test-parse-options$X: parse-options.o -test-parse-options.o: parse-options.h - -.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) +.PRECIOUS: $(TEST_OBJS) test-%$X: test-%.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) From 7a1894e3039bc554e2a193c895b2ab5300b7f823 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:16 -0600 Subject: [PATCH 0006/3211] Makefile: remove wt-status.h from LIB_H A list of the few translation units using this header is half-populated already. Including the dependency on this header twice (once explicitly, once through LIB_H) makes it difficult to figure out where future headers should be added to the Makefile. Signed-off-by: Jonathan Nieder --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1670ee87c3..8cf6383a9d 100644 --- a/Makefile +++ b/Makefile @@ -512,7 +512,6 @@ LIB_H += tree-walk.h LIB_H += unpack-trees.h LIB_H += userdiff.h LIB_H += utf8.h -LIB_H += wt-status.h LIB_H += xdiff-interface.h LIB_H += xdiff/xdiff.h @@ -1664,7 +1663,7 @@ builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h builtin-send-pack.o transport.o: send-pack.h builtin-log.o builtin-shortlog.o: shortlog.h builtin-prune.o builtin-reflog.o reachable.o: reachable.h -builtin-revert.o wt-status.o: wt-status.h +builtin-commit.o builtin-revert.o wt-status.o: wt-status.h builtin-tar-tree.o archive-tar.o: tar.h builtin-pack-objects.o: thread-utils.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h From 066ddda6cdf2652a85430a979dbf1cd65fbfad0f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:37 -0600 Subject: [PATCH 0007/3211] Makefile: clean up http-walker.o dependency rules http-walker.o depends on http.h twice: once in the rule listing files that use http.h, and again in the rule explaining how to build it. Messy. Signed-off-by: Jonathan Nieder --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8cf6383a9d..593801ae1c 100644 --- a/Makefile +++ b/Makefile @@ -1628,7 +1628,7 @@ http.o: http.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $< ifdef NO_EXPAT -http-walker.o: http-walker.c http.h GIT-CFLAGS +http-walker.o: http-walker.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $< endif From 3e6577b45e755b53c9cccb24d75916fa3f2e1916 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:59 -0600 Subject: [PATCH 0008/3211] Makefile: drop dependency on $(wildcard */*.h) The files this pulls in are already pulled in by other dependency rules (some recently added). Signed-off-by: Jonathan Nieder --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 593801ae1c..98810b246b 100644 --- a/Makefile +++ b/Makefile @@ -1655,7 +1655,7 @@ git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) -$(patsubst git-%$X,%.o,$(PROGRAMS)) $(TEST_OBJS) git.o: $(LIB_H) $(wildcard */*.h) +$(patsubst git-%$X,%.o,$(PROGRAMS)) $(TEST_OBJS) git.o: $(LIB_H) builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o: branch.h builtin-bundle.o bundle.o transport.o: bundle.h builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h From 75df714487fd5d40b370f2d0f993f347d0170599 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:47:25 -0600 Subject: [PATCH 0009/3211] Makefile: transport.o depends on branch.h now Since commit e9fcd1e2 (Add push --set-upstream, 2010-01-16), transport.c uses branch.h. Signed-off-by: Jonathan Nieder --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6f0f64753d..5ebd5b2487 100644 --- a/Makefile +++ b/Makefile @@ -1719,7 +1719,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) $(TEST_OBJS) git.o: $(LIB_H) -builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o: branch.h +builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o transport.o: branch.h builtin-bundle.o bundle.o transport.o: bundle.h builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h From beeb4564bb7133fd12e6811a701686982b10cc2f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:49:33 -0600 Subject: [PATCH 0010/3211] Makefile: rearrange dependency rules Put rules listing dependencies of compiled objects (.o files) on header files (.h files) in one place, to make them easier to compare and modify all at once. Add a GIT_OBJS variable listing objects that depend on LIB_H, for similar reasons. No change in build-time behavior intended. Signed-off-by: Jonathan Nieder --- Makefile | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 5ebd5b2487..1811a9e74e 100644 --- a/Makefile +++ b/Makefile @@ -1666,6 +1666,12 @@ git.o git.spec \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ : GIT-VERSION-FILE +GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(TEST_OBJS) \ + git.o http.o http-walker.o remote-curl.o \ + $(patsubst git-%$X,%.o,$(PROGRAMS)) +XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o xdiff/xpatience.o + %.o: %.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< %.s: %.c GIT-CFLAGS FORCE @@ -1673,6 +1679,25 @@ git.o git.spec \ %.o: %.S GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< +$(GIT_OBJS): $(LIB_H) +builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o transport.o: branch.h +builtin-bundle.o bundle.o transport.o: bundle.h +builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h +builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h +builtin-send-pack.o transport.o: send-pack.h +builtin-log.o builtin-shortlog.o: shortlog.h +builtin-prune.o builtin-reflog.o reachable.o: reachable.h +builtin-commit.o builtin-revert.o wt-status.o: wt-status.h +builtin-tar-tree.o archive-tar.o: tar.h +builtin-pack-objects.o: thread-utils.h +http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h +http.o http-walker.o http-push.o remote-curl.o: http.h + + +xdiff-interface.o $(XDIFF_OBJS): \ + xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ + xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h + exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ '-DBINDIR="$(bindir_relative_SQ)"' \ @@ -1696,10 +1721,6 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o remote-curl.o: http.h - -http.o http-walker.o remote-curl.o: $(LIB_H) - git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) @@ -1717,29 +1738,9 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) -$(patsubst git-%$X,%.o,$(PROGRAMS)) $(TEST_OBJS) git.o: $(LIB_H) -builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o transport.o: branch.h -builtin-bundle.o bundle.o transport.o: bundle.h -builtin-bisect--helper.o builtin-rev-list.o bisect.o: bisect.h -builtin-clone.o builtin-fetch-pack.o transport.o: fetch-pack.h -builtin-send-pack.o transport.o: send-pack.h -builtin-log.o builtin-shortlog.o: shortlog.h -builtin-prune.o builtin-reflog.o reachable.o: reachable.h -builtin-commit.o builtin-revert.o wt-status.o: wt-status.h -builtin-tar-tree.o archive-tar.o: tar.h -builtin-pack-objects.o: thread-utils.h -http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h - $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ - xdiff/xmerge.o xdiff/xpatience.o -xdiff-interface.o $(XDIFF_OBJS): \ - xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ - xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h - $(XDIFF_LIB): $(XDIFF_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) From 30248886ce89e5467ce734908775ed90b9138e99 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:51:24 -0600 Subject: [PATCH 0011/3211] Makefile: disable default implicit rules The git makefile never uses any default implicit rules. Unfortunately, if a prerequisite for one of the intended rules is missing, a default rule can be used in its place: $ make var.s CC var.s $ rm var.c $ make var.o as -o var.o var.s Avoiding the default rules avoids this hard-to-debug behavior. It also should speed things up a little in the normal case. Future patches may restrict the scope of the %.o: %.c pattern. This patch would then ensure that for targets not listed, we do not fall back to the default rule. Signed-off-by: Jonathan Nieder --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 1811a9e74e..8d3299a8f0 100644 --- a/Makefile +++ b/Makefile @@ -1672,6 +1672,8 @@ GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(TEST_OBJS) \ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o +.SUFFIXES: + %.o: %.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< %.s: %.c GIT-CFLAGS FORCE From c373991375a4903dbb9bea69e2ce11ce819253e2 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:52:11 -0600 Subject: [PATCH 0012/3211] Makefile: list generated object files in OBJECTS Set the OBJECTS variable to a comprehensive list of all object file targets. To make sure it is truly comprehensive, restrict the scope of the %.o pattern rule to only generate objects in this list. Attempts to build other object files will fail loudly: $ touch foo.c $ make foo.o make: *** No rule to make target `foo.o'. Stop. providing a reminder to add the new object to the OBJECTS list. The new variable is otherwise unused. The intent is for later patches to take advantage of it. Signed-off-by: Jonathan Nieder --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8d3299a8f0..a3774f7172 100644 --- a/Makefile +++ b/Makefile @@ -1671,14 +1671,19 @@ GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(TEST_OBJS) \ $(patsubst git-%$X,%.o,$(PROGRAMS)) XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o +OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) + +ASM_SRC := $(wildcard $(OBJECTS:o=S)) +ASM_OBJ := $(ASM_SRC:S=o) +C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) .SUFFIXES: -%.o: %.c GIT-CFLAGS +$(C_OBJ): %.o: %.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< %.s: %.c GIT-CFLAGS FORCE $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< -%.o: %.S GIT-CFLAGS +$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< $(GIT_OBJS): $(LIB_H) From dfea575017ddc2ae7c9e8dcb978f4557f07715d3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:52:49 -0600 Subject: [PATCH 0013/3211] Makefile: lazily compute header dependencies Use the gcc -MMD -MP -MF options to generate dependency rules as a byproduct when building .o files if the COMPUTE_HEADER_DEPENDENCIES variable is defined. That variable is left undefined by default for now. As each object file is built, write a makefile fragment containing its dependencies in the deps/ subdirectory of its containing directory. The deps/ directories should be generated if they are missing at the start of each build. So let each object file depend on $(missing_dep_dirs), which lists only the directories of this kind that are missing to avoid needlessly regenerating files when the directories' timestamps change. gcc learned the -MMD -MP -MF options in version 3.0, so most gcc users should have them by now. The dependencies this option computes are more specific than the rough estimates hard-coded in the Makefile, greatly speeding up rebuilds when only a little-used header file has changed. Signed-off-by: Jonathan Nieder --- .gitignore | 1 + Makefile | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8df8f88bea..7b3acb7664 100644 --- a/.gitignore +++ b/.gitignore @@ -177,6 +177,7 @@ *.exe *.[aos] *.py[co] +*.o.d *+ /config.mak /autom4te.cache diff --git a/Makefile b/Makefile index a3774f7172..df361737b0 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,10 @@ all:: # DEFAULT_EDITOR='~/bin/vi', # DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR', # DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork' +# +# Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option +# and you want to avoid rebuilding objects when an unrelated header file +# changes. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -1677,14 +1681,48 @@ ASM_SRC := $(wildcard $(OBJECTS:o=S)) ASM_OBJ := $(ASM_SRC:S=o) C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) +ifdef COMPUTE_HEADER_DEPENDENCIES +dep_dirs := $(addsuffix deps,$(sort $(dir $(OBJECTS)))) +$(dep_dirs): + mkdir -p $@ + +missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) +else +dep_dirs = +missing_dep_dirs = +endif + .SUFFIXES: -$(C_OBJ): %.o: %.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< +$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< %.s: %.c GIT-CFLAGS FORCE $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< -$(ASM_OBJ): %.o: %.S GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< +$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< + +ifdef COMPUTE_HEADER_DEPENDENCIES +# Take advantage of gcc's on-the-fly dependency generation +# See . +dep_files := $(wildcard $(foreach f,$(OBJECTS),$(dir f)deps/$(notdir $f).d)) +ifneq ($(dep_files),) +include $(dep_files) +endif + +dep_file = $(dir $@)deps/$(notdir $@).d +dep_args = -MF $(dep_file) -MMD -MP +else +dep_args = + +# Dependencies on header files, for platforms that do not support +# the gcc -MMD option. +# +# Dependencies on automatically generated headers such as common-cmds.h +# should _not_ be included here, since they are necessary even when +# building an object for the first time. +# +# XXX. Please check occasionally that these include all dependencies +# gcc detects! $(GIT_OBJS): $(LIB_H) builtin-branch.o builtin-checkout.o builtin-clone.o builtin-reset.o branch.o transport.o: branch.h @@ -1700,10 +1738,10 @@ builtin-pack-objects.o: thread-utils.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o remote-curl.o: http.h - xdiff-interface.o $(XDIFF_OBJS): \ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h +endif exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ @@ -2011,6 +2049,7 @@ clean: $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers + $(RM) -r $(dep_dirs) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache From 1b22c99c14b30f0add6f108a5883f003a2d81e01 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:54:23 -0600 Subject: [PATCH 0014/3211] Makefile: list standalone program object files in PROGRAM_OBJS Because of new commands like git-remote-http, the OBJECTS list contains fictitious objects such as remote-http.o. Thus any out-of-tree rules that require all $(OBJECTS) to be buildable are broken. Add a list of real program objects to avoid this problem. To avoid duplication of effort, calculate the command list in the PROGRAMS variable using the expansion of PROGRAM_OBJS. This calculation occurs at the time $(PROGRAMS) is expanded, so later additions to PROGRAM_OBJS will be reflected in it, provided they occur before the build rules begin on line 1489. Signed-off-by: Jonathan Nieder --- Makefile | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index df361737b0..630687f26e 100644 --- a/Makefile +++ b/Makefile @@ -341,6 +341,7 @@ COMPAT_CFLAGS = COMPAT_OBJS = LIB_H = LIB_OBJS = +PROGRAM_OBJS = PROGRAMS = SCRIPT_PERL = SCRIPT_PYTHON = @@ -390,12 +391,15 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) -PROGRAMS += git-fast-import$X -PROGRAMS += git-imap-send$X -PROGRAMS += git-shell$X -PROGRAMS += git-show-index$X -PROGRAMS += git-upload-pack$X -PROGRAMS += git-http-backend$X + +PROGRAM_OBJS += fast-import.o +PROGRAM_OBJS += imap-send.o +PROGRAM_OBJS += shell.o +PROGRAM_OBJS += show-index.o +PROGRAM_OBJS += upload-pack.o +PROGRAM_OBJS += http-backend.o + +PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) TEST_PROGRAMS_NEED_X += test-chmtime TEST_PROGRAMS_NEED_X += test-ctype @@ -1139,11 +1143,12 @@ else REMOTE_CURL_PRIMARY = git-remote-http$X REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) - PROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X + PROGRAM_OBJS += http-fetch.o + PROGRAMS += $(REMOTE_CURL_NAMES) curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT - PROGRAMS += git-http-push$X + PROGRAM_OBJS += http-push.o endif endif ifndef NO_EXPAT @@ -1163,7 +1168,7 @@ endif EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS - PROGRAMS += git-daemon$X + PROGRAM_OBJS += daemon.o endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl @@ -1670,9 +1675,8 @@ git.o git.spec \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ : GIT-VERSION-FILE -GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(TEST_OBJS) \ - git.o http.o http-walker.o remote-curl.o \ - $(patsubst git-%$X,%.o,$(PROGRAMS)) +GIT_OBJS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ + git.o http.o http-walker.o remote-curl.o XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) From f2fabbf76e458010852ac7ced30594c8ab96b9fd Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:57:15 -0600 Subject: [PATCH 0015/3211] Teach Makefile to check header dependencies Add a target to use the gcc-generated makefile snippets for dependencies on header files to check the hard-coded dependencies. With this patch applied, if any dependencies are missing, then make clean make COMPUTE_HEADER_DEPENDENCIES=YesPlease make CHECK_HEADER_DEPENDENCIES=YesPlease will produce an error message like the following: CHECK fast-import.o missing dependencies: exec_cmd.h make: *** [fast-import.o] Error 1 Signed-off-by: Jonathan Nieder --- Makefile | 105 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 630687f26e..45b1f54a63 100644 --- a/Makefile +++ b/Makefile @@ -221,6 +221,9 @@ all:: # Define COMPUTE_HEADER_DEPENDENCIES if your compiler supports the -MMD option # and you want to avoid rebuilding objects when an unrelated header file # changes. +# +# Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded +# dependency rules. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -1088,6 +1091,14 @@ endif -include config.mak.autogen -include config.mak +ifdef CHECK_HEADER_DEPENDENCIES +USE_COMPUTED_HEADER_DEPENDENCIES = +endif + +ifdef COMPUTE_HEADER_DEPENDENCIES +USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease +endif + ifdef SANE_TOOL_PATH SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH)) BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix $(SANE_TOOL_PATH_SQ)|' @@ -1681,9 +1692,7 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) -ASM_SRC := $(wildcard $(OBJECTS:o=S)) -ASM_OBJ := $(ASM_SRC:S=o) -C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) +dep_files := $(foreach f,$(OBJECTS),$(dir $f)deps/$(notdir $f).d) ifdef COMPUTE_HEADER_DEPENDENCIES dep_dirs := $(addsuffix deps,$(sort $(dir $(OBJECTS)))) @@ -1691,33 +1700,89 @@ $(dep_dirs): mkdir -p $@ missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) -else +dep_file = $(dir $@)deps/$(notdir $@).d +dep_args = -MF $(dep_file) -MMD -MP +ifdef CHECK_HEADER_DEPENDENCIES +$(error cannot compute header dependencies outside a normal build. \ +Please unset CHECK_HEADER_DEPENDENCIES and try again) +endif +endif + +ifndef COMPUTE_HEADER_DEPENDENCIES +ifndef CHECK_HEADER_DEPENDENCIES dep_dirs = missing_dep_dirs = +dep_args = endif +endif + +ifdef CHECK_HEADER_DEPENDENCIES +ifndef PRINT_HEADER_DEPENDENCIES +missing_deps = $(filter-out $(notdir $^), \ + $(notdir $(shell $(MAKE) -s $@ \ + CHECK_HEADER_DEPENDENCIES=YesPlease \ + USE_COMPUTED_HEADER_DEPENDENCIES=YesPlease \ + PRINT_HEADER_DEPENDENCIES=YesPlease))) +endif +endif + +ASM_SRC := $(wildcard $(OBJECTS:o=S)) +ASM_OBJ := $(ASM_SRC:S=o) +C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) .SUFFIXES: -$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) - $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< -%.s: %.c GIT-CFLAGS FORCE - $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< -$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) - $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< +ifdef PRINT_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c FORCE + echo $^ +$(ASM_OBJ): %.o: %.S FORCE + echo $^ -ifdef COMPUTE_HEADER_DEPENDENCIES -# Take advantage of gcc's on-the-fly dependency generation -# See . -dep_files := $(wildcard $(foreach f,$(OBJECTS),$(dir f)deps/$(notdir $f).d)) -ifneq ($(dep_files),) -include $(dep_files) +ifndef CHECK_HEADER_DEPENDENCIES +$(error cannot print header dependencies during a normal build. \ +Please set CHECK_HEADER_DEPENDENCIES and try again) +endif endif -dep_file = $(dir $@)deps/$(notdir $@).d -dep_args = -MF $(dep_file) -MMD -MP -else -dep_args = +ifndef PRINT_HEADER_DEPENDENCIES +ifdef CHECK_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c $(dep_files) FORCE + @set -e; echo CHECK $@; \ + missing_deps="$(missing_deps)"; \ + if test "$$missing_deps"; \ + then \ + echo missing dependencies: $$missing_deps; \ + false; \ + fi +$(ASM_OBJ): %.o: %.S $(dep_files) FORCE + @set -e; echo CHECK $@; \ + missing_deps="$(missing_deps)"; \ + if test "$$missing_deps"; \ + then \ + echo missing dependencies: $$missing_deps; \ + false; \ + fi +endif +endif +ifndef CHECK_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< +$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $< +endif + +%.s: %.c GIT-CFLAGS FORCE + $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $< + +ifdef USE_COMPUTED_HEADER_DEPENDENCIES +# Take advantage of gcc's on-the-fly dependency generation +# See . +dep_files_present := $(wildcard $(dep_files)) +ifneq ($(dep_files_present),) +include $(dep_files_present) +endif +else # Dependencies on header files, for platforms that do not support # the gcc -MMD option. # From 13be3e31f1775bd496fc5bc1ab5b745052f4d07d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Jan 2010 22:03:24 -0800 Subject: [PATCH 0016/3211] Reword "detached HEAD" notification The old "advice" message explained how to create a branch after going into a detached HEAD state but didn't make it clear why the user may want to do so. Also "moving to ... which isn't a local branch" was unclear if it is complaining, if it is describing the new state, or if it is explaining why the HEAD is detached (the true reason is the last one). Give the established phrase 'detached HEAD' first to make it easy for users to look up the concept in documentation, and briefly describe what can be done in the state (i.e. play around without having to clean up) before telling the user how to keep what was done during the temporary state. Allow the long description to be hidden by setting advice.detachedHead configuration to false. We might want to customize the advice depending on how the commit to check out was spelled (e.g. instead of "new-branch-name", we way want to say "topic" when "git checkout origin/topic" triggered this message) in later updates, but this encapsulates that into a separate function and it should be a good first step. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 5 +++++ advice.c | 2 ++ advice.h | 1 + builtin-checkout.c | 18 ++++++++++++++++-- t/t7201-co.sh | 32 ++++++++++++++++++++++---------- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 17901e244a..fee44d8a4a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -138,6 +138,11 @@ advice.*:: Advice on how to set your identity configuration when your information is guessed from the system username and domain name. Default: true. + + detachedHead:: + Advice shown when you used linkgit::git-checkout[1] to + move to the detach HEAD state, to instruct how to create + a local branch after the fact. Default: true. -- core.fileMode:: diff --git a/advice.c b/advice.c index 936d98ba2b..0be4b5f008 100644 --- a/advice.c +++ b/advice.c @@ -5,6 +5,7 @@ int advice_status_hints = 1; int advice_commit_before_merge = 1; int advice_resolve_conflict = 1; int advice_implicit_identity = 1; +int advice_detached_head = 1; static struct { const char *name; @@ -15,6 +16,7 @@ static struct { { "commitbeforemerge", &advice_commit_before_merge }, { "resolveconflict", &advice_resolve_conflict }, { "implicitidentity", &advice_implicit_identity }, + { "detachedhead", &advice_detached_head }, }; int git_default_advice_config(const char *var, const char *value) diff --git a/advice.h b/advice.h index 9b7a3ad1ca..3244ebb5c1 100644 --- a/advice.h +++ b/advice.h @@ -8,6 +8,7 @@ extern int advice_status_hints; extern int advice_commit_before_merge; extern int advice_resolve_conflict; extern int advice_implicit_identity; +extern int advice_detached_head; int git_default_advice_config(const char *var, const char *value); diff --git a/builtin-checkout.c b/builtin-checkout.c index 527781728e..c5ab7835e1 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -488,6 +488,20 @@ static void report_tracking(struct branch_info *new) strbuf_release(&sb); } +static void detach_advice(const char *old_path, const char *new_name) +{ + const char fmt[] = + "Note: checking out '%s'.\n\n" + "You are in 'detached HEAD' state. You can look around, make experimental\n" + "changes and commit them, and you can discard any commits you make in this\n" + "state without impacting any branches by performing another checkout.\n\n" + "If you want to create a new branch to retain commits you create, you may\n" + "do so (now or later) by using -b with the checkout command again. Example:\n\n" + " git checkout -b new_branch_name\n\n"; + + fprintf(stderr, fmt, new_name); +} + static void update_refs_for_switch(struct checkout_opts *opts, struct branch_info *old, struct branch_info *new) @@ -522,8 +536,8 @@ static void update_refs_for_switch(struct checkout_opts *opts, update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, REF_NODEREF, DIE_ON_ERR); if (!opts->quiet) { - if (old->path) - fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b \n", new->name); + if (old->path && advice_detached_head) + detach_advice(old->path, new->name); describe_detached_head("HEAD is now at", new->commit); } } diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 6442f710be..d20ed61b48 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -166,19 +166,31 @@ test_expect_success 'checkout -m with merge conflict' ' ! test -s current ' -test_expect_success 'checkout to detach HEAD' ' +test_expect_success 'checkout to detach HEAD (with advice declined)' ' + git config advice.detachedHead false && git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && - (cat >messages.expect < -HEAD is now at 7329388... Initial A one, A two -EOF -) && - test_cmp messages.expect messages && + grep "HEAD is now at 7329388" messages && + test 1 -eq $(wc -l /dev/null 2>&1 + then + echo "OOPS, HEAD is still symbolic???" + false + else + : happy + fi +' + +test_expect_success 'checkout to detach HEAD' ' + git config advice.detachedHead true && + git checkout -f renamer && git clean -f && + git checkout renamer^ 2>messages && + grep "HEAD is now at 7329388" messages && + test 1 -lt $(wc -l Date: Fri, 29 Jan 2010 15:17:59 +0100 Subject: [PATCH 0017/3211] request-pull: avoid mentioning that the start point is a single commit Previously we ran shortlog on the start commit which always printed "(1)" after the start commit, which gives no information, but makes the output less easy to read. Instead of giving the author name of the commit, use the space for committer timestamp to help recipient judge the freshness of the offered branch more easily. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- git-request-pull.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-request-pull.sh b/git-request-pull.sh index 630ceddf03..8fd15f6df4 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -65,11 +65,11 @@ if [ -z "$branch" ]; then status=1 fi -echo "The following changes since commit $baserev:" -git shortlog --max-count=1 $baserev | sed -e 's/^\(.\)/ \1/' +git show -s --format='The following changes since commit %H: -echo "are available in the git repository at:" -echo + %s (%ci) + +are available in the git repository at:' $baserev echo " $url $branch" echo From b9b727ddb3c9e005bc4e9af0b990b6ef06d7f621 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Jan 2010 15:59:09 -0800 Subject: [PATCH 0018/3211] t6000lib: Fix permission 4848509 (Fix permissions on test scripts, 2007-04-13) forgot to make this included file non-executable. Signed-off-by: Junio C Hamano --- t/t6000lib.sh | 2 ++ 1 file changed, 2 insertions(+) mode change 100755 => 100644 t/t6000lib.sh diff --git a/t/t6000lib.sh b/t/t6000lib.sh old mode 100755 new mode 100644 index d40262159b..4f72a3d890 --- a/t/t6000lib.sh +++ b/t/t6000lib.sh @@ -1,3 +1,5 @@ +: included from 6002 and others + [ -d .git/refs/tags ] || mkdir -p .git/refs/tags :> sed.script From c32056e0ef193002f80d75fd795e156ddf65c4ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Jan 2010 16:04:25 -0800 Subject: [PATCH 0019/3211] lib-patch-mode.sh: Fix permission In the same sprit as 4848509 (Fix permissions on test scripts, 2007-04-13), t/lib-patch-mode.sh should not be executable. Signed-off-by: Junio C Hamano --- t/lib-patch-mode.sh | 2 ++ 1 file changed, 2 insertions(+) mode change 100755 => 100644 t/lib-patch-mode.sh diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh old mode 100755 new mode 100644 index afb4b6686c..06c3c91762 --- a/t/lib-patch-mode.sh +++ b/t/lib-patch-mode.sh @@ -1,3 +1,5 @@ +: included from t2016 and others + . ./test-lib.sh set_state () { From 46bac904581798eaf0f07f91701c2c72219e207f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 11:46:53 -0800 Subject: [PATCH 0020/3211] Do not install shell libraries executable Some scripts are expected to be sourced instead of executed on their own. Avoid some confusion by not marking them executable. The executable bit was confusing the valgrind support of our test scripts, which assumed that any executable without a #!-line should be intercepted and run through valgrind. So during valgrind-enabled tests, any script sourcing these files actually sourced the valgrind interception script instead. Reported-by: Jeff King Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Makefile | 42 ++++++++++++++++++++++++++++-------------- git-parse-remote.sh | 0 git-sh-setup.sh | 0 3 files changed, 28 insertions(+), 14 deletions(-) mode change 100755 => 100644 git-parse-remote.sh mode change 100755 => 100644 git-sh-setup.sh diff --git a/Makefile b/Makefile index af08c8f452..6bbeb2401f 100644 --- a/Makefile +++ b/Makefile @@ -341,6 +341,7 @@ PROGRAMS = SCRIPT_PERL = SCRIPT_PYTHON = SCRIPT_SH = +SCRIPT_LIB = TEST_PROGRAMS = SCRIPT_SH += git-am.sh @@ -352,20 +353,21 @@ SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh -SCRIPT_SH += git-mergetool--lib.sh SCRIPT_SH += git-notes.sh -SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-repack.sh SCRIPT_SH += git-request-pull.sh -SCRIPT_SH += git-sh-setup.sh SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh +SCRIPT_LIB += git-mergetool--lib +SCRIPT_LIB += git-parse-remote +SCRIPT_LIB += git-sh-setup + SCRIPT_PERL += git-add--interactive.perl SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl @@ -1454,7 +1456,7 @@ export TAR INSTALL DESTDIR SHELL_PATH SHELL = $(SHELL_PATH) -all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS +all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif @@ -1505,17 +1507,25 @@ common-cmds.h: ./generate-cmdlist.sh command-list.txt common-cmds.h: $(wildcard Documentation/git-*.txt) $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@ +define cmd_munge_script +$(RM) $@ $@+ && \ +sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ + -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ + -e $(BROKEN_PATH_FIX) \ + $@.sh >$@+ +endef + $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh - $(QUIET_GEN)$(RM) $@ $@+ && \ - sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ - -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e $(BROKEN_PATH_FIX) \ - $@.sh >$@+ && \ + $(QUIET_GEN)$(cmd_munge_script) && \ chmod +x $@+ && \ mv $@+ $@ +$(SCRIPT_LIB) : % : %.sh + $(QUIET_GEN)$(cmd_munge_script) && \ + mv $@+ $@ + ifndef NO_PERL $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak @@ -1866,6 +1876,7 @@ install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' + $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install ifndef NO_PERL @@ -1985,7 +1996,7 @@ distclean: clean clean: $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) - $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X + $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* @@ -2017,7 +2028,7 @@ endif ### Check documentation # check-docs:: - @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \ + @(for v in $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk; \ do \ case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ @@ -2060,9 +2071,12 @@ check-docs:: documented,gitrepository-layout | \ documented,gittutorial | \ documented,gittutorial-2 | \ + documented,git-bisect-lk2009 | \ + documented.git-remote-helpers | \ + documented,gitworkflows | \ sentinel,not,matching,is,ok ) continue ;; \ esac; \ - case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \ + case " $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git gitk " in \ *" $$cmd "*) ;; \ *) echo "removed but $$how: $$cmd" ;; \ esac; \ diff --git a/git-parse-remote.sh b/git-parse-remote.sh old mode 100755 new mode 100644 diff --git a/git-sh-setup.sh b/git-sh-setup.sh old mode 100755 new mode 100644 From ec5e0bb860bf98008d51b8235d19ed3407141295 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 15:23:53 -0600 Subject: [PATCH 0021/3211] Makefile: tuck away generated makefile fragments in .depend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When building with COMPUTE_HEADER_DEPENDENCIES on, save dependency information to .depend/ instead of deps/ so it does not show up in ‘ls’ output. Otherwise, the extra directories can be distracting. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 45b1f54a63..c9391390b9 100644 --- a/Makefile +++ b/Makefile @@ -1692,15 +1692,15 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) -dep_files := $(foreach f,$(OBJECTS),$(dir $f)deps/$(notdir $f).d) +dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) ifdef COMPUTE_HEADER_DEPENDENCIES -dep_dirs := $(addsuffix deps,$(sort $(dir $(OBJECTS)))) +dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) $(dep_dirs): mkdir -p $@ missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) -dep_file = $(dir $@)deps/$(notdir $@).d +dep_file = $(dir $@).depend/$(notdir $@).d dep_args = -MF $(dep_file) -MMD -MP ifdef CHECK_HEADER_DEPENDENCIES $(error cannot compute header dependencies outside a normal build. \ From 010acc151981ebfb0e369ffdeaef63803c58ef2b Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 15:37:25 -0600 Subject: [PATCH 0022/3211] Makefile: always remove .depend directories on 'make clean' Even if COMPUTE_HEADER_DEPENDENCIES is not set, some .o.d files might be lying around from previous builds when it was. This is especially likely because using the CHECK_HEADER_DEPENDENCIES feature requires building sometimes with COMPUTE... on and sometimes with it off. At the end of such an exercise, to get a blank slate, the user ought to be able to just run 'make clean'. Make it so. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c9391390b9..93e1a9218e 100644 --- a/Makefile +++ b/Makefile @@ -1693,9 +1693,9 @@ XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) +dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) ifdef COMPUTE_HEADER_DEPENDENCIES -dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) $(dep_dirs): mkdir -p $@ From 9517e6b84357252e1882091343661c34d978771e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 3 Feb 2010 21:23:18 -0800 Subject: [PATCH 0023/3211] Typofixes outside documentation area begining -> beginning canonicalizations -> canonicalization comand -> command dewrapping -> unwrapping dirtyness -> dirtiness DISCLAMER -> DISCLAIMER explicitely -> explicitly feeded -> fed impiled -> implied madatory -> mandatory mimick -> mimic preceeding -> preceding reqeuest -> request substition -> substitution Signed-off-by: Junio C Hamano --- builtin-apply.c | 2 +- builtin-cat-file.c | 5 +++-- builtin-log.c | 2 +- builtin-prune.c | 2 +- builtin-show-branch.c | 2 +- compat/win32/pthread.c | 2 +- connect.c | 2 +- contrib/fast-import/import-directories.perl | 2 +- daemon.c | 2 +- diff.c | 2 +- levenshtein.h | 2 +- path.c | 2 +- perl/Git.pm | 4 ++-- refs.c | 2 +- setup.c | 2 +- test-chmtime.c | 2 +- transport-helper.c | 2 +- 17 files changed, 20 insertions(+), 19 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 2a1004d025..3af4ae0c26 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2006,7 +2006,7 @@ static int find_pos(struct image *img, return -1; /* - * If match_begining or match_end is specified, there is no + * If match_beginning or match_end is specified, there is no * point starting from a wrong line that will never match and * wander around and wait for a match at the specified end. */ diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 5906842008..a933eaa043 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -219,9 +219,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) "exit with zero when there's no error", 'e'), OPT_SET_INT('p', NULL, &opt, "pretty-print object's content", 'p'), OPT_SET_INT(0, "batch", &batch, - "show info and content of objects feeded on stdin", BATCH), + "show info and content of objects fed from the standard input", + BATCH), OPT_SET_INT(0, "batch-check", &batch, - "show info about objects feeded on stdin", + "show info about objects fed from the standard input", BATCH_CHECK), OPT_END() }; diff --git a/builtin-log.c b/builtin-log.c index 8d16832f7e..e0d5caa61b 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -1089,7 +1089,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* * We cannot move this anywhere earlier because we do want to - * know if --root was given explicitly from the comand line. + * know if --root was given explicitly from the command line. */ rev.show_root_diff = 1; diff --git a/builtin-prune.c b/builtin-prune.c index 8459aec8e8..4675f6054f 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -106,7 +106,7 @@ static void prune_object_dir(const char *path) /* * Write errors (particularly out of space) can result in * failed temporary packs (and more rarely indexes and other - * files begining with "tmp_") accumulating in the object + * files beginning with "tmp_") accumulating in the object * and the pack directories. */ static void remove_temporary_files(const char *path) diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 9f13caa76d..35a709e630 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -567,7 +567,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); /* * default_arg is now passed to parse_options(), so we need to - * mimick the real argv a bit better. + * mimic the real argv a bit better. */ if (!default_num) { default_alloc = 20; diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 5fc1670bee..0f949fc425 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2009 Andrzej K. Haczewski * - * DISCLAMER: The implementation is Git-specific, it is subset of original + * DISCLAIMER: The implementation is Git-specific, it is subset of original * Pthreads API, without lots of other features that Git doesn't use. * Git also makes sure that the passed arguments are valid, so there's * no need for double-checking. diff --git a/connect.c b/connect.c index 20054e4d0f..a37cf6af04 100644 --- a/connect.c +++ b/connect.c @@ -504,7 +504,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, /* * Don't do destructive transforms with git:// as that - * protocol code does '[]' dewrapping of its own. + * protocol code does '[]' unwrapping of its own. */ if (host[0] == '[') { end = strchr(host + 1, ']'); diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl index 5782d80e26..3a5da4ab00 100755 --- a/contrib/fast-import/import-directories.perl +++ b/contrib/fast-import/import-directories.perl @@ -344,7 +344,7 @@ sub parsekeyvaluepair Key and value strings may be enclosed in quotes, in which case whitespace inside the quotes is preserved. Additionally, an equal -sign may be included in the key by preceeding it with a backslash. +sign may be included in the key by preceding it with a backslash. For example: "key1 "=value1 diff --git a/daemon.c b/daemon.c index 6c2bd97713..3769b6f570 100644 --- a/daemon.c +++ b/daemon.c @@ -407,7 +407,7 @@ static void parse_host_and_port(char *hostport, char **host, end = strchr(hostport, ']'); if (!end) - die("Invalid reqeuest ('[' without ']')"); + die("Invalid request ('[' without ']')"); *end = '\0'; *host = hostport + 1; if (!end[1]) diff --git a/diff.c b/diff.c index 381cc8d4fd..9038057a76 100644 --- a/diff.c +++ b/diff.c @@ -3642,7 +3642,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) struct diff_filepair *p = q->queue[i]; /* - * 1. Entries that come from stat info dirtyness + * 1. Entries that come from stat info dirtiness * always have both sides (iow, not create/delete), * one side of the object name is unknown, with * the same mode and size. Keep the ones that diff --git a/levenshtein.h b/levenshtein.h index 0173abeef5..4105bf3549 100644 --- a/levenshtein.h +++ b/levenshtein.h @@ -2,7 +2,7 @@ #define LEVENSHTEIN_H int levenshtein(const char *string1, const char *string2, - int swap_penalty, int substition_penalty, + int swap_penalty, int substitution_penalty, int insertion_penalty, int deletion_penalty); #endif diff --git a/path.c b/path.c index 79aa104712..e166d5380e 100644 --- a/path.c +++ b/path.c @@ -610,7 +610,7 @@ int daemon_avoid_alias(const char *p) /* * This resurrects the belts and suspenders paranoia check by HPA * done in <435560F7.4080006@zytor.com> thread, now enter_repo() - * does not do getcwd() based path canonicalizations. + * does not do getcwd() based path canonicalization. * * sl becomes true immediately after seeing '/' and continues to * be true as long as dots continue after that without intervening diff --git a/perl/Git.pm b/perl/Git.pm index e8df55d2f2..970fe434ed 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -204,14 +204,14 @@ sub repository { $dir = $opts{Directory}; unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") { - # Mimick git-rev-parse --git-dir error message: + # Mimic git-rev-parse --git-dir error message: throw Error::Simple("fatal: Not a git repository: $dir"); } my $search = Git->repository(Repository => $dir); try { $search->command('symbolic-ref', 'HEAD'); } catch Git::Error::Command with { - # Mimick git-rev-parse --git-dir error message: + # Mimic git-rev-parse --git-dir error message: throw Error::Simple("fatal: Not a git repository: $dir"); } diff --git a/refs.c b/refs.c index 503a8c2bd0..f3fcbe023a 100644 --- a/refs.c +++ b/refs.c @@ -706,7 +706,7 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, has_glob_specials = strpbrk(pattern, "?*["); if (!has_glob_specials) { - /* Append impiled '/' '*' if not present. */ + /* Append implied '/' '*' if not present. */ if (real_pattern.buf[real_pattern.len - 1] != '/') strbuf_addch(&real_pattern, '/'); /* No need to check for '*', there is none. */ diff --git a/setup.c b/setup.c index 710e2f3008..fac34f77a7 100644 --- a/setup.c +++ b/setup.c @@ -206,7 +206,7 @@ int is_inside_work_tree(void) } /* - * set_work_tree() is only ever called if you set GIT_DIR explicitely. + * set_work_tree() is only ever called if you set GIT_DIR explicitly. * The old behaviour (which we retain here) is to set the work tree root * to the cwd, unless overridden by the config, the command line, or * GIT_WORK_TREE. diff --git a/test-chmtime.c b/test-chmtime.c index fe476cb618..92713d16da 100644 --- a/test-chmtime.c +++ b/test-chmtime.c @@ -1,7 +1,7 @@ /* * This program can either change modification time of the given * file(s) or just print it. The program does not change atime nor - * ctime (their values are explicitely preserved). + * ctime (their values are explicitly preserved). * * The mtime can be changed to an absolute value: * diff --git a/transport-helper.c b/transport-helper.c index 107742891f..f822972020 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -171,7 +171,7 @@ static struct child_process *get_helper(struct transport *transport) } else if (!strcmp(capname, "connect")) { data->connect = 1; } else if (mandatory) { - die("Unknown madatory capability %s. This remote " + die("Unknown mandatory capability %s. This remote " "helper probably needs newer version of Git.\n", capname); } From 4f41b611481bad08319966f7787fc7c4c7bfaa52 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:37 -0800 Subject: [PATCH 0024/3211] run-command: Allow stderr to be a caller supplied pipe Like .out, .err may now be set to a file descriptor > 0, which is a writable pipe/socket/file that the child's stderr will be redirected into. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/technical/api-run-command.txt | 2 +- run-command.c | 8 ++++++++ run-command.h | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index b26c28133c..a1280dd837 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -135,7 +135,7 @@ stderr as follows: .in: The FD must be readable; it becomes child's stdin. .out: The FD must be writable; it becomes child's stdout. - .err > 0 is not supported. + .err: The FD must be writable; it becomes child's stderr. The specified FD is closed by start_command(), even if it fails to run the sub-process! diff --git a/run-command.c b/run-command.c index cf2d8f7fae..bfd231243d 100644 --- a/run-command.c +++ b/run-command.c @@ -94,6 +94,9 @@ fail_pipe: else if (need_err) { dup2(fderr[1], 2); close_pair(fderr); + } else if (cmd->err > 1) { + dup2(cmd->err, 2); + close(cmd->err); } if (cmd->no_stdout) @@ -156,6 +159,9 @@ fail_pipe: } else if (need_err) { s2 = dup(2); dup2(fderr[1], 2); + } else if (cmd->err > 2) { + s2 = dup(2); + dup2(cmd->err, 2); } if (cmd->no_stdout) { @@ -228,6 +234,8 @@ fail_pipe: if (need_err) close(fderr[1]); + else if (cmd->err) + close(cmd->err); return 0; } diff --git a/run-command.h b/run-command.h index fb342090e3..a29171adae 100644 --- a/run-command.h +++ b/run-command.h @@ -18,7 +18,7 @@ struct child_process { * - Specify > 0 to set a channel to a particular FD as follows: * .in: a readable FD, becomes child's stdin * .out: a writable FD, becomes child's stdout/stderr - * .err > 0 not supported + * .err: a writable FD, becomes child's stderr * The specified FD is closed by start_command(), even in case * of errors! */ From ae6a5609c025d9ac79e54a3a052704e25d885314 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Fri, 5 Feb 2010 12:57:38 -0800 Subject: [PATCH 0025/3211] run-command: support custom fd-set in async This patch adds the possibility to supply a set of non-0 file descriptors for async process communication instead of the default-created pipe. Additionally, we now support bi-directional communiction with the async procedure, by giving the async function both read and write file descriptors. To retain compatiblity and similar "API feel" with start_command, we require start_async callers to set .out = -1 to get a readable file descriptor. If either of .in or .out is 0, we supply no file descriptor to the async process. [sp: Note: Erik started this patch, and a huge bulk of it is his work. All bugs were introduced later by Shawn.] Signed-off-by: Erik Faye-Lund Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/technical/api-run-command.txt | 50 ++++++++++--- builtin-fetch-pack.c | 7 +- convert.c | 5 +- remote-curl.c | 7 +- run-command.c | 83 +++++++++++++++++---- run-command.h | 9 ++- upload-pack.c | 7 +- 7 files changed, 131 insertions(+), 37 deletions(-) diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index a1280dd837..8994859c81 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -64,8 +64,8 @@ The functions above do the following: `start_async`:: Run a function asynchronously. Takes a pointer to a `struct - async` that specifies the details and returns a pipe FD - from which the caller reads. See below for details. + async` that specifies the details and returns a set of pipe FDs + for communication with the function. See below for details. `finish_async`:: @@ -180,17 +180,47 @@ The caller: struct async variable; 2. initializes .proc and .data; 3. calls start_async(); -4. processes the data by reading from the fd in .out; -5. closes .out; +4. processes communicates with proc through .in and .out; +5. closes .in and .out; 6. calls finish_async(). +The members .in, .out are used to provide a set of fd's for +communication between the caller and the callee as follows: + +. Specify 0 to have no file descriptor passed. The callee will + receive -1 in the corresponding argument. + +. Specify < 0 to have a pipe allocated; start_async() replaces + with the pipe FD in the following way: + + .in: Returns the writable pipe end into which the caller + writes; the readable end of the pipe becomes the function's + in argument. + + .out: Returns the readable pipe end from which the caller + reads; the writable end of the pipe becomes the function's + out argument. + + The caller of start_async() must close the returned FDs after it + has completed reading from/writing from them. + +. Specify a file descriptor > 0 to be used by the function: + + .in: The FD must be readable; it becomes the function's in. + .out: The FD must be writable; it becomes the function's out. + + The specified FD is closed by start_async(), even if it fails to + run the function. + The function pointer in .proc has the following signature: - int proc(int fd, void *data); + int proc(int in, int out, void *data); -. fd specifies a writable file descriptor to which the function must - write the data that it produces. The function *must* close this - descriptor before it returns. +. in, out specifies a set of file descriptors to which the function + must read/write the data that it needs/produces. The function + *must* close these descriptors before it returns. A descriptor + may be -1 if the caller did not configure a descriptor for that + direction. . data is the value that the caller has specified in the .data member of struct async. @@ -205,8 +235,8 @@ because this facility is implemented by a pipe to a forked process on UNIX, but by a thread in the same address space on Windows: . It cannot change the program's state (global variables, environment, - etc.) in a way that the caller notices; in other words, .out is the - only communication channel to the caller. + etc.) in a way that the caller notices; in other words, .in and .out + are the only communication channels to the caller. . It must not change the program's state that the caller of the facility also uses. diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 8ed4a6feaa..dbd8b7bcc8 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -586,12 +586,12 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } -static int sideband_demux(int fd, void *data) +static int sideband_demux(int in, int out, void *data) { int *xd = data; - int ret = recv_sideband("fetch-pack", xd[0], fd); - close(fd); + int ret = recv_sideband("fetch-pack", xd[0], out); + close(out); return ret; } @@ -613,6 +613,7 @@ static int get_pack(int xd[2], char **pack_lockfile) */ demux.proc = sideband_demux; demux.data = xd; + demux.out = -1; if (start_async(&demux)) die("fetch-pack: unable to fork off sideband" " demultiplexer"); diff --git a/convert.c b/convert.c index 491e7141b4..e70ee094a7 100644 --- a/convert.c +++ b/convert.c @@ -241,7 +241,7 @@ struct filter_params { const char *cmd; }; -static int filter_buffer(int fd, void *data) +static int filter_buffer(int in, int out, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. @@ -254,7 +254,7 @@ static int filter_buffer(int fd, void *data) memset(&child_process, 0, sizeof(child_process)); child_process.argv = argv; child_process.in = -1; - child_process.out = fd; + child_process.out = out; if (start_command(&child_process)) return error("cannot fork to run external filter %s", params->cmd); @@ -291,6 +291,7 @@ static int apply_filter(const char *path, const char *src, size_t len, memset(&async, 0, sizeof(async)); async.proc = filter_buffer; async.data = ¶ms; + async.out = -1; params.src = src; params.size = len; params.cmd = cmd; diff --git a/remote-curl.c b/remote-curl.c index 3edbf5717c..6bb3366264 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -184,13 +184,13 @@ static struct discovery* discover_refs(const char *service) return last; } -static int write_discovery(int fd, void *data) +static int write_discovery(int in, int out, void *data) { struct discovery *heads = data; int err = 0; - if (write_in_full(fd, heads->buf, heads->len) != heads->len) + if (write_in_full(out, heads->buf, heads->len) != heads->len) err = 1; - close(fd); + close(out); return err; } @@ -202,6 +202,7 @@ static struct ref *parse_git_refs(struct discovery *heads) memset(&async, 0, sizeof(async)); async.proc = write_discovery; async.data = heads; + async.out = -1; if (start_async(&async)) die("cannot start thread to parse advertised refs"); diff --git a/run-command.c b/run-command.c index bfd231243d..0d95340833 100644 --- a/run-command.c +++ b/run-command.c @@ -327,17 +327,51 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const static unsigned __stdcall run_thread(void *data) { struct async *async = data; - return async->proc(async->fd_for_proc, async->data); + return async->proc(async->proc_in, async->proc_out, async->data); } #endif int start_async(struct async *async) { - int pipe_out[2]; + int need_in, need_out; + int fdin[2], fdout[2]; + int proc_in, proc_out; - if (pipe(pipe_out) < 0) - return error("cannot create pipe: %s", strerror(errno)); - async->out = pipe_out[0]; + need_in = async->in < 0; + if (need_in) { + if (pipe(fdin) < 0) { + if (async->out > 0) + close(async->out); + return error("cannot create pipe: %s", strerror(errno)); + } + async->in = fdin[1]; + } + + need_out = async->out < 0; + if (need_out) { + if (pipe(fdout) < 0) { + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + return error("cannot create pipe: %s", strerror(errno)); + } + async->out = fdout[0]; + } + + if (need_in) + proc_in = fdin[0]; + else if (async->in) + proc_in = async->in; + else + proc_in = -1; + + if (need_out) + proc_out = fdout[1]; + else if (async->out) + proc_out = async->out; + else + proc_out = -1; #ifndef WIN32 /* Flush stdio before fork() to avoid cloning buffers */ @@ -346,24 +380,47 @@ int start_async(struct async *async) async->pid = fork(); if (async->pid < 0) { error("fork (async) failed: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } if (!async->pid) { - close(pipe_out[0]); - exit(!!async->proc(pipe_out[1], async->data)); + if (need_in) + close(fdin[1]); + if (need_out) + close(fdout[0]); + exit(!!async->proc(proc_in, proc_out, async->data)); } - close(pipe_out[1]); + + if (need_in) + close(fdin[0]); + else if (async->in) + close(async->in); + + if (need_out) + close(fdout[1]); + else if (async->out) + close(async->out); #else - async->fd_for_proc = pipe_out[1]; + async->proc_in = proc_in; + async->proc_out = proc_out; async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL); if (!async->tid) { error("cannot create thread: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } #endif return 0; + +error: + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + + if (need_out) + close_pair(fdout); + else if (async->out) + close(async->out); + return -1; } int finish_async(struct async *async) diff --git a/run-command.h b/run-command.h index a29171adae..65ccb1c60f 100644 --- a/run-command.h +++ b/run-command.h @@ -64,17 +64,20 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const */ struct async { /* - * proc writes to fd and closes it; + * proc reads from in; closes it before return + * proc writes to out; closes it before return * returns 0 on success, non-zero on failure */ - int (*proc)(int fd, void *data); + int (*proc)(int in, int out, void *data); void *data; + int in; /* caller writes here and closes it */ int out; /* caller reads from here and closes it */ #ifndef WIN32 pid_t pid; #else HANDLE tid; - int fd_for_proc; + int proc_in; + int proc_out; #endif }; diff --git a/upload-pack.c b/upload-pack.c index df151813f9..dc464d78b3 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -105,12 +105,12 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } -static int do_rev_list(int fd, void *create_full_pack) +static int do_rev_list(int in, int out, void *create_full_pack) { int i; struct rev_info revs; - pack_pipe = xfdopen(fd, "w"); + pack_pipe = xfdopen(out, "w"); init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; @@ -162,8 +162,9 @@ static void create_pack_file(void) int arg = 0; if (shallow_nr) { + memset(&rev_list, 0, sizeof(rev_list)); rev_list.proc = do_rev_list; - rev_list.data = 0; + rev_list.out = -1; if (start_async(&rev_list)) die("git upload-pack: unable to fork git-rev-list"); argv[arg++] = "pack-objects"; From 0c499ea60fda716198c76f2d5febe3998d302afb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:39 -0800 Subject: [PATCH 0026/3211] send-pack: demultiplex a sideband stream with status data If the server advertises side-band-64k capability, we request it and pull the status report data out of side band #1, and let side band #2 go to our stderr. The latter channel be used by the remote side to send our user messages. This basically mirrors the side-band-64k capability in upload-pack. Servers may choose to use side band #2 to send error messages from hook scripts that are meant for the push end user. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-send-pack.c | 66 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 8fffdbf200..2478e1851a 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -372,6 +372,14 @@ static void print_helper_status(struct ref *ref) strbuf_release(&buf); } +static int sideband_demux(int in, int out, void *data) +{ + int *fd = data; + int ret = recv_sideband("send-pack", fd[0], out); + close(out); + return ret; +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -382,18 +390,22 @@ int send_pack(struct send_pack_args *args, struct strbuf req_buf = STRBUF_INIT; struct ref *ref; int new_refs; - int ask_for_status_report = 0; int allow_deleting_refs = 0; - int expect_status_report = 0; + int status_report = 0; + int use_sideband = 0; + unsigned cmds_sent = 0; int ret; + struct async demux; /* Does the other end support the reporting? */ if (server_supports("report-status")) - ask_for_status_report = 1; + status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; + if (server_supports("side-band-64k")) + use_sideband = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@ -456,28 +468,30 @@ int send_pack(struct send_pack_args *args, if (!ref->deletion) new_refs++; - if (!args->dry_run) { + if (args->dry_run) { + ref->status = REF_STATUS_OK; + } else { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); - if (ask_for_status_report) { - packet_buf_write(&req_buf, "%s %s %s%c%s", + if (!cmds_sent && (status_report || use_sideband)) { + packet_buf_write(&req_buf, "%s %s %s%c%s%s", old_hex, new_hex, ref->name, 0, - "report-status"); - ask_for_status_report = 0; - expect_status_report = 1; + status_report ? " report-status" : "", + use_sideband ? " side-band-64k" : ""); } else packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); + ref->status = status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + cmds_sent++; } - ref->status = expect_status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; } if (args->stateless_rpc) { - if (!args->dry_run) { + if (!args->dry_run && cmds_sent) { packet_buf_flush(&req_buf); send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } @@ -487,23 +501,43 @@ int send_pack(struct send_pack_args *args, } strbuf_release(&req_buf); - if (new_refs && !args->dry_run) { + if (use_sideband && cmds_sent) { + memset(&demux, 0, sizeof(demux)); + demux.proc = sideband_demux; + demux.data = fd; + demux.out = -1; + if (start_async(&demux)) + die("receive-pack: unable to fork off sideband demultiplexer"); + in = demux.out; + } + + if (new_refs && cmds_sent) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (use_sideband) + finish_async(&demux); return -1; } } - if (args->stateless_rpc && !args->dry_run) + if (args->stateless_rpc && cmds_sent) packet_flush(out); - if (expect_status_report) + if (status_report && cmds_sent) ret = receive_status(in, remote_refs); else ret = 0; if (args->stateless_rpc) packet_flush(out); + if (use_sideband && cmds_sent) { + if (finish_async(&demux)) { + error("error in sideband demultiplexer"); + ret = -1; + } + close(demux.out); + } + if (ret < 0) return ret; for (ref = remote_refs; ref; ref = ref->next) { From 185c04e041fb33191c5828339381fd8c4058a43a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:40 -0800 Subject: [PATCH 0027/3211] receive-pack: Refactor how capabilities are shown to the client Moving capability advertisement into the packet_write call itself makes it easier to add additional capabilities to the list, be it optional by configuration, or always present in the protocol. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 78c0e69cdc..325ec6e2c4 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -31,7 +31,7 @@ static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; -static char *capabilities_to_send; +static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -105,19 +105,21 @@ static int receive_pack_config(const char *var, const char *value, void *cb) static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - if (!capabilities_to_send) + if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else - packet_write(1, "%s %s%c%s\n", - sha1_to_hex(sha1), path, 0, capabilities_to_send); - capabilities_to_send = NULL; + packet_write(1, "%s %s%c%s%s\n", + sha1_to_hex(sha1), path, 0, + " report-status delete-refs", + prefer_ofs_delta ? " ofs-delta" : ""); + sent_capabilities = 1; return 0; } static void write_head_info(void) { for_each_ref(show_ref, NULL); - if (capabilities_to_send) + if (!sent_capabilities) show_ref("capabilities^{}", null_sha1, 0, NULL); } @@ -670,10 +672,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; - capabilities_to_send = (prefer_ofs_delta) ? - " report-status delete-refs ofs-delta " : - " report-status delete-refs "; - if (advertise_refs || !stateless_rpc) { add_alternate_refs(); write_head_info(); From 38a81b4e82ebf57549f2fa082b329c36dffc0b18 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:41 -0800 Subject: [PATCH 0028/3211] receive-pack: Wrap status reports inside side-band-64k If the client requests the side-band-64k protocol capability we now wrap the status report data inside of packets sent to band #1. This permits us to later send additional progress or informational messages down band #2. If side-band-64k was enabled, we always send a final flush packet to let the client know we are done transmitting. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 325ec6e2c4..ff3f11731d 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -2,6 +2,7 @@ #include "pack.h" #include "refs.h" #include "pkt-line.h" +#include "sideband.h" #include "run-command.h" #include "exec_cmd.h" #include "commit.h" @@ -27,6 +28,7 @@ static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; +static int use_sideband; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; @@ -110,7 +112,7 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void else packet_write(1, "%s %s%c%s%s\n", sha1_to_hex(sha1), path, 0, - " report-status delete-refs", + " report-status delete-refs side-band-64k", prefer_ofs_delta ? " ofs-delta" : ""); sent_capabilities = 1; return 0; @@ -466,6 +468,8 @@ static void read_head_info(void) if (reflen + 82 < len) { if (strstr(refname + reflen + 1, "report-status")) report_status = 1; + if (strstr(refname + reflen + 1, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; } cmd = xmalloc(sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@ -565,17 +569,25 @@ static const char *unpack(void) static void report(const char *unpack_status) { struct command *cmd; - packet_write(1, "unpack %s\n", - unpack_status ? unpack_status : "ok"); + struct strbuf buf = STRBUF_INIT; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) - packet_write(1, "ok %s\n", - cmd->ref_name); + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); else - packet_write(1, "ng %s %s\n", - cmd->ref_name, cmd->error_string); + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, cmd->error_string); } - packet_flush(1); + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + safe_write(1, buf.buf, buf.len); + strbuf_release(&buf); } static int delete_only(struct command *cmd) @@ -705,5 +717,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (auto_update_server_info) update_server_info(0); } + if (use_sideband) + packet_flush(1); return 0; } From 6d525d389fbef814b11e41f196e6656f2e95f412 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:42 -0800 Subject: [PATCH 0029/3211] receive-pack: Send hook output over side band #2 If the client requests to enable side-band-64k capability we can safely send any hook stdout or stderr data down side band #2, so the client can present it to the user. If side-band-64k isn't enabled, hooks continue to inherit stderr from the parent receive-pack process. When the side band channel is being used the push client will wind up prefixing all server messages with "remote: ", just like fetch does, so our test vector has to be updated with the new expected output. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 65 +++++++++++++++++++++++++++++++++++++---- t/t5401-update-hooks.sh | 22 +++++++------- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index ff3f11731d..da1c26b6be 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -139,11 +139,25 @@ static struct command *commands; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; +static int copy_to_sideband(int in, int out, void *arg) +{ + char data[128]; + while (1) { + ssize_t sz = xread(in, data, sizeof(data)); + if (sz <= 0) + break; + send_sideband(1, 2, data, sz, use_sideband); + } + close(in); + return 0; +} + static int run_receive_hook(const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; struct child_process proc; + struct async muxer; const char *argv[2]; int have_input = 0, code; @@ -163,9 +177,23 @@ static int run_receive_hook(const char *hook_name) proc.in = -1; proc.stdout_to_stderr = 1; + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + return code; + proc.err = muxer.in; + } + code = start_command(&proc); - if (code) + if (code) { + if (use_sideband) + finish_async(&muxer); return code; + } + for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) { size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", @@ -177,6 +205,8 @@ static int run_receive_hook(const char *hook_name) } } close(proc.in); + if (use_sideband) + finish_async(&muxer); return finish_command(&proc); } @@ -184,6 +214,8 @@ static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; const char *argv[5]; + struct child_process proc; + int code; if (access(update_hook, X_OK) < 0) return 0; @@ -194,8 +226,18 @@ static int run_update_hook(struct command *cmd) argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | - RUN_COMMAND_STDOUT_TO_STDERR); + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + code = start_command(&proc); + if (code) + return code; + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + return finish_command(&proc); } static int is_ref_checked_out(const char *ref) @@ -384,8 +426,9 @@ static char update_post_hook[] = "hooks/post-update"; static void run_update_post_hook(struct command *cmd) { struct command *cmd_p; - int argc, status; + int argc; const char **argv; + struct child_process proc; for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { if (cmd_p->error_string) @@ -407,8 +450,18 @@ static void run_update_post_hook(struct command *cmd) argc++; } argv[argc] = NULL; - status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN - | RUN_COMMAND_STDOUT_TO_STDERR); + + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + if (!start_command(&proc)) { + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + finish_command(&proc); + } } static void execute_commands(const char *unpacker_error) diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 64f66c94f3..c3cf397b03 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -118,19 +118,19 @@ test_expect_success 'send-pack produced no output' ' ' cat <expect -STDOUT pre-receive -STDERR pre-receive -STDOUT update refs/heads/master -STDERR update refs/heads/master -STDOUT update refs/heads/tofail -STDERR update refs/heads/tofail -STDOUT post-receive -STDERR post-receive -STDOUT post-update -STDERR post-update +remote: STDOUT pre-receive +remote: STDERR pre-receive +remote: STDOUT update refs/heads/master +remote: STDERR update refs/heads/master +remote: STDOUT update refs/heads/tofail +remote: STDERR update refs/heads/tofail +remote: STDOUT post-receive +remote: STDERR post-receive +remote: STDOUT post-update +remote: STDERR post-update EOF test_expect_success 'send-pack stderr contains hook messages' ' - grep ^STD send.err >actual && + grep ^remote: send.err | sed "s/ *\$//" >actual && test_cmp - actual Date: Sat, 6 Feb 2010 10:35:19 +0100 Subject: [PATCH 0030/3211] setenv(GIT_DIR) clean-up This patch converts the setenv() calls in path.c and setup.c. After the call, git grep with a pager works again in bare repos. It leaves the setenv(GIT_DIR_ENVIRONMENT, ...) calls in git.c alone, as they respond to command line switches that emulate the effect of setting the environment variable directly. The remaining site in environment.c is in set_git_dir() and is left alone, too, of course. Finally, builtin-init-db.c is left changed because the repo is still being carefully constructed when the environment variable is set. This fixes git shortlog when run inside a git directory, which had been broken by abe549e1. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- path.c | 2 +- setup.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/path.c b/path.c index 79aa104712..0005df3a5f 100644 --- a/path.c +++ b/path.c @@ -336,7 +336,7 @@ char *enter_repo(char *path, int strict) if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 && validate_headref("HEAD") == 0) { - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format(); return path; } diff --git a/setup.c b/setup.c index 710e2f3008..b38cbee145 100644 --- a/setup.c +++ b/setup.c @@ -404,9 +404,9 @@ const char *setup_git_directory_gently(int *nongit_ok) inside_work_tree = 0; if (offset != len) { cwd[offset] = '\0'; - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + set_git_dir(cwd); } else - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format_gently(nongit_ok); return NULL; } From 0455ec0330fd29146d6e16189cc9262566cc566d Mon Sep 17 00:00:00 2001 From: Aaron Crane Date: Sat, 6 Feb 2010 18:26:24 +0000 Subject: [PATCH 0031/3211] cvsimport: new -R option: generate .git/cvs-revisions mapping This option causes the creation or updating of a file mapping CVS (filename, revision number) pairs to Git commit IDs. This is expected to be useful if you have CVS revision numbers stored in commit messages, bug-tracking systems, email archives, and the like. Signed-off-by: Aaron Crane Signed-off-by: Junio C Hamano --- Documentation/git-cvsimport.txt | 18 ++++++++++++++++- git-cvsimport.perl | 21 +++++++++++++++---- t/t9600-cvsimport.sh | 36 ++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index ddfcb3d143..8bcd875a67 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -13,7 +13,7 @@ SYNOPSIS [-A ] [-p ] [-P ] [-C ] [-z ] [-i] [-k] [-u] [-s ] [-a] [-m] [-M ] [-S ] [-L ] - [-r ] [] + [-r ] [-R] [] DESCRIPTION @@ -157,6 +157,22 @@ It is not recommended to use this feature if you intend to export changes back to CVS again later with 'git cvsexportcommit'. +-R:: + Generate a `$GIT_DIR/cvs-revisions` file containing a mapping from CVS + revision numbers to newly-created Git commit IDs. The generated file + will contain one line for each (filename, revision) pair imported; + each line will look like ++ +--------- +src/widget.c 1.1 1d862f173cdc7325b6fa6d2ae1cfd61fd1b512b7 +--------- ++ +The revision data is appended to the file if it already exists, for use when +doing incremental imports. ++ +This option may be useful if you have CVS revision numbers stored in commit +messages, bug-tracking systems, email archives, and the like. + -h:: Print a short usage message and exit. diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 4853bf7a0d..9e03eee458 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -29,7 +29,7 @@ use IPC::Open2; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r); +our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R); my (%conv_author_name, %conv_author_email); sub usage(;$) { @@ -40,7 +40,7 @@ Usage: git cvsimport # fetch/update GIT from CVS [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file] [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k] [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit] - [-r remote] [CVS_module] + [-r remote] [-R] [CVS_module] END exit(1); } @@ -110,7 +110,7 @@ sub read_repo_config { } } -my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:"; +my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:R"; read_repo_config($opts); Getopt::Long::Configure( 'no_ignore_case', 'bundling' ); @@ -659,6 +659,11 @@ if ($opt_A) { write_author_info("$git_dir/cvs-authors"); } +# open .git/cvs-revisions, if requested +open my $revision_map, '>>', "$git_dir/cvs-revisions" + or die "Can't open $git_dir/cvs-revisions for appending: $!\n" + if defined $opt_R; + # # run cvsps into a file unless we are getting @@ -742,7 +747,7 @@ sub write_tree () { } my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); -my (@old,@new,@skipped,%ignorebranch); +my (@old,@new,@skipped,%ignorebranch,@commit_revisions); # commits that cvsps cannot place anywhere... $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; @@ -825,6 +830,11 @@ sub commit { system('git' , 'update-ref', "$remote/$branch", $cid) == 0 or die "Cannot write branch $branch for update: $!\n"; + if ($revision_map) { + print $revision_map "@$_ $cid\n" for @commit_revisions; + } + @commit_revisions = (); + if ($tag) { my ($xtag) = $tag; $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY ** @@ -959,6 +969,7 @@ while () { push(@skipped, $fn); next; } + push @commit_revisions, [$fn, $rev]; print "Fetching $fn v $rev\n" if $opt_v; my ($tmpname, $size) = $cvs->file($fn,$rev); if ($size == -1) { @@ -981,7 +992,9 @@ while () { unlink($tmpname); } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { my $fn = $1; + my $rev = $2; $fn =~ s#^/+##; + push @commit_revisions, [$fn, $rev]; push(@old,$fn); print "Delete $fn\n" if $opt_v; } elsif ($state == 9 and /^\s*$/) { diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 363345faef..b572ce3ab7 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -47,13 +47,20 @@ EOF test_expect_success 'import a trivial module' ' - git cvsimport -a -z 0 -C module-git module && + git cvsimport -a -R -z 0 -C module-git module && test_cmp module-cvs/o_fortuna module-git/o_fortuna ' test_expect_success 'pack refs' 'cd module-git && git gc && cd ..' +test_expect_success 'initial import has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'update cvs module' ' cd module-cvs && @@ -86,13 +93,21 @@ EOF test_expect_success 'update git module' ' cd module-git && - git cvsimport -a -z 0 module && + git cvsimport -a -R -z 0 module && git merge origin && cd .. && test_cmp module-cvs/o_fortuna module-git/o_fortuna ' +test_expect_success 'update has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1 HEAD^ && + git log --format="o_fortuna 1.2 %H" -1 HEAD) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'update cvs module' ' cd module-cvs && @@ -107,13 +122,22 @@ test_expect_success 'cvsimport.module config works' ' cd module-git && git config cvsimport.module module && - git cvsimport -a -z0 && + git cvsimport -a -R -z0 && git merge origin && cd .. && test_cmp module-cvs/tick module-git/tick ' +test_expect_success 'second update has correct .git/cvs-revisions' ' + + (cd module-git && + git log --format="o_fortuna 1.1 %H" -1 HEAD^^ && + git log --format="o_fortuna 1.2 %H" -1 HEAD^ + git log --format="tick 1.1 %H" -1 HEAD) > expected && + test_cmp expected module-git/.git/cvs-revisions +' + test_expect_success 'import from a CVS working tree' ' $CVS co -d import-from-wt module && @@ -126,6 +150,12 @@ test_expect_success 'import from a CVS working tree' ' ' +test_expect_success 'no .git/cvs-revisions created by default' ' + + ! test -e import-from-wt/.git/cvs-revisions + +' + test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master' test_done From 1123c67ceee2f310b08ab5d67b076ef04ab59bfc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 6 Feb 2010 23:44:15 -0500 Subject: [PATCH 0032/3211] accept "git grep -- pattern" Currently the only way to "quote" a grep pattern that might begin with a dash is to use "git grep -e pattern". This works just fine, and is also the way right way to do it on many traditional grep implemenations. Some people prefer to use "git grep -- pattern", however, as "--" is the usual "end of options" marker, and at least GNU grep and Solaris 10 grep support this. This patch makes that syntax work. There is a slight behavior change, in that "git grep -- $X" used to be interpreted as "grep for -- in $X". However, that usage is questionable. "--" is usually the end-of-options marker, so "git grep" was unlike many other greps in treating it as a literal pattern (e.g., both GNU grep and Solaris 10 grep will treat "grep --" as missing a pattern). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-grep.c | 10 ++++++++++ t/t7002-grep.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/builtin-grep.c b/builtin-grep.c index 26d4deb1cc..63d4b95b0d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -861,6 +861,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + /* + * skip a -- separator; we know it cannot be + * separating revisions from pathnames if + * we haven't even had any patterns yet + */ + if (argc > 0 && !opt.pattern_list && !strcmp(argv[0], "--")) { + argv++; + argc--; + } + /* First unrecognized non-option token */ if (argc > 0 && !opt.pattern_list) { append_grep_pattern(&opt, argv[0], "command line", 0, diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 7144f815c0..0b583cbfc1 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -434,4 +434,37 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' +test_expect_success 'setup double-dash tests' ' +cat >double-dash < +other +EOF +git add double-dash +' + +cat >expected < +EOF +test_expect_success 'grep -- pattern' ' + git grep -- "->" >actual && + test_cmp expected actual +' +test_expect_success 'grep -- pattern -- pathspec' ' + git grep -- "->" -- double-dash >actual && + test_cmp expected actual +' +test_expect_success 'grep -e pattern -- path' ' + git grep -e "->" -- double-dash >actual && + test_cmp expected actual +' + +cat >expected <actual && + test_cmp expected actual +' + test_done From 9c898a18ea37aa04a84c3a2d18794cf892862702 Mon Sep 17 00:00:00 2001 From: Heiko Voigt Date: Sun, 7 Feb 2010 22:47:56 +0100 Subject: [PATCH 0033/3211] git-gui: check whether systems nice command works or disable it This fixes issue 394 from msysgit. It seems that the Gnuwin32 project provides a nice command but it returns a "not implemented" error. To help users we now try to execute once and disable it in case it fails. Signed-off-by: Heiko Voigt Signed-off-by: Shawn O. Pearce --- git-gui.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 85fbc021fe..549f59ba73 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -388,6 +388,9 @@ proc _lappend_nice {cmd_var} { if {![info exists _nice]} { set _nice [_which nice] + if {[catch {exec $_nice git version}]} { + set _nice {} + } } if {$_nice ne {}} { lappend cmd $_nice From 6b3fa7e7d13a3c8fddba43ad6e8acb314d1985ac Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 9 Feb 2010 18:01:29 -0800 Subject: [PATCH 0034/3211] t5401: Use a bare repository for the remote peer We want to avoid the warnings (or later, test failures) about updating the current branch. It was never my intention to have this test deal with a repository with a working directory, and it is a very old bug that the test even used a non-bare repository for the remote side of the push operations. This fixes the interleaved output error we were seeing as a test failure by avoiding the giant warning message we were getting back about updating the current branch being risky. Its not a real fix, but is something we should do no matter what, because the behavior will change in the future to reject, and the test would break at that time. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5401-update-hooks.sh | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index c3cf397b03..7240fabfec 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -17,22 +17,22 @@ test_expect_success setup ' commit1=$(echo modify | git commit-tree $tree1 -p $commit0) && git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && - git clone ./. victim && - GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && + git clone --bare ./. victim.git && + GIT_DIR=victim.git git update-ref refs/heads/tofail $commit1 && git update-ref refs/heads/master $commit1 && git update-ref refs/heads/tofail $commit0 ' -cat >victim/.git/hooks/pre-receive <<'EOF' +cat >victim.git/hooks/pre-receive <<'EOF' #!/bin/sh printf %s "$@" >>$GIT_DIR/pre-receive.args cat - >$GIT_DIR/pre-receive.stdin echo STDOUT pre-receive echo STDERR pre-receive >&2 EOF -chmod u+x victim/.git/hooks/pre-receive +chmod u+x victim.git/hooks/pre-receive -cat >victim/.git/hooks/update <<'EOF' +cat >victim.git/hooks/update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/update.args read x; printf %s "$x" >$GIT_DIR/update.stdin @@ -40,77 +40,77 @@ echo STDOUT update $1 echo STDERR update $1 >&2 test "$1" = refs/heads/master || exit EOF -chmod u+x victim/.git/hooks/update +chmod u+x victim.git/hooks/update -cat >victim/.git/hooks/post-receive <<'EOF' +cat >victim.git/hooks/post-receive <<'EOF' #!/bin/sh printf %s "$@" >>$GIT_DIR/post-receive.args cat - >$GIT_DIR/post-receive.stdin echo STDOUT post-receive echo STDERR post-receive >&2 EOF -chmod u+x victim/.git/hooks/post-receive +chmod u+x victim.git/hooks/post-receive -cat >victim/.git/hooks/post-update <<'EOF' +cat >victim.git/hooks/post-update <<'EOF' #!/bin/sh echo "$@" >>$GIT_DIR/post-update.args read x; printf %s "$x" >$GIT_DIR/post-update.stdin echo STDOUT post-update echo STDERR post-update >&2 EOF -chmod u+x victim/.git/hooks/post-update +chmod u+x victim.git/hooks/post-update test_expect_success push ' - test_must_fail git send-pack --force ./victim/.git \ + test_must_fail git send-pack --force ./victim.git \ master tofail >send.out 2>send.err ' test_expect_success 'updated as expected' ' - test $(GIT_DIR=victim/.git git rev-parse master) = $commit1 && - test $(GIT_DIR=victim/.git git rev-parse tofail) = $commit1 + test $(GIT_DIR=victim.git git rev-parse master) = $commit1 && + test $(GIT_DIR=victim.git git rev-parse tofail) = $commit1 ' test_expect_success 'hooks ran' ' - test -f victim/.git/pre-receive.args && - test -f victim/.git/pre-receive.stdin && - test -f victim/.git/update.args && - test -f victim/.git/update.stdin && - test -f victim/.git/post-receive.args && - test -f victim/.git/post-receive.stdin && - test -f victim/.git/post-update.args && - test -f victim/.git/post-update.stdin + test -f victim.git/pre-receive.args && + test -f victim.git/pre-receive.stdin && + test -f victim.git/update.args && + test -f victim.git/update.stdin && + test -f victim.git/post-receive.args && + test -f victim.git/post-receive.stdin && + test -f victim.git/post-update.args && + test -f victim.git/post-update.stdin ' test_expect_success 'pre-receive hook input' ' (echo $commit0 $commit1 refs/heads/master; echo $commit1 $commit0 refs/heads/tofail - ) | test_cmp - victim/.git/pre-receive.stdin + ) | test_cmp - victim.git/pre-receive.stdin ' test_expect_success 'update hook arguments' ' (echo refs/heads/master $commit0 $commit1; echo refs/heads/tofail $commit1 $commit0 - ) | test_cmp - victim/.git/update.args + ) | test_cmp - victim.git/update.args ' test_expect_success 'post-receive hook input' ' echo $commit0 $commit1 refs/heads/master | - test_cmp - victim/.git/post-receive.stdin + test_cmp - victim.git/post-receive.stdin ' test_expect_success 'post-update hook arguments' ' echo refs/heads/master | - test_cmp - victim/.git/post-update.args + test_cmp - victim.git/post-update.args ' test_expect_success 'all hook stdin is /dev/null' ' - ! test -s victim/.git/update.stdin && - ! test -s victim/.git/post-update.stdin + ! test -s victim.git/update.stdin && + ! test -s victim.git/post-update.stdin ' test_expect_success 'all *-receive hook args are empty' ' - ! test -s victim/.git/pre-receive.args && - ! test -s victim/.git/post-receive.args + ! test -s victim.git/pre-receive.args && + ! test -s victim.git/post-receive.args ' test_expect_success 'send-pack produced no output' ' From 466dbc42f58623d4341d6b1171b5cc13e2b700df Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 10 Feb 2010 09:34:12 -0800 Subject: [PATCH 0035/3211] receive-pack: Send internal errors over side-band #2 If the client has requested side-band-64k capability, send any of the internal error or warning messages in the muxed side-band stream using the same band as our hook output, band #2. By putting everything in one stream we ensure all messages are processed by the side-band demuxer, avoiding interleaving between our own stderr and the side-band demuxer's stderr buffers. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-receive-pack.c | 64 ++++++++++++++++++++++++++++++++--------- t/t5401-update-hooks.sh | 3 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index da1c26b6be..a5543f9918 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -139,6 +139,42 @@ static struct command *commands; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; +static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); +static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); + +static void report_message(const char *prefix, const char *err, va_list params) +{ + int sz = strlen(prefix); + char msg[4096]; + + strncpy(msg, prefix, sz); + sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params); + if (sz > (sizeof(msg) - 1)) + sz = sizeof(msg) - 1; + msg[sz++] = '\n'; + + if (use_sideband) + send_sideband(1, 2, msg, sz, use_sideband); + else + xwrite(2, msg, sz); +} + +static void rp_warning(const char *err, ...) +{ + va_list params; + va_start(params, err); + report_message("warning: ", err, params); + va_end(params); +} + +static void rp_error(const char *err, ...) +{ + va_list params; + va_start(params, err); + report_message("error: ", err, params); + va_end(params); +} + static int copy_to_sideband(int in, int out, void *arg) { char data[128]; @@ -276,7 +312,7 @@ static void warn_unconfigured_deny(void) { int i; for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) - warning("%s", warn_unconfigured_deny_msg[i]); + rp_warning("%s", warn_unconfigured_deny_msg[i]); } static char *warn_unconfigured_deny_delete_current_msg[] = { @@ -302,7 +338,7 @@ static void warn_unconfigured_deny_delete_current(void) for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); i++) - warning("%s", warn_unconfigured_deny_delete_current_msg[i]); + rp_warning("%s", warn_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -314,7 +350,7 @@ static const char *update(struct command *cmd) /* only refs/... are allowed */ if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) { - error("refusing to create funny ref '%s' remotely", name); + rp_error("refusing to create funny ref '%s' remotely", name); return "funny refname"; } @@ -324,12 +360,12 @@ static const char *update(struct command *cmd) break; case DENY_UNCONFIGURED: case DENY_WARN: - warning("updating the current branch"); + rp_warning("updating the current branch"); if (deny_current_branch == DENY_UNCONFIGURED) warn_unconfigured_deny(); break; case DENY_REFUSE: - error("refusing to update checked out branch: %s", name); + rp_error("refusing to update checked out branch: %s", name); return "branch is currently checked out"; } } @@ -342,7 +378,7 @@ static const char *update(struct command *cmd) if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) { if (deny_deletes && !prefixcmp(name, "refs/heads/")) { - error("denying ref deletion for %s", name); + rp_error("denying ref deletion for %s", name); return "deletion prohibited"; } @@ -354,10 +390,10 @@ static const char *update(struct command *cmd) case DENY_UNCONFIGURED: if (deny_delete_current == DENY_UNCONFIGURED) warn_unconfigured_deny_delete_current(); - warning("deleting the current branch"); + rp_warning("deleting the current branch"); break; case DENY_REFUSE: - error("refusing to delete the current branch: %s", name); + rp_error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } } @@ -387,23 +423,23 @@ static const char *update(struct command *cmd) break; free_commit_list(bases); if (!ent) { - error("denying non-fast-forward %s" - " (you should pull first)", name); + rp_error("denying non-fast-forward %s" + " (you should pull first)", name); return "non-fast-forward"; } } if (run_update_hook(cmd)) { - error("hook declined to update %s", name); + rp_error("hook declined to update %s", name); return "hook declined"; } if (is_null_sha1(new_sha1)) { if (!parse_object(old_sha1)) { - warning ("Allowing deletion of corrupt ref."); + rp_warning("Allowing deletion of corrupt ref."); old_sha1 = NULL; } if (delete_ref(name, old_sha1, 0)) { - error("failed to delete %s", name); + rp_error("failed to delete %s", name); return "failed to delete"; } return NULL; /* good */ @@ -411,7 +447,7 @@ static const char *update(struct command *cmd) else { lock = lock_any_ref_for_update(name, old_sha1, 0); if (!lock) { - error("failed to lock %s", name); + rp_error("failed to lock %s", name); return "failed to lock"; } if (write_ref_sha1(lock, new_sha1, "push")) { diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 7240fabfec..17bcb0b040 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -124,6 +124,7 @@ remote: STDOUT update refs/heads/master remote: STDERR update refs/heads/master remote: STDOUT update refs/heads/tofail remote: STDERR update refs/heads/tofail +remote: error: hook declined to update refs/heads/tofail remote: STDOUT post-receive remote: STDERR post-receive remote: STDOUT post-update @@ -131,7 +132,7 @@ remote: STDERR post-update EOF test_expect_success 'send-pack stderr contains hook messages' ' grep ^remote: send.err | sed "s/ *\$//" >actual && - test_cmp - actual Date: Thu, 11 Feb 2010 16:06:01 -0500 Subject: [PATCH 0036/3211] cherry-pick: rewrap advice message The current message overflows on an 80-character terminal. While we're at it, fix the spelling of 'committing'. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-revert.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 8ac86f0943..83e5c0a755 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -213,13 +213,13 @@ static char *help_msg(const unsigned char *sha1) return msg; strcpy(helpbuf, " After resolving the conflicts,\n" - "mark the corrected paths with 'git add ' " - "or 'git rm ' and commit the result."); + "mark the corrected paths with 'git add ' or 'git rm '\n" + "and commit the result."); if (action == CHERRY_PICK) { sprintf(helpbuf + strlen(helpbuf), - "\nWhen commiting, use the option " - "'-c %s' to retain authorship and message.", + " When committing, use the option '-c %s'\n" + "to retain authorship and message.", find_unique_abbrev(sha1, DEFAULT_ABBREV)); } return helpbuf; From dd9314cc2a2f353bf9438db14cbbf02a1c219bda Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:06:43 -0500 Subject: [PATCH 0037/3211] cherry-pick: refactor commit parsing code These lines are really just lookup_commit_reference re-implemented. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-revert.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 83e5c0a755..012c64644d 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -68,15 +68,9 @@ static void parse_args(int argc, const char **argv) if (get_sha1(arg, sha1)) die ("Cannot find '%s'", arg); - commit = (struct commit *)parse_object(sha1); + commit = lookup_commit_reference(sha1); if (!commit) - die ("Could not find %s", sha1_to_hex(sha1)); - if (commit->object.type == OBJ_TAG) { - commit = (struct commit *) - deref_tag((struct object *)commit, arg, strlen(arg)); - } - if (commit->object.type != OBJ_COMMIT) - die ("'%s' does not point to a commit", arg); + exit(1); } static char *get_oneline(const char *message) From 08565bdb4b5d1da597ed8b90d1a23729f7c006d0 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:07:06 -0500 Subject: [PATCH 0038/3211] cherry-pick: format help message as strbuf This gets rid of the fixed-size buffer and an unchecked sprintf. That sprintf is actually OK as the only variable-sized thing put in it is an abbreviated sha1, which is bounded at 40 characters. However, the next patch will change that to something unbounded. Note that this function now returns an allocated buffer instead of a static one; however, it doesn't matter as the only caller exits immediately. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-revert.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 012c64644d..77e4f4eed3 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -200,23 +200,23 @@ static void set_author_ident_env(const char *message) static char *help_msg(const unsigned char *sha1) { - static char helpbuf[1024]; + struct strbuf helpbuf = STRBUF_INIT; char *msg = getenv("GIT_CHERRY_PICK_HELP"); if (msg) return msg; - strcpy(helpbuf, " After resolving the conflicts,\n" + strbuf_addstr(&helpbuf, " After resolving the conflicts,\n" "mark the corrected paths with 'git add ' or 'git rm '\n" "and commit the result."); if (action == CHERRY_PICK) { - sprintf(helpbuf + strlen(helpbuf), + strbuf_addf(&helpbuf, " When committing, use the option '-c %s'\n" "to retain authorship and message.", find_unique_abbrev(sha1, DEFAULT_ABBREV)); } - return helpbuf; + return strbuf_detach(&helpbuf, NULL); } static struct tree *empty_tree(void) From 97915544f84132b210a336d5877fafd7cb104abe Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:08:15 -0500 Subject: [PATCH 0039/3211] cherry-pick: show commit name instead of sha1 When we have a conflict, we advise the user to do: git commit -c $sha1 This works fine, but is unnecessarily confusing and annoying for the user to type, when: git commit -c $the_thing_you_called_cherry_pick_with works just as well. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-revert.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index 77e4f4eed3..ad612497a2 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -38,6 +38,7 @@ static const char * const cherry_pick_usage[] = { static int edit, no_replay, no_commit, mainline, signoff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; +static const char *commit_name; static int allow_rerere_auto; static const char *me; @@ -49,7 +50,6 @@ static void parse_args(int argc, const char **argv) const char * const * usage_str = action == REVERT ? revert_usage : cherry_pick_usage; unsigned char sha1[20]; - const char *arg; int noop; struct option options[] = { OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"), @@ -64,10 +64,10 @@ static void parse_args(int argc, const char **argv) if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1) usage_with_options(usage_str, options); - arg = argv[0]; - if (get_sha1(arg, sha1)) - die ("Cannot find '%s'", arg); + commit_name = argv[0]; + if (get_sha1(commit_name, sha1)) + die ("Cannot find '%s'", commit_name); commit = lookup_commit_reference(sha1); if (!commit) exit(1); @@ -198,7 +198,7 @@ static void set_author_ident_env(const char *message) sha1_to_hex(commit->object.sha1)); } -static char *help_msg(const unsigned char *sha1) +static char *help_msg(const char *name) { struct strbuf helpbuf = STRBUF_INIT; char *msg = getenv("GIT_CHERRY_PICK_HELP"); @@ -214,7 +214,7 @@ static char *help_msg(const unsigned char *sha1) strbuf_addf(&helpbuf, " When committing, use the option '-c %s'\n" "to retain authorship and message.", - find_unique_abbrev(sha1, DEFAULT_ABBREV)); + name); } return strbuf_detach(&helpbuf, NULL); } @@ -403,7 +403,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (commit_lock_file(&msg_file) < 0) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg(commit->object.sha1)); + me, help_msg(commit_name)); rerere(allow_rerere_auto); exit(1); } From 4d128884fbcce6c7effc729e1c52f06ce17037ee Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:19:37 -0500 Subject: [PATCH 0040/3211] cherry-pick: prettify the advice message It's hard to see the "how to commit" part of this message, which users may want to cut and paste. On top of that, having it in paragraph form means that a really long commit name may cause ugly wrapping. Let's make it prettier, like: Automatic cherry-pick failed. After resolving the conflicts, mark the corrected paths with 'git add ' or 'git rm ' and commit the result with: git commit -c HEAD~23 Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-revert.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builtin-revert.c b/builtin-revert.c index ad612497a2..eff52687a8 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -208,14 +208,16 @@ static char *help_msg(const char *name) strbuf_addstr(&helpbuf, " After resolving the conflicts,\n" "mark the corrected paths with 'git add ' or 'git rm '\n" - "and commit the result."); + "and commit the result"); if (action == CHERRY_PICK) { - strbuf_addf(&helpbuf, - " When committing, use the option '-c %s'\n" - "to retain authorship and message.", + strbuf_addf(&helpbuf, " with: \n" + "\n" + " git commit -c %s\n", name); } + else + strbuf_addch(&helpbuf, '.'); return strbuf_detach(&helpbuf, NULL); } From 67d176300c0a79d5cf65402c641fcec7c5388f29 Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Fri, 12 Feb 2010 20:36:12 +0900 Subject: [PATCH 0041/3211] git-imap-send: Convert LF to CRLF before storing patch to draft box When storing a message over IMAP (RFC 3501 6.3.11), the message should be in the format of an RFC 2822 message; most notably, CRLF must be used as a line terminator. Convert "\n" line endings in the payload to CRLF before feeding it to IMAP APPEND command. Signed-off-by: Hitoshi Mitake Signed-off-by: Junio C Hamano --- imap-send.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/imap-send.c b/imap-send.c index de8114bac0..3527b56d5d 100644 --- a/imap-send.c +++ b/imap-send.c @@ -91,7 +91,6 @@ struct msg_data { char *data; int len; unsigned char flags; - unsigned int crlf:1; }; static const char imap_send_usage[] = "git imap-send < "; @@ -1166,6 +1165,44 @@ static int imap_make_flags(int flags, char *buf) return d; } +static void lf_to_crlf(struct msg_data *msg) +{ + char *new; + int i, j, lfnum = 0; + + if (msg->data[0] == '\n') + lfnum++; + for (i = 1; i < msg->len; i++) { + if (msg->data[i - 1] != '\r' && msg->data[i] == '\n') + lfnum++; + } + + new = xmalloc(msg->len + lfnum); + if (msg->data[0] == '\n') { + new[0] = '\r'; + new[1] = '\n'; + i = 1; + j = 2; + } else { + new[0] = msg->data[0]; + i = 1; + j = 1; + } + for ( ; i < msg->len; i++) { + if (msg->data[i] != '\n') { + new[j++] = msg->data[i]; + continue; + } + if (msg->data[i - 1] != '\r') + new[j++] = '\r'; + /* otherwise it already had CR before */ + new[j++] = '\n'; + } + msg->len += lfnum; + free(msg->data); + msg->data = new; +} + static int imap_store_msg(struct store *gctx, struct msg_data *data) { struct imap_store *ctx = (struct imap_store *)gctx; @@ -1175,6 +1212,7 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data) int ret, d; char flagstr[128]; + lf_to_crlf(data); memset(&cb, 0, sizeof(cb)); cb.dlen = data->len; From 88d9d45d071379e81e585faa95e4f28414d7d973 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 10 Feb 2010 02:11:49 +0100 Subject: [PATCH 0042/3211] git log -p -m: document -m and honor --first-parent git log -p -m is used to show one merge entry per parent, with an appropriate diff; this can be useful when examining histories where full set of changes introduced by a merged branch is interesting, not only the conflicts. This patch properly documents the -m switch, which has so far been mentioned only as a fairly special diff-tree flag. It also makes the code show full patch entry only for the first parent when --first-parent is used. Thus: git log -p -m --first-parent will show the history from the "main branch perspective", while also including full diff of changes introduced by other merged in branches. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- Documentation/diff-generate-patch.txt | 3 ++- Documentation/git-log.txt | 9 +++++++++ Documentation/rev-list-options.txt | 13 +++++++++++-- log-tree.c | 10 ++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index 0f25ba7e38..8f9a2412fd 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -56,7 +56,8 @@ combined diff format "git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or '--cc' option to produce 'combined diff'. For showing a merge commit -with "git log -p", this is the default format. +with "git log -p", this is the default format; you can force showing +full diff with the '-m' option. A 'combined diff' format looks like this: ------------ diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 0e39bb61ee..fb184ba186 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -118,6 +118,15 @@ git log master --not --remotes=*/master:: Shows all commits that are in local master but not in any remote repository master branches. +git log -p -m --first-parent:: + + Shows the history including change diffs, but only from the + "main branch" perspective, skipping commits that come from merged + branches, and showing full diffs of changes introduced by the merges. + This makes sense only when following a strict policy of merging all + topic branches when staying on a single integration branch. + + Discussion ---------- diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 6e9baf8b38..39a064b533 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -108,8 +108,8 @@ options may be given. See linkgit:git-diff-files[1] for more options. -c:: - This flag changes the way a merge commit is displayed. It shows - the differences from each of the parents to the merge result + With this option, diff output for a merge commit + shows the differences from each of the parents to the merge result simultaneously instead of showing pairwise diff between a parent and the result one at a time. Furthermore, it lists only files which were modified from all parents. @@ -121,6 +121,15 @@ 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. +-m:: + + This flag makes the merge commits show the full diff like + regular commits; for each merge parent, a separate log entry + and diff is generated. An exception is that only diff against + the first parent is shown when '--first-parent' option is given; + in that case, the output represents the changes the merge + brought _into_ the then-current branch. + -r:: Show recursive diffs. diff --git a/log-tree.c b/log-tree.c index 27afcf6972..d3ae969f60 100644 --- a/log-tree.c +++ b/log-tree.c @@ -514,6 +514,16 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log return 0; else if (opt->combine_merges) return do_diff_combined(opt, commit); + else if (opt->first_parent_only) { + /* + * Generate merge log entry only for the first + * parent, showing summary diff of the others + * we merged _in_. + */ + diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt); + log_tree_diff_flush(opt); + return !opt->loginfo; + } /* If we show individual diffs, show the parent info */ log->parent = parents->item; From 40dae3094db123be664c69b356b6e4f4e33d80e6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:29:27 -0800 Subject: [PATCH 0043/3211] builtin-for-each-ref.c: comment fixes The primary purpose of this is to get rid of stale comments that lamented the lack of callback parameter from for_each_ref() which we have already fixed. While at it we adjust the multi-line comment style to match the style convention. Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index a5a83f1469..3698e822c8 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -143,7 +143,8 @@ static const char *find_next(const char *cp) { while (*cp) { if (*cp == '%') { - /* %( is the start of an atom; + /* + * %( is the start of an atom; * %% is a quoted per-cent. */ if (cp[1] == '(') @@ -420,7 +421,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru grab_date(wholine, v, name); } - /* For a tag or a commit object, if "creator" or "creatordate" is + /* + * For a tag or a commit object, if "creator" or "creatordate" is * requested, do something special. */ if (strcmp(who, "tagger") && strcmp(who, "committer")) @@ -502,7 +504,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj } } -/* We want to have empty print-string for field requests +/* + * We want to have empty print-string for field requests * that do not apply (e.g. "authordate" for a tag object) */ static void fill_missing_values(struct atom_value *val) @@ -633,18 +636,21 @@ static void populate_value(struct refinfo *ref) if (!eaten) free(buf); - /* If there is no atom that wants to know about tagged + /* + * If there is no atom that wants to know about tagged * object, we are done. */ if (!need_tagged || (obj->type != OBJ_TAG)) return; - /* If it is a tag object, see if we use a value that derefs + /* + * If it is a tag object, see if we use a value that derefs * the object, and if we do grab the object it refers to. */ tagged = ((struct tag *)obj)->tagged->sha1; - /* NEEDSWORK: This derefs tag only once, which + /* + * NEEDSWORK: This derefs tag only once, which * is good to deal with chains of trust, but * is not consistent with what deref_tag() does * which peels the onion to the core. @@ -681,9 +687,8 @@ struct grab_ref_cbdata { }; /* - * A call-back given to for_each_ref(). It is unfortunate that we - * need to use global variables to pass extra information to this - * function. + * A call-back given to for_each_ref(). Filter refs and keep them for + * later object processing. */ static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { @@ -711,7 +716,8 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f return 0; } - /* We do not open the object yet; sort may only need refname + /* + * We do not open the object yet; sort may only need refname * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ From 20322e0b552b9860377c162675ce5c49bfd32e83 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:38:42 -0800 Subject: [PATCH 0044/3211] builtin-for-each-ref.c: check if we need to peel onion while parsing the format Instead of iterating over the parsed atoms that are used in the output format after all the parsing is done, check it while parsing the format string. --- builtin-for-each-ref.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 3698e822c8..d68977ee63 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -133,6 +133,8 @@ static int parse_atom(const char *atom, const char *ep) (sizeof(*used_atom_type) * used_atom_cnt)); used_atom[at] = xmemdupz(atom, ep - atom); used_atom_type[at] = valid_atom[i].cmp_type; + if (*atom == '*') + need_tagged = 1; return at; } @@ -944,13 +946,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) refs = cbdata.grab_array; num_refs = cbdata.grab_cnt; - for (i = 0; i < used_atom_cnt; i++) { - if (used_atom[i][0] == '*') { - need_tagged = 1; - break; - } - } - sort_refs(sort, refs, num_refs); if (!maxcount || num_refs < maxcount) From 5cdd628c84d808feb6ec407b2ecd74dfea63f865 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:57:08 -0800 Subject: [PATCH 0045/3211] for-each-ref --format='%(symref) %(symref:short)' New %(symref) output atom expands to the name of the ref a symbolic ref points at, or an empty string if the ref being shown is not a symref. This may help scripted Porcelain writers. Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index d68977ee63..b9b03e14d3 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -33,6 +33,8 @@ struct ref_sort { struct refinfo { char *refname; unsigned char objectname[20]; + int flag; + const char *symref; struct atom_value *value; }; @@ -68,6 +70,7 @@ static struct { { "body" }, { "contents" }, { "upstream" }, + { "symref" }, }; /* @@ -82,7 +85,7 @@ static struct { */ static const char **used_atom; static cmp_type *used_atom_type; -static int used_atom_cnt, sort_atom_limit, need_tagged; +static int used_atom_cnt, sort_atom_limit, need_tagged, need_symref; /* * Used to parse format string and sort specifiers @@ -135,6 +138,8 @@ static int parse_atom(const char *atom, const char *ep) used_atom_type[at] = valid_atom[i].cmp_type; if (*atom == '*') need_tagged = 1; + if (!strcmp(used_atom[at], "symref")) + need_symref = 1; return at; } @@ -566,6 +571,16 @@ static void populate_value(struct refinfo *ref) ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); + if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { + unsigned char unused1[20]; + const char *symref; + symref = resolve_ref(ref->refname, unused1, 1, NULL); + if (symref) + ref->symref = xstrdup(symref); + else + ref->symref = ""; + } + /* Fill in specials first */ for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; @@ -581,6 +596,8 @@ static void populate_value(struct refinfo *ref) if (!prefixcmp(name, "refname")) refname = ref->refname; + else if (!prefixcmp(name, "symref")) + refname = ref->symref ? ref->symref : ""; else if (!prefixcmp(name, "upstream")) { struct branch *branch; /* only local branches may have an upstream */ @@ -726,6 +743,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f ref = xcalloc(1, sizeof(*ref)); ref->refname = xstrdup(refname); hashcpy(ref->objectname, sha1); + ref->flag = flag; cnt = cb->grab_cnt; cb->grab_array = xrealloc(cb->grab_array, From 88fb7f27f6036028b3470e0e2a0efed1d9c7ee78 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 12:05:44 -0800 Subject: [PATCH 0046/3211] for-each-ref --format='%(flag)' This expands to "symref" or "packed" or an empty string, exposing the internal "flag" the for_each_ref() callback functions are called with. Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index b9b03e14d3..62be1bbfd6 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -71,6 +71,7 @@ static struct { { "contents" }, { "upstream" }, { "symref" }, + { "flag" }, }; /* @@ -558,6 +559,13 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v } } +static inline char *copy_advance(char *dst, const char *src) +{ + while (*src) + *dst++ = *src++; + return dst; +} + /* * Parse the object referred by ref, and grab needed value. */ @@ -610,6 +618,20 @@ static void populate_value(struct refinfo *ref) continue; refname = branch->merge[0]->dst; } + else if (!strcmp(name, "flag")) { + char buf[256], *cp = buf; + if (ref->flag & REF_ISSYMREF) + cp = copy_advance(cp, ",symref"); + if (ref->flag & REF_ISPACKED) + cp = copy_advance(cp, ",packed"); + if (cp == buf) + v->s = ""; + else { + *cp = '\0'; + v->s = xstrdup(buf + 1); + } + continue; + } else continue; From 318721e3acd8f9c06fde0d528b7547a99f65c4a1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 15:01:37 -0800 Subject: [PATCH 0047/3211] Start 1.7.1 cycle Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.1.txt | 17 +++++++++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes-1.7.1.txt diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes-1.7.1.txt new file mode 100644 index 0000000000..b0bf8366c0 --- /dev/null +++ b/Documentation/RelNotes-1.7.1.txt @@ -0,0 +1,17 @@ +Git v1.7.1 Release Notes +======================== + +Updates since v1.7.0 +-------------------- + +Fixes since v1.7.0 +------------------ + +All of the fixes in v1.7.0.X maintenance series are included in this +release, unless otherwise noted. + +--- +exec >/var/tmp/1 +echo O=$(git describe) +O=v1.7.0 +git shortlog --no-merges ^maint $O.. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 577e1fd20e..a668143d7a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.0 +DEF_VER=v1.7.0.GIT LF=' ' diff --git a/RelNotes b/RelNotes index 7b9bde663b..00e77229dd 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.7.0.txt \ No newline at end of file +Documentation/RelNotes-1.7.1.txt \ No newline at end of file From 9b2504831859ffdffbe15b9a6a7b7ede37557b06 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 15:04:00 -0800 Subject: [PATCH 0048/3211] Start 1.7.0 maintenance track Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.0.1.txt | 11 +++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes-1.7.0.1.txt diff --git a/Documentation/RelNotes-1.7.0.1.txt b/Documentation/RelNotes-1.7.0.1.txt new file mode 100644 index 0000000000..ab0f9764da --- /dev/null +++ b/Documentation/RelNotes-1.7.0.1.txt @@ -0,0 +1,11 @@ +Git v1.7.0.1 Release Notes +========================== + +Fixes since v1.7.0 +------------------ + +-- +exec >/var/tmp/1 +echo O=$(git describe) +O=v1.7.0 +git shortlog $O.. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 577e1fd20e..a668143d7a 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.0 +DEF_VER=v1.7.0.GIT LF=' ' diff --git a/RelNotes b/RelNotes index 7b9bde663b..9fadb0afe4 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.7.0.txt \ No newline at end of file +Documentation/RelNotes-1.7.0.1.txt \ No newline at end of file From 59332d13b2b23840452180368914921bffe9bfbc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 6 Feb 2010 10:40:08 -0800 Subject: [PATCH 0049/3211] Resurrect "git grep --no-index" This reverts commit 3c8f6c8 (Revert 30816237 and 7e62265, 2010-02-05) as the issue has been sorted out. --- builtin-grep.c | 40 +++++++++++++++++++++++++++++++++++++ git.c | 2 +- t/t7002-grep.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/builtin-grep.c b/builtin-grep.c index 26d4deb1cc..0ef849cb84 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -14,6 +14,7 @@ #include "userdiff.h" #include "grep.h" #include "quote.h" +#include "dir.h" #ifndef NO_PTHREADS #include "thread-utils.h" @@ -645,6 +646,24 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } +static int grep_directory(struct grep_opt *opt, const char **paths) +{ + struct dir_struct dir; + int i, hit = 0; + + memset(&dir, 0, sizeof(dir)); + setup_standard_excludes(&dir); + + fill_directory(&dir, paths); + for (i = 0; i < dir.nr; i++) { + hit |= grep_file(opt, dir.entries[i]->name); + if (hit && opt->status_only) + break; + } + free_grep_patterns(opt); + return hit; +} + static int context_callback(const struct option *opt, const char *arg, int unset) { @@ -739,9 +758,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) const char **paths = NULL; int i; int dummy; + int nongit = 0, use_index = 1; struct option options[] = { OPT_BOOLEAN(0, "cached", &cached, "search in index instead of in the work tree"), + OPT_BOOLEAN(0, "index", &use_index, + "--no-index finds in contents not managed by git"), OPT_GROUP(""), OPT_BOOLEAN('v', "invert-match", &opt.invert, "show non-matching lines"), @@ -824,6 +846,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; + prefix = setup_git_directory_gently(&nongit); + /* * 'git grep -h', unlike 'git grep -h ', is a request * to show usage information and exit. @@ -861,6 +885,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); + if (use_index && nongit) + /* die the same way as if we did it at the beginning */ + setup_git_directory(); + /* First unrecognized non-option token */ if (argc > 0 && !opt.pattern_list) { append_grep_pattern(&opt, argv[0], "command line", 0, @@ -922,6 +950,18 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } + if (!use_index) { + int hit; + if (cached) + die("--cached cannot be used with --no-index."); + if (list.nr) + die("--no-index cannot be used with revs."); + hit = grep_directory(&opt, paths); + if (use_threads) + hit |= wait_all(); + return !hit; + } + if (!list.nr) { int hit; if (!cached) diff --git a/git.c b/git.c index 4c3028c098..b3e23f1044 100644 --- a/git.c +++ b/git.c @@ -317,7 +317,7 @@ static void handle_internal_command(int argc, const char **argv) { "fsck-objects", cmd_fsck, RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, + { "grep", cmd_grep, USE_PAGER }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, { "index-pack", cmd_index_pack }, diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 7144f815c0..bf4d4dcb2b 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -434,4 +434,56 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' +test_expect_success 'outside of git repository' ' + rm -fr non && + mkdir -p non/git/sub && + echo hello >non/git/file1 && + echo world >non/git/sub/file2 && + echo ".*o*" >non/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >non/expect.full && + echo file2:world >non/expect.sub + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full + cd sub && + test_must_fail git grep o && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + +test_expect_success 'inside git repository but with --no-index' ' + rm -fr is && + mkdir -p is/git/sub && + echo hello >is/git/file1 && + echo world >is/git/sub/file2 && + echo ".*o*" >is/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >is/expect.full && + : >is/expect.empty && + echo file2:world >is/expect.sub + ( + cd is/git && + git init && + test_must_fail git grep o >../actual.full && + test_cmp ../expect.empty ../actual.full && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full && + cd sub && + test_must_fail git grep o >../../actual.sub && + test_cmp ../../expect.empty ../../actual.sub && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + test_done From 0ab1faae39173be6126364461c1be86542e6b17d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:09 +0100 Subject: [PATCH 0050/3211] Minor cosmetic fixes to notes.c Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/notes.c b/notes.c index 023adce982..47e38a10a8 100644 --- a/notes.c +++ b/notes.c @@ -1,7 +1,6 @@ #include "cache.h" #include "commit.h" #include "notes.h" -#include "refs.h" #include "utf8.h" #include "strbuf.h" #include "tree-walk.h" @@ -93,7 +92,7 @@ static void **note_tree_search(struct int_node **tree, i = GET_NIBBLE(*n, key_sha1); p = (*tree)->a[i]; - switch(GET_PTR_TYPE(p)) { + switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: *tree = CLR_PTR_TYPE(p); (*n)++; @@ -195,7 +194,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); - switch(GET_PTR_TYPE(*p)) { + switch (GET_PTR_TYPE(*p)) { case PTR_TYPE_NULL: assert(!*p); *p = SET_PTR_TYPE(entry, type); @@ -257,7 +256,7 @@ static void note_tree_free(struct int_node *tree) unsigned int i; for (i = 0; i < 16; i++) { void *p = tree->a[i]; - switch(GET_PTR_TYPE(p)) { + switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: note_tree_free(CLR_PTR_TYPE(p)); /* fall through */ @@ -274,7 +273,7 @@ static void note_tree_free(struct int_node *tree) * - hex_len - Length of above segment. Must be multiple of 2 between 0 and 40 * - sha1 - Partial SHA1 value is written here * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20 - * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format). + * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format)). * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2). * Pads sha1 with NULs up to sha1_len (not included in returned length). */ From a7e7eff66206829f7752c565198dbe6f40ef72a0 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:10 +0100 Subject: [PATCH 0051/3211] Notes API: get_commit_notes() -> format_note() + remove the commit restriction There is really no reason why only commit objects can be annotated. By changing the struct commit parameter to get_commit_notes() into a sha1 we gain the ability to annotate any object type. To reflect this in the function naming as well, we rename get_commit_notes() to format_note(). This patch also fixes comments and variable names throughout notes.c as a consequence of the removal of the unnecessary 'commit' restriction. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 33 ++++++++++++++++----------------- notes.h | 11 ++++++++++- pretty.c | 8 ++++---- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/notes.c b/notes.c index 47e38a10a8..4ee4fec233 100644 --- a/notes.c +++ b/notes.c @@ -1,5 +1,4 @@ #include "cache.h" -#include "commit.h" #include "notes.h" #include "utf8.h" #include "strbuf.h" @@ -24,10 +23,10 @@ struct int_node { /* * Leaf nodes come in two variants, note entries and subtree entries, * distinguished by the LSb of the leaf node pointer (see above). - * As a note entry, the key is the SHA1 of the referenced commit, and the + * As a note entry, the key is the SHA1 of the referenced object, and the * value is the SHA1 of the note object. * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the - * referenced commit, using the last byte of the key to store the length of + * referenced object, using the last byte of the key to store the length of * the prefix. The value is the SHA1 of the tree object containing the notes * subtree. */ @@ -210,7 +209,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (concatenate_notes(l->val_sha1, entry->val_sha1)) die("failed to concatenate note %s " - "into note %s for commit %s", + "into note %s for object %s", sha1_to_hex(entry->val_sha1), sha1_to_hex(l->val_sha1), sha1_to_hex(l->key_sha1)); @@ -298,7 +297,7 @@ static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, static void load_subtree(struct leaf_node *subtree, struct int_node *node, unsigned int n) { - unsigned char commit_sha1[20]; + unsigned char object_sha1[20]; unsigned int prefix_len; void *buf; struct tree_desc desc; @@ -311,23 +310,23 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, prefix_len = subtree->key_sha1[19]; assert(prefix_len * 2 >= n); - memcpy(commit_sha1, subtree->key_sha1, prefix_len); + memcpy(object_sha1, subtree->key_sha1, prefix_len); while (tree_entry(&desc, &entry)) { int len = get_sha1_hex_segment(entry.path, strlen(entry.path), - commit_sha1 + prefix_len, 20 - prefix_len); + object_sha1 + prefix_len, 20 - prefix_len); if (len < 0) continue; /* entry.path is not a SHA1 sum. Skip */ len += prefix_len; /* - * If commit SHA1 is complete (len == 20), assume note object - * If commit SHA1 is incomplete (len < 20), assume note subtree + * If object SHA1 is complete (len == 20), assume note object + * If object SHA1 is incomplete (len < 20), assume note subtree */ if (len <= 20) { unsigned char type = PTR_TYPE_NOTE; struct leaf_node *l = (struct leaf_node *) xcalloc(sizeof(struct leaf_node), 1); - hashcpy(l->key_sha1, commit_sha1); + hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, entry.sha1); if (len < 20) { if (!S_ISDIR(entry.mode)) @@ -343,12 +342,12 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, static void initialize_notes(const char *notes_ref_name) { - unsigned char sha1[20], commit_sha1[20]; + unsigned char sha1[20], object_sha1[20]; unsigned mode; struct leaf_node root_tree; - if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) || - get_tree_entry(commit_sha1, "", sha1, &mode)) + if (!notes_ref_name || read_ref(notes_ref_name, object_sha1) || + get_tree_entry(object_sha1, "", sha1, &mode)) return; hashclr(root_tree.key_sha1); @@ -356,9 +355,9 @@ static void initialize_notes(const char *notes_ref_name) load_subtree(&root_tree, &root_node, 0); } -static unsigned char *lookup_notes(const unsigned char *commit_sha1) +static unsigned char *lookup_notes(const unsigned char *object_sha1) { - struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1); + struct leaf_node *found = note_tree_find(&root_node, 0, object_sha1); if (found) return found->val_sha1; return NULL; @@ -371,7 +370,7 @@ void free_notes(void) initialized = 0; } -void get_commit_notes(const struct commit *commit, struct strbuf *sb, +void format_note(const unsigned char *object_sha1, struct strbuf *sb, const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; @@ -390,7 +389,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb, initialized = 1; } - sha1 = lookup_notes(commit->object.sha1); + sha1 = lookup_notes(object_sha1); if (!sha1) return; diff --git a/notes.h b/notes.h index a1421e351a..d745ed12da 100644 --- a/notes.h +++ b/notes.h @@ -4,10 +4,19 @@ /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); +/* Flags controlling how notes are formatted */ #define NOTES_SHOW_HEADER 1 #define NOTES_INDENT 2 -void get_commit_notes(const struct commit *commit, struct strbuf *sb, +/* + * Fill the given strbuf with the notes associated with the given object. + * + * If the internal notes structure is not initialized, it will be auto- + * initialized to the default value (see documentation for init_notes() above). + * + * 'flags' is a bitwise combination of the above formatting flags. + */ +void format_note(const unsigned char *object_sha1, struct strbuf *sb, const char *output_encoding, int flags); #endif diff --git a/pretty.c b/pretty.c index d493cade26..076b918b52 100644 --- a/pretty.c +++ b/pretty.c @@ -775,8 +775,8 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, } return 0; /* unknown %g placeholder */ case 'N': - get_commit_notes(commit, sb, git_log_output_encoding ? - git_log_output_encoding : git_commit_encoding, 0); + format_note(commit->object.sha1, sb, git_log_output_encoding ? + git_log_output_encoding : git_commit_encoding, 0); return 1; } @@ -1095,8 +1095,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, strbuf_addch(sb, '\n'); if (context->show_notes) - get_commit_notes(commit, sb, encoding, - NOTES_SHOW_HEADER | NOTES_INDENT); + format_note(commit->object.sha1, sb, encoding, + NOTES_SHOW_HEADER | NOTES_INDENT); free(reencoded); } From 3b78cdbe693092a58d9724deb14bd7be0dd4d7b3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:11 +0100 Subject: [PATCH 0052/3211] Add tests for checking correct handling of $GIT_NOTES_REF and core.notesRef Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3301-notes.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 5d9604b815..18aad53899 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -206,4 +206,52 @@ do ' done +test_expect_success 'create other note on a different notes ref (setup)' ' + : > a5 && + git add a5 && + test_tick && + git commit -m 5th && + GIT_NOTES_REF="refs/notes/other" git notes edit -m "other note" +' + +cat > expect-other << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th + +Notes: + other note +EOF + +cat > expect-not-other << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th +EOF + +test_expect_success 'Do not show note on other ref by default' ' + git log -1 > output && + test_cmp expect-not-other output +' + +test_expect_success 'Do show note when ref is given in GIT_NOTES_REF' ' + GIT_NOTES_REF="refs/notes/other" git log -1 > output && + test_cmp expect-other output +' + +test_expect_success 'Do show note when ref is given in core.notesRef config' ' + git config core.notesRef "refs/notes/other" && + git log -1 > output && + test_cmp expect-other output +' + +test_expect_success 'Do not show note when core.notesRef is overridden' ' + GIT_NOTES_REF="refs/notes/wrong" git log -1 > output && + test_cmp expect-not-other output +' + test_done From 709f79b0894859a6624e99b3a0c4714dd4ece494 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:12 +0100 Subject: [PATCH 0053/3211] Notes API: init_notes(): Initialize the notes tree from the given notes ref Created by a simple refactoring of initialize_notes(). Also add a new 'flags' parameter, which is a bitwise combination of notes initialization flags. For now, there is only one flag - NOTES_INIT_EMPTY - which indicates that the notes tree should not auto-load the contents of the given (or default) notes ref, but rather should leave the notes tree initialized to an empty state. This will become useful in the future when manipulating the notes tree through the notes API. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 30 ++++++++++++++++++------------ notes.h | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/notes.c b/notes.c index 4ee4fec233..3f4ae35340 100644 --- a/notes.c +++ b/notes.c @@ -340,15 +340,28 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, free(buf); } -static void initialize_notes(const char *notes_ref_name) +void init_notes(const char *notes_ref, int flags) { unsigned char sha1[20], object_sha1[20]; unsigned mode; struct leaf_node root_tree; - if (!notes_ref_name || read_ref(notes_ref_name, object_sha1) || - get_tree_entry(object_sha1, "", sha1, &mode)) + assert(!initialized); + initialized = 1; + + if (!notes_ref) + notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT); + if (!notes_ref) + notes_ref = notes_ref_name; /* value of core.notesRef config */ + if (!notes_ref) + notes_ref = GIT_NOTES_DEFAULT_REF; + + if (flags & NOTES_INIT_EMPTY || !notes_ref || + read_ref(notes_ref, object_sha1)) return; + if (get_tree_entry(object_sha1, "", sha1, &mode)) + die("Failed to read notes tree referenced by %s (%s)", + notes_ref, object_sha1); hashclr(root_tree.key_sha1); hashcpy(root_tree.val_sha1, sha1); @@ -379,15 +392,8 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb, unsigned long linelen, msglen; enum object_type type; - if (!initialized) { - const char *env = getenv(GIT_NOTES_REF_ENVIRONMENT); - if (env) - notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT); - else if (!notes_ref_name) - notes_ref_name = GIT_NOTES_DEFAULT_REF; - initialize_notes(notes_ref_name); - initialized = 1; - } + if (!initialized) + init_notes(NULL, 0); sha1 = lookup_notes(object_sha1); if (!sha1) diff --git a/notes.h b/notes.h index d745ed12da..6b527991b0 100644 --- a/notes.h +++ b/notes.h @@ -1,6 +1,26 @@ #ifndef NOTES_H #define NOTES_H +/* + * Flags controlling behaviour of notes tree initialization + * + * Default behaviour is to initialize the notes tree from the tree object + * specified by the given (or default) notes ref. + */ +#define NOTES_INIT_EMPTY 1 + +/* + * Initialize internal notes tree structure with the notes tree at the given + * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment + * variable is used, and if that is missing, the default notes ref is used + * ("refs/notes/commits"). + * + * If you need to re-intialize the internal notes tree structure (e.g. loading + * from a different notes ref), please first de-initialize the current notes + * tree by calling free_notes(). + */ +void init_notes(const char *notes_ref, int flags); + /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); From 2626b5367047d8b8d5c4555ec6b579ed37a6625d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:13 +0100 Subject: [PATCH 0054/3211] Notes API: add_note(): Add note objects to the internal notes tree structure Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 11 +++++++++++ notes.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/notes.c b/notes.c index 3f4ae35340..2c0d14ed8f 100644 --- a/notes.c +++ b/notes.c @@ -368,6 +368,17 @@ void init_notes(const char *notes_ref, int flags) load_subtree(&root_tree, &root_node, 0); } +void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1) +{ + struct leaf_node *l; + + assert(initialized); + l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); + hashcpy(l->key_sha1, object_sha1); + hashcpy(l->val_sha1, note_sha1); + note_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE); +} + static unsigned char *lookup_notes(const unsigned char *object_sha1) { struct leaf_node *found = note_tree_find(&root_node, 0, object_sha1); diff --git a/notes.h b/notes.h index 6b527991b0..5f2285217e 100644 --- a/notes.h +++ b/notes.h @@ -21,6 +21,10 @@ */ void init_notes(const char *notes_ref, int flags); +/* Add the given note object to the internal notes tree structure */ +void add_note(const unsigned char *object_sha1, + const unsigned char *note_sha1); + /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); From 1ec666b092d1df5691cd4d38f20aaeadd5049aa2 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:14 +0100 Subject: [PATCH 0055/3211] Notes API: remove_note(): Remove note objects from the notes tree structure This includes adding internal functions for maintaining a healthy notes tree structure after removing individual notes. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- notes.h | 3 ++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/notes.c b/notes.c index 2c0d14ed8f..2e82d71987 100644 --- a/notes.c +++ b/notes.c @@ -44,7 +44,7 @@ struct leaf_node { #define CLR_PTR_TYPE(ptr) ((void *) ((uintptr_t) (ptr) & ~3)) #define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type))) -#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((~n & 0x01) << 2)) & 0x0f) +#define GET_NIBBLE(n, sha1) (((sha1[(n) >> 1]) >> ((~(n) & 0x01) << 2)) & 0x0f) #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) @@ -249,6 +249,79 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, note_tree_insert(new_node, n + 1, entry, type); } +/* + * How to consolidate an int_node: + * If there are > 1 non-NULL entries, give up and return non-zero. + * Otherwise replace the int_node at the given index in the given parent node + * with the only entry (or a NULL entry if no entries) from the given tree, + * and return 0. + */ +static int note_tree_consolidate(struct int_node *tree, + struct int_node *parent, unsigned char index) +{ + unsigned int i; + void *p = NULL; + + assert(tree && parent); + assert(CLR_PTR_TYPE(parent->a[index]) == tree); + + for (i = 0; i < 16; i++) { + if (GET_PTR_TYPE(tree->a[i]) != PTR_TYPE_NULL) { + if (p) /* more than one entry */ + return -2; + p = tree->a[i]; + } + } + + /* replace tree with p in parent[index] */ + parent->a[index] = p; + free(tree); + return 0; +} + +/* + * To remove a leaf_node: + * Search to the tree location appropriate for the given leaf_node's key: + * - If location does not hold a matching entry, abort and do nothing. + * - Replace the matching leaf_node with a NULL entry (and free the leaf_node). + * - Consolidate int_nodes repeatedly, while walking up the tree towards root. + */ +static void note_tree_remove(struct int_node *tree, unsigned char n, + struct leaf_node *entry) +{ + struct leaf_node *l; + struct int_node *parent_stack[20]; + unsigned char i, j; + void **p = note_tree_search(&tree, &n, entry->key_sha1); + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE) + return; /* type mismatch, nothing to remove */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + if (hashcmp(l->key_sha1, entry->key_sha1)) + return; /* key mismatch, nothing to remove */ + + /* we have found a matching entry */ + free(l); + *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL); + + /* consolidate this tree level, and parent levels, if possible */ + if (!n) + return; /* cannot consolidate top level */ + /* first, build stack of ancestors between root and current node */ + parent_stack[0] = &root_node; + for (i = 0; i < n; i++) { + j = GET_NIBBLE(i, entry->key_sha1); + parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]); + } + assert(i == n && parent_stack[i] == tree); + /* next, unwind stack until note_tree_consolidate() is done */ + while (i > 0 && + !note_tree_consolidate(parent_stack[i], parent_stack[i - 1], + GET_NIBBLE(i - 1, entry->key_sha1))) + i--; +} + /* Free the entire notes data contained in the given tree */ static void note_tree_free(struct int_node *tree) { @@ -379,6 +452,16 @@ void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1) note_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE); } +void remove_note(const unsigned char *object_sha1) +{ + struct leaf_node l; + + assert(initialized); + hashcpy(l.key_sha1, object_sha1); + hashclr(l.val_sha1); + return note_tree_remove(&root_node, 0, &l); +} + static unsigned char *lookup_notes(const unsigned char *object_sha1) { struct leaf_node *found = note_tree_find(&root_node, 0, object_sha1); diff --git a/notes.h b/notes.h index 5f2285217e..9e66855222 100644 --- a/notes.h +++ b/notes.h @@ -25,6 +25,9 @@ void init_notes(const char *notes_ref, int flags); void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1); +/* Remove the given note object from the internal notes tree structure */ +void remove_note(const unsigned char *object_sha1); + /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); From 9b391f218a5b732a5a8abae87d3165e97fe2f6f6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:15 +0100 Subject: [PATCH 0056/3211] Notes API: get_note(): Return the note annotating the given object Created by a simple cleanup and rename of lookup_notes(). Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 15 ++++++++------- notes.h | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/notes.c b/notes.c index 2e82d71987..a0a85b4daf 100644 --- a/notes.c +++ b/notes.c @@ -462,12 +462,13 @@ void remove_note(const unsigned char *object_sha1) return note_tree_remove(&root_node, 0, &l); } -static unsigned char *lookup_notes(const unsigned char *object_sha1) +const unsigned char *get_note(const unsigned char *object_sha1) { - struct leaf_node *found = note_tree_find(&root_node, 0, object_sha1); - if (found) - return found->val_sha1; - return NULL; + struct leaf_node *found; + + assert(initialized); + found = note_tree_find(&root_node, 0, object_sha1); + return found ? found->val_sha1 : NULL; } void free_notes(void) @@ -481,7 +482,7 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb, const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; - unsigned char *sha1; + const unsigned char *sha1; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; @@ -489,7 +490,7 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb, if (!initialized) init_notes(NULL, 0); - sha1 = lookup_notes(object_sha1); + sha1 = get_note(object_sha1); if (!sha1) return; diff --git a/notes.h b/notes.h index 9e66855222..0041aecae0 100644 --- a/notes.h +++ b/notes.h @@ -28,6 +28,13 @@ void add_note(const unsigned char *object_sha1, /* Remove the given note object from the internal notes tree structure */ void remove_note(const unsigned char *object_sha1); +/* + * Get the note object SHA1 containing the note data for the given object + * + * Return NULL if the given object has no notes. + */ +const unsigned char *get_note(const unsigned char *object_sha1); + /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); From 73f77b909f87fcaece42ec50d8d0c1c35efbf947 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:16 +0100 Subject: [PATCH 0057/3211] Notes API: for_each_note(): Traverse the entire notes tree with a callback This includes a first attempt at creating an optimal fanout scheme (which is calculated on-the-fly, while traversing). Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ notes.h | 47 ++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/notes.c b/notes.c index a0a85b4daf..eabd6f30cd 100644 --- a/notes.c +++ b/notes.c @@ -413,6 +413,133 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, free(buf); } +/* + * Determine optimal on-disk fanout for this part of the notes tree + * + * Given a (sub)tree and the level in the internal tree structure, determine + * whether or not the given existing fanout should be expanded for this + * (sub)tree. + * + * Values of the 'fanout' variable: + * - 0: No fanout (all notes are stored directly in the root notes tree) + * - 1: 2/38 fanout + * - 2: 2/2/36 fanout + * - 3: 2/2/2/34 fanout + * etc. + */ +static unsigned char determine_fanout(struct int_node *tree, unsigned char n, + unsigned char fanout) +{ + /* + * The following is a simple heuristic that works well in practice: + * For each even-numbered 16-tree level (remember that each on-disk + * fanout level corresponds to _two_ 16-tree levels), peek at all 16 + * entries at that tree level. If all of them are either int_nodes or + * subtree entries, then there are likely plenty of notes below this + * level, so we return an incremented fanout. + */ + unsigned int i; + if ((n % 2) || (n > 2 * fanout)) + return fanout; + for (i = 0; i < 16; i++) { + switch (GET_PTR_TYPE(tree->a[i])) { + case PTR_TYPE_SUBTREE: + case PTR_TYPE_INTERNAL: + continue; + default: + return fanout; + } + } + return fanout + 1; +} + +static void construct_path_with_fanout(const unsigned char *sha1, + unsigned char fanout, char *path) +{ + unsigned int i = 0, j = 0; + const char *hex_sha1 = sha1_to_hex(sha1); + assert(fanout < 20); + while (fanout) { + path[i++] = hex_sha1[j++]; + path[i++] = hex_sha1[j++]; + path[i++] = '/'; + fanout--; + } + strcpy(path + i, hex_sha1 + j); +} + +static int for_each_note_helper(struct int_node *tree, unsigned char n, + unsigned char fanout, int flags, each_note_fn fn, + void *cb_data) +{ + unsigned int i; + void *p; + int ret = 0; + struct leaf_node *l; + static char path[40 + 19 + 1]; /* hex SHA1 + 19 * '/' + NUL */ + + fanout = determine_fanout(tree, n, fanout); + for (i = 0; i < 16; i++) { +redo: + p = tree->a[i]; + switch (GET_PTR_TYPE(p)) { + case PTR_TYPE_INTERNAL: + /* recurse into int_node */ + ret = for_each_note_helper(CLR_PTR_TYPE(p), n + 1, + fanout, flags, fn, cb_data); + break; + case PTR_TYPE_SUBTREE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + /* + * Subtree entries in the note tree represent parts of + * the note tree that have not yet been explored. There + * is a direct relationship between subtree entries at + * level 'n' in the tree, and the 'fanout' variable: + * Subtree entries at level 'n <= 2 * fanout' should be + * preserved, since they correspond exactly to a fanout + * directory in the on-disk structure. However, subtree + * entries at level 'n > 2 * fanout' should NOT be + * preserved, but rather consolidated into the above + * notes tree level. We achieve this by unconditionally + * unpacking subtree entries that exist below the + * threshold level at 'n = 2 * fanout'. + */ + if (n <= 2 * fanout && + flags & FOR_EACH_NOTE_YIELD_SUBTREES) { + /* invoke callback with subtree */ + unsigned int path_len = + l->key_sha1[19] * 2 + fanout; + assert(path_len < 40 + 19); + construct_path_with_fanout(l->key_sha1, fanout, + path); + /* Create trailing slash, if needed */ + if (path[path_len - 1] != '/') + path[path_len++] = '/'; + path[path_len] = '\0'; + ret = fn(l->key_sha1, l->val_sha1, path, + cb_data); + } + if (n > fanout * 2 || + !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) { + /* unpack subtree and resume traversal */ + tree->a[i] = NULL; + load_subtree(l, tree, n); + free(l); + goto redo; + } + break; + case PTR_TYPE_NOTE: + l = (struct leaf_node *) CLR_PTR_TYPE(p); + construct_path_with_fanout(l->key_sha1, fanout, path); + ret = fn(l->key_sha1, l->val_sha1, path, cb_data); + break; + } + if (ret) + return ret; + } + return 0; +} + void init_notes(const char *notes_ref, int flags) { unsigned char sha1[20], object_sha1[20]; @@ -471,6 +598,12 @@ const unsigned char *get_note(const unsigned char *object_sha1) return found ? found->val_sha1 : NULL; } +int for_each_note(int flags, each_note_fn fn, void *cb_data) +{ + assert(initialized); + return for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data); +} + void free_notes(void) { note_tree_free(&root_node); diff --git a/notes.h b/notes.h index 0041aecae0..2131912902 100644 --- a/notes.h +++ b/notes.h @@ -35,6 +35,53 @@ void remove_note(const unsigned char *object_sha1); */ const unsigned char *get_note(const unsigned char *object_sha1); +/* + * Flags controlling behaviour of for_each_note() + * + * Default behaviour of for_each_note() is to traverse every single note object + * in the notes tree, unpacking subtree entries along the way. + * The following flags can be used to alter the default behaviour: + * + * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into + * subtree entries while traversing the notes tree. This causes notes within + * those subtrees NOT to be passed to the callback. Use this flag if you + * don't want to traverse _all_ notes, but only want to traverse the parts + * of the notes tree that have already been unpacked (this includes at least + * all notes that have been added/changed). + * + * - YIELD_SUBTREES causes any subtree entries that are encountered to be + * passed to the callback, before recursing into them. Subtree entries are + * not note objects, but represent intermediate directories in the notes + * tree. When passed to the callback, subtree entries will have a trailing + * slash in their path, which the callback may use to differentiate between + * note entries and subtree entries. Note that already-unpacked subtree + * entries are not part of the notes tree, and will therefore not be yielded. + * If this flag is used together with DONT_UNPACK_SUBTREES, for_each_note() + * will yield the subtree entry, but not recurse into it. + */ +#define FOR_EACH_NOTE_DONT_UNPACK_SUBTREES 1 +#define FOR_EACH_NOTE_YIELD_SUBTREES 2 + +/* + * Invoke the specified callback function for each note + * + * If the callback returns nonzero, the note walk is aborted, and the return + * value from the callback is returned from for_each_note(). Hence, a zero + * return value from for_each_note() indicates that all notes were walked + * successfully. + * + * IMPORTANT: The callback function is NOT allowed to change the notes tree. + * In other words, the following functions can NOT be invoked (on the current + * notes tree) from within the callback: + * - add_note() + * - remove_note() + * - free_notes() + */ +typedef int each_note_fn(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data); +int for_each_note(int flags, each_note_fn fn, void *cb_data); + /* Free (and de-initialize) the internal notes tree structure */ void free_notes(void); From 61a7cca0c6504aee7bae7837582230561bdb81d4 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:17 +0100 Subject: [PATCH 0058/3211] Notes API: write_notes_tree(): Store the notes tree in the database Uses for_each_note() to traverse the notes tree, and produces tree objects on the fly representing the "on-disk" version of the notes tree with appropriate fanout. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ notes.h | 38 +++++++++++++-- 2 files changed, 180 insertions(+), 3 deletions(-) diff --git a/notes.c b/notes.c index eabd6f30cd..b576f7e624 100644 --- a/notes.c +++ b/notes.c @@ -1,5 +1,6 @@ #include "cache.h" #include "notes.h" +#include "tree.h" #include "utf8.h" #include "strbuf.h" #include "tree-walk.h" @@ -540,6 +541,126 @@ redo: return 0; } +struct tree_write_stack { + struct tree_write_stack *next; + struct strbuf buf; + char path[2]; /* path to subtree in next, if any */ +}; + +static inline int matches_tree_write_stack(struct tree_write_stack *tws, + const char *full_path) +{ + return full_path[0] == tws->path[0] && + full_path[1] == tws->path[1] && + full_path[2] == '/'; +} + +static void write_tree_entry(struct strbuf *buf, unsigned int mode, + const char *path, unsigned int path_len, const + unsigned char *sha1) +{ + strbuf_addf(buf, "%06o %.*s%c", mode, path_len, path, '\0'); + strbuf_add(buf, sha1, 20); +} + +static void tree_write_stack_init_subtree(struct tree_write_stack *tws, + const char *path) +{ + struct tree_write_stack *n; + assert(!tws->next); + assert(tws->path[0] == '\0' && tws->path[1] == '\0'); + n = (struct tree_write_stack *) + xmalloc(sizeof(struct tree_write_stack)); + n->next = NULL; + strbuf_init(&n->buf, 256 * (32 + 40)); /* assume 256 entries per tree */ + n->path[0] = n->path[1] = '\0'; + tws->next = n; + tws->path[0] = path[0]; + tws->path[1] = path[1]; +} + +static int tree_write_stack_finish_subtree(struct tree_write_stack *tws) +{ + int ret; + struct tree_write_stack *n = tws->next; + unsigned char s[20]; + if (n) { + ret = tree_write_stack_finish_subtree(n); + if (ret) + return ret; + ret = write_sha1_file(n->buf.buf, n->buf.len, tree_type, s); + if (ret) + return ret; + strbuf_release(&n->buf); + free(n); + tws->next = NULL; + write_tree_entry(&tws->buf, 040000, tws->path, 2, s); + tws->path[0] = tws->path[1] = '\0'; + } + return 0; +} + +static int write_each_note_helper(struct tree_write_stack *tws, + const char *path, unsigned int mode, + const unsigned char *sha1) +{ + size_t path_len = strlen(path); + unsigned int n = 0; + int ret; + + /* Determine common part of tree write stack */ + while (tws && 3 * n < path_len && + matches_tree_write_stack(tws, path + 3 * n)) { + n++; + tws = tws->next; + } + + /* tws point to last matching tree_write_stack entry */ + ret = tree_write_stack_finish_subtree(tws); + if (ret) + return ret; + + /* Start subtrees needed to satisfy path */ + while (3 * n + 2 < path_len && path[3 * n + 2] == '/') { + tree_write_stack_init_subtree(tws, path + 3 * n); + n++; + tws = tws->next; + } + + /* There should be no more directory components in the given path */ + assert(memchr(path + 3 * n, '/', path_len - (3 * n)) == NULL); + + /* Finally add given entry to the current tree object */ + write_tree_entry(&tws->buf, mode, path + 3 * n, path_len - (3 * n), + sha1); + + return 0; +} + +struct write_each_note_data { + struct tree_write_stack *root; +}; + +static int write_each_note(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + struct write_each_note_data *d = + (struct write_each_note_data *) cb_data; + size_t note_path_len = strlen(note_path); + unsigned int mode = 0100644; + + if (note_path[note_path_len - 1] == '/') { + /* subtree entry */ + note_path_len--; + note_path[note_path_len] = '\0'; + mode = 040000; + } + assert(note_path_len <= 40 + 19); + + return write_each_note_helper(d->root, note_path, mode, note_sha1); +} + void init_notes(const char *notes_ref, int flags) { unsigned char sha1[20], object_sha1[20]; @@ -604,6 +725,30 @@ int for_each_note(int flags, each_note_fn fn, void *cb_data) return for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data); } +int write_notes_tree(unsigned char *result) +{ + struct tree_write_stack root; + struct write_each_note_data cb_data; + int ret; + + assert(initialized); + + /* Prepare for traversal of current notes tree */ + root.next = NULL; /* last forward entry in list is grounded */ + strbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */ + root.path[0] = root.path[1] = '\0'; + cb_data.root = &root; + + /* Write tree objects representing current notes tree */ + ret = for_each_note(FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | + FOR_EACH_NOTE_YIELD_SUBTREES, + write_each_note, &cb_data) || + tree_write_stack_finish_subtree(&root) || + write_sha1_file(root.buf.buf, root.buf.len, tree_type, result); + strbuf_release(&root.buf); + return ret; +} + void free_notes(void) { note_tree_free(&root_node); diff --git a/notes.h b/notes.h index 2131912902..c49b7a512f 100644 --- a/notes.h +++ b/notes.h @@ -21,11 +21,23 @@ */ void init_notes(const char *notes_ref, int flags); -/* Add the given note object to the internal notes tree structure */ +/* + * Add the given note object to the internal notes tree structure + * + * IMPORTANT: The changes made by add_note() to the internal notes tree structure + * are not persistent until a subsequent call to write_notes_tree() returns + * zero. + */ void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1); -/* Remove the given note object from the internal notes tree structure */ +/* + * Remove the given note object from the internal notes tree structure + * + * IMPORTANT: The changes made by remove_note() to the internal notes tree + * structure are not persistent until a subsequent call to write_notes_tree() + * returns zero. + */ void remove_note(const unsigned char *object_sha1); /* @@ -82,7 +94,27 @@ typedef int each_note_fn(const unsigned char *object_sha1, void *cb_data); int for_each_note(int flags, each_note_fn fn, void *cb_data); -/* Free (and de-initialize) the internal notes tree structure */ +/* + * Write the internal notes tree structure to the object database + * + * Creates a new tree object encapsulating the current state of the + * internal notes tree, and stores its SHA1 into the 'result' argument. + * + * Returns zero on success, non-zero on failure. + * + * IMPORTANT: Changes made to the internal notes tree structure are not + * persistent until this function has returned zero. Please also remember + * to create a corresponding commit object, and update the appropriate + * notes ref. + */ +int write_notes_tree(unsigned char *result); + +/* + * Free (and de-initialize) the internal notes tree structure + * + * IMPORTANT: Changes made to the notes tree since the last, successful + * call to write_notes_tree() will be lost. + */ void free_notes(void); /* Flags controlling how notes are formatted */ From cd30539214bb09881b84c796a50d30e409dee3fa Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:18 +0100 Subject: [PATCH 0059/3211] Notes API: Allow multiple concurrent notes trees with new struct notes_tree The new struct notes_tree encapsulates access to a specific notes tree. It is provided to allow callers to make use of several different notes trees simultaneously. A struct notes_tree * parameter is added to every function in the notes API. In all cases, NULL can be passed, in which case the fallback "default" notes tree (default_notes_tree) is used. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 90 +++++++++++++++++++++++++++++++++++--------------------- notes.h | 81 ++++++++++++++++++++++++++++++++------------------ pretty.c | 7 +++-- 3 files changed, 112 insertions(+), 66 deletions(-) diff --git a/notes.c b/notes.c index b576f7e624..08a369af82 100644 --- a/notes.c +++ b/notes.c @@ -50,9 +50,7 @@ struct leaf_node { #define SUBTREE_SHA1_PREFIXCMP(key_sha1, subtree_sha1) \ (memcmp(key_sha1, subtree_sha1, subtree_sha1[19])) -static struct int_node root_node; - -static int initialized; +struct notes_tree default_notes_tree; static void load_subtree(struct leaf_node *subtree, struct int_node *node, unsigned int n); @@ -287,8 +285,8 @@ static int note_tree_consolidate(struct int_node *tree, * - Replace the matching leaf_node with a NULL entry (and free the leaf_node). * - Consolidate int_nodes repeatedly, while walking up the tree towards root. */ -static void note_tree_remove(struct int_node *tree, unsigned char n, - struct leaf_node *entry) +static void note_tree_remove(struct notes_tree *t, struct int_node *tree, + unsigned char n, struct leaf_node *entry) { struct leaf_node *l; struct int_node *parent_stack[20]; @@ -310,7 +308,7 @@ static void note_tree_remove(struct int_node *tree, unsigned char n, if (!n) return; /* cannot consolidate top level */ /* first, build stack of ancestors between root and current node */ - parent_stack[0] = &root_node; + parent_stack[0] = t->root; for (i = 0; i < n; i++) { j = GET_NIBBLE(i, entry->key_sha1); parent_stack[i + 1] = CLR_PTR_TYPE(parent_stack[i]->a[j]); @@ -661,14 +659,15 @@ static int write_each_note(const unsigned char *object_sha1, return write_each_note_helper(d->root, note_path, mode, note_sha1); } -void init_notes(const char *notes_ref, int flags) +void init_notes(struct notes_tree *t, const char *notes_ref, int flags) { unsigned char sha1[20], object_sha1[20]; unsigned mode; struct leaf_node root_tree; - assert(!initialized); - initialized = 1; + if (!t) + t = &default_notes_tree; + assert(!t->initialized); if (!notes_ref) notes_ref = getenv(GIT_NOTES_REF_ENVIRONMENT); @@ -677,6 +676,10 @@ void init_notes(const char *notes_ref, int flags) if (!notes_ref) notes_ref = GIT_NOTES_DEFAULT_REF; + t->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + t->ref = notes_ref ? xstrdup(notes_ref) : NULL; + t->initialized = 1; + if (flags & NOTES_INIT_EMPTY || !notes_ref || read_ref(notes_ref, object_sha1)) return; @@ -686,52 +689,65 @@ void init_notes(const char *notes_ref, int flags) hashclr(root_tree.key_sha1); hashcpy(root_tree.val_sha1, sha1); - load_subtree(&root_tree, &root_node, 0); + load_subtree(&root_tree, t->root, 0); } -void add_note(const unsigned char *object_sha1, const unsigned char *note_sha1) +void add_note(struct notes_tree *t, const unsigned char *object_sha1, + const unsigned char *note_sha1) { struct leaf_node *l; - assert(initialized); + if (!t) + t = &default_notes_tree; + assert(t->initialized); l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, note_sha1); - note_tree_insert(&root_node, 0, l, PTR_TYPE_NOTE); + note_tree_insert(t->root, 0, l, PTR_TYPE_NOTE); } -void remove_note(const unsigned char *object_sha1) +void remove_note(struct notes_tree *t, const unsigned char *object_sha1) { struct leaf_node l; - assert(initialized); + if (!t) + t = &default_notes_tree; + assert(t->initialized); hashcpy(l.key_sha1, object_sha1); hashclr(l.val_sha1); - return note_tree_remove(&root_node, 0, &l); + return note_tree_remove(t, t->root, 0, &l); } -const unsigned char *get_note(const unsigned char *object_sha1) +const unsigned char *get_note(struct notes_tree *t, + const unsigned char *object_sha1) { struct leaf_node *found; - assert(initialized); - found = note_tree_find(&root_node, 0, object_sha1); + if (!t) + t = &default_notes_tree; + assert(t->initialized); + found = note_tree_find(t->root, 0, object_sha1); return found ? found->val_sha1 : NULL; } -int for_each_note(int flags, each_note_fn fn, void *cb_data) +int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, + void *cb_data) { - assert(initialized); - return for_each_note_helper(&root_node, 0, 0, flags, fn, cb_data); + if (!t) + t = &default_notes_tree; + assert(t->initialized); + return for_each_note_helper(t->root, 0, 0, flags, fn, cb_data); } -int write_notes_tree(unsigned char *result) +int write_notes_tree(struct notes_tree *t, unsigned char *result) { struct tree_write_stack root; struct write_each_note_data cb_data; int ret; - assert(initialized); + if (!t) + t = &default_notes_tree; + assert(t->initialized); /* Prepare for traversal of current notes tree */ root.next = NULL; /* last forward entry in list is grounded */ @@ -740,7 +756,7 @@ int write_notes_tree(unsigned char *result) cb_data.root = &root; /* Write tree objects representing current notes tree */ - ret = for_each_note(FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | + ret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | FOR_EACH_NOTE_YIELD_SUBTREES, write_each_note, &cb_data) || tree_write_stack_finish_subtree(&root) || @@ -749,15 +765,19 @@ int write_notes_tree(unsigned char *result) return ret; } -void free_notes(void) +void free_notes(struct notes_tree *t) { - note_tree_free(&root_node); - memset(&root_node, 0, sizeof(struct int_node)); - initialized = 0; + if (!t) + t = &default_notes_tree; + if (t->root) + note_tree_free(t->root); + free(t->root); + free(t->ref); + memset(t, 0, sizeof(struct notes_tree)); } -void format_note(const unsigned char *object_sha1, struct strbuf *sb, - const char *output_encoding, int flags) +void format_note(struct notes_tree *t, const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; const unsigned char *sha1; @@ -765,10 +785,12 @@ void format_note(const unsigned char *object_sha1, struct strbuf *sb, unsigned long linelen, msglen; enum object_type type; - if (!initialized) - init_notes(NULL, 0); + if (!t) + t = &default_notes_tree; + if (!t->initialized) + init_notes(t, NULL, 0); - sha1 = get_note(object_sha1); + sha1 = get_note(t, object_sha1); if (!sha1) return; diff --git a/notes.h b/notes.h index c49b7a512f..12acc38b08 100644 --- a/notes.h +++ b/notes.h @@ -1,6 +1,21 @@ #ifndef NOTES_H #define NOTES_H +/* + * Notes tree object + * + * Encapsulates the internal notes tree structure associated with a notes ref. + * Whenever a struct notes_tree pointer is required below, you may pass NULL in + * order to use the default/internal notes tree. E.g. you only need to pass a + * non-NULL value if you need to refer to several different notes trees + * simultaneously. + */ +extern struct notes_tree { + struct int_node *root; + char *ref; + int initialized; +} default_notes_tree; + /* * Flags controlling behaviour of notes tree initialization * @@ -10,48 +25,54 @@ #define NOTES_INIT_EMPTY 1 /* - * Initialize internal notes tree structure with the notes tree at the given + * Initialize the given notes_tree with the notes tree structure at the given * ref. If given ref is NULL, the value of the $GIT_NOTES_REF environment * variable is used, and if that is missing, the default notes ref is used * ("refs/notes/commits"). * - * If you need to re-intialize the internal notes tree structure (e.g. loading - * from a different notes ref), please first de-initialize the current notes - * tree by calling free_notes(). + * If you need to re-intialize a notes_tree structure (e.g. when switching from + * one notes ref to another), you must first de-initialize the notes_tree + * structure by calling free_notes(struct notes_tree *). + * + * If you pass t == NULL, the default internal notes_tree will be initialized. + * + * Precondition: The notes_tree structure is zeroed (this can be achieved with + * memset(t, 0, sizeof(struct notes_tree))) */ -void init_notes(const char *notes_ref, int flags); +void init_notes(struct notes_tree *t, const char *notes_ref, int flags); /* - * Add the given note object to the internal notes tree structure + * Add the given note object to the given notes_tree structure * - * IMPORTANT: The changes made by add_note() to the internal notes tree structure + * IMPORTANT: The changes made by add_note() to the given notes_tree structure * are not persistent until a subsequent call to write_notes_tree() returns * zero. */ -void add_note(const unsigned char *object_sha1, +void add_note(struct notes_tree *t, const unsigned char *object_sha1, const unsigned char *note_sha1); /* - * Remove the given note object from the internal notes tree structure + * Remove the given note object from the given notes_tree structure * - * IMPORTANT: The changes made by remove_note() to the internal notes tree + * IMPORTANT: The changes made by remove_note() to the given notes_tree * structure are not persistent until a subsequent call to write_notes_tree() * returns zero. */ -void remove_note(const unsigned char *object_sha1); +void remove_note(struct notes_tree *t, const unsigned char *object_sha1); /* * Get the note object SHA1 containing the note data for the given object * * Return NULL if the given object has no notes. */ -const unsigned char *get_note(const unsigned char *object_sha1); +const unsigned char *get_note(struct notes_tree *t, + const unsigned char *object_sha1); /* * Flags controlling behaviour of for_each_note() * * Default behaviour of for_each_note() is to traverse every single note object - * in the notes tree, unpacking subtree entries along the way. + * in the given notes tree, unpacking subtree entries along the way. * The following flags can be used to alter the default behaviour: * * - DONT_UNPACK_SUBTREES causes for_each_note() NOT to unpack and recurse into @@ -75,7 +96,7 @@ const unsigned char *get_note(const unsigned char *object_sha1); #define FOR_EACH_NOTE_YIELD_SUBTREES 2 /* - * Invoke the specified callback function for each note + * Invoke the specified callback function for each note in the given notes_tree * * If the callback returns nonzero, the note walk is aborted, and the return * value from the callback is returned from for_each_note(). Hence, a zero @@ -92,30 +113,30 @@ const unsigned char *get_note(const unsigned char *object_sha1); typedef int each_note_fn(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data); -int for_each_note(int flags, each_note_fn fn, void *cb_data); +int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, + void *cb_data); /* - * Write the internal notes tree structure to the object database + * Write the given notes_tree structure to the object database * - * Creates a new tree object encapsulating the current state of the - * internal notes tree, and stores its SHA1 into the 'result' argument. + * Creates a new tree object encapsulating the current state of the given + * notes_tree, and stores its SHA1 into the 'result' argument. * * Returns zero on success, non-zero on failure. * - * IMPORTANT: Changes made to the internal notes tree structure are not - * persistent until this function has returned zero. Please also remember - * to create a corresponding commit object, and update the appropriate - * notes ref. + * IMPORTANT: Changes made to the given notes_tree are not persistent until + * this function has returned zero. Please also remember to create a + * corresponding commit object, and update the appropriate notes ref. */ -int write_notes_tree(unsigned char *result); +int write_notes_tree(struct notes_tree *t, unsigned char *result); /* - * Free (and de-initialize) the internal notes tree structure + * Free (and de-initialize) the given notes_tree structure * - * IMPORTANT: Changes made to the notes tree since the last, successful + * IMPORTANT: Changes made to the given notes_tree since the last, successful * call to write_notes_tree() will be lost. */ -void free_notes(void); +void free_notes(struct notes_tree *t); /* Flags controlling how notes are formatted */ #define NOTES_SHOW_HEADER 1 @@ -124,12 +145,14 @@ void free_notes(void); /* * Fill the given strbuf with the notes associated with the given object. * - * If the internal notes structure is not initialized, it will be auto- + * If the given notes_tree structure is not initialized, it will be auto- * initialized to the default value (see documentation for init_notes() above). + * If the given notes_tree is NULL, the internal/default notes_tree will be + * used instead. * * 'flags' is a bitwise combination of the above formatting flags. */ -void format_note(const unsigned char *object_sha1, struct strbuf *sb, - const char *output_encoding, int flags); +void format_note(struct notes_tree *t, const unsigned char *object_sha1, + struct strbuf *sb, const char *output_encoding, int flags); #endif diff --git a/pretty.c b/pretty.c index 076b918b52..f999485a54 100644 --- a/pretty.c +++ b/pretty.c @@ -775,8 +775,9 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, } return 0; /* unknown %g placeholder */ case 'N': - format_note(commit->object.sha1, sb, git_log_output_encoding ? - git_log_output_encoding : git_commit_encoding, 0); + format_note(NULL, commit->object.sha1, sb, + git_log_output_encoding ? git_log_output_encoding + : git_commit_encoding, 0); return 1; } @@ -1095,7 +1096,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, strbuf_addch(sb, '\n'); if (context->show_notes) - format_note(commit->object.sha1, sb, encoding, + format_note(NULL, commit->object.sha1, sb, encoding, NOTES_SHOW_HEADER | NOTES_INDENT); free(reencoded); From 73f464b5f3fe4dd5109b9fb9e58c1fe55393902d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:19 +0100 Subject: [PATCH 0060/3211] Refactor notes concatenation into a flexible interface for combining notes When adding a note to an object that already has an existing note, the current solution is to concatenate the contents of the two notes. However, the caller may instead wish to _overwrite_ the existing note with the new note, or maybe even _ignore_ the new note, and keep the existing one. There might also be other ways of combining notes that are only known to the caller. Therefore, instead of unconditionally concatenating notes, we let the caller specify how to combine notes, by passing in a pointer to a function for combining notes. The caller may choose to implement its own function for notes combining, but normally one of the following three conveniently supplied notes combination functions will be sufficient: - combine_notes_concatenate() combines the two notes by appending the contents of the new note to the contents of the existing note. - combine_notes_overwrite() replaces the existing note with the new note. - combine_notes_ignore() keeps the existing note, and ignores the new note. A combine_notes function can be passed to init_notes() to choose a default combine_notes function for that notes tree. If NULL is given, the notes tree falls back to combine_notes_concatenate() as the ultimate default. A combine_notes function can also be passed directly to add_note(), to control the notes combining behaviour for a note addition in particular. If NULL is passed, the combine_notes function registered for the given notes tree is used. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 138 ++++++++++++++++++++++++++++++++------------------------ notes.h | 34 +++++++++++++- 2 files changed, 112 insertions(+), 60 deletions(-) diff --git a/notes.c b/notes.c index 08a369af82..dc4e4f619f 100644 --- a/notes.c +++ b/notes.c @@ -1,5 +1,6 @@ #include "cache.h" #include "notes.h" +#include "blob.h" #include "tree.h" #include "utf8.h" #include "strbuf.h" @@ -127,55 +128,12 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, return NULL; } -/* Create a new blob object by concatenating the two given blob objects */ -static int concatenate_notes(unsigned char *cur_sha1, - const unsigned char *new_sha1) -{ - char *cur_msg, *new_msg, *buf; - unsigned long cur_len, new_len, buf_len; - enum object_type cur_type, new_type; - int ret; - - /* read in both note blob objects */ - new_msg = read_sha1_file(new_sha1, &new_type, &new_len); - if (!new_msg || !new_len || new_type != OBJ_BLOB) { - free(new_msg); - return 0; - } - cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); - if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { - free(cur_msg); - free(new_msg); - hashcpy(cur_sha1, new_sha1); - return 0; - } - - /* we will separate the notes by a newline anyway */ - if (cur_msg[cur_len - 1] == '\n') - cur_len--; - - /* concatenate cur_msg and new_msg into buf */ - buf_len = cur_len + 1 + new_len; - buf = (char *) xmalloc(buf_len); - memcpy(buf, cur_msg, cur_len); - buf[cur_len] = '\n'; - memcpy(buf + cur_len + 1, new_msg, new_len); - - free(cur_msg); - free(new_msg); - - /* create a new blob object from buf */ - ret = write_sha1_file(buf, buf_len, "blob", cur_sha1); - free(buf); - return ret; -} - /* * To insert a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location is unused (NULL), store the tweaked pointer directly there * - If location holds a note entry that matches the note-to-be-inserted, then - * concatenate the two notes. + * combine the two notes (by calling the given combine_notes function). * - If location holds a note entry that matches the subtree-to-be-inserted, * then unpack the subtree-to-be-inserted into the location. * - If location holds a matching subtree entry, unpack the subtree at that @@ -184,7 +142,8 @@ static int concatenate_notes(unsigned char *cur_sha1, * node-to-be-inserted, and store the new int_node into the location. */ static void note_tree_insert(struct int_node *tree, unsigned char n, - struct leaf_node *entry, unsigned char type) + struct leaf_node *entry, unsigned char type, + combine_notes_fn combine_notes) { struct int_node *new_node; struct leaf_node *l; @@ -205,12 +164,11 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!hashcmp(l->val_sha1, entry->val_sha1)) return; - if (concatenate_notes(l->val_sha1, - entry->val_sha1)) - die("failed to concatenate note %s " - "into note %s for object %s", - sha1_to_hex(entry->val_sha1), + if (combine_notes(l->val_sha1, entry->val_sha1)) + die("failed to combine notes %s and %s" + " for object %s", sha1_to_hex(l->val_sha1), + sha1_to_hex(entry->val_sha1), sha1_to_hex(l->key_sha1)); free(entry); return; @@ -233,7 +191,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, *p = NULL; load_subtree(l, tree, n); free(l); - note_tree_insert(tree, n, entry, type); + note_tree_insert(tree, n, entry, type, combine_notes); return; } break; @@ -243,9 +201,9 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); - note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p)); + note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes); *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - note_tree_insert(new_node, n + 1, entry, type); + note_tree_insert(new_node, n + 1, entry, type, combine_notes); } /* @@ -406,7 +364,8 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - note_tree_insert(node, n, l, type); + note_tree_insert(node, n, l, type, + combine_notes_concatenate); } } free(buf); @@ -659,7 +618,64 @@ static int write_each_note(const unsigned char *object_sha1, return write_each_note_helper(d->root, note_path, mode, note_sha1); } -void init_notes(struct notes_tree *t, const char *notes_ref, int flags) +int combine_notes_concatenate(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + char *cur_msg = NULL, *new_msg = NULL, *buf; + unsigned long cur_len, new_len, buf_len; + enum object_type cur_type, new_type; + int ret; + + /* read in both note blob objects */ + if (!is_null_sha1(new_sha1)) + new_msg = read_sha1_file(new_sha1, &new_type, &new_len); + if (!new_msg || !new_len || new_type != OBJ_BLOB) { + free(new_msg); + return 0; + } + if (!is_null_sha1(cur_sha1)) + cur_msg = read_sha1_file(cur_sha1, &cur_type, &cur_len); + if (!cur_msg || !cur_len || cur_type != OBJ_BLOB) { + free(cur_msg); + free(new_msg); + hashcpy(cur_sha1, new_sha1); + return 0; + } + + /* we will separate the notes by a newline anyway */ + if (cur_msg[cur_len - 1] == '\n') + cur_len--; + + /* concatenate cur_msg and new_msg into buf */ + buf_len = cur_len + 1 + new_len; + buf = (char *) xmalloc(buf_len); + memcpy(buf, cur_msg, cur_len); + buf[cur_len] = '\n'; + memcpy(buf + cur_len + 1, new_msg, new_len); + free(cur_msg); + free(new_msg); + + /* create a new blob object from buf */ + ret = write_sha1_file(buf, buf_len, blob_type, cur_sha1); + free(buf); + return ret; +} + +int combine_notes_overwrite(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + hashcpy(cur_sha1, new_sha1); + return 0; +} + +int combine_notes_ignore(unsigned char *cur_sha1, + const unsigned char *new_sha1) +{ + return 0; +} + +void init_notes(struct notes_tree *t, const char *notes_ref, + combine_notes_fn combine_notes, int flags) { unsigned char sha1[20], object_sha1[20]; unsigned mode; @@ -676,8 +692,12 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags) if (!notes_ref) notes_ref = GIT_NOTES_DEFAULT_REF; + if (!combine_notes) + combine_notes = combine_notes_concatenate; + t->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1); t->ref = notes_ref ? xstrdup(notes_ref) : NULL; + t->combine_notes = combine_notes; t->initialized = 1; if (flags & NOTES_INIT_EMPTY || !notes_ref || @@ -693,17 +713,19 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags) } void add_note(struct notes_tree *t, const unsigned char *object_sha1, - const unsigned char *note_sha1) + const unsigned char *note_sha1, combine_notes_fn combine_notes) { struct leaf_node *l; if (!t) t = &default_notes_tree; assert(t->initialized); + if (!combine_notes) + combine_notes = t->combine_notes; l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, note_sha1); - note_tree_insert(t->root, 0, l, PTR_TYPE_NOTE); + note_tree_insert(t->root, 0, l, PTR_TYPE_NOTE, combine_notes); } void remove_note(struct notes_tree *t, const unsigned char *object_sha1) @@ -788,7 +810,7 @@ void format_note(struct notes_tree *t, const unsigned char *object_sha1, if (!t) t = &default_notes_tree; if (!t->initialized) - init_notes(t, NULL, 0); + init_notes(t, NULL, NULL, 0); sha1 = get_note(t, object_sha1); if (!sha1) diff --git a/notes.h b/notes.h index 12acc38b08..20d6e171ff 100644 --- a/notes.h +++ b/notes.h @@ -1,6 +1,30 @@ #ifndef NOTES_H #define NOTES_H +/* + * Function type for combining two notes annotating the same object. + * + * When adding a new note annotating the same object as an existing note, it is + * up to the caller to decide how to combine the two notes. The decision is + * made by passing in a function of the following form. The function accepts + * two SHA1s -- of the existing note and the new note, respectively. The + * function then combines the notes in whatever way it sees fit, and writes the + * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return + * value indicates failure. + * + * The two given SHA1s must both be non-NULL and different from each other. + * + * The default combine_notes function (you get this when passing NULL) is + * combine_notes_concatenate(), which appends the contents of the new note to + * the contents of the existing note. + */ +typedef int combine_notes_fn(unsigned char *cur_sha1, const unsigned char *new_sha1); + +/* Common notes combinators */ +int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1); +int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1); +int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1); + /* * Notes tree object * @@ -13,6 +37,7 @@ extern struct notes_tree { struct int_node *root; char *ref; + combine_notes_fn *combine_notes; int initialized; } default_notes_tree; @@ -36,10 +61,15 @@ extern struct notes_tree { * * If you pass t == NULL, the default internal notes_tree will be initialized. * + * The combine_notes function that is passed becomes the default combine_notes + * function for the given notes_tree. If NULL is passed, the default + * combine_notes function is combine_notes_concatenate(). + * * Precondition: The notes_tree structure is zeroed (this can be achieved with * memset(t, 0, sizeof(struct notes_tree))) */ -void init_notes(struct notes_tree *t, const char *notes_ref, int flags); +void init_notes(struct notes_tree *t, const char *notes_ref, + combine_notes_fn combine_notes, int flags); /* * Add the given note object to the given notes_tree structure @@ -49,7 +79,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, int flags); * zero. */ void add_note(struct notes_tree *t, const unsigned char *object_sha1, - const unsigned char *note_sha1); + const unsigned char *note_sha1, combine_notes_fn combine_notes); /* * Remove the given note object from the given notes_tree structure From cd067d3bf4ea3f89969cd143be3e281e1c5ac58a Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:20 +0100 Subject: [PATCH 0061/3211] Builtin-ify git-notes The builtin-ification includes some minor behavioural changes to the command-line interface: It is no longer allowed to mix the -m and -F arguments, and it is not allowed to use multiple -F options. As part of the builtin-ification, we add the commit_notes() function to the builtin API. This function (together with the notes.h API) can be easily used from other builtins to manipulate the notes tree. Also includes needed changes to t3301. This patch has been improved by the following contributions: - Stephen Boyd: Use die() instead of fprintf(stderr, ...) followed by exit(1) Cc: Stephen Boyd Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 6 +- Makefile | 2 +- builtin-notes.c | 280 ++++++++++++++++++ builtin.h | 3 + git-notes.sh => contrib/examples/git-notes.sh | 0 git.c | 1 + t/t3301-notes.sh | 98 +++--- 7 files changed, 350 insertions(+), 40 deletions(-) create mode 100644 builtin-notes.c rename git-notes.sh => contrib/examples/git-notes.sh (100%) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index d4487cab52..0d1ada6a0c 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -37,14 +37,12 @@ OPTIONS ------- -m :: Use the given note message (instead of prompting). - If multiple `-m` (or `-F`) options are given, their - values are concatenated as separate paragraphs. + If multiple `-m` options are given, their values + are concatenated as separate paragraphs. -F :: Take the note message from the given file. Use '-' to read the note message from the standard input. - If multiple `-F` (or `-m`) options are given, their - values are concatenated as separate paragraphs. Author diff --git a/Makefile b/Makefile index c0dbee2ae4..1f95f93f76 100644 --- a/Makefile +++ b/Makefile @@ -353,7 +353,6 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-mergetool--lib.sh -SCRIPT_SH += git-notes.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh @@ -671,6 +670,7 @@ BUILTIN_OBJS += builtin-mktag.o BUILTIN_OBJS += builtin-mktree.o BUILTIN_OBJS += builtin-mv.o BUILTIN_OBJS += builtin-name-rev.o +BUILTIN_OBJS += builtin-notes.o BUILTIN_OBJS += builtin-pack-objects.o BUILTIN_OBJS += builtin-pack-redundant.o BUILTIN_OBJS += builtin-pack-refs.o diff --git a/builtin-notes.c b/builtin-notes.c new file mode 100644 index 0000000000..89aa6e072e --- /dev/null +++ b/builtin-notes.c @@ -0,0 +1,280 @@ +/* + * Builtin "git notes" + * + * Copyright (c) 2010 Johan Herland + * + * Based on git-notes.sh by Johannes Schindelin, + * and builtin-tag.c by Kristian Høgsberg and Carlos Rica. + */ + +#include "cache.h" +#include "builtin.h" +#include "notes.h" +#include "blob.h" +#include "commit.h" +#include "refs.h" +#include "exec_cmd.h" +#include "run-command.h" +#include "parse-options.h" + +static const char * const git_notes_usage[] = { + "git notes edit [-m | -F ] []", + "git notes show []", + NULL +}; + +static const char note_template[] = + "\n" + "#\n" + "# Write/edit the notes for the following object:\n" + "#\n"; + +static void write_note_data(int fd, const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + char *buf = read_sha1_file(sha1, &type, &size); + if (buf) { + if (size) + write_or_die(fd, buf, size); + free(buf); + } +} + +static void write_commented_object(int fd, const unsigned char *object) +{ + const char *show_args[5] = + {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; + struct child_process show; + struct strbuf buf = STRBUF_INIT; + FILE *show_out; + + /* Invoke "git show --stat --no-notes $object" */ + memset(&show, 0, sizeof(show)); + show.argv = show_args; + show.no_stdin = 1; + show.out = -1; + show.err = 0; + show.git_cmd = 1; + if (start_command(&show)) + die("unable to start 'show' for object '%s'", + sha1_to_hex(object)); + + /* Open the output as FILE* so strbuf_getline() can be used. */ + show_out = xfdopen(show.out, "r"); + if (show_out == NULL) + die_errno("can't fdopen 'show' output fd"); + + /* Prepend "# " to each output line and write result to 'fd' */ + while (strbuf_getline(&buf, show_out, '\n') != EOF) { + write_or_die(fd, "# ", 2); + write_or_die(fd, buf.buf, buf.len); + write_or_die(fd, "\n", 1); + } + strbuf_release(&buf); + if (fclose(show_out)) + die_errno("failed to close pipe to 'show' for object '%s'", + sha1_to_hex(object)); + if (finish_command(&show)) + die("failed to finish 'show' for object '%s'", + sha1_to_hex(object)); +} + +static void create_note(const unsigned char *object, + struct strbuf *buf, + int skip_editor, + const unsigned char *prev, + unsigned char *result) +{ + char *path = NULL; + + if (!skip_editor) { + int fd; + + /* write the template message before editing: */ + path = git_pathdup("NOTES_EDITMSG"); + fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + die_errno("could not create file '%s'", path); + + if (prev) + write_note_data(fd, prev); + write_or_die(fd, note_template, strlen(note_template)); + + write_commented_object(fd, object); + + close(fd); + + if (launch_editor(path, buf, NULL)) { + die("Please supply the note contents using either -m" \ + " or -F option"); + } + } + + stripspace(buf, 1); + + if (!skip_editor && !buf->len) { + fprintf(stderr, "Removing note for object %s\n", + sha1_to_hex(object)); + hashclr(result); + } else { + if (write_sha1_file(buf->buf, buf->len, blob_type, result)) { + error("unable to write note object"); + if (path) + error("The note contents has been left in %s", + path); + exit(128); + } + } + + if (path) { + unlink_or_warn(path); + free(path); + } +} + +struct msg_arg { + int given; + struct strbuf buf; +}; + +static int parse_msg_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + + if (!arg) + return -1; + if (msg->buf.len) + strbuf_addstr(&(msg->buf), "\n\n"); + strbuf_addstr(&(msg->buf), arg); + msg->given = 1; + return 0; +} + +int commit_notes(struct notes_tree *t, const char *msg) +{ + struct commit_list *parent; + unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; + struct strbuf buf = STRBUF_INIT; + + if (!t) + t = &default_notes_tree; + if (!t->initialized || !t->ref || !*t->ref) + die("Cannot commit uninitialized/unreferenced notes tree"); + + /* Prepare commit message and reflog message */ + strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ + strbuf_addstr(&buf, msg); + if (buf.buf[buf.len - 1] != '\n') + strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ + + /* Convert notes tree to tree object */ + if (write_notes_tree(t, tree_sha1)) + die("Failed to write current notes tree to database"); + + /* Create new commit for the tree object */ + if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ + parent = xmalloc(sizeof(*parent)); + parent->item = lookup_commit(prev_commit); + parent->next = NULL; + } else { + hashclr(prev_commit); + parent = NULL; + } + if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) + die("Failed to commit notes tree to database"); + + /* Update notes ref with new commit */ + update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + + strbuf_release(&buf); + return 0; +} + +int cmd_notes(int argc, const char **argv, const char *prefix) +{ + struct strbuf buf = STRBUF_INIT; + struct notes_tree *t; + unsigned char object[20], new_note[20]; + const unsigned char *note; + const char *object_ref; + int edit = 0, show = 0; + const char *msgfile = NULL; + struct msg_arg msg = { 0, STRBUF_INIT }; + struct option options[] = { + OPT_GROUP("Notes edit options"), + OPT_CALLBACK('m', NULL, &msg, "msg", + "note contents as a string", parse_msg_arg), + OPT_FILENAME('F', NULL, &msgfile, "note contents in a file"), + OPT_END() + }; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0); + + if (argc && !strcmp(argv[0], "edit")) + edit = 1; + else if (argc && !strcmp(argv[0], "show")) + show = 1; + + if (edit + show != 1) + usage_with_options(git_notes_usage, options); + + object_ref = argc == 2 ? argv[1] : "HEAD"; + if (argc > 2) { + error("too many parameters"); + usage_with_options(git_notes_usage, options); + } + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + + init_notes(NULL, NULL, NULL, 0); + t = &default_notes_tree; + + if (prefixcmp(t->ref, "refs/notes/")) + die("Refusing to %s notes in %s (outside of refs/notes/)", + edit ? "edit" : "show", t->ref); + + note = get_note(t, object); + + /* show command */ + + if (show && !note) { + error("No note found for object %s.", sha1_to_hex(object)); + return 1; + } else if (show) { + const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; + return execv_git_cmd(show_args); + } + + /* edit command */ + + if (msg.given || msgfile) { + if (msg.given && msgfile) { + error("mixing -m and -F options is not allowed."); + usage_with_options(git_notes_usage, options); + } + if (msg.given) + strbuf_addbuf(&buf, &(msg.buf)); + else { + if (!strcmp(msgfile, "-")) { + if (strbuf_read(&buf, 0, 1024) < 0) + die_errno("cannot read '%s'", msgfile); + } else { + if (strbuf_read_file(&buf, msgfile, 1024) < 0) + die_errno("could not open or read '%s'", + msgfile); + } + } + } + + create_note(object, &buf, msg.given || msgfile, note, new_note); + add_note(t, object, new_note, combine_notes_overwrite); + commit_notes(t, "Note added by 'git notes edit'"); + + free_notes(t); + strbuf_release(&buf); + return 0; +} diff --git a/builtin.h b/builtin.h index e8202f3f5e..cdf98477a6 100644 --- a/builtin.h +++ b/builtin.h @@ -5,6 +5,7 @@ #include "strbuf.h" #include "cache.h" #include "commit.h" +#include "notes.h" extern const char git_version_string[]; extern const char git_usage_string[]; @@ -18,6 +19,7 @@ extern int fmt_merge_msg(int merge_summary, struct strbuf *in, extern int commit_tree(const char *msg, unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author); +extern int commit_notes(struct notes_tree *t, const char *msg); extern int check_pager_config(const char *cmd); extern int cmd_add(int argc, const char **argv, const char *prefix); @@ -78,6 +80,7 @@ extern int cmd_mktag(int argc, const char **argv, const char *prefix); extern int cmd_mktree(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_notes(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); extern int cmd_pack_redundant(int argc, const char **argv, const char *prefix); extern int cmd_patch_id(int argc, const char **argv, const char *prefix); diff --git a/git-notes.sh b/contrib/examples/git-notes.sh similarity index 100% rename from git-notes.sh rename to contrib/examples/git-notes.sh diff --git a/git.c b/git.c index b3e23f1044..32f76e15ea 100644 --- a/git.c +++ b/git.c @@ -343,6 +343,7 @@ static void handle_internal_command(int argc, const char **argv) { "mktree", cmd_mktree, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, + { "notes", cmd_notes, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, { "pack-redundant", cmd_pack_redundant, RUN_SETUP }, { "patch-id", cmd_patch_id }, diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 18aad53899..10f62f4122 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -12,8 +12,8 @@ echo "$MSG" > "$1" echo "$MSG" >& 2 EOF chmod a+x fake_editor.sh -VISUAL=./fake_editor.sh -export VISUAL +GIT_EDITOR=./fake_editor.sh +export GIT_EDITOR test_expect_success 'cannot annotate non-existing HEAD' ' (MSG=3 && export MSG && test_must_fail git notes edit) @@ -56,8 +56,17 @@ test_expect_success 'handle empty notes gracefully' ' test_expect_success 'create notes' ' git config core.notesRef refs/notes/commits && + MSG=b0 git notes edit && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b0 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'edit existing notes' ' MSG=b1 git notes edit && - test ! -f .git/new-notes && + test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && test b1 = $(git notes show) && git show HEAD^ && @@ -110,19 +119,16 @@ test_expect_success 'show multi-line notes' ' git log -2 > output && test_cmp expect-multiline output ' -test_expect_success 'create -m and -F notes (setup)' ' +test_expect_success 'create -F notes (setup)' ' : > a4 && git add a4 && test_tick && git commit -m 4th && echo "xyzzy" > note5 && - git notes edit -m spam -F note5 -m "foo -bar -baz" + git notes edit -F note5 ' -whitespace=" " -cat > expect-m-and-F << EOF +cat > expect-F << EOF commit 15023535574ded8b1a89052b32673f84cf9582b8 Author: A U Thor Date: Thu Apr 7 15:16:13 2005 -0700 @@ -130,21 +136,15 @@ Date: Thu Apr 7 15:16:13 2005 -0700 4th Notes: - spam -$whitespace xyzzy -$whitespace - foo - bar - baz EOF -printf "\n" >> expect-m-and-F -cat expect-multiline >> expect-m-and-F +printf "\n" >> expect-F +cat expect-multiline >> expect-F -test_expect_success 'show -m and -F notes' ' +test_expect_success 'show -F notes' ' git log -3 > output && - test_cmp expect-m-and-F output + test_cmp expect-F output ' cat >expect << EOF @@ -164,13 +164,7 @@ test_expect_success 'git log --pretty=raw does not show notes' ' cat >>expect <output && @@ -179,17 +173,17 @@ test_expect_success 'git log --show-notes' ' test_expect_success 'git log --no-notes' ' git log -1 --no-notes >output && - ! grep spam output + ! grep xyzzy output ' test_expect_success 'git format-patch does not show notes' ' git format-patch -1 --stdout >output && - ! grep spam output + ! grep xyzzy output ' test_expect_success 'git format-patch --show-notes does show notes' ' git format-patch --show-notes -1 --stdout >output && - grep spam output + grep xyzzy output ' for pretty in \ @@ -202,35 +196,69 @@ do esac test_expect_success "git show $pretty does$not show notes" ' git show $p >output && - eval "$negate grep spam output" + eval "$negate grep xyzzy output" ' done -test_expect_success 'create other note on a different notes ref (setup)' ' +test_expect_success 'create -m notes (setup)' ' : > a5 && git add a5 && test_tick && git commit -m 5th && - GIT_NOTES_REF="refs/notes/other" git notes edit -m "other note" + git notes edit -m spam -m "foo +bar +baz" ' -cat > expect-other << EOF +whitespace=" " +cat > expect-m << EOF commit bd1753200303d0a0344be813e504253b3d98e74d Author: A U Thor Date: Thu Apr 7 15:17:13 2005 -0700 5th +Notes: + spam +$whitespace + foo + bar + baz +EOF + +printf "\n" >> expect-m +cat expect-F >> expect-m + +test_expect_success 'show -m notes' ' + git log -4 > output && + test_cmp expect-m output +' + +test_expect_success 'create other note on a different notes ref (setup)' ' + : > a6 && + git add a6 && + test_tick && + git commit -m 6th && + GIT_NOTES_REF="refs/notes/other" git notes edit -m "other note" +' + +cat > expect-other << EOF +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 +Author: A U Thor +Date: Thu Apr 7 15:18:13 2005 -0700 + + 6th + Notes: other note EOF cat > expect-not-other << EOF -commit bd1753200303d0a0344be813e504253b3d98e74d +commit 387a89921c73d7ed72cd94d179c1c7048ca47756 Author: A U Thor -Date: Thu Apr 7 15:17:13 2005 -0700 +Date: Thu Apr 7 15:18:13 2005 -0700 - 5th + 6th EOF test_expect_success 'Do not show note on other ref by default' ' From b24bb99756c8b6fde01c23ebbb4abc37d12fb1eb Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:21 +0100 Subject: [PATCH 0062/3211] t3301: Verify successful annotation of non-commits Adds a testcase verifying that git-notes works successfully on tree, blob, and tag objects. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3301-notes.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 10f62f4122..fd5e593ae6 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -282,4 +282,21 @@ test_expect_success 'Do not show note when core.notesRef is overridden' ' test_cmp expect-not-other output ' +test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' + echo "Note on a tree" > expect + git notes edit -m "Note on a tree" HEAD: && + git notes show HEAD: > actual && + test_cmp expect actual && + echo "Note on a blob" > expect + filename=$(git ls-tree --name-only HEAD | head -n1) && + git notes edit -m "Note on a blob" HEAD:$filename && + git notes show HEAD:$filename > actual && + test_cmp expect actual && + echo "Note on a tag" > expect + git tag -a -m "This is an annotated tag" foobar HEAD^ && + git notes edit -m "Note on a tag" foobar && + git notes show foobar > actual && + test_cmp expect actual +' + test_done From 048cdd4665efafde2902cb72376f2ca497254246 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:22 +0100 Subject: [PATCH 0063/3211] t3305: Verify that adding many notes with git-notes triggers increased fanout Add a test verifying that the notes code automatically restructures the notes tree into a deeper fanout level, when many notes are added with "git notes". Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3305-notes-fanout.sh | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 t/t3305-notes-fanout.sh diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh new file mode 100755 index 0000000000..823b0ff23b --- /dev/null +++ b/t/t3305-notes-fanout.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='Test that adding many notes triggers automatic fanout restructuring' + +. ./test-lib.sh + +test_expect_success 'creating many notes with git-notes' ' + num_notes=300 && + i=0 && + while test $i -lt $num_notes + do + i=$(($i + 1)) && + test_tick && + echo "file for commit #$i" > file && + git add file && + git commit -q -m "commit #$i" && + git notes edit -m "note #$i" || return 1 + done +' + +test_expect_success 'many notes created correctly with git-notes' ' + git log | grep "^ " > output && + i=300 && + while test $i -gt 0 + do + echo " commit #$i" && + echo " note #$i" && + i=$(($i - 1)); + done > expect && + test_cmp expect output +' + +test_expect_success 'many notes created with git-notes triggers fanout' ' + # Expect entire notes tree to have a fanout == 1 + git ls-tree -r --name-only refs/notes/commits | + while read path + do + case "$path" in + ??/??????????????????????????????????????) + : true + ;; + *) + echo "Invalid path \"$path\"" && + return 1 + ;; + esac + done +' + +test_done From 851c2b3791f24e319c23331887d4b8150ca4d9ba Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:23 +0100 Subject: [PATCH 0064/3211] Teach notes code to properly preserve non-notes in the notes tree The note tree structure allows for non-note entries to coexist with note entries in a notes tree. Although we certainly expect there to be very few non-notes in a notes tree, we should still support them to a certain degree. This patch teaches the notes code to preserve non-notes when updating the notes tree with write_notes_tree(). Non-notes are not affected by fanout restructuring. For non-notes to be handled correctly, we can no longer allow subtree entries that do not match the fanout structure produced by the notes code itself. This means that fanouts like 4/36, 6/34, 8/32, 4/4/32, etc. are no longer recognized as note subtrees; only 2-based fanouts are allowed (2/38, 2/2/36, 2/2/2/34, etc.). Since the notes code has never at any point _produced_ non-2-based fanouts, it is highly unlikely that this change will cause problems for anyone. The patch also adds some tests verifying the correct handling of non-notes in a notes tree. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 219 +++++++++++++++++++++++++++++++------- notes.h | 1 + t/t3303-notes-subtrees.sh | 28 +++-- t/t3304-notes-mixed.sh | 36 ++++++- 4 files changed, 233 insertions(+), 51 deletions(-) diff --git a/notes.c b/notes.c index dc4e4f619f..d432517587 100644 --- a/notes.c +++ b/notes.c @@ -37,6 +37,21 @@ struct leaf_node { unsigned char val_sha1[20]; }; +/* + * A notes tree may contain entries that are not notes, and that do not follow + * the naming conventions of notes. There are typically none/few of these, but + * we still need to keep track of them. Keep a simple linked list sorted alpha- + * betically on the non-note path. The list is populated when parsing tree + * objects in load_subtree(), and the non-notes are correctly written back into + * the tree objects produced by write_notes_tree(). + */ +struct non_note { + struct non_note *next; /* grounded (last->next == NULL) */ + char *path; + unsigned int mode; + unsigned char sha1[20]; +}; + #define PTR_TYPE_NULL 0 #define PTR_TYPE_INTERNAL 1 #define PTR_TYPE_NOTE 2 @@ -53,8 +68,8 @@ struct leaf_node { struct notes_tree default_notes_tree; -static void load_subtree(struct leaf_node *subtree, struct int_node *node, - unsigned int n); +static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, + struct int_node *node, unsigned int n); /* * Search the tree until the appropriate location for the given key is found: @@ -71,7 +86,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, * - an unused leaf node (NULL) * In any case, set *tree and *n, and return pointer to the tree location. */ -static void **note_tree_search(struct int_node **tree, +static void **note_tree_search(struct notes_tree *t, struct int_node **tree, unsigned char *n, const unsigned char *key_sha1) { struct leaf_node *l; @@ -83,9 +98,9 @@ static void **note_tree_search(struct int_node **tree, if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ (*tree)->a[0] = NULL; - load_subtree(l, *tree, *n); + load_subtree(t, l, *tree, *n); free(l); - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); } } @@ -95,15 +110,15 @@ static void **note_tree_search(struct int_node **tree, case PTR_TYPE_INTERNAL: *tree = CLR_PTR_TYPE(p); (*n)++; - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); case PTR_TYPE_SUBTREE: l = (struct leaf_node *) CLR_PTR_TYPE(p); if (!SUBTREE_SHA1_PREFIXCMP(key_sha1, l->key_sha1)) { /* unpack tree and resume search */ (*tree)->a[i] = NULL; - load_subtree(l, *tree, *n); + load_subtree(t, l, *tree, *n); free(l); - return note_tree_search(tree, n, key_sha1); + return note_tree_search(t, tree, n, key_sha1); } /* fall through */ default: @@ -116,10 +131,11 @@ static void **note_tree_search(struct int_node **tree, * Search to the tree location appropriate for the given key: * If a note entry with matching key, return the note entry, else return NULL. */ -static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, +static struct leaf_node *note_tree_find(struct notes_tree *t, + struct int_node *tree, unsigned char n, const unsigned char *key_sha1) { - void **p = note_tree_search(&tree, &n, key_sha1); + void **p = note_tree_search(t, &tree, &n, key_sha1); if (GET_PTR_TYPE(*p) == PTR_TYPE_NOTE) { struct leaf_node *l = (struct leaf_node *) CLR_PTR_TYPE(*p); if (!hashcmp(key_sha1, l->key_sha1)) @@ -141,13 +157,13 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n, * - Else, create a new int_node, holding both the node-at-location and the * node-to-be-inserted, and store the new int_node into the location. */ -static void note_tree_insert(struct int_node *tree, unsigned char n, - struct leaf_node *entry, unsigned char type, +static void note_tree_insert(struct notes_tree *t, struct int_node *tree, + unsigned char n, struct leaf_node *entry, unsigned char type, combine_notes_fn combine_notes) { struct int_node *new_node; struct leaf_node *l; - void **p = note_tree_search(&tree, &n, entry->key_sha1); + void **p = note_tree_search(t, &tree, &n, entry->key_sha1); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ l = (struct leaf_node *) CLR_PTR_TYPE(*p); @@ -178,7 +194,7 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, entry->key_sha1)) { /* unpack 'entry' */ - load_subtree(entry, tree, n); + load_subtree(t, entry, tree, n); free(entry); return; } @@ -189,9 +205,10 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { /* unpack 'l' and restart insert */ *p = NULL; - load_subtree(l, tree, n); + load_subtree(t, l, tree, n); free(l); - note_tree_insert(tree, n, entry, type, combine_notes); + note_tree_insert(t, tree, n, entry, type, + combine_notes); return; } break; @@ -201,9 +218,10 @@ static void note_tree_insert(struct int_node *tree, unsigned char n, assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); - note_tree_insert(new_node, n + 1, l, GET_PTR_TYPE(*p), combine_notes); + note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), + combine_notes); *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - note_tree_insert(new_node, n + 1, entry, type, combine_notes); + note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); } /* @@ -249,7 +267,7 @@ static void note_tree_remove(struct notes_tree *t, struct int_node *tree, struct leaf_node *l; struct int_node *parent_stack[20]; unsigned char i, j; - void **p = note_tree_search(&tree, &n, entry->key_sha1); + void **p = note_tree_search(t, &tree, &n, entry->key_sha1); assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ if (GET_PTR_TYPE(*p) != PTR_TYPE_NOTE) @@ -324,14 +342,67 @@ static int get_sha1_hex_segment(const char *hex, unsigned int hex_len, return len; } -static void load_subtree(struct leaf_node *subtree, struct int_node *node, - unsigned int n) +static int non_note_cmp(const struct non_note *a, const struct non_note *b) +{ + return strcmp(a->path, b->path); +} + +static void add_non_note(struct notes_tree *t, const char *path, + unsigned int mode, const unsigned char *sha1) +{ + struct non_note *p = t->prev_non_note, *n; + n = (struct non_note *) xmalloc(sizeof(struct non_note)); + n->next = NULL; + n->path = xstrdup(path); + n->mode = mode; + hashcpy(n->sha1, sha1); + t->prev_non_note = n; + + if (!t->first_non_note) { + t->first_non_note = n; + return; + } + + if (non_note_cmp(p, n) < 0) + ; /* do nothing */ + else if (non_note_cmp(t->first_non_note, n) <= 0) + p = t->first_non_note; + else { + /* n sorts before t->first_non_note */ + n->next = t->first_non_note; + t->first_non_note = n; + return; + } + + /* n sorts equal or after p */ + while (p->next && non_note_cmp(p->next, n) <= 0) + p = p->next; + + if (non_note_cmp(p, n) == 0) { /* n ~= p; overwrite p with n */ + assert(strcmp(p->path, n->path) == 0); + p->mode = n->mode; + hashcpy(p->sha1, n->sha1); + free(n); + t->prev_non_note = p; + return; + } + + /* n sorts between p and p->next */ + n->next = p->next; + p->next = n; +} + +static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, + struct int_node *node, unsigned int n) { unsigned char object_sha1[20]; unsigned int prefix_len; void *buf; struct tree_desc desc; struct name_entry entry; + int len, path_len; + unsigned char type; + struct leaf_node *l; buf = fill_tree_descriptor(&desc, subtree->val_sha1); if (!buf) @@ -342,31 +413,68 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node, assert(prefix_len * 2 >= n); memcpy(object_sha1, subtree->key_sha1, prefix_len); while (tree_entry(&desc, &entry)) { - int len = get_sha1_hex_segment(entry.path, strlen(entry.path), + path_len = strlen(entry.path); + len = get_sha1_hex_segment(entry.path, path_len, object_sha1 + prefix_len, 20 - prefix_len); if (len < 0) - continue; /* entry.path is not a SHA1 sum. Skip */ + goto handle_non_note; /* entry.path is not a SHA1 */ len += prefix_len; /* * If object SHA1 is complete (len == 20), assume note object - * If object SHA1 is incomplete (len < 20), assume note subtree + * If object SHA1 is incomplete (len < 20), and current + * component consists of 2 hex chars, assume note subtree */ if (len <= 20) { - unsigned char type = PTR_TYPE_NOTE; - struct leaf_node *l = (struct leaf_node *) + type = PTR_TYPE_NOTE; + l = (struct leaf_node *) xcalloc(sizeof(struct leaf_node), 1); hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, entry.sha1); if (len < 20) { - if (!S_ISDIR(entry.mode)) - continue; /* entry cannot be subtree */ + if (!S_ISDIR(entry.mode) || path_len != 2) + goto handle_non_note; /* not subtree */ l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - note_tree_insert(node, n, l, type, + note_tree_insert(t, node, n, l, type, combine_notes_concatenate); } + continue; + +handle_non_note: + /* + * Determine full path for this non-note entry: + * The filename is already found in entry.path, but the + * directory part of the path must be deduced from the subtree + * containing this entry. We assume here that the overall notes + * tree follows a strict byte-based progressive fanout + * structure (i.e. using 2/38, 2/2/36, etc. fanouts, and not + * e.g. 4/36 fanout). This means that if a non-note is found at + * path "dead/beef", the following code will register it as + * being found on "de/ad/beef". + * On the other hand, if you use such non-obvious non-note + * paths in the middle of a notes tree, you deserve what's + * coming to you ;). Note that for non-notes that are not + * SHA1-like at the top level, there will be no problems. + * + * To conclude, it is strongly advised to make sure non-notes + * have at least one non-hex character in the top-level path + * component. + */ + { + char non_note_path[PATH_MAX]; + char *p = non_note_path; + const char *q = sha1_to_hex(subtree->key_sha1); + int i; + for (i = 0; i < prefix_len; i++) { + *p++ = *q++; + *p++ = *q++; + *p++ = '/'; + } + strcpy(p, entry.path); + add_non_note(t, non_note_path, entry.mode, entry.sha1); + } } free(buf); } @@ -426,9 +534,9 @@ static void construct_path_with_fanout(const unsigned char *sha1, strcpy(path + i, hex_sha1 + j); } -static int for_each_note_helper(struct int_node *tree, unsigned char n, - unsigned char fanout, int flags, each_note_fn fn, - void *cb_data) +static int for_each_note_helper(struct notes_tree *t, struct int_node *tree, + unsigned char n, unsigned char fanout, int flags, + each_note_fn fn, void *cb_data) { unsigned int i; void *p; @@ -443,7 +551,7 @@ redo: switch (GET_PTR_TYPE(p)) { case PTR_TYPE_INTERNAL: /* recurse into int_node */ - ret = for_each_note_helper(CLR_PTR_TYPE(p), n + 1, + ret = for_each_note_helper(t, CLR_PTR_TYPE(p), n + 1, fanout, flags, fn, cb_data); break; case PTR_TYPE_SUBTREE: @@ -481,7 +589,7 @@ redo: !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) { /* unpack subtree and resume traversal */ tree->a[i] = NULL; - load_subtree(l, tree, n); + load_subtree(t, l, tree, n); free(l); goto redo; } @@ -596,8 +704,29 @@ static int write_each_note_helper(struct tree_write_stack *tws, struct write_each_note_data { struct tree_write_stack *root; + struct non_note *next_non_note; }; +static int write_each_non_note_until(const char *note_path, + struct write_each_note_data *d) +{ + struct non_note *n = d->next_non_note; + int cmp, ret; + while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) { + if (note_path && cmp == 0) + ; /* do nothing, prefer note to non-note */ + else { + ret = write_each_note_helper(d->root, n->path, n->mode, + n->sha1); + if (ret) + return ret; + } + n = n->next; + } + d->next_non_note = n; + return 0; +} + static int write_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@ -615,7 +744,9 @@ static int write_each_note(const unsigned char *object_sha1, } assert(note_path_len <= 40 + 19); - return write_each_note_helper(d->root, note_path, mode, note_sha1); + /* Weave non-note entries into note entries */ + return write_each_non_note_until(note_path, d) || + write_each_note_helper(d->root, note_path, mode, note_sha1); } int combine_notes_concatenate(unsigned char *cur_sha1, @@ -696,6 +827,8 @@ void init_notes(struct notes_tree *t, const char *notes_ref, combine_notes = combine_notes_concatenate; t->root = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + t->first_non_note = NULL; + t->prev_non_note = NULL; t->ref = notes_ref ? xstrdup(notes_ref) : NULL; t->combine_notes = combine_notes; t->initialized = 1; @@ -709,7 +842,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref, hashclr(root_tree.key_sha1); hashcpy(root_tree.val_sha1, sha1); - load_subtree(&root_tree, t->root, 0); + load_subtree(t, &root_tree, t->root, 0); } void add_note(struct notes_tree *t, const unsigned char *object_sha1, @@ -725,7 +858,7 @@ void add_note(struct notes_tree *t, const unsigned char *object_sha1, l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, note_sha1); - note_tree_insert(t->root, 0, l, PTR_TYPE_NOTE, combine_notes); + note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes); } void remove_note(struct notes_tree *t, const unsigned char *object_sha1) @@ -748,7 +881,7 @@ const unsigned char *get_note(struct notes_tree *t, if (!t) t = &default_notes_tree; assert(t->initialized); - found = note_tree_find(t->root, 0, object_sha1); + found = note_tree_find(t, t->root, 0, object_sha1); return found ? found->val_sha1 : NULL; } @@ -758,7 +891,7 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, if (!t) t = &default_notes_tree; assert(t->initialized); - return for_each_note_helper(t->root, 0, 0, flags, fn, cb_data); + return for_each_note_helper(t, t->root, 0, 0, flags, fn, cb_data); } int write_notes_tree(struct notes_tree *t, unsigned char *result) @@ -776,11 +909,13 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result) strbuf_init(&root.buf, 256 * (32 + 40)); /* assume 256 entries */ root.path[0] = root.path[1] = '\0'; cb_data.root = &root; + cb_data.next_non_note = t->first_non_note; /* Write tree objects representing current notes tree */ ret = for_each_note(t, FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | FOR_EACH_NOTE_YIELD_SUBTREES, write_each_note, &cb_data) || + write_each_non_note_until(NULL, &cb_data) || tree_write_stack_finish_subtree(&root) || write_sha1_file(root.buf.buf, root.buf.len, tree_type, result); strbuf_release(&root.buf); @@ -794,6 +929,12 @@ void free_notes(struct notes_tree *t) if (t->root) note_tree_free(t->root); free(t->root); + while (t->first_non_note) { + t->prev_non_note = t->first_non_note->next; + free(t->first_non_note->path); + free(t->first_non_note); + t->first_non_note = t->prev_non_note; + } free(t->ref); memset(t, 0, sizeof(struct notes_tree)); } diff --git a/notes.h b/notes.h index 20d6e171ff..f98578f91f 100644 --- a/notes.h +++ b/notes.h @@ -36,6 +36,7 @@ int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1) */ extern struct notes_tree { struct int_node *root; + struct non_note *first_non_note, *prev_non_note; char *ref; combine_notes_fn *combine_notes; int initialized; diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh index edc4bc8841..75ec18778e 100755 --- a/t/t3303-notes-subtrees.sh +++ b/t/t3303-notes-subtrees.sh @@ -95,12 +95,12 @@ INPUT_END test_expect_success 'test notes in 2/38-fanout' 'test_sha1_based "s|^..|&/|"' test_expect_success 'verify notes in 2/38-fanout' 'verify_notes' -test_expect_success 'test notes in 4/36-fanout' 'test_sha1_based "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout' 'verify_notes' - test_expect_success 'test notes in 2/2/36-fanout' 'test_sha1_based "s|^\(..\)\(..\)|\1/\2/|"' test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes' +test_expect_success 'test notes in 2/2/2/34-fanout' 'test_sha1_based "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"' +test_expect_success 'verify notes in 2/2/2/34-fanout' 'verify_notes' + test_same_notes () { ( start_note_commit && @@ -128,14 +128,17 @@ INPUT_END git fast-import --quiet } -test_expect_success 'test same notes in 4/36-fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" "s|^....|&/|"' -test_expect_success 'verify same notes in 4/36-fanout and 2/38-fanout' 'verify_notes' +test_expect_success 'test same notes in no fanout and 2/38-fanout' 'test_same_notes "s|^..|&/|" ""' +test_expect_success 'verify same notes in no fanout and 2/38-fanout' 'verify_notes' + +test_expect_success 'test same notes in no fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" ""' +test_expect_success 'verify same notes in no fanout and 2/2/36-fanout' 'verify_notes' test_expect_success 'test same notes in 2/38-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' test_expect_success 'verify same notes in 2/38-fanout and 2/2/36-fanout' 'verify_notes' -test_expect_success 'test same notes in 4/36-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' -test_expect_success 'verify same notes in 4/36-fanout and 2/2/36-fanout' 'verify_notes' +test_expect_success 'test same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'test_same_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|"' +test_expect_success 'verify same notes in 2/2/2/34-fanout and 2/2/36-fanout' 'verify_notes' test_concatenated_notes () { ( @@ -176,13 +179,16 @@ verify_concatenated_notes () { test_cmp expect output } -test_expect_success 'test notes in 4/36-fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' +test_expect_success 'test notes in no fanout concatenated with 2/38-fanout' 'test_concatenated_notes "s|^..|&/|" ""' +test_expect_success 'verify notes in no fanout concatenated with 2/38-fanout' 'verify_concatenated_notes' + +test_expect_success 'test notes in no fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" ""' +test_expect_success 'verify notes in no fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' test_expect_success 'test notes in 2/38-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^..|&/|"' test_expect_success 'verify notes in 2/38-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' -test_expect_success 'test notes in 4/36-fanout concatenated with 2/2/36-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)|\1/\2/|" "s|^....|&/|"' -test_expect_success 'verify notes in 4/36-fanout concatenated with 2/2/36-fanout' 'verify_concatenated_notes' +test_expect_success 'test notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'test_concatenated_notes "s|^\(..\)\(..\)\(..\)|\1/\2/\3/|" "s|^\(..\)\(..\)|\1/\2/|"' +test_expect_success 'verify notes in 2/2/36-fanout concatenated with 2/2/2/34-fanout' 'verify_concatenated_notes' test_done diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh index 256687ffb5..c975a6d3f7 100755 --- a/t/t3304-notes-mixed.sh +++ b/t/t3304-notes-mixed.sh @@ -131,6 +131,17 @@ data <expect_nn3 <expect_nn4 < actual_nn2 && test_cmp expect_nn2 actual_nn2 && git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 && - test_cmp expect_nn3 actual_nn3 + test_cmp expect_nn3 actual_nn3 && + git cat-file -p refs/notes/commits:dead/beef > actual_nn4 && + test_cmp expect_nn4 actual_nn4 +' + +test_expect_success "git-notes preserves non-notes" ' + + test_tick && + git notes edit -m "foo bar" +' + +test_expect_success "verify contents of non-notes after git-notes" ' + + git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 && + test_cmp expect_nn1 actual_nn1 && + git cat-file -p refs/notes/commits:deadbeef > actual_nn2 && + test_cmp expect_nn2 actual_nn2 && + git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 && + test_cmp expect_nn3 actual_nn3 && + git cat-file -p refs/notes/commits:dead/beef > actual_nn4 && + test_cmp expect_nn4 actual_nn4 ' test_done From a0b4dfa9b35a2ebac578ea5547b041bb78557238 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:24 +0100 Subject: [PATCH 0065/3211] Teach builtin-notes to remove empty notes When the result of editing a note is an empty string, the associated note entry should be deleted from the notes tree. This allows deleting notes by invoking either "git notes -m ''" or "git notes -F /dev/null". Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- builtin-notes.c | 15 +++++++++++---- t/t3301-notes.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/builtin-notes.c b/builtin-notes.c index 89aa6e072e..7b4cb1367e 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -113,7 +113,7 @@ static void create_note(const unsigned char *object, stripspace(buf, 1); - if (!skip_editor && !buf->len) { + if (!buf->len) { fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); hashclr(result); @@ -197,7 +197,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) struct notes_tree *t; unsigned char object[20], new_note[20]; const unsigned char *note; - const char *object_ref; + const char *object_ref, *logmsg; + int edit = 0, show = 0; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; @@ -271,8 +272,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix) } create_note(object, &buf, msg.given || msgfile, note, new_note); - add_note(t, object, new_note, combine_notes_overwrite); - commit_notes(t, "Note added by 'git notes edit'"); + if (is_null_sha1(new_note)) { + remove_note(t, object); + logmsg = "Note removed by 'git notes edit'"; + } else { + add_note(t, object, new_note, combine_notes_overwrite); + logmsg = "Note added by 'git notes edit'"; + } + commit_notes(t, logmsg); free_notes(t); strbuf_release(&buf); diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index fd5e593ae6..fe59e73c21 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -234,6 +234,37 @@ test_expect_success 'show -m notes' ' test_cmp expect-m output ' +test_expect_success 'remove note with -F /dev/null (setup)' ' + git notes edit -F /dev/null +' + +cat > expect-rm-F << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th +EOF + +printf "\n" >> expect-rm-F +cat expect-F >> expect-rm-F + +test_expect_success 'verify note removal with -F /dev/null' ' + git log -4 > output && + test_cmp expect-rm-F output && + ! git notes show +' + +test_expect_success 'do not create empty note with -m "" (setup)' ' + git notes edit -m "" +' + +test_expect_success 'verify non-creation of note with -m ""' ' + git log -4 > output && + test_cmp expect-rm-F output && + ! git notes show +' + test_expect_success 'create other note on a different notes ref (setup)' ' : > a6 && git add a6 && From 92b3385fca620f47580d695b80b96432c76f0822 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:25 +0100 Subject: [PATCH 0066/3211] builtin-notes: Add "remove" subcommand for removing existing notes Using "git notes remove" is equivalent to specifying an empty note message. The patch includes tests verifying correct behaviour of the new subcommand. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 15 ++++++--- builtin-notes.c | 63 ++++++++++++++++++++----------------- t/t3301-notes.sh | 27 ++++++++++++++++ 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 0d1ada6a0c..a52d23ac11 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -8,14 +8,14 @@ git-notes - Add/inspect commit notes SYNOPSIS -------- [verse] -'git notes' (edit [-F | -m ] | show) [commit] +'git notes' (edit [-F | -m ] | show | remove) [commit] DESCRIPTION ----------- -This command allows you to add notes to commit messages, without -changing the commit. To discern these notes from the message stored -in the commit object, the notes are indented like the message, after -an unindented line saying "Notes:". +This command allows you to add/remove notes to/from commit messages, +without changing the commit. To discern these notes from the message +stored in the commit object, the notes are indented like the message, +after an unindented line saying "Notes:". To disable commit notes, you have to set the config variable core.notesRef to the empty string. Alternatively, you can set it @@ -32,6 +32,11 @@ edit:: show:: Show the notes for a given commit (defaults to HEAD). +remove:: + Remove the notes for a given commit (defaults to HEAD). + This is equivalent to specifying an empty note message to + the `edit` subcommand. + OPTIONS ------- diff --git a/builtin-notes.c b/builtin-notes.c index 7b4cb1367e..7c4007523b 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -20,6 +20,7 @@ static const char * const git_notes_usage[] = { "git notes edit [-m | -F ] []", "git notes show []", + "git notes remove []", NULL }; @@ -197,9 +198,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) struct notes_tree *t; unsigned char object[20], new_note[20]; const unsigned char *note; - const char *object_ref, *logmsg; + const char *object_ref; + char logmsg[100]; - int edit = 0, show = 0; + int edit = 0, show = 0, remove = 0; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { @@ -218,10 +220,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix) edit = 1; else if (argc && !strcmp(argv[0], "show")) show = 1; + else if (argc && !strcmp(argv[0], "remove")) + remove = 1; - if (edit + show != 1) + if (edit + show + remove != 1) usage_with_options(git_notes_usage, options); + if ((msg.given || msgfile) && !edit) { + error("cannot use -m/-F options with %s subcommand.", argv[0]); + usage_with_options(git_notes_usage, options); + } + + if (msg.given && msgfile) { + error("mixing -m and -F options is not allowed."); + usage_with_options(git_notes_usage, options); + } + object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) { error("too many parameters"); @@ -236,7 +250,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (prefixcmp(t->ref, "refs/notes/")) die("Refusing to %s notes in %s (outside of refs/notes/)", - edit ? "edit" : "show", t->ref); + argv[0], t->ref); note = get_note(t, object); @@ -250,35 +264,28 @@ int cmd_notes(int argc, const char **argv, const char *prefix) return execv_git_cmd(show_args); } - /* edit command */ + /* edit/remove command */ - if (msg.given || msgfile) { - if (msg.given && msgfile) { - error("mixing -m and -F options is not allowed."); - usage_with_options(git_notes_usage, options); - } - if (msg.given) - strbuf_addbuf(&buf, &(msg.buf)); - else { - if (!strcmp(msgfile, "-")) { - if (strbuf_read(&buf, 0, 1024) < 0) - die_errno("cannot read '%s'", msgfile); - } else { - if (strbuf_read_file(&buf, msgfile, 1024) < 0) - die_errno("could not open or read '%s'", - msgfile); - } - } + if (remove) + strbuf_reset(&buf); + else if (msg.given) + strbuf_addbuf(&buf, &(msg.buf)); + else if (msgfile) { + if (!strcmp(msgfile, "-")) { + if (strbuf_read(&buf, 0, 1024) < 0) + die_errno("cannot read '%s'", msgfile); + } else if (strbuf_read_file(&buf, msgfile, 1024) < 0) + die_errno("could not open or read '%s'", msgfile); } - create_note(object, &buf, msg.given || msgfile, note, new_note); - if (is_null_sha1(new_note)) { + create_note(object, &buf, msg.given || msgfile || remove, note, + new_note); + if (is_null_sha1(new_note)) remove_note(t, object); - logmsg = "Note removed by 'git notes edit'"; - } else { + else add_note(t, object, new_note, combine_notes_overwrite); - logmsg = "Note added by 'git notes edit'"; - } + snprintf(logmsg, sizeof(logmsg), "Note %s by 'git notes %s'", + is_null_sha1(new_note) ? "removed" : "added", argv[0]); commit_notes(t, logmsg); free_notes(t); diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index fe59e73c21..d29daac5b4 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -265,6 +265,33 @@ test_expect_success 'verify non-creation of note with -m ""' ' ! git notes show ' +test_expect_success 'remove note with "git notes remove" (setup)' ' + git notes remove HEAD^ +' + +cat > expect-rm-remove << EOF +commit bd1753200303d0a0344be813e504253b3d98e74d +Author: A U Thor +Date: Thu Apr 7 15:17:13 2005 -0700 + + 5th + +commit 15023535574ded8b1a89052b32673f84cf9582b8 +Author: A U Thor +Date: Thu Apr 7 15:16:13 2005 -0700 + + 4th +EOF + +printf "\n" >> expect-rm-remove +cat expect-multiline >> expect-rm-remove + +test_expect_success 'verify note removal with "git notes remove"' ' + git log -4 > output && + test_cmp expect-rm-remove output && + ! git notes show HEAD^ +' + test_expect_success 'create other note on a different notes ref (setup)' ' : > a6 && git add a6 && From b0032d1e06e4a53d908309fce6ad3c5f5a3559a3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:26 +0100 Subject: [PATCH 0067/3211] t3305: Verify that removing notes triggers automatic fanout consolidation Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- t/t3305-notes-fanout.sh | 47 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh index 823b0ff23b..c6d263b236 100755 --- a/t/t3305-notes-fanout.sh +++ b/t/t3305-notes-fanout.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='Test that adding many notes triggers automatic fanout restructuring' +test_description='Test that adding/removing many notes triggers automatic fanout restructuring' . ./test-lib.sh @@ -47,4 +47,49 @@ test_expect_success 'many notes created with git-notes triggers fanout' ' done ' +test_expect_success 'deleting most notes with git-notes' ' + num_notes=250 && + i=0 && + git rev-list HEAD | + while read sha1 + do + i=$(($i + 1)) && + if test $i -gt $num_notes + then + break + fi && + test_tick && + git notes remove "$sha1" + done +' + +test_expect_success 'most notes deleted correctly with git-notes' ' + git log HEAD~250 | grep "^ " > output && + i=50 && + while test $i -gt 0 + do + echo " commit #$i" && + echo " note #$i" && + i=$(($i - 1)); + done > expect && + test_cmp expect output +' + +test_expect_success 'deleting most notes triggers fanout consolidation' ' + # Expect entire notes tree to have a fanout == 0 + git ls-tree -r --name-only refs/notes/commits | + while read path + do + case "$path" in + ????????????????????????????????????????) + : true + ;; + *) + echo "Invalid path \"$path\"" && + return 1 + ;; + esac + done +' + test_done From 00fbe63627b72c807e558643f0634e435137122f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:27 +0100 Subject: [PATCH 0068/3211] Notes API: prune_notes(): Prune notes that belong to non-existing objects When an object is made unreachable by Git, any notes that annotate that object are not automagically made unreachable, since all notes are always trivially reachable from a notes ref. In order to remove notes for non-existing objects, we therefore need to add functionality for traversing the notes tree and explicitly removing references to notes that annotate non-reachable objects. Thus the notes objects themselves also become unreachable, and are removed by a later garbage collect. prune_notes() performs this traversal (by using for_each_note() internally), and removes the notes in question from the notes tree. Note that the effect of prune_notes() is not persistent unless a subsequent call to write_notes_tree() is made. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- notes.c | 39 +++++++++++++++++++++++++++++++++++++++ notes.h | 12 ++++++++++++ 2 files changed, 51 insertions(+) diff --git a/notes.c b/notes.c index d432517587..3ba3e6de17 100644 --- a/notes.c +++ b/notes.c @@ -749,6 +749,29 @@ static int write_each_note(const unsigned char *object_sha1, write_each_note_helper(d->root, note_path, mode, note_sha1); } +struct note_delete_list { + struct note_delete_list *next; + const unsigned char *sha1; +}; + +static int prune_notes_helper(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + struct note_delete_list **l = (struct note_delete_list **) cb_data; + struct note_delete_list *n; + + if (has_sha1_file(object_sha1)) + return 0; /* nothing to do for this note */ + + /* failed to find object => prune this note */ + n = (struct note_delete_list *) xmalloc(sizeof(*n)); + n->next = *l; + n->sha1 = object_sha1; + *l = n; + return 0; +} + int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1) { @@ -922,6 +945,22 @@ int write_notes_tree(struct notes_tree *t, unsigned char *result) return ret; } +void prune_notes(struct notes_tree *t) +{ + struct note_delete_list *l = NULL; + + if (!t) + t = &default_notes_tree; + assert(t->initialized); + + for_each_note(t, 0, prune_notes_helper, &l); + + while (l) { + remove_note(t, l->sha1); + l = l->next; + } +} + void free_notes(struct notes_tree *t) { if (!t) diff --git a/notes.h b/notes.h index f98578f91f..bad03ccab7 100644 --- a/notes.h +++ b/notes.h @@ -161,6 +161,18 @@ int for_each_note(struct notes_tree *t, int flags, each_note_fn fn, */ int write_notes_tree(struct notes_tree *t, unsigned char *result); +/* + * Remove all notes annotating non-existing objects from the given notes tree + * + * All notes in the given notes_tree that are associated with objects that no + * longer exist in the database, are removed from the notes tree. + * + * IMPORTANT: The changes made by prune_notes() to the given notes_tree + * structure are not persistent until a subsequent call to write_notes_tree() + * returns zero. + */ +void prune_notes(struct notes_tree *t); + /* * Free (and de-initialize) the given notes_tree structure * From d6576e1fe3ee4bd7bd2cf01d17499c94da24876f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:28 +0100 Subject: [PATCH 0069/3211] builtin-notes: Add "prune" subcommand for removing notes for missing objects "git notes prune" will remove all notes that annotate unreachable/non- existing objects. The patch includes tests verifying correct behaviour of the new subcommand. Suggested-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 4 +- builtin-notes.c | 28 +++++++---- t/t3306-notes-prune.sh | 94 +++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 11 deletions(-) create mode 100755 t/t3306-notes-prune.sh diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index a52d23ac11..3973f90265 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -8,7 +8,7 @@ git-notes - Add/inspect commit notes SYNOPSIS -------- [verse] -'git notes' (edit [-F | -m ] | show | remove) [commit] +'git notes' (edit [-F | -m ] | show | remove | prune) [commit] DESCRIPTION ----------- @@ -37,6 +37,8 @@ remove:: This is equivalent to specifying an empty note message to the `edit` subcommand. +prune:: + Remove all notes for non-existing/unreachable objects. OPTIONS ------- diff --git a/builtin-notes.c b/builtin-notes.c index 7c4007523b..48bc455a1a 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -21,6 +21,7 @@ static const char * const git_notes_usage[] = { "git notes edit [-m | -F ] []", "git notes show []", "git notes remove []", + "git notes prune", NULL }; @@ -201,7 +202,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *object_ref; char logmsg[100]; - int edit = 0, show = 0, remove = 0; + int edit = 0, show = 0, remove = 0, prune = 0; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { @@ -222,8 +223,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) show = 1; else if (argc && !strcmp(argv[0], "remove")) remove = 1; + else if (argc && !strcmp(argv[0], "prune")) + prune = 1; - if (edit + show + remove != 1) + if (edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); if ((msg.given || msgfile) && !edit) { @@ -237,7 +240,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) } object_ref = argc == 2 ? argv[1] : "HEAD"; - if (argc > 2) { + if (argc > 2 || (prune && argc > 1)) { error("too many parameters"); usage_with_options(git_notes_usage, options); } @@ -264,7 +267,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) return execv_git_cmd(show_args); } - /* edit/remove command */ + /* edit/remove/prune command */ if (remove) strbuf_reset(&buf); @@ -278,12 +281,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix) die_errno("could not open or read '%s'", msgfile); } - create_note(object, &buf, msg.given || msgfile || remove, note, - new_note); - if (is_null_sha1(new_note)) - remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + if (prune) { + hashclr(new_note); + prune_notes(t); + } else { + create_note(object, &buf, msg.given || msgfile || remove, note, + new_note); + if (is_null_sha1(new_note)) + remove_note(t, object); + else + add_note(t, object, new_note, combine_notes_overwrite); + } snprintf(logmsg, sizeof(logmsg), "Note %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); commit_notes(t, logmsg); diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh new file mode 100755 index 0000000000..b0adc7e5bd --- /dev/null +++ b/t/t3306-notes-prune.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +test_description='Test git notes prune' + +. ./test-lib.sh + +test_expect_success 'setup: create a few commits with notes' ' + + : > file1 && + git add file1 && + test_tick && + git commit -m 1st && + git notes edit -m "Note #1" && + : > file2 && + git add file2 && + test_tick && + git commit -m 2nd && + git notes edit -m "Note #2" && + : > file3 && + git add file3 && + test_tick && + git commit -m 3rd && + git notes edit -m "Note #3" +' + +cat > expect < +Date: Thu Apr 7 15:15:13 2005 -0700 + + 3rd + +Notes: + Note #3 + +commit 08341ad9e94faa089d60fd3f523affb25c6da189 +Author: A U Thor +Date: Thu Apr 7 15:14:13 2005 -0700 + + 2nd + +Notes: + Note #2 + +commit ab5f302035f2e7aaf04265f08b42034c23256e1f +Author: A U Thor +Date: Thu Apr 7 15:13:13 2005 -0700 + + 1st + +Notes: + Note #1 +END_OF_LOG + +test_expect_success 'verify commits and notes' ' + + git log > actual && + test_cmp expect actual +' + +test_expect_success 'remove some commits' ' + + git reset --hard HEAD~2 && + git reflog expire --expire=now HEAD && + git gc --prune=now +' + +test_expect_success 'verify that commits are gone' ' + + ! git cat-file -p 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + ! git cat-file -p 08341ad9e94faa089d60fd3f523affb25c6da189 && + git cat-file -p ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_expect_success 'verify that notes are still present' ' + + git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_expect_success 'prune notes' ' + + git notes prune +' + +test_expect_success 'verify that notes are gone' ' + + ! git notes show 5ee1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && + ! git notes show 08341ad9e94faa089d60fd3f523affb25c6da189 && + git notes show ab5f302035f2e7aaf04265f08b42034c23256e1f +' + +test_done From 7d541174650fb209a00b629d113cb954c24a19de Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:29 +0100 Subject: [PATCH 0070/3211] Documentation: Generalize git-notes docs to 'objects' instead of 'commits' Notes can annotate arbitrary objects (not only commits), but this is not reflected in the current documentation. This patch rewrites the git-notes documentation to talk about 'objects' instead of 'commits'. However, the discussion on commit notes and how they are displayed by 'git log' is largely preserved. Finally, I add myself to the Author/Documentation credits, since most of the lines in the git-notes code and docs are blamed on me. Cc: Johannes Schindelin Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 3973f90265..84db2a4678 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -3,37 +3,41 @@ git-notes(1) NAME ---- -git-notes - Add/inspect commit notes +git-notes - Add/inspect object notes SYNOPSIS -------- [verse] -'git notes' (edit [-F | -m ] | show | remove | prune) [commit] +'git notes' (edit [-F | -m ] | show | remove | prune) [object] DESCRIPTION ----------- -This command allows you to add/remove notes to/from commit messages, -without changing the commit. To discern these notes from the message -stored in the commit object, the notes are indented like the message, -after an unindented line saying "Notes:". +This command allows you to add/remove notes to/from objects, without +changing the objects themselves. -To disable commit notes, you have to set the config variable -core.notesRef to the empty string. Alternatively, you can set it -to a different ref, something like "refs/notes/bugzilla". This setting -can be overridden by the environment variable "GIT_NOTES_REF". +A typical use of notes is to extend a commit message without having +to change the commit itself. Such commit notes can be shown by `git log` +along with the original commit message. To discern these notes from the +message stored in the commit object, the notes are indented like the +message, after an unindented line saying "Notes:". + +To disable notes, you have to set the config variable core.notesRef to +the empty string. Alternatively, you can set it to a different ref, +something like "refs/notes/bugzilla". This setting can be overridden +by the environment variable "GIT_NOTES_REF". SUBCOMMANDS ----------- edit:: - Edit the notes for a given commit (defaults to HEAD). + Edit the notes for a given object (defaults to HEAD). show:: - Show the notes for a given commit (defaults to HEAD). + Show the notes for a given object (defaults to HEAD). remove:: - Remove the notes for a given commit (defaults to HEAD). + Remove the notes for a given object (defaults to HEAD). This is equivalent to specifying an empty note message to the `edit` subcommand. @@ -54,11 +58,12 @@ OPTIONS Author ------ -Written by Johannes Schindelin +Written by Johannes Schindelin and +Johan Herland Documentation ------------- -Documentation by Johannes Schindelin +Documentation by Johannes Schindelin and Johan Herland GIT --- From e397421abf1003c4da824441f91d8c990b70fa98 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:30 +0100 Subject: [PATCH 0071/3211] builtin-notes: Add "list" subcommand for listing note objects "git notes list" will list all note objects in the current notes ref (in the format " "). "git notes list " will list the note object associated with the given , or fail loudly if the given has no associated notes. If no arguments are given to "git notes", it defaults to the "list" subcommand. This is for pseudo-compatibility with "git tag" and "git branch". The patch includes tests verifying correct behaviour of the new subcommand. Suggested-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 13 ++++++++++++- builtin-notes.c | 37 ++++++++++++++++++++++++++++++++----- t/t3301-notes.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 84db2a4678..4d29d5fb94 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -8,7 +8,12 @@ git-notes - Add/inspect object notes SYNOPSIS -------- [verse] -'git notes' (edit [-F | -m ] | show | remove | prune) [object] +'git notes' [list []] +'git notes' edit [-F | -m ] [] +'git notes' show [] +'git notes' remove [] +'git notes' prune + DESCRIPTION ----------- @@ -30,6 +35,12 @@ by the environment variable "GIT_NOTES_REF". SUBCOMMANDS ----------- +list:: + List the notes object for a given object. If no object is + given, show a list of all note objects and the objects they + annotate (in the format " "). + This is the default subcommand if no subcommand is given. + edit:: Edit the notes for a given object (defaults to HEAD). diff --git a/builtin-notes.c b/builtin-notes.c index 48bc455a1a..b808534129 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -18,6 +18,7 @@ #include "parse-options.h" static const char * const git_notes_usage[] = { + "git notes [list []]", "git notes edit [-m | -F ] []", "git notes show []", "git notes remove []", @@ -31,6 +32,14 @@ static const char note_template[] = "# Write/edit the notes for the following object:\n" "#\n"; +static int list_each_note(const unsigned char *object_sha1, + const unsigned char *note_sha1, char *note_path, + void *cb_data) +{ + printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1)); + return 0; +} + static void write_note_data(int fd, const unsigned char *sha1) { unsigned long size; @@ -202,7 +211,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *object_ref; char logmsg[100]; - int edit = 0, show = 0, remove = 0, prune = 0; + int list = 0, edit = 0, show = 0, remove = 0, prune = 0; + int given_object; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { @@ -217,7 +227,9 @@ int cmd_notes(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_notes_usage, 0); - if (argc && !strcmp(argv[0], "edit")) + if (argc && !strcmp(argv[0], "list")) + list = 1; + else if (argc && !strcmp(argv[0], "edit")) edit = 1; else if (argc && !strcmp(argv[0], "show")) show = 1; @@ -225,8 +237,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) remove = 1; else if (argc && !strcmp(argv[0], "prune")) prune = 1; + else if (!argc) + list = 1; /* Default to 'list' if no other subcommand given */ - if (edit + show + remove + prune != 1) + if (list + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); if ((msg.given || msgfile) && !edit) { @@ -239,7 +253,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); } - object_ref = argc == 2 ? argv[1] : "HEAD"; + given_object = argc == 2; + object_ref = given_object ? argv[1] : "HEAD"; if (argc > 2 || (prune && argc > 1)) { error("too many parameters"); usage_with_options(git_notes_usage, options); @@ -257,9 +272,21 @@ int cmd_notes(int argc, const char **argv, const char *prefix) note = get_note(t, object); + /* list command */ + + if (list) { + if (given_object) { + if (note) { + puts(sha1_to_hex(note)); + return 0; + } + } else + return for_each_note(t, 0, list_each_note, NULL); + } + /* show command */ - if (show && !note) { + if ((list || show) && !note) { error("No note found for object %s.", sha1_to_hex(object)); return 1; } else if (show) { diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index d29daac5b4..768a1cb028 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -292,6 +292,38 @@ test_expect_success 'verify note removal with "git notes remove"' ' ! git notes show HEAD^ ' +cat > expect << EOF +c18dc024e14f08d18d14eea0d747ff692d66d6a3 1584215f1d29c65e99c6c6848626553fdd07fd75 +c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5 +EOF + +test_expect_success 'list notes with "git notes list"' ' + git notes list > output && + test_cmp expect output +' + +test_expect_success 'list notes with "git notes"' ' + git notes > output && + test_cmp expect output +' + +cat > expect << EOF +c18dc024e14f08d18d14eea0d747ff692d66d6a3 +EOF + +test_expect_success 'list specific note with "git notes list "' ' + git notes list HEAD^^ > output && + test_cmp expect output +' + +cat > expect << EOF +EOF + +test_expect_success 'listing non-existing notes fails' ' + test_must_fail git notes list HEAD > output && + test_cmp expect output +' + test_expect_success 'create other note on a different notes ref (setup)' ' : > a6 && git add a6 && From ba20f15e0a220705695a1ee19fb28234861b890d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:31 +0100 Subject: [PATCH 0072/3211] builtin-notes: Add --message/--file aliases for -m/-F options Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 2 ++ builtin-notes.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 4d29d5fb94..8969f6f8fa 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -58,11 +58,13 @@ prune:: OPTIONS ------- -m :: +--message=:: Use the given note message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. -F :: +--file=:: Take the note message from the given file. Use '-' to read the note message from the standard input. diff --git a/builtin-notes.c b/builtin-notes.c index b808534129..ec959bcb31 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -217,9 +217,9 @@ int cmd_notes(int argc, const char **argv, const char *prefix) struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { OPT_GROUP("Notes edit options"), - OPT_CALLBACK('m', NULL, &msg, "msg", + OPT_CALLBACK('m', "message", &msg, "msg", "note contents as a string", parse_msg_arg), - OPT_FILENAME('F', NULL, &msgfile, "note contents in a file"), + OPT_FILENAME('F', "file", &msgfile, "note contents in a file"), OPT_END() }; From 7aa4754e552eff22d70d496dea73a9c7639d66d3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:32 +0100 Subject: [PATCH 0073/3211] builtin-notes: Add "add" subcommand for adding notes to objects "git notes add" is identical to "git notes edit" except that instead of editing existing notes for a given object, you can only add notes to an object that currently has none. If "git notes add" finds existing notes for the given object, the addition is aborted. However, if the new -f/--force option is used, "git notes add" will _overwrite_ the existing notes with the new notes contents. If there is no existing notes for the given object. "git notes add" is identical to "git notes edit" (i.e. it adds a new note). The patch includes tests verifying correct behaviour of the new subcommand. Suggested-by: Joey Hess Improved-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 11 ++++++++ builtin-notes.c | 30 +++++++++++++++++--- t/t3301-notes.sh | 55 +++++++++++++++++++++++++------------ 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 8969f6f8fa..94e12b57e4 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -9,6 +9,7 @@ SYNOPSIS -------- [verse] 'git notes' [list []] +'git notes' add [-f] [-F | -m ] [] 'git notes' edit [-F | -m ] [] 'git notes' show [] 'git notes' remove [] @@ -41,6 +42,11 @@ list:: annotate (in the format " "). This is the default subcommand if no subcommand is given. +add:: + Add notes for a given object (defaults to HEAD). Abort if the + object already has notes, abort. (use `-f` to overwrite an + existing note). + edit:: Edit the notes for a given object (defaults to HEAD). @@ -57,6 +63,11 @@ prune:: OPTIONS ------- +-f:: +--force:: + When adding notes to an object that already has notes, + overwrite the existing notes (instead of aborting). + -m :: --message=:: Use the given note message (instead of prompting). diff --git a/builtin-notes.c b/builtin-notes.c index ec959bcb31..006edf6b19 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -19,6 +19,7 @@ static const char * const git_notes_usage[] = { "git notes [list []]", + "git notes add [-f] [-m | -F ] []", "git notes edit [-m | -F ] []", "git notes show []", "git notes remove []", @@ -211,7 +212,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *object_ref; char logmsg[100]; - int list = 0, edit = 0, show = 0, remove = 0, prune = 0; + int list = 0, add = 0, edit = 0, show = 0, remove = 0, prune = 0, + force = 0; int given_object; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; @@ -220,6 +222,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) OPT_CALLBACK('m', "message", &msg, "msg", "note contents as a string", parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, "note contents in a file"), + OPT_BOOLEAN('f', "force", &force, "replace existing notes"), OPT_END() }; @@ -229,6 +232,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (argc && !strcmp(argv[0], "list")) list = 1; + else if (argc && !strcmp(argv[0], "add")) + add = 1; else if (argc && !strcmp(argv[0], "edit")) edit = 1; else if (argc && !strcmp(argv[0], "show")) @@ -240,10 +245,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) else if (!argc) list = 1; /* Default to 'list' if no other subcommand given */ - if (list + edit + show + remove + prune != 1) + if (list + add + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); - if ((msg.given || msgfile) && !edit) { + if ((msg.given || msgfile) && !(add || edit)) { error("cannot use -m/-F options with %s subcommand.", argv[0]); usage_with_options(git_notes_usage, options); } @@ -253,6 +258,11 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); } + if (force && !add) { + error("cannot use -f option with %s subcommand.", argv[0]); + usage_with_options(git_notes_usage, options); + } + given_object = argc == 2; object_ref = given_object ? argv[1] : "HEAD"; if (argc > 2 || (prune && argc > 1)) { @@ -294,7 +304,19 @@ int cmd_notes(int argc, const char **argv, const char *prefix) return execv_git_cmd(show_args); } - /* edit/remove/prune command */ + /* add/edit/remove/prune command */ + + if (add && note) { + if (force) + fprintf(stderr, "Overwriting existing notes for object %s\n", + sha1_to_hex(object)); + else { + error("Cannot add notes. Found existing notes for object %s. " + "Use '-f' to overwrite existing notes", + sha1_to_hex(object)); + return 1; + } + } if (remove) strbuf_reset(&buf); diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 768a1cb028..df458ca939 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -16,7 +16,7 @@ GIT_EDITOR=./fake_editor.sh export GIT_EDITOR test_expect_success 'cannot annotate non-existing HEAD' ' - (MSG=3 && export MSG && test_must_fail git notes edit) + (MSG=3 && export MSG && test_must_fail git notes add) ' test_expect_success setup ' @@ -32,18 +32,18 @@ test_expect_success setup ' test_expect_success 'need valid notes ref' ' (MSG=1 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && - test_must_fail git notes edit) && + test_must_fail git notes add) && (MSG=2 GIT_NOTES_REF=/ && export MSG GIT_NOTES_REF && test_must_fail git notes show) ' -test_expect_success 'refusing to edit in refs/heads/' ' +test_expect_success 'refusing to add notes in refs/heads/' ' (MSG=1 GIT_NOTES_REF=refs/heads/bogus && export MSG GIT_NOTES_REF && - test_must_fail git notes edit) + test_must_fail git notes add) ' -test_expect_success 'refusing to edit in refs/remotes/' ' +test_expect_success 'refusing to edit notes in refs/remotes/' ' (MSG=1 GIT_NOTES_REF=refs/remotes/bogus && export MSG GIT_NOTES_REF && test_must_fail git notes edit) @@ -56,16 +56,34 @@ test_expect_success 'handle empty notes gracefully' ' test_expect_success 'create notes' ' git config core.notesRef refs/notes/commits && - MSG=b0 git notes edit && + MSG=b4 git notes add && test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && - test b0 = $(git notes show) && + test b4 = $(git notes show) && git show HEAD^ && test_must_fail git notes show HEAD^ ' test_expect_success 'edit existing notes' ' - MSG=b1 git notes edit && + MSG=b3 git notes edit && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b3 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'cannot add note where one exists' ' + ! MSG=b2 git notes add && + test ! -f .git/NOTES_EDITMSG && + test 1 = $(git ls-tree refs/notes/commits | wc -l) && + test b3 = $(git notes show) && + git show HEAD^ && + test_must_fail git notes show HEAD^ +' + +test_expect_success 'can overwrite existing note with "git notes add -f"' ' + MSG=b1 git notes add -f && test ! -f .git/NOTES_EDITMSG && test 1 = $(git ls-tree refs/notes/commits | wc -l) && test b1 = $(git notes show) && @@ -89,6 +107,7 @@ test_expect_success 'show notes' ' git log -1 > output && test_cmp expect output ' + test_expect_success 'create multi-line notes (setup)' ' : > a3 && git add a3 && @@ -96,7 +115,7 @@ test_expect_success 'create multi-line notes (setup)' ' git commit -m 3rd && MSG="b3 c3c3c3c3 -d3d3d3" git notes edit +d3d3d3" git notes add ' cat > expect-multiline << EOF @@ -125,7 +144,7 @@ test_expect_success 'create -F notes (setup)' ' test_tick && git commit -m 4th && echo "xyzzy" > note5 && - git notes edit -F note5 + git notes add -F note5 ' cat > expect-F << EOF @@ -205,7 +224,7 @@ test_expect_success 'create -m notes (setup)' ' git add a5 && test_tick && git commit -m 5th && - git notes edit -m spam -m "foo + git notes add -m spam -m "foo bar baz" ' @@ -234,8 +253,8 @@ test_expect_success 'show -m notes' ' test_cmp expect-m output ' -test_expect_success 'remove note with -F /dev/null (setup)' ' - git notes edit -F /dev/null +test_expect_success 'remove note with add -f -F /dev/null (setup)' ' + git notes add -f -F /dev/null ' cat > expect-rm-F << EOF @@ -256,7 +275,7 @@ test_expect_success 'verify note removal with -F /dev/null' ' ' test_expect_success 'do not create empty note with -m "" (setup)' ' - git notes edit -m "" + git notes add -m "" ' test_expect_success 'verify non-creation of note with -m ""' ' @@ -329,7 +348,7 @@ test_expect_success 'create other note on a different notes ref (setup)' ' git add a6 && test_tick && git commit -m 6th && - GIT_NOTES_REF="refs/notes/other" git notes edit -m "other note" + GIT_NOTES_REF="refs/notes/other" git notes add -m "other note" ' cat > expect-other << EOF @@ -374,17 +393,17 @@ test_expect_success 'Do not show note when core.notesRef is overridden' ' test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' echo "Note on a tree" > expect - git notes edit -m "Note on a tree" HEAD: && + git notes add -m "Note on a tree" HEAD: && git notes show HEAD: > actual && test_cmp expect actual && echo "Note on a blob" > expect filename=$(git ls-tree --name-only HEAD | head -n1) && - git notes edit -m "Note on a blob" HEAD:$filename && + git notes add -m "Note on a blob" HEAD:$filename && git notes show HEAD:$filename > actual && test_cmp expect actual && echo "Note on a tag" > expect git tag -a -m "This is an annotated tag" foobar HEAD^ && - git notes edit -m "Note on a tag" foobar && + git notes add -m "Note on a tag" foobar && git notes show foobar > actual && test_cmp expect actual ' From 2347fae50b2f75c6c0b362bd9ef24249419ed2b1 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:33 +0100 Subject: [PATCH 0074/3211] builtin-notes: Add "append" subcommand for appending to note objects "git notes append" is equivalent to "git notes edit" except that instead of editing existing notes contents, you can only append to it. This is useful for quickly adding annotations like e.g.: git notes append -m "Acked-by: A U Thor " "git notes append" takes the same -m/-F options as "git notes add". If there is no existing note to append to, "git notes append" is identical to "git notes add" (i.e. it adds a new note). The patch includes tests verifying correct behaviour of the new subcommand. Suggested-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 5 +++++ builtin-notes.c | 35 ++++++++++++++++++++++++++--------- t/t3301-notes.sh | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 94e12b57e4..35dd8fa8a0 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git notes' [list []] 'git notes' add [-f] [-F | -m ] [] +'git notes' append [-F | -m ] [] 'git notes' edit [-F | -m ] [] 'git notes' show [] 'git notes' remove [] @@ -47,6 +48,10 @@ add:: object already has notes, abort. (use `-f` to overwrite an existing note). +append:: + Append to the notes of an existing object (defaults to HEAD). + Creates a new notes object if needed. + edit:: Edit the notes for a given object (defaults to HEAD). diff --git a/builtin-notes.c b/builtin-notes.c index 006edf6b19..c88df00b3a 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -20,6 +20,7 @@ static const char * const git_notes_usage[] = { "git notes [list []]", "git notes add [-f] [-m | -F ] []", + "git notes append [-m | -F ] []", "git notes edit [-m | -F ] []", "git notes show []", "git notes remove []", @@ -94,7 +95,7 @@ static void write_commented_object(int fd, const unsigned char *object) static void create_note(const unsigned char *object, struct strbuf *buf, - int skip_editor, + int skip_editor, int append_only, const unsigned char *prev, unsigned char *result) { @@ -109,7 +110,7 @@ static void create_note(const unsigned char *object, if (fd < 0) die_errno("could not create file '%s'", path); - if (prev) + if (prev && !append_only) write_note_data(fd, prev); write_or_die(fd, note_template, strlen(note_template)); @@ -125,6 +126,20 @@ static void create_note(const unsigned char *object, stripspace(buf, 1); + if (prev && append_only) { + /* Append buf to previous note contents */ + unsigned long size; + enum object_type type; + char *prev_buf = read_sha1_file(prev, &type, &size); + + strbuf_grow(buf, size + 1); + if (buf->len && prev_buf && size) + strbuf_insert(buf, 0, "\n", 1); + if (prev_buf && size) + strbuf_insert(buf, 0, prev_buf, size); + free(prev_buf); + } + if (!buf->len) { fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); @@ -212,8 +227,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *object_ref; char logmsg[100]; - int list = 0, add = 0, edit = 0, show = 0, remove = 0, prune = 0, - force = 0; + int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0, + prune = 0, force = 0; int given_object; const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; @@ -234,6 +249,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) list = 1; else if (argc && !strcmp(argv[0], "add")) add = 1; + else if (argc && !strcmp(argv[0], "append")) + append = 1; else if (argc && !strcmp(argv[0], "edit")) edit = 1; else if (argc && !strcmp(argv[0], "show")) @@ -245,10 +262,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) else if (!argc) list = 1; /* Default to 'list' if no other subcommand given */ - if (list + add + edit + show + remove + prune != 1) + if (list + add + append + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); - if ((msg.given || msgfile) && !(add || edit)) { + if ((msg.given || msgfile) && !(add || append || edit)) { error("cannot use -m/-F options with %s subcommand.", argv[0]); usage_with_options(git_notes_usage, options); } @@ -304,7 +321,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) return execv_git_cmd(show_args); } - /* add/edit/remove/prune command */ + /* add/append/edit/remove/prune command */ if (add && note) { if (force) @@ -334,8 +351,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) hashclr(new_note); prune_notes(t); } else { - create_note(object, &buf, msg.given || msgfile || remove, note, - new_note); + create_note(object, &buf, msg.given || msgfile || remove, + append, note, new_note); if (is_null_sha1(new_note)) remove_note(t, object); else diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index df458ca939..290ed63d4e 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -343,6 +343,42 @@ test_expect_success 'listing non-existing notes fails' ' test_cmp expect output ' +cat > expect << EOF +Initial set of notes + +More notes appended with git notes append +EOF + +test_expect_success 'append to existing note with "git notes append"' ' + git notes add -m "Initial set of notes" && + git notes append -m "More notes appended with git notes append" && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'appending empty string does not change existing note' ' + git notes append -m "" && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'git notes append == add when there is no existing note' ' + git notes remove HEAD && + test_must_fail git notes list HEAD && + git notes append -m "Initial set of notes + +More notes appended with git notes append" && + git notes show > output && + test_cmp expect output +' + +test_expect_success 'appending empty string to non-existing note does not create note' ' + git notes remove HEAD && + test_must_fail git notes list HEAD && + git notes append -m "" && + test_must_fail git notes list HEAD +' + test_expect_success 'create other note on a different notes ref (setup)' ' : > a6 && git add a6 && From aaec9bcf6d85a084725e8386bf314a6ef6842468 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:34 +0100 Subject: [PATCH 0075/3211] builtin-notes: Deprecate the -m/-F options for "git notes edit" The semantics for "git notes edit -m/-F" overlap with those for "git notes add -f", and the behaviour (i.e. overwriting existing notes with the given message/file) is more intuitively captured by (and better documented with) "git notes add -f". Suggested-by: Junio C Hamano Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 2 +- builtin-notes.c | 10 ++++++++-- t/t3304-notes-mixed.sh | 2 +- t/t3305-notes-fanout.sh | 2 +- t/t3306-notes-prune.sh | 6 +++--- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 35dd8fa8a0..53c5d9014d 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git notes' [list []] 'git notes' add [-f] [-F | -m ] [] 'git notes' append [-F | -m ] [] -'git notes' edit [-F | -m ] [] +'git notes' edit [] 'git notes' show [] 'git notes' remove [] 'git notes' prune diff --git a/builtin-notes.c b/builtin-notes.c index c88df00b3a..572b47746b 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -21,7 +21,7 @@ static const char * const git_notes_usage[] = { "git notes [list []]", "git notes add [-f] [-m | -F ] []", "git notes append [-m | -F ] []", - "git notes edit [-m | -F ] []", + "git notes edit []", "git notes show []", "git notes remove []", "git notes prune", @@ -233,7 +233,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { - OPT_GROUP("Notes edit options"), + OPT_GROUP("Notes options"), OPT_CALLBACK('m', "message", &msg, "msg", "note contents as a string", parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, "note contents in a file"), @@ -270,6 +270,12 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); } + if ((msg.given || msgfile) && edit) { + fprintf(stderr, "The -m and -F options has been deprecated for" + " the 'edit' subcommand.\n" + "Please use 'git notes add -f -m/-F' instead.\n"); + } + if (msg.given && msgfile) { error("mixing -m and -F options is not allowed."); usage_with_options(git_notes_usage, options); diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh index c975a6d3f7..1709e8c00b 100755 --- a/t/t3304-notes-mixed.sh +++ b/t/t3304-notes-mixed.sh @@ -188,7 +188,7 @@ test_expect_success "verify contents of non-notes" ' test_expect_success "git-notes preserves non-notes" ' test_tick && - git notes edit -m "foo bar" + git notes add -f -m "foo bar" ' test_expect_success "verify contents of non-notes after git-notes" ' diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh index c6d263b236..b1ea64b213 100755 --- a/t/t3305-notes-fanout.sh +++ b/t/t3305-notes-fanout.sh @@ -14,7 +14,7 @@ test_expect_success 'creating many notes with git-notes' ' echo "file for commit #$i" > file && git add file && git commit -q -m "commit #$i" && - git notes edit -m "note #$i" || return 1 + git notes add -m "note #$i" || return 1 done ' diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh index b0adc7e5bd..a0ed0353e6 100755 --- a/t/t3306-notes-prune.sh +++ b/t/t3306-notes-prune.sh @@ -10,17 +10,17 @@ test_expect_success 'setup: create a few commits with notes' ' git add file1 && test_tick && git commit -m 1st && - git notes edit -m "Note #1" && + git notes add -m "Note #1" && : > file2 && git add file2 && test_tick && git commit -m 2nd && - git notes edit -m "Note #2" && + git notes add -m "Note #2" && : > file3 && git add file3 && test_tick && git commit -m 3rd && - git notes edit -m "Note #3" + git notes add -m "Note #3" ' cat > expect < Date: Sat, 13 Feb 2010 22:28:35 +0100 Subject: [PATCH 0076/3211] builtin-notes: Refactor handling of -F option to allow combining -m and -F By moving the -F option handling into a separate function (parse_file_arg), we can start allowing several -F options, and mixed usage of -m and -F options. Each -m/-F given appends to the note message, in the order they are given on the command-line. The patch includes tests verifying correct behaviour of the new functionality. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- builtin-notes.c | 94 ++++++++++++++++++++++++++---------------------- t/t3301-notes.sh | 23 +++++++++++- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/builtin-notes.c b/builtin-notes.c index 572b47746b..190c46c3be 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -34,6 +34,11 @@ static const char note_template[] = "# Write/edit the notes for the following object:\n" "#\n"; +struct msg_arg { + int given; + struct strbuf buf; +}; + static int list_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@ -93,15 +98,13 @@ static void write_commented_object(int fd, const unsigned char *object) sha1_to_hex(object)); } -static void create_note(const unsigned char *object, - struct strbuf *buf, - int skip_editor, int append_only, - const unsigned char *prev, +static void create_note(const unsigned char *object, struct msg_arg *msg, + int append_only, const unsigned char *prev, unsigned char *result) { char *path = NULL; - if (!skip_editor) { + if (!msg->given) { int fd; /* write the template message before editing: */ @@ -118,34 +121,33 @@ static void create_note(const unsigned char *object, close(fd); - if (launch_editor(path, buf, NULL)) { + if (launch_editor(path, &(msg->buf), NULL)) { die("Please supply the note contents using either -m" \ " or -F option"); } + stripspace(&(msg->buf), 1); } - stripspace(buf, 1); - if (prev && append_only) { /* Append buf to previous note contents */ unsigned long size; enum object_type type; char *prev_buf = read_sha1_file(prev, &type, &size); - strbuf_grow(buf, size + 1); - if (buf->len && prev_buf && size) - strbuf_insert(buf, 0, "\n", 1); + strbuf_grow(&(msg->buf), size + 1); + if (msg->buf.len && prev_buf && size) + strbuf_insert(&(msg->buf), 0, "\n", 1); if (prev_buf && size) - strbuf_insert(buf, 0, prev_buf, size); + strbuf_insert(&(msg->buf), 0, prev_buf, size); free(prev_buf); } - if (!buf->len) { + if (!msg->buf.len) { fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); hashclr(result); } else { - if (write_sha1_file(buf->buf, buf->len, blob_type, result)) { + if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { error("unable to write note object"); if (path) error("The note contents has been left in %s", @@ -160,20 +162,39 @@ static void create_note(const unsigned char *object, } } -struct msg_arg { - int given; - struct strbuf buf; -}; - static int parse_msg_arg(const struct option *opt, const char *arg, int unset) { struct msg_arg *msg = opt->value; if (!arg) return -1; + + strbuf_grow(&(msg->buf), strlen(arg) + 2); if (msg->buf.len) - strbuf_addstr(&(msg->buf), "\n\n"); + strbuf_addstr(&(msg->buf), "\n"); strbuf_addstr(&(msg->buf), arg); + stripspace(&(msg->buf), 0); + + msg->given = 1; + return 0; +} + +static int parse_file_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + + if (!arg) + return -1; + + if (msg->buf.len) + strbuf_addstr(&(msg->buf), "\n"); + if (!strcmp(arg, "-")) { + if (strbuf_read(&(msg->buf), 0, 1024) < 0) + die_errno("cannot read '%s'", arg); + } else if (strbuf_read_file(&(msg->buf), arg, 1024) < 0) + die_errno("could not open or read '%s'", arg); + stripspace(&(msg->buf), 0); + msg->given = 1; return 0; } @@ -220,7 +241,6 @@ int commit_notes(struct notes_tree *t, const char *msg) int cmd_notes(int argc, const char **argv, const char *prefix) { - struct strbuf buf = STRBUF_INIT; struct notes_tree *t; unsigned char object[20], new_note[20]; const unsigned char *note; @@ -230,13 +250,13 @@ int cmd_notes(int argc, const char **argv, const char *prefix) int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0, prune = 0, force = 0; int given_object; - const char *msgfile = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct option options[] = { OPT_GROUP("Notes options"), - OPT_CALLBACK('m', "message", &msg, "msg", + OPT_CALLBACK('m', "message", &msg, "MSG", "note contents as a string", parse_msg_arg), - OPT_FILENAME('F', "file", &msgfile, "note contents in a file"), + OPT_CALLBACK('F', "file", &msg, "FILE", + "note contents in a file", parse_file_arg), OPT_BOOLEAN('f', "force", &force, "replace existing notes"), OPT_END() }; @@ -265,21 +285,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (list + add + append + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); - if ((msg.given || msgfile) && !(add || append || edit)) { + if (msg.given && !(add || append || edit)) { error("cannot use -m/-F options with %s subcommand.", argv[0]); usage_with_options(git_notes_usage, options); } - if ((msg.given || msgfile) && edit) { + if (msg.given && edit) { fprintf(stderr, "The -m and -F options has been deprecated for" " the 'edit' subcommand.\n" "Please use 'git notes add -f -m/-F' instead.\n"); } - if (msg.given && msgfile) { - error("mixing -m and -F options is not allowed."); - usage_with_options(git_notes_usage, options); - } if (force && !add) { error("cannot use -f option with %s subcommand.", argv[0]); @@ -341,24 +357,16 @@ int cmd_notes(int argc, const char **argv, const char *prefix) } } - if (remove) - strbuf_reset(&buf); - else if (msg.given) - strbuf_addbuf(&buf, &(msg.buf)); - else if (msgfile) { - if (!strcmp(msgfile, "-")) { - if (strbuf_read(&buf, 0, 1024) < 0) - die_errno("cannot read '%s'", msgfile); - } else if (strbuf_read_file(&buf, msgfile, 1024) < 0) - die_errno("could not open or read '%s'", msgfile); + if (remove) { + msg.given = 1; + strbuf_reset(&(msg.buf)); } if (prune) { hashclr(new_note); prune_notes(t); } else { - create_note(object, &buf, msg.given || msgfile || remove, - append, note, new_note); + create_note(object, &msg, append, note, new_note); if (is_null_sha1(new_note)) remove_note(t, object); else @@ -369,6 +377,6 @@ int cmd_notes(int argc, const char **argv, const char *prefix) commit_notes(t, logmsg); free_notes(t); - strbuf_release(&buf); + strbuf_release(&(msg.buf)); return 0; } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 290ed63d4e..07090e3997 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -284,8 +284,29 @@ test_expect_success 'verify non-creation of note with -m ""' ' ! git notes show ' +cat > expect-combine_m_and_F << EOF +foo + +xyzzy + +bar + +zyxxy + +baz +EOF + +test_expect_success 'create note with combination of -m and -F' ' + echo "xyzzy" > note_a && + echo "zyxxy" > note_b && + git notes add -m "foo" -F note_a -m "bar" -F note_b -m "baz" && + git notes show > output && + test_cmp expect-combine_m_and_F output +' + test_expect_success 'remove note with "git notes remove" (setup)' ' - git notes remove HEAD^ + git notes remove HEAD^ && + git notes remove ' cat > expect-rm-remove << EOF From 0691cff7dca6b68569183be991d59ebb72b6170e Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:36 +0100 Subject: [PATCH 0077/3211] builtin-notes: Add -c/-C options for reusing notes Inspired by the -c/-C options to "git commit", we teach these options to "git notes add/append" to allow reuse of note objects. With this patch in place, it is now easy to copy or move notes between objects. For example, to copy object A's notes to object B: git notes add [-f] -C $(git notes list A) B To move instead of copying, you simply remove the notes from the source object afterwards, e.g.: git notes remove A The patch includes tests verifying correct behaviour of the new functionality. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 12 +++- builtin-notes.c | 63 ++++++++++++++++---- t/t3301-notes.sh | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 12 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 53c5d9014d..15de4b344a 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git notes' [list []] -'git notes' add [-f] [-F | -m ] [] -'git notes' append [-F | -m ] [] +'git notes' add [-f] [-F | -m | (-c | -C) ] [] +'git notes' append [-F | -m | (-c | -C) ] [] 'git notes' edit [] 'git notes' show [] 'git notes' remove [] @@ -84,6 +84,14 @@ OPTIONS Take the note message from the given file. Use '-' to read the note message from the standard input. +-C :: +--reuse-message=:: + Reuse the note message from the given note object. + +-c :: +--reedit-message=:: + Like '-C', but with '-c' the editor is invoked, so that + the user can further edit the note message. Author ------ diff --git a/builtin-notes.c b/builtin-notes.c index 190c46c3be..98de1154c9 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -19,8 +19,8 @@ static const char * const git_notes_usage[] = { "git notes [list []]", - "git notes add [-f] [-m | -F ] []", - "git notes append [-m | -F ] []", + "git notes add [-f] [-m | -F | (-c | -C) ] []", + "git notes append [-m | -F | (-c | -C) ] []", "git notes edit []", "git notes show []", "git notes remove []", @@ -36,6 +36,7 @@ static const char note_template[] = struct msg_arg { int given; + int use_editor; struct strbuf buf; }; @@ -104,7 +105,7 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, { char *path = NULL; - if (!msg->given) { + if (msg->use_editor || !msg->given) { int fd; /* write the template message before editing: */ @@ -113,13 +114,16 @@ static void create_note(const unsigned char *object, struct msg_arg *msg, if (fd < 0) die_errno("could not create file '%s'", path); - if (prev && !append_only) + if (msg->given) + write_or_die(fd, msg->buf.buf, msg->buf.len); + else if (prev && !append_only) write_note_data(fd, prev); write_or_die(fd, note_template, strlen(note_template)); write_commented_object(fd, object); close(fd); + strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { die("Please supply the note contents using either -m" \ @@ -199,6 +203,40 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) return 0; } +static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + char *buf; + unsigned char object[20]; + enum object_type type; + unsigned long len; + + if (!arg) + return -1; + + if (msg->buf.len) + strbuf_addstr(&(msg->buf), "\n"); + + if (get_sha1(arg, object)) + die("Failed to resolve '%s' as a valid ref.", arg); + if (!(buf = read_sha1_file(object, &type, &len)) || !len) { + free(buf); + die("Failed to read object '%s'.", arg);; + } + strbuf_add(&(msg->buf), buf, len); + free(buf); + + msg->given = 1; + return 0; +} + +static int parse_reedit_arg(const struct option *opt, const char *arg, int unset) +{ + struct msg_arg *msg = opt->value; + msg->use_editor = 1; + return parse_reuse_arg(opt, arg, unset); +} + int commit_notes(struct notes_tree *t, const char *msg) { struct commit_list *parent; @@ -250,13 +288,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix) int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0, prune = 0, force = 0; int given_object; - struct msg_arg msg = { 0, STRBUF_INIT }; + struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { OPT_GROUP("Notes options"), OPT_CALLBACK('m', "message", &msg, "MSG", "note contents as a string", parse_msg_arg), OPT_CALLBACK('F', "file", &msg, "FILE", "note contents in a file", parse_file_arg), + OPT_CALLBACK('c', "reedit-message", &msg, "OBJECT", + "reuse and edit specified note object", parse_reedit_arg), + OPT_CALLBACK('C', "reuse-message", &msg, "OBJECT", + "reuse specified note object", parse_reuse_arg), OPT_BOOLEAN('f', "force", &force, "replace existing notes"), OPT_END() }; @@ -286,17 +328,17 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); if (msg.given && !(add || append || edit)) { - error("cannot use -m/-F options with %s subcommand.", argv[0]); + error("cannot use -m/-F/-c/-C options with %s subcommand.", + argv[0]); usage_with_options(git_notes_usage, options); } if (msg.given && edit) { - fprintf(stderr, "The -m and -F options has been deprecated for" - " the 'edit' subcommand.\n" - "Please use 'git notes add -f -m/-F' instead.\n"); + fprintf(stderr, "The -m/-F/-c/-C options have been deprecated " + "for the 'edit' subcommand.\n" + "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"); } - if (force && !add) { error("cannot use -f option with %s subcommand.", argv[0]); usage_with_options(git_notes_usage, options); @@ -359,6 +401,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (remove) { msg.given = 1; + msg.use_editor = 0; strbuf_reset(&(msg.buf)); } diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 07090e3997..6447e5f54e 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -465,4 +465,120 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' test_cmp expect actual ' +cat > expect << EOF +commit 2ede89468182a62d0bde2583c736089bcf7d7e92 +Author: A U Thor +Date: Thu Apr 7 15:19:13 2005 -0700 + + 7th + +Notes: + other note +EOF + +test_expect_success 'create note from other note with "git notes add -C"' ' + : > a7 && + git add a7 && + test_tick && + git commit -m 7th && + git notes add -C $(git notes list HEAD^) && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +test_expect_success 'create note from non-existing note with "git notes add -C" fails' ' + : > a8 && + git add a8 && + test_tick && + git commit -m 8th && + test_must_fail git notes add -C deadbeef && + test_must_fail git notes list HEAD +' + +cat > expect << EOF +commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b +Author: A U Thor +Date: Thu Apr 7 15:21:13 2005 -0700 + + 9th + +Notes: + yet another note +EOF + +test_expect_success 'create note from other note with "git notes add -c"' ' + : > a9 && + git add a9 && + test_tick && + git commit -m 9th && + MSG="yet another note" git notes add -c $(git notes list HEAD^^) && + git log -1 > actual && + test_cmp expect actual +' + +test_expect_success 'create note from non-existing note with "git notes add -c" fails' ' + : > a10 && + git add a10 && + test_tick && + git commit -m 10th && + test_must_fail MSG="yet another note" git notes add -c deadbeef && + test_must_fail git notes list HEAD +' + +cat > expect << EOF +commit 016e982bad97eacdbda0fcbd7ce5b0ba87c81f1b +Author: A U Thor +Date: Thu Apr 7 15:21:13 2005 -0700 + + 9th + +Notes: + yet another note +$whitespace + yet another note +EOF + +test_expect_success 'append to note from other note with "git notes append -C"' ' + git notes append -C $(git notes list HEAD^) HEAD^ && + git log -1 HEAD^ > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit ffed603236bfa3891c49644257a83598afe8ae5a +Author: A U Thor +Date: Thu Apr 7 15:22:13 2005 -0700 + + 10th + +Notes: + other note +EOF + +test_expect_success 'create note from other note with "git notes append -c"' ' + MSG="other note" git notes append -c $(git notes list HEAD^) && + git log -1 > actual && + test_cmp expect actual +' + +cat > expect << EOF +commit ffed603236bfa3891c49644257a83598afe8ae5a +Author: A U Thor +Date: Thu Apr 7 15:22:13 2005 -0700 + + 10th + +Notes: + other note +$whitespace + yet another note +EOF + +test_expect_success 'append to note from other note with "git notes append -c"' ' + MSG="yet another note" git notes append -c $(git notes list HEAD) && + git log -1 > actual && + test_cmp expect actual +' + test_done From 5848769f9de68cfb0735710c6c4d2f08aa53f317 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:37 +0100 Subject: [PATCH 0078/3211] builtin-notes: Misc. refactoring of argc and exit value handling This is in preparation of future patches that add additional subcommands. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- builtin-notes.c | 61 +++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/builtin-notes.c b/builtin-notes.c index 98de1154c9..bbf98a9f6f 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -287,7 +287,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0, prune = 0, force = 0; - int given_object; + int given_object = 0, i = 1, retval = 0; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { OPT_GROUP("Notes options"), @@ -321,8 +321,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) remove = 1; else if (argc && !strcmp(argv[0], "prune")) prune = 1; - else if (!argc) + else if (!argc) { list = 1; /* Default to 'list' if no other subcommand given */ + i = 0; + } if (list + add + append + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); @@ -344,9 +346,10 @@ int cmd_notes(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_usage, options); } - given_object = argc == 2; - object_ref = given_object ? argv[1] : "HEAD"; - if (argc > 2 || (prune && argc > 1)) { + given_object = argc > i; + object_ref = given_object ? argv[i++] : "HEAD"; + + if (argc > i || (prune && given_object)) { error("too many parameters"); usage_with_options(git_notes_usage, options); } @@ -369,34 +372,38 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (given_object) { if (note) { puts(sha1_to_hex(note)); - return 0; + goto end; } - } else - return for_each_note(t, 0, list_each_note, NULL); + } else { + retval = for_each_note(t, 0, list_each_note, NULL); + goto end; + } } /* show command */ if ((list || show) && !note) { error("No note found for object %s.", sha1_to_hex(object)); - return 1; + retval = 1; + goto end; } else if (show) { const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; - return execv_git_cmd(show_args); + retval = execv_git_cmd(show_args); + goto end; } /* add/append/edit/remove/prune command */ if (add && note) { - if (force) - fprintf(stderr, "Overwriting existing notes for object %s\n", - sha1_to_hex(object)); - else { - error("Cannot add notes. Found existing notes for object %s. " - "Use '-f' to overwrite existing notes", + if (!force) { + error("Cannot add notes. Found existing notes for object" + " %s. Use '-f' to overwrite existing notes", sha1_to_hex(object)); - return 1; + retval = 1; + goto end; } + fprintf(stderr, "Overwriting existing notes for object %s\n", + sha1_to_hex(object)); } if (remove) { @@ -408,18 +415,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix) if (prune) { hashclr(new_note); prune_notes(t); - } else { + goto commit; + } else create_note(object, &msg, append, note, new_note); - if (is_null_sha1(new_note)) - remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); - } - snprintf(logmsg, sizeof(logmsg), "Note %s by 'git notes %s'", + + if (is_null_sha1(new_note)) + remove_note(t, object); + else + add_note(t, object, new_note, combine_notes_overwrite); + +commit: + snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); commit_notes(t, logmsg); +end: free_notes(t); strbuf_release(&(msg.buf)); - return 0; + return retval; } From e73bbd96c6e9ce11a101dac03402d0f718a1bd23 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:38 +0100 Subject: [PATCH 0079/3211] builtin-notes: Add "copy" subcommand for copying notes between objects This is useful for keeping notes to objects that are being rewritten by e.g. 'git commit --amend', 'git rebase', or 'git cherry-pick'. "git notes copy " is in practice equivalent to "git notes add -C $(git notes list ) ", although it is somewhat more convenient for regular users. "git notes copy" takes the same -f option as "git add", to overwrite existing notes at the target (instead of aborting with an error message). If the -object has no notes, "git notes copy" will abort with an error message. The patch includes tests verifying correct behaviour of the new subcommand. Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/git-notes.txt | 8 +++++ builtin-notes.c | 39 ++++++++++++++++++----- t/t3301-notes.sh | 63 +++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 15de4b344a..14f73b988e 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git notes' [list []] 'git notes' add [-f] [-F | -m | (-c | -C) ] [] +'git notes' copy [-f] 'git notes' append [-F | -m | (-c | -C) ] [] 'git notes' edit [] 'git notes' show [] @@ -48,6 +49,13 @@ add:: object already has notes, abort. (use `-f` to overwrite an existing note). +copy:: + Copy the notes for the first object onto the second object. + Abort if the second object already has notes, or if the first + objects has none. (use -f to overwrite existing notes to the + second object). This subcommand is equivalent to: + `git notes add [-f] -C $(git notes list ) ` + append:: Append to the notes of an existing object (defaults to HEAD). Creates a new notes object if needed. diff --git a/builtin-notes.c b/builtin-notes.c index bbf98a9f6f..123ecad830 100644 --- a/builtin-notes.c +++ b/builtin-notes.c @@ -20,6 +20,7 @@ static const char * const git_notes_usage[] = { "git notes [list []]", "git notes add [-f] [-m | -F | (-c | -C) ] []", + "git notes copy [-f] ", "git notes append [-m | -F | (-c | -C) ] []", "git notes edit []", "git notes show []", @@ -280,13 +281,13 @@ int commit_notes(struct notes_tree *t, const char *msg) int cmd_notes(int argc, const char **argv, const char *prefix) { struct notes_tree *t; - unsigned char object[20], new_note[20]; + unsigned char object[20], from_obj[20], new_note[20]; const unsigned char *note; const char *object_ref; char logmsg[100]; - int list = 0, add = 0, append = 0, edit = 0, show = 0, remove = 0, - prune = 0, force = 0; + int list = 0, add = 0, copy = 0, append = 0, edit = 0, show = 0, + remove = 0, prune = 0, force = 0; int given_object = 0, i = 1, retval = 0; struct msg_arg msg = { 0, 0, STRBUF_INIT }; struct option options[] = { @@ -311,6 +312,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix) list = 1; else if (argc && !strcmp(argv[0], "add")) add = 1; + else if (argc && !strcmp(argv[0], "copy")) + copy = 1; else if (argc && !strcmp(argv[0], "append")) append = 1; else if (argc && !strcmp(argv[0], "edit")) @@ -326,7 +329,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) i = 0; } - if (list + add + append + edit + show + remove + prune != 1) + if (list + add + copy + append + edit + show + remove + prune != 1) usage_with_options(git_notes_usage, options); if (msg.given && !(add || append || edit)) { @@ -341,11 +344,22 @@ int cmd_notes(int argc, const char **argv, const char *prefix) "Please use 'git notes add -f -m/-F/-c/-C' instead.\n"); } - if (force && !add) { + if (force && !(add || copy)) { error("cannot use -f option with %s subcommand.", argv[0]); usage_with_options(git_notes_usage, options); } + if (copy) { + const char *from_ref; + if (argc < 3) { + error("too few parameters"); + usage_with_options(git_notes_usage, options); + } + from_ref = argv[i++]; + if (get_sha1(from_ref, from_obj)) + die("Failed to resolve '%s' as a valid ref.", from_ref); + } + given_object = argc > i; object_ref = given_object ? argv[i++] : "HEAD"; @@ -394,11 +408,11 @@ int cmd_notes(int argc, const char **argv, const char *prefix) /* add/append/edit/remove/prune command */ - if (add && note) { + if ((add || copy) && note) { if (!force) { - error("Cannot add notes. Found existing notes for object" + error("Cannot %s notes. Found existing notes for object" " %s. Use '-f' to overwrite existing notes", - sha1_to_hex(object)); + argv[0], sha1_to_hex(object)); retval = 1; goto end; } @@ -416,6 +430,15 @@ int cmd_notes(int argc, const char **argv, const char *prefix) hashclr(new_note); prune_notes(t); goto commit; + } else if (copy) { + const unsigned char *from_note = get_note(t, from_obj); + if (!from_note) { + error("Missing notes on source object %s. Cannot copy.", + sha1_to_hex(from_obj)); + retval = 1; + goto end; + } + hashcpy(new_note, from_note); } else create_note(object, &msg, append, note, new_note); diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 6447e5f54e..90178f96d2 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -581,4 +581,67 @@ test_expect_success 'append to note from other note with "git notes append -c"' test_cmp expect actual ' +cat > expect << EOF +commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be +Author: A U Thor +Date: Thu Apr 7 15:23:13 2005 -0700 + + 11th + +Notes: + other note +$whitespace + yet another note +EOF + +test_expect_success 'copy note with "git notes copy"' ' + : > a11 && + git add a11 && + test_tick && + git commit -m 11th && + git notes copy HEAD^ HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +test_expect_success 'prevent overwrite with "git notes copy"' ' + test_must_fail git notes copy HEAD~2 HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD^)" +' + +cat > expect << EOF +commit 6352c5e33dbcab725fe0579be16aa2ba8eb369be +Author: A U Thor +Date: Thu Apr 7 15:23:13 2005 -0700 + + 11th + +Notes: + yet another note +$whitespace + yet another note +EOF + +test_expect_success 'allow overwrite with "git notes copy -f"' ' + git notes copy -f HEAD~2 HEAD && + git log -1 > actual && + test_cmp expect actual && + test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" +' + +test_expect_success 'cannot copy note from object without notes' ' + : > a12 && + git add a12 && + test_tick && + git commit -m 12th && + : > a13 && + git add a13 && + test_tick && + git commit -m 13th && + test_must_fail git notes copy HEAD^ HEAD +' + test_done From ef0065034a68edfc0ffed9965bb895073ad710db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sun, 14 Feb 2010 10:56:46 +0100 Subject: [PATCH 0080/3211] fix minor memory leak in get_tree_entry() Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- tree-walk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tree-walk.c b/tree-walk.c index 02e2aed773..a0fe88c55d 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -250,6 +250,7 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch if (name[0] == '\0') { hashcpy(sha1, root); + free(tree); return 0; } From ed0cb46ebb020234da94a843ca341dde8e9e3911 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, 14 Feb 2010 22:44:41 +0700 Subject: [PATCH 0081/3211] make_absolute_path(): Do not append redundant slash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When concatenating two paths, if the first one already have '/', do not put another '/' in between the two paths. Usually this is not the case as getcwd() won't return '/foo/bar/', except when you are standing at root, then it will return '/'. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- abspath.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/abspath.c b/abspath.c index b88122cbe7..c91a29cb29 100644 --- a/abspath.c +++ b/abspath.c @@ -54,8 +54,9 @@ const char *make_absolute_path(const char *path) if (len + strlen(last_elem) + 2 > PATH_MAX) die ("Too long path name: '%s/%s'", buf, last_elem); - buf[len] = '/'; - strcpy(buf + len + 1, last_elem); + if (len && buf[len-1] != '/') + buf[len++] = '/'; + strcpy(buf + len, last_elem); free(last_elem); last_elem = NULL; } From d06f15d9c0c2b072e5eebf87fa93ee9a899ed642 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, 14 Feb 2010 22:44:42 +0700 Subject: [PATCH 0082/3211] init-db, rev-parse --git-dir: do not append redundant slash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If git_dir already has the trailing slash, don't put another one before .git. This only happens when git_dir is '/' or 'C:/' Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-init-db.c | 9 ++++++--- builtin-rev-parse.c | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/builtin-init-db.c b/builtin-init-db.c index dd84caecbc..aae7a4d7ee 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -331,11 +331,14 @@ int init_db(const char *template_dir, unsigned int flags) git_config_set("receive.denyNonFastforwards", "true"); } - if (!(flags & INIT_DB_QUIET)) - printf("%s%s Git repository in %s/\n", + if (!(flags & INIT_DB_QUIET)) { + const char *git_dir = get_git_dir(); + int len = strlen(git_dir); + printf("%s%s Git repository in %s%s\n", reinit ? "Reinitialized existing" : "Initialized empty", shared_repository ? " shared" : "", - get_git_dir()); + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + } return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37d0233521..d0ccb4c968 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -608,6 +608,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--git-dir")) { const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); static char cwd[PATH_MAX]; + int len; if (gitdir) { puts(gitdir); continue; @@ -618,7 +619,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!getcwd(cwd, PATH_MAX)) die_errno("unable to get current working directory"); - printf("%s/.git\n", cwd); + len = strlen(cwd); + printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { From 9fabb6d7513ab0a264de146d72a8447bc85b5e85 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 05:55:53 -0600 Subject: [PATCH 0083/3211] Fix 'git var' usage synopsis The parameter to 'git var' is not optional. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git-var.txt | 2 +- builtin-var.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index bb981822a4..458f3e2755 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -8,7 +8,7 @@ git-var - Show a git logical variable SYNOPSIS -------- -'git var' [ -l | ] +'git var' ( -l | ) DESCRIPTION ----------- diff --git a/builtin-var.c b/builtin-var.c index 2280518190..e6ee7bc0b6 100644 --- a/builtin-var.c +++ b/builtin-var.c @@ -6,7 +6,7 @@ #include "cache.h" #include "exec_cmd.h" -static const char var_usage[] = "git var [-l | ]"; +static const char var_usage[] = "git var (-l | )"; static const char *editor(int flag) { From 64778d24a93ad455e5883120aef350ede20061c4 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 05:59:59 -0600 Subject: [PATCH 0084/3211] Make 'git var GIT_PAGER' always print the configured pager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scripted commands that want to use git’s configured pager know better than ‘git var’ does whether stdout is going to be a tty at the appropriate time. Checking isatty(1) as git_pager() does now won’t cut it, since the output of git var itself is almost never a terminal. The symptom is that when used by humans, ‘git var GIT_PAGER’ behaves as it should, but when used by scripts, it always returns ‘cat’! So avoid tricks with isatty() and just always print the configured pager. This does not fix the callers to check isatty(1) themselves yet. Nevertheless, this patch alone is enough to fix 'am --interactive'. Thanks to Sebastian Celis for the report and Jeff King for the analysis. Reported-by: Sebastian Celis Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin-var.c | 2 +- cache.h | 2 +- pager.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin-var.c b/builtin-var.c index e6ee7bc0b6..70fdb4dec7 100644 --- a/builtin-var.c +++ b/builtin-var.c @@ -20,7 +20,7 @@ static const char *editor(int flag) static const char *pager(int flag) { - const char *pgm = git_pager(); + const char *pgm = git_pager(1); if (!pgm) pgm = "cat"; diff --git a/cache.h b/cache.h index d478eff1f3..d454b7e686 100644 --- a/cache.h +++ b/cache.h @@ -775,7 +775,7 @@ 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 *git_editor(void); -extern const char *git_pager(void); +extern const char *git_pager(int stdout_is_tty); struct checkout { const char *base_dir; diff --git a/pager.c b/pager.c index 2c7e8ecb3c..dac358f047 100644 --- a/pager.c +++ b/pager.c @@ -48,11 +48,11 @@ static void wait_for_pager_signal(int signo) raise(signo); } -const char *git_pager(void) +const char *git_pager(int stdout_is_tty) { const char *pager; - if (!isatty(1)) + if (!stdout_is_tty) return NULL; pager = getenv("GIT_PAGER"); @@ -73,7 +73,7 @@ const char *git_pager(void) void setup_pager(void) { - const char *pager = git_pager(); + const char *pager = git_pager(isatty(1)); if (!pager) return; From 06300d9753349a83212360445d241d70a46375fa Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 06:02:35 -0600 Subject: [PATCH 0085/3211] git.1: Clarify the behavior of the --paginate option The --paginate option is meant to negate the effect of an explicit or implicit pager. = false setting. Thus it turns the pager on if output is going to a terminal rather than unconditionally. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/git.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index 01c463101b..f26641a5f4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -229,7 +229,10 @@ help ...`. -p:: --paginate:: - Pipe all output into 'less' (or if set, $PAGER). + Pipe all output into 'less' (or if set, $PAGER) if standard + output is a terminal. This overrides the `pager.` + configuration options (see the "Configuration Mechanism" section + below). --no-pager:: Do not pipe git output into a pager. @@ -401,7 +404,8 @@ people. Here is an example: ------------ Various commands read from the configuration file and adjust -their operation accordingly. +their operation accordingly. See linkgit:git-config[1] for a +list. Identifier Terminology From 190c1cda7eb6dc03be80f45d3d174c313d23da2c Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 06:06:10 -0600 Subject: [PATCH 0086/3211] git svn: Fix launching of pager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In commit dec543e (am -i, git-svn: use "git var GIT_PAGER"), I tried to teach git svn to defer to git var on what pager to use. In the process, I introduced two bugs: - The value set for $pager in config_pager has local scope, so run_pager never sees it; - git var cannot tell whether git svn’s output is going to a terminal, so the value chosen for $pager does not reflect that information. Fix them. Reported-by: Sebastian Celis Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-svn.perl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 265852f459..473a0b9d55 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -5459,7 +5459,12 @@ sub git_svn_log_cmd { # adapted from pager.c sub config_pager { - chomp(my $pager = command_oneline(qw(var GIT_PAGER))); + if (! -t *STDOUT) { + $ENV{GIT_PAGER_IN_USE} = 'false'; + $pager = undef; + return; + } + chomp($pager = command_oneline(qw(var GIT_PAGER))); if ($pager eq 'cat') { $pager = undef; } @@ -5467,7 +5472,7 @@ sub config_pager { } sub run_pager { - return unless -t *STDOUT && defined $pager; + return unless defined $pager; pipe my ($rfd, $wfd) or return; defined(my $pid = fork) or ::fatal "Can't fork: $!"; if (!$pid) { From e6e592db4c0099a6412aed6e868769535900f112 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 14 Feb 2010 22:46:28 +0100 Subject: [PATCH 0087/3211] gitweb: Die if there are parsing errors in config file Otherwise the errors can propagate, and show in damnest places, and you would spend your time chasing ghosts instead of debugging real problem (yes, it is from personal experience). This follows (parts of) advice in `perldoc -f do` documentation. This required restructoring code a bit, so we die only if we are reading (executing) config file. As a side effect $GITWEB_CONFIG_SYSTEM is always available, even when we use $GITWEB_CONFIG. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1f6978ac1f..20106a4f42 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -550,11 +550,14 @@ sub filter_snapshot_fmts { } our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; +our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; +# die if there are errors parsing config file if (-e $GITWEB_CONFIG) { do $GITWEB_CONFIG; -} else { - our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; - do $GITWEB_CONFIG_SYSTEM if -e $GITWEB_CONFIG_SYSTEM; + die $@ if $@; +} elsif (-e $GITWEB_CONFIG_SYSTEM) { + do $GITWEB_CONFIG_SYSTEM; + die $@ if $@; } # Get loadavg of system, to compare against $maxload. From f6dff119d51e0067d213068093039bb2f939d139 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 23:04:13 -0600 Subject: [PATCH 0088/3211] am: Fix launching of pager The pagination functionality in git am has some problems: - It does not check if stdout is a tty, so it always paginates. - If $GIT_PAGER uses any environment variables, they are being ignored, since it does not run $GIT_PAGER through eval. - If $GIT_PAGER is set to the empty string, instead of passing output through to stdout, it tries to run $dotest/patch. Fix them. While at it, move the definition of git_pager() to git-sh-setup so authors of other commands are not tempted to reimplement it with the same mistakes. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-am.sh | 5 +---- git-sh-setup.sh | 13 +++++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/git-am.sh b/git-am.sh index 3c08d53161..b11af03e0b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -663,10 +663,7 @@ do [eE]*) git_editor "$dotest/final-commit" action=again ;; [vV]*) action=again - : ${GIT_PAGER=$(git var GIT_PAGER)} - : ${LESS=-FRSX} - export LESS - $GIT_PAGER "$dotest/patch" ;; + git_pager "$dotest/patch" ;; *) action=again ;; esac done diff --git a/git-sh-setup.sh b/git-sh-setup.sh index d56426dd39..44fb4670ae 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -107,6 +107,19 @@ git_editor() { eval "$GIT_EDITOR" '"$@"' } +git_pager() { + if test -t 1 + then + GIT_PAGER=$(git var GIT_PAGER) + else + GIT_PAGER=cat + fi + : ${LESS=-FRSX} + export LESS + + eval "$GIT_PAGER" '"$@"' +} + sane_grep () { GREP_OPTIONS= LC_ALL=C grep "$@" } From 7283bbc70a55d7364fbeaefc1009c03fcfc8d929 Mon Sep 17 00:00:00 2001 From: Pete Harlan Date: Mon, 15 Feb 2010 15:33:18 -0800 Subject: [PATCH 0089/3211] Remove hyphen from "git-command" in two error messages Signed-off-by: Pete Harlan Signed-off-by: Junio C Hamano --- git.c | 2 +- help.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git.c b/git.c index 11544cdb40..a83cab7002 100644 --- a/git.c +++ b/git.c @@ -516,7 +516,7 @@ int main(int argc, const char **argv) break; if (was_alias) { fprintf(stderr, "Expansion of alias '%s' failed; " - "'%s' is not a git-command\n", + "'%s' is not a git command\n", cmd, argv[0]); exit(1); } diff --git a/help.c b/help.c index 9da97d7462..7f4928e459 100644 --- a/help.c +++ b/help.c @@ -350,7 +350,7 @@ const char *help_unknown_cmd(const char *cmd) return assumed; } - fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); + fprintf(stderr, "git: '%s' is not a git command. See 'git --help'.\n", cmd); if (SIMILAR_ENOUGH(best_similarity)) { fprintf(stderr, "\nDid you mean %s?\n", From 8324b977aef3d2301f170e23f498b50e11302575 Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Mon, 15 Feb 2010 23:10:45 -0500 Subject: [PATCH 0090/3211] diff: make sure --output=/bad/path is caught The return value from fopen wasn't being checked. Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- diff.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diff.c b/diff.c index 17a2b4df29..8d8405aba2 100644 --- a/diff.c +++ b/diff.c @@ -2799,6 +2799,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); + if (!options->file) + die_errno("Could not open '%s'", arg + strlen("--output=")); options->close_file = 1; } else return 0; From 460ccd0e19774fd5e4f69de5a454068c686ac5a6 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 15 Feb 2010 17:05:46 +0100 Subject: [PATCH 0091/3211] stash pop: remove 'apply' options during 'drop' invocation The 'git stash pop' option parsing used to remove the first argument in --index mode. At the time this was implemented, this first argument was always --index. However, since the invention of the -q option in fcdd0e9 (stash: teach quiet option, 2009-06-17) you can cause an internal invocation of git stash drop --index by running git stash pop -q --index which then of course fails because drop doesn't know --index. To handle this, instead let 'git stash apply' decide what the future argument to 'drop' should be. Warning: this means that 'git stash apply' must parse all options that 'drop' can take, and deal with them in the same way. This is currently true for its only option -q. Signed-off-by: Thomas Rast Acked-by: Stephen Boyd Signed-off-by: Junio C Hamano --- git-stash.sh | 7 +++++-- t/t3903-stash.sh | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/git-stash.sh b/git-stash.sh index 4febbbfa5d..79b2771099 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -222,6 +222,7 @@ show_stash () { } apply_stash () { + applied_stash= unstash_index= while test $# != 0 @@ -243,6 +244,9 @@ apply_stash () { if test $# = 0 then have_stash || die 'Nothing to apply' + applied_stash="$ref_stash@{0}" + else + applied_stash="$*" fi # stash records the work tree, and is a merge between the @@ -421,8 +425,7 @@ pop) shift if apply_stash "$@" then - test -z "$unstash_index" || shift - drop_stash "$@" + drop_stash "$applied_stash" fi ;; branch) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 5514f74b30..476e5ec038 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -194,6 +194,15 @@ test_expect_success 'pop -q is quiet' ' test ! -s output.out ' +test_expect_success 'pop -q --index works and is quiet' ' + echo foo > file && + git add file && + git stash save --quiet && + git stash pop -q --index > output.out 2>&1 && + test foo = "$(git show :file)" && + test ! -s output.out +' + test_expect_success 'drop -q is quiet' ' git stash && git stash drop -q > output.out 2>&1 && From 6977c250ac6cacb5ee441bff832fdeab4d0cd8f9 Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Tue, 16 Feb 2010 01:55:21 -0500 Subject: [PATCH 0092/3211] git diff --quiet -w: check and report the status The option -w tells the diff machinery to inspect the contents to set the exit status, instead of checking the blob object level difference alone. However, --quiet tells the diff machinery not to look at the contents, which means DIFF_FROM_CONTENTS has no chance to inspect the change. Work it around by calling diff_flush_patch() with output sent to /dev/null. Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- diff.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/diff.c b/diff.c index 381cc8d4fd..7216b1e8db 100644 --- a/diff.c +++ b/diff.c @@ -3520,6 +3520,29 @@ void diff_flush(struct diff_options *options) separator++; } + if (output_format & DIFF_FORMAT_NO_OUTPUT && + DIFF_OPT_TST(options, EXIT_WITH_STATUS) && + DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + /* + * run diff_flush_patch for the exit status. setting + * options->file to /dev/null should be safe, becaue we + * aren't supposed to produce any output anyway. + */ + if (options->close_file) + fclose(options->file); + options->file = fopen("/dev/null", "w"); + if (!options->file) + die_errno("Could not open /dev/null"); + options->close_file = 1; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, options); + if (options->found_changes) + break; + } + } + if (output_format & DIFF_FORMAT_PATCH) { if (separator) { putc(options->line_termination, options->file); From 4bb43de25977063187dedf054122985bc5a2660e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 16 Feb 2010 12:22:08 +0700 Subject: [PATCH 0093/3211] Move offset_1st_component() to path.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementation is also lightly modified to use is_dir_sep() instead of hardcoding '/'. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- cache.h | 1 + path.c | 7 +++++++ sha1_file.c | 7 ------- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cache.h b/cache.h index 04ae824d64..caf4192b88 100644 --- a/cache.h +++ b/cache.h @@ -664,6 +664,7 @@ int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); +int offset_1st_component(const char *path); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); diff --git a/path.c b/path.c index 79aa104712..06fd9e0577 100644 --- a/path.c +++ b/path.c @@ -649,3 +649,10 @@ int daemon_avoid_alias(const char *p) } } } + +int offset_1st_component(const char *path) +{ + if (has_dos_drive_prefix(path)) + return 2 + is_dir_sep(path[2]); + return is_dir_sep(path[0]); +} diff --git a/sha1_file.c b/sha1_file.c index 23d347c45f..923d9d1cd1 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -83,13 +83,6 @@ int get_sha1_hex(const char *hex, unsigned char *sha1) return 0; } -static inline int offset_1st_component(const char *path) -{ - if (has_dos_drive_prefix(path)) - return 2 + (path[2] == '/'); - return *path == '/'; -} - int safe_create_leading_directories(char *path) { char *pos = path + offset_1st_component(path); From 72ec8ba6dddbe2c53500d35d7ed343c153874fc6 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, 14 Feb 2010 22:44:44 +0700 Subject: [PATCH 0094/3211] Support working directory located at root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git should work regardless where the working directory is located, even at root. This patch fixes two places where it assumes working directory always have parent directory. In setup_git_directory_gently(), when Git goes up to root and finds .git there, it happily sets worktree to "" instead of "/". In prefix_path(), loosen the outside repo check a little bit. Usually when a path XXX is inside worktree /foo, it must be either "/foo", or "/foo/...". When worktree is simply "/", we can safely ignore the check: we have a slash at the beginning already. Not related to worktree, but also set gitdir correctly if a bare repo is placed (insanely?) at root. Thanks João Carlos Mendes Luís for pointing out this problem. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- setup.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/setup.c b/setup.c index 2cf0f19937..3b9efdbaea 100644 --- a/setup.c +++ b/setup.c @@ -18,14 +18,15 @@ const char *prefix_path(const char *prefix, int len, const char *path) if (normalize_path_copy(sanitized, sanitized)) goto error_out; if (is_absolute_path(orig)) { - size_t len, total; + size_t root_len, len, total; const char *work_tree = get_git_work_tree(); if (!work_tree) goto error_out; len = strlen(work_tree); + root_len = offset_1st_component(work_tree); total = strlen(sanitized) + 1; if (strncmp(sanitized, work_tree, len) || - (sanitized[len] != '\0' && sanitized[len] != '/')) { + (len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) { error_out: die("'%s' is outside repository", orig); } @@ -294,7 +295,7 @@ const char *setup_git_directory_gently(int *nongit_ok) static char cwd[PATH_MAX+1]; const char *gitdirenv; const char *gitfile_dir; - int len, offset, ceil_offset; + int len, offset, ceil_offset, root_len; /* * Let's assume that we are in a git repository. @@ -376,10 +377,11 @@ const char *setup_git_directory_gently(int *nongit_ok) if (!work_tree_env) inside_work_tree = 0; if (offset != len) { - cwd[offset] = '\0'; - setenv(GIT_DIR_ENVIRONMENT, cwd, 1); + root_len = offset_1st_component(cwd); + cwd[offset > root_len ? offset : root_len] = '\0'; + set_git_dir(cwd); } else - setenv(GIT_DIR_ENVIRONMENT, ".", 1); + set_git_dir("."); check_repository_format_gently(nongit_ok); return NULL; } @@ -400,7 +402,8 @@ const char *setup_git_directory_gently(int *nongit_ok) inside_git_dir = 0; if (!work_tree_env) inside_work_tree = 1; - git_work_tree_cfg = xstrndup(cwd, offset); + root_len = offset_1st_component(cwd); + git_work_tree_cfg = xstrndup(cwd, offset > root_len ? offset : root_len); if (check_repository_format_gently(nongit_ok)) return NULL; if (offset == len) From 3719b2fe554adc2f7a34a16b90f6894f299aab3c 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, 14 Feb 2010 22:44:45 +0700 Subject: [PATCH 0095/3211] Add test for using Git at root of file system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This kind of test requires a throw-away root filesystem so that it can play on. If you have such a system, go ahead, "chmod 777 /" and run this test manually. Because this is a dangerous test, you are required to set an env variable, and not to use root to run it. Script prepare-root.sh may help you set up a chroot environment with Git test suite inside. You will need Linux, static linked busybox, rsync and root permission to use it. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- t/t1509-root-worktree.sh | 249 ++++++++++++++++++++++++++++++++++++++ t/t1509/excludes | 14 +++ t/t1509/prepare-chroot.sh | 38 ++++++ 3 files changed, 301 insertions(+) create mode 100755 t/t1509-root-worktree.sh create mode 100644 t/t1509/excludes create mode 100755 t/t1509/prepare-chroot.sh diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-worktree.sh new file mode 100755 index 0000000000..5322a3bf97 --- /dev/null +++ b/t/t1509-root-worktree.sh @@ -0,0 +1,249 @@ +#!/bin/sh + +test_description='Test Git when git repository is located at root + +This test requires write access in root. Do not bother if you do not +have a throwaway chroot or VM. + +Script t1509/prepare-chroot.sh may help you setup chroot, then you +can chroot in and execute this test from there. +' + +. ./test-lib.sh + +test_cmp_val() { + echo "$1" > expected + echo "$2" > result + test_cmp expected result +} + +test_vars() { + test_expect_success "$1: gitdir" ' + test_cmp_val "'"$2"'" "$(git rev-parse --git-dir)" + ' + + test_expect_success "$1: worktree" ' + test_cmp_val "'"$3"'" "$(git rev-parse --show-toplevel)" + ' + + test_expect_success "$1: prefix" ' + test_cmp_val "'"$4"'" "$(git rev-parse --show-prefix)" + ' +} + +test_foobar_root() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add foo/foome && + git add foo/bar/barme && + git add me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + +} + +test_foobar_foo() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add foome && + git add bar/barme && + git add ../me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' +} + +test_foobar_foobar() { + test_expect_success 'add relative' ' + test -z "$(cd / && git ls-files)" && + git add ../foome && + git add barme && + git add ../../me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' + + test_expect_success 'add absolute' ' + test -z "$(cd / && git ls-files)" && + git add /foo/foome && + git add /foo/bar/barme && + git add /me && + ( cd / && git ls-files --stage ) > result && + test_cmp /ls.expected result && + rm "$(git rev-parse --git-dir)/index" + ' +} + +if ! test_have_prereq POSIXPERM || ! [ -w / ]; then + say "Dangerous test skipped. Read this test if you want to execute it" + test_done +fi + +if [ "$IKNOWWHATIAMDOING" != "YES" ]; then + say "You must set env var IKNOWWHATIAMDOING=YES in order to run this test" + test_done +fi + +if [ "$UID" = 0 ]; then + say "No you can't run this with root" + test_done +fi + +ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d + +test_expect_success 'setup' ' + rm -rf /foo + mkdir /foo && + mkdir /foo/bar && + echo 1 > /foo/foome && + echo 1 > /foo/bar/barme && + echo 1 > /me +' + +say "GIT_DIR absolute, GIT_WORK_TREE set" + +test_expect_success 'go to /' 'cd /' + +cat >ls.expected < expected && + git init > result && + test_cmp expected result +' + +test_vars 'auto gitdir, root' ".git" "/" "" +test_foobar_root + +test_expect_success 'go to /foo' 'cd /foo' +test_vars 'auto gitdir, foo' "/.git" "/" "foo/" +test_foobar_foo + +test_expect_success 'go to /foo/bar' 'cd /foo/bar' +test_vars 'auto gitdir, foo/bar' "/.git" "/" "foo/bar/" +test_foobar_foobar + +test_expect_success 'cleanup' 'rm -rf /.git' + +say "auto bare gitdir" + +# DESTROYYYYY!!!!! +test_expect_success 'setup' ' + rm -rf /refs /objects /info /hooks + rm /* + cd / && + echo "Initialized empty Git repository in /" > expected && + git init --bare > result && + test_cmp expected result +' + +test_vars 'auto gitdir, root' "." "" "" + +test_expect_success 'go to /foo' 'cd /foo' + +test_vars 'auto gitdir, root' "/" "" "" + +test_done diff --git a/t/t1509/excludes b/t/t1509/excludes new file mode 100644 index 0000000000..d4d21d31a9 --- /dev/null +++ b/t/t1509/excludes @@ -0,0 +1,14 @@ +*.o +*~ +*.bak +*.c +*.h +.git +contrib +Documentation +git-gui +gitk-git +gitweb +t/t4013 +t/t5100 +t/t5515 diff --git a/t/t1509/prepare-chroot.sh b/t/t1509/prepare-chroot.sh new file mode 100755 index 0000000000..c5334a8fa4 --- /dev/null +++ b/t/t1509/prepare-chroot.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +die() { + echo >&2 "$@" + exit 1 +} + +xmkdir() { + while [ -n "$1" ]; do + [ -d "$1" ] || mkdir "$1" || die "Unable to mkdir $1" + shift + done +} + +R="$1" + +[ -n "$R" ] || die "Usage: prepare-chroot.sh " +[ -x git ] || die "This script needs to be executed at git source code's top directory" +[ -x /bin/busybox ] || die "You need busybox" + +xmkdir "$R" "$R/bin" "$R/etc" "$R/lib" "$R/dev" +[ -c "$R/dev/null" ] || die "/dev/null is missing. Do mknod $R/dev/null c 1 3 && chmod 666 $R/dev/null" +echo "root:x:0:0:root:/:/bin/sh" > "$R/etc/passwd" +echo "$(id -nu):x:$(id -u):$(id -g)::$(pwd)/t:/bin/sh" >> "$R/etc/passwd" +echo "root::0:root" > "$R/etc/group" +echo "$(id -ng)::$(id -g):$(id -nu)" >> "$R/etc/group" + +[ -x "$R/bin/busybox" ] || cp /bin/busybox "$R/bin/busybox" +[ -x "$R/bin/sh" ] || ln -s /bin/busybox "$R/bin/sh" +[ -x "$R/bin/su" ] || ln -s /bin/busybox "$R/bin/su" + +mkdir -p "$R$(pwd)" +rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)" +ldd git | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do + mkdir -p "$R$(dirname $i)" + cp "$i" "$R/$i" +done +echo "Execute this in root: 'chroot $R /bin/su - $(id -nu)'" From 003c6abdb27c367747847a76b0a7890d67c794be Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 16 Feb 2010 02:03:16 -0500 Subject: [PATCH 0096/3211] dwim_ref: fix dangling symref warning If we encounter a symref that is dangling, in most cases we will warn about it. The one exception is a dangling HEAD, as that indicates a branch yet to be born. However, the check in dwim_ref was not quite right. If we were fed something like "HEAD^0" we would try to resolve "HEAD", see that it is dangling, and then check whether the _original_ string we got was "HEAD" (which it wasn't in this case). And that makes no sense; the dangling thing we found was not "HEAD^0" but rather "HEAD". Fixing this squelches a scary warning from "submodule summary HEAD" (and consequently "git status" with status.submodulesummary set) in an empty repo, as the submodule script calls "git rev-parse -q --verify HEAD^0". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sha1_name.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 44bb62d270..9677afdadb 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -278,8 +278,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) *ref = xstrdup(r); if (!warn_ambiguous_refs) break; - } else if ((flag & REF_ISSYMREF) && - (len != 4 || strcmp(str, "HEAD"))) + } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) warning("ignoring dangling symref %s.", fullref); } free(last_branch); From b0d66e156c5b312d468344569202d8ca4094f67f Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Tue, 16 Feb 2010 15:18:21 +0800 Subject: [PATCH 0097/3211] transport: add got_remote_refs flag transport_get_remote_refs() in tranport.c checks transport->remote_refs to determine whether transport->get_refs_list() should be invoked. The logic is "if it is NULL, we haven't run ls-remote to find out yet". However, transport->remote_refs could still be NULL while cloning from an empty repository. This causes get_refs_list() to be run unnecessarily. Introduce a flag, transport->got_remote_refs, to more explicitly record if we have run transport->get_refs_list() already. Signed-off-by: Tay Ray Chuan Acked-by: Jeff King Signed-off-by: Junio C Hamano --- transport.c | 5 ++++- transport.h | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/transport.c b/transport.c index 3846aacb47..08e4fa0354 100644 --- a/transport.c +++ b/transport.c @@ -918,6 +918,7 @@ struct transport *transport_get(struct remote *remote, const char *url) if (!remote) die("No remote provided to transport_get()"); + ret->got_remote_refs = 0; ret->remote = remote; helper = remote->foreign_vcs; @@ -1079,8 +1080,10 @@ int transport_push(struct transport *transport, const struct ref *transport_get_remote_refs(struct transport *transport) { - if (!transport->remote_refs) + if (!transport->got_remote_refs) { transport->remote_refs = transport->get_refs_list(transport, 0); + transport->got_remote_refs = 1; + } return transport->remote_refs; } diff --git a/transport.h b/transport.h index 7cea5cc723..6dd9ae182f 100644 --- a/transport.h +++ b/transport.h @@ -19,6 +19,12 @@ struct transport { void *data; const struct ref *remote_refs; + /** + * Indicates whether we already called get_refs_list(); set by + * transport.c::transport_get_remote_refs(). + */ + unsigned got_remote_refs : 1; + /** * Returns 0 if successful, positive if the option is not * recognized or is inapplicable, and negative if the option From 5f02d31597df35ca98e9080e12bfbb18dadadcbe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 15 Feb 2010 18:34:28 -0800 Subject: [PATCH 0098/3211] Fix use of mutex in threaded grep The program can decide at runtime not to use threading even if the support is compiled in. In such a case, mutexes are not necessary and left uninitialized. But the code incorrectly tried to take and release the read_sha1_mutex unconditionally. Signed-off-by: Junio C Hamano Acked-by: Fredrik Kuivinen --- builtin-grep.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/builtin-grep.c b/builtin-grep.c index 26d4deb1cc..362122c432 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -408,15 +408,25 @@ static int pathspec_matches(const char **paths, const char *name, int max_depth) return 0; } +static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) +{ + void *data; + + if (use_threads) { + read_sha1_lock(); + data = read_sha1_file(sha1, type, size); + read_sha1_unlock(); + } else { + data = read_sha1_file(sha1, type, size); + } + return data; +} + static void *load_sha1(const unsigned char *sha1, unsigned long *size, const char *name) { enum object_type type; - char *data; - - read_sha1_lock(); - data = read_sha1_file(sha1, &type, size); - read_sha1_unlock(); + void *data = lock_and_read_sha1_file(sha1, &type, size); if (!data) error("'%s': unable to read %s", name, sha1_to_hex(sha1)); @@ -605,10 +615,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, void *data; unsigned long size; - read_sha1_lock(); - data = read_sha1_file(entry.sha1, &type, &size); - read_sha1_unlock(); - + data = lock_and_read_sha1_file(entry.sha1, &type, &size); if (!data) die("unable to read tree (%s)", sha1_to_hex(entry.sha1)); From d3f69766c431bab6321fed1ce64d914dc44ff87f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Feb 2010 22:25:03 -0800 Subject: [PATCH 0099/3211] Prepare 1.7.0.1 release notes Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.0.1.txt | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.7.0.1.txt b/Documentation/RelNotes-1.7.0.1.txt index ab0f9764da..312a3f5534 100644 --- a/Documentation/RelNotes-1.7.0.1.txt +++ b/Documentation/RelNotes-1.7.0.1.txt @@ -4,8 +4,28 @@ Git v1.7.0.1 Release Notes Fixes since v1.7.0 ------------------ + * In a freshly created repository "rev-parse HEAD^0" complained that + it is dangling symref, even though "rev-parse HEAD" didn't. + + * Message from "git cherry-pick" was harder to read and use than necessary + when it stopped due to conflicting changes. + + * "git diff --output=/path/that/cannot/be/written" did not correctly + error out. + + * "git grep -e -pattern-that-begin-with-dash paths..." could not be + spelled as "git grep -- -pattern-that-begin-with-dash paths..." which + would be a GNU way to use "--" as "end of options". + + * "git grep" compiled with threading support tried to access an + uninitialized mutex on boxes with a single CPU. + + * "git stash pop -q --index" failed because the unnecessary --index + option was propagated to "git stash drop" that is internally run at the + end. + -- exec >/var/tmp/1 echo O=$(git describe) -O=v1.7.0 +O=v1.7.0-11-g354d9f8 git shortlog $O.. From 81f45e7dc4e930ffc17dce3e377e6adc3eb3d8de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 9 Feb 2010 17:30:49 -0500 Subject: [PATCH 0100/3211] git add -u: die on unmatched pathspec If a pathspec is supplied to 'git add -u' and no path matches the pattern, fail with an approriate error message and exit code. Tested-by: Chris Packham Signed-off-by: Junio C Hamano --- builtin-add.c | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index cb6e5906fb..016987c224 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -41,7 +41,19 @@ static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) } } -static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +static char *find_used_pathspec(const char **pathspec) +{ + char *seen; + int i; + + for (i = 0; pathspec[i]; i++) + ; /* just counting */ + seen = xcalloc(i, 1); + fill_pathspec_matches(pathspec, seen, i); + return seen; +} + +static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { char *seen; int i, specs; @@ -61,13 +73,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p } dir->nr = dst - dir->entries; fill_pathspec_matches(pathspec, seen, specs); - - for (i = 0; i < specs; i++) { - if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) - die("pathspec '%s' did not match any files", - pathspec[i]); - } - free(seen); + return seen; } static void treat_gitlinks(const char **pathspec) @@ -283,6 +289,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int flags; int add_new_files; int require_pathspec; + char *seen = NULL; git_config(add_config, NULL); @@ -342,7 +349,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) /* This picks up the paths that are not tracked */ baselen = fill_directory(&dir, pathspec); if (pathspec) - prune_directory(&dir, pathspec, baselen); + seen = prune_directory(&dir, pathspec, baselen); } if (refresh_only) { @@ -350,6 +357,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) goto finish; } + if (pathspec) { + int i; + if (!seen) + seen = find_used_pathspec(pathspec); + for (i = 0; pathspec[i]; i++) { + if (!seen[i] && pathspec[i][0] + && !file_exists(pathspec[i])) + die("pathspec '%s' did not match any files", + pathspec[i]); + } + free(seen); + } + exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) From 1e7ef746d3a635742690817fefe00b66a044dfe5 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 9 Feb 2010 17:30:48 -0500 Subject: [PATCH 0101/3211] test for add with non-existent pathspec Add a test for 'git add -u pathspec' and 'git add pathspec' where pathspec does not exist. The expected result is that git add exits with an error message and an appropriate exit code. Signed-off-by: Chris Packham Signed-off-by: Junio C Hamano --- t/t2200-add-update.sh | 5 +++++ t/t3700-add.sh | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 912075063b..2ad2819a34 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -176,4 +176,9 @@ test_expect_success 'add -u resolves unmerged paths' ' ' +test_expect_success '"add -u non-existent" should fail' ' + test_must_fail git add -u non-existent && + ! (git ls-files | grep "non-existent") +' + test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 85eb0fbf96..525c9a8fdf 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -255,4 +255,9 @@ test_expect_success 'git add to resolve conflicts on otherwise ignored path' ' git add track-this ' +test_expect_success '"add non-existent" should fail' ' + test_must_fail git add non-existent && + ! (git ls-files | grep "non-existent") +' + test_done From 3ac4440801905d11de3ba585c2fe306311db6c45 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Mon, 15 Feb 2010 19:25:40 -0500 Subject: [PATCH 0102/3211] grep documentation: clarify what files match Clarify that git-grep(1) searches only tracked files, and that each is a pathspec, as in any other ordinary git commands. Add an example to show a simple use case for searching all .c and .h files in the current directory and below. Signed-off-by: Mark Lodato Signed-off-by: Junio C Hamano --- Documentation/git-grep.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 8c700200f5..fa91e8bebd 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -22,12 +22,12 @@ SYNOPSIS [-A ] [-B ] [-C ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] [...] - [--] [...] + [--] [...] DESCRIPTION ----------- -Look for specified patterns in the working tree files, blobs -registered in the index file, or given tree objects. +Look for specified patterns in the tracked files in the work tree, blobs +registered in the index file, or blobs in given tree objects. OPTIONS @@ -49,7 +49,7 @@ OPTIONS Don't match the pattern in binary files. --max-depth :: - For each pathspec given on command line, descend at most + For each given on command line, descend at most levels of directories. A negative value means no limit. -w:: @@ -163,12 +163,19 @@ OPTIONS \--:: Signals the end of options; the rest of the parameters - are limiters. + are limiters. +...:: + If given, limit the search to paths matching at least one pattern. + Both leading paths match and glob(7) patterns are supported. Example ------- +git grep 'time_t' -- '*.[ch]':: + Looks for `time_t` in all tracked .c and .h files in the working + directory and its subdirectories. + git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \):: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. From 8420ccd8b884e1edceca9ea5824f3ff300c0c4ba Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Feb 2010 23:59:46 -0800 Subject: [PATCH 0103/3211] git_config_maybe_bool() Some configuration variables can take boolean values in addition to enumeration specific to them. Introduce git_config_maybe_bool() that returns 0 or 1 if the given value is boolean, or -1 if not, so that a parser for such a variable can check for boolean first and then parse other kinds of values as a fallback. Signed-off-by: Junio C Hamano --- cache.h | 1 + config.c | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index d478eff1f3..dd3be0a065 100644 --- a/cache.h +++ b/cache.h @@ -924,6 +924,7 @@ extern int git_config_int(const char *, const char *); extern unsigned long git_config_ulong(const char *, const char *); extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); +extern int git_config_maybe_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); extern int git_config_set(const char *, const char *); diff --git a/config.c b/config.c index 6963fbea43..64e41bea22 100644 --- a/config.c +++ b/config.c @@ -322,17 +322,30 @@ unsigned long git_config_ulong(const char *name, const char *value) return ret; } -int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +int git_config_maybe_bool(const char *name, const char *value) { - *is_bool = 1; if (!value) return 1; if (!*value) return 0; - if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) + if (!strcasecmp(value, "true") + || !strcasecmp(value, "yes") + || !strcasecmp(value, "on")) return 1; - if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) + if (!strcasecmp(value, "false") + || !strcasecmp(value, "no") + || !strcasecmp(value, "off")) return 0; + return -1; +} + +int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +{ + int v = git_config_maybe_bool(name, value); + if (0 <= v) { + *is_bool = 1; + return v; + } *is_bool = 0; return git_config_int(name, value); } From eb734454098fb68af1fb0e157dd5e67bb15a602d Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:39:52 +1300 Subject: [PATCH 0104/3211] Add `log.decorate' configuration variable. This alows the 'git-log --decorate' to be enabled by default so that normal log outout contains ant ref names of commits that are shown. Signed-off-by: Steven Drake Signed-off-by: Junio C Hamano --- Documentation/config.txt | 7 +++++++ builtin-log.c | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 4c36aa95b7..db280326de 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1232,6 +1232,13 @@ log.date:: following alternatives: {relative,local,default,iso,rfc,short}. See linkgit:git-log[1]. +log.decorate:: + Print out the ref names of any commits that are shown by the log + command. If 'short' is specified, the ref name prefixes 'refs/heads/', + 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is + specified, the full ref name (including prefix) will be printed. + This is the same as the log commands '--decorate' option. + log.showroot:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. diff --git a/builtin-log.c b/builtin-log.c index 8d16832f7e..3100dc00a8 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -24,6 +24,7 @@ static const char *default_date_mode = NULL; static int default_show_root = 1; +static int decoration_style = 0; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -35,7 +36,6 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { int i; - int decoration_style = 0; rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; @@ -252,6 +252,13 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_patch_subject_prefix, var, value); if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); + if (!strcmp(var, "log.decorate")) { + if (!strcmp(value, "full")) + decoration_style = DECORATE_FULL_REFS; + else if (!strcmp(value, "short")) + decoration_style = DECORATE_SHORT_REFS; + return 0; + } if (!strcmp(var, "log.showroot")) { default_show_root = git_config_bool(var, value); return 0; From 8a3d203bd02bec48a02557961899d81da172fa23 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 10:20:49 -0800 Subject: [PATCH 0105/3211] log.decorate: usability fixes The configuration is meant to suppliment --decorate command line option that can be used as a boolean to turn the feature on, so it is natural to expect [log] decorate decorate = yes to work. The original commit would segfault with the first one, and would not understand the second one. Once a user has this configuration in ~/.gitconfig, there needs to be a way to override it from the command line. Add --no-decorate option to log family and also allow --decorate=no to mean the same thing. Since we allow setting log.decorate to 'true', the command line also should accept --decorate=yes and behave accordingly. New tests in t4202 are designed to exercise the interaction between the configuration variable and the command line option that overrides it. Signed-off-by: Junio C Hamano --- Documentation/git-log.txt | 3 ++- builtin-log.c | 35 ++++++++++++++++++++-------- t/t4202-log.sh | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 0e39bb61ee..64bb879fed 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -37,7 +37,8 @@ include::diff-options.txt[] and , see "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. ---decorate[=short|full]:: +--no-decorate:: +--decorate[=short|full|no]:: Print out the ref names of any commits that are shown. If 'short' is specified, the ref name prefixes 'refs/heads/', 'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is specified, the diff --git a/builtin-log.c b/builtin-log.c index 3100dc00a8..0afba31c1f 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -24,7 +24,7 @@ static const char *default_date_mode = NULL; static int default_show_root = 1; -static int decoration_style = 0; +static int decoration_style; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -32,6 +32,23 @@ static const char * const builtin_log_usage = "git log [] [..] [[--] ...]\n" " or: git show [options] ..."; +static int parse_decoration_style(const char *var, const char *value) +{ + switch (git_config_maybe_bool(var, value)) { + case 1: + return DECORATE_SHORT_REFS; + case 0: + return 0; + default: + break; + } + if (!strcmp(value, "full")) + return DECORATE_FULL_REFS; + else if (!strcmp(value, "short")) + return DECORATE_SHORT_REFS; + return -1; +} + static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { @@ -74,12 +91,11 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, decoration_style = DECORATE_SHORT_REFS; } else if (!prefixcmp(arg, "--decorate=")) { const char *v = skip_prefix(arg, "--decorate="); - if (!strcmp(v, "full")) - decoration_style = DECORATE_FULL_REFS; - else if (!strcmp(v, "short")) - decoration_style = DECORATE_SHORT_REFS; - else + decoration_style = parse_decoration_style(arg, v); + if (decoration_style < 0) die("invalid --decorate option: %s", arg); + } else if (!strcmp(arg, "--no-decorate")) { + decoration_style = 0; } else if (!strcmp(arg, "--source")) { rev->show_source = 1; } else if (!strcmp(arg, "-h")) { @@ -253,10 +269,9 @@ static int git_log_config(const char *var, const char *value, void *cb) if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { - if (!strcmp(value, "full")) - decoration_style = DECORATE_FULL_REFS; - else if (!strcmp(value, "short")) - decoration_style = DECORATE_SHORT_REFS; + decoration_style = parse_decoration_style(var, value); + if (decoration_style < 0) + decoration_style = 0; /* maybe warn? */ return 0; } if (!strcmp(var, "log.showroot")) { diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1dc224f6fb..2230e606ed 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -387,5 +387,54 @@ test_expect_success 'log --graph with merge' ' test_cmp expect actual ' +test_expect_success 'log.decorate configuration' ' + git config --unset-all log.decorate || : + + git log --oneline >expect.none && + git log --oneline --decorate >expect.short && + git log --oneline --decorate=full >expect.full && + + echo "[log] decorate" >>.git/config && + git log --oneline >actual && + test_cmp expect.short actual && + + git config --unset-all log.decorate && + git config log.decorate true && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + git log --oneline --decorate=no >actual && + test_cmp expect.none actual && + + git config --unset-all log.decorate && + git config log.decorate no && + git log --oneline >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate short && + git log --oneline >actual && + test_cmp expect.short actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate=full >actual && + test_cmp expect.full actual && + + git config --unset-all log.decorate && + git config log.decorate full && + git log --oneline >actual && + test_cmp expect.full actual && + git log --oneline --no-decorate >actual && + test_cmp expect.none actual && + git log --oneline --decorate >actual && + test_cmp expect.short actual + +' + test_done From ae9c606ed228ea6a17ddde3a1e26451d02f51df7 Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Mon, 15 Feb 2010 22:34:07 -0800 Subject: [PATCH 0106/3211] imap-send: support CRAM-MD5 authentication CRAM-MD5 authentication ought to be independent from SSL, but NO_OPENSSL build will not support this because the base64 and md5 code are used from the OpenSSL library in this implementation. Signed-off-by: Hitoshi Mitake Signed-off-by: Junio C Hamano --- Documentation/git-imap-send.txt | 4 + imap-send.c | 146 ++++++++++++++++++++++++++++---- 2 files changed, 135 insertions(+), 15 deletions(-) diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 57db955bd4..6cafbe2ec1 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -71,6 +71,10 @@ imap.preformattedHTML:: option causes Thunderbird to send the patch as a plain/text, format=fixed email. Default is `false`. +imap.authMethod:: + Specify authenticate method for authentication with IMAP server. + Current supported method is 'CRAM-MD5' only. + Examples ~~~~~~~~ diff --git a/imap-send.c b/imap-send.c index ba72fa4b6e..3ee25d9643 100644 --- a/imap-send.c +++ b/imap-send.c @@ -27,6 +27,9 @@ #include "run-command.h" #ifdef NO_OPENSSL typedef void *SSL; +#else +#include +#include #endif struct store_conf { @@ -140,6 +143,20 @@ struct imap_server_conf { int use_ssl; int ssl_verify; int use_html; + char *auth_method; +}; + +static struct imap_server_conf server = { + NULL, /* name */ + NULL, /* tunnel */ + NULL, /* host */ + 0, /* port */ + NULL, /* user */ + NULL, /* pass */ + 0, /* use_ssl */ + 1, /* ssl_verify */ + 0, /* use_html */ + NULL, /* auth_method */ }; struct imap_store_conf { @@ -214,6 +231,7 @@ enum CAPABILITY { LITERALPLUS, NAMESPACE, STARTTLS, + AUTH_CRAM_MD5, }; static const char *cap_list[] = { @@ -222,6 +240,7 @@ static const char *cap_list[] = { "LITERAL+", "NAMESPACE", "STARTTLS", + "AUTH=CRAM-MD5", }; #define RESP_OK 0 @@ -949,6 +968,87 @@ static void imap_close_store(struct store *ctx) free(ctx); } +#ifndef NO_OPENSSL + +/* + * hexchar() and cram() functions are based on the code from the isync + * project (http://isync.sf.net/). + */ +static char hexchar(unsigned int b) +{ + return b < 10 ? '0' + b : 'a' + (b - 10); +} + +#define ENCODED_SIZE(n) (4*((n+2)/3)) +static char *cram(const char *challenge_64, const char *user, const char *pass) +{ + int i, resp_len, encoded_len, decoded_len; + HMAC_CTX hmac; + unsigned char hash[16]; + char hex[33]; + char *response, *response_64, *challenge; + + /* + * length of challenge_64 (i.e. base-64 encoded string) is a good + * enough upper bound for challenge (decoded result). + */ + encoded_len = strlen(challenge_64); + challenge = xmalloc(encoded_len); + decoded_len = EVP_DecodeBlock((unsigned char *)challenge, + (unsigned char *)challenge_64, encoded_len); + if (decoded_len < 0) + die("invalid challenge %s", challenge_64); + HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5()); + HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len); + HMAC_Final(&hmac, hash, NULL); + HMAC_CTX_cleanup(&hmac); + + hex[32] = 0; + for (i = 0; i < 16; i++) { + hex[2 * i] = hexchar((hash[i] >> 4) & 0xf); + hex[2 * i + 1] = hexchar(hash[i] & 0xf); + } + + /* response: " " */ + resp_len = strlen(user) + 1 + strlen(hex) + 1; + response = xmalloc(resp_len); + sprintf(response, "%s %s", user, hex); + + response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1); + encoded_len = EVP_EncodeBlock((unsigned char *)response_64, + (unsigned char *)response, resp_len); + if (encoded_len < 0) + die("EVP_EncodeBlock error"); + response_64[encoded_len] = '\0'; + return (char *)response_64; +} + +#else + +static char *cram(const char *challenge_64, const char *user, const char *pass) +{ + die("If you want to use CRAM-MD5 authenticate method, " + "you have to build git-imap-send with OpenSSL library."); +} + +#endif + +static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt) +{ + int ret; + char *response; + + response = cram(prompt, server.user, server.pass); + + ret = socket_write(&ctx->imap->buf.sock, response, strlen(response)); + if (ret != strlen(response)) + return error("IMAP error: sending response failed\n"); + + free(response); + + return 0; +} + static struct store *imap_open_store(struct imap_server_conf *srvc) { struct imap_store *ctx; @@ -1130,9 +1230,34 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) if (!imap->buf.sock.ssl) imap_warn("*** IMAP Warning *** Password is being " "sent in the clear\n"); - if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { - fprintf(stderr, "IMAP error: LOGIN failed\n"); - goto bail; + + if (srvc->auth_method) { + struct imap_cmd_cb cb; + + if (!strcmp(srvc->auth_method, "CRAM-MD5")) { + if (!CAP(AUTH_CRAM_MD5)) { + fprintf(stderr, "You specified" + "CRAM-MD5 as authentication method, " + "but %s doesn't support it.\n", srvc->host); + goto bail; + } + /* CRAM-MD5 */ + + memset(&cb, 0, sizeof(cb)); + cb.cont = auth_cram_md5; + if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) { + fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n"); + goto bail; + } + } else { + fprintf(stderr, "Unknown authentication method:%s\n", srvc->host); + goto bail; + } + } else { + if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { + fprintf(stderr, "IMAP error: LOGIN failed\n"); + goto bail; + } } } /* !preauth */ @@ -1310,18 +1435,6 @@ static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs) return 1; } -static struct imap_server_conf server = { - NULL, /* name */ - NULL, /* tunnel */ - NULL, /* host */ - 0, /* port */ - NULL, /* user */ - NULL, /* pass */ - 0, /* use_ssl */ - 1, /* ssl_verify */ - 0, /* use_html */ -}; - static char *imap_folder; static int git_imap_config(const char *key, const char *val, void *cb) @@ -1361,6 +1474,9 @@ static int git_imap_config(const char *key, const char *val, void *cb) server.port = git_config_int(key, val); else if (!strcmp("tunnel", key)) server.tunnel = xstrdup(val); + else if (!strcmp("authmethod", key)) + server.auth_method = xstrdup(val); + return 0; } From ab62677b1424d4e53cf222c973b841d3dada4cf3 Mon Sep 17 00:00:00 2001 From: Gabriel Filion Date: Tue, 16 Feb 2010 23:18:50 -0500 Subject: [PATCH 0107/3211] require_work_tree broken with NONGIT_OK With NONGIT_OK set, require_work_tree function outside a git repository gives a syntax error. This is caused by an incorrect use of "test" that didn't anticipate $(git rev-parse --is-inside-work-tree) may return an empty string. Properly quote the argument to "test", and send the standard error stream to /dev/null to avoid giving duplicate error messages. Signed-off-by: Gabriel Filion Signed-off-by: Junio C Hamano --- git-sh-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 7bef43f39d..d2789410de 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -59,7 +59,7 @@ cd_to_toplevel () { } require_work_tree () { - test $(git rev-parse --is-inside-work-tree) = true || + test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || die "fatal: $0 cannot be used without a working tree." } From 3fc366bdbbcafa36ac4e35e3124959beb94c7fce Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:51 -0500 Subject: [PATCH 0108/3211] fast-import: start using struct pack_idx_entry This is in preparation for using write_idx_file(). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- fast-import.c | 57 +++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/fast-import.c b/fast-import.c index b477dc6a8f..c29737ef94 100644 --- a/fast-import.c +++ b/fast-import.c @@ -164,12 +164,11 @@ Format of STDIN stream: struct object_entry { + struct pack_idx_entry idx; struct object_entry *next; - uint32_t offset; uint32_t type : TYPE_BITS, pack_id : PACK_ID_BITS, depth : DEPTH_BITS; - unsigned char sha1[20]; }; struct object_entry_pool @@ -521,7 +520,7 @@ static struct object_entry *new_object(unsigned char *sha1) alloc_objects(object_entry_alloc); e = blocks->next_free++; - hashcpy(e->sha1, sha1); + hashcpy(e->idx.sha1, sha1); return e; } @@ -530,7 +529,7 @@ static struct object_entry *find_object(unsigned char *sha1) unsigned int h = sha1[0] << 8 | sha1[1]; struct object_entry *e; for (e = object_table[h]; e; e = e->next) - if (!hashcmp(sha1, e->sha1)) + if (!hashcmp(sha1, e->idx.sha1)) return e; return NULL; } @@ -542,7 +541,7 @@ static struct object_entry *insert_object(unsigned char *sha1) struct object_entry *p = NULL; while (e) { - if (!hashcmp(sha1, e->sha1)) + if (!hashcmp(sha1, e->idx.sha1)) return e; p = e; e = e->next; @@ -550,7 +549,7 @@ static struct object_entry *insert_object(unsigned char *sha1) e = new_object(sha1); e->next = NULL; - e->offset = 0; + e->idx.offset = 0; if (p) p->next = e; else @@ -857,7 +856,7 @@ static int oecmp (const void *a_, const void *b_) { struct object_entry *a = *((struct object_entry**)a_); struct object_entry *b = *((struct object_entry**)b_); - return hashcmp(a->sha1, b->sha1); + return hashcmp(a->idx.sha1, b->idx.sha1); } static char *create_index(void) @@ -887,7 +886,7 @@ static char *create_index(void) for (i = 0; i < 256; i++) { struct object_entry **next = c; while (next < last) { - if ((*next)->sha1[0] != i) + if ((*next)->idx.sha1[0] != i) break; next++; } @@ -901,10 +900,10 @@ static char *create_index(void) sha1write(f, array, 256 * sizeof(int)); git_SHA1_Init(&ctx); for (c = idx; c != last; c++) { - uint32_t offset = htonl((*c)->offset); + uint32_t offset = htonl((*c)->idx.offset); sha1write(f, &offset, 4); - sha1write(f, (*c)->sha1, sizeof((*c)->sha1)); - git_SHA1_Update(&ctx, (*c)->sha1, 20); + sha1write(f, (*c)->idx.sha1, sizeof((*c)->idx.sha1)); + git_SHA1_Update(&ctx, (*c)->idx.sha1, 20); } sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); sha1close(f, NULL, CSUM_FSYNC); @@ -1063,13 +1062,13 @@ static int store_object( e = insert_object(sha1); if (mark) insert_mark(mark, e); - if (e->offset) { + if (e->idx.offset) { duplicate_count_by_type[type]++; return 1; } else if (find_sha1_pack(sha1, packed_git)) { e->type = type; e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ + e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[type]++; return 1; } @@ -1127,12 +1126,12 @@ static int store_object( e->type = type; e->pack_id = pack_id; - e->offset = pack_size; + e->idx.offset = pack_size; object_count++; object_count_by_type[type]++; if (delta) { - unsigned long ofs = e->offset - last->offset; + unsigned long ofs = e->idx.offset - last->offset; unsigned pos = sizeof(hdr) - 1; delta_count_by_type[type]++; @@ -1165,7 +1164,7 @@ static int store_object( } else { strbuf_swap(&last->data, dat); } - last->offset = e->offset; + last->offset = e->idx.offset; last->depth = e->depth; } return 0; @@ -1259,14 +1258,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) if (mark) insert_mark(mark, e); - if (e->offset) { + if (e->idx.offset) { duplicate_count_by_type[OBJ_BLOB]++; truncate_pack(offset); } else if (find_sha1_pack(sha1, packed_git)) { e->type = OBJ_BLOB; e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ + e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[OBJ_BLOB]++; truncate_pack(offset); @@ -1274,7 +1273,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) e->depth = 0; e->type = OBJ_BLOB; e->pack_id = pack_id; - e->offset = offset; + e->idx.offset = offset; object_count++; object_count_by_type[OBJ_BLOB]++; } @@ -1326,7 +1325,7 @@ static void *gfi_unpack_entry( */ p->pack_size = pack_size + 20; } - return unpack_entry(p, oe->offset, &type, sizep); + return unpack_entry(p, oe->idx.offset, &type, sizep); } static const char *get_mode(const char *str, uint16_t *modep) @@ -1457,7 +1456,7 @@ static void store_tree(struct tree_entry *root) if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { mktree(t, 0, &old_tree); lo.data = old_tree; - lo.offset = le->offset; + lo.offset = le->idx.offset; lo.depth = t->delta_depth; } @@ -1715,7 +1714,7 @@ static void dump_marks_helper(FILE *f, for (k = 0; k < 1024; k++) { if (m->data.marked[k]) fprintf(f, ":%" PRIuMAX " %s\n", base + k, - sha1_to_hex(m->data.marked[k]->sha1)); + sha1_to_hex(m->data.marked[k]->idx.sha1)); } } } @@ -1798,7 +1797,7 @@ static void read_marks(void) e = insert_object(sha1); e->type = type; e->pack_id = MAX_PACK_ID; - e->offset = 1; /* just not zero! */ + e->idx.offset = 1; /* just not zero! */ } insert_mark(mark, e); } @@ -2183,7 +2182,7 @@ static void file_change_m(struct branch *b) if (*p == ':') { char *x; oe = find_mark(strtoumax(p + 1, &x, 10)); - hashcpy(sha1, oe->sha1); + hashcpy(sha1, oe->idx.sha1); p = x; } else if (!prefixcmp(p, "inline")) { inline_data = 1; @@ -2316,7 +2315,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout) if (*p == ':') { char *x; oe = find_mark(strtoumax(p + 1, &x, 10)); - hashcpy(sha1, oe->sha1); + hashcpy(sha1, oe->idx.sha1); p = x; } else if (!prefixcmp(p, "inline")) { inline_data = 1; @@ -2339,7 +2338,7 @@ static void note_change_n(struct branch *b, unsigned char old_fanout) struct object_entry *commit_oe = find_mark(commit_mark); if (commit_oe->type != OBJ_COMMIT) die("Mark :%" PRIuMAX " not a commit", commit_mark); - hashcpy(commit_sha1, commit_oe->sha1); + hashcpy(commit_sha1, commit_oe->idx.sha1); } else if (!get_sha1(p, commit_sha1)) { unsigned long size; char *buf = read_object_with_reference(commit_sha1, @@ -2446,7 +2445,7 @@ static int parse_from(struct branch *b) struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) die("Mark :%" PRIuMAX " not a commit", idnum); - hashcpy(b->sha1, oe->sha1); + hashcpy(b->sha1, oe->idx.sha1); if (oe->pack_id != MAX_PACK_ID) { unsigned long size; char *buf = gfi_unpack_entry(oe, &size); @@ -2481,7 +2480,7 @@ static struct hash_list *parse_merge(unsigned int *count) struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) die("Mark :%" PRIuMAX " not a commit", idnum); - hashcpy(n->sha1, oe->sha1); + hashcpy(n->sha1, oe->idx.sha1); } else if (!get_sha1(from, n->sha1)) { unsigned long size; char *buf = read_object_with_reference(n->sha1, @@ -2639,7 +2638,7 @@ static void parse_new_tag(void) from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); type = oe->type; - hashcpy(sha1, oe->sha1); + hashcpy(sha1, oe->idx.sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; char *buf; From 212818160d588fae403dfc823250e95ffbac76e5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:52 -0500 Subject: [PATCH 0109/3211] fast-import: use sha1write() for pack data This is in preparation for using write_idx_file(). Also, by using sha1write() we get some buffering to reduces the number of write syscalls, and the written data is SHA1 summed which allows for the extra data integrity validation check performed in fixup_pack_header_footer() (details on this in commit abeb40e5aa). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- fast-import.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/fast-import.c b/fast-import.c index c29737ef94..7d737ba63e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -312,6 +312,7 @@ static struct atom_str **atom_table; /* The .pack file being generated */ static unsigned int pack_id; +static struct sha1file *pack_file; static struct packed_git *pack_data; static struct packed_git **all_packs; static unsigned long pack_size; @@ -838,11 +839,12 @@ static void start_packfile(void) p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2); strcpy(p->pack_name, tmpfile); p->pack_fd = pack_fd; + pack_file = sha1fd(pack_fd, p->pack_name); hdr.hdr_signature = htonl(PACK_SIGNATURE); hdr.hdr_version = htonl(2); hdr.hdr_entries = 0; - write_or_die(p->pack_fd, &hdr, sizeof(hdr)); + sha1write(pack_file, &hdr, sizeof(hdr)); pack_data = p; pack_size = sizeof(hdr); @@ -956,15 +958,17 @@ static void end_packfile(void) clear_delta_base_cache(); if (object_count) { + unsigned char cur_pack_sha1[20]; char *idx_name; int i; struct branch *b; struct tag *t; close_pack_windows(pack_data); + sha1close(pack_file, cur_pack_sha1, 0); fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1, pack_data->pack_name, object_count, - NULL, 0); + cur_pack_sha1, pack_size); close(pack_data->pack_fd); idx_name = keep_pack(create_index()); @@ -1138,22 +1142,22 @@ static int store_object( e->depth = last->depth + 1; hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); - write_or_die(pack_data->pack_fd, hdr, hdrlen); + sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; hdr[pos] = ofs & 127; while (ofs >>= 7) hdr[--pos] = 128 | (--ofs & 127); - write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos); + sha1write(pack_file, hdr + pos, sizeof(hdr) - pos); pack_size += sizeof(hdr) - pos; } else { e->depth = 0; hdrlen = encode_header(type, dat->len, hdr); - write_or_die(pack_data->pack_fd, hdr, hdrlen); + sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; } - write_or_die(pack_data->pack_fd, out, s.total_out); + sha1write(pack_file, out, s.total_out); pack_size += s.total_out; free(out); @@ -1170,12 +1174,17 @@ static int store_object( return 0; } -static void truncate_pack(off_t to) +static void truncate_pack(off_t to, git_SHA_CTX *ctx) { if (ftruncate(pack_data->pack_fd, to) || lseek(pack_data->pack_fd, to, SEEK_SET) != to) die_errno("cannot truncate pack to skip duplicate"); pack_size = to; + + /* yes this is a layering violation */ + pack_file->total = to; + pack_file->offset = 0; + pack_file->ctx = *ctx; } static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) @@ -1188,6 +1197,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) unsigned long hdrlen; off_t offset; git_SHA_CTX c; + git_SHA_CTX pack_file_ctx; z_stream s; int status = Z_OK; @@ -1198,6 +1208,10 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) offset = pack_size; + /* preserve the pack_file SHA1 ctx in case we have to truncate later */ + sha1flush(pack_file); + pack_file_ctx = pack_file->ctx; + hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; if (out_sz <= hdrlen) die("impossibly large object header"); @@ -1232,7 +1246,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) if (!s.avail_out || status == Z_STREAM_END) { size_t n = s.next_out - out_buf; - write_or_die(pack_data->pack_fd, out_buf, n); + sha1write(pack_file, out_buf, n); pack_size += n; s.next_out = out_buf; s.avail_out = out_sz; @@ -1260,14 +1274,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) if (e->idx.offset) { duplicate_count_by_type[OBJ_BLOB]++; - truncate_pack(offset); + truncate_pack(offset, &pack_file_ctx); } else if (find_sha1_pack(sha1, packed_git)) { e->type = OBJ_BLOB; e->pack_id = MAX_PACK_ID; e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[OBJ_BLOB]++; - truncate_pack(offset); + truncate_pack(offset, &pack_file_ctx); } else { e->depth = 0; @@ -1316,6 +1330,7 @@ static void *gfi_unpack_entry( * the newly written data. */ close_pack_windows(p); + sha1flush(pack_file); /* We have to offer 20 bytes additional on the end of * the packfile as the core unpacker code assumes the From 427cb22c40484f9b8c2881bc9e99636591a22655 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:53 -0500 Subject: [PATCH 0110/3211] fast-import: use write_idx_file() instead of custom code This allows for the creation of pack index version 2 with its object CRC and the possibility for a pack to be larger than 4 GB. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- fast-import.c | 63 ++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/fast-import.c b/fast-import.c index 7d737ba63e..9d7ab09620 100644 --- a/fast-import.c +++ b/fast-import.c @@ -854,67 +854,30 @@ static void start_packfile(void) all_packs[pack_id] = p; } -static int oecmp (const void *a_, const void *b_) +static const char *create_index(void) { - struct object_entry *a = *((struct object_entry**)a_); - struct object_entry *b = *((struct object_entry**)b_); - return hashcmp(a->idx.sha1, b->idx.sha1); -} - -static char *create_index(void) -{ - static char tmpfile[PATH_MAX]; - git_SHA_CTX ctx; - struct sha1file *f; - struct object_entry **idx, **c, **last, *e; + const char *tmpfile; + struct pack_idx_entry **idx, **c, **last; + struct object_entry *e; struct object_entry_pool *o; - uint32_t array[256]; - int i, idx_fd; - /* Build the sorted table of object IDs. */ - idx = xmalloc(object_count * sizeof(struct object_entry*)); + /* Build the table of object IDs. */ + idx = xmalloc(object_count * sizeof(*idx)); c = idx; for (o = blocks; o; o = o->next_pool) for (e = o->next_free; e-- != o->entries;) if (pack_id == e->pack_id) - *c++ = e; + *c++ = &e->idx; last = idx + object_count; if (c != last) die("internal consistency error creating the index"); - qsort(idx, object_count, sizeof(struct object_entry*), oecmp); - /* Generate the fan-out array. */ - c = idx; - for (i = 0; i < 256; i++) { - struct object_entry **next = c; - while (next < last) { - if ((*next)->idx.sha1[0] != i) - break; - next++; - } - array[i] = htonl(next - idx); - c = next; - } - - idx_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), - "pack/tmp_idx_XXXXXX"); - f = sha1fd(idx_fd, tmpfile); - sha1write(f, array, 256 * sizeof(int)); - git_SHA1_Init(&ctx); - for (c = idx; c != last; c++) { - uint32_t offset = htonl((*c)->idx.offset); - sha1write(f, &offset, 4); - sha1write(f, (*c)->idx.sha1, sizeof((*c)->idx.sha1)); - git_SHA1_Update(&ctx, (*c)->idx.sha1, 20); - } - sha1write(f, pack_data->sha1, sizeof(pack_data->sha1)); - sha1close(f, NULL, CSUM_FSYNC); + tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1); free(idx); - git_SHA1_Final(pack_data->sha1, &ctx); return tmpfile; } -static char *keep_pack(char *curr_index_name) +static char *keep_pack(const char *curr_index_name) { static char name[PATH_MAX]; static const char *keep_msg = "fast-import"; @@ -936,6 +899,7 @@ static char *keep_pack(char *curr_index_name) get_object_directory(), sha1_to_hex(pack_data->sha1)); if (move_temp_to_file(curr_index_name, name)) die("cannot store index file"); + free((void *)curr_index_name); return name; } @@ -1134,6 +1098,8 @@ static int store_object( object_count++; object_count_by_type[type]++; + crc32_begin(pack_file); + if (delta) { unsigned long ofs = e->idx.offset - last->offset; unsigned pos = sizeof(hdr) - 1; @@ -1160,6 +1126,8 @@ static int store_object( sha1write(pack_file, out, s.total_out); pack_size += s.total_out; + e->idx.crc32 = crc32_end(pack_file); + free(out); free(delta); if (last) { @@ -1219,6 +1187,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) git_SHA1_Init(&c); git_SHA1_Update(&c, out_buf, hdrlen); + crc32_begin(pack_file); + memset(&s, 0, sizeof(s)); deflateInit(&s, pack_compression_level); @@ -1288,6 +1258,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) e->type = OBJ_BLOB; e->pack_id = pack_id; e->idx.offset = offset; + e->idx.crc32 = crc32_end(pack_file); object_count++; object_count_by_type[OBJ_BLOB]++; } From 89e0a3a131d251b5345845529d5258ab91105c9b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:54 -0500 Subject: [PATCH 0111/3211] fast-import: make default pack size unlimited Now that fast-import is creating packs with index version 2, there is no point limiting the pack size by default. A pack split will still happen if off_t is not sufficiently large to hold large offsets. While updating the doc, let's remove the "packfiles fit on CDs" suggestion. Pack files created by fast-import are still suboptimal and a 'git repack -a -f -d' or even 'git gc --aggressive' would be a pretty good idea before considering storage on CDs. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.txt | 5 +---- fast-import.c | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 6764ff1886..19082b04eb 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -45,10 +45,7 @@ OPTIONS --max-pack-size=:: Maximum size of each output packfile. - The default is 4 GiB as that is the maximum allowed - packfile size (due to file format limitations). Some - importers may wish to lower this, such as to ensure the - resulting packfiles fit on CDs. + The default is unlimited. --big-file-threshold=:: Maximum size of a blob that fast-import will attempt to diff --git a/fast-import.c b/fast-import.c index 9d7ab09620..d2f45b18d9 100644 --- a/fast-import.c +++ b/fast-import.c @@ -191,7 +191,7 @@ struct mark_set struct last_object { struct strbuf data; - uint32_t offset; + off_t offset; unsigned int depth; unsigned no_swap : 1; }; @@ -279,7 +279,7 @@ struct recent_command /* Configured limits on output */ static unsigned long max_depth = 10; -static off_t max_packsize = (1LL << 32) - 1; +static off_t max_packsize; static uintmax_t big_file_threshold = 512 * 1024 * 1024; static int force_update; static int pack_compression_level = Z_DEFAULT_COMPRESSION; @@ -315,7 +315,7 @@ static unsigned int pack_id; static struct sha1file *pack_file; static struct packed_git *pack_data; static struct packed_git **all_packs; -static unsigned long pack_size; +static off_t pack_size; /* Table of objects we've written. */ static unsigned int object_entry_alloc = 5000; @@ -1068,7 +1068,7 @@ static int store_object( deflateEnd(&s); /* Determine if we should auto-checkpoint. */ - if ((pack_size + 60 + s.total_out) > max_packsize + if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize) || (pack_size + 60 + s.total_out) < pack_size) { /* This new object needs to *not* have the current pack_id. */ @@ -1101,7 +1101,7 @@ static int store_object( crc32_begin(pack_file); if (delta) { - unsigned long ofs = e->idx.offset - last->offset; + off_t ofs = e->idx.offset - last->offset; unsigned pos = sizeof(hdr) - 1; delta_count_by_type[type]++; @@ -1170,7 +1170,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) int status = Z_OK; /* Determine if we should auto-checkpoint. */ - if ((pack_size + 60 + len) > max_packsize + if ((max_packsize && (pack_size + 60 + len) > max_packsize) || (pack_size + 60 + len) < pack_size) cycle_packfile(); From 8c2ca8dd8a5d6d8beaa0a4abed0c135004eef772 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:55 -0500 Subject: [PATCH 0112/3211] fast-import: honor pack.indexversion and pack.packsizelimit config vars Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- fast-import.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fast-import.c b/fast-import.c index d2f45b18d9..7fc98620b2 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2876,6 +2876,17 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_compression_seen = 1; return 0; } + if (!strcmp(k, "pack.indexversion")) { + pack_idx_default_version = git_config_int(k, v); + if (pack_idx_default_version > 2) + die("bad pack.indexversion=%"PRIu32, + pack_idx_default_version); + return 0; + } + if (!strcmp(k, "pack.packsizelimit")) { + max_packsize = git_config_ulong(k, v); + return 0; + } if (!strcmp(k, "core.bigfilethreshold")) { long n = git_config_int(k, v); big_file_threshold = 0 < n ? n : 0; From b500d5e11ea67d29dd7be622f65571d611d6e9a3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:56 -0500 Subject: [PATCH 0113/3211] fast-import: use the diff_delta() max_delta_size argument This let diff_delta() abort early if it is going to bust the given size limit. Also, only objects larger than 20 bytes are considered as objects smaller than that are most certainly going to produce larger deltas than the original object due to the additional headers. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- fast-import.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/fast-import.c b/fast-import.c index 7fc98620b2..74f08bd554 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1041,14 +1041,10 @@ static int store_object( return 1; } - if (last && last->data.buf && last->depth < max_depth) { + if (last && last->data.buf && last->depth < max_depth && dat->len > 20) { delta = diff_delta(last->data.buf, last->data.len, dat->buf, dat->len, - &deltalen, 0); - if (delta && deltalen >= dat->len) { - free(delta); - delta = NULL; - } + &deltalen, dat->len - 20); } else delta = NULL; From 3deea89c5feb0dfdfb99ea752f83497d97a3bdd5 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Tue, 16 Feb 2010 11:21:14 +0100 Subject: [PATCH 0114/3211] submodule summary: Don't barf when invoked in an empty repo When invoking "git submodule summary" in an empty repo (which can be indirectly done by setting status.submodulesummary = true), it currently emits an error message (via "git diff-index") since HEAD points to an unborn branch. This patch adds handling of the HEAD-points-to-unborn-branch special case, so that "git submodule summary" no longer emits this error message. The patch also adds a test case that verifies the fix. Suggested-by: Jeff King Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- git-submodule.sh | 7 +++++-- t/t7401-submodule-summary.sh | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index 664f21721c..5869c00f2d 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -553,12 +553,15 @@ cmd_summary() { test $summary_limit = 0 && return - if rev=$(git rev-parse -q --verify "$1^0") + if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"}) then head=$rev shift + elif test -z "$1" -o "$1" = "HEAD" + then + return else - head=HEAD + head="HEAD" fi if [ -n "$files" ] diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index d3c039f724..cee319da0a 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -227,4 +227,11 @@ test_expect_success 'fail when using --files together with --cached' " test_must_fail git submodule summary --files --cached " +test_expect_success 'should not fail in an empty repo' " + git init xyzzy && + cd xyzzy && + git submodule summary >output 2>&1 && + test_cmp output /dev/null +" + test_done From 453541fcfcbc54aa3b0035667e5d5885d407d0a5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:51:18 +0100 Subject: [PATCH 0115/3211] gitweb: esc_html (short) error message in die_error The error message (second argument to die_error) is meant to be short, one-line text description of given error. A few callers call die_error with error message containing unescaped user supplied data ($hash, $file_name). Instead of forcing callers to escape data, simply call esc_html on the parameter. Note that optional third parameter, which contains detailed error description, is meant to be HTML formatted, and therefore should be not escaped. While at it update esc_html synopsis/usage, and bring default error description to read 'Internal Server Error' (titlecased). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1f6978ac1f..2ccbb6aa34 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3372,7 +3372,7 @@ sub git_footer_html { ""; } -# die_error(, ) +# die_error(, [, ]) # Example: die_error(404, 'Hash not found') # By convention, use the following status codes (as defined in RFC 2616): # 400: Invalid or missing CGI parameters, or @@ -3387,7 +3387,7 @@ sub git_footer_html { # or down for maintenance). Generally, this is a temporary state. sub die_error { my $status = shift || 500; - my $error = shift || "Internal server error"; + my $error = esc_html(shift || "Internal Server Error"); my $extra = shift; my %http_responses = ( From 1df48766137f2040e2e992d7d278d5aca26406cf Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:52:25 +0100 Subject: [PATCH 0116/3211] gitweb: Protect escaping functions against calling on undef This is a bit of future-proofing esc_html and friends: when called with undefined value they would now would return undef... which would probably mean that error would still occur, but closer to the source of problem. This means that we can safely use esc_html(shift) || "Internal Server Error" in die_error() instead of esc_html(shift || "Internal Server Error") Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2ccbb6aa34..3c879b88fe 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1143,6 +1143,7 @@ sub validate_refname { # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning sub to_utf8 { my $str = shift; + return undef unless defined $str; if (utf8::valid($str)) { utf8::decode($str); return $str; @@ -1155,6 +1156,7 @@ sub to_utf8 { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; + return undef unless defined $str; $str =~ s/([^A-Za-z0-9\-_.~()\/:@ ]+)/CGI::escape($1)/eg; $str =~ s/ /\+/g; return $str; @@ -1163,6 +1165,7 @@ sub esc_param { # quote unsafe chars in whole URL, so some charactrs cannot be quoted sub esc_url { my $str = shift; + return undef unless defined $str; $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; $str =~ s/ /\+/g; @@ -1174,6 +1177,8 @@ sub esc_html { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -1188,6 +1193,8 @@ sub esc_path { my $str = shift; my %opts = @_; + return undef unless defined $str; + $str = to_utf8($str); $str = $cgi->escapeHTML($str); if ($opts{'-nbsp'}) { @@ -3387,7 +3394,7 @@ sub git_footer_html { # or down for maintenance). Generally, this is a temporary state. sub die_error { my $status = shift || 500; - my $error = esc_html(shift || "Internal Server Error"); + my $error = esc_html(shift) || "Internal Server Error"; my $extra = shift; my %http_responses = ( From 90b45187ba51a15c8a80680597240e32421f53bb Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:42:31 +1300 Subject: [PATCH 0117/3211] Add `init.templatedir` configuration variable. Rather than having to pass --template to git init and clone for a custom setup, `init.templatedir` may be set in '~/.gitconfig'. The environment variable GIT_TEMPLATE_DIR can already be used for this but this is nicer. System administrators may prefer using this variable in the system-wide config file to point at a locally modified copy (e.g. /etc/gittemplate) rather than editing vanilla template files in '/usr/share'. Signed-off-by: Steven Drake Signed-off-by: Junio C Hamano --- builtin-init-db.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/builtin-init-db.c b/builtin-init-db.c index dd84caecbc..0eb9efc939 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -20,6 +20,7 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; +static const char *init_db_template_dir; static void safe_create_dir(const char *dir, int share) { @@ -120,6 +121,8 @@ static void copy_templates(const char *template_dir) if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); + if (!template_dir) + template_dir = init_db_template_dir; if (!template_dir) template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); if (!template_dir[0]) @@ -165,6 +168,16 @@ static void copy_templates(const char *template_dir) closedir(dir); } +static int git_init_db_config(const char *k, const char *v, void *cb) +{ + if (!v) + return config_error_nonbool(k); + if (!strcmp(k, "init.templatedir")) + return git_config_pathname(&init_db_template_dir, k, v); + + return 0; +} + static int create_default_files(const char *template_path) { const char *git_dir = get_git_dir(); @@ -190,6 +203,9 @@ static int create_default_files(const char *template_path) safe_create_dir(git_path("refs/heads"), 1); safe_create_dir(git_path("refs/tags"), 1); + /* Just look for `init.templatedir` */ + git_config(git_init_db_config, NULL); + /* First copy the templates -- we might have the default * config file there, in which case we would want to read * from it after installing. From d8a8488d569420c4e0a96a8df36b246d888e68d4 Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:44:46 +1300 Subject: [PATCH 0118/3211] Add a "TEMPLATE DIRECTORY" section to git-init[1]. Create a more inoformative section to describe template directory and refer to it in config.txt and with the '--template' option of git-init and git-clone commands. Signed-off-by: Steven Drake Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-clone.txt | 3 +-- Documentation/git-init.txt | 29 +++++++++++++++++++++-------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 4c36aa95b7..310d36b20f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1200,6 +1200,10 @@ imap:: The configuration variables in the 'imap' section are described in linkgit:git-imap-send[1]. +init.templatedir:: + Specify the directory from which templates will be copied. + (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) + instaweb.browser:: Specify the program that will be used to browse your working repository in gitweb. See linkgit:git-instaweb[1]. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index f43c8b2c08..3919b7d44f 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -149,8 +149,7 @@ objects from the source repository into a pack in the cloned repository. --template=:: Specify the directory from which templates will be used; - if unset the templates are taken from the installation - defined default, typically `/usr/share/git-core/templates`. + (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) --depth :: Create a 'shallow' clone with a history truncated to the diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 7ee102da48..246b07ebf9 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -28,14 +28,8 @@ current working directory. --template=:: -Provide the directory from which templates will be used. The default template -directory is `/usr/share/git-core/templates`. - -When specified, `` is used as the source of the template -files rather than the default. The template files include some directory -structure, some suggested "exclude patterns", and copies of non-executing -"hook" files. The suggested patterns and hook files are all modifiable and -extensible. +Specify the directory from which templates will be used. (See the "TEMPLATE +DIRECTORY" section below.) --shared[={false|true|umask|group|all|world|everybody|0xxx}]:: @@ -106,6 +100,25 @@ of the repository, such as installing the default hooks and setting the configuration variables. The old name is retained for backward compatibility reasons. +TEMPLATE DIRECTORY +------------------ + +The template directory contains files and directories that will be copied to +the `$GIT_DIR` after it is created. + +The template directory used will (in order): + + - The argument given with the `--template` option. + + - The contents of the `$GIT_TEMPLATE_DIR` environment variable. + + - The `init.templatedir` configuration variable. + + - The default template directory: `/usr/share/git-core/templates`. + +The default template directory includes some directory structure, some +suggested "exclude patterns", and copies of sample "hook" files. +The suggested patterns and hook files are all modifiable and extensible. EXAMPLES -------- From 149794dd1db18b22f4df94ef13cf61520d7be1d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 12:30:41 -0800 Subject: [PATCH 0119/3211] status: preload index to optimize lstat(2) calls Noticed by James Pickens Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index 55676fd874..bb97000320 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -1046,7 +1046,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (*argv) s.pathspec = get_pathspec(prefix, argv); - read_cache(); + read_cache_preload(s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.in_merge = in_merge; From e3ff352c73a87d533fd239c3f9d4bb978c8ce387 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 15:00:00 -0800 Subject: [PATCH 0120/3211] Update 1.7.0.1 release notes Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.0.1.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.7.0.1.txt b/Documentation/RelNotes-1.7.0.1.txt index 312a3f5534..970cd59330 100644 --- a/Documentation/RelNotes-1.7.0.1.txt +++ b/Documentation/RelNotes-1.7.0.1.txt @@ -27,5 +27,5 @@ Fixes since v1.7.0 -- exec >/var/tmp/1 echo O=$(git describe) -O=v1.7.0-11-g354d9f8 +O=v1.7.0-22-gc69f921 git shortlog $O.. From c2c85ed5d90d96a9d06170729a1a8b15af5256a4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 15:01:11 -0800 Subject: [PATCH 0121/3211] Update draft release notes to 1.7.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.1.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.7.1.txt b/Documentation/RelNotes-1.7.1.txt index b0bf8366c0..8c18ca5d22 100644 --- a/Documentation/RelNotes-1.7.1.txt +++ b/Documentation/RelNotes-1.7.1.txt @@ -4,6 +4,9 @@ Git v1.7.1 Release Notes Updates since v1.7.0 -------------------- + * "git grep" learned "--no-index" option, to search inside contents that + are not managed by git. + Fixes since v1.7.0 ------------------ @@ -13,5 +16,5 @@ release, unless otherwise noted. --- exec >/var/tmp/1 echo O=$(git describe) -O=v1.7.0 +O=v1.7.0-36-gfaa3b47 git shortlog --no-merges ^maint $O.. From 72a534dab016813eec71935323b197650a2c0c9d Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Wed, 17 Feb 2010 20:56:02 +0000 Subject: [PATCH 0122/3211] connect.c: move duplicated code to a new function 'get_host_and_port' The following functions: git_tcp_connect_sock (IPV6 version) git_tcp_connect_sock (no IPV6 version), git_proxy_connect have common block of code. Move it to a new function 'get_host_and_port' Signed-off-by: Michael Lukashov Signed-off-by: Junio C Hamano --- connect.c | 83 +++++++++++++++++++------------------------------------ 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/connect.c b/connect.c index 20054e4d0f..f4563f951a 100644 --- a/connect.c +++ b/connect.c @@ -152,6 +152,28 @@ static enum protocol get_protocol(const char *name) #define STR_(s) # s #define STR(s) STR_(s) +static void get_host_and_port(char **host, const char **port) +{ + char *colon, *end; + + if (*host[0] == '[') { + end = strchr(*host + 1, ']'); + if (end) { + *end = 0; + end++; + (*host)++; + } else + end = *host; + } else + end = *host; + colon = strchr(end, ':'); + + if (colon) { + *colon = 0; + *port = colon + 1; + } +} + #ifndef NO_IPV6 static const char *ai_name(const struct addrinfo *ai) @@ -170,30 +192,14 @@ static const char *ai_name(const struct addrinfo *ai) static int git_tcp_connect_sock(char *host, int flags) { int sockfd = -1, saved_errno = 0; - char *colon, *end; const char *port = STR(DEFAULT_GIT_PORT); struct addrinfo hints, *ai0, *ai; int gai; int cnt = 0; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - if (!*port) - port = ""; - } + get_host_and_port(&host, &port); + if (!*port) + port = ""; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; @@ -251,30 +257,15 @@ static int git_tcp_connect_sock(char *host, int flags) static int git_tcp_connect_sock(char *host, int flags) { int sockfd = -1, saved_errno = 0; - char *colon, *end; - char *port = STR(DEFAULT_GIT_PORT), *ep; + const char *port = STR(DEFAULT_GIT_PORT); + char *ep; struct hostent *he; struct sockaddr_in sa; char **ap; unsigned int nport; int cnt; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - } + get_host_and_port(&host, &port); if (flags & CONNECT_VERBOSE) fprintf(stderr, "Looking up %s ... ", host); @@ -406,26 +397,10 @@ static int git_use_proxy(const char *host) static void git_proxy_connect(int fd[2], char *host) { const char *port = STR(DEFAULT_GIT_PORT); - char *colon, *end; const char *argv[4]; struct child_process proxy; - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - *end = 0; - end++; - host++; - } else - end = host; - } else - end = host; - colon = strchr(end, ':'); - - if (colon) { - *colon = 0; - port = colon + 1; - } + get_host_and_port(&host, &port); argv[0] = git_proxy_command; argv[1] = host; From f1863d0d16b9a5288671e17b7fa2eba8244ead2f Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:52 +0000 Subject: [PATCH 0123/3211] refactor duplicated code in builtin-send-pack.c and transport.c The following functions are (almost) identical: verify_remote_names update_tracking_ref refs_pushed print_push_status Move common versions of these functions to transport.c and rename them, as suggested by Jeff King and Junio C Hamano. These functions have been removed entirely from builtin-send-pack.c, since they are only used internally by print_push_status(): print_ref_status status_abbrev print_ok_ref_status print_one_push_status Also, move #define SUMMARY_WIDTH to transport.h and rename it TRANSPORT_SUMMARY_WIDTH as it is used in builtin-fetch.c and transport.c Signed-off-by: Michael Lukashov Acked-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- builtin-fetch.c | 20 ++--- builtin-send-pack.c | 191 ++------------------------------------------ transport.c | 22 +++-- transport.h | 11 +++ 4 files changed, 37 insertions(+), 207 deletions(-) diff --git a/builtin-fetch.c b/builtin-fetch.c index 8654fa7a2d..d3b9d8a133 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" +#include "transport.h" static const char * const builtin_fetch_usage[] = { "git fetch [options] [ ...]", @@ -205,7 +206,6 @@ static int s_update_ref(const char *action, return 0; } -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) #define REFCOL_WIDTH 10 static int update_local_ref(struct ref *ref, @@ -224,7 +224,7 @@ static int update_local_ref(struct ref *ref, if (!hashcmp(ref->old_sha1, ref->new_sha1)) { if (verbosity > 0) - sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, + sprintf(display, "= %-*s %-*s -> %s", TRANSPORT_SUMMARY_WIDTH, "[up to date]", REFCOL_WIDTH, remote, pretty_ref); return 0; @@ -239,7 +239,7 @@ static int update_local_ref(struct ref *ref, * the head, and the old value of the head isn't empty... */ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -249,7 +249,7 @@ static int update_local_ref(struct ref *ref, int r; r = s_update_ref("updating tag", ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '-', - SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } @@ -271,7 +271,7 @@ static int update_local_ref(struct ref *ref, r = s_update_ref(msg, ref, 0); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, + TRANSPORT_SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } @@ -284,7 +284,7 @@ static int update_local_ref(struct ref *ref, strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); r = s_update_ref("fast-forward", ref, 1); sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : ' ', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? " (unable to update local ref)" : ""); return r; } else if (force || ref->force) { @@ -295,13 +295,13 @@ static int update_local_ref(struct ref *ref, strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV)); r = s_update_ref("forced-update", ref, 1); sprintf(display, "%c %-*s %-*s -> %s (%s)", r ? '!' : '+', - SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref, r ? "unable to update local ref" : "forced update"); return r; } else { sprintf(display, "! %-*s %-*s -> %s (non-fast-forward)", - SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, + TRANSPORT_SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote, pretty_ref); return 1; } @@ -393,7 +393,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, rc |= update_local_ref(ref, what, note); else sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - SUMMARY_WIDTH, *kind ? kind : "branch", + TRANSPORT_SUMMARY_WIDTH, *kind ? kind : "branch", REFCOL_WIDTH, *what ? what : "HEAD"); if (*note) { if (verbosity >= 0 && !shown_url) { @@ -514,7 +514,7 @@ static int prune_refs(struct transport *transport, struct ref *ref_map) result |= delete_ref(ref->name, NULL, 0); if (verbosity >= 0) { fprintf(stderr, " x %-*s %-*s -> %s\n", - SUMMARY_WIDTH, "[deleted]", + TRANSPORT_SUMMARY_WIDTH, "[deleted]", REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); warn_dangling_symref(stderr, dangling_msg, ref->name); } diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 76c72065de..cbda3117d8 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -7,6 +7,7 @@ #include "remote.h" #include "send-pack.h" #include "quote.h" +#include "transport.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" @@ -169,156 +170,6 @@ static int receive_status(int in, struct ref *refs) return ret; } -static void update_tracking_ref(struct remote *remote, struct ref *ref) -{ - struct refspec rs; - - if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE) - return; - - rs.src = ref->name; - rs.dst = NULL; - - if (!remote_find_tracking(remote, &rs)) { - if (args.verbose) - fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); - if (ref->deletion) { - delete_ref(rs.dst, NULL, 0); - } else - update_ref("update by push", rs.dst, - ref->new_sha1, NULL, 0, 0); - free(rs.dst); - } -} - -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) - -static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) -{ - fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); - if (from) - fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); - else - fputs(prettify_refname(to->name), stderr); - if (msg) { - fputs(" (", stderr); - fputs(msg, stderr); - fputc(')', stderr); - } - fputc('\n', stderr); -} - -static const char *status_abbrev(unsigned char sha1[20]) -{ - return find_unique_abbrev(sha1, DEFAULT_ABBREV); -} - -static void print_ok_ref_status(struct ref *ref) -{ - if (ref->deletion) - print_ref_status('-', "[deleted]", ref, NULL, NULL); - else if (is_null_sha1(ref->old_sha1)) - print_ref_status('*', - (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : - "[new branch]"), - ref, ref->peer_ref, NULL); - else { - char quickref[84]; - char type; - const char *msg; - - strcpy(quickref, status_abbrev(ref->old_sha1)); - if (ref->nonfastforward) { - strcat(quickref, "..."); - type = '+'; - msg = "forced update"; - } else { - strcat(quickref, ".."); - type = ' '; - msg = NULL; - } - strcat(quickref, status_abbrev(ref->new_sha1)); - - print_ref_status(type, quickref, ref, ref->peer_ref, msg); - } -} - -static int print_one_push_status(struct ref *ref, const char *dest, int count) -{ - if (!count) - fprintf(stderr, "To %s\n", dest); - - switch(ref->status) { - case REF_STATUS_NONE: - print_ref_status('X', "[no match]", ref, NULL, NULL); - break; - case REF_STATUS_REJECT_NODELETE: - print_ref_status('!', "[rejected]", ref, NULL, - "remote does not support deleting refs"); - break; - case REF_STATUS_UPTODATE: - print_ref_status('=', "[up to date]", ref, - ref->peer_ref, NULL); - break; - case REF_STATUS_REJECT_NONFASTFORWARD: - print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast-forward"); - break; - case REF_STATUS_REMOTE_REJECT: - print_ref_status('!', "[remote rejected]", ref, - ref->deletion ? NULL : ref->peer_ref, - ref->remote_status); - break; - case REF_STATUS_EXPECTING_REPORT: - print_ref_status('!', "[remote failure]", ref, - ref->deletion ? NULL : ref->peer_ref, - "remote failed to report status"); - break; - case REF_STATUS_OK: - print_ok_ref_status(ref); - break; - } - - return 1; -} - -static void print_push_status(const char *dest, struct ref *refs) -{ - struct ref *ref; - int n = 0; - - if (args.verbose) { - for (ref = refs; ref; ref = ref->next) - if (ref->status == REF_STATUS_UPTODATE) - n += print_one_push_status(ref, dest, n); - } - - for (ref = refs; ref; ref = ref->next) - if (ref->status == REF_STATUS_OK) - n += print_one_push_status(ref, dest, n); - - for (ref = refs; ref; ref = ref->next) { - if (ref->status != REF_STATUS_NONE && - ref->status != REF_STATUS_UPTODATE && - ref->status != REF_STATUS_OK) - n += print_one_push_status(ref, dest, n); - } -} - -static int refs_pushed(struct ref *ref) -{ - for (; ref; ref = ref->next) { - switch(ref->status) { - case REF_STATUS_NONE: - case REF_STATUS_UPTODATE: - break; - default: - return 1; - } - } - return 0; -} - static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; @@ -489,37 +340,6 @@ int send_pack(struct send_pack_args *args, return 0; } -static void verify_remote_names(int nr_heads, const char **heads) -{ - int i; - - for (i = 0; i < nr_heads; i++) { - const char *local = heads[i]; - const char *remote = strrchr(heads[i], ':'); - - if (*local == '+') - local++; - - /* A matching refspec is okay. */ - if (remote == local && remote[1] == '\0') - continue; - - remote = remote ? (remote + 1) : local; - switch (check_ref_format(remote)) { - case 0: /* ok */ - case CHECK_REF_FORMAT_ONELEVEL: - /* ok but a single level -- that is fine for - * a match pattern. - */ - case CHECK_REF_FORMAT_WILDCARD: - /* ok but ends with a pattern-match character */ - continue; - } - die("remote part of refspec is not a valid name in %s", - heads[i]); - } -} - int cmd_send_pack(int argc, const char **argv, const char *prefix) { int i, nr_refspecs = 0; @@ -536,6 +356,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; + int nonfastforward = 0; argv++; for (i = 1; i < argc; i++, argv++) { @@ -628,7 +449,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, &extra_have); - verify_remote_names(nr_refspecs, refspecs); + transport_verify_remote_names(nr_refspecs, refspecs); local_refs = get_local_heads(); @@ -657,15 +478,15 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret |= finish_connect(conn); if (!helper_status) - print_push_status(dest, remote_refs); + transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward); if (!args.dry_run && remote) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(remote, ref); + transport_update_tracking_ref(remote, ref, args.verbose); } - if (!ret && !refs_pushed(remote_refs)) + if (!ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); return ret; diff --git a/transport.c b/transport.c index 3846aacb47..6d7a1259a6 100644 --- a/transport.c +++ b/transport.c @@ -573,7 +573,7 @@ static int push_had_errors(struct ref *ref) return 0; } -static int refs_pushed(struct ref *ref) +int transport_refs_pushed(struct ref *ref) { for (; ref; ref = ref->next) { switch(ref->status) { @@ -587,7 +587,7 @@ static int refs_pushed(struct ref *ref) return 0; } -static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) { struct refspec rs; @@ -609,8 +609,6 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref, int verb } } -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) - static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain) { if (porcelain) { @@ -623,7 +621,7 @@ static void print_ref_status(char flag, const char *summary, struct ref *to, str else fprintf(stdout, "%s\n", summary); } else { - fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary); if (from) fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); else @@ -711,8 +709,8 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i return 1; } -static void print_push_status(const char *dest, struct ref *refs, - int verbose, int porcelain, int * nonfastforward) +void transport_print_push_status(const char *dest, struct ref *refs, + int verbose, int porcelain, int *nonfastforward) { struct ref *ref; int n = 0; @@ -738,7 +736,7 @@ static void print_push_status(const char *dest, struct ref *refs, } } -static void verify_remote_names(int nr_heads, const char **heads) +void transport_verify_remote_names(int nr_heads, const char **heads) { int i; @@ -1018,7 +1016,7 @@ int transport_push(struct transport *transport, int *nonfastforward) { *nonfastforward = 0; - verify_remote_names(refspec_nr, refspec); + transport_verify_remote_names(refspec_nr, refspec); if (transport->push) { /* Maybe FIXME. But no important transport uses this case. */ @@ -1057,7 +1055,7 @@ int transport_push(struct transport *transport, ret |= err; if (!quiet || err) - print_push_status(transport->url, remote_refs, + transport_print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); @@ -1067,10 +1065,10 @@ int transport_push(struct transport *transport, if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(transport->remote, ref, verbose); + transport_update_tracking_ref(transport->remote, ref, verbose); } - if (!quiet && !ret && !refs_pushed(remote_refs)) + if (!quiet && !ret && !transport_refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); return ret; } diff --git a/transport.h b/transport.h index 7cea5cc723..7a9bb57d99 100644 --- a/transport.h +++ b/transport.h @@ -92,6 +92,7 @@ struct transport { #define TRANSPORT_PUSH_PORCELAIN 32 #define TRANSPORT_PUSH_QUIET 64 #define TRANSPORT_PUSH_SET_UPSTREAM 128 +#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); @@ -142,4 +143,14 @@ int transport_connect(struct transport *transport, const char *name, /* Transport methods defined outside transport.c */ int transport_helper_init(struct transport *transport, const char *name); +/* common methods used by transport.c and builtin-send-pack.c */ +void transport_verify_remote_names(int nr_heads, const char **heads); + +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose); + +int transport_refs_pushed(struct ref *ref); + +void transport_print_push_status(const char *dest, struct ref *refs, + int verbose, int porcelain, int *nonfastforward); + #endif From 06b65939b083ba1b71043005bf83b4883e98264e Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:55 +0000 Subject: [PATCH 0124/3211] refactor duplicated fill_mm() in checkout and merge-recursive The following function is duplicated: fill_mm Move it to xdiff-interface.c and rename it 'read_mmblob', as suggested by Junio C Hamano. Also, change parameters order for consistency with read_mmfile(). Signed-off-by: Michael Lukashov Signed-off-by: Junio C Hamano --- builtin-checkout.c | 24 +++--------------------- merge-recursive.c | 23 +++-------------------- xdiff-interface.c | 17 +++++++++++++++++ xdiff-interface.h | 1 + 4 files changed, 24 insertions(+), 41 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 527781728e..22ae92f608 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -128,24 +128,6 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, (stage == 2) ? "our" : "their"); } -/* NEEDSWORK: share with merge-recursive */ -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - static int checkout_merged(int pos, struct checkout *state) { struct cache_entry *ce = active_cache[pos]; @@ -163,9 +145,9 @@ static int checkout_merged(int pos, struct checkout *state) ce_stage(active_cache[pos+2]) != 3) return error("path '%s' does not have all 3 versions", path); - fill_mm(active_cache[pos]->sha1, &ancestor); - fill_mm(active_cache[pos+1]->sha1, &ours); - fill_mm(active_cache[pos+2]->sha1, &theirs); + read_mmblob(&ancestor, active_cache[pos]->sha1); + read_mmblob(&ours, active_cache[pos+1]->sha1); + read_mmblob(&theirs, active_cache[pos+2]->sha1); status = ll_merge(&result_buf, path, &ancestor, &ours, "ours", &theirs, "theirs", 0); diff --git a/merge-recursive.c b/merge-recursive.c index cb53b01c19..195ebf9744 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -599,23 +599,6 @@ struct merge_file_info merge:1; }; -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - static int merge_3way(struct merge_options *o, mmbuffer_t *result_buf, struct diff_filespec *one, @@ -653,9 +636,9 @@ static int merge_3way(struct merge_options *o, name2 = xstrdup(mkpath("%s", branch2)); } - fill_mm(one->sha1, &orig); - fill_mm(a->sha1, &src1); - fill_mm(b->sha1, &src2); + read_mmblob(&orig, one->sha1); + read_mmblob(&src1, a->sha1); + read_mmblob(&src2, b->sha1); merge_status = ll_merge(result_buf, a->path, &orig, &src1, name1, &src2, name2, diff --git a/xdiff-interface.c b/xdiff-interface.c index 01f14fb50f..ca5e3fbae8 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -218,6 +218,23 @@ int read_mmfile(mmfile_t *ptr, const char *filename) return 0; } +void read_mmblob(mmfile_t *ptr, const unsigned char *sha1) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + ptr->ptr = xstrdup(""); + ptr->size = 0; + return; + } + + ptr->ptr = read_sha1_file(sha1, &type, &size); + if (!ptr->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + ptr->size = size; +} + #define FIRST_FEW_BYTES 8000 int buffer_is_binary(const char *ptr, unsigned long size) { diff --git a/xdiff-interface.h b/xdiff-interface.h index 55572c39a1..abba70c16b 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -18,6 +18,7 @@ int parse_hunk_header(char *line, int len, int *ob, int *on, int *nb, int *nn); int read_mmfile(mmfile_t *ptr, const char *filename); +void read_mmblob(mmfile_t *ptr, const unsigned char *sha1); int buffer_is_binary(const char *ptr, unsigned long size); extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags); From 1b22b6c897efa8aec6eeb51f72a73d507d5e336f Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:54 +0000 Subject: [PATCH 0125/3211] refactor duplicated encode_header in pack-objects and fast-import The following function is duplicated: encode_header Move this function to sha1_file.c and rename it 'encode_in_pack_object_header', as suggested by Junio C Hamano Signed-off-by: Michael Lukashov Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 31 ++----------------------------- cache.h | 8 ++++++++ fast-import.c | 29 +++-------------------------- sha1_file.c | 20 ++++++++++++++++++++ 4 files changed, 33 insertions(+), 55 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index e1d3adf405..6b2f65c6db 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -154,33 +154,6 @@ static unsigned long do_compress(void **pptr, unsigned long size) return stream.total_out; } -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", then three bits of "type", - * and the high bit is "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - /* * we are going to reuse the existing object data as is. make * sure it is not corrupt. @@ -321,7 +294,7 @@ static unsigned long write_object(struct sha1file *f, * The object header is a byte of 'type' followed by zero or * more bytes of length. */ - hdrlen = encode_header(type, size, header); + hdrlen = encode_in_pack_object_header(type, size, header); if (type == OBJ_OFS_DELTA) { /* @@ -372,7 +345,7 @@ static unsigned long write_object(struct sha1file *f, if (entry->delta) type = (allow_ofs_delta && entry->delta->idx.offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; - hdrlen = encode_header(type, entry->size, header); + hdrlen = encode_in_pack_object_header(type, entry->size, header); offset = entry->in_pack_offset; revidx = find_pack_revindex(p, offset); diff --git a/cache.h b/cache.h index d478eff1f3..2654b608ba 100644 --- a/cache.h +++ b/cache.h @@ -911,6 +911,14 @@ extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsign extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", then three bits of "type", + * and the high bit is "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr); /* Dumb servers support */ extern int update_server_info(int); diff --git a/fast-import.c b/fast-import.c index 74f08bd554..309f2c58a2 100644 --- a/fast-import.c +++ b/fast-import.c @@ -980,29 +980,6 @@ static void cycle_packfile(void) start_packfile(); } -static size_t encode_header( - enum object_type type, - uintmax_t size, - unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - static int store_object( enum object_type type, struct strbuf *dat, @@ -1103,7 +1080,7 @@ static int store_object( delta_count_by_type[type]++; e->depth = last->depth + 1; - hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr); + hdrlen = encode_in_pack_object_header(OBJ_OFS_DELTA, deltalen, hdr); sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; @@ -1114,7 +1091,7 @@ static int store_object( pack_size += sizeof(hdr) - pos; } else { e->depth = 0; - hdrlen = encode_header(type, dat->len, hdr); + hdrlen = encode_in_pack_object_header(type, dat->len, hdr); sha1write(pack_file, hdr, hdrlen); pack_size += hdrlen; } @@ -1188,7 +1165,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) memset(&s, 0, sizeof(s)); deflateInit(&s, pack_compression_level); - hdrlen = encode_header(OBJ_BLOB, len, out_buf); + hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf); if (out_sz <= hdrlen) die("impossibly large object header"); diff --git a/sha1_file.c b/sha1_file.c index 657825e14e..f3c9823c56 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1475,6 +1475,26 @@ const char *packed_object_info_detail(struct packed_git *p, } } +int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} + static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long *sizep) { From cc1b8d8bc6e453b96798574d67ce9590eb3e82e1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 17 Feb 2010 20:16:20 -0500 Subject: [PATCH 0126/3211] docs: don't talk about $GIT_DIR/refs/ everywhere It is misleading to say that we pull refs from $GIT_DIR/refs/*, because we may also consult the packed refs mechanism. These days we tend to treat the "refs hierarchy" as more of an abstract namespace that happens to be represented as $GIT_DIR/refs. At best, this is a minor inaccuracy, but at worst it can confuse users who then look in $GIT_DIR/refs and find that it is missing some of the refs they expected to see. This patch drops most uses of "$GIT_DIR/refs/*", changing them into just "refs/*", under the assumption that users can handle the concept of an abstract refs namespace. There are a few things to note: - most cases just dropped the $GIT_DIR/ portion. But for cases where that left _just_ the word "refs", I changed it to "refs/" to help indicate that it was a hierarchy. I didn't do the same for longer paths (e.g., "refs/heads" remained, instead of becoming "refs/heads/"). - in some cases, no change was made, as the text was explicitly about unpacked refs (e.g., the discussion in git-pack-refs). - In some cases it made sense instead to note the existence of packed refs (e.g., in check-ref-format and rev-parse). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/git-check-ref-format.txt | 5 +++-- Documentation/git-clone.txt | 2 +- Documentation/git-fetch-pack.txt | 2 +- Documentation/git-pack-objects.txt | 2 +- Documentation/git-prune.txt | 2 +- Documentation/git-push.txt | 6 +++--- Documentation/git-rev-parse.txt | 22 ++++++++++++---------- Documentation/git-show-branch.txt | 10 +++++----- Documentation/git-stash.txt | 2 +- Documentation/rev-list-options.txt | 22 +++++++++++----------- 10 files changed, 39 insertions(+), 36 deletions(-) diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index e1c4320f02..379eee6734 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -19,8 +19,9 @@ status if it is not. A reference is used in git to specify branches and tags. A branch head is stored under the `$GIT_DIR/refs/heads` directory, and -a tag is stored under the `$GIT_DIR/refs/tags` directory. git -imposes the following rules on how references are named: +a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs +are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file). +git imposes the following rules on how references are named: . They can include slash `/` for hierarchical (directory) grouping, but no slash-separated component can begin with a diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index f43c8b2c08..88ea6246a1 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -29,7 +29,7 @@ arguments will in addition merge the remote master branch into the current master branch, if any. This default configuration is achieved by creating references to -the remote branch heads under `$GIT_DIR/refs/remotes/origin` and +the remote branch heads under `refs/remotes/origin` and by initializing `remote.origin.url` and `remote.origin.fetch` configuration variables. diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index e9952e8210..97ea7973a0 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -18,7 +18,7 @@ higher level wrapper of this command, instead. Invokes 'git-upload-pack' on a possibly remote repository and asks it to send objects missing from this repository, to update the named heads. The list of commits available locally -is found out by scanning local $GIT_DIR/refs/ and sent to +is found out by scanning the local refs/ hierarchy and sent to 'git-upload-pack' running on the other end. This command degenerates to download everything to complete the diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index ffd5025f7b..61fd7d0998 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -73,7 +73,7 @@ base-name:: --all:: This implies `--revs`. In addition to the list of revision arguments read from the standard input, pretend - as if all refs under `$GIT_DIR/refs` are specified to be + as if all refs under `refs/` are specified to be included. --include-tag:: diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 3bb7304517..15cfb7a8dc 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -17,7 +17,7 @@ NOTE: In most cases, users should run 'git gc', which calls 'git prune'. See the section "NOTES", below. This runs 'git fsck --unreachable' using all the refs -available in `$GIT_DIR/refs`, optionally with additional set of +available in `refs/`, optionally with additional set of objects specified on the command line, and prunes all unpacked objects unreachable from any of these head objects from the object database. In addition, it diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index bd79119dd3..3f103ccb00 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -69,11 +69,11 @@ nor in any Push line of the corresponding remotes file---see below). --all:: Instead of naming each ref to push, specifies that all - refs under `$GIT_DIR/refs/heads/` be pushed. + refs under `refs/heads/` be pushed. --mirror:: Instead of naming each ref to push, specifies that all - refs under `$GIT_DIR/refs/` (which includes but is not + refs under `refs/` (which includes but is not limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`) be mirrored to the remote repository. Newly created local refs will be pushed to the remote end, locally updated refs @@ -96,7 +96,7 @@ nor in any Push line of the corresponding remotes file---see below). the same as prefixing all refs with a colon. --tags:: - All refs under `$GIT_DIR/refs/tags` are pushed, in + All refs under `refs/tags` are pushed, in addition to refspecs explicitly listed on the command line. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index d677c72d5e..1a613aa108 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -101,15 +101,14 @@ OPTIONS abbreviation mode. --all:: - Show all refs found in `$GIT_DIR/refs`. + Show all refs found in `refs/`. --branches[=pattern]:: --tags[=pattern]:: --remotes[=pattern]:: Show all branches, tags, or remote-tracking branches, - respectively (i.e., refs found in `$GIT_DIR/refs/heads`, - `$GIT_DIR/refs/tags`, or `$GIT_DIR/refs/remotes`, - respectively). + respectively (i.e., refs found in `refs/heads`, + `refs/tags`, or `refs/remotes`, respectively). + If a `pattern` is given, only refs matching the given shell glob are shown. If the pattern does not contain a globbing character (`?`, @@ -189,7 +188,7 @@ blobs contained in a commit. `g`, and an abbreviated object name. * A symbolic ref name. E.g. 'master' typically means the commit - object referenced by $GIT_DIR/refs/heads/master. If you + object referenced by refs/heads/master. If you happen to have both heads/master and tags/master, you can explicitly say 'heads/master' to tell git which one you mean. When ambiguous, a `` is disambiguated by taking the @@ -198,15 +197,15 @@ blobs contained in a commit. . if `$GIT_DIR/` exists, that is what you mean (this is usually useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD` and `MERGE_HEAD`); - . otherwise, `$GIT_DIR/refs/` if exists; + . otherwise, `refs/` if exists; - . otherwise, `$GIT_DIR/refs/tags/` if exists; + . otherwise, `refs/tags/` if exists; - . otherwise, `$GIT_DIR/refs/heads/` if exists; + . otherwise, `refs/heads/` if exists; - . otherwise, `$GIT_DIR/refs/remotes/` if exists; + . otherwise, `refs/remotes/` if exists; - . otherwise, `$GIT_DIR/refs/remotes//HEAD` if exists. + . otherwise, `refs/remotes//HEAD` if exists. + HEAD names the commit your changes in the working tree is based on. FETCH_HEAD records the branch you fetched from a remote repository @@ -217,6 +216,9 @@ you can change the tip of the branch back to the state before you ran them easily. MERGE_HEAD records the commit(s) you are merging into your branch when you run 'git merge'. ++ +Note that any of the `refs/*` cases above may come either from +the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file. * A ref followed by the suffix '@' with a date specification enclosed in a brace diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 734336119c..b9c4154e73 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -20,8 +20,8 @@ DESCRIPTION ----------- Shows the commit ancestry graph starting from the commits named -with s or s (or all refs under $GIT_DIR/refs/heads -and/or $GIT_DIR/refs/tags) semi-visually. +with s or s (or all refs under refs/heads +and/or refs/tags) semi-visually. It cannot show more than 29 branches and commits at a time. @@ -37,8 +37,8 @@ OPTIONS :: A glob pattern that matches branch or tag names under - $GIT_DIR/refs. For example, if you have many topic - branches under $GIT_DIR/refs/heads/topic, giving + refs/. For example, if you have many topic + branches under refs/heads/topic, giving `topic/*` would show all of them. -r:: @@ -176,7 +176,7 @@ EXAMPLE ------- If you keep your primary branches immediately under -`$GIT_DIR/refs/heads`, and topic branches in subdirectories of +`refs/heads`, and topic branches in subdirectories of it, having the following in the configuration file may help: ------------ diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 84e555d81d..473889a660 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -33,7 +33,7 @@ A stash is by default listed as "WIP on 'branchname' ...", but you can give a more descriptive message on the command line when you create one. -The latest stash you created is stored in `$GIT_DIR/refs/stash`; older +The latest stash you created is stored in `refs/stash`; older stashes are found in the reflog of this reference and can be named using the usual reflog syntax (e.g. `stash@\{0}` is the most recently created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}` diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 6e9baf8b38..81c0e6f184 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -225,26 +225,26 @@ endif::git-rev-list[] --all:: - Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the + Pretend as if all the refs in `refs/` are listed on the command line as ''. --branches[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/heads` are listed + Pretend as if all the refs in `refs/heads` are listed on the command line as ''. If `pattern` is given, limit branches to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. --tags[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/tags` are listed + Pretend as if all the refs in `refs/tags` are listed on the command line as ''. If `pattern` is given, limit tags to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. --remotes[=pattern]:: - Pretend as if all the refs in `$GIT_DIR/refs/remotes` are listed + Pretend as if all the refs in `refs/remotes` are listed on the command line as ''. If `pattern`is given, limit remote tracking branches to ones matching given shell glob. If pattern lacks '?', '*', or '[', '/*' at the end is implied. @@ -259,9 +259,9 @@ endif::git-rev-list[] ifndef::git-rev-list[] --bisect:: - Pretend as if the bad bisection ref `$GIT_DIR/refs/bisect/bad` + Pretend as if the bad bisection ref `refs/bisect/bad` was listed and as if it was followed by `--not` and the good - bisection refs `$GIT_DIR/refs/bisect/good-*` on the command + bisection refs `refs/bisect/good-*` on the command line. endif::git-rev-list[] @@ -561,10 +561,10 @@ Bisection Helpers Limit output to the one commit object which is roughly halfway between included and excluded commits. Note that the bad bisection ref -`$GIT_DIR/refs/bisect/bad` is added to the included commits (if it -exists) and the good bisection refs `$GIT_DIR/refs/bisect/good-*` are +`refs/bisect/bad` is added to the included commits (if it +exists) and the good bisection refs `refs/bisect/good-*` are added to the excluded commits (if they exist). Thus, supposing there -are no refs in `$GIT_DIR/refs/bisect/`, if +are no refs in `refs/bisect/`, if ----------------------------------------------------------------------- $ git rev-list --bisect foo ^bar ^baz @@ -585,7 +585,7 @@ one. --bisect-vars:: This calculates the same as `--bisect`, except that refs in -`$GIT_DIR/refs/bisect/` are not used, and except that this outputs +`refs/bisect/` are not used, and except that this outputs text ready to be eval'ed by the shell. These lines will assign the name of the midpoint revision to the variable `bisect_rev`, and the expected number of commits to be tested after `bisect_rev` is tested @@ -599,7 +599,7 @@ number of commits to be tested if `bisect_rev` turns out to be bad to This outputs all the commit objects between the included and excluded commits, ordered by their distance to the included and excluded -commits. Refs in `$GIT_DIR/refs/bisect/` are not used. The farthest +commits. Refs in `refs/bisect/` are not used. The farthest from them is displayed first. (This is the only one displayed by `--bisect`.) + From 738820a913d05427b6c86d227aafd2bac7cd38d1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 18 Feb 2010 01:10:28 -0800 Subject: [PATCH 0127/3211] Documentation: describe --thin more accurately The description for --thin was misleading and downright wrong. Correct it with some inspiration from the description of index-pack's --fix-thin and some background information from Nicolas Pitre . Signed-off-by: Stephen Boyd Signed-off-by: Junio C Hamano --- Documentation/git-fetch-pack.txt | 4 ++-- Documentation/git-index-pack.txt | 12 ++++------ Documentation/git-pack-objects.txt | 35 ++++++++++++++++++++---------- Documentation/git-push.txt | 7 +++--- Documentation/git-send-pack.txt | 4 ++-- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 97ea7973a0..4a8487c154 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -44,8 +44,8 @@ OPTIONS locked against repacking. --thin:: - Spend extra cycles to minimize the number of objects to be sent. - Use it on slower connection. + Fetch a "thin" pack, which records objects in deltified form based + on objects not included in the pack to reduce network traffic. --include-tag:: If the remote side supports it, annotated tags objects will diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index 65a301bece..f3ccc72f0d 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -46,14 +46,10 @@ OPTIONS 'git repack'. --fix-thin:: - It is possible for 'git pack-objects' to build - "thin" pack, which records objects in deltified form based on - objects not included in the pack to reduce network traffic. - Those objects are expected to be present on the receiving end - and they must be included in the pack for that pack to be self - contained and indexable. Without this option any attempt to - index a thin pack will fail. This option only makes sense in - conjunction with --stdin. + Fix a "thin" pack produced by `git pack-objects --thin` (see + linkgit:git-pack-objects[1] for details) by adding the + excluded objects the deltified objects are based on to the + pack. This option only makes sense in conjunction with --stdin. --keep:: Before moving the index into its final destination diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 61fd7d0998..034caedc39 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -21,16 +21,21 @@ DESCRIPTION Reads list of objects from the standard input, and writes a packed archive with specified base-name, or to the standard output. -A packed archive is an efficient way to transfer set of objects -between two repositories, and also is an archival format which -is efficient to access. The packed archive format (.pack) is -designed to be self contained so that it can be unpacked without -any further information, but for fast, random access to the objects -in the pack, a pack index file (.idx) will be generated. +A packed archive is an efficient way to transfer a set of objects +between two repositories as well as an access efficient archival +format. In a packed archive, an object is either stored as a +compressed whole or as a difference from some other object. +The latter is often called a delta. -Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or +The packed archive format (.pack) is designed to be self-contained +so that it can be unpacked without any further information. Therefore, +each object that a delta depends upon must be present within the pack. + +A pack index file (.idx) is generated for fast, random access to the +objects in the pack. Placing both the index file (.idx) and the packed +archive (.pack) in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES) -enables git to read from such an archive. +enables git to read from the pack archive. The 'git unpack-objects' command can read the packed archive and expand the objects contained in the pack into "one-file @@ -38,10 +43,6 @@ one-object" format; this is typically done by the smart-pull commands when a pack is created on-the-fly for efficient network transport by their peers. -In a packed archive, an object is either stored as a compressed -whole, or as a difference from some other object. The latter is -often called a delta. - OPTIONS ------- @@ -179,6 +180,16 @@ base-name:: Add --no-reuse-object if you want to force a uniform compression level on all data no matter the source. +--thin:: + Create a "thin" pack by omitting the common objects between a + sender and a receiver in order to reduce network transfer. This + option only makes sense in conjunction with --stdout. ++ +Note: A thin pack violates the packed archive format by omitting +required objects and is thus unusable by git without making it +self-contained. Use `git index-pack --fix-thin` +(see linkgit:git-index-pack[1]) to restore the self-contained property. + --delta-base-offset:: A packed archive can express base object of a delta as either 20-byte object name or as an offset in the diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 3f103ccb00..49b6bd9d92 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -141,9 +141,10 @@ useful if you write an alias or script around 'git push'. --thin:: --no-thin:: - These options are passed to 'git send-pack'. Thin - transfer spends extra cycles to minimize the number of - objects to be sent and meant to be used on slower connection. + These options are passed to linkgit:git-send-pack[1]. A thin transfer + significantly reduces the amount of sent data when the sender and + receiver share many of the same objects in common. The default is + \--thin. -v:: --verbose:: diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 8178d92642..deaa7d9654 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -48,8 +48,8 @@ OPTIONS Run verbosely. --thin:: - Spend extra cycles to minimize the number of objects to be sent. - Use it on slower connection. + Send a "thin" pack, which records objects in deltified form based + on objects not included in the pack to reduce network traffic. :: A remote host to house the repository. When this From 73e9da019655261e456ed862340880de365111f0 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Tue, 16 Feb 2010 23:55:58 -0500 Subject: [PATCH 0128/3211] Add an optional argument for --color options Make git-branch, git-show-branch, git-grep, and all the diff-based programs accept an optional argument for --color. The argument is a colorbool: "always", "never", or "auto". If no argument is given, "always" is used; --no-color is an alias for --color=never. This makes the command-line interface consistent with other GNU tools, such as `ls' and `grep', and with the git-config color options. Note that, without an argument, --color and --no-color work exactly as before. To implement this, two internal changes were made: 1. Allow the first argument of git_config_colorbool() to be NULL, in which case it returns -1 if the argument isn't "always", "never", or "auto". 2. Add OPT_COLOR_FLAG(), OPT__COLOR(), and parse_opt_color_flag_cb() to the option parsing library. The callback uses git_config_colorbool(), so color.h is now a dependency of parse-options.c. Signed-off-by: Mark Lodato Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 4 +++- Documentation/git-branch.txt | 6 ++++-- Documentation/git-grep.txt | 6 ++++-- Documentation/git-show-branch.txt | 6 ++++-- Documentation/technical/api-parse-options.txt | 12 ++++++++++++ builtin-branch.c | 2 +- builtin-grep.c | 2 +- builtin-show-branch.c | 4 ++-- color.c | 3 +++ diff.c | 9 +++++++++ parse-options.c | 16 ++++++++++++++++ parse-options.h | 7 +++++++ 12 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8707d0e740..60e922e6ef 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -117,12 +117,14 @@ any of those replacements occurred. option and lists the commits in that commit range like the 'summary' option of linkgit:git-submodule[1] does. ---color:: +--color[=]:: Show colored diff. + The value must be always (the default), never, or auto. --no-color:: Turn off colored diff, even when the configuration file gives the default to color output. + Same as `--color=never`. --color-words[=]:: Show colored word diff, i.e., color words which have changed. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 6b6c3da2d9..903a690f10 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches SYNOPSIS -------- [verse] -'git branch' [--color | --no-color] [-r | -a] +'git branch' [--color[=] | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]] [(--merged | --no-merged | --contains) []] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] @@ -84,12 +84,14 @@ OPTIONS -M:: Move/rename a branch even if the new branch name already exists. ---color:: +--color[=]:: Color branches to highlight current, local, and remote branches. + The value must be always (the default), never, or auto. --no-color:: Turn off branch colors, even when the configuration file gives the default to color output. + Same as `--color=never`. -r:: List or delete (if used with -d) the remote-tracking branches. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index e019e760b4..70c7ef95f6 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -18,7 +18,7 @@ SYNOPSIS [-z | --null] [-c | --count] [--all-match] [-q | --quiet] [--max-depth ] - [--color | --no-color] + [--color[=] | --no-color] [-A ] [-B ] [-C ] [-f ] [-e] [--and|--or|--not|(|)|-e ...] [...] @@ -111,12 +111,14 @@ OPTIONS Instead of showing every matched line, show the number of lines that match. ---color:: +--color[=]:: Show colored matches. + The value must be always (the default), never, or auto. --no-color:: Turn off match highlighting, even when the configuration file gives the default to color output. + Same as `--color=never`. -[ABC] :: Show `context` trailing (`A` -- after), or leading (`B` diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 734336119c..519f9e1dd7 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order] - [--current] [--color | --no-color] [--sparse] + [--current] [--color[=] | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]... @@ -117,13 +117,15 @@ OPTIONS When no explicit parameter is given, it defaults to the current branch (or `HEAD` if it is detached). ---color:: +--color[=]:: Color the status sign (one of these: `*` `!` `+` `-`) of each commit corresponding to the branch it's in. + The value must be always (the default), never, or auto. --no-color:: Turn off colored output, even when the configuration file gives the default to color output. + Same as `--color=never`. Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 50f9e9ac17..312e3b2e2b 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -115,6 +115,9 @@ There are some macros to easily define options: `OPT__ABBREV(&int_var)`:: Add `\--abbrev[=]`. +`OPT__COLOR(&int_var, description)`:: + Add `\--color[=]` and `--no-color`. + `OPT__DRY_RUN(&int_var)`:: Add `-n, \--dry-run`. @@ -183,6 +186,15 @@ There are some macros to easily define options: arguments. Short options that happen to be digits take precedence over it. +`OPT_COLOR_FLAG(short, long, &int_var, description)`:: + Introduce an option that takes an optional argument that can + have one of three values: "always", "never", or "auto". If the + argument is not given, it defaults to "always". The `--no-` form + works like `--long=never`; it cannot take an argument. If + "always", set `int_var` to 1; if "never", set `int_var` to 0; if + "auto", set `int_var` to 1 if stdout is a tty or a pager, + 0 otherwise. + The last element of the array must be `OPT_END()`. diff --git a/builtin-branch.c b/builtin-branch.c index a28a13986d..6cf7e721e6 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -610,7 +610,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, "change upstream info", BRANCH_TRACK_OVERRIDE), - OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), + OPT__COLOR(&branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), { diff --git a/builtin-grep.c b/builtin-grep.c index 26d4deb1cc..00cbd90bf6 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -782,7 +782,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) "print NUL after filenames"), OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), - OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1), + OPT__COLOR(&opt.color, "highlight matches"), OPT_GROUP(""), OPT_CALLBACK('C', NULL, &opt, "n", "show context lines before and after matches", diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 9f13caa76d..32d862ab23 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -6,7 +6,7 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]...", + "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color[=] | --no-color] [--sparse] [--more= | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [ | ]...", "git show-branch (-g|--reflog)[=[,]] [--list] []", NULL }; @@ -661,7 +661,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) "show remote-tracking and local branches"), OPT_BOOLEAN('r', "remotes", &all_remotes, "show remote-tracking branches"), - OPT_BOOLEAN(0, "color", &showbranch_use_color, + OPT__COLOR(&showbranch_use_color, "color '*!+-' corresponding to the branch"), { OPTION_INTEGER, 0, "more", &extra, "n", "show more commits after the common ancestor", diff --git a/color.c b/color.c index 62977f4808..8f07fc9547 100644 --- a/color.c +++ b/color.c @@ -138,6 +138,9 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) goto auto_color; } + if (!var) + return -1; + /* Missing or explicit false to turn off colorization */ if (!git_config_bool(var, value)) return 0; diff --git a/diff.c b/diff.c index 381cc8d4fd..8f645f6f8d 100644 --- a/diff.c +++ b/diff.c @@ -2826,6 +2826,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, FOLLOW_RENAMES); else if (!strcmp(arg, "--color")) DIFF_OPT_SET(options, COLOR_DIFF); + else if (!prefixcmp(arg, "--color=")) { + int value = git_config_colorbool(NULL, arg+8, -1); + if (value == 0) + DIFF_OPT_CLR(options, COLOR_DIFF); + else if (value > 0) + DIFF_OPT_SET(options, COLOR_DIFF); + else + return error("option `color' expects \"always\", \"auto\", or \"never\""); + } else if (!strcmp(arg, "--no-color")) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) { diff --git a/parse-options.c b/parse-options.c index d218122af5..c83035d013 100644 --- a/parse-options.c +++ b/parse-options.c @@ -2,6 +2,7 @@ #include "parse-options.h" #include "cache.h" #include "commit.h" +#include "color.h" static int parse_options_usage(const char * const *usagestr, const struct option *opts); @@ -599,6 +600,21 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_color_flag_cb(const struct option *opt, const char *arg, + int unset) +{ + int value; + + if (!arg) + arg = unset ? "never" : (const char *)opt->defval; + value = git_config_colorbool(NULL, arg, -1); + if (value < 0) + return opterror(opt, + "expects \"always\", \"auto\", or \"never\"", 0); + *(int *)opt->value = value; + return 0; +} + int parse_opt_verbosity_cb(const struct option *opt, const char *arg, int unset) { diff --git a/parse-options.h b/parse-options.h index 0c996916b6..9429f7e361 100644 --- a/parse-options.h +++ b/parse-options.h @@ -135,6 +135,10 @@ struct option { PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } #define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ "FILE", (h) } +#define OPT_COLOR_FLAG(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \ + parse_opt_color_flag_cb, (intptr_t)"always" } + /* parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. @@ -187,6 +191,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); +extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); @@ -203,5 +208,7 @@ extern int parse_opt_tertiary(const struct option *, const char *, int); { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ "use digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } +#define OPT__COLOR(var, h) \ + OPT_COLOR_FLAG(0, "color", (var), (h)) #endif From 3fc0d131c573f6f774e2e4abba9cbda694b08321 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 19 Feb 2010 00:57:21 -0500 Subject: [PATCH 0129/3211] rm: fix bug in recursive subdirectory removal If we remove a path in a/deep/subdirectory, we should try to remove as many trailing components as possible (i.e., subdirectory, then deep, then a). However, the test for the return value of rmdir was reversed, so we only ever deleted at most one level. The fix is in remove_path, so "apply" and "merge-recursive" also are fixed. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- dir.c | 2 +- t/t3600-rm.sh | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index 6aae09a22e..fdc0a2ede1 100644 --- a/dir.c +++ b/dir.c @@ -864,7 +864,7 @@ int remove_path(const char *name) slash = dirs + (slash - name); do { *slash = '\0'; - } while (rmdir(dirs) && (slash = strrchr(dirs, '/'))); + } while (rmdir(dirs) == 0 && (slash = strrchr(dirs, '/'))); free(dirs); } return 0; diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 76b1bb4545..0aaf0ad84b 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -271,4 +271,12 @@ test_expect_success 'choking "git rm" should not let it die with cruft' ' test "$status" != 0 ' +test_expect_success 'rm removes subdirectories recursively' ' + mkdir -p dir/subdir/subsubdir && + echo content >dir/subdir/subsubdir/file && + git add dir/subdir/subsubdir/file && + git rm -f dir/subdir/subsubdir/file && + ! test -d dir +' + test_done From e9e921981d10554a325f4a1e67e920947e0e4800 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 15 Feb 2010 04:33:06 -0800 Subject: [PATCH 0130/3211] Documentation: Fix indentation problem in git-commit(1) Ever since the "See linkgit:git-config[1]..." paragraph was added to the description for --untracked-files (d6293d1), the paragraphs for the following options were indented at the same level as the "See linkgit:git-config[1]" paragraph. This problem showed up in the manpages, but not in the HTML documentation. While this does fix the alignment of the options following --untracked-files in the manpage, the "See linkgit..." portion of the description does not retain its previous indentation level in the manpages, or HTML documentation. Signed-off-by: Jacob Helwig Acked-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index e99bb14754..64fb458b45 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -197,13 +197,13 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) Show untracked files (Default: 'all'). + The mode parameter is optional, and is used to specify -the handling of untracked files. The possible options are: +the handling of untracked files. ++ +The possible options are: + --- - 'no' - Show no untracked files - 'normal' - Shows untracked files and directories - 'all' - Also shows individual files in untracked directories. --- + See linkgit:git-config[1] for configuration variable used to change the default for when the option is not From 470b452628d80e89ee869b6b86af6c536ba0b24d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 19 Feb 2010 21:55:33 -0800 Subject: [PATCH 0131/3211] mailinfo: do not strip leading spaces even for a header line Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 3 +-- t/t5100/msg0015 | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index a50ac2256c..ce2ef6bede 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -779,8 +779,7 @@ static int handle_commit_msg(struct strbuf *line) return 0; if (still_looking) { - strbuf_ltrim(line); - if (!line->len) + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) return 0; } diff --git a/t/t5100/msg0015 b/t/t5100/msg0015 index 9577238685..4abb3d5c6c 100644 --- a/t/t5100/msg0015 +++ b/t/t5100/msg0015 @@ -1,2 +1,2 @@ -- a list + - a list - of stuff From 4551d03541e5eec411bb367f7967ff933d176df4 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 20 Feb 2010 01:18:44 +0100 Subject: [PATCH 0132/3211] t1450: fix testcases that were wrongly expecting failure Almost exactly a year ago in 02a6552 (Test fsck a bit harder), I introduced two testcases that were expecting failure. However, the only bug was that the testcases wrote *blobs* because I forgot to pass -t tag to hash-object. Fix this, and then adjust the rest of the test to properly check the result. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- t/t1450-fsck.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index a22632f483..49cae3ed52 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -66,12 +66,12 @@ tagger T A Gger 1234567890 -0000 This is an invalid tag. EOF -test_expect_failure 'tag pointing to nonexistent' ' - tag=$(git hash-object -w --stdin < invalid-tag) && +test_expect_success 'tag pointing to nonexistent' ' + tag=$(git hash-object -t tag -w --stdin < invalid-tag) && echo $tag > .git/refs/tags/invalid && - git fsck --tags 2>out && + test_must_fail git fsck --tags >out && cat out && - grep "could not load tagged object" out && + grep "broken link" out && rm .git/refs/tags/invalid ' @@ -84,12 +84,12 @@ tagger T A Gger 1234567890 -0000 This is an invalid tag. EOF -test_expect_failure 'tag pointing to something else than its type' ' - tag=$(git hash-object -w --stdin < wrong-tag) && +test_expect_success 'tag pointing to something else than its type' ' + tag=$(git hash-object -t tag -w --stdin < wrong-tag) && echo $tag > .git/refs/tags/wrong && - git fsck --tags 2>out && + test_must_fail git fsck --tags 2>out && cat out && - grep "some sane error message" out && + grep "error in tag.*broken links" out && rm .git/refs/tags/wrong ' From b39c3612eb443e77bd04d645578e1155988c6dde Mon Sep 17 00:00:00 2001 From: Evan Powers Date: Tue, 16 Feb 2010 00:44:08 -0800 Subject: [PATCH 0133/3211] git-p4: fix bug in symlink handling Fix inadvertent breakage from b932705 (git-p4: stream from perforce to speed up clones, 2009-07-30) in the code that strips the trailing '\n' from p4 print on a symlink. (In practice, contents is of the form ['target\n', ''].) Signed-off-by: Evan Powers Acked-by: Pete Wyckoff Signed-off-by: Junio C Hamano --- contrib/fast-import/git-p4 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index e7c48144e6..cd96c6f81f 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -967,9 +967,8 @@ class P4Sync(Command): elif file["type"] == "symlink": mode = "120000" # p4 print on a symlink contains "target\n", so strip it off - last = contents.pop() - last = last[:-1] - contents.append(last) + data = ''.join(contents) + contents = [data[:-1]] if self.isWindows and file["type"].endswith("text"): mangled = [] From bb96a2c9005f925b4e80ece0a7cd6230f7f4b43d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Fri, 19 Feb 2010 23:15:01 +0100 Subject: [PATCH 0134/3211] utf8.c: remove print_wrapped_text() strbuf_add_wrapped_text() is called only from print_wrapped_text() without a strbuf (in which case it writes its results to stdout). At its only callsite, supply a strbuf, call strbuf_add_wrapped_text() directly and remove the wrapper function. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- builtin-shortlog.c | 17 ++++++++++++++--- utf8.c | 5 ----- utf8.h | 1 - 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 8aa63c7857..d96858f9ad 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -301,9 +301,19 @@ parse_done: return 0; } +static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s, + const struct shortlog *log) +{ + int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap); + if (col != log->wrap) + strbuf_addch(sb, '\n'); +} + void shortlog_output(struct shortlog *log) { int i, j; + struct strbuf sb = STRBUF_INIT; + if (log->sort_by_number) qsort(log->list.items, log->list.nr, sizeof(struct string_list_item), compare_by_number); @@ -318,9 +328,9 @@ void shortlog_output(struct shortlog *log) const char *msg = onelines->items[j].string; if (log->wrap_lines) { - int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap); - if (col != log->wrap) - putchar('\n'); + strbuf_reset(&sb); + add_wrapped_shortlog_msg(&sb, msg, log); + fwrite(sb.buf, sb.len, 1, stdout); } else printf(" %s\n", msg); @@ -334,6 +344,7 @@ void shortlog_output(struct shortlog *log) log->list.items[i].util = NULL; } + strbuf_release(&sb); log->list.strdup_strings = 1; string_list_clear(&log->list, 1); clear_mailmap(&log->mailmap); diff --git a/utf8.c b/utf8.c index 7ddff23fa7..5c8a2697f3 100644 --- a/utf8.c +++ b/utf8.c @@ -405,11 +405,6 @@ new_line: } } -int print_wrapped_text(const char *text, int indent, int indent2, int width) -{ - return strbuf_add_wrapped_text(NULL, text, indent, indent2, width); -} - int is_encoding_utf8(const char *name) { if (!name) diff --git a/utf8.h b/utf8.h index ae30ae4c6e..b09687d500 100644 --- a/utf8.h +++ b/utf8.h @@ -9,7 +9,6 @@ int utf8_strwidth(const char *string); int is_utf8(const char *text); int is_encoding_utf8(const char *name); -int print_wrapped_text(const char *text, int indent, int indent2, int len); int strbuf_add_wrapped_text(struct strbuf *buf, const char *text, int indent, int indent2, int width); From 3c0ff44a1ee92bd0f811b95d747a08763983566b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Fri, 19 Feb 2010 23:15:55 +0100 Subject: [PATCH 0135/3211] utf8.c: remove print_spaces() The previous patch made sure that strbuf_add_wrapped_text() (and thus strbuf_add_indented_text(), too) always get a strbuf. Make use of this fact by adding strbuf_addchars(), a small helper that adds a char the specified number of times to a strbuf, and use it to replace print_spaces(). Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- utf8.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/utf8.c b/utf8.c index 5c8a2697f3..a4e36ff33c 100644 --- a/utf8.c +++ b/utf8.c @@ -288,14 +288,11 @@ static inline void strbuf_write(struct strbuf *sb, const char *buf, int len) fwrite(buf, len, 1, stdout); } -static void print_spaces(struct strbuf *buf, int count) +static void strbuf_addchars(struct strbuf *sb, int c, size_t n) { - static const char s[] = " "; - while (count >= sizeof(s)) { - strbuf_write(buf, s, sizeof(s) - 1); - count -= sizeof(s) - 1; - } - strbuf_write(buf, s, count); + strbuf_grow(sb, n); + memset(sb->buf + sb->len, c, n); + strbuf_setlen(sb, sb->len + n); } static void strbuf_add_indented_text(struct strbuf *buf, const char *text, @@ -307,7 +304,7 @@ static void strbuf_add_indented_text(struct strbuf *buf, const char *text, const char *eol = strchrnul(text, '\n'); if (*eol == '\n') eol++; - print_spaces(buf, indent); + strbuf_addchars(buf, ' ', indent); strbuf_write(buf, text, eol - text); text = eol; indent = indent2; @@ -366,7 +363,7 @@ int strbuf_add_wrapped_text(struct strbuf *buf, if (space) start = space; else - print_spaces(buf, indent); + strbuf_addchars(buf, ' ', indent); strbuf_write(buf, start, text - start); if (!c) return w; From 68ad5e1e9c10e8a640703aadbdf8b8366014373b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Fri, 19 Feb 2010 23:16:45 +0100 Subject: [PATCH 0136/3211] utf8.c: remove strbuf_write() The patch before the previous one made sure that all callers of strbuf_add_wrapped_text() supply a strbuf. Replace all calls of strbuf_write() with regular strbuf functions and remove it. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- utf8.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/utf8.c b/utf8.c index a4e36ff33c..9f64f59d66 100644 --- a/utf8.c +++ b/utf8.c @@ -280,14 +280,6 @@ int is_utf8(const char *text) return 1; } -static inline void strbuf_write(struct strbuf *sb, const char *buf, int len) -{ - if (sb) - strbuf_insert(sb, sb->len, buf, len); - else - fwrite(buf, len, 1, stdout); -} - static void strbuf_addchars(struct strbuf *sb, int c, size_t n) { strbuf_grow(sb, n); @@ -305,7 +297,7 @@ static void strbuf_add_indented_text(struct strbuf *buf, const char *text, if (*eol == '\n') eol++; strbuf_addchars(buf, ' ', indent); - strbuf_write(buf, text, eol - text); + strbuf_add(buf, text, eol - text); text = eol; indent = indent2; } @@ -364,7 +356,7 @@ int strbuf_add_wrapped_text(struct strbuf *buf, start = space; else strbuf_addchars(buf, ' ', indent); - strbuf_write(buf, start, text - start); + strbuf_add(buf, start, text - start); if (!c) return w; space = text; @@ -373,20 +365,20 @@ int strbuf_add_wrapped_text(struct strbuf *buf, else if (c == '\n') { space++; if (*space == '\n') { - strbuf_write(buf, "\n", 1); + strbuf_addch(buf, '\n'); goto new_line; } else if (!isalnum(*space)) goto new_line; else - strbuf_write(buf, " ", 1); + strbuf_addch(buf, ' '); } w++; text++; } else { new_line: - strbuf_write(buf, "\n", 1); + strbuf_addch(buf, '\n'); text = bol = space + isspace(*space); space = NULL; w = indent = indent2; From 462749b728f72079a67202d4d0d1ef19ef993f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Fri, 19 Feb 2010 23:20:44 +0100 Subject: [PATCH 0137/3211] utf8.c: speculatively assume utf-8 in strbuf_add_wrapped_text() is_utf8() works by calling utf8_width() for each character at the supplied location. In strbuf_add_wrapped_text(), we do that anyway while wrapping the lines. So instead of checking the encoding beforehand, optimistically assume that it's utf-8 and wrap along until an invalid character is hit, and when that happens start over. This pays off if the text consists only of valid utf-8 characters. The following command was run against the Linux kernel repo with git 1.7.0: $ time git log --format='%b' v2.6.32 >/dev/null real 0m2.679s user 0m2.580s sys 0m0.100s $ time git log --format='%w(60,4,8)%b' >/dev/null real 0m4.342s user 0m4.230s sys 0m0.110s And with this patch series: $ time git log --format='%w(60,4,8)%b' >/dev/null real 0m3.741s user 0m3.630s sys 0m0.110s So the cost of wrapping is reduced to 70% in this case. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano --- utf8.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/utf8.c b/utf8.c index 9f64f59d66..6db9cd9a07 100644 --- a/utf8.c +++ b/utf8.c @@ -324,16 +324,21 @@ static size_t display_mode_esc_sequence_len(const char *s) * consumed (and no extra indent is necessary for the first line). */ int strbuf_add_wrapped_text(struct strbuf *buf, - const char *text, int indent, int indent2, int width) + const char *text, int indent1, int indent2, int width) { - int w = indent, assume_utf8 = is_utf8(text); - const char *bol = text, *space = NULL; + int indent, w, assume_utf8 = 1; + const char *bol, *space, *start = text; + size_t orig_len = buf->len; if (width <= 0) { - strbuf_add_indented_text(buf, text, indent, indent2); + strbuf_add_indented_text(buf, text, indent1, indent2); return 1; } +retry: + bol = text; + w = indent = indent1; + space = NULL; if (indent < 0) { w = -indent; space = text; @@ -385,9 +390,15 @@ new_line: } continue; } - if (assume_utf8) + if (assume_utf8) { w += utf8_width(&text, NULL); - else { + if (!text) { + assume_utf8 = 0; + text = start; + strbuf_setlen(buf, orig_len); + goto retry; + } + } else { w++; text++; } From 21da42621489df7824f1fce7180702a9aa540707 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Thu, 18 Feb 2010 22:29:30 -0500 Subject: [PATCH 0138/3211] Documentation: pack-objects: Clarify --local's semantics. The current documentation suggests that --local also ignores any objects in local packs, which is incorrect. Change the language to be clearer and more parallel to the other options that ignore objects. While we're at it, fix a trivial error in --incremental's documentation. Signed-off-by: Nelson Elhage Signed-off-by: Junio C Hamano --- Documentation/git-pack-objects.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 034caedc39..769f1de6b6 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -119,14 +119,13 @@ base-name:: standard input. --incremental:: - This flag causes an object already in a pack ignored + This flag causes an object already in a pack to be ignored even if it appears in the standard input. --local:: - This flag is similar to `--incremental`; instead of - ignoring all packed objects, it only ignores objects - that are packed and/or not in the local object store - (i.e. borrowed from an alternate). + This flag causes an object that is borrowed from an alternate + object store to be ignored even if it appears in the standard + input. --non-empty:: Only create a packed archive if it would contain at From 60b6e2200deff208a9757721544a3a311034804f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 19 Feb 2010 01:18:58 -0600 Subject: [PATCH 0139/3211] tests: Add tests for automatic use of pager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Git’s automatic pagination support has some subtleties. Add some tests to make sure we don’t break: - when git will use a pager by default; - the effect of the --paginate and --no-pager options; - the effect of pagination on use of color; - how the choice of pager is configured. This does not yet test: - use of pager by scripted commands (git svn and git am); - effect of the pager.* configuration variables; - setting of the LESS variable. Some features involve checking whether stdout is a terminal, so many of these tests are skipped unless output is passed through to the terminal (i.e., unless $GIT_TEST_OPTS includes --verbose). The immediate purpose for these tests was to avoid making things worse after the breakage from my jn/editor-pager series (see commit 376f39, 2009-11-20). Thanks to Sebastian Celis for the report. Helped-by: Jeff King Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- t/t7006-pager.sh | 163 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 t/t7006-pager.sh diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh new file mode 100755 index 0000000000..4f52ea5732 --- /dev/null +++ b/t/t7006-pager.sh @@ -0,0 +1,163 @@ +#!/bin/sh + +test_description='Test automatic use of a pager.' + +. ./test-lib.sh + +rm -f stdout_is_tty +test_expect_success 'is stdout a terminal?' ' + if test -t 1 + then + : > stdout_is_tty + fi +' + +if test -e stdout_is_tty +then + test_set_prereq TTY +else + say stdout is not a terminal, so skipping some tests. +fi + +unset GIT_PAGER GIT_PAGER_IN_USE +git config --unset core.pager +PAGER='cat > paginated.out' +export PAGER + +test_expect_success 'setup' ' + test_commit initial +' + +rm -f paginated.out +test_expect_success TTY 'some commands use a pager' ' + git log && + test -e paginated.out +' + +rm -f paginated.out +test_expect_success TTY 'some commands do not use a pager' ' + git rev-list HEAD && + ! test -e paginated.out +' + +rm -f paginated.out +test_expect_success 'no pager when stdout is a pipe' ' + git log | cat && + ! test -e paginated.out +' + +rm -f paginated.out +test_expect_success 'no pager when stdout is a regular file' ' + git log > file && + ! test -e paginated.out +' + +rm -f paginated.out +test_expect_success TTY 'git --paginate rev-list uses a pager' ' + git --paginate rev-list HEAD && + test -e paginated.out +' + +rm -f file paginated.out +test_expect_success 'no pager even with --paginate when stdout is a pipe' ' + git --paginate log | cat && + ! test -e paginated.out +' + +rm -f paginated.out +test_expect_success TTY 'no pager with --no-pager' ' + git --no-pager log && + ! test -e paginated.out +' + +# A colored commit log will begin with an appropriate ANSI escape +# for the first color; the text "commit" comes later. +colorful() { + read firstline < $1 + ! expr "$firstline" : "^[a-zA-Z]" >/dev/null +} + +rm -f colorful.log colorless.log +test_expect_success 'tests can detect color' ' + git log --no-color > colorless.log && + git log --color > colorful.log && + ! colorful colorless.log && + colorful colorful.log +' + +rm -f colorless.log +git config color.ui auto +test_expect_success 'no color when stdout is a regular file' ' + git log > colorless.log && + ! colorful colorless.log +' + +rm -f paginated.out +git config color.ui auto +test_expect_success TTY 'color when writing to a pager' ' + TERM=vt100 git log && + colorful paginated.out +' + +rm -f colorful.log +git config color.ui auto +test_expect_success 'color when writing to a file intended for a pager' ' + TERM=vt100 GIT_PAGER_IN_USE=true git log > colorful.log && + colorful colorful.log +' + +unset PAGER GIT_PAGER +git config --unset core.pager +test_expect_success 'determine default pager' ' + less=$(git var GIT_PAGER) && + test -n "$less" +' + +if expr "$less" : '^[a-z]*$' > /dev/null && test_have_prereq TTY +then + test_set_prereq SIMPLEPAGER +fi + +unset PAGER GIT_PAGER +git config --unset core.pager +rm -f default_pager_used +test_expect_success SIMPLEPAGER 'default pager is used by default' ' + cat > $less <<-EOF && + #!$SHELL_PATH + : > default_pager_used + EOF + chmod +x $less && + PATH=.:$PATH git log && + test -e default_pager_used +' + +unset GIT_PAGER +git config --unset core.pager +rm -f PAGER_used +test_expect_success TTY 'PAGER overrides default pager' ' + PAGER=": > PAGER_used" && + export PAGER && + git log && + test -e PAGER_used +' + +unset GIT_PAGER +rm -f core.pager_used +test_expect_success TTY 'core.pager overrides PAGER' ' + PAGER=: && + export PAGER && + git config core.pager ": > core.pager_used" && + git log && + test -e core.pager_used +' + +rm -f GIT_PAGER_used +test_expect_success TTY 'GIT_PAGER overrides core.pager' ' + git config core.pager : && + GIT_PAGER=": > GIT_PAGER_used" && + export GIT_PAGER && + git log && + test -e GIT_PAGER_used +' + +test_done From 36c079756f9f3ad0bbbe2097550c62427670146b Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 20 Feb 2010 12:42:04 +0100 Subject: [PATCH 0140/3211] cherry_pick_list: quit early if one side is empty The --cherry-pick logic starts by counting the commits on each side, so that it can filter away commits on the bigger one. However, so far it missed an opportunity for optimization: it doesn't need to do any work if either side is empty. This in particular helps the common use-case 'git rebase -i HEAD~$n': it internally uses --cherry-pick, but since HEAD~$n is a direct ancestor the left side is always empty. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- revision.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/revision.c b/revision.c index e75079a6e1..c2fad2fd7e 100644 --- a/revision.c +++ b/revision.c @@ -514,6 +514,9 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs) right_count++; } + if (!left_count || !right_count) + return; + left_first = left_count < right_count; init_patch_ids(&ids); if (revs->diffopt.nr_paths) { From 2d3ca21677597902f66bf2f2b2cf1b4a623f1e4f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sat, 20 Feb 2010 02:50:25 -0600 Subject: [PATCH 0141/3211] t7006-pager: if stdout is not a terminal, make a new one Testing pagination requires (fake or real) access to a terminal so we can see whether the pagination automatically kicks in, which makes it hard to get good coverage when running tests without --verbose. There are a number of ways to work around that: - Replace all isatty calls with calls to a custom xisatty wrapper that usually checks for a terminal but can be overridden for tests. This would be workable, but it would require implementing xisatty separately in three languages (C, shell, and perl) and making sure that any code that is to be tested always uses the wrapper. - Redirect stdout to /dev/tty. This would be problematic because there might be no terminal available, and even if a terminal is available, it might not be appropriate to spew output to it. - Create a new pseudo-terminal on the fly and capture its output. This patch implements the third approach. The new test-terminal.perl helper uses IO::Pty from Expect.pm to create a terminal and executes the program specified by its arguments with that terminal as stdout. If the IO::Pty module is missing or not working on a system, the test script will maintain its old behavior (skipping most of its tests unless GIT_TEST_OPTS includes --verbose). Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- t/t7006-pager.sh | 35 +++++++++++++++-------- t/t7006/test-terminal.perl | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100755 t/t7006/test-terminal.perl diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 4f52ea5732..da0f96262d 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -5,18 +5,31 @@ test_description='Test automatic use of a pager.' . ./test-lib.sh rm -f stdout_is_tty -test_expect_success 'is stdout a terminal?' ' +test_expect_success 'set up terminal for tests' ' if test -t 1 then : > stdout_is_tty + elif + test_have_prereq PERL && + "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl \ + sh -c "test -t 1" + then + : > test_terminal_works fi ' if test -e stdout_is_tty then + test_terminal() { "$@"; } + test_set_prereq TTY +elif test -e test_terminal_works +then + test_terminal() { + "$PERL_PATH" "$TEST_DIRECTORY"/t7006/test-terminal.perl "$@" + } test_set_prereq TTY else - say stdout is not a terminal, so skipping some tests. + say no usable terminal, so skipping some tests fi unset GIT_PAGER GIT_PAGER_IN_USE @@ -30,13 +43,13 @@ test_expect_success 'setup' ' rm -f paginated.out test_expect_success TTY 'some commands use a pager' ' - git log && + test_terminal git log && test -e paginated.out ' rm -f paginated.out test_expect_success TTY 'some commands do not use a pager' ' - git rev-list HEAD && + test_terminal git rev-list HEAD && ! test -e paginated.out ' @@ -54,7 +67,7 @@ test_expect_success 'no pager when stdout is a regular file' ' rm -f paginated.out test_expect_success TTY 'git --paginate rev-list uses a pager' ' - git --paginate rev-list HEAD && + test_terminal git --paginate rev-list HEAD && test -e paginated.out ' @@ -66,7 +79,7 @@ test_expect_success 'no pager even with --paginate when stdout is a pipe' ' rm -f paginated.out test_expect_success TTY 'no pager with --no-pager' ' - git --no-pager log && + test_terminal git --no-pager log && ! test -e paginated.out ' @@ -95,7 +108,7 @@ test_expect_success 'no color when stdout is a regular file' ' rm -f paginated.out git config color.ui auto test_expect_success TTY 'color when writing to a pager' ' - TERM=vt100 git log && + TERM=vt100 test_terminal git log && colorful paginated.out ' @@ -127,7 +140,7 @@ test_expect_success SIMPLEPAGER 'default pager is used by default' ' : > default_pager_used EOF chmod +x $less && - PATH=.:$PATH git log && + PATH=.:$PATH test_terminal git log && test -e default_pager_used ' @@ -137,7 +150,7 @@ rm -f PAGER_used test_expect_success TTY 'PAGER overrides default pager' ' PAGER=": > PAGER_used" && export PAGER && - git log && + test_terminal git log && test -e PAGER_used ' @@ -147,7 +160,7 @@ test_expect_success TTY 'core.pager overrides PAGER' ' PAGER=: && export PAGER && git config core.pager ": > core.pager_used" && - git log && + test_terminal git log && test -e core.pager_used ' @@ -156,7 +169,7 @@ test_expect_success TTY 'GIT_PAGER overrides core.pager' ' git config core.pager : && GIT_PAGER=": > GIT_PAGER_used" && export GIT_PAGER && - git log && + test_terminal git log && test -e GIT_PAGER_used ' diff --git a/t/t7006/test-terminal.perl b/t/t7006/test-terminal.perl new file mode 100755 index 0000000000..73ff809371 --- /dev/null +++ b/t/t7006/test-terminal.perl @@ -0,0 +1,58 @@ +#!/usr/bin/perl +use strict; +use warnings; +use IO::Pty; +use File::Copy; + +# Run @$argv in the background with stdout redirected to $out. +sub start_child { + my ($argv, $out) = @_; + my $pid = fork; + if (not defined $pid) { + die "fork failed: $!" + } elsif ($pid == 0) { + open STDOUT, ">&", $out; + close $out; + exec(@$argv) or die "cannot exec '$argv->[0]': $!" + } + return $pid; +} + +# Wait for $pid to finish. +sub finish_child { + # Simplified from wait_or_whine() in run-command.c. + my ($pid) = @_; + + my $waiting = waitpid($pid, 0); + if ($waiting < 0) { + die "waitpid failed: $!"; + } elsif ($? & 127) { + my $code = $? & 127; + warn "died of signal $code"; + return $code - 128; + } else { + return $? >> 8; + } +} + +sub xsendfile { + my ($out, $in) = @_; + + # Note: the real sendfile() cannot read from a terminal. + + # It is unspecified by POSIX whether reads + # from a disconnected terminal will return + # EIO (as in AIX 4.x, IRIX, and Linux) or + # end-of-file. Either is fine. + copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!"; +} + +if ($#ARGV < 1) { + die "usage: test-terminal program args"; +} +my $master = new IO::Pty; +my $slave = $master->slave; +my $pid = start_child(\@ARGV, $slave); +close $slave; +xsendfile(\*STDOUT, $master); +exit(finish_child($pid)); From 9892bebafe0865d8f4f3f18d60a1cfa2d1447cd7 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 20 Feb 2010 23:27:31 -0500 Subject: [PATCH 0142/3211] sha1_file: don't malloc the whole compressed result when writing out objects There is no real advantage to malloc the whole output buffer and deflate the data in a single pass when writing loose objects. That is like only 1% faster while using more memory, especially with large files where memory usage is far more. It is best to deflate and write the data out in small chunks reusing the same memory instead. For example, using 'git add' on a few large files averaging 40 MB ... Before: 21.45user 1.10system 0:22.57elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+828040outputs (0major+142640minor)pagefaults 0swaps After: 21.50user 1.25system 0:22.76elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+828040outputs (0major+104408minor)pagefaults 0swaps While the runtime stayed relatively the same, the number of minor page faults went down significantly. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_file.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 657825e14e..9196b5783d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2281,8 +2281,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, void *buf, unsigned long len, time_t mtime) { int fd, ret; - size_t size; - unsigned char *compressed; + unsigned char compressed[4096]; z_stream stream; char *filename; static char tmpfile[PATH_MAX]; @@ -2301,12 +2300,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, /* Set it up */ memset(&stream, 0, sizeof(stream)); deflateInit(&stream, zlib_compression_level); - size = 8 + deflateBound(&stream, len+hdrlen); - compressed = xmalloc(size); - - /* Compress it */ stream.next_out = compressed; - stream.avail_out = size; + stream.avail_out = sizeof(compressed); /* First header.. */ stream.next_in = (unsigned char *)hdr; @@ -2317,20 +2312,21 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, /* Then the data itself.. */ stream.next_in = buf; stream.avail_in = len; - ret = deflate(&stream, Z_FINISH); + do { + ret = deflate(&stream, Z_FINISH); + if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) + die("unable to write sha1 file"); + stream.next_out = compressed; + stream.avail_out = sizeof(compressed); + } while (ret == Z_OK); + if (ret != Z_STREAM_END) die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret); - ret = deflateEnd(&stream); if (ret != Z_OK) die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret); - size = stream.total_out; - - if (write_buffer(fd, compressed, size) < 0) - die("unable to write sha1 file"); close_sha1_file(fd); - free(compressed); if (mtime) { struct utimbuf utb; From ea68b0ce9f8ce8da3e360aed3cbd6720159ffbee Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 21 Feb 2010 09:32:19 +0300 Subject: [PATCH 0143/3211] hash-object: don't use mmap() for small files Using read() instead of mmap() can be 39% speed up for 1Kb files and is 1% speed up 1Mb files. For larger files, it is better to use mmap(), because the difference between is not significant, and when there is not enough memory, mmap() performs much better, because it avoids swapping. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- sha1_file.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sha1_file.c b/sha1_file.c index 657825e14e..0375159604 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2434,6 +2434,8 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size, return ret; } +#define SMALL_FILE_SIZE (32*1024) + int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path) { @@ -2448,6 +2450,14 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, else ret = -1; strbuf_release(&sbuf); + } else if (size <= SMALL_FILE_SIZE) { + char *buf = xmalloc(size); + if (size == read_in_full(fd, buf, size)) + ret = index_mem(sha1, buf, size, write_object, type, + path); + else + ret = error("short read %s", strerror(errno)); + free(buf); } else if (size) { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); ret = index_mem(sha1, buf, size, write_object, type, path); From 1caaf225f86b7b1818103e72774e191fb1f3d0bb Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Sun, 21 Feb 2010 21:58:44 -0500 Subject: [PATCH 0144/3211] git-diff: add a test for git diff --quiet -w This patch adds two test cases for: 6977c25 git diff --quiet -w: check and report the status Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- t/t4017-diff-retval.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh index 60dd2014d5..0391a5827e 100755 --- a/t/t4017-diff-retval.sh +++ b/t/t4017-diff-retval.sh @@ -5,6 +5,9 @@ test_description='Return value of diffs' . ./test-lib.sh test_expect_success 'setup' ' + echo "1 " >a && + git add . && + git commit -m zeroth && echo 1 >a && git add . && git commit -m first && @@ -13,6 +16,18 @@ test_expect_success 'setup' ' git commit -a -m second ' +test_expect_success 'git diff --quiet -w HEAD^^ HEAD^' ' + git diff --quiet -w HEAD^^ HEAD^ +' + +test_expect_success 'git diff --quiet HEAD^^ HEAD^' ' + test_must_fail git diff --quiet HEAD^^ HEAD^ +' + +test_expect_success 'git diff --quiet -w HEAD^ HEAD' ' + test_must_fail git diff --quiet -w HEAD^ HEAD +' + test_expect_success 'git diff-tree HEAD^ HEAD' ' git diff-tree --exit-code HEAD^ HEAD test $? = 1 From 748af44c63ea6fec12690f1693f3dddd963e88d5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sun, 21 Feb 2010 15:48:06 -0500 Subject: [PATCH 0145/3211] sha1_file: be paranoid when creating loose objects We don't want the data being deflated and stored into loose objects to be different from what we expect. While the deflated data is protected by a CRC which is good enough for safe data retrieval operations, we still want to be doubly sure that the source data used at object creation time is still what we expected once that data has been deflated and its CRC32 computed. The most plausible data corruption may occur if the source file is modified while Git is deflating and writing it out in a loose object. Or Git itself could have a bug causing memory corruption. Or even bad RAM could cause trouble. So it is best to make sure everything is coherent and checksum protected from beginning to end. To do so we compute the SHA1 of the data being deflated _after_ the deflate operation has consumed that data, and make sure it matches with the expected SHA1. This way we can rely on the CRC32 checked by the inflate operation to provide a good indication that the data is still coherent with its SHA1 hash. One pathological case we ignore is when the data is modified before (or during) deflate call, but changed back before it is hashed. There is some overhead of course. Using 'git add' on a set of large files: Before: real 0m25.210s user 0m23.783s sys 0m1.408s After: real 0m26.537s user 0m25.175s sys 0m1.358s The overhead is around 5% for full data coherency guarantee. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- sha1_file.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sha1_file.c b/sha1_file.c index 9196b5783d..c0214d7946 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2283,6 +2283,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, int fd, ret; unsigned char compressed[4096]; z_stream stream; + git_SHA_CTX c; + unsigned char parano_sha1[20]; char *filename; static char tmpfile[PATH_MAX]; @@ -2302,18 +2304,22 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, deflateInit(&stream, zlib_compression_level); stream.next_out = compressed; stream.avail_out = sizeof(compressed); + git_SHA1_Init(&c); /* First header.. */ stream.next_in = (unsigned char *)hdr; stream.avail_in = hdrlen; while (deflate(&stream, 0) == Z_OK) /* nothing */; + git_SHA1_Update(&c, hdr, hdrlen); /* Then the data itself.. */ stream.next_in = buf; stream.avail_in = len; do { + unsigned char *in0 = stream.next_in; ret = deflate(&stream, Z_FINISH); + git_SHA1_Update(&c, in0, stream.next_in - in0); if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) die("unable to write sha1 file"); stream.next_out = compressed; @@ -2325,6 +2331,9 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, ret = deflateEnd(&stream); if (ret != Z_OK) die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret); + git_SHA1_Final(parano_sha1, &c); + if (hashcmp(sha1, parano_sha1) != 0) + die("confused by unstable object source data for %s", sha1_to_hex(sha1)); close_sha1_file(fd); From 8c33b4cf67f47ee46fe0984751fd40c4cf7cf392 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 22 Feb 2010 02:46:33 -0600 Subject: [PATCH 0146/3211] tests: Fix race condition in t7006-pager Pagers that do not consume their input are dangerous: for example, $ GIT_PAGER=: git log $ echo $? 141 $ The only reason these tests were able to work before was that 'git log' would write to the pipe (and not fill it) before the pager had time to terminate and close the pipe. Fix it by using a program that consumes its input, namely wc (as suggested by Johannes). Reported-by: Johannes Sixt Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- t/t7006-pager.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index da0f96262d..d9202d5af5 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -137,7 +137,7 @@ rm -f default_pager_used test_expect_success SIMPLEPAGER 'default pager is used by default' ' cat > $less <<-EOF && #!$SHELL_PATH - : > default_pager_used + wc > default_pager_used EOF chmod +x $less && PATH=.:$PATH test_terminal git log && @@ -148,7 +148,7 @@ unset GIT_PAGER git config --unset core.pager rm -f PAGER_used test_expect_success TTY 'PAGER overrides default pager' ' - PAGER=": > PAGER_used" && + PAGER="wc > PAGER_used" && export PAGER && test_terminal git log && test -e PAGER_used @@ -157,17 +157,17 @@ test_expect_success TTY 'PAGER overrides default pager' ' unset GIT_PAGER rm -f core.pager_used test_expect_success TTY 'core.pager overrides PAGER' ' - PAGER=: && + PAGER=wc && export PAGER && - git config core.pager ": > core.pager_used" && + git config core.pager "wc > core.pager_used" && test_terminal git log && test -e core.pager_used ' rm -f GIT_PAGER_used test_expect_success TTY 'GIT_PAGER overrides core.pager' ' - git config core.pager : && - GIT_PAGER=": > GIT_PAGER_used" && + git config core.pager wc && + GIT_PAGER="wc > GIT_PAGER_used" && export GIT_PAGER && test_terminal git log && test -e GIT_PAGER_used From 81b50f3ce40bfdd66e5d967bf82be001039a9a98 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 22 Feb 2010 08:42:18 -0800 Subject: [PATCH 0147/3211] Move 'builtin-*' into a 'builtin/' subdirectory This shrinks the top-level directory a bit, and makes it much more pleasant to use auto-completion on the thing. Instead of [torvalds@nehalem git]$ em buil Display all 180 possibilities? (y or n) [torvalds@nehalem git]$ em builtin-sh builtin-shortlog.c builtin-show-branch.c builtin-show-ref.c builtin-shortlog.o builtin-show-branch.o builtin-show-ref.o [torvalds@nehalem git]$ em builtin-shor builtin-shortlog.c builtin-shortlog.o [torvalds@nehalem git]$ em builtin-shortlog.c you get [torvalds@nehalem git]$ em buil [type] builtin/ builtin.h [torvalds@nehalem git]$ em builtin [auto-completes to] [torvalds@nehalem git]$ em builtin/sh [type] shortlog.c shortlog.o show-branch.c show-branch.o show-ref.c show-ref.o [torvalds@nehalem git]$ em builtin/sho [auto-completes to] [torvalds@nehalem git]$ em builtin/shor [type] shortlog.c shortlog.o [torvalds@nehalem git]$ em builtin/shortlog.c which doesn't seem all that different, but not having that annoying break in "Display all 180 possibilities?" is quite a relief. NOTE! If you do this in a clean tree (no object files etc), or using an editor that has auto-completion rules that ignores '*.o' files, you won't see that annoying 'Display all 180 possibilities?' message - it will just show the choices instead. I think bash has some cut-off around 100 choices or something. So the reason I see this is that I'm using an odd editory, and thus don't have the rules to cut down on auto-completion. But you can simulate that by using 'ls' instead, or something similar. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 194 +++++++++--------- builtin-add.c => builtin/add.c | 0 builtin-annotate.c => builtin/annotate.c | 0 builtin-apply.c => builtin/apply.c | 0 builtin-archive.c => builtin/archive.c | 0 .../bisect--helper.c | 0 builtin-blame.c => builtin/blame.c | 0 builtin-branch.c => builtin/branch.c | 0 builtin-bundle.c => builtin/bundle.c | 0 builtin-cat-file.c => builtin/cat-file.c | 0 builtin-check-attr.c => builtin/check-attr.c | 0 .../check-ref-format.c | 0 .../checkout-index.c | 0 builtin-checkout.c => builtin/checkout.c | 0 builtin-clean.c => builtin/clean.c | 0 builtin-clone.c => builtin/clone.c | 0 .../commit-tree.c | 0 builtin-commit.c => builtin/commit.c | 0 builtin-config.c => builtin/config.c | 0 .../count-objects.c | 0 builtin-describe.c => builtin/describe.c | 0 builtin-diff-files.c => builtin/diff-files.c | 0 builtin-diff-index.c => builtin/diff-index.c | 0 builtin-diff-tree.c => builtin/diff-tree.c | 0 builtin-diff.c => builtin/diff.c | 0 .../fast-export.c | 0 builtin-fetch-pack.c => builtin/fetch-pack.c | 0 builtin-fetch.c => builtin/fetch.c | 0 .../fmt-merge-msg.c | 0 .../for-each-ref.c | 0 builtin-fsck.c => builtin/fsck.c | 0 builtin-gc.c => builtin/gc.c | 0 builtin-grep.c => builtin/grep.c | 0 .../hash-object.c | 0 builtin-help.c => builtin/help.c | 0 builtin-index-pack.c => builtin/index-pack.c | 0 builtin-init-db.c => builtin/init-db.c | 0 builtin-log.c => builtin/log.c | 0 builtin-ls-files.c => builtin/ls-files.c | 0 builtin-ls-remote.c => builtin/ls-remote.c | 0 builtin-ls-tree.c => builtin/ls-tree.c | 0 builtin-mailinfo.c => builtin/mailinfo.c | 0 builtin-mailsplit.c => builtin/mailsplit.c | 0 builtin-merge-base.c => builtin/merge-base.c | 0 builtin-merge-file.c => builtin/merge-file.c | 0 .../merge-index.c | 0 builtin-merge-ours.c => builtin/merge-ours.c | 0 .../merge-recursive.c | 0 builtin-merge-tree.c => builtin/merge-tree.c | 0 builtin-merge.c => builtin/merge.c | 0 builtin-mktag.c => builtin/mktag.c | 0 builtin-mktree.c => builtin/mktree.c | 0 builtin-mv.c => builtin/mv.c | 0 builtin-name-rev.c => builtin/name-rev.c | 0 .../pack-objects.c | 0 .../pack-redundant.c | 0 builtin-pack-refs.c => builtin/pack-refs.c | 0 builtin-patch-id.c => builtin/patch-id.c | 0 .../prune-packed.c | 0 builtin-prune.c => builtin/prune.c | 0 builtin-push.c => builtin/push.c | 0 builtin-read-tree.c => builtin/read-tree.c | 0 .../receive-pack.c | 0 builtin-reflog.c => builtin/reflog.c | 0 builtin-remote.c => builtin/remote.c | 0 builtin-replace.c => builtin/replace.c | 0 builtin-rerere.c => builtin/rerere.c | 0 builtin-reset.c => builtin/reset.c | 0 builtin-rev-list.c => builtin/rev-list.c | 0 builtin-rev-parse.c => builtin/rev-parse.c | 0 builtin-revert.c => builtin/revert.c | 0 builtin-rm.c => builtin/rm.c | 0 builtin-send-pack.c => builtin/send-pack.c | 0 builtin-shortlog.c => builtin/shortlog.c | 0 .../show-branch.c | 0 builtin-show-ref.c => builtin/show-ref.c | 0 builtin-stripspace.c => builtin/stripspace.c | 0 .../symbolic-ref.c | 0 builtin-tag.c => builtin/tag.c | 0 builtin-tar-tree.c => builtin/tar-tree.c | 0 .../unpack-file.c | 0 .../unpack-objects.c | 0 .../update-index.c | 0 builtin-update-ref.c => builtin/update-ref.c | 0 .../update-server-info.c | 0 .../upload-archive.c | 0 builtin-var.c => builtin/var.c | 0 .../verify-pack.c | 0 builtin-verify-tag.c => builtin/verify-tag.c | 0 builtin-write-tree.c => builtin/write-tree.c | 0 90 files changed, 97 insertions(+), 97 deletions(-) rename builtin-add.c => builtin/add.c (100%) rename builtin-annotate.c => builtin/annotate.c (100%) rename builtin-apply.c => builtin/apply.c (100%) rename builtin-archive.c => builtin/archive.c (100%) rename builtin-bisect--helper.c => builtin/bisect--helper.c (100%) rename builtin-blame.c => builtin/blame.c (100%) rename builtin-branch.c => builtin/branch.c (100%) rename builtin-bundle.c => builtin/bundle.c (100%) rename builtin-cat-file.c => builtin/cat-file.c (100%) rename builtin-check-attr.c => builtin/check-attr.c (100%) rename builtin-check-ref-format.c => builtin/check-ref-format.c (100%) rename builtin-checkout-index.c => builtin/checkout-index.c (100%) rename builtin-checkout.c => builtin/checkout.c (100%) rename builtin-clean.c => builtin/clean.c (100%) rename builtin-clone.c => builtin/clone.c (100%) rename builtin-commit-tree.c => builtin/commit-tree.c (100%) rename builtin-commit.c => builtin/commit.c (100%) rename builtin-config.c => builtin/config.c (100%) rename builtin-count-objects.c => builtin/count-objects.c (100%) rename builtin-describe.c => builtin/describe.c (100%) rename builtin-diff-files.c => builtin/diff-files.c (100%) rename builtin-diff-index.c => builtin/diff-index.c (100%) rename builtin-diff-tree.c => builtin/diff-tree.c (100%) rename builtin-diff.c => builtin/diff.c (100%) rename builtin-fast-export.c => builtin/fast-export.c (100%) rename builtin-fetch-pack.c => builtin/fetch-pack.c (100%) rename builtin-fetch.c => builtin/fetch.c (100%) rename builtin-fmt-merge-msg.c => builtin/fmt-merge-msg.c (100%) rename builtin-for-each-ref.c => builtin/for-each-ref.c (100%) rename builtin-fsck.c => builtin/fsck.c (100%) rename builtin-gc.c => builtin/gc.c (100%) rename builtin-grep.c => builtin/grep.c (100%) rename builtin-hash-object.c => builtin/hash-object.c (100%) rename builtin-help.c => builtin/help.c (100%) rename builtin-index-pack.c => builtin/index-pack.c (100%) rename builtin-init-db.c => builtin/init-db.c (100%) rename builtin-log.c => builtin/log.c (100%) rename builtin-ls-files.c => builtin/ls-files.c (100%) rename builtin-ls-remote.c => builtin/ls-remote.c (100%) rename builtin-ls-tree.c => builtin/ls-tree.c (100%) rename builtin-mailinfo.c => builtin/mailinfo.c (100%) rename builtin-mailsplit.c => builtin/mailsplit.c (100%) rename builtin-merge-base.c => builtin/merge-base.c (100%) rename builtin-merge-file.c => builtin/merge-file.c (100%) rename builtin-merge-index.c => builtin/merge-index.c (100%) rename builtin-merge-ours.c => builtin/merge-ours.c (100%) rename builtin-merge-recursive.c => builtin/merge-recursive.c (100%) rename builtin-merge-tree.c => builtin/merge-tree.c (100%) rename builtin-merge.c => builtin/merge.c (100%) rename builtin-mktag.c => builtin/mktag.c (100%) rename builtin-mktree.c => builtin/mktree.c (100%) rename builtin-mv.c => builtin/mv.c (100%) rename builtin-name-rev.c => builtin/name-rev.c (100%) rename builtin-pack-objects.c => builtin/pack-objects.c (100%) rename builtin-pack-redundant.c => builtin/pack-redundant.c (100%) rename builtin-pack-refs.c => builtin/pack-refs.c (100%) rename builtin-patch-id.c => builtin/patch-id.c (100%) rename builtin-prune-packed.c => builtin/prune-packed.c (100%) rename builtin-prune.c => builtin/prune.c (100%) rename builtin-push.c => builtin/push.c (100%) rename builtin-read-tree.c => builtin/read-tree.c (100%) rename builtin-receive-pack.c => builtin/receive-pack.c (100%) rename builtin-reflog.c => builtin/reflog.c (100%) rename builtin-remote.c => builtin/remote.c (100%) rename builtin-replace.c => builtin/replace.c (100%) rename builtin-rerere.c => builtin/rerere.c (100%) rename builtin-reset.c => builtin/reset.c (100%) rename builtin-rev-list.c => builtin/rev-list.c (100%) rename builtin-rev-parse.c => builtin/rev-parse.c (100%) rename builtin-revert.c => builtin/revert.c (100%) rename builtin-rm.c => builtin/rm.c (100%) rename builtin-send-pack.c => builtin/send-pack.c (100%) rename builtin-shortlog.c => builtin/shortlog.c (100%) rename builtin-show-branch.c => builtin/show-branch.c (100%) rename builtin-show-ref.c => builtin/show-ref.c (100%) rename builtin-stripspace.c => builtin/stripspace.c (100%) rename builtin-symbolic-ref.c => builtin/symbolic-ref.c (100%) rename builtin-tag.c => builtin/tag.c (100%) rename builtin-tar-tree.c => builtin/tar-tree.c (100%) rename builtin-unpack-file.c => builtin/unpack-file.c (100%) rename builtin-unpack-objects.c => builtin/unpack-objects.c (100%) rename builtin-update-index.c => builtin/update-index.c (100%) rename builtin-update-ref.c => builtin/update-ref.c (100%) rename builtin-update-server-info.c => builtin/update-server-info.c (100%) rename builtin-upload-archive.c => builtin/upload-archive.c (100%) rename builtin-var.c => builtin/var.c (100%) rename builtin-verify-pack.c => builtin/verify-pack.c (100%) rename builtin-verify-tag.c => builtin/verify-tag.c (100%) rename builtin-write-tree.c => builtin/write-tree.c (100%) diff --git a/Makefile b/Makefile index afedb54b48..f1025d5c03 100644 --- a/Makefile +++ b/Makefile @@ -301,7 +301,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ # Those must not be GNU-specific; they are shared with perl/ which may # be built by a different compiler. (Note that this is an artifact now # but it still might be nice to keep that distinction.) -BASIC_CFLAGS = +BASIC_CFLAGS = -I. BASIC_LDFLAGS = # Guard against environment variables @@ -370,8 +370,8 @@ PROGRAMS += git-upload-pack$X PROGRAMS += git-http-backend$X # List built-in command $C whose implementation cmd_$C() is not in -# builtin-$C.o but is linked in as part of some other command. -BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) +# builtin/$C.o but is linked in as part of some other command. +BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS)) BUILT_INS += git-cherry$X BUILT_INS += git-cherry-pick$X @@ -594,95 +594,95 @@ LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o -BUILTIN_OBJS += builtin-add.o -BUILTIN_OBJS += builtin-annotate.o -BUILTIN_OBJS += builtin-apply.o -BUILTIN_OBJS += builtin-archive.o -BUILTIN_OBJS += builtin-bisect--helper.o -BUILTIN_OBJS += builtin-blame.o -BUILTIN_OBJS += builtin-branch.o -BUILTIN_OBJS += builtin-bundle.o -BUILTIN_OBJS += builtin-cat-file.o -BUILTIN_OBJS += builtin-check-attr.o -BUILTIN_OBJS += builtin-check-ref-format.o -BUILTIN_OBJS += builtin-checkout-index.o -BUILTIN_OBJS += builtin-checkout.o -BUILTIN_OBJS += builtin-clean.o -BUILTIN_OBJS += builtin-clone.o -BUILTIN_OBJS += builtin-commit-tree.o -BUILTIN_OBJS += builtin-commit.o -BUILTIN_OBJS += builtin-config.o -BUILTIN_OBJS += builtin-count-objects.o -BUILTIN_OBJS += builtin-describe.o -BUILTIN_OBJS += builtin-diff-files.o -BUILTIN_OBJS += builtin-diff-index.o -BUILTIN_OBJS += builtin-diff-tree.o -BUILTIN_OBJS += builtin-diff.o -BUILTIN_OBJS += builtin-fast-export.o -BUILTIN_OBJS += builtin-fetch-pack.o -BUILTIN_OBJS += builtin-fetch.o -BUILTIN_OBJS += builtin-fmt-merge-msg.o -BUILTIN_OBJS += builtin-for-each-ref.o -BUILTIN_OBJS += builtin-fsck.o -BUILTIN_OBJS += builtin-gc.o -BUILTIN_OBJS += builtin-grep.o -BUILTIN_OBJS += builtin-hash-object.o -BUILTIN_OBJS += builtin-help.o -BUILTIN_OBJS += builtin-index-pack.o -BUILTIN_OBJS += builtin-init-db.o -BUILTIN_OBJS += builtin-log.o -BUILTIN_OBJS += builtin-ls-files.o -BUILTIN_OBJS += builtin-ls-remote.o -BUILTIN_OBJS += builtin-ls-tree.o -BUILTIN_OBJS += builtin-mailinfo.o -BUILTIN_OBJS += builtin-mailsplit.o -BUILTIN_OBJS += builtin-merge.o -BUILTIN_OBJS += builtin-merge-base.o -BUILTIN_OBJS += builtin-merge-file.o -BUILTIN_OBJS += builtin-merge-index.o -BUILTIN_OBJS += builtin-merge-ours.o -BUILTIN_OBJS += builtin-merge-recursive.o -BUILTIN_OBJS += builtin-merge-tree.o -BUILTIN_OBJS += builtin-mktag.o -BUILTIN_OBJS += builtin-mktree.o -BUILTIN_OBJS += builtin-mv.o -BUILTIN_OBJS += builtin-name-rev.o -BUILTIN_OBJS += builtin-pack-objects.o -BUILTIN_OBJS += builtin-pack-redundant.o -BUILTIN_OBJS += builtin-pack-refs.o -BUILTIN_OBJS += builtin-patch-id.o -BUILTIN_OBJS += builtin-prune-packed.o -BUILTIN_OBJS += builtin-prune.o -BUILTIN_OBJS += builtin-push.o -BUILTIN_OBJS += builtin-read-tree.o -BUILTIN_OBJS += builtin-receive-pack.o -BUILTIN_OBJS += builtin-reflog.o -BUILTIN_OBJS += builtin-remote.o -BUILTIN_OBJS += builtin-replace.o -BUILTIN_OBJS += builtin-rerere.o -BUILTIN_OBJS += builtin-reset.o -BUILTIN_OBJS += builtin-rev-list.o -BUILTIN_OBJS += builtin-rev-parse.o -BUILTIN_OBJS += builtin-revert.o -BUILTIN_OBJS += builtin-rm.o -BUILTIN_OBJS += builtin-send-pack.o -BUILTIN_OBJS += builtin-shortlog.o -BUILTIN_OBJS += builtin-show-branch.o -BUILTIN_OBJS += builtin-show-ref.o -BUILTIN_OBJS += builtin-stripspace.o -BUILTIN_OBJS += builtin-symbolic-ref.o -BUILTIN_OBJS += builtin-tag.o -BUILTIN_OBJS += builtin-tar-tree.o -BUILTIN_OBJS += builtin-unpack-file.o -BUILTIN_OBJS += builtin-unpack-objects.o -BUILTIN_OBJS += builtin-update-index.o -BUILTIN_OBJS += builtin-update-ref.o -BUILTIN_OBJS += builtin-update-server-info.o -BUILTIN_OBJS += builtin-upload-archive.o -BUILTIN_OBJS += builtin-var.o -BUILTIN_OBJS += builtin-verify-pack.o -BUILTIN_OBJS += builtin-verify-tag.o -BUILTIN_OBJS += builtin-write-tree.o +BUILTIN_OBJS += builtin/add.o +BUILTIN_OBJS += builtin/annotate.o +BUILTIN_OBJS += builtin/apply.o +BUILTIN_OBJS += builtin/archive.o +BUILTIN_OBJS += builtin/bisect--helper.o +BUILTIN_OBJS += builtin/blame.o +BUILTIN_OBJS += builtin/branch.o +BUILTIN_OBJS += builtin/bundle.o +BUILTIN_OBJS += builtin/cat-file.o +BUILTIN_OBJS += builtin/check-attr.o +BUILTIN_OBJS += builtin/check-ref-format.o +BUILTIN_OBJS += builtin/checkout-index.o +BUILTIN_OBJS += builtin/checkout.o +BUILTIN_OBJS += builtin/clean.o +BUILTIN_OBJS += builtin/clone.o +BUILTIN_OBJS += builtin/commit-tree.o +BUILTIN_OBJS += builtin/commit.o +BUILTIN_OBJS += builtin/config.o +BUILTIN_OBJS += builtin/count-objects.o +BUILTIN_OBJS += builtin/describe.o +BUILTIN_OBJS += builtin/diff-files.o +BUILTIN_OBJS += builtin/diff-index.o +BUILTIN_OBJS += builtin/diff-tree.o +BUILTIN_OBJS += builtin/diff.o +BUILTIN_OBJS += builtin/fast-export.o +BUILTIN_OBJS += builtin/fetch-pack.o +BUILTIN_OBJS += builtin/fetch.o +BUILTIN_OBJS += builtin/fmt-merge-msg.o +BUILTIN_OBJS += builtin/for-each-ref.o +BUILTIN_OBJS += builtin/fsck.o +BUILTIN_OBJS += builtin/gc.o +BUILTIN_OBJS += builtin/grep.o +BUILTIN_OBJS += builtin/hash-object.o +BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/index-pack.o +BUILTIN_OBJS += builtin/init-db.o +BUILTIN_OBJS += builtin/log.o +BUILTIN_OBJS += builtin/ls-files.o +BUILTIN_OBJS += builtin/ls-remote.o +BUILTIN_OBJS += builtin/ls-tree.o +BUILTIN_OBJS += builtin/mailinfo.o +BUILTIN_OBJS += builtin/mailsplit.o +BUILTIN_OBJS += builtin/merge.o +BUILTIN_OBJS += builtin/merge-base.o +BUILTIN_OBJS += builtin/merge-file.o +BUILTIN_OBJS += builtin/merge-index.o +BUILTIN_OBJS += builtin/merge-ours.o +BUILTIN_OBJS += builtin/merge-recursive.o +BUILTIN_OBJS += builtin/merge-tree.o +BUILTIN_OBJS += builtin/mktag.o +BUILTIN_OBJS += builtin/mktree.o +BUILTIN_OBJS += builtin/mv.o +BUILTIN_OBJS += builtin/name-rev.o +BUILTIN_OBJS += builtin/pack-objects.o +BUILTIN_OBJS += builtin/pack-redundant.o +BUILTIN_OBJS += builtin/pack-refs.o +BUILTIN_OBJS += builtin/patch-id.o +BUILTIN_OBJS += builtin/prune-packed.o +BUILTIN_OBJS += builtin/prune.o +BUILTIN_OBJS += builtin/push.o +BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/receive-pack.o +BUILTIN_OBJS += builtin/reflog.o +BUILTIN_OBJS += builtin/remote.o +BUILTIN_OBJS += builtin/replace.o +BUILTIN_OBJS += builtin/rerere.o +BUILTIN_OBJS += builtin/reset.o +BUILTIN_OBJS += builtin/rev-list.o +BUILTIN_OBJS += builtin/rev-parse.o +BUILTIN_OBJS += builtin/revert.o +BUILTIN_OBJS += builtin/rm.o +BUILTIN_OBJS += builtin/send-pack.o +BUILTIN_OBJS += builtin/shortlog.o +BUILTIN_OBJS += builtin/show-branch.o +BUILTIN_OBJS += builtin/show-ref.o +BUILTIN_OBJS += builtin/stripspace.o +BUILTIN_OBJS += builtin/symbolic-ref.o +BUILTIN_OBJS += builtin/tag.o +BUILTIN_OBJS += builtin/tar-tree.o +BUILTIN_OBJS += builtin/unpack-file.o +BUILTIN_OBJS += builtin/unpack-objects.o +BUILTIN_OBJS += builtin/update-index.o +BUILTIN_OBJS += builtin/update-ref.o +BUILTIN_OBJS += builtin/update-server-info.o +BUILTIN_OBJS += builtin/upload-archive.o +BUILTIN_OBJS += builtin/var.o +BUILTIN_OBJS += builtin/verify-pack.o +BUILTIN_OBJS += builtin/verify-tag.o +BUILTIN_OBJS += builtin/write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) EXTLIBS = @@ -1447,8 +1447,8 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h -builtin-help.s builtin-help.o: ALL_CFLAGS += \ +builtin/help.o: common-cmds.h +builtin/help.s builtin/help.o: ALL_CFLAGS += \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' @@ -1604,7 +1604,7 @@ exec_cmd.s exec_cmd.o: ALL_CFLAGS += \ '-DBINDIR="$(bindir_relative_SQ)"' \ '-DPREFIX="$(prefix_SQ)"' -builtin-init-db.s builtin-init-db.o: ALL_CFLAGS += \ +builtin/init-db.s builtin/init-db.o: ALL_CFLAGS += \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' config.s config.o: ALL_CFLAGS += -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' @@ -1646,7 +1646,7 @@ $(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) -builtin-revert.o wt-status.o: wt-status.h +builtin/revert.o wt-status.o: wt-status.h $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) @@ -1934,7 +1934,7 @@ distclean: clean clean: $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ - $(LIB_FILE) $(XDIFF_LIB) + builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers diff --git a/builtin-add.c b/builtin/add.c similarity index 100% rename from builtin-add.c rename to builtin/add.c diff --git a/builtin-annotate.c b/builtin/annotate.c similarity index 100% rename from builtin-annotate.c rename to builtin/annotate.c diff --git a/builtin-apply.c b/builtin/apply.c similarity index 100% rename from builtin-apply.c rename to builtin/apply.c diff --git a/builtin-archive.c b/builtin/archive.c similarity index 100% rename from builtin-archive.c rename to builtin/archive.c diff --git a/builtin-bisect--helper.c b/builtin/bisect--helper.c similarity index 100% rename from builtin-bisect--helper.c rename to builtin/bisect--helper.c diff --git a/builtin-blame.c b/builtin/blame.c similarity index 100% rename from builtin-blame.c rename to builtin/blame.c diff --git a/builtin-branch.c b/builtin/branch.c similarity index 100% rename from builtin-branch.c rename to builtin/branch.c diff --git a/builtin-bundle.c b/builtin/bundle.c similarity index 100% rename from builtin-bundle.c rename to builtin/bundle.c diff --git a/builtin-cat-file.c b/builtin/cat-file.c similarity index 100% rename from builtin-cat-file.c rename to builtin/cat-file.c diff --git a/builtin-check-attr.c b/builtin/check-attr.c similarity index 100% rename from builtin-check-attr.c rename to builtin/check-attr.c diff --git a/builtin-check-ref-format.c b/builtin/check-ref-format.c similarity index 100% rename from builtin-check-ref-format.c rename to builtin/check-ref-format.c diff --git a/builtin-checkout-index.c b/builtin/checkout-index.c similarity index 100% rename from builtin-checkout-index.c rename to builtin/checkout-index.c diff --git a/builtin-checkout.c b/builtin/checkout.c similarity index 100% rename from builtin-checkout.c rename to builtin/checkout.c diff --git a/builtin-clean.c b/builtin/clean.c similarity index 100% rename from builtin-clean.c rename to builtin/clean.c diff --git a/builtin-clone.c b/builtin/clone.c similarity index 100% rename from builtin-clone.c rename to builtin/clone.c diff --git a/builtin-commit-tree.c b/builtin/commit-tree.c similarity index 100% rename from builtin-commit-tree.c rename to builtin/commit-tree.c diff --git a/builtin-commit.c b/builtin/commit.c similarity index 100% rename from builtin-commit.c rename to builtin/commit.c diff --git a/builtin-config.c b/builtin/config.c similarity index 100% rename from builtin-config.c rename to builtin/config.c diff --git a/builtin-count-objects.c b/builtin/count-objects.c similarity index 100% rename from builtin-count-objects.c rename to builtin/count-objects.c diff --git a/builtin-describe.c b/builtin/describe.c similarity index 100% rename from builtin-describe.c rename to builtin/describe.c diff --git a/builtin-diff-files.c b/builtin/diff-files.c similarity index 100% rename from builtin-diff-files.c rename to builtin/diff-files.c diff --git a/builtin-diff-index.c b/builtin/diff-index.c similarity index 100% rename from builtin-diff-index.c rename to builtin/diff-index.c diff --git a/builtin-diff-tree.c b/builtin/diff-tree.c similarity index 100% rename from builtin-diff-tree.c rename to builtin/diff-tree.c diff --git a/builtin-diff.c b/builtin/diff.c similarity index 100% rename from builtin-diff.c rename to builtin/diff.c diff --git a/builtin-fast-export.c b/builtin/fast-export.c similarity index 100% rename from builtin-fast-export.c rename to builtin/fast-export.c diff --git a/builtin-fetch-pack.c b/builtin/fetch-pack.c similarity index 100% rename from builtin-fetch-pack.c rename to builtin/fetch-pack.c diff --git a/builtin-fetch.c b/builtin/fetch.c similarity index 100% rename from builtin-fetch.c rename to builtin/fetch.c diff --git a/builtin-fmt-merge-msg.c b/builtin/fmt-merge-msg.c similarity index 100% rename from builtin-fmt-merge-msg.c rename to builtin/fmt-merge-msg.c diff --git a/builtin-for-each-ref.c b/builtin/for-each-ref.c similarity index 100% rename from builtin-for-each-ref.c rename to builtin/for-each-ref.c diff --git a/builtin-fsck.c b/builtin/fsck.c similarity index 100% rename from builtin-fsck.c rename to builtin/fsck.c diff --git a/builtin-gc.c b/builtin/gc.c similarity index 100% rename from builtin-gc.c rename to builtin/gc.c diff --git a/builtin-grep.c b/builtin/grep.c similarity index 100% rename from builtin-grep.c rename to builtin/grep.c diff --git a/builtin-hash-object.c b/builtin/hash-object.c similarity index 100% rename from builtin-hash-object.c rename to builtin/hash-object.c diff --git a/builtin-help.c b/builtin/help.c similarity index 100% rename from builtin-help.c rename to builtin/help.c diff --git a/builtin-index-pack.c b/builtin/index-pack.c similarity index 100% rename from builtin-index-pack.c rename to builtin/index-pack.c diff --git a/builtin-init-db.c b/builtin/init-db.c similarity index 100% rename from builtin-init-db.c rename to builtin/init-db.c diff --git a/builtin-log.c b/builtin/log.c similarity index 100% rename from builtin-log.c rename to builtin/log.c diff --git a/builtin-ls-files.c b/builtin/ls-files.c similarity index 100% rename from builtin-ls-files.c rename to builtin/ls-files.c diff --git a/builtin-ls-remote.c b/builtin/ls-remote.c similarity index 100% rename from builtin-ls-remote.c rename to builtin/ls-remote.c diff --git a/builtin-ls-tree.c b/builtin/ls-tree.c similarity index 100% rename from builtin-ls-tree.c rename to builtin/ls-tree.c diff --git a/builtin-mailinfo.c b/builtin/mailinfo.c similarity index 100% rename from builtin-mailinfo.c rename to builtin/mailinfo.c diff --git a/builtin-mailsplit.c b/builtin/mailsplit.c similarity index 100% rename from builtin-mailsplit.c rename to builtin/mailsplit.c diff --git a/builtin-merge-base.c b/builtin/merge-base.c similarity index 100% rename from builtin-merge-base.c rename to builtin/merge-base.c diff --git a/builtin-merge-file.c b/builtin/merge-file.c similarity index 100% rename from builtin-merge-file.c rename to builtin/merge-file.c diff --git a/builtin-merge-index.c b/builtin/merge-index.c similarity index 100% rename from builtin-merge-index.c rename to builtin/merge-index.c diff --git a/builtin-merge-ours.c b/builtin/merge-ours.c similarity index 100% rename from builtin-merge-ours.c rename to builtin/merge-ours.c diff --git a/builtin-merge-recursive.c b/builtin/merge-recursive.c similarity index 100% rename from builtin-merge-recursive.c rename to builtin/merge-recursive.c diff --git a/builtin-merge-tree.c b/builtin/merge-tree.c similarity index 100% rename from builtin-merge-tree.c rename to builtin/merge-tree.c diff --git a/builtin-merge.c b/builtin/merge.c similarity index 100% rename from builtin-merge.c rename to builtin/merge.c diff --git a/builtin-mktag.c b/builtin/mktag.c similarity index 100% rename from builtin-mktag.c rename to builtin/mktag.c diff --git a/builtin-mktree.c b/builtin/mktree.c similarity index 100% rename from builtin-mktree.c rename to builtin/mktree.c diff --git a/builtin-mv.c b/builtin/mv.c similarity index 100% rename from builtin-mv.c rename to builtin/mv.c diff --git a/builtin-name-rev.c b/builtin/name-rev.c similarity index 100% rename from builtin-name-rev.c rename to builtin/name-rev.c diff --git a/builtin-pack-objects.c b/builtin/pack-objects.c similarity index 100% rename from builtin-pack-objects.c rename to builtin/pack-objects.c diff --git a/builtin-pack-redundant.c b/builtin/pack-redundant.c similarity index 100% rename from builtin-pack-redundant.c rename to builtin/pack-redundant.c diff --git a/builtin-pack-refs.c b/builtin/pack-refs.c similarity index 100% rename from builtin-pack-refs.c rename to builtin/pack-refs.c diff --git a/builtin-patch-id.c b/builtin/patch-id.c similarity index 100% rename from builtin-patch-id.c rename to builtin/patch-id.c diff --git a/builtin-prune-packed.c b/builtin/prune-packed.c similarity index 100% rename from builtin-prune-packed.c rename to builtin/prune-packed.c diff --git a/builtin-prune.c b/builtin/prune.c similarity index 100% rename from builtin-prune.c rename to builtin/prune.c diff --git a/builtin-push.c b/builtin/push.c similarity index 100% rename from builtin-push.c rename to builtin/push.c diff --git a/builtin-read-tree.c b/builtin/read-tree.c similarity index 100% rename from builtin-read-tree.c rename to builtin/read-tree.c diff --git a/builtin-receive-pack.c b/builtin/receive-pack.c similarity index 100% rename from builtin-receive-pack.c rename to builtin/receive-pack.c diff --git a/builtin-reflog.c b/builtin/reflog.c similarity index 100% rename from builtin-reflog.c rename to builtin/reflog.c diff --git a/builtin-remote.c b/builtin/remote.c similarity index 100% rename from builtin-remote.c rename to builtin/remote.c diff --git a/builtin-replace.c b/builtin/replace.c similarity index 100% rename from builtin-replace.c rename to builtin/replace.c diff --git a/builtin-rerere.c b/builtin/rerere.c similarity index 100% rename from builtin-rerere.c rename to builtin/rerere.c diff --git a/builtin-reset.c b/builtin/reset.c similarity index 100% rename from builtin-reset.c rename to builtin/reset.c diff --git a/builtin-rev-list.c b/builtin/rev-list.c similarity index 100% rename from builtin-rev-list.c rename to builtin/rev-list.c diff --git a/builtin-rev-parse.c b/builtin/rev-parse.c similarity index 100% rename from builtin-rev-parse.c rename to builtin/rev-parse.c diff --git a/builtin-revert.c b/builtin/revert.c similarity index 100% rename from builtin-revert.c rename to builtin/revert.c diff --git a/builtin-rm.c b/builtin/rm.c similarity index 100% rename from builtin-rm.c rename to builtin/rm.c diff --git a/builtin-send-pack.c b/builtin/send-pack.c similarity index 100% rename from builtin-send-pack.c rename to builtin/send-pack.c diff --git a/builtin-shortlog.c b/builtin/shortlog.c similarity index 100% rename from builtin-shortlog.c rename to builtin/shortlog.c diff --git a/builtin-show-branch.c b/builtin/show-branch.c similarity index 100% rename from builtin-show-branch.c rename to builtin/show-branch.c diff --git a/builtin-show-ref.c b/builtin/show-ref.c similarity index 100% rename from builtin-show-ref.c rename to builtin/show-ref.c diff --git a/builtin-stripspace.c b/builtin/stripspace.c similarity index 100% rename from builtin-stripspace.c rename to builtin/stripspace.c diff --git a/builtin-symbolic-ref.c b/builtin/symbolic-ref.c similarity index 100% rename from builtin-symbolic-ref.c rename to builtin/symbolic-ref.c diff --git a/builtin-tag.c b/builtin/tag.c similarity index 100% rename from builtin-tag.c rename to builtin/tag.c diff --git a/builtin-tar-tree.c b/builtin/tar-tree.c similarity index 100% rename from builtin-tar-tree.c rename to builtin/tar-tree.c diff --git a/builtin-unpack-file.c b/builtin/unpack-file.c similarity index 100% rename from builtin-unpack-file.c rename to builtin/unpack-file.c diff --git a/builtin-unpack-objects.c b/builtin/unpack-objects.c similarity index 100% rename from builtin-unpack-objects.c rename to builtin/unpack-objects.c diff --git a/builtin-update-index.c b/builtin/update-index.c similarity index 100% rename from builtin-update-index.c rename to builtin/update-index.c diff --git a/builtin-update-ref.c b/builtin/update-ref.c similarity index 100% rename from builtin-update-ref.c rename to builtin/update-ref.c diff --git a/builtin-update-server-info.c b/builtin/update-server-info.c similarity index 100% rename from builtin-update-server-info.c rename to builtin/update-server-info.c diff --git a/builtin-upload-archive.c b/builtin/upload-archive.c similarity index 100% rename from builtin-upload-archive.c rename to builtin/upload-archive.c diff --git a/builtin-var.c b/builtin/var.c similarity index 100% rename from builtin-var.c rename to builtin/var.c diff --git a/builtin-verify-pack.c b/builtin/verify-pack.c similarity index 100% rename from builtin-verify-pack.c rename to builtin/verify-pack.c diff --git a/builtin-verify-tag.c b/builtin/verify-tag.c similarity index 100% rename from builtin-verify-tag.c rename to builtin/verify-tag.c diff --git a/builtin-write-tree.c b/builtin/write-tree.c similarity index 100% rename from builtin-write-tree.c rename to builtin/write-tree.c From 7aba6185d55e06db3f3ef18daa63baf3821e5030 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:11 +0100 Subject: [PATCH 0148/3211] Add a testcase for ACL with restrictive umask. Right now, Git creates unreadable pack files on non-shared repositories when the user has a umask of 077, even when the default ACLs for the directory would give read/write access to a specific user. Loose object files are created world-readable, which doesn't break ACLs, but isn't necessarily desirable. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- t/t1304-default-acl.sh | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100755 t/t1304-default-acl.sh diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh new file mode 100755 index 0000000000..07dd6af99c --- /dev/null +++ b/t/t1304-default-acl.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# +# Copyright (c) 2010 Matthieu Moy +# + +test_description='Test repository with default ACL' + +# Create the test repo with restrictive umask +# => this must come before . ./test-lib.sh +umask 077 + +. ./test-lib.sh + +# We need an arbitrary other user give permission to using ACLs. root +# is a good candidate: exists on all unices, and it has permission +# anyway, so we don't create a security hole running the testsuite. + +if ! setfacl -m u:root:rwx .; then + say "Skipping ACL tests: unable to use setfacl" + test_done +fi + +modebits () { + ls -l "$1" | sed -e 's|^\(..........\).*|\1|' +} + +check_perms_and_acl () { + actual=$(modebits "$1") && + case "$actual" in + -r--r-----*) + : happy + ;; + *) + echo "Got permission '$actual', expected '-r--r-----'" + false + ;; + esac && + getfacl "$1" > actual && + grep -q "user:root:rwx" actual && + grep -q "user:${LOGNAME}:rwx" actual && + grep -q "mask::r--" actual && + grep -q "group::---" actual || false +} + +dirs_to_set="./ .git/ .git/objects/ .git/objects/pack/" + +test_expect_success 'Setup test repo' ' + setfacl -m u:root:rwx $dirs_to_set && + setfacl -d -m u:"$LOGNAME":rwx $dirs_to_set && + setfacl -d -m u:root:rwx $dirs_to_set && + + touch file.txt && + git add file.txt && + git commit -m "init" +' + +test_expect_failure 'Objects creation does not break ACLs with restrictive umask' ' + # SHA1 for empty blob + check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +' + +test_expect_failure 'git gc does not break ACLs with restrictive umask' ' + git gc && + check_perms_and_acl .git/objects/pack/*.pack +' + +test_done From 00787ed55adbc2350efa911bf0bdebf6ca08c095 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:12 +0100 Subject: [PATCH 0149/3211] Move gitmkstemps to path.c This function used to be only a compatibility function, but we're going to extend it and actually use it, so make it part of Git. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Makefile | 1 - compat/mkstemps.c | 70 ----------------------------------------------- path.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 71 deletions(-) delete mode 100644 compat/mkstemps.c diff --git a/Makefile b/Makefile index 7bf2fca407..4387d4207f 100644 --- a/Makefile +++ b/Makefile @@ -1200,7 +1200,6 @@ ifdef NO_MKDTEMP endif ifdef NO_MKSTEMPS COMPAT_CFLAGS += -DNO_MKSTEMPS - COMPAT_OBJS += compat/mkstemps.o endif ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV diff --git a/compat/mkstemps.c b/compat/mkstemps.c deleted file mode 100644 index 14179c8e6d..0000000000 --- a/compat/mkstemps.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../git-compat-util.h" - -/* Adapted from libiberty's mkstemp.c. */ - -#undef TMP_MAX -#define TMP_MAX 16384 - -int gitmkstemps(char *pattern, int suffix_len) -{ - static const char letters[] = - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789"; - static const int num_letters = 62; - uint64_t value; - struct timeval tv; - char *template; - size_t len; - int fd, count; - - len = strlen(pattern); - - if (len < 6 + suffix_len) { - errno = EINVAL; - return -1; - } - - if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) { - errno = EINVAL; - return -1; - } - - /* - * Replace pattern's XXXXXX characters with randomness. - * Try TMP_MAX different filenames. - */ - gettimeofday(&tv, NULL); - value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid(); - template = &pattern[len - 6 - suffix_len]; - for (count = 0; count < TMP_MAX; ++count) { - uint64_t v = value; - /* Fill in the random bits. */ - template[0] = letters[v % num_letters]; v /= num_letters; - template[1] = letters[v % num_letters]; v /= num_letters; - template[2] = letters[v % num_letters]; v /= num_letters; - template[3] = letters[v % num_letters]; v /= num_letters; - template[4] = letters[v % num_letters]; v /= num_letters; - template[5] = letters[v % num_letters]; v /= num_letters; - - fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, 0600); - if (fd > 0) - return fd; - /* - * Fatal error (EPERM, ENOSPC etc). - * It doesn't make sense to loop. - */ - if (errno != EEXIST) - break; - /* - * This is a random value. It is only necessary that - * the next TMP_MAX values generated by adding 7777 to - * VALUE are different with (module 2^32). - */ - value += 7777; - } - /* We return the null string if we can't find a unique file name. */ - pattern[0] = '\0'; - errno = EINVAL; - return -1; -} diff --git a/path.c b/path.c index 79aa104712..ab2e3687ab 100644 --- a/path.c +++ b/path.c @@ -157,6 +157,75 @@ int git_mkstemps(char *path, size_t len, const char *template, int suffix_len) return mkstemps(path, suffix_len); } +/* Adapted from libiberty's mkstemp.c. */ + +#undef TMP_MAX +#define TMP_MAX 16384 + +int gitmkstemps(char *pattern, int suffix_len) +{ + static const char letters[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + static const int num_letters = 62; + uint64_t value; + struct timeval tv; + char *template; + size_t len; + int fd, count; + + len = strlen(pattern); + + if (len < 6 + suffix_len) { + errno = EINVAL; + return -1; + } + + if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) { + errno = EINVAL; + return -1; + } + + /* + * Replace pattern's XXXXXX characters with randomness. + * Try TMP_MAX different filenames. + */ + gettimeofday(&tv, NULL); + value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid(); + template = &pattern[len - 6 - suffix_len]; + for (count = 0; count < TMP_MAX; ++count) { + uint64_t v = value; + /* Fill in the random bits. */ + template[0] = letters[v % num_letters]; v /= num_letters; + template[1] = letters[v % num_letters]; v /= num_letters; + template[2] = letters[v % num_letters]; v /= num_letters; + template[3] = letters[v % num_letters]; v /= num_letters; + template[4] = letters[v % num_letters]; v /= num_letters; + template[5] = letters[v % num_letters]; v /= num_letters; + + fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, 0600); + if (fd > 0) + return fd; + /* + * Fatal error (EPERM, ENOSPC etc). + * It doesn't make sense to loop. + */ + if (errno != EEXIST) + break; + /* + * This is a random value. It is only necessary that + * the next TMP_MAX values generated by adding 7777 to + * VALUE are different with (module 2^32). + */ + value += 7777; + } + /* We return the null string if we can't find a unique file name. */ + pattern[0] = '\0'; + errno = EINVAL; + return -1; +} + int validate_headref(const char *path) { struct stat st; From b862b61c03797fd00490bb8caf05be840b79c6cb Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:13 +0100 Subject: [PATCH 0150/3211] git_mkstemp_mode, xmkstemp_mode: variants of gitmkstemps with mode argument. gitmkstemps emulates the behavior of mkstemps, which is usually used to create files in a shared directory like /tmp/, hence, it creates files with permission 0600. Add git_mkstemps_mode() that allows us to specify the desired mode, and make git_mkstemps() a wrapper that always uses 0600 to call it. Later we will use git_mkstemps_mode() when creating pack files. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- cache.h | 4 ++++ path.c | 15 +++++++++++++-- wrapper.c | 10 ++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cache.h b/cache.h index d478eff1f3..0319637723 100644 --- a/cache.h +++ b/cache.h @@ -641,6 +641,10 @@ int git_mkstemp(char *path, size_t n, const char *template); int git_mkstemps(char *path, size_t n, const char *template, int suffix_len); +/* set default permissions by passing mode arguments to open(2) */ +int git_mkstemps_mode(char *pattern, int suffix_len, int mode); +int git_mkstemp_mode(char *pattern, int mode); + /* * NOTE NOTE NOTE!! * diff --git a/path.c b/path.c index ab2e3687ab..03d284ba8b 100644 --- a/path.c +++ b/path.c @@ -162,7 +162,7 @@ int git_mkstemps(char *path, size_t len, const char *template, int suffix_len) #undef TMP_MAX #define TMP_MAX 16384 -int gitmkstemps(char *pattern, int suffix_len) +int git_mkstemps_mode(char *pattern, int suffix_len, int mode) { static const char letters[] = "abcdefghijklmnopqrstuvwxyz" @@ -204,7 +204,7 @@ int gitmkstemps(char *pattern, int suffix_len) template[4] = letters[v % num_letters]; v /= num_letters; template[5] = letters[v % num_letters]; v /= num_letters; - fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, 0600); + fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode); if (fd > 0) return fd; /* @@ -226,6 +226,17 @@ int gitmkstemps(char *pattern, int suffix_len) return -1; } +int git_mkstemp_mode(char *pattern, int mode) +{ + /* mkstemp is just mkstemps with no suffix */ + return git_mkstemps_mode(pattern, 0, mode); +} + +int gitmkstemps(char *pattern, int suffix_len) +{ + return git_mkstemps_mode(pattern, suffix_len, 0600); +} + int validate_headref(const char *path) { struct stat st; diff --git a/wrapper.c b/wrapper.c index 0e3e20a3fd..673762fde9 100644 --- a/wrapper.c +++ b/wrapper.c @@ -204,6 +204,16 @@ int xmkstemp(char *template) return fd; } +int xmkstemp_mode(char *template, int mode) +{ + int fd; + + fd = git_mkstemp_mode(template, mode); + if (fd < 0) + die_errno("Unable to create temporary file"); + return fd; +} + /* * zlib wrappers to make sure we don't silently miss errors * at init time. From f80c7ae8fe9c0f3ce93c96a2dccaba34e456e33a Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:14 +0100 Subject: [PATCH 0151/3211] Use git_mkstemp_mode and xmkstemp_mode in odb_mkstemp, not chmod later. We used to create 0600 files, and then use chmod to set the group and other permission bits to the umask. This usually has the same effect as a normal file creation with a umask. But in the presence of ACLs, the group permission plays the role of the ACL mask: the "g" bits of newly created files are chosen according to default ACL mask of the directory, not according to the umask, and doing a chmod() on these "g" bits affect the ACL's mask instead of actual group permission. In other words, creating files with 0600 and then doing a chmod to the umask creates files which are unreadable by users allowed in the default ACL. To create the files without breaking ACLs, we let the umask do it's job at the file's creation time, and get rid of the later chmod. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 18 ++---------------- t/t1304-default-acl.sh | 2 +- wrapper.c | 10 +++++++--- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index e1d3adf405..539e75d56f 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -464,9 +464,6 @@ static int write_one(struct sha1file *f, return 1; } -/* forward declaration for write_pack_file */ -static int adjust_perm(const char *path, mode_t mode); - static void write_pack_file(void) { uint32_t i = 0, j; @@ -523,21 +520,17 @@ static void write_pack_file(void) } if (!pack_to_stdout) { - mode_t mode = umask(0); struct stat st; const char *idx_tmp_name; char tmpname[PATH_MAX]; - umask(mode); - mode = 0444 & ~mode; - idx_tmp_name = write_idx_file(NULL, written_list, nr_written, sha1); snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); free_pack_by_name(tmpname); - if (adjust_perm(pack_tmp_name, mode)) + if (adjust_shared_perm(pack_tmp_name)) die_errno("unable to make temporary pack file readable"); if (rename(pack_tmp_name, tmpname)) die_errno("unable to rename temporary pack file"); @@ -565,7 +558,7 @@ static void write_pack_file(void) snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", base_name, sha1_to_hex(sha1)); - if (adjust_perm(idx_tmp_name, mode)) + if (adjust_shared_perm(idx_tmp_name)) die_errno("unable to make temporary index file readable"); if (rename(idx_tmp_name, tmpname)) die_errno("unable to rename temporary index file"); @@ -2125,13 +2118,6 @@ static void get_object_list(int ac, const char **av) loosen_unused_packed_objects(&revs); } -static int adjust_perm(const char *path, mode_t mode) -{ - if (chmod(path, mode)) - return -1; - return adjust_shared_perm(path); -} - int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index 07dd6af99c..8472dbb44a 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -59,7 +59,7 @@ test_expect_failure 'Objects creation does not break ACLs with restrictive umask check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 ' -test_expect_failure 'git gc does not break ACLs with restrictive umask' ' +test_expect_success 'git gc does not break ACLs with restrictive umask' ' git gc && check_perms_and_acl .git/objects/pack/*.pack ' diff --git a/wrapper.c b/wrapper.c index 673762fde9..9c71b21242 100644 --- a/wrapper.c +++ b/wrapper.c @@ -277,10 +277,14 @@ int git_inflate(z_streamp strm, int flush) int odb_mkstemp(char *template, size_t limit, const char *pattern) { int fd; - + /* + * we let the umask do its job, don't try to be more + * restrictive except to remove write permission. + */ + int mode = 0444; snprintf(template, limit, "%s/%s", get_object_directory(), pattern); - fd = mkstemp(template); + fd = git_mkstemp_mode(template, mode); if (0 <= fd) return fd; @@ -289,7 +293,7 @@ int odb_mkstemp(char *template, size_t limit, const char *pattern) snprintf(template, limit, "%s/%s", get_object_directory(), pattern); safe_create_leading_directories(template); - return xmkstemp(template); + return xmkstemp_mode(template, mode); } int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) From 1d9740cb324f7f5d798ecfc259dc213b244ad9b7 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:15 +0100 Subject: [PATCH 0152/3211] git_mkstemps_mode: don't set errno to EINVAL on exit. When reaching the end of git_mkstemps_mode, at least one call to open() has been done, and errno has been set accordingly. Setting errno is therefore not necessary, and actually harmfull since callers can't distinguish e.g. permanent failure from ENOENT, which can just mean that we need to create the containing directory. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- path.c | 1 - 1 file changed, 1 deletion(-) diff --git a/path.c b/path.c index 03d284ba8b..12ef731ace 100644 --- a/path.c +++ b/path.c @@ -222,7 +222,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) } /* We return the null string if we can't find a unique file name. */ pattern[0] = '\0'; - errno = EINVAL; return -1; } From 5256b006312e4d06e11b49a8b128e9e550e54f31 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Mon, 22 Feb 2010 23:32:16 +0100 Subject: [PATCH 0153/3211] Use git_mkstemp_mode instead of plain mkstemp to create object files We used to unnecessarily give the read permission to group and others, regardless of the umask, which isn't serious because the objects are still protected by their containing directory, but isn't necessary either. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- sha1_file.c | 6 +++--- t/t1304-default-acl.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 657825e14e..3316f282c6 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2206,7 +2206,7 @@ int move_temp_to_file(const char *tmpfile, const char *filename) } out: - if (set_shared_perm(filename, (S_IFREG|0444))) + if (adjust_shared_perm(filename)) return error("unable to set permission to '%s'", filename); return 0; } @@ -2262,7 +2262,7 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) } memcpy(buffer, filename, dirlen); strcpy(buffer + dirlen, "tmp_obj_XXXXXX"); - fd = mkstemp(buffer); + fd = git_mkstemp_mode(buffer, 0444); if (fd < 0 && dirlen && errno == ENOENT) { /* Make sure the directory exists */ memcpy(buffer, filename, dirlen); @@ -2272,7 +2272,7 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) /* Try again */ strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX"); - fd = mkstemp(buffer); + fd = git_mkstemp_mode(buffer, 0444); } return fd; } diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index 8472dbb44a..cc30be4a65 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -54,7 +54,7 @@ test_expect_success 'Setup test repo' ' git commit -m "init" ' -test_expect_failure 'Objects creation does not break ACLs with restrictive umask' ' +test_expect_success 'Objects creation does not break ACLs with restrictive umask' ' # SHA1 for empty blob check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 ' From e1327ed5fbaca0af44db89d60c33b641e2f21ee1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 22 Feb 2010 20:05:44 -0500 Subject: [PATCH 0154/3211] add-interactive: fix bogus diff header line ordering When we look at a patch for adding hunks interactively, we first split it into a header and a list of hunks. Some of the header lines, such as mode changes and deletion, however, become their own selectable hunks. Later when we reassemble the patch, we simply concatenate the header and the selected hunks. This leads to patches like this: diff --git a/file b/file index d95f3ad..0000000 --- a/file +++ /dev/null deleted file mode 100644 @@ -1 +0,0 @@ -content Notice how the deletion comes _after_ the ---/+++ lines, when it should come before. In many cases, we can get away with this as git-apply accepts the slightly bogus input. However, in the specific case of a deletion line that is being applied via "apply -R", this malformed patch triggers an assert in git-apply. This comes up when discarding a deletion via "git checkout -p". Rather than try to make git-apply accept our odd input, let's just reassemble the patch in the correct order. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 24 +++++++++++++++++++++++- t/t2016-checkout-patch.sh | 8 ++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 75b71967a7..7493e68b84 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -957,6 +957,28 @@ sub coalesce_overlapping_hunks { return @out; } +sub reassemble_patch { + my $head = shift; + my @patch; + + # Include everything in the header except the beginning of the diff. + push @patch, (grep { !/^[-+]{3}/ } @$head); + + # Then include any headers from the hunk lines, which must + # come before any actual hunk. + while (@_ && $_[0] !~ /^@/) { + push @patch, shift; + } + + # Then begin the diff. + push @patch, grep { /^[-+]{3}/ } @$head; + + # And then the actual hunks. + push @patch, @_; + + return @patch; +} + sub color_diff { return map { colored((/^@/ ? $fraginfo_color : @@ -1454,7 +1476,7 @@ sub patch_update_file { if (@result) { my $fh; - my @patch = (@{$head->{TEXT}}, @result); + my @patch = reassemble_patch($head->{TEXT}, @result); my $apply_routine = $patch_mode_flavour{APPLY}; &$apply_routine(@patch); refresh(); diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh index 4d1c2e9e09..2144184d79 100755 --- a/t/t2016-checkout-patch.sh +++ b/t/t2016-checkout-patch.sh @@ -66,6 +66,14 @@ test_expect_success 'git checkout -p HEAD^' ' verify_state dir/foo parent parent ' +test_expect_success 'git checkout -p handles deletion' ' + set_state dir/foo work index && + rm dir/foo && + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo index index +' + # The idea in the rest is that bar sorts first, so we always say 'y' # first and if the path limiter fails it'll apply to bar instead of # dir/foo. There's always an extra 'n' to reject edits to dir/foo in From f965c525a4d04837b1b7ed43e3fa2dc8c0df4e2b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 23 Feb 2010 15:02:37 -0500 Subject: [PATCH 0155/3211] move encode_in_pack_object_header() to a better place Commit 1b22b6c897 made duplicated versions of encode_header() into a common version called encode_in_pack_object_header(). There is however a better location that sha1_file.c for such a function though, as sha1_file.c contains nothing related to the creation of packs, and it is quite populated already. Also the comment that was moved to the header file should really remain near the function as it covers implementation details and provides no information about the actual function interface. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- cache.h | 8 -------- pack-write.c | 27 +++++++++++++++++++++++++++ pack.h | 1 + sha1_file.c | 20 -------------------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/cache.h b/cache.h index 2654b608ba..d478eff1f3 100644 --- a/cache.h +++ b/cache.h @@ -911,14 +911,6 @@ extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsign extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); -/* - * The per-object header is a pretty dense thing, which is - * - first byte: low four bits are "size", then three bits of "type", - * and the high bit is "size continues". - * - each byte afterwards: low seven bits are size continuation, - * with the high bit being "size continues" - */ -int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr); /* Dumb servers support */ extern int update_server_info(int); diff --git a/pack-write.c b/pack-write.c index 9f47cf9961..a905ca4486 100644 --- a/pack-write.c +++ b/pack-write.c @@ -253,3 +253,30 @@ char *index_pack_lockfile(int ip_out) } return NULL; } + +/* + * The per-object header is a pretty dense thing, which is + * - first byte: low four bits are "size", then three bits of "type", + * and the high bit is "size continues". + * - each byte afterwards: low seven bits are size continuation, + * with the high bit being "size continues" + */ +int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr) +{ + int n = 1; + unsigned char c; + + if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) + die("bad type %d", type); + + c = (type << 4) | (size & 15); + size >>= 4; + while (size) { + *hdr++ = c | 0x80; + c = size & 0x7f; + size >>= 7; + n++; + } + *hdr = c; + return n; +} diff --git a/pack.h b/pack.h index b759a23d4d..d268c014c9 100644 --- a/pack.h +++ b/pack.h @@ -60,6 +60,7 @@ extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off extern int verify_pack(struct packed_git *); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); extern char *index_pack_lockfile(int fd); +extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *); #define PH_ERROR_EOF (-1) #define PH_ERROR_PACK_SIGNATURE (-2) diff --git a/sha1_file.c b/sha1_file.c index f3c9823c56..657825e14e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1475,26 +1475,6 @@ const char *packed_object_info_detail(struct packed_git *p, } } -int encode_in_pack_object_header(enum object_type type, uintmax_t size, unsigned char *hdr) -{ - int n = 1; - unsigned char c; - - if (type < OBJ_COMMIT || type > OBJ_REF_DELTA) - die("bad type %d", type); - - c = (type << 4) | (size & 15); - size >>= 4; - while (size) { - *hdr++ = c | 0x80; - c = size & 0x7f; - size >>= 7; - n++; - } - *hdr = c; - return n; -} - static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long *sizep) { From 16758621d5a4a78eed7c183b60bf7ebaeaf305c5 Mon Sep 17 00:00:00 2001 From: Bert Wesarg Date: Tue, 23 Feb 2010 21:11:12 +0100 Subject: [PATCH 0156/3211] Documentation: mention conflict marker size argument (%L) for merge driver 23a64c9e (conflict-marker-size: new attribute, 2010-01-16) introduced the new attribute and also pass the conflict marker size as %L to merge driver commands. This documents the substitution. Signed-off-by: Bert Wesarg Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index b396a871b3..d892e642ed 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -511,7 +511,8 @@ command to run to merge ancestor's version (`%O`), current version (`%A`) and the other branches' version (`%B`). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is -built. +built. Additionally, %L will be replaced with the conflict marker +size (see below). The merge driver is expected to leave the result of the merge in the file named with `%A` by overwriting it, and exit with zero From 53a52ff33d42faf2dcceea5b33f8049bd9416555 Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Tue, 23 Feb 2010 14:33:48 +0200 Subject: [PATCH 0157/3211] Allow '+', '-' and '.' in remote helper names According to relevant RFCs, in addition to alphanumerics, the following characters are valid in URL scheme parts: '+', '-' and '.', but currently only alphanumerics are allowed in remote helper names. Allow those three characters in remote helper names (both 'foo://' and 'foo::' syntax). Signed-off-by: Ilari Liusvaara Signed-off-by: Junio C Hamano --- transport.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/transport.c b/transport.c index 3846aacb47..d3ec449859 100644 --- a/transport.c +++ b/transport.c @@ -872,6 +872,21 @@ static int is_file(const char *url) return S_ISREG(buf.st_mode); } +static int isurlschemechar(int first_flag, int ch) +{ + /* + * The set of valid URL schemes, as per STD66 (RFC3986) is + * '[A-Za-z][A-Za-z0-9+.-]*'. But use sightly looser check + * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version + * of check used '[A-Za-z0-9]+' so not to break any remote + * helpers. + */ + int alphanumeric, special; + alphanumeric = ch > 0 && isalnum(ch); + special = ch == '+' || ch == '-' || ch == '.'; + return alphanumeric || (!first_flag && special); +} + static int is_url(const char *url) { const char *url2, *first_slash; @@ -896,7 +911,7 @@ static int is_url(const char *url) */ url2 = url; while (url2 < first_slash - 1) { - if (!isalnum((unsigned char)*url2)) + if (!isurlschemechar(url2 == url, (unsigned char)*url2)) return 0; url2++; } @@ -929,7 +944,7 @@ struct transport *transport_get(struct remote *remote, const char *url) if (url) { const char *p = url; - while (isalnum(*p)) + while (isurlschemechar(p == url, *p)) p++; if (!prefixcmp(p, "::")) helper = xstrndup(url, p - url); From aa0945701e39cf099ca9c28f79e239359781e4f6 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 23 Feb 2010 12:42:56 +0100 Subject: [PATCH 0158/3211] Print RUNTIME_PREFIX warning only when GIT_TRACE is set When RUNTIME_PREFIX is enabled, the installation prefix is derived by trying a limited set of known locations where the git executable can reside. If none of these is found, a warning is emitted. When git is built in a directory that matches neither of these known names, the warning would always be emitted when the uninstalled executable is run. This is a problem on Windows, where gitk picks the uninstalled git when invoked from the build directory and gets confused by the warning. Print the warning only when GIT_TRACE is set. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- exec_cmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec_cmd.c b/exec_cmd.c index 408e4e55e1..b2c07c70ce 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -28,7 +28,7 @@ const char *system_path(const char *path) !(prefix = strip_path_suffix(argv0_path, BINDIR)) && !(prefix = strip_path_suffix(argv0_path, "git"))) { prefix = PREFIX; - fprintf(stderr, "RUNTIME_PREFIX requested, " + trace_printf("RUNTIME_PREFIX requested, " "but prefix computation failed. " "Using static fallback '%s'.\n", prefix); } From 689b8c290db9d5880699dd3538134daffc1c55d0 Mon Sep 17 00:00:00 2001 From: Bert Wesarg Date: Tue, 23 Feb 2010 21:11:53 +0100 Subject: [PATCH 0159/3211] rerere: fix memory leak if rerere images can't be read Signed-off-by: Bert Wesarg Signed-off-by: Junio C Hamano --- rerere.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rerere.c b/rerere.c index d1d3e75395..a59f74f76c 100644 --- a/rerere.c +++ b/rerere.c @@ -364,7 +364,7 @@ static int find_conflict(struct string_list *conflict) static int merge(const char *name, const char *path) { int ret; - mmfile_t cur, base, other; + mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; mmbuffer_t result = {NULL, 0}; if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0) @@ -372,8 +372,10 @@ static int merge(const char *name, const char *path) if (read_mmfile(&cur, rerere_path(name, "thisimage")) || read_mmfile(&base, rerere_path(name, "preimage")) || - read_mmfile(&other, rerere_path(name, "postimage"))) - return 1; + read_mmfile(&other, rerere_path(name, "postimage"))) { + ret = 1; + goto out; + } ret = ll_merge(&result, path, &base, &cur, "", &other, "", 0); if (!ret) { FILE *f = fopen(path, "w"); @@ -387,6 +389,7 @@ static int merge(const char *name, const char *path) strerror(errno)); } +out: free(cur.ptr); free(base.ptr); free(other.ptr); From 29b67543d3060a08bee601dbad91dd400e833052 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 22 Feb 2010 08:35:46 -0600 Subject: [PATCH 0160/3211] am: remove rebase-apply directory before gc When git am does an automatic gc it doesn't clean up the rebase-apply directory until after this has finished. This means that if the user aborts the gc then future am or rebase operations will report that an existing operation is in progress, which is undesirable and confusing. Reported by Mark Brown through http://bugs.debian.org/570966 Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- git-am.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git-am.sh b/git-am.sh index 3c08d53161..ebfbee59d3 100755 --- a/git-am.sh +++ b/git-am.sh @@ -776,6 +776,5 @@ do go_next done -git gc --auto - rm -fr "$dotest" +git gc --auto From c63437cbd79e4c11ddcd06cc59f81401ae393794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Tue, 23 Feb 2010 22:02:57 +0100 Subject: [PATCH 0161/3211] bash: improve aliased command recognition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support completion for aliases, the completion script tries to figure out which git command is invoked by an alias. Its implementation in __git_aliased_command() is rather straightforward: it returns the first word from the alias. For simple aliases starting with the git command (e.g. alias.last = cat-file commit HEAD) this gives the right results. Unfortunately, it does not work with shell command aliases, which can get rather complex, as illustrated by one of Junio's aliases: [alias] lgm = "!sh -c 'GIT_NOTES_REF=refs/notes/amlog git log \"$@\" || :' -" In this case the current implementation returns "!sh" as the aliased git command, which is obviosly wrong. The full parsing of a shell command alias like that in the completion code is clearly unfeasible. However, we can easily improve on aliased command recognition by eleminating stuff that is definitely not a git command: shell commands (anything starting with '!'), command line options (anything starting with '-'), environment variables (anything with a '=' in it), and git itself. This way the above alias would be handled correctly, and the completion script would correctly recognize "log" as the aliased git command. Of course, this solution is not perfect either, and could be fooled easily. It's not hard to construct an alias, in which a word does not match any of these filter patterns, but is still not a git command (e.g. by setting an environment variable to a value which contains spaces). It may even return false positives, when the output of a git command is piped into an other git command, and the second gets the command line options via $@, but options for the first one are offered. However, the following patches will enable the user to supply custom completion scripts for aliases, which can be used to remedy these problematic cases. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index fe93747c93..78c4983983 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -625,10 +625,15 @@ __git_aliased_command () local word cmdline=$(git --git-dir="$(__gitdir)" \ config --get "alias.$1") for word in $cmdline; do - if [ "${word##-*}" ]; then - echo $word + case "$word" in + \!*) : shell command alias ;; + -*) : option ;; + *=*) : setting env ;; + git) : git itself ;; + *) + echo "$word" return - fi + esac done } From 424cce832d180843ab9b54eec3a7643948fc1afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Tue, 23 Feb 2010 22:02:58 +0100 Subject: [PATCH 0162/3211] bash: support user-supplied completion scripts for user's git commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bash completion script already provides support to complete aliases, options and refs for aliases (if the alias can be traced back to a supported git command by __git_aliased_command()), and the user's custom git commands, but it does not support the options of the user's custom git commands (of course; how could it know about the options of a custom git command?). Users of such custom git commands could extend git's bash completion script by writing functions to support their commands, but they might have issues with it: they might not have the rights to modify a system-wide git completion script, and they will need to track and merge upstream changes in the future. This patch addresses this by providing means for users to supply custom completion scriplets for their custom git commands without modifying the main git bash completion script. Instead of having a huge hard-coded list of command-completion function pairs (in _git()), the completion script will figure out which completion function to call based on the command's name. That is, when completing the options of 'git foo', the main completion script will check whether the function '_git_foo' is declared, and if declared, it will invoke that function to perform the completion. If such a function is not declared, it will fall back to complete file names. So, users will only need to provide this '_git_foo' completion function in a separate file, source that file, and it will be used the next time they press TAB after 'git foo '. There are two git commands (stage and whatchanged), for which the completion functions of other commands were used, therefore they got their own completion function. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 67 +++++--------------------- 1 file changed, 12 insertions(+), 55 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 78c4983983..2ac356705b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1439,6 +1439,11 @@ _git_send_email () COMPREPLY=() } +_git_stage () +{ + _git_add +} + __git_config_get_set_variables () { local prevword word config_file= c=$COMP_CWORD @@ -2170,6 +2175,11 @@ _git_tag () esac } +_git_whatchanged () +{ + _git_log +} + _git () { local i c=1 command __git_dir @@ -2209,61 +2219,8 @@ _git () local expansion=$(__git_aliased_command "$command") [ "$expansion" ] && command="$expansion" - case "$command" in - am) _git_am ;; - add) _git_add ;; - apply) _git_apply ;; - archive) _git_archive ;; - bisect) _git_bisect ;; - bundle) _git_bundle ;; - branch) _git_branch ;; - checkout) _git_checkout ;; - cherry) _git_cherry ;; - cherry-pick) _git_cherry_pick ;; - clean) _git_clean ;; - clone) _git_clone ;; - commit) _git_commit ;; - config) _git_config ;; - describe) _git_describe ;; - diff) _git_diff ;; - difftool) _git_difftool ;; - fetch) _git_fetch ;; - format-patch) _git_format_patch ;; - fsck) _git_fsck ;; - gc) _git_gc ;; - grep) _git_grep ;; - help) _git_help ;; - init) _git_init ;; - log) _git_log ;; - ls-files) _git_ls_files ;; - ls-remote) _git_ls_remote ;; - ls-tree) _git_ls_tree ;; - merge) _git_merge;; - mergetool) _git_mergetool;; - merge-base) _git_merge_base ;; - mv) _git_mv ;; - name-rev) _git_name_rev ;; - notes) _git_notes ;; - pull) _git_pull ;; - push) _git_push ;; - rebase) _git_rebase ;; - remote) _git_remote ;; - replace) _git_replace ;; - reset) _git_reset ;; - revert) _git_revert ;; - rm) _git_rm ;; - send-email) _git_send_email ;; - shortlog) _git_shortlog ;; - show) _git_show ;; - show-branch) _git_show_branch ;; - stash) _git_stash ;; - stage) _git_add ;; - submodule) _git_submodule ;; - svn) _git_svn ;; - tag) _git_tag ;; - whatchanged) _git_log ;; - *) COMPREPLY=() ;; - esac + local completion_func="_git_${command//-/_}" + declare -F $completion_func >/dev/null && $completion_func } _gitk () From 8024ea60db290dd64db294e1019a0ada64ce1770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Tue, 23 Feb 2010 22:02:59 +0100 Subject: [PATCH 0163/3211] bash: support user-supplied completion scripts for aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell command aliases can get rather complex, and the completion script can not always determine correctly the git command invoked by such an alias. For such cases users might want to provide custom completion scripts the same way like for their custom commands made possible by the previous patch. The current completion script does not allow this, because if it encounters an alias, then it will unconditionally perform completion for the aliased git command (in case it can determine the aliased git command, of course). With this patch the completion script will first search for a completion function for the command given on the command line, be it a git command, a custom git command of the user, or an alias, and invoke that function to perform the completion. This has no effect on git commands, because they can not be aliased anyway. If it is an alias and there is a completion function for that alias (e.g. _git_foo() for the alias 'foo'), then it will be invoked to perform completion, allowing users to provide custom completion functions for aliases. If such a completion function can not be found, only then will the completion script check whether the command given on the command line is an alias or not, and proceed as usual (i.e. find out the aliased git command and provide completion for it). Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2ac356705b..8593fd707b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2216,11 +2216,14 @@ _git () return fi - local expansion=$(__git_aliased_command "$command") - [ "$expansion" ] && command="$expansion" - local completion_func="_git_${command//-/_}" - declare -F $completion_func >/dev/null && $completion_func + declare -F $completion_func >/dev/null && $completion_func && return + + local expansion=$(__git_aliased_command "$command") + if [ -n "$expansion" ]; then + completion_func="_git_${expansion//-/_}" + declare -F $completion_func >/dev/null && $completion_func + fi } _gitk () From 66729509457f11b15216c3cdfdbb2f34eddfbe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Tue, 23 Feb 2010 22:03:00 +0100 Subject: [PATCH 0164/3211] bash: completion for gitk aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gitk aliases either start with "!gitk", or look something like "!sh -c FOO=bar gitk", IOW they contain the "gitk" word. With this patch the completion script will recognize these cases and will offer gitk's options. Just like the earlier change improving on aliased command recognition, this change can also be fooled easily by some complex aliases, but users of such aliases could remedy it with custom completion functions. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8593fd707b..3029f160b0 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -626,6 +626,10 @@ __git_aliased_command () config --get "alias.$1") for word in $cmdline; do case "$word" in + \!gitk|gitk) + echo "gitk" + return + ;; \!*) : shell command alias ;; -*) : option ;; *=*) : setting env ;; @@ -1087,6 +1091,11 @@ _git_gc () COMPREPLY=() } +_git_gitk () +{ + _gitk +} + _git_grep () { __git_has_doubledash && return From c54b74afb78e385d645447711da2201ad45151fa Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Wed, 24 Feb 2010 20:50:20 +0800 Subject: [PATCH 0165/3211] Documentation/git-clone: mention progress in -v After 5a518ad (clone: use --progress to force progress reporting), -v/--verbose did not affect whether progress status was reported to stderr, and users accustomed to using -v to do so since 21188b1 (Implement git clone -v) may be confused. Mitigate such risks by stating -v does not affect progress in the documentation. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- Documentation/git-clone.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index f43c8b2c08..aa89632a37 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -102,7 +102,8 @@ objects from the source repository into a pack in the cloned repository. --verbose:: -v:: - Run verbosely. + Run verbosely. Does not affect the reporting of progress status + to the standard error stream. --progress:: Progress status is reported on the standard error stream From 409b8d82dfc216ce5e89c81fdc2431dbf897c2d7 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Wed, 24 Feb 2010 20:50:21 +0800 Subject: [PATCH 0166/3211] Documentation/git-pull: put verbosity options before merge/fetch ones After 3f7a9b5 (Documentation/git-pull.txt: Add subtitles above included option files, Thu Oct 22 2009), the -q/-v options were mentioned only for the merge options section, giving the impression that git-fetch did not take those arguments. Follow 90e4311 (git-pull: do not mention --quiet and --verbose twice, Mon Sep 7 2009) and hide -q/-v for merge options, while mentioning -q/-v before the merge- and fetch-specific options. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- Documentation/git-pull.txt | 8 ++++++++ Documentation/merge-options.txt | 2 ++ 2 files changed, 10 insertions(+) diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 31f42ea21a..d47f9ddd89 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -31,6 +31,14 @@ in a state that is hard to back out of in the case of a conflict. OPTIONS ------- +-q:: +--quiet:: + Pass --quiet to git-fetch and git-merge. + +-v:: +--verbose:: + Pass --verbose to git-fetch and git-merge. + Options related to merging ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 3b83dba1a0..37ce9a17fc 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -67,6 +67,7 @@ option can be used to override --squash. Synonyms to --stat and --no-stat; these are deprecated and will be removed in the future. +ifndef::git-pull[] -q:: --quiet:: Operate quietly. @@ -74,6 +75,7 @@ option can be used to override --squash. -v:: --verbose:: Be verbose. +endif::git-pull[] -X