From 9bc454df08ca2a27b51ac0ab9ff8f154e51b8698 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 19 Jan 2010 05:25:57 +0100 Subject: 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 0f5022eed..da61f20e8 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. */ -- cgit v1.2.1 From ffbc5dc2d0cbdbd63a4ae04dc2cc1ebf385fcc25 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 19 Jan 2010 05:25:58 +0100 Subject: 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 8704d0019..1a9c1c749 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 de896c948..2ebda9782 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 -- cgit v1.2.1 From 80235ba79ef43349f455cce869397b3e726f4058 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Jan 2010 20:09:06 -0800 Subject: "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 | 46 ++++++++++++++++++++++++++++++++++++++++------ grep.h | 2 ++ revision.c | 3 ++- t/t7002-grep.sh | 10 +++++++++- 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/builtin-grep.c b/builtin-grep.c index 529461fb8..d57c4d9be 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 cd97ded4d..c53ad9fa0 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 bdadf2c0c..eee99f31f 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; - - if (opt->all_match) - opt->extended = 1; + struct grep_expr *header_expr = NULL; + + 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 75370f60d..e39e5146d 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 25fa14d93..c84243d7b 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 76c5e091b..6baa4461f 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 && -- cgit v1.2.1 From 21528abc3612e3dae0c195b81848dd9aa18d2a02 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:44:47 -0600 Subject: 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 fd7f51e8b..bad8f804c 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) -- cgit v1.2.1 From daa99a917293d962ce565a04a09b0de08e32b8ba Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:45:54 -0600 Subject: 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 bad8f804c..1670ee87c 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) -- cgit v1.2.1 From 7a1894e3039bc554e2a193c895b2ab5300b7f823 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:16 -0600 Subject: 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 1670ee87c..8cf6383a9 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 -- cgit v1.2.1 From 066ddda6cdf2652a85430a979dbf1cd65fbfad0f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:37 -0600 Subject: 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 8cf6383a9..593801ae1 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 -- cgit v1.2.1 From 3e6577b45e755b53c9cccb24d75916fa3f2e1916 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:46:59 -0600 Subject: 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 593801ae1..98810b246 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 -- cgit v1.2.1 From 75df714487fd5d40b370f2d0f993f347d0170599 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:47:25 -0600 Subject: 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 6f0f64753..5ebd5b248 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 -- cgit v1.2.1 From beeb4564bb7133fd12e6811a701686982b10cc2f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:49:33 -0600 Subject: 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 5ebd5b248..1811a9e74 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) -- cgit v1.2.1 From 30248886ce89e5467ce734908775ed90b9138e99 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:51:24 -0600 Subject: 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 1811a9e74..8d3299a8f 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 -- cgit v1.2.1 From c373991375a4903dbb9bea69e2ce11ce819253e2 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:52:11 -0600 Subject: 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 8d3299a8f..a3774f717 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) -- cgit v1.2.1 From dfea575017ddc2ae7c9e8dcb978f4557f07715d3 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:52:49 -0600 Subject: 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 8df8f88be..7b3acb766 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 a3774f717..df361737b 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 -- cgit v1.2.1 From 1b22c99c14b30f0add6f108a5883f003a2d81e01 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:54:23 -0600 Subject: 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 df361737b..630687f26 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) -- cgit v1.2.1 From f2fabbf76e458010852ac7ced30594c8ab96b9fd Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Tue, 26 Jan 2010 09:57:15 -0600 Subject: 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 | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 630687f26..45b1f54a6 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: +ifdef PRINT_HEADER_DEPENDENCIES +$(C_OBJ): %.o: %.c FORCE + echo $^ +$(ASM_OBJ): %.o: %.S FORCE + echo $^ + +ifndef CHECK_HEADER_DEPENDENCIES +$(error cannot print header dependencies during a normal build. \ +Please set CHECK_HEADER_DEPENDENCIES and try again) +endif +endif + +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) $< -%.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) $< +endif -ifdef COMPUTE_HEADER_DEPENDENCIES +%.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 := $(wildcard $(foreach f,$(OBJECTS),$(dir f)deps/$(notdir $f).d)) -ifneq ($(dep_files),) -include $(dep_files) +dep_files_present := $(wildcard $(dep_files)) +ifneq ($(dep_files_present),) +include $(dep_files_present) 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. # -- cgit v1.2.1 From 13be3e31f1775bd496fc5bc1ab5b745052f4d07d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Jan 2010 22:03:24 -0800 Subject: 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 17901e244..fee44d8a4 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 936d98ba2..0be4b5f00 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 9b7a3ad1c..3244ebb5c 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 527781728..c5ab7835e 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 6442f710b..d20ed61b4 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: 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 630ceddf0..8fd15f6df 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 -- cgit v1.2.1 From b9b727ddb3c9e005bc4e9af0b990b6ef06d7f621 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Jan 2010 15:59:09 -0800 Subject: 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 d40262159..4f72a3d89 --- 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 -- cgit v1.2.1 From c32056e0ef193002f80d75fd795e156ddf65c4ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Jan 2010 16:04:25 -0800 Subject: 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 afb4b6686..06c3c9176 --- 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 () { -- cgit v1.2.1 From 46bac904581798eaf0f07f91701c2c72219e207f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 11:46:53 -0800 Subject: 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 af08c8f45..6bbeb2401 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 -- cgit v1.2.1 From ec5e0bb860bf98008d51b8235d19ed3407141295 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 15:23:53 -0600 Subject: 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 45b1f54a6..c9391390b 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. \ -- cgit v1.2.1 From 010acc151981ebfb0e369ffdeaef63803c58ef2b Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 31 Jan 2010 15:37:25 -0600 Subject: 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 c9391390b..93e1a9218 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 $@ -- cgit v1.2.1 From 9517e6b84357252e1882091343661c34d978771e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 3 Feb 2010 21:23:18 -0800 Subject: Typofixes outside documentation area begining -> beginning canonicalizations -> canonicalization comand -> command dewrapping -> unwrapping dirtyness -> dirtiness DISCLAMER -> DISCLAIMER explicitely -> explicitly feeded -> fed impiled -> implied madatory -> mandatory mimick -> mimic preceeding -> preceding reqeuest -> request substition -> substitution Signed-off-by: Junio C Hamano --- 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 2a1004d02..3af4ae0c2 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 590684200..a933eaa04 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 8d16832f7..e0d5caa61 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 8459aec8e..4675f6054 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 9f13caa76..35a709e63 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 5fc1670be..0f949fc42 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 20054e4d0..a37cf6af0 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 5782d80e2..3a5da4ab0 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 6c2bd9771..3769b6f57 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 381cc8d4f..9038057a7 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 0173abeef..4105bf354 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 79aa10471..e166d5380 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 e8df55d2f..970fe434e 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 503a8c2bd..f3fcbe023 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 710e2f300..fac34f77a 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 fe476cb61..92713d16d 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 107742891..f82297202 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); } -- cgit v1.2.1 From 4f41b611481bad08319966f7787fc7c4c7bfaa52 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:37 -0800 Subject: 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 b26c28133..a1280dd83 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 cf2d8f7fa..bfd231243 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 fb342090e..a29171ada 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! */ -- cgit v1.2.1 From ae6a5609c025d9ac79e54a3a052704e25d885314 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Fri, 5 Feb 2010 12:57:38 -0800 Subject: 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 a1280dd83..8994859c8 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 8ed4a6fea..dbd8b7bcc 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 491e7141b..e70ee094a 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 3edbf5717..6bb336626 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 bfd231243..0d9534083 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 a29171ada..65ccb1c60 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 df151813f..dc464d78b 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"; -- cgit v1.2.1 From 0c499ea60fda716198c76f2d5febe3998d302afb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:39 -0800 Subject: 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 8fffdbf20..2478e1851 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) { -- cgit v1.2.1 From 185c04e041fb33191c5828339381fd8c4058a43a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:40 -0800 Subject: 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 78c0e69cd..325ec6e2c 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(); -- cgit v1.2.1 From 38a81b4e82ebf57549f2fa082b329c36dffc0b18 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:41 -0800 Subject: 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 325ec6e2c..ff3f11731 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; } -- cgit v1.2.1 From 6d525d389fbef814b11e41f196e6656f2e95f412 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 5 Feb 2010 12:57:42 -0800 Subject: 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 ff3f11731..da1c26b6b 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 64f66c94f..c3cf397b0 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: 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 79aa10471..0005df3a5 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 710e2f300..b38cbee14 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; } -- cgit v1.2.1 From 0455ec0330fd29146d6e16189cc9262566cc566d Mon Sep 17 00:00:00 2001 From: Aaron Crane Date: Sat, 6 Feb 2010 18:26:24 +0000 Subject: 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 ddfcb3d14..8bcd875a6 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 4853bf7a0..9e03eee45 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 363345fae..b572ce3ab 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 -- cgit v1.2.1 From 1123c67ceee2f310b08ab5d67b076ef04ab59bfc Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 6 Feb 2010 23:44:15 -0500 Subject: 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 26d4deb1c..63d4b95b0 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 7144f815c..0b583cbfc 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 -- cgit v1.2.1 From 9c898a18ea37aa04a84c3a2d18794cf892862702 Mon Sep 17 00:00:00 2001 From: Heiko Voigt Date: Sun, 7 Feb 2010 22:47:56 +0100 Subject: 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 85fbc021f..549f59ba7 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 -- cgit v1.2.1 From 6b3fa7e7d13a3c8fddba43ad6e8acb314d1985ac Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 9 Feb 2010 18:01:29 -0800 Subject: 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 c3cf397b0..7240fabfe 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' ' -- cgit v1.2.1 From 466dbc42f58623d4341d6b1171b5cc13e2b700df Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 10 Feb 2010 09:34:12 -0800 Subject: 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 da1c26b6b..a5543f991 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 7240fabfe..17bcb0b04 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: 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 8ac86f094..83e5c0a75 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; -- cgit v1.2.1 From dd9314cc2a2f353bf9438db14cbbf02a1c219bda Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:06:43 -0500 Subject: 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 83e5c0a75..012c64644 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) -- cgit v1.2.1 From 08565bdb4b5d1da597ed8b90d1a23729f7c006d0 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:07:06 -0500 Subject: 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 012c64644..77e4f4eed 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) -- cgit v1.2.1 From 97915544f84132b210a336d5877fafd7cb104abe Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:08:15 -0500 Subject: 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 77e4f4eed..ad612497a 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); } -- cgit v1.2.1 From 4d128884fbcce6c7effc729e1c52f06ce17037ee Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 11 Feb 2010 16:19:37 -0500 Subject: 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 ad612497a..eff52687a 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); } -- cgit v1.2.1 From 67d176300c0a79d5cf65402c641fcec7c5388f29 Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Fri, 12 Feb 2010 20:36:12 +0900 Subject: 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 de8114bac..3527b56d5 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; -- cgit v1.2.1 From 88d9d45d071379e81e585faa95e4f28414d7d973 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 10 Feb 2010 02:11:49 +0100 Subject: 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 0f25ba7e3..8f9a2412f 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 0e39bb61e..fb184ba18 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 6e9baf8b3..39a064b53 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 27afcf697..d3ae969f6 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; -- cgit v1.2.1 From 40dae3094db123be664c69b356b6e4f4e33d80e6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:29:27 -0800 Subject: 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 a5a83f146..3698e822c 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. */ -- cgit v1.2.1 From 20322e0b552b9860377c162675ce5c49bfd32e83 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:38:42 -0800 Subject: 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 3698e822c..d68977ee6 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) -- cgit v1.2.1 From 5cdd628c84d808feb6ec407b2ecd74dfea63f865 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 11:57:08 -0800 Subject: 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 d68977ee6..b9b03e14d 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, -- cgit v1.2.1 From 88fb7f27f6036028b3470e0e2a0efed1d9c7ee78 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 12:05:44 -0800 Subject: 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 b9b03e14d..62be1bbfd 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; -- cgit v1.2.1 From 318721e3acd8f9c06fde0d528b7547a99f65c4a1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 15:01:37 -0800 Subject: 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 000000000..b0bf8366c --- /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 577e1fd20..a668143d7 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 7b9bde663..00e77229d 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 -- cgit v1.2.1 From 9b2504831859ffdffbe15b9a6a7b7ede37557b06 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Feb 2010 15:04:00 -0800 Subject: 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 000000000..ab0f9764d --- /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 577e1fd20..a668143d7 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 7b9bde663..9fadb0afe 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 -- cgit v1.2.1 From 59332d13b2b23840452180368914921bffe9bfbc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 6 Feb 2010 10:40:08 -0800 Subject: 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 26d4deb1c..0ef849cb8 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 4c3028c09..b3e23f104 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 7144f815c..bf4d4dcb2 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 -- cgit v1.2.1 From 0ab1faae39173be6126364461c1be86542e6b17d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:09 +0100 Subject: 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 023adce98..47e38a10a 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). */ -- cgit v1.2.1 From a7e7eff66206829f7752c565198dbe6f40ef72a0 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:10 +0100 Subject: 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 47e38a10a..4ee4fec23 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 a1421e351..d745ed12d 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 d493cade2..076b918b5 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); } -- cgit v1.2.1 From 3b78cdbe693092a58d9724deb14bd7be0dd4d7b3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:11 +0100 Subject: 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 5d9604b81..18aad5389 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 -- cgit v1.2.1 From 709f79b0894859a6624e99b3a0c4714dd4ece494 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:12 +0100 Subject: 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 4ee4fec23..3f4ae3534 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 d745ed12d..6b527991b 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); -- cgit v1.2.1 From 2626b5367047d8b8d5c4555ec6b579ed37a6625d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:13 +0100 Subject: 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 3f4ae3534..2c0d14ed8 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 6b527991b..5f2285217 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); -- cgit v1.2.1 From 1ec666b092d1df5691cd4d38f20aaeadd5049aa2 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:14 +0100 Subject: 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 2c0d14ed8..2e82d7198 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 5f2285217..9e6685522 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); -- cgit v1.2.1 From 9b391f218a5b732a5a8abae87d3165e97fe2f6f6 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:15 +0100 Subject: 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 2e82d7198..a0a85b4da 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 9e6685522..0041aecae 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); -- cgit v1.2.1 From 73f77b909f87fcaece42ec50d8d0c1c35efbf947 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:16 +0100 Subject: 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 a0a85b4da..eabd6f30c 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 0041aecae..213191290 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); -- cgit v1.2.1 From 61a7cca0c6504aee7bae7837582230561bdb81d4 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:17 +0100 Subject: 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 eabd6f30c..b576f7e62 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 213191290..c49b7a512 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 */ -- cgit v1.2.1 From cd30539214bb09881b84c796a50d30e409dee3fa Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:18 +0100 Subject: 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 b576f7e62..08a369af8 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 c49b7a512..12acc38b0 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 076b918b5..f999485a5 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); -- cgit v1.2.1 From 73f464b5f3fe4dd5109b9fb9e58c1fe55393902d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:19 +0100 Subject: 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 08a369af8..dc4e4f619 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 12acc38b0..20d6e171f 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 -- cgit v1.2.1 From cd067d3bf4ea3f89969cd143be3e281e1c5ac58a Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:20 +0100 Subject: 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 + contrib/examples/git-notes.sh | 121 ++++++++++++++++++ git-notes.sh | 121 ------------------ git.c | 1 + t/t3301-notes.sh | 98 +++++++++------ 8 files changed, 471 insertions(+), 161 deletions(-) create mode 100644 builtin-notes.c create mode 100755 contrib/examples/git-notes.sh delete mode 100755 git-notes.sh diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index d4487cab5..0d1ada6a0 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 c0dbee2ae..1f95f93f7 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 000000000..89aa6e072 --- /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 e8202f3f5..cdf98477a 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/contrib/examples/git-notes.sh b/contrib/examples/git-notes.sh new file mode 100755 index 000000000..e642e47d9 --- /dev/null +++ b/contrib/examples/git-notes.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +USAGE="(edit [-F | -m ] | show) [commit]" +. git-sh-setup + +test -z "$1" && usage +ACTION="$1"; shift + +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" +test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" + +MESSAGE= +while test $# != 0 +do + case "$1" in + -m) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$1" + else + MESSAGE="$MESSAGE + +$1" + fi + shift + fi + ;; + -F) + test "$ACTION" = "edit" || usage + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + if [ -z "$MESSAGE" ]; then + MESSAGE="$(cat "$1")" + else + MESSAGE="$MESSAGE + +$(cat "$1")" + fi + shift + fi + ;; + -*) + usage + ;; + *) + break + ;; + esac +done + +COMMIT=$(git rev-parse --verify --default HEAD "$@") || +die "Invalid commit: $@" + +case "$ACTION" in +edit) + if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then + die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" + fi + + MSG_FILE="$GIT_DIR/new-notes-$COMMIT" + GIT_INDEX_FILE="$MSG_FILE.idx" + export GIT_INDEX_FILE + + trap ' + test -f "$MSG_FILE" && rm "$MSG_FILE" + test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" + ' 0 + + CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') + if [ -z "$CURRENT_HEAD" ]; then + PARENT= + else + PARENT="-p $CURRENT_HEAD" + git read-tree "$GIT_NOTES_REF" || die "Could not read index" + fi + + if [ -z "$MESSAGE" ]; then + GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" + if [ ! -z "$CURRENT_HEAD" ]; then + git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null + fi + core_editor="$(git config core.editor)" + ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" + else + echo "$MESSAGE" > "$MSG_FILE" + fi + + grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed + mv "$MSG_FILE".processed "$MSG_FILE" + if [ -s "$MSG_FILE" ]; then + BLOB=$(git hash-object -w "$MSG_FILE") || + die "Could not write into object database" + git update-index --add --cacheinfo 0644 $BLOB $COMMIT || + die "Could not write index" + else + test -z "$CURRENT_HEAD" && + die "Will not initialise with empty tree" + git update-index --force-remove $COMMIT || + die "Could not update index" + fi + + TREE=$(git write-tree) || die "Could not write tree" + NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || + die "Could not annotate" + git update-ref -m "Annotate $COMMIT" \ + "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD +;; +show) + git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || + die "No note for commit $COMMIT." + git show "$GIT_NOTES_REF":$COMMIT +;; +*) + usage +esac diff --git a/git-notes.sh b/git-notes.sh deleted file mode 100755 index e642e47d9..000000000 --- a/git-notes.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/sh - -USAGE="(edit [-F | -m ] | show) [commit]" -. git-sh-setup - -test -z "$1" && usage -ACTION="$1"; shift - -test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="$(git config core.notesref)" -test -z "$GIT_NOTES_REF" && GIT_NOTES_REF="refs/notes/commits" - -MESSAGE= -while test $# != 0 -do - case "$1" in - -m) - test "$ACTION" = "edit" || usage - shift - if test "$#" = "0"; then - die "error: option -m needs an argument" - else - if [ -z "$MESSAGE" ]; then - MESSAGE="$1" - else - MESSAGE="$MESSAGE - -$1" - fi - shift - fi - ;; - -F) - test "$ACTION" = "edit" || usage - shift - if test "$#" = "0"; then - die "error: option -F needs an argument" - else - if [ -z "$MESSAGE" ]; then - MESSAGE="$(cat "$1")" - else - MESSAGE="$MESSAGE - -$(cat "$1")" - fi - shift - fi - ;; - -*) - usage - ;; - *) - break - ;; - esac -done - -COMMIT=$(git rev-parse --verify --default HEAD "$@") || -die "Invalid commit: $@" - -case "$ACTION" in -edit) - if [ "${GIT_NOTES_REF#refs/notes/}" = "$GIT_NOTES_REF" ]; then - die "Refusing to edit notes in $GIT_NOTES_REF (outside of refs/notes/)" - fi - - MSG_FILE="$GIT_DIR/new-notes-$COMMIT" - GIT_INDEX_FILE="$MSG_FILE.idx" - export GIT_INDEX_FILE - - trap ' - test -f "$MSG_FILE" && rm "$MSG_FILE" - test -f "$GIT_INDEX_FILE" && rm "$GIT_INDEX_FILE" - ' 0 - - CURRENT_HEAD=$(git show-ref "$GIT_NOTES_REF" | cut -f 1 -d ' ') - if [ -z "$CURRENT_HEAD" ]; then - PARENT= - else - PARENT="-p $CURRENT_HEAD" - git read-tree "$GIT_NOTES_REF" || die "Could not read index" - fi - - if [ -z "$MESSAGE" ]; then - GIT_NOTES_REF= git log -1 $COMMIT | sed "s/^/#/" > "$MSG_FILE" - if [ ! -z "$CURRENT_HEAD" ]; then - git cat-file blob :$COMMIT >> "$MSG_FILE" 2> /dev/null - fi - core_editor="$(git config core.editor)" - ${GIT_EDITOR:-${core_editor:-${VISUAL:-${EDITOR:-vi}}}} "$MSG_FILE" - else - echo "$MESSAGE" > "$MSG_FILE" - fi - - grep -v ^# < "$MSG_FILE" | git stripspace > "$MSG_FILE".processed - mv "$MSG_FILE".processed "$MSG_FILE" - if [ -s "$MSG_FILE" ]; then - BLOB=$(git hash-object -w "$MSG_FILE") || - die "Could not write into object database" - git update-index --add --cacheinfo 0644 $BLOB $COMMIT || - die "Could not write index" - else - test -z "$CURRENT_HEAD" && - die "Will not initialise with empty tree" - git update-index --force-remove $COMMIT || - die "Could not update index" - fi - - TREE=$(git write-tree) || die "Could not write tree" - NEW_HEAD=$(echo Annotate $COMMIT | git commit-tree $TREE $PARENT) || - die "Could not annotate" - git update-ref -m "Annotate $COMMIT" \ - "$GIT_NOTES_REF" $NEW_HEAD $CURRENT_HEAD -;; -show) - git rev-parse -q --verify "$GIT_NOTES_REF":$COMMIT > /dev/null || - die "No note for commit $COMMIT." - git show "$GIT_NOTES_REF":$COMMIT -;; -*) - usage -esac diff --git a/git.c b/git.c index b3e23f104..32f76e15e 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 18aad5389..10f62f412 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' ' -- cgit v1.2.1 From b24bb99756c8b6fde01c23ebbb4abc37d12fb1eb Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:21 +0100 Subject: 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 10f62f412..fd5e593ae 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 -- cgit v1.2.1 From 048cdd4665efafde2902cb72376f2ca497254246 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:22 +0100 Subject: 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 000000000..823b0ff23 --- /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 -- cgit v1.2.1 From 851c2b3791f24e319c23331887d4b8150ca4d9ba Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:23 +0100 Subject: 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 dc4e4f619..d43251758 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 20d6e171f..f98578f91 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 edc4bc884..75ec18778 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 256687ffb..c975a6d3f 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 -- cgit v1.2.1 From a0b4dfa9b35a2ebac578ea5547b041bb78557238 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:24 +0100 Subject: 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 89aa6e072..7b4cb1367 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 fd5e593ae..fe59e73c2 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 && -- cgit v1.2.1 From 92b3385fca620f47580d695b80b96432c76f0822 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:25 +0100 Subject: 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 | 65 +++++++++++++++++++++++++-------------------- t/t3301-notes.sh | 27 +++++++++++++++++++ 3 files changed, 73 insertions(+), 34 deletions(-) diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 0d1ada6a0..a52d23ac1 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 7b4cb1367..7c4007523 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 */ - - 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); - } - } + /* edit/remove command */ + + 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 fe59e73c2..d29daac5b 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 && -- cgit v1.2.1 From b0032d1e06e4a53d908309fce6ad3c5f5a3559a3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:26 +0100 Subject: 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 823b0ff23..c6d263b23 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 -- cgit v1.2.1 From 00fbe63627b72c807e558643f0634e435137122f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:27 +0100 Subject: 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 d43251758..3ba3e6de1 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 f98578f91..bad03ccab 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 * -- cgit v1.2.1 From d6576e1fe3ee4bd7bd2cf01d17499c94da24876f Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:28 +0100 Subject: 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 a52d23ac1..3973f9026 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 7c4007523..48bc455a1 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 000000000..b0adc7e5b --- /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 -- cgit v1.2.1 From 7d541174650fb209a00b629d113cb954c24a19de Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:29 +0100 Subject: 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 3973f9026..84db2a467 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 --- -- cgit v1.2.1 From e397421abf1003c4da824441f91d8c990b70fa98 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:30 +0100 Subject: 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 84db2a467..4d29d5fb9 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 48bc455a1..b80853412 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 d29daac5b..768a1cb02 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 && -- cgit v1.2.1 From ba20f15e0a220705695a1ee19fb28234861b890d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:31 +0100 Subject: 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 4d29d5fb9..8969f6f8f 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 b80853412..ec959bcb3 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() }; -- cgit v1.2.1 From 7aa4754e552eff22d70d496dea73a9c7639d66d3 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:32 +0100 Subject: 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 8969f6f8f..94e12b57e 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 ec959bcb3..006edf6b1 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 768a1cb02..df458ca93 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 ' -- cgit v1.2.1 From 2347fae50b2f75c6c0b362bd9ef24249419ed2b1 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:33 +0100 Subject: 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 94e12b57e..35dd8fa8a 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 006edf6b1..c88df00b3 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 df458ca93..290ed63d4 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 && -- cgit v1.2.1 From aaec9bcf6d85a084725e8386bf314a6ef6842468 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:34 +0100 Subject: 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 35dd8fa8a..53c5d9014 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 c88df00b3..572b47746 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 c975a6d3f..1709e8c00 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 c6d263b23..b1ea64b21 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 b0adc7e5b..a0ed0353e 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: 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 572b47746..190c46c3b 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 290ed63d4..07090e399 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 -- cgit v1.2.1 From 0691cff7dca6b68569183be991d59ebb72b6170e Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:36 +0100 Subject: 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 53c5d9014..15de4b344 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 190c46c3b..98de1154c 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 07090e399..6447e5f54 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 -- cgit v1.2.1 From 5848769f9de68cfb0735710c6c4d2f08aa53f317 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:37 +0100 Subject: 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 98de1154c..bbf98a9f6 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; } -- cgit v1.2.1 From e73bbd96c6e9ce11a101dac03402d0f718a1bd23 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Sat, 13 Feb 2010 22:28:38 +0100 Subject: 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 15de4b344..14f73b988 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 bbf98a9f6..123ecad83 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 6447e5f54..90178f96d 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 -- cgit v1.2.1 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: 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 02e2aed77..a0fe88c55 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; } -- cgit v1.2.1 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: 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 b88122cbe..c91a29cb2 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; } -- cgit v1.2.1 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: 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 dd84caecb..aae7a4d7e 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 37d023352..d0ccb4c96 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")) { -- cgit v1.2.1 From 9fabb6d7513ab0a264de146d72a8447bc85b5e85 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 05:55:53 -0600 Subject: 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 bb981822a..458f3e275 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 228051819..e6ee7bc0b 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) { -- cgit v1.2.1 From 64778d24a93ad455e5883120aef350ede20061c4 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 05:59:59 -0600 Subject: 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 e6ee7bc0b..70fdb4dec 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 d478eff1f..d454b7e68 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 2c7e8ecb3..dac358f04 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; -- cgit v1.2.1 From 06300d9753349a83212360445d241d70a46375fa Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 06:02:35 -0600 Subject: 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 01c463101..f26641a5f 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 -- cgit v1.2.1 From 190c1cda7eb6dc03be80f45d3d174c313d23da2c Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 06:06:10 -0600 Subject: 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 265852f45..473a0b9d5 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) { -- cgit v1.2.1 From e6e592db4c0099a6412aed6e868769535900f112 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 14 Feb 2010 22:46:28 +0100 Subject: 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 1f6978ac1..20106a4f4 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. -- cgit v1.2.1 From f6dff119d51e0067d213068093039bb2f939d139 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 14 Feb 2010 23:04:13 -0600 Subject: 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 3c08d5316..b11af03e0 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 d56426dd3..44fb4670a 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 "$@" } -- cgit v1.2.1 From 7283bbc70a55d7364fbeaefc1009c03fcfc8d929 Mon Sep 17 00:00:00 2001 From: Pete Harlan Date: Mon, 15 Feb 2010 15:33:18 -0800 Subject: 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 11544cdb4..a83cab700 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 9da97d746..7f4928e45 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", -- cgit v1.2.1 From 8324b977aef3d2301f170e23f498b50e11302575 Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Mon, 15 Feb 2010 23:10:45 -0500 Subject: diff: make sure --output=/bad/path is caught The return value from fopen wasn't being checked. Signed-off-by: Larry D'Anna Signed-off-by: Junio C Hamano --- diff.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/diff.c b/diff.c index 17a2b4df2..8d8405aba 100644 --- a/diff.c +++ b/diff.c @@ -2799,6 +2799,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); + if (!options->file) + die_errno("Could not open '%s'", arg + strlen("--output=")); options->close_file = 1; } else return 0; -- cgit v1.2.1 From 460ccd0e19774fd5e4f69de5a454068c686ac5a6 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 15 Feb 2010 17:05:46 +0100 Subject: 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 4febbbfa5..79b277109 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 5514f74b3..476e5ec03 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 && -- cgit v1.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: 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 381cc8d4f..7216b1e8d 100644 --- a/diff.c +++ b/diff.c @@ -3520,6 +3520,29 @@ void diff_flush(struct diff_options *options) separator++; } + if (output_format & DIFF_FORMAT_NO_OUTPUT && + DIFF_OPT_TST(options, EXIT_WITH_STATUS) && + DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) { + /* + * run diff_flush_patch for the exit status. setting + * options->file to /dev/null should be safe, becaue we + * aren't supposed to produce any output anyway. + */ + if (options->close_file) + fclose(options->file); + options->file = fopen("/dev/null", "w"); + if (!options->file) + die_errno("Could not open /dev/null"); + options->close_file = 1; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_patch(p, options); + if (options->found_changes) + break; + } + } + if (output_format & DIFF_FORMAT_PATCH) { if (separator) { putc(options->line_termination, options->file); -- cgit v1.2.1 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: 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 04ae824d6..caf4192b8 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 79aa10471..06fd9e057 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 23d347c45..923d9d1cd 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); -- cgit v1.2.1 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: 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 2cf0f1993..3b9efdbae 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) -- cgit v1.2.1 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: 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 000000000..5322a3bf9 --- /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 000000000..d4d21d31a --- /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 000000000..c5334a8fa --- /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)'" -- cgit v1.2.1 From 003c6abdb27c367747847a76b0a7890d67c794be Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 16 Feb 2010 02:03:16 -0500 Subject: 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 44bb62d27..9677afdad 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); -- cgit v1.2.1 From b0d66e156c5b312d468344569202d8ca4094f67f Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Tue, 16 Feb 2010 15:18:21 +0800 Subject: 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 3846aacb4..08e4fa035 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 7cea5cc72..6dd9ae182 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 -- cgit v1.2.1 From 5f02d31597df35ca98e9080e12bfbb18dadadcbe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 15 Feb 2010 18:34:28 -0800 Subject: 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 26d4deb1c..362122c43 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)); -- cgit v1.2.1 From d3f69766c431bab6321fed1ce64d914dc44ff87f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Feb 2010 22:25:03 -0800 Subject: 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 ab0f9764d..312a3f553 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.. -- cgit v1.2.1 From 81f45e7dc4e930ffc17dce3e377e6adc3eb3d8de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 9 Feb 2010 17:30:49 -0500 Subject: 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 cb6e5906f..016987c22 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) -- cgit v1.2.1 From 1e7ef746d3a635742690817fefe00b66a044dfe5 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 9 Feb 2010 17:30:48 -0500 Subject: 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 912075063..2ad2819a3 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 85eb0fbf9..525c9a8fd 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 -- cgit v1.2.1 From 3ac4440801905d11de3ba585c2fe306311db6c45 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Mon, 15 Feb 2010 19:25:40 -0500 Subject: 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 8c700200f..fa91e8beb 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`. -- cgit v1.2.1 From 8420ccd8b884e1edceca9ea5824f3ff300c0c4ba Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Feb 2010 23:59:46 -0800 Subject: 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 d478eff1f..dd3be0a06 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 6963fbea4..64e41bea2 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); } -- cgit v1.2.1 From eb734454098fb68af1fb0e157dd5e67bb15a602d Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:39:52 +1300 Subject: 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 4c36aa95b..db280326d 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 8d16832f7..3100dc00a 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; -- cgit v1.2.1 From 8a3d203bd02bec48a02557961899d81da172fa23 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 10:20:49 -0800 Subject: 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 0e39bb61e..64bb879fe 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 3100dc00a..0afba31c1 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 1dc224f6f..2230e606e 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 -- cgit v1.2.1 From ae9c606ed228ea6a17ddde3a1e26451d02f51df7 Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Mon, 15 Feb 2010 22:34:07 -0800 Subject: 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 57db955bd..6cafbe2ec 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 ba72fa4b6..3ee25d964 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; } -- cgit v1.2.1 From ab62677b1424d4e53cf222c973b841d3dada4cf3 Mon Sep 17 00:00:00 2001 From: Gabriel Filion Date: Tue, 16 Feb 2010 23:18:50 -0500 Subject: 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 7bef43f39..d2789410d 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." } -- cgit v1.2.1 From 3fc366bdbbcafa36ac4e35e3124959beb94c7fce Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:51 -0500 Subject: 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 b477dc6a8..c29737ef9 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; -- cgit v1.2.1 From 212818160d588fae403dfc823250e95ffbac76e5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:52 -0500 Subject: 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 c29737ef9..7d737ba63 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 -- cgit v1.2.1 From 427cb22c40484f9b8c2881bc9e99636591a22655 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:53 -0500 Subject: 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 7d737ba63..9d7ab0962 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]++; } -- cgit v1.2.1 From 89e0a3a131d251b5345845529d5258ab91105c9b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:54 -0500 Subject: 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 6764ff188..19082b04e 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 9d7ab0962..d2f45b18d 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(); -- cgit v1.2.1 From 8c2ca8dd8a5d6d8beaa0a4abed0c135004eef772 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:55 -0500 Subject: 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 d2f45b18d..7fc98620b 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; -- cgit v1.2.1 From b500d5e11ea67d29dd7be622f65571d611d6e9a3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 17 Feb 2010 14:05:56 -0500 Subject: 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 7fc98620b..74f08bd55 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; -- cgit v1.2.1 From 3deea89c5feb0dfdfb99ea752f83497d97a3bdd5 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Tue, 16 Feb 2010 11:21:14 +0100 Subject: 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 664f21721..5869c00f2 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 d3c039f72..cee319da0 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 -- cgit v1.2.1 From 453541fcfcbc54aa3b0035667e5d5885d407d0a5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:51:18 +0100 Subject: 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 1f6978ac1..2ccbb6aa3 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 = ( -- cgit v1.2.1 From 1df48766137f2040e2e992d7d278d5aca26406cf Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 7 Feb 2010 21:52:25 +0100 Subject: 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 2ccbb6aa3..3c879b88f 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 = ( -- cgit v1.2.1 From 90b45187ba51a15c8a80680597240e32421f53bb Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:42:31 +1300 Subject: 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 dd84caecb..0eb9efc93 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. -- cgit v1.2.1 From d8a8488d569420c4e0a96a8df36b246d888e68d4 Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Wed, 17 Feb 2010 12:44:46 +1300 Subject: 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 4c36aa95b..310d36b20 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 f43c8b2c0..3919b7d44 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 7ee102da4..246b07ebf 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 -------- -- cgit v1.2.1 From 149794dd1db18b22f4df94ef13cf61520d7be1d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 12:30:41 -0800 Subject: 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 55676fd87..bb9700032 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; -- cgit v1.2.1 From e3ff352c73a87d533fd239c3f9d4bb978c8ce387 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 15:00:00 -0800 Subject: 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 312a3f553..970cd5933 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.. -- cgit v1.2.1 From c2c85ed5d90d96a9d06170729a1a8b15af5256a4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 17 Feb 2010 15:01:11 -0800 Subject: 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 b0bf8366c..8c18ca5d2 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.. -- cgit v1.2.1 From 72a534dab016813eec71935323b197650a2c0c9d Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Wed, 17 Feb 2010 20:56:02 +0000 Subject: 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 20054e4d0..f4563f951 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; -- cgit v1.2.1 From f1863d0d16b9a5288671e17b7fa2eba8244ead2f Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:52 +0000 Subject: 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 8654fa7a2..d3b9d8a13 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 76c72065d..cbda3117d 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 3846aacb4..6d7a1259a 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 7cea5cc72..7a9bb57d9 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 -- cgit v1.2.1 From 06b65939b083ba1b71043005bf83b4883e98264e Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:55 +0000 Subject: 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 527781728..22ae92f60 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 cb53b01c1..195ebf974 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 01f14fb50..ca5e3fbae 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 55572c39a..abba70c16 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); -- cgit v1.2.1 From 1b22b6c897efa8aec6eeb51f72a73d507d5e336f Mon Sep 17 00:00:00 2001 From: Michael Lukashov Date: Tue, 16 Feb 2010 23:42:54 +0000 Subject: 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 e1d3adf40..6b2f65c6d 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 d478eff1f..2654b608b 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 74f08bd55..309f2c58a 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 657825e14..f3c9823c5 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) { -- cgit v1.2.1 From cc1b8d8bc6e453b96798574d67ce9590eb3e82e1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 17 Feb 2010 20:16:20 -0500 Subject: 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 e1c4320f0..379eee673 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 f43c8b2c0..88ea6246a 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 e9952e821..97ea7973a 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 ffd5025f7..61fd7d099 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 3bb730451..15cfb7a8d 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 bd79119dd..3f103ccb0 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 d677c72d5..1a613aa10 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 734336119..b9c4154e7 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 84e555d81..473889a66 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 6e9baf8b3..81c0e6f18 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`.) + -- cgit v1.2.1 From 738820a913d05427b6c86d227aafd2bac7cd38d1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 18 Feb 2010 01:10:28 -0800 Subject: 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 | 37 ++++++++++++++++++++++++------------- Documentation/git-push.txt | 7 ++++--- Documentation/git-send-pack.txt | 4 ++-- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 97ea7973a..4a8487c15 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 65a301bec..f3ccc72f0 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 61fd7d099..034caedc3 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. - -Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or +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. + +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 3f103ccb0..49b6bd9d9 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 8178d9264..deaa7d965 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 -- cgit v1.2.1 From 73e9da019655261e456ed862340880de365111f0 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Tue, 16 Feb 2010 23:55:58 -0500 Subject: Add an optional argument for --color options Make git-branch, git-show-branch, git-grep, and all the diff-based programs accept an optional argument for --color. The argument is a colorbool: "always", "never", or "auto". If no argument is given, "always" is used; --no-color is an alias for --color=never. This makes the command-line interface consistent with other GNU tools, such as `ls' and `grep', and with the git-config color options. Note that, without an argument, --color and --no-color work exactly as before. To implement this, two internal changes were made: 1. Allow the first argument of git_config_colorbool() to be NULL, in which case it returns -1 if the argument isn't "always", "never", or "auto". 2. Add OPT_COLOR_FLAG(), OPT__COLOR(), and parse_opt_color_flag_cb() to the option parsing library. The callback uses git_config_colorbool(), so color.h is now a dependency of parse-options.c. Signed-off-by: Mark Lodato Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 4 +++- Documentation/git-branch.txt | 6 ++++-- Documentation/git-grep.txt | 6 ++++-- Documentation/git-show-branch.txt | 6 ++++-- Documentation/technical/api-parse-options.txt | 12 ++++++++++++ builtin-branch.c | 2 +- builtin-grep.c | 2 +- builtin-show-branch.c | 4 ++-- color.c | 3 +++ diff.c | 9 +++++++++ parse-options.c | 16 ++++++++++++++++ parse-options.h | 7 +++++++ 12 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8707d0e74..60e922e6e 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 6b6c3da2d..903a690f1 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 e019e760b..70c7ef95f 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 734336119..519f9e1dd 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 50f9e9ac1..312e3b2e2 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 a28a13986..6cf7e721e 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 26d4deb1c..00cbd90bf 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 9f13caa76..32d862ab2 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 62977f480..8f07fc954 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 381cc8d4f..8f645f6f8 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 d218122af..c83035d01 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 0c996916b..9429f7e36 100644 --- a/parse-options.h +++ b/parse-options.h @@ -135,6 +135,10 @@ struct option { PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } #define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ "FILE", (h) } +#define OPT_COLOR_FLAG(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \ + parse_opt_color_flag_cb, (intptr_t)"always" } + /* parse_options() will filter out the processed options and leave the * non-option arguments in argv[]. @@ -187,6 +191,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); +extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); @@ -203,5 +208,7 @@ extern int parse_opt_tertiary(const struct option *, const char *, int); { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ "use digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } +#define OPT__COLOR(var, h) \ + OPT_COLOR_FLAG(0, "color", (var), (h)) #endif -- cgit v1.2.1 From 3fc0d131c573f6f774e2e4abba9cbda694b08321 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 19 Feb 2010 00:57:21 -0500 Subject: 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 6aae09a22..fdc0a2ede 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 76b1bb454..0aaf0ad84 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 -- cgit v1.2.1 From e9e921981d10554a325f4a1e67e920947e0e4800 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 15 Feb 2010 04:33:06 -0800 Subject: 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 e99bb1475..64fb458b4 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 -- cgit v1.2.1 From 470b452628d80e89ee869b6b86af6c536ba0b24d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 19 Feb 2010 21:55:33 -0800 Subject: 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 a50ac2256..ce2ef6bed 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 957723868..4abb3d5c6 100644 --- a/t/t5100/msg0015 +++ b/t/t5100/msg0015 @@ -1,2 +1,2 @@ -- a list + - a list - of stuff -- cgit v1.2.1 From 4551d03541e5eec411bb367f7967ff933d176df4 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 20 Feb 2010 01:18:44 +0100 Subject: 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 a22632f48..49cae3ed5 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 ' -- cgit v1.2.1 From b39c3612eb443e77bd04d645578e1155988c6dde Mon Sep 17 00:00:00 2001 From: Evan Powers Date: Tue, 16 Feb 2010 00:44:08 -0800 Subject: 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 e7c48144e..cd96c6f81 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 = [] -- cgit v1.2.1 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: 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 8aa63c785..d96858f9a 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 7ddff23fa..5c8a2697f 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 ae30ae4c6..b09687d50 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); -- cgit v1.2.1 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: 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 5c8a2697f..a4e36ff33 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; -- cgit v1.2.1 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: 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 a4e36ff33..9f64f59d6 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; -- cgit v1.2.1 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: 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 9f64f59d6..6db9cd9a0 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++; } -- cgit v1.2.1 From 21da42621489df7824f1fce7180702a9aa540707 Mon Sep 17 00:00:00 2001 From: Nelson Elhage Date: Thu, 18 Feb 2010 22:29:30 -0500 Subject: 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 034caedc3..769f1de6b 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 -- cgit v1.2.1 From 60b6e2200deff208a9757721544a3a311034804f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Fri, 19 Feb 2010 01:18:58 -0600 Subject: 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 000000000..4f52ea573 --- /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 -- cgit v1.2.1 From 36c079756f9f3ad0bbbe2097550c62427670146b Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Sat, 20 Feb 2010 12:42:04 +0100 Subject: 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 e75079a6e..c2fad2fd7 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) { -- cgit v1.2.1 From 2d3ca21677597902f66bf2f2b2cf1b4a623f1e4f Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sat, 20 Feb 2010 02:50:25 -0600 Subject: 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 4f52ea573..da0f96262 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 000000000..73ff80937 --- /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)); -- cgit v1.2.1 From 9892bebafe0865d8f4f3f18d60a1cfa2d1447cd7 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 20 Feb 2010 23:27:31 -0500 Subject: 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 657825e14..9196b5783 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; -- cgit v1.2.1 From ea68b0ce9f8ce8da3e360aed3cbd6720159ffbee Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 21 Feb 2010 09:32:19 +0300 Subject: 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 657825e14..037515960 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); -- cgit v1.2.1 From 1caaf225f86b7b1818103e72774e191fb1f3d0bb Mon Sep 17 00:00:00 2001 From: Larry D'Anna Date: Sun, 21 Feb 2010 21:58:44 -0500 Subject: 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 60dd2014d..0391a5827 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 -- cgit v1.2.1 From 748af44c63ea6fec12690f1693f3dddd963e88d5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sun, 21 Feb 2010 15:48:06 -0500 Subject: 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 9196b5783..c0214d794 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); -- cgit v1.2.1 From 8c33b4cf67f47ee46fe0984751fd40c4cf7cf392 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 22 Feb 2010 02:46:33 -0600 Subject: 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 da0f96262..d9202d5af 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 -- cgit v1.2.1 From 81b50f3ce40bfdd66e5d967bf82be001039a9a98 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 22 Feb 2010 08:42:18 -0800 Subject: 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 | 442 ----- builtin-annotate.c | 24 - builtin-apply.c | 3670 ------------------------------------------ builtin-archive.c | 129 -- builtin-bisect--helper.c | 28 - builtin-blame.c | 2477 ---------------------------- builtin-branch.c | 696 -------- builtin-bundle.c | 67 - builtin-cat-file.c | 258 --- builtin-check-attr.c | 123 -- builtin-check-ref-format.c | 61 - builtin-checkout-index.c | 315 ---- builtin-checkout.c | 853 ---------- builtin-clean.c | 171 -- builtin-clone.c | 671 -------- builtin-commit-tree.c | 133 -- builtin-commit.c | 1310 --------------- builtin-config.c | 496 ------ builtin-count-objects.c | 130 -- builtin-describe.c | 396 ----- builtin-diff-files.c | 68 - builtin-diff-index.c | 50 - builtin-diff-tree.c | 170 -- builtin-diff.c | 425 ----- builtin-fast-export.c | 633 -------- builtin-fetch-pack.c | 976 ----------- builtin-fetch.c | 920 ----------- builtin-fmt-merge-msg.c | 381 ----- builtin-for-each-ref.c | 955 ----------- builtin-fsck.c | 684 -------- builtin-gc.c | 255 --- builtin-grep.c | 1010 ------------ builtin-hash-object.c | 134 -- builtin-help.c | 466 ------ builtin-index-pack.c | 1045 ------------ builtin-init-db.c | 498 ------ builtin-log.c | 1352 ---------------- builtin-ls-files.c | 606 ------- builtin-ls-remote.c | 107 -- builtin-ls-tree.c | 176 -- builtin-mailinfo.c | 1064 ------------ builtin-mailsplit.c | 309 ---- builtin-merge-base.c | 63 - builtin-merge-file.c | 96 -- builtin-merge-index.c | 111 -- builtin-merge-ours.c | 34 - builtin-merge-recursive.c | 84 - builtin-merge-tree.c | 358 ---- builtin-merge.c | 1292 --------------- builtin-mktag.c | 179 -- builtin-mktree.c | 190 --- builtin-mv.c | 227 --- builtin-name-rev.c | 305 ---- builtin-pack-objects.c | 2375 --------------------------- builtin-pack-redundant.c | 696 -------- builtin-pack-refs.c | 21 - builtin-patch-id.c | 85 - builtin-prune-packed.c | 86 - builtin-prune.c | 169 -- builtin-push.c | 247 --- builtin-read-tree.c | 235 --- builtin-receive-pack.c | 800 --------- builtin-reflog.c | 719 --------- builtin-remote.c | 1447 ----------------- builtin-replace.c | 159 -- builtin-rerere.c | 155 -- builtin-reset.c | 356 ---- builtin-rev-list.c | 400 ----- builtin-rev-parse.c | 724 --------- builtin-revert.c | 460 ------ builtin-rm.c | 272 ---- builtin-send-pack.c | 706 -------- builtin-shortlog.c | 343 ---- builtin-show-branch.c | 967 ----------- builtin-show-ref.c | 249 --- builtin-stripspace.c | 90 -- builtin-symbolic-ref.c | 57 - builtin-tag.c | 487 ------ builtin-tar-tree.c | 103 -- builtin-unpack-file.c | 38 - builtin-unpack-objects.c | 568 ------- builtin-update-index.c | 788 --------- builtin-update-ref.c | 58 - builtin-update-server-info.c | 25 - builtin-upload-archive.c | 167 -- builtin-var.c | 99 -- builtin-verify-pack.c | 166 -- builtin-verify-tag.c | 114 -- builtin-write-tree.c | 56 - builtin/add.c | 442 +++++ builtin/annotate.c | 24 + builtin/apply.c | 3670 ++++++++++++++++++++++++++++++++++++++++++ builtin/archive.c | 129 ++ builtin/bisect--helper.c | 28 + builtin/blame.c | 2477 ++++++++++++++++++++++++++++ builtin/branch.c | 696 ++++++++ builtin/bundle.c | 67 + builtin/cat-file.c | 258 +++ builtin/check-attr.c | 123 ++ builtin/check-ref-format.c | 61 + builtin/checkout-index.c | 315 ++++ builtin/checkout.c | 853 ++++++++++ builtin/clean.c | 171 ++ builtin/clone.c | 671 ++++++++ builtin/commit-tree.c | 133 ++ builtin/commit.c | 1310 +++++++++++++++ builtin/config.c | 496 ++++++ builtin/count-objects.c | 130 ++ builtin/describe.c | 396 +++++ builtin/diff-files.c | 68 + builtin/diff-index.c | 50 + builtin/diff-tree.c | 170 ++ builtin/diff.c | 425 +++++ builtin/fast-export.c | 633 ++++++++ builtin/fetch-pack.c | 976 +++++++++++ builtin/fetch.c | 920 +++++++++++ builtin/fmt-merge-msg.c | 381 +++++ builtin/for-each-ref.c | 955 +++++++++++ builtin/fsck.c | 684 ++++++++ builtin/gc.c | 255 +++ builtin/grep.c | 1010 ++++++++++++ builtin/hash-object.c | 134 ++ builtin/help.c | 466 ++++++ builtin/index-pack.c | 1045 ++++++++++++ builtin/init-db.c | 498 ++++++ builtin/log.c | 1352 ++++++++++++++++ builtin/ls-files.c | 606 +++++++ builtin/ls-remote.c | 107 ++ builtin/ls-tree.c | 176 ++ builtin/mailinfo.c | 1064 ++++++++++++ builtin/mailsplit.c | 309 ++++ builtin/merge-base.c | 63 + builtin/merge-file.c | 96 ++ builtin/merge-index.c | 111 ++ builtin/merge-ours.c | 34 + builtin/merge-recursive.c | 84 + builtin/merge-tree.c | 358 ++++ builtin/merge.c | 1292 +++++++++++++++ builtin/mktag.c | 179 ++ builtin/mktree.c | 190 +++ builtin/mv.c | 227 +++ builtin/name-rev.c | 305 ++++ builtin/pack-objects.c | 2375 +++++++++++++++++++++++++++ builtin/pack-redundant.c | 696 ++++++++ builtin/pack-refs.c | 21 + builtin/patch-id.c | 85 + builtin/prune-packed.c | 86 + builtin/prune.c | 169 ++ builtin/push.c | 247 +++ builtin/read-tree.c | 235 +++ builtin/receive-pack.c | 800 +++++++++ builtin/reflog.c | 719 +++++++++ builtin/remote.c | 1447 +++++++++++++++++ builtin/replace.c | 159 ++ builtin/rerere.c | 155 ++ builtin/reset.c | 356 ++++ builtin/rev-list.c | 400 +++++ builtin/rev-parse.c | 724 +++++++++ builtin/revert.c | 460 ++++++ builtin/rm.c | 272 ++++ builtin/send-pack.c | 706 ++++++++ builtin/shortlog.c | 343 ++++ builtin/show-branch.c | 967 +++++++++++ builtin/show-ref.c | 249 +++ builtin/stripspace.c | 90 ++ builtin/symbolic-ref.c | 57 + builtin/tag.c | 487 ++++++ builtin/tar-tree.c | 103 ++ builtin/unpack-file.c | 38 + builtin/unpack-objects.c | 568 +++++++ builtin/update-index.c | 788 +++++++++ builtin/update-ref.c | 58 + builtin/update-server-info.c | 25 + builtin/upload-archive.c | 167 ++ builtin/var.c | 99 ++ builtin/verify-pack.c | 166 ++ builtin/verify-tag.c | 114 ++ builtin/write-tree.c | 56 + 179 files changed, 42257 insertions(+), 42257 deletions(-) delete mode 100644 builtin-add.c delete mode 100644 builtin-annotate.c delete mode 100644 builtin-apply.c delete mode 100644 builtin-archive.c delete mode 100644 builtin-bisect--helper.c delete mode 100644 builtin-blame.c delete mode 100644 builtin-branch.c delete mode 100644 builtin-bundle.c delete mode 100644 builtin-cat-file.c delete mode 100644 builtin-check-attr.c delete mode 100644 builtin-check-ref-format.c delete mode 100644 builtin-checkout-index.c delete mode 100644 builtin-checkout.c delete mode 100644 builtin-clean.c delete mode 100644 builtin-clone.c delete mode 100644 builtin-commit-tree.c delete mode 100644 builtin-commit.c delete mode 100644 builtin-config.c delete mode 100644 builtin-count-objects.c delete mode 100644 builtin-describe.c delete mode 100644 builtin-diff-files.c delete mode 100644 builtin-diff-index.c delete mode 100644 builtin-diff-tree.c delete mode 100644 builtin-diff.c delete mode 100644 builtin-fast-export.c delete mode 100644 builtin-fetch-pack.c delete mode 100644 builtin-fetch.c delete mode 100644 builtin-fmt-merge-msg.c delete mode 100644 builtin-for-each-ref.c delete mode 100644 builtin-fsck.c delete mode 100644 builtin-gc.c delete mode 100644 builtin-grep.c delete mode 100644 builtin-hash-object.c delete mode 100644 builtin-help.c delete mode 100644 builtin-index-pack.c delete mode 100644 builtin-init-db.c delete mode 100644 builtin-log.c delete mode 100644 builtin-ls-files.c delete mode 100644 builtin-ls-remote.c delete mode 100644 builtin-ls-tree.c delete mode 100644 builtin-mailinfo.c delete mode 100644 builtin-mailsplit.c delete mode 100644 builtin-merge-base.c delete mode 100644 builtin-merge-file.c delete mode 100644 builtin-merge-index.c delete mode 100644 builtin-merge-ours.c delete mode 100644 builtin-merge-recursive.c delete mode 100644 builtin-merge-tree.c delete mode 100644 builtin-merge.c delete mode 100644 builtin-mktag.c delete mode 100644 builtin-mktree.c delete mode 100644 builtin-mv.c delete mode 100644 builtin-name-rev.c delete mode 100644 builtin-pack-objects.c delete mode 100644 builtin-pack-redundant.c delete mode 100644 builtin-pack-refs.c delete mode 100644 builtin-patch-id.c delete mode 100644 builtin-prune-packed.c delete mode 100644 builtin-prune.c delete mode 100644 builtin-push.c delete mode 100644 builtin-read-tree.c delete mode 100644 builtin-receive-pack.c delete mode 100644 builtin-reflog.c delete mode 100644 builtin-remote.c delete mode 100644 builtin-replace.c delete mode 100644 builtin-rerere.c delete mode 100644 builtin-reset.c delete mode 100644 builtin-rev-list.c delete mode 100644 builtin-rev-parse.c delete mode 100644 builtin-revert.c delete mode 100644 builtin-rm.c delete mode 100644 builtin-send-pack.c delete mode 100644 builtin-shortlog.c delete mode 100644 builtin-show-branch.c delete mode 100644 builtin-show-ref.c delete mode 100644 builtin-stripspace.c delete mode 100644 builtin-symbolic-ref.c delete mode 100644 builtin-tag.c delete mode 100644 builtin-tar-tree.c delete mode 100644 builtin-unpack-file.c delete mode 100644 builtin-unpack-objects.c delete mode 100644 builtin-update-index.c delete mode 100644 builtin-update-ref.c delete mode 100644 builtin-update-server-info.c delete mode 100644 builtin-upload-archive.c delete mode 100644 builtin-var.c delete mode 100644 builtin-verify-pack.c delete mode 100644 builtin-verify-tag.c delete mode 100644 builtin-write-tree.c create mode 100644 builtin/add.c create mode 100644 builtin/annotate.c create mode 100644 builtin/apply.c create mode 100644 builtin/archive.c create mode 100644 builtin/bisect--helper.c create mode 100644 builtin/blame.c create mode 100644 builtin/branch.c create mode 100644 builtin/bundle.c create mode 100644 builtin/cat-file.c create mode 100644 builtin/check-attr.c create mode 100644 builtin/check-ref-format.c create mode 100644 builtin/checkout-index.c create mode 100644 builtin/checkout.c create mode 100644 builtin/clean.c create mode 100644 builtin/clone.c create mode 100644 builtin/commit-tree.c create mode 100644 builtin/commit.c create mode 100644 builtin/config.c create mode 100644 builtin/count-objects.c create mode 100644 builtin/describe.c create mode 100644 builtin/diff-files.c create mode 100644 builtin/diff-index.c create mode 100644 builtin/diff-tree.c create mode 100644 builtin/diff.c create mode 100644 builtin/fast-export.c create mode 100644 builtin/fetch-pack.c create mode 100644 builtin/fetch.c create mode 100644 builtin/fmt-merge-msg.c create mode 100644 builtin/for-each-ref.c create mode 100644 builtin/fsck.c create mode 100644 builtin/gc.c create mode 100644 builtin/grep.c create mode 100644 builtin/hash-object.c create mode 100644 builtin/help.c create mode 100644 builtin/index-pack.c create mode 100644 builtin/init-db.c create mode 100644 builtin/log.c create mode 100644 builtin/ls-files.c create mode 100644 builtin/ls-remote.c create mode 100644 builtin/ls-tree.c create mode 100644 builtin/mailinfo.c create mode 100644 builtin/mailsplit.c create mode 100644 builtin/merge-base.c create mode 100644 builtin/merge-file.c create mode 100644 builtin/merge-index.c create mode 100644 builtin/merge-ours.c create mode 100644 builtin/merge-recursive.c create mode 100644 builtin/merge-tree.c create mode 100644 builtin/merge.c create mode 100644 builtin/mktag.c create mode 100644 builtin/mktree.c create mode 100644 builtin/mv.c create mode 100644 builtin/name-rev.c create mode 100644 builtin/pack-objects.c create mode 100644 builtin/pack-redundant.c create mode 100644 builtin/pack-refs.c create mode 100644 builtin/patch-id.c create mode 100644 builtin/prune-packed.c create mode 100644 builtin/prune.c create mode 100644 builtin/push.c create mode 100644 builtin/read-tree.c create mode 100644 builtin/receive-pack.c create mode 100644 builtin/reflog.c create mode 100644 builtin/remote.c create mode 100644 builtin/replace.c create mode 100644 builtin/rerere.c create mode 100644 builtin/reset.c create mode 100644 builtin/rev-list.c create mode 100644 builtin/rev-parse.c create mode 100644 builtin/revert.c create mode 100644 builtin/rm.c create mode 100644 builtin/send-pack.c create mode 100644 builtin/shortlog.c create mode 100644 builtin/show-branch.c create mode 100644 builtin/show-ref.c create mode 100644 builtin/stripspace.c create mode 100644 builtin/symbolic-ref.c create mode 100644 builtin/tag.c create mode 100644 builtin/tar-tree.c create mode 100644 builtin/unpack-file.c create mode 100644 builtin/unpack-objects.c create mode 100644 builtin/update-index.c create mode 100644 builtin/update-ref.c create mode 100644 builtin/update-server-info.c create mode 100644 builtin/upload-archive.c create mode 100644 builtin/var.c create mode 100644 builtin/verify-pack.c create mode 100644 builtin/verify-tag.c create mode 100644 builtin/write-tree.c diff --git a/Makefile b/Makefile index afedb54b4..f1025d5c0 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 deleted file mode 100644 index 2705f8d05..000000000 --- a/builtin-add.c +++ /dev/null @@ -1,442 +0,0 @@ -/* - * "git add" builtin command - * - * Copyright (C) 2006 Linus Torvalds - */ -#include "cache.h" -#include "builtin.h" -#include "dir.h" -#include "exec_cmd.h" -#include "cache-tree.h" -#include "run-command.h" -#include "parse-options.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" - -static const char * const builtin_add_usage[] = { - "git add [options] [--] ...", - NULL -}; -static int patch_interactive, add_interactive, edit_interactive; -static int take_worktree_changes; - -struct update_callback_data -{ - int flags; - int add_errors; -}; - -static void update_callback(struct diff_queue_struct *q, - struct diff_options *opt, void *cbdata) -{ - int i; - struct update_callback_data *data = cbdata; - - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - const char *path = p->one->path; - switch (p->status) { - default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - /* - * ADD_CACHE_IGNORE_REMOVAL is unset if "git - * add -u" is calling us, In such a case, a - * missing work tree file needs to be removed - * if there is an unmerged entry at stage #2, - * but such a diff record is followed by - * another with DIFF_STATUS_DELETED (and if - * there is no stage #2, we won't see DELETED - * nor MODIFIED). We can simply continue - * either way. - */ - if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) - continue; - /* - * Otherwise, it is "git add path" is asking - * to explicitly add it; we fall through. A - * missing work tree file is an error and is - * caught by add_file_to_index() in such a - * case. - */ - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_TYPE_CHANGED: - if (add_file_to_index(&the_index, path, data->flags)) { - if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); - data->add_errors++; - } - break; - case DIFF_STATUS_DELETED: - if (data->flags & ADD_CACHE_IGNORE_REMOVAL) - break; - if (!(data->flags & ADD_CACHE_PRETEND)) - remove_file_from_index(&the_index, path); - if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); - break; - } - } -} - -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) -{ - struct update_callback_data data; - struct rev_info rev; - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; - rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; - run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - return !!data.add_errors; -} - -static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) -{ - int num_unmatched = 0, i; - - /* - * Since we are walking the index as if we were walking the directory, - * we have to mark the matched pathspec as seen; otherwise we will - * mistakenly think that the user gave a pathspec that did not match - * anything. - */ - for (i = 0; i < specs; i++) - if (!seen[i]) - num_unmatched++; - if (!num_unmatched) - return; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); - } -} - -static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) -{ - char *seen; - int i, specs; - struct dir_entry **src, **dst; - - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); - - src = dst = dir->entries; - i = dir->nr; - while (--i >= 0) { - struct dir_entry *entry = *src++; - if (match_pathspec(pathspec, entry->name, entry->len, - prefix, seen)) - *dst++ = entry; - } - 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); -} - -static void treat_gitlinks(const char **pathspec) -{ - int i; - - if (!pathspec || !*pathspec) - return; - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (S_ISGITLINK(ce->ce_mode)) { - int len = ce_namelen(ce), j; - for (j = 0; pathspec[j]; j++) { - int len2 = strlen(pathspec[j]); - if (len2 <= len || pathspec[j][len] != '/' || - memcmp(ce->name, pathspec[j], len)) - continue; - if (len2 == len + 1) - /* strip trailing slash */ - pathspec[j] = xstrndup(ce->name, len); - else - die ("Path '%s' is in submodule '%.*s'", - pathspec[j], len, ce->name); - } - } - } -} - -static void refresh(int verbose, const char **pathspec) -{ - char *seen; - int i, specs; - - for (specs = 0; pathspec[specs]; specs++) - /* nothing */; - seen = xcalloc(specs, 1); - refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, "Unstaged changes after refreshing the index:"); - for (i = 0; i < specs; i++) { - if (!seen[i]) - die("pathspec '%s' did not match any files", pathspec[i]); - } - free(seen); -} - -static const char **validate_pathspec(int argc, const char **argv, const char *prefix) -{ - const char **pathspec = get_pathspec(prefix, argv); - - if (pathspec) { - const char **p; - for (p = pathspec; *p; p++) { - if (has_symlink_leading_path(*p, strlen(*p))) { - int len = prefix ? strlen(prefix) : 0; - die("'%s' is beyond a symbolic link", *p + len); - } - } - } - - return pathspec; -} - -int run_add_interactive(const char *revision, const char *patch_mode, - const char **pathspec) -{ - int status, ac, pc = 0; - const char **args; - - if (pathspec) - while (pathspec[pc]) - pc++; - - args = xcalloc(sizeof(const char *), (pc + 5)); - ac = 0; - args[ac++] = "add--interactive"; - if (patch_mode) - args[ac++] = patch_mode; - if (revision) - args[ac++] = revision; - args[ac++] = "--"; - if (pc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); - ac += pc; - } - args[ac] = NULL; - - status = run_command_v_opt(args, RUN_GIT_CMD); - free(args); - return status; -} - -int interactive_add(int argc, const char **argv, const char *prefix) -{ - const char **pathspec = NULL; - - if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); - if (!pathspec) - return -1; - } - - return run_add_interactive(NULL, - patch_interactive ? "--patch" : NULL, - pathspec); -} - -static int edit_patch(int argc, const char **argv, const char *prefix) -{ - char *file = xstrdup(git_path("ADD_EDIT.patch")); - const char *apply_argv[] = { "apply", "--recount", "--cached", - file, NULL }; - struct child_process child; - struct rev_info rev; - int out; - struct stat st; - - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - - if (read_cache() < 0) - die ("Could not read the index"); - - init_revisions(&rev, prefix); - rev.diffopt.context = 7; - - argc = setup_revisions(argc, argv, &rev, NULL); - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - out = open(file, O_CREAT | O_WRONLY, 0644); - if (out < 0) - die ("Could not open '%s' for writing.", file); - rev.diffopt.file = xfdopen(out, "w"); - rev.diffopt.close_file = 1; - if (run_diff_files(&rev, 0)) - die ("Could not write patch"); - - launch_editor(file, NULL, NULL); - - if (stat(file, &st)) - die_errno("Could not stat '%s'", file); - if (!st.st_size) - die("Empty patch. Aborted."); - - memset(&child, 0, sizeof(child)); - child.git_cmd = 1; - child.argv = apply_argv; - if (run_command(&child)) - die ("Could not apply '%s'", file); - - unlink(file); - return 0; -} - -static struct lock_file lock_file; - -static const char ignore_error[] = -"The following paths are ignored by one of your .gitignore files:\n"; - -static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove, intent_to_add; - -static struct option builtin_add_options[] = { - OPT__DRY_RUN(&show_only), - OPT__VERBOSE(&verbose), - OPT_GROUP(""), - OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), - OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), - OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), - OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), - OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), - OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), - OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), - OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), - OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), - OPT_END(), -}; - -static int add_config(const char *var, const char *value, void *cb) -{ - if (!strcasecmp(var, "add.ignore-errors")) { - ignore_add_errors = git_config_bool(var, value); - return 0; - } - return git_default_config(var, value, cb); -} - -static int add_files(struct dir_struct *dir, int flags) -{ - int i, exit_status = 0; - - if (dir->ignored_nr) { - fprintf(stderr, ignore_error); - for (i = 0; i < dir->ignored_nr; i++) - fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, "Use -f if you really want to add them.\n"); - die("no files added"); - } - - for (i = 0; i < dir->nr; i++) - if (add_file_to_cache(dir->entries[i]->name, flags)) { - if (!ignore_add_errors) - die("adding files failed"); - exit_status = 1; - } - return exit_status; -} - -int cmd_add(int argc, const char **argv, const char *prefix) -{ - int exit_status = 0; - int newfd; - const char **pathspec; - struct dir_struct dir; - int flags; - int add_new_files; - int require_pathspec; - - git_config(add_config, NULL); - - argc = parse_options(argc, argv, prefix, builtin_add_options, - builtin_add_usage, PARSE_OPT_KEEP_ARGV0); - if (patch_interactive) - add_interactive = 1; - if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix)); - - if (edit_interactive) - return(edit_patch(argc, argv, prefix)); - argc--; - argv++; - - if (addremove && take_worktree_changes) - die("-A and -u are mutually incompatible"); - if ((addremove || take_worktree_changes) && !argc) { - static const char *here[2] = { ".", NULL }; - argc = 1; - argv = here; - } - - add_new_files = !take_worktree_changes && !refresh_only; - require_pathspec = !take_worktree_changes; - - newfd = hold_locked_index(&lock_file, 1); - - flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | - (show_only ? ADD_CACHE_PRETEND : 0) | - (intent_to_add ? ADD_CACHE_INTENT : 0) | - (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | - (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); - - if (require_pathspec && argc == 0) { - fprintf(stderr, "Nothing specified, nothing added.\n"); - fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); - return 0; - } - pathspec = validate_pathspec(argc, argv, prefix); - - if (read_cache() < 0) - die("index file corrupt"); - treat_gitlinks(pathspec); - - if (add_new_files) { - int baselen; - - /* Set up the default git porcelain excludes */ - memset(&dir, 0, sizeof(dir)); - if (!ignored_too) { - dir.flags |= DIR_COLLECT_IGNORED; - setup_standard_excludes(&dir); - } - - /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, pathspec); - if (pathspec) - prune_directory(&dir, pathspec, baselen); - } - - if (refresh_only) { - refresh(verbose, pathspec); - goto finish; - } - - exit_status |= add_files_to_cache(prefix, pathspec, flags); - - if (add_new_files) - exit_status |= add_files(&dir, flags); - - finish: - if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return exit_status; -} diff --git a/builtin-annotate.c b/builtin-annotate.c deleted file mode 100644 index fc43eed36..000000000 --- a/builtin-annotate.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * "git annotate" builtin alias - * - * Copyright (C) 2006 Ryan Anderson - */ -#include "git-compat-util.h" -#include "builtin.h" - -int cmd_annotate(int argc, const char **argv, const char *prefix) -{ - const char **nargv; - int i; - nargv = xmalloc(sizeof(char *) * (argc + 2)); - - nargv[0] = "annotate"; - nargv[1] = "-c"; - - for (i = 1; i < argc; i++) { - nargv[i+1] = argv[i]; - } - nargv[argc + 1] = NULL; - - return cmd_blame(argc + 1, nargv, prefix); -} diff --git a/builtin-apply.c b/builtin-apply.c deleted file mode 100644 index 3af4ae0c2..000000000 --- a/builtin-apply.c +++ /dev/null @@ -1,3670 +0,0 @@ -/* - * apply.c - * - * Copyright (C) Linus Torvalds, 2005 - * - * This applies patches on top of some (arbitrary) version of the SCM. - * - */ -#include "cache.h" -#include "cache-tree.h" -#include "quote.h" -#include "blob.h" -#include "delta.h" -#include "builtin.h" -#include "string-list.h" -#include "dir.h" -#include "parse-options.h" - -/* - * --check turns on checking that the working tree matches the - * files that are being modified, but doesn't apply the patch - * --stat does just a diffstat, and doesn't actually apply - * --numstat does numeric diffstat, and doesn't actually apply - * --index-info shows the old and new index info for paths if available. - * --index updates the cache as well. - * --cached updates only the cache without ever touching the working tree. - */ -static const char *prefix; -static int prefix_length = -1; -static int newfd = -1; - -static int unidiff_zero; -static int p_value = 1; -static int p_value_known; -static int check_index; -static int update_index; -static int cached; -static int diffstat; -static int numstat; -static int summary; -static int check; -static int apply = 1; -static int apply_in_reverse; -static int apply_with_reject; -static int apply_verbosely; -static int no_add; -static const char *fake_ancestor; -static int line_termination = '\n'; -static unsigned int p_context = UINT_MAX; -static const char * const apply_usage[] = { - "git apply [options] [...]", - NULL -}; - -static enum ws_error_action { - nowarn_ws_error, - warn_on_ws_error, - die_on_ws_error, - correct_ws_error, -} ws_error_action = warn_on_ws_error; -static int whitespace_error; -static int squelch_whitespace_errors = 5; -static int applied_after_fixing_ws; - -static enum ws_ignore { - ignore_ws_none, - ignore_ws_change, -} ws_ignore_action = ignore_ws_none; - - -static const char *patch_input_file; -static const char *root; -static int root_len; -static int read_stdin = 1; -static int options; - -static void parse_whitespace_option(const char *option) -{ - if (!option) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "warn")) { - ws_error_action = warn_on_ws_error; - return; - } - if (!strcmp(option, "nowarn")) { - ws_error_action = nowarn_ws_error; - return; - } - if (!strcmp(option, "error")) { - ws_error_action = die_on_ws_error; - return; - } - if (!strcmp(option, "error-all")) { - ws_error_action = die_on_ws_error; - squelch_whitespace_errors = 0; - return; - } - if (!strcmp(option, "strip") || !strcmp(option, "fix")) { - ws_error_action = correct_ws_error; - return; - } - die("unrecognized whitespace option '%s'", option); -} - -static void parse_ignorewhitespace_option(const char *option) -{ - if (!option || !strcmp(option, "no") || - !strcmp(option, "false") || !strcmp(option, "never") || - !strcmp(option, "none")) { - ws_ignore_action = ignore_ws_none; - return; - } - if (!strcmp(option, "change")) { - ws_ignore_action = ignore_ws_change; - return; - } - die("unrecognized whitespace ignore option '%s'", option); -} - -static void set_default_whitespace_mode(const char *whitespace_option) -{ - if (!whitespace_option && !apply_default_whitespace) - ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error); -} - -/* - * For "diff-stat" like behaviour, we keep track of the biggest change - * we've seen, and the longest filename. That allows us to do simple - * scaling. - */ -static int max_change, max_len; - -/* - * Various "current state", notably line numbers and what - * file (and how) we're patching right now.. The "is_xxxx" - * things are flags, where -1 means "don't know yet". - */ -static int linenr = 1; - -/* - * This represents one "hunk" from a patch, starting with - * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The - * patch text is pointed at by patch, and its byte length - * is stored in size. leading and trailing are the number - * of context lines. - */ -struct fragment { - unsigned long leading, trailing; - unsigned long oldpos, oldlines; - unsigned long newpos, newlines; - const char *patch; - int size; - int rejected; - int linenr; - struct fragment *next; -}; - -/* - * When dealing with a binary patch, we reuse "leading" field - * to store the type of the binary hunk, either deflated "delta" - * or deflated "literal". - */ -#define binary_patch_method leading -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 - -/* - * This represents a "patch" to a file, both metainfo changes - * such as creation/deletion, filemode and content changes represented - * as a series of fragments. - */ -struct patch { - char *new_name, *old_name, *def_name; - unsigned int old_mode, new_mode; - int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ - int rejected; - unsigned ws_rule; - unsigned long deflate_origlen; - int lines_added, lines_deleted; - int score; - unsigned int is_toplevel_relative:1; - unsigned int inaccurate_eof:1; - unsigned int is_binary:1; - unsigned int is_copy:1; - unsigned int is_rename:1; - unsigned int recount:1; - struct fragment *fragments; - char *result; - size_t resultsize; - char old_sha1_prefix[41]; - char new_sha1_prefix[41]; - struct patch *next; -}; - -/* - * A line in a file, len-bytes long (includes the terminating LF, - * except for an incomplete line at the end if the file ends with - * one), and its contents hashes to 'hash'. - */ -struct line { - size_t len; - unsigned hash : 24; - unsigned flag : 8; -#define LINE_COMMON 1 -}; - -/* - * This represents a "file", which is an array of "lines". - */ -struct image { - char *buf; - size_t len; - size_t nr; - size_t alloc; - struct line *line_allocated; - struct line *line; -}; - -/* - * Records filenames that have been touched, in order to handle - * the case where more than one patches touch the same file. - */ - -static struct string_list fn_table; - -static uint32_t hash_line(const char *cp, size_t len) -{ - size_t i; - uint32_t h; - for (i = 0, h = 0; i < len; i++) { - if (!isspace(cp[i])) { - h = h * 3 + (cp[i] & 0xff); - } - } - return h; -} - -/* - * Compare lines s1 of length n1 and s2 of length n2, ignoring - * whitespace difference. Returns 1 if they match, 0 otherwise - */ -static int fuzzy_matchlines(const char *s1, size_t n1, - const char *s2, size_t n2) -{ - const char *last1 = s1 + n1 - 1; - const char *last2 = s2 + n2 - 1; - int result = 0; - - if (n1 < 0 || n2 < 0) - return 0; - - /* ignore line endings */ - while ((*last1 == '\r') || (*last1 == '\n')) - last1--; - while ((*last2 == '\r') || (*last2 == '\n')) - last2--; - - /* skip leading whitespace */ - while (isspace(*s1) && (s1 <= last1)) - s1++; - while (isspace(*s2) && (s2 <= last2)) - s2++; - /* early return if both lines are empty */ - if ((s1 > last1) && (s2 > last2)) - return 1; - while (!result) { - result = *s1++ - *s2++; - /* - * Skip whitespace inside. We check for whitespace on - * both buffers because we don't want "a b" to match - * "ab" - */ - if (isspace(*s1) && isspace(*s2)) { - while (isspace(*s1) && s1 <= last1) - s1++; - while (isspace(*s2) && s2 <= last2) - s2++; - } - /* - * If we reached the end on one side only, - * lines don't match - */ - if ( - ((s2 > last2) && (s1 <= last1)) || - ((s1 > last1) && (s2 <= last2))) - return 0; - if ((s1 > last1) && (s2 > last2)) - break; - } - - return !result; -} - -static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) -{ - ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); - img->line_allocated[img->nr].len = len; - img->line_allocated[img->nr].hash = hash_line(bol, len); - img->line_allocated[img->nr].flag = flag; - img->nr++; -} - -static void prepare_image(struct image *image, char *buf, size_t len, - int prepare_linetable) -{ - const char *cp, *ep; - - memset(image, 0, sizeof(*image)); - image->buf = buf; - image->len = len; - - if (!prepare_linetable) - return; - - ep = image->buf + image->len; - cp = image->buf; - while (cp < ep) { - const char *next; - for (next = cp; next < ep && *next != '\n'; next++) - ; - if (next < ep) - next++; - add_line_info(image, cp, next - cp, 0); - cp = next; - } - image->line = image->line_allocated; -} - -static void clear_image(struct image *image) -{ - free(image->buf); - image->buf = NULL; - image->len = 0; -} - -static void say_patch_name(FILE *output, const char *pre, - struct patch *patch, const char *post) -{ - fputs(pre, output); - if (patch->old_name && patch->new_name && - strcmp(patch->old_name, patch->new_name)) { - quote_c_style(patch->old_name, NULL, output, 0); - fputs(" => ", output); - quote_c_style(patch->new_name, NULL, output, 0); - } else { - const char *n = patch->new_name; - if (!n) - n = patch->old_name; - quote_c_style(n, NULL, output, 0); - } - fputs(post, output); -} - -#define CHUNKSIZE (8192) -#define SLOP (16) - -static void read_patch_file(struct strbuf *sb, int fd) -{ - if (strbuf_read(sb, fd, 0) < 0) - die_errno("git apply: failed to read"); - - /* - * Make sure that we have some slop in the buffer - * so that we can do speculative "memcmp" etc, and - * see to it that it is NUL-filled. - */ - strbuf_grow(sb, SLOP); - memset(sb->buf + sb->len, 0, SLOP); -} - -static unsigned long linelen(const char *buffer, unsigned long size) -{ - unsigned long len = 0; - while (size--) { - len++; - if (*buffer++ == '\n') - break; - } - return len; -} - -static int is_dev_null(const char *str) -{ - return !memcmp("/dev/null", str, 9) && isspace(str[9]); -} - -#define TERM_SPACE 1 -#define TERM_TAB 2 - -static int name_terminate(const char *name, int namelen, int c, int terminate) -{ - if (c == ' ' && !(terminate & TERM_SPACE)) - return 0; - if (c == '\t' && !(terminate & TERM_TAB)) - return 0; - - return 1; -} - -/* remove double slashes to make --index work with such filenames */ -static char *squash_slash(char *name) -{ - int i = 0, j = 0; - - if (!name) - return NULL; - - while (name[i]) { - if ((name[j++] = name[i++]) == '/') - while (name[i] == '/') - i++; - } - name[j] = '\0'; - return name; -} - -static char *find_name(const char *line, char *def, int p_value, int terminate) -{ - int len; - const char *start = NULL; - - if (p_value == 0) - start = line; - - if (*line == '"') { - struct strbuf name = STRBUF_INIT; - - /* - * Proposed "new-style" GNU patch/diff format; see - * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 - */ - if (!unquote_c_style(&name, line, NULL)) { - char *cp; - - for (cp = name.buf; p_value; p_value--) { - cp = strchr(cp, '/'); - if (!cp) - break; - cp++; - } - if (cp) { - /* name can later be freed, so we need - * to memmove, not just return cp - */ - strbuf_remove(&name, 0, cp - name.buf); - free(def); - if (root) - strbuf_insert(&name, 0, root, root_len); - return squash_slash(strbuf_detach(&name, NULL)); - } - } - strbuf_release(&name); - } - - for (;;) { - char c = *line; - - if (isspace(c)) { - if (c == '\n') - break; - if (name_terminate(start, line-start, c, terminate)) - break; - } - line++; - if (c == '/' && !--p_value) - start = line; - } - if (!start) - return squash_slash(def); - len = line - start; - if (!len) - return squash_slash(def); - - /* - * Generally we prefer the shorter name, especially - * if the other one is just a variation of that with - * something else tacked on to the end (ie "file.orig" - * or "file~"). - */ - if (def) { - int deflen = strlen(def); - if (deflen < len && !strncmp(start, def, deflen)) - return squash_slash(def); - free(def); - } - - if (root) { - char *ret = xmalloc(root_len + len + 1); - strcpy(ret, root); - memcpy(ret + root_len, start, len); - ret[root_len + len] = '\0'; - return squash_slash(ret); - } - - return squash_slash(xmemdupz(start, len)); -} - -static int count_slashes(const char *cp) -{ - int cnt = 0; - char ch; - - while ((ch = *cp++)) - if (ch == '/') - cnt++; - return cnt; -} - -/* - * Given the string after "--- " or "+++ ", guess the appropriate - * p_value for the given patch. - */ -static int guess_p_value(const char *nameline) -{ - char *name, *cp; - int val = -1; - - if (is_dev_null(nameline)) - return -1; - name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB); - if (!name) - return -1; - cp = strchr(name, '/'); - if (!cp) - val = 0; - else if (prefix) { - /* - * Does it begin with "a/$our-prefix" and such? Then this is - * very likely to apply to our directory. - */ - if (!strncmp(name, prefix, prefix_length)) - val = count_slashes(prefix); - else { - cp++; - if (!strncmp(cp, prefix, prefix_length)) - val = count_slashes(prefix) + 1; - } - } - free(name); - return val; -} - -/* - * Does the ---/+++ line has the POSIX timestamp after the last HT? - * GNU diff puts epoch there to signal a creation/deletion event. Is - * this such a timestamp? - */ -static int has_epoch_timestamp(const char *nameline) -{ - /* - * We are only interested in epoch timestamp; any non-zero - * fraction cannot be one, hence "(\.0+)?" in the regexp below. - * For the same reason, the date must be either 1969-12-31 or - * 1970-01-01, and the seconds part must be "00". - */ - const char stamp_regexp[] = - "^(1969-12-31|1970-01-01)" - " " - "[0-2][0-9]:[0-5][0-9]:00(\\.0+)?" - " " - "([-+][0-2][0-9][0-5][0-9])\n"; - const char *timestamp = NULL, *cp; - static regex_t *stamp; - regmatch_t m[10]; - int zoneoffset; - int hourminute; - int status; - - for (cp = nameline; *cp != '\n'; cp++) { - if (*cp == '\t') - timestamp = cp + 1; - } - if (!timestamp) - return 0; - if (!stamp) { - stamp = xmalloc(sizeof(*stamp)); - if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) { - warning("Cannot prepare timestamp regexp %s", - stamp_regexp); - return 0; - } - } - - status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0); - if (status) { - if (status != REG_NOMATCH) - warning("regexec returned %d for input: %s", - status, timestamp); - return 0; - } - - zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10); - zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100); - if (timestamp[m[3].rm_so] == '-') - zoneoffset = -zoneoffset; - - /* - * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31 - * (west of GMT) or 1970-01-01 (east of GMT) - */ - if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) || - (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10))) - return 0; - - hourminute = (strtol(timestamp + 11, NULL, 10) * 60 + - strtol(timestamp + 14, NULL, 10) - - zoneoffset); - - return ((zoneoffset < 0 && hourminute == 1440) || - (0 <= zoneoffset && !hourminute)); -} - -/* - * Get the name etc info from the ---/+++ lines of a traditional patch header - * - * FIXME! The end-of-filename heuristics are kind of screwy. For existing - * files, we can happily check the index for a match, but for creating a - * new file we should try to match whatever "patch" does. I have no idea. - */ -static void parse_traditional_patch(const char *first, const char *second, struct patch *patch) -{ - char *name; - - first += 4; /* skip "--- " */ - second += 4; /* skip "+++ " */ - if (!p_value_known) { - int p, q; - p = guess_p_value(first); - q = guess_p_value(second); - if (p < 0) p = q; - if (0 <= p && p == q) { - p_value = p; - p_value_known = 1; - } - } - if (is_dev_null(first)) { - patch->is_new = 1; - patch->is_delete = 0; - name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB); - patch->new_name = name; - } else if (is_dev_null(second)) { - patch->is_new = 0; - patch->is_delete = 1; - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); - patch->old_name = name; - } else { - name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB); - name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB); - if (has_epoch_timestamp(first)) { - patch->is_new = 1; - patch->is_delete = 0; - patch->new_name = name; - } else if (has_epoch_timestamp(second)) { - patch->is_new = 0; - patch->is_delete = 1; - patch->old_name = name; - } else { - patch->old_name = patch->new_name = name; - } - } - if (!name) - die("unable to find filename in patch at line %d", linenr); -} - -static int gitdiff_hdrend(const char *line, struct patch *patch) -{ - return -1; -} - -/* - * We're anal about diff header consistency, to make - * sure that we don't end up having strange ambiguous - * patches floating around. - * - * As a result, gitdiff_{old|new}name() will check - * their names against any previous information, just - * to make sure.. - */ -static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) -{ - if (!orig_name && !isnull) - return find_name(line, NULL, p_value, TERM_TAB); - - if (orig_name) { - int len; - const char *name; - char *another; - name = orig_name; - len = strlen(name); - if (isnull) - die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, p_value, TERM_TAB); - if (!another || memcmp(another, name, len + 1)) - die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); - free(another); - return orig_name; - } - else { - /* expect "/dev/null" */ - if (memcmp("/dev/null", line, 9) || line[9] != '\n') - die("git apply: bad git-diff - expected /dev/null on line %d", linenr); - return NULL; - } -} - -static int gitdiff_oldname(const char *line, struct patch *patch) -{ - patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old"); - return 0; -} - -static int gitdiff_newname(const char *line, struct patch *patch) -{ - patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new"); - return 0; -} - -static int gitdiff_oldmode(const char *line, struct patch *patch) -{ - patch->old_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_newmode(const char *line, struct patch *patch) -{ - patch->new_mode = strtoul(line, NULL, 8); - return 0; -} - -static int gitdiff_delete(const char *line, struct patch *patch) -{ - patch->is_delete = 1; - patch->old_name = patch->def_name; - return gitdiff_oldmode(line, patch); -} - -static int gitdiff_newfile(const char *line, struct patch *patch) -{ - patch->is_new = 1; - patch->new_name = patch->def_name; - return gitdiff_newmode(line, patch); -} - -static int gitdiff_copysrc(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_copydst(const char *line, struct patch *patch) -{ - patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_renamesrc(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_renamedst(const char *line, struct patch *patch) -{ - patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); - return 0; -} - -static int gitdiff_similarity(const char *line, struct patch *patch) -{ - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; - return 0; -} - -static int gitdiff_dissimilarity(const char *line, struct patch *patch) -{ - if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX) - patch->score = 0; - return 0; -} - -static int gitdiff_index(const char *line, struct patch *patch) -{ - /* - * index line is N hexadecimal, "..", N hexadecimal, - * and optional space with octal mode. - */ - const char *ptr, *eol; - int len; - - ptr = strchr(line, '.'); - if (!ptr || ptr[1] != '.' || 40 < ptr - line) - return 0; - len = ptr - line; - memcpy(patch->old_sha1_prefix, line, len); - patch->old_sha1_prefix[len] = 0; - - line = ptr + 2; - ptr = strchr(line, ' '); - eol = strchr(line, '\n'); - - if (!ptr || eol < ptr) - ptr = eol; - len = ptr - line; - - if (40 < len) - return 0; - memcpy(patch->new_sha1_prefix, line, len); - patch->new_sha1_prefix[len] = 0; - if (*ptr == ' ') - patch->old_mode = strtoul(ptr+1, NULL, 8); - return 0; -} - -/* - * This is normal for a diff that doesn't change anything: we'll fall through - * into the next diff. Tell the parser to break out. - */ -static int gitdiff_unrecognized(const char *line, struct patch *patch) -{ - return -1; -} - -static const char *stop_at_slash(const char *line, int llen) -{ - int nslash = p_value; - int i; - - for (i = 0; i < llen; i++) { - int ch = line[i]; - if (ch == '/' && --nslash <= 0) - return &line[i]; - } - return NULL; -} - -/* - * This is to extract the same name that appears on "diff --git" - * line. We do not find and return anything if it is a rename - * patch, and it is OK because we will find the name elsewhere. - * We need to reliably find name only when it is mode-change only, - * creation or deletion of an empty file. In any of these cases, - * both sides are the same name under a/ and b/ respectively. - */ -static char *git_header_name(char *line, int llen) -{ - const char *name; - const char *second = NULL; - size_t len; - - line += strlen("diff --git "); - llen -= strlen("diff --git "); - - if (*line == '"') { - const char *cp; - struct strbuf first = STRBUF_INIT; - struct strbuf sp = STRBUF_INIT; - - if (unquote_c_style(&first, line, &second)) - goto free_and_fail1; - - /* advance to the first slash */ - cp = stop_at_slash(first.buf, first.len); - /* we do not accept absolute paths */ - if (!cp || cp == first.buf) - goto free_and_fail1; - strbuf_remove(&first, 0, cp + 1 - first.buf); - - /* - * second points at one past closing dq of name. - * find the second name. - */ - while ((second < line + llen) && isspace(*second)) - second++; - - if (line + llen <= second) - goto free_and_fail1; - if (*second == '"') { - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail1; - cp = stop_at_slash(sp.buf, sp.len); - if (!cp || cp == sp.buf) - goto free_and_fail1; - /* They must match, otherwise ignore */ - if (strcmp(cp + 1, first.buf)) - goto free_and_fail1; - strbuf_release(&sp); - return strbuf_detach(&first, NULL); - } - - /* unquoted second */ - cp = stop_at_slash(second, line + llen - second); - if (!cp || cp == second) - goto free_and_fail1; - cp++; - if (line + llen - cp != first.len + 1 || - memcmp(first.buf, cp, first.len)) - goto free_and_fail1; - return strbuf_detach(&first, NULL); - - free_and_fail1: - strbuf_release(&first); - strbuf_release(&sp); - return NULL; - } - - /* unquoted first name */ - name = stop_at_slash(line, llen); - if (!name || name == line) - return NULL; - name++; - - /* - * since the first name is unquoted, a dq if exists must be - * the beginning of the second name. - */ - for (second = name; second < line + llen; second++) { - if (*second == '"') { - struct strbuf sp = STRBUF_INIT; - const char *np; - - if (unquote_c_style(&sp, second, NULL)) - goto free_and_fail2; - - np = stop_at_slash(sp.buf, sp.len); - if (!np || np == sp.buf) - goto free_and_fail2; - np++; - - len = sp.buf + sp.len - np; - if (len < second - name && - !strncmp(np, name, len) && - isspace(name[len])) { - /* Good */ - strbuf_remove(&sp, 0, np - sp.buf); - return strbuf_detach(&sp, NULL); - } - - free_and_fail2: - strbuf_release(&sp); - return NULL; - } - } - - /* - * Accept a name only if it shows up twice, exactly the same - * form. - */ - for (len = 0 ; ; len++) { - switch (name[len]) { - default: - continue; - case '\n': - return NULL; - case '\t': case ' ': - second = name+len; - for (;;) { - char c = *second++; - if (c == '\n') - return NULL; - if (c == '/') - break; - } - if (second[len] == '\n' && !memcmp(name, second, len)) { - return xmemdupz(name, len); - } - } - } -} - -/* Verify that we recognize the lines following a git header */ -static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch) -{ - unsigned long offset; - - /* A git diff has explicit new/delete information, so we don't guess */ - patch->is_new = 0; - patch->is_delete = 0; - - /* - * Some things may not have the old name in the - * rest of the headers anywhere (pure mode changes, - * or removing or adding empty files), so we get - * the default name from the header. - */ - patch->def_name = git_header_name(line, len); - if (patch->def_name && root) { - char *s = xmalloc(root_len + strlen(patch->def_name) + 1); - strcpy(s, root); - strcpy(s + root_len, patch->def_name); - free(patch->def_name); - patch->def_name = s; - } - - line += len; - size -= len; - linenr++; - for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) { - static const struct opentry { - const char *str; - int (*fn)(const char *, struct patch *); - } optable[] = { - { "@@ -", gitdiff_hdrend }, - { "--- ", gitdiff_oldname }, - { "+++ ", gitdiff_newname }, - { "old mode ", gitdiff_oldmode }, - { "new mode ", gitdiff_newmode }, - { "deleted file mode ", gitdiff_delete }, - { "new file mode ", gitdiff_newfile }, - { "copy from ", gitdiff_copysrc }, - { "copy to ", gitdiff_copydst }, - { "rename old ", gitdiff_renamesrc }, - { "rename new ", gitdiff_renamedst }, - { "rename from ", gitdiff_renamesrc }, - { "rename to ", gitdiff_renamedst }, - { "similarity index ", gitdiff_similarity }, - { "dissimilarity index ", gitdiff_dissimilarity }, - { "index ", gitdiff_index }, - { "", gitdiff_unrecognized }, - }; - int i; - - len = linelen(line, size); - if (!len || line[len-1] != '\n') - break; - for (i = 0; i < ARRAY_SIZE(optable); i++) { - const struct opentry *p = optable + i; - int oplen = strlen(p->str); - if (len < oplen || memcmp(p->str, line, oplen)) - continue; - if (p->fn(line + oplen, patch) < 0) - return offset; - break; - } - } - - return offset; -} - -static int parse_num(const char *line, unsigned long *p) -{ - char *ptr; - - if (!isdigit(*line)) - return 0; - *p = strtoul(line, &ptr, 10); - return ptr - line; -} - -static int parse_range(const char *line, int len, int offset, const char *expect, - unsigned long *p1, unsigned long *p2) -{ - int digits, ex; - - if (offset < 0 || offset >= len) - return -1; - line += offset; - len -= offset; - - digits = parse_num(line, p1); - if (!digits) - return -1; - - offset += digits; - line += digits; - len -= digits; - - *p2 = 1; - if (*line == ',') { - digits = parse_num(line+1, p2); - if (!digits) - return -1; - - offset += digits+1; - line += digits+1; - len -= digits+1; - } - - ex = strlen(expect); - if (ex > len) - return -1; - if (memcmp(line, expect, ex)) - return -1; - - return offset + ex; -} - -static void recount_diff(char *line, int size, struct fragment *fragment) -{ - int oldlines = 0, newlines = 0, ret = 0; - - if (size < 1) { - warning("recount: ignore empty hunk"); - return; - } - - for (;;) { - int len = linelen(line, size); - size -= len; - line += len; - - if (size < 1) - break; - - switch (*line) { - case ' ': case '\n': - newlines++; - /* fall through */ - case '-': - oldlines++; - continue; - case '+': - newlines++; - continue; - case '\\': - continue; - case '@': - ret = size < 3 || prefixcmp(line, "@@ "); - break; - case 'd': - ret = size < 5 || prefixcmp(line, "diff "); - break; - default: - ret = -1; - break; - } - if (ret) { - warning("recount: unexpected line: %.*s", - (int)linelen(line, size), line); - return; - } - break; - } - fragment->oldlines = oldlines; - fragment->newlines = newlines; -} - -/* - * Parse a unified diff fragment header of the - * form "@@ -a,b +c,d @@" - */ -static int parse_fragment_header(char *line, int len, struct fragment *fragment) -{ - int offset; - - if (!len || line[len-1] != '\n') - return -1; - - /* Figure out the number of lines in a fragment */ - offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines); - offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines); - - return offset; -} - -static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch) -{ - unsigned long offset, len; - - patch->is_toplevel_relative = 0; - patch->is_rename = patch->is_copy = 0; - patch->is_new = patch->is_delete = -1; - patch->old_mode = patch->new_mode = 0; - patch->old_name = patch->new_name = NULL; - for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) { - unsigned long nextlen; - - len = linelen(line, size); - if (!len) - break; - - /* Testing this early allows us to take a few shortcuts.. */ - if (len < 6) - continue; - - /* - * Make sure we don't find any unconnected patch fragments. - * That's a sign that we didn't find a header, and that a - * patch has become corrupted/broken up. - */ - if (!memcmp("@@ -", line, 4)) { - struct fragment dummy; - if (parse_fragment_header(line, len, &dummy) < 0) - continue; - die("patch fragment without header at line %d: %.*s", - linenr, (int)len-1, line); - } - - if (size < len + 6) - break; - - /* - * Git patch? It might not have a real patch, just a rename - * or mode change, so we handle that specially - */ - if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_header(line, len, size, patch); - if (git_hdr_len <= len) - continue; - if (!patch->old_name && !patch->new_name) { - if (!patch->def_name) - die("git diff header lacks filename information when removing " - "%d leading pathname components (line %d)" , p_value, linenr); - patch->old_name = patch->new_name = patch->def_name; - } - patch->is_toplevel_relative = 1; - *hdrsize = git_hdr_len; - return offset; - } - - /* --- followed by +++ ? */ - if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) - continue; - - /* - * We only accept unified patches, so we want it to - * at least have "@@ -a,b +c,d @@\n", which is 14 chars - * minimum ("@@ -0,0 +1 @@\n" is the shortest). - */ - nextlen = linelen(line + len, size - len); - if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) - continue; - - /* Ok, we'll consider it a patch */ - parse_traditional_patch(line, line+len, patch); - *hdrsize = len + nextlen; - linenr += 2; - return offset; - } - return -1; -} - -static void record_ws_error(unsigned result, const char *line, int len, int linenr) -{ - char *err; - - if (!result) - return; - - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) - return; - - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len, line); - free(err); -} - -static void check_whitespace(const char *line, int len, unsigned ws_rule) -{ - unsigned result = ws_check(line + 1, len - 1, ws_rule); - - record_ws_error(result, line + 1, len - 2, linenr); -} - -/* - * Parse a unified diff. Note that this really needs to parse each - * fragment separately, since the only way to know the difference - * between a "---" that is part of a patch, and a "---" that starts - * the next patch is to look at the line counts.. - */ -static int parse_fragment(char *line, unsigned long size, - struct patch *patch, struct fragment *fragment) -{ - int added, deleted; - int len = linelen(line, size), offset; - unsigned long oldlines, newlines; - unsigned long leading, trailing; - - offset = parse_fragment_header(line, len, fragment); - if (offset < 0) - return -1; - if (offset > 0 && patch->recount) - recount_diff(line + offset, size - offset, fragment); - oldlines = fragment->oldlines; - newlines = fragment->newlines; - leading = 0; - trailing = 0; - - /* Parse the thing.. */ - line += len; - size -= len; - linenr++; - added = deleted = 0; - for (offset = len; - 0 < size; - offset += len, size -= len, line += len, linenr++) { - if (!oldlines && !newlines) - break; - len = linelen(line, size); - if (!len || line[len-1] != '\n') - return -1; - switch (*line) { - default: - return -1; - case '\n': /* newer GNU diff, an empty context line */ - case ' ': - oldlines--; - newlines--; - if (!deleted && !added) - leading++; - trailing++; - break; - case '-': - if (apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - deleted++; - oldlines--; - trailing = 0; - break; - case '+': - if (!apply_in_reverse && - ws_error_action != nowarn_ws_error) - check_whitespace(line, len, patch->ws_rule); - added++; - newlines--; - trailing = 0; - break; - - /* - * We allow "\ No newline at end of file". Depending - * on locale settings when the patch was produced we - * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". - * Checking for 12 is just for sanity check -- any - * l10n of "\ No newline..." is at least that long. - */ - case '\\': - if (len < 12 || memcmp(line, "\\ ", 2)) - return -1; - break; - } - } - if (oldlines || newlines) - return -1; - fragment->leading = leading; - fragment->trailing = trailing; - - /* - * If a fragment ends with an incomplete line, we failed to include - * it in the above loop because we hit oldlines == newlines == 0 - * before seeing it. - */ - if (12 < size && !memcmp(line, "\\ ", 2)) - offset += linelen(line, size); - - patch->lines_added += added; - patch->lines_deleted += deleted; - - if (0 < patch->is_new && oldlines) - return error("new file depends on old contents"); - if (0 < patch->is_delete && newlines) - return error("deleted file still has contents"); - return offset; -} - -static int parse_single_patch(char *line, unsigned long size, struct patch *patch) -{ - unsigned long offset = 0; - unsigned long oldlines = 0, newlines = 0, context = 0; - struct fragment **fragp = &patch->fragments; - - while (size > 4 && !memcmp(line, "@@ -", 4)) { - struct fragment *fragment; - int len; - - fragment = xcalloc(1, sizeof(*fragment)); - fragment->linenr = linenr; - len = parse_fragment(line, size, patch, fragment); - if (len <= 0) - die("corrupt patch at line %d", linenr); - fragment->patch = line; - fragment->size = len; - oldlines += fragment->oldlines; - newlines += fragment->newlines; - context += fragment->leading + fragment->trailing; - - *fragp = fragment; - fragp = &fragment->next; - - offset += len; - line += len; - size -= len; - } - - /* - * If something was removed (i.e. we have old-lines) it cannot - * be creation, and if something was added it cannot be - * deletion. However, the reverse is not true; --unified=0 - * patches that only add are not necessarily creation even - * though they do not have any old lines, and ones that only - * delete are not necessarily deletion. - * - * Unfortunately, a real creation/deletion patch do _not_ have - * any context line by definition, so we cannot safely tell it - * apart with --unified=0 insanity. At least if the patch has - * more than one hunk it is not creation or deletion. - */ - if (patch->is_new < 0 && - (oldlines || (patch->fragments && patch->fragments->next))) - patch->is_new = 0; - if (patch->is_delete < 0 && - (newlines || (patch->fragments && patch->fragments->next))) - patch->is_delete = 0; - - if (0 < patch->is_new && oldlines) - die("new file %s depends on old contents", patch->new_name); - if (0 < patch->is_delete && newlines) - die("deleted file %s still has contents", patch->old_name); - if (!patch->is_delete && !newlines && context) - fprintf(stderr, "** warning: file %s becomes empty but " - "is not deleted\n", patch->new_name); - - return offset; -} - -static inline int metadata_changes(struct patch *patch) -{ - return patch->is_rename > 0 || - patch->is_copy > 0 || - patch->is_new > 0 || - patch->is_delete || - (patch->old_mode && patch->new_mode && - patch->old_mode != patch->new_mode); -} - -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - git_inflate_init(&stream); - st = git_inflate(&stream, Z_FINISH); - git_inflate_end(&stream); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - -static struct fragment *parse_binary_hunk(char **buf_p, - unsigned long *sz_p, - int *status_p, - int *used_p) -{ - /* - * Expect a line that begins with binary patch method ("literal" - * or "delta"), followed by the length of data before deflating. - * a sequence of 'length-byte' followed by base-85 encoded data - * should follow, terminated by a newline. - * - * Each 5-byte sequence of base-85 encodes up to 4 bytes, - * and we would limit the patch line to 66 characters, - * so one line can fit up to 13 groups that would decode - * to 52 bytes max. The length byte 'A'-'Z' corresponds - * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - */ - int llen, used; - unsigned long size = *sz_p; - char *buffer = *buf_p; - int patch_method; - unsigned long origlen; - char *data = NULL; - int hunk_size = 0; - struct fragment *frag; - - llen = linelen(buffer, size); - used = llen; - - *status_p = 0; - - if (!prefixcmp(buffer, "delta ")) { - patch_method = BINARY_DELTA_DEFLATED; - origlen = strtoul(buffer + 6, NULL, 10); - } - else if (!prefixcmp(buffer, "literal ")) { - patch_method = BINARY_LITERAL_DEFLATED; - origlen = strtoul(buffer + 8, NULL, 10); - } - else - return NULL; - - linenr++; - buffer += llen; - while (1) { - int byte_length, max_byte_length, newsize; - llen = linelen(buffer, size); - used += llen; - linenr++; - if (llen == 1) { - /* consume the blank line */ - buffer++; - size--; - break; - } - /* - * Minimum line is "A00000\n" which is 7-byte long, - * and the line length must be multiple of 5 plus 2. - */ - if ((llen < 7) || (llen-2) % 5) - goto corrupt; - max_byte_length = (llen - 2) / 5 * 4; - byte_length = *buffer; - if ('A' <= byte_length && byte_length <= 'Z') - byte_length = byte_length - 'A' + 1; - else if ('a' <= byte_length && byte_length <= 'z') - byte_length = byte_length - 'a' + 27; - else - goto corrupt; - /* if the input length was not multiple of 4, we would - * have filler at the end but the filler should never - * exceed 3 bytes - */ - if (max_byte_length < byte_length || - byte_length <= max_byte_length - 4) - goto corrupt; - newsize = hunk_size + byte_length; - data = xrealloc(data, newsize); - if (decode_85(data + hunk_size, buffer + 1, byte_length)) - goto corrupt; - hunk_size = newsize; - buffer += llen; - size -= llen; - } - - frag = xcalloc(1, sizeof(*frag)); - frag->patch = inflate_it(data, hunk_size, origlen); - if (!frag->patch) - goto corrupt; - free(data); - frag->size = origlen; - *buf_p = buffer; - *sz_p = size; - *used_p = used; - frag->binary_patch_method = patch_method; - return frag; - - corrupt: - free(data); - *status_p = -1; - error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); - return NULL; -} - -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) -{ - /* - * We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "literal" or - * "delta") and the length of data before deflating; a - * sequence of 'length-byte' followed by base-85 encoded data - * follows. - * - * When a binary patch is reversible, there is another binary - * hunk in the same format, starting with patch method (either - * "literal" or "delta") with the length of data, and a sequence - * of length-byte + base-85 encoded data, terminated with another - * empty line. This data, when applied to the postimage, produces - * the preimage. - */ - struct fragment *forward; - struct fragment *reverse; - int status; - int used, used_1; - - forward = parse_binary_hunk(&buffer, &size, &status, &used); - if (!forward && !status) - /* there has to be one hunk (forward hunk) */ - return error("unrecognized binary patch at line %d", linenr-1); - if (status) - /* otherwise we already gave an error message */ - return status; - - reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); - if (reverse) - used += used_1; - else if (status) { - /* - * Not having reverse hunk is not an error, but having - * a corrupt reverse hunk is. - */ - free((void*) forward->patch); - free(forward); - return status; - } - forward->next = reverse; - patch->fragments = forward; - patch->is_binary = 1; - return used; -} - -static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) -{ - int hdrsize, patchsize; - int offset = find_header(buffer, size, &hdrsize, patch); - - if (offset < 0) - return offset; - - patch->ws_rule = whitespace_rule(patch->new_name - ? patch->new_name - : patch->old_name); - - patchsize = parse_single_patch(buffer + offset + hdrsize, - size - offset - hdrsize, patch); - - if (!patchsize) { - static const char *binhdr[] = { - "Binary files ", - "Files ", - NULL, - }; - static const char git_binary[] = "GIT binary patch\n"; - int i; - int hd = hdrsize + offset; - unsigned long llen = linelen(buffer + hd, size - hd); - - if (llen == sizeof(git_binary) - 1 && - !memcmp(git_binary, buffer + hd, llen)) { - int used; - linenr++; - used = parse_binary(buffer + hd + llen, - size - hd - llen, patch); - if (used) - patchsize = used + llen; - else - patchsize = 0; - } - else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) { - for (i = 0; binhdr[i]; i++) { - int len = strlen(binhdr[i]); - if (len < size - hd && - !memcmp(binhdr[i], buffer + hd, len)) { - linenr++; - patch->is_binary = 1; - patchsize = llen; - break; - } - } - } - - /* Empty patch cannot be applied if it is a text patch - * without metadata change. A binary patch appears - * empty to us here. - */ - if ((apply || check) && - (!patch->is_binary && !metadata_changes(patch))) - die("patch with only garbage at line %d", linenr); - } - - return offset + hdrsize + patchsize; -} - -#define swap(a,b) myswap((a),(b),sizeof(a)) - -#define myswap(a, b, size) do { \ - unsigned char mytmp[size]; \ - memcpy(mytmp, &a, size); \ - memcpy(&a, &b, size); \ - memcpy(&b, mytmp, size); \ -} while (0) - -static void reverse_patches(struct patch *p) -{ - for (; p; p = p->next) { - struct fragment *frag = p->fragments; - - swap(p->new_name, p->old_name); - swap(p->new_mode, p->old_mode); - swap(p->is_new, p->is_delete); - swap(p->lines_added, p->lines_deleted); - swap(p->old_sha1_prefix, p->new_sha1_prefix); - - for (; frag; frag = frag->next) { - swap(frag->newpos, frag->oldpos); - swap(frag->newlines, frag->oldlines); - } - } -} - -static const char pluses[] = -"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= -"----------------------------------------------------------------------"; - -static void show_stats(struct patch *patch) -{ - struct strbuf qname = STRBUF_INIT; - char *cp = patch->new_name ? patch->new_name : patch->old_name; - int max, add, del; - - quote_c_style(cp, &qname, NULL, 0); - - /* - * "scale" the filename - */ - max = max_len; - if (max > 50) - max = 50; - - if (qname.len > max) { - cp = strchr(qname.buf + qname.len + 3 - max, '/'); - if (!cp) - cp = qname.buf + qname.len + 3 - max; - strbuf_splice(&qname, 0, cp - qname.buf, "...", 3); - } - - if (patch->is_binary) { - printf(" %-*s | Bin\n", max, qname.buf); - strbuf_release(&qname); - return; - } - - printf(" %-*s |", max, qname.buf); - strbuf_release(&qname); - - /* - * scale the add/delete - */ - max = max + max_change > 70 ? 70 - max : max_change; - add = patch->lines_added; - del = patch->lines_deleted; - - if (max_change > 0) { - int total = ((add + del) * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted, - add, pluses, del, minuses); -} - -static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) -{ - switch (st->st_mode & S_IFMT) { - case S_IFLNK: - if (strbuf_readlink(buf, path, st->st_size) < 0) - return error("unable to read symlink %s", path); - return 0; - case S_IFREG: - if (strbuf_read_file(buf, path, st->st_size) != st->st_size) - return error("unable to open or read %s", path); - convert_to_git(path, buf->buf, buf->len, buf, 0); - return 0; - default: - return -1; - } -} - -/* - * Update the preimage, and the common lines in postimage, - * from buffer buf of length len. If postlen is 0 the postimage - * is updated in place, otherwise it's updated on a new buffer - * of length postlen - */ - -static void update_pre_post_images(struct image *preimage, - struct image *postimage, - char *buf, - size_t len, size_t postlen) -{ - int i, ctx; - char *new, *old, *fixed; - struct image fixed_preimage; - - /* - * Update the preimage with whitespace fixes. Note that we - * are not losing preimage->buf -- apply_one_fragment() will - * free "oldlines". - */ - prepare_image(&fixed_preimage, buf, len, 1); - assert(fixed_preimage.nr == preimage->nr); - for (i = 0; i < preimage->nr; i++) - fixed_preimage.line[i].flag = preimage->line[i].flag; - free(preimage->line_allocated); - *preimage = fixed_preimage; - - /* - * Adjust the common context lines in postimage. This can be - * done in-place when we are just doing whitespace fixing, - * which does not make the string grow, but needs a new buffer - * when ignoring whitespace causes the update, since in this case - * we could have e.g. tabs converted to multiple spaces. - * We trust the caller to tell us if the update can be done - * in place (postlen==0) or not. - */ - old = postimage->buf; - if (postlen) - new = postimage->buf = xmalloc(postlen); - else - new = old; - fixed = preimage->buf; - for (i = ctx = 0; i < postimage->nr; i++) { - size_t len = postimage->line[i].len; - if (!(postimage->line[i].flag & LINE_COMMON)) { - /* an added line -- no counterparts in preimage */ - memmove(new, old, len); - old += len; - new += len; - continue; - } - - /* a common context -- skip it in the original postimage */ - old += len; - - /* and find the corresponding one in the fixed preimage */ - while (ctx < preimage->nr && - !(preimage->line[ctx].flag & LINE_COMMON)) { - fixed += preimage->line[ctx].len; - ctx++; - } - if (preimage->nr <= ctx) - die("oops"); - - /* and copy it in, while fixing the line length */ - len = preimage->line[ctx].len; - memcpy(new, fixed, len); - new += len; - fixed += len; - postimage->line[i].len = len; - ctx++; - } - - /* Fix the length of the whole thing */ - postimage->len = new - postimage->buf; -} - -static int match_fragment(struct image *img, - struct image *preimage, - struct image *postimage, - unsigned long try, - int try_lno, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - char *fixed_buf, *buf, *orig, *target; - - if (preimage->nr + try_lno > img->nr) - return 0; - - if (match_beginning && try_lno) - return 0; - - if (match_end && preimage->nr + try_lno != img->nr) - return 0; - - /* Quick hash check */ - for (i = 0; i < preimage->nr; i++) - if (preimage->line[i].hash != img->line[try_lno + i].hash) - return 0; - - /* - * Do we have an exact match? If we were told to match - * at the end, size must be exactly at try+fragsize, - * otherwise try+fragsize must be still within the preimage, - * and either case, the old piece should match the preimage - * exactly. - */ - if ((match_end - ? (try + preimage->len == img->len) - : (try + preimage->len <= img->len)) && - !memcmp(img->buf + try, preimage->buf, preimage->len)) - return 1; - - /* - * No exact match. If we are ignoring whitespace, run a line-by-line - * fuzzy matching. We collect all the line length information because - * we need it to adjust whitespace if we match. - */ - if (ws_ignore_action == ignore_ws_change) { - size_t imgoff = 0; - size_t preoff = 0; - size_t postlen = postimage->len; - for (i = 0; i < preimage->nr; i++) { - size_t prelen = preimage->line[i].len; - size_t imglen = img->line[try_lno+i].len; - - if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, - preimage->buf + preoff, prelen)) - return 0; - if (preimage->line[i].flag & LINE_COMMON) - postlen += imglen - prelen; - imgoff += imglen; - preoff += prelen; - } - - /* - * Ok, the preimage matches with whitespace fuzz. Update it and - * the common postimage lines to use the same whitespace as the - * target. imgoff now holds the true length of the target that - * matches the preimage, and we need to update the line lengths - * of the preimage to match the target ones. - */ - fixed_buf = xmalloc(imgoff); - memcpy(fixed_buf, img->buf + try, imgoff); - for (i = 0; i < preimage->nr; i++) - preimage->line[i].len = img->line[try_lno+i].len; - - /* - * Update the preimage buffer and the postimage context lines. - */ - update_pre_post_images(preimage, postimage, - fixed_buf, imgoff, postlen); - return 1; - } - - if (ws_error_action != correct_ws_error) - return 0; - - /* - * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. We haven't been asked to - * ignore whitespace, we were asked to correct whitespace - * errors, so let's try matching after whitespace correction. - */ - fixed_buf = xmalloc(preimage->len + 1); - buf = fixed_buf; - orig = preimage->buf; - target = img->buf + try; - for (i = 0; i < preimage->nr; i++) { - size_t fixlen; /* length after fixing the preimage */ - size_t oldlen = preimage->line[i].len; - size_t tgtlen = img->line[try_lno + i].len; - size_t tgtfixlen; /* length after fixing the target line */ - char tgtfixbuf[1024], *tgtfix; - int match; - - /* Try fixing the line in the preimage */ - fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL); - - /* Try fixing the line in the target */ - if (sizeof(tgtfixbuf) > tgtlen) - tgtfix = tgtfixbuf; - else - tgtfix = xmalloc(tgtlen); - tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL); - - /* - * If they match, either the preimage was based on - * a version before our tree fixed whitespace breakage, - * or we are lacking a whitespace-fix patch the tree - * the preimage was based on already had (i.e. target - * has whitespace breakage, the preimage doesn't). - * In either case, we are fixing the whitespace breakages - * so we might as well take the fix together with their - * real change. - */ - match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen)); - - if (tgtfix != tgtfixbuf) - free(tgtfix); - if (!match) - goto unmatch_exit; - - orig += oldlen; - buf += fixlen; - target += tgtlen; - } - - /* - * Yes, the preimage is based on an older version that still - * has whitespace breakages unfixed, and fixing them makes the - * hunk match. Update the context lines in the postimage. - */ - update_pre_post_images(preimage, postimage, - fixed_buf, buf - fixed_buf, 0); - return 1; - - unmatch_exit: - free(fixed_buf); - return 0; -} - -static int find_pos(struct image *img, - struct image *preimage, - struct image *postimage, - int line, - unsigned ws_rule, - int match_beginning, int match_end) -{ - int i; - unsigned long backwards, forwards, try; - int backwards_lno, forwards_lno, try_lno; - - if (preimage->nr > img->nr) - return -1; - - /* - * 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. - */ - if (match_beginning) - line = 0; - else if (match_end) - line = img->nr - preimage->nr; - - if (line > img->nr) - line = img->nr; - - try = 0; - for (i = 0; i < line; i++) - try += img->line[i].len; - - /* - * There's probably some smart way to do this, but I'll leave - * that to the smart and beautiful people. I'm simple and stupid. - */ - backwards = try; - backwards_lno = line; - forwards = try; - forwards_lno = line; - try_lno = line; - - for (i = 0; ; i++) { - if (match_fragment(img, preimage, postimage, - try, try_lno, ws_rule, - match_beginning, match_end)) - return try_lno; - - again: - if (backwards_lno == 0 && forwards_lno == img->nr) - break; - - if (i & 1) { - if (backwards_lno == 0) { - i++; - goto again; - } - backwards_lno--; - backwards -= img->line[backwards_lno].len; - try = backwards; - try_lno = backwards_lno; - } else { - if (forwards_lno == img->nr) { - i++; - goto again; - } - forwards += img->line[forwards_lno].len; - forwards_lno++; - try = forwards; - try_lno = forwards_lno; - } - - } - return -1; -} - -static void remove_first_line(struct image *img) -{ - img->buf += img->line[0].len; - img->len -= img->line[0].len; - img->line++; - img->nr--; -} - -static void remove_last_line(struct image *img) -{ - img->len -= img->line[--img->nr].len; -} - -static void update_image(struct image *img, - int applied_pos, - struct image *preimage, - struct image *postimage) -{ - /* - * remove the copy of preimage at offset in img - * and replace it with postimage - */ - int i, nr; - size_t remove_count, insert_count, applied_at = 0; - char *result; - - for (i = 0; i < applied_pos; i++) - applied_at += img->line[i].len; - - remove_count = 0; - for (i = 0; i < preimage->nr; i++) - remove_count += img->line[applied_pos + i].len; - insert_count = postimage->len; - - /* Adjust the contents */ - result = xmalloc(img->len + insert_count - remove_count + 1); - memcpy(result, img->buf, applied_at); - memcpy(result + applied_at, postimage->buf, postimage->len); - memcpy(result + applied_at + postimage->len, - img->buf + (applied_at + remove_count), - img->len - (applied_at + remove_count)); - free(img->buf); - img->buf = result; - img->len += insert_count - remove_count; - result[img->len] = '\0'; - - /* Adjust the line table */ - nr = img->nr + postimage->nr - preimage->nr; - if (preimage->nr < postimage->nr) { - /* - * NOTE: this knows that we never call remove_first_line() - * on anything other than pre/post image. - */ - img->line = xrealloc(img->line, nr * sizeof(*img->line)); - img->line_allocated = img->line; - } - if (preimage->nr != postimage->nr) - memmove(img->line + applied_pos + postimage->nr, - img->line + applied_pos + preimage->nr, - (img->nr - (applied_pos + preimage->nr)) * - sizeof(*img->line)); - memcpy(img->line + applied_pos, - postimage->line, - postimage->nr * sizeof(*img->line)); - img->nr = nr; -} - -static int apply_one_fragment(struct image *img, struct fragment *frag, - int inaccurate_eof, unsigned ws_rule) -{ - int match_beginning, match_end; - const char *patch = frag->patch; - int size = frag->size; - char *old, *new, *oldlines, *newlines; - int new_blank_lines_at_end = 0; - unsigned long leading, trailing; - int pos, applied_pos; - struct image preimage; - struct image postimage; - - memset(&preimage, 0, sizeof(preimage)); - memset(&postimage, 0, sizeof(postimage)); - oldlines = xmalloc(size); - newlines = xmalloc(size); - - old = oldlines; - new = newlines; - while (size > 0) { - char first; - int len = linelen(patch, size); - int plen, added; - int added_blank_line = 0; - int is_blank_context = 0; - - if (!len) - break; - - /* - * "plen" is how much of the line we should use for - * the actual patch data. Normally we just remove the - * first character on the line, but if the line is - * followed by "\ No newline", then we also remove the - * last one (which is the newline, of course). - */ - plen = len - 1; - if (len < size && patch[len] == '\\') - plen--; - first = *patch; - if (apply_in_reverse) { - if (first == '-') - first = '+'; - else if (first == '+') - first = '-'; - } - - switch (first) { - case '\n': - /* Newer GNU diff, empty context line */ - if (plen < 0) - /* ... followed by '\No newline'; nothing */ - break; - *old++ = '\n'; - *new++ = '\n'; - add_line_info(&preimage, "\n", 1, LINE_COMMON); - add_line_info(&postimage, "\n", 1, LINE_COMMON); - is_blank_context = 1; - break; - case ' ': - if (plen && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - is_blank_context = 1; - case '-': - memcpy(old, patch + 1, plen); - add_line_info(&preimage, old, plen, - (first == ' ' ? LINE_COMMON : 0)); - old += plen; - if (first == '-') - break; - /* Fall-through for ' ' */ - case '+': - /* --no-add does not add new lines */ - if (first == '+' && no_add) - break; - - if (first != '+' || - !whitespace_error || - ws_error_action != correct_ws_error) { - memcpy(new, patch + 1, plen); - added = plen; - } - else { - added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws); - } - add_line_info(&postimage, new, added, - (first == '+' ? 0 : LINE_COMMON)); - new += added; - if (first == '+' && - (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) - added_blank_line = 1; - break; - case '@': case '\\': - /* Ignore it, we already handled it */ - break; - default: - if (apply_verbosely) - error("invalid start of line: '%c'", first); - return -1; - } - if (added_blank_line) - new_blank_lines_at_end++; - else if (is_blank_context) - ; - else - new_blank_lines_at_end = 0; - patch += len; - size -= len; - } - if (inaccurate_eof && - old > oldlines && old[-1] == '\n' && - new > newlines && new[-1] == '\n') { - old--; - new--; - } - - leading = frag->leading; - trailing = frag->trailing; - - /* - * A hunk to change lines at the beginning would begin with - * @@ -1,L +N,M @@ - * but we need to be careful. -U0 that inserts before the second - * line also has this pattern. - * - * And a hunk to add to an empty file would begin with - * @@ -0,0 +N,M @@ - * - * In other words, a hunk that is (frag->oldpos <= 1) with or - * without leading context must match at the beginning. - */ - match_beginning = (!frag->oldpos || - (frag->oldpos == 1 && !unidiff_zero)); - - /* - * A hunk without trailing lines must match at the end. - * However, we simply cannot tell if a hunk must match end - * from the lack of trailing lines if the patch was generated - * with unidiff without any context. - */ - match_end = !unidiff_zero && !trailing; - - pos = frag->newpos ? (frag->newpos - 1) : 0; - preimage.buf = oldlines; - preimage.len = old - oldlines; - postimage.buf = newlines; - postimage.len = new - newlines; - preimage.line = preimage.line_allocated; - postimage.line = postimage.line_allocated; - - for (;;) { - - applied_pos = find_pos(img, &preimage, &postimage, pos, - ws_rule, match_beginning, match_end); - - if (applied_pos >= 0) - break; - - /* Am I at my context limits? */ - if ((leading <= p_context) && (trailing <= p_context)) - break; - if (match_beginning || match_end) { - match_beginning = match_end = 0; - continue; - } - - /* - * Reduce the number of context lines; reduce both - * leading and trailing if they are equal otherwise - * just reduce the larger context. - */ - if (leading >= trailing) { - remove_first_line(&preimage); - remove_first_line(&postimage); - pos--; - leading--; - } - if (trailing > leading) { - remove_last_line(&preimage); - remove_last_line(&postimage); - trailing--; - } - } - - if (applied_pos >= 0) { - if (new_blank_lines_at_end && - preimage.nr + applied_pos == img->nr && - (ws_rule & WS_BLANK_AT_EOF) && - ws_error_action != nowarn_ws_error) { - record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); - if (ws_error_action == correct_ws_error) { - while (new_blank_lines_at_end--) - remove_last_line(&postimage); - } - /* - * We would want to prevent write_out_results() - * from taking place in apply_patch() that follows - * the callchain led us here, which is: - * apply_patch->check_patch_list->check_patch-> - * apply_data->apply_fragments->apply_one_fragment - */ - if (ws_error_action == die_on_ws_error) - apply = 0; - } - - /* - * Warn if it was necessary to reduce the number - * of context lines. - */ - if ((leading != frag->leading) || - (trailing != frag->trailing)) - fprintf(stderr, "Context reduced to (%ld/%ld)" - " to apply fragment at %d\n", - leading, trailing, applied_pos+1); - update_image(img, applied_pos, &preimage, &postimage); - } else { - if (apply_verbosely) - error("while searching for:\n%.*s", - (int)(old - oldlines), oldlines); - } - - free(oldlines); - free(newlines); - free(preimage.line_allocated); - free(postimage.line_allocated); - - return (applied_pos < 0); -} - -static int apply_binary_fragment(struct image *img, struct patch *patch) -{ - struct fragment *fragment = patch->fragments; - unsigned long len; - void *dst; - - /* Binary patch is irreversible without the optional second hunk */ - if (apply_in_reverse) { - if (!fragment->next) - return error("cannot reverse-apply a binary patch " - "without the reverse hunk to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - fragment = fragment->next; - } - switch (fragment->binary_patch_method) { - case BINARY_DELTA_DEFLATED: - dst = patch_delta(img->buf, img->len, fragment->patch, - fragment->size, &len); - if (!dst) - return -1; - clear_image(img); - img->buf = dst; - img->len = len; - return 0; - case BINARY_LITERAL_DEFLATED: - clear_image(img); - img->len = fragment->size; - img->buf = xmalloc(img->len+1); - memcpy(img->buf, fragment->patch, img->len); - img->buf[img->len] = '\0'; - return 0; - } - return -1; -} - -static int apply_binary(struct image *img, struct patch *patch) -{ - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned char sha1[20]; - - /* - * For safety, we require patch index line to contain - * full 40-byte textual SHA1 for old and new, at least for now. - */ - if (strlen(patch->old_sha1_prefix) != 40 || - strlen(patch->new_sha1_prefix) != 40 || - get_sha1_hex(patch->old_sha1_prefix, sha1) || - get_sha1_hex(patch->new_sha1_prefix, sha1)) - return error("cannot apply binary patch to '%s' " - "without full index line", name); - - if (patch->old_name) { - /* - * See if the old one matches what the patch - * applies to. - */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) - return error("the patch applies to '%s' (%s), " - "which does not match the " - "current contents.", - name, sha1_to_hex(sha1)); - } - else { - /* Otherwise, the old one must be empty. */ - if (img->len) - return error("the patch applies to an empty " - "'%s' but it is not empty", name); - } - - get_sha1_hex(patch->new_sha1_prefix, sha1); - if (is_null_sha1(sha1)) { - clear_image(img); - return 0; /* deletion patch */ - } - - if (has_sha1_file(sha1)) { - /* We already have the postimage */ - enum object_type type; - unsigned long size; - char *result; - - result = read_sha1_file(sha1, &type, &size); - if (!result) - return error("the necessary postimage %s for " - "'%s' cannot be read", - patch->new_sha1_prefix, name); - clear_image(img); - img->buf = result; - img->len = size; - } else { - /* - * We have verified buf matches the preimage; - * apply the patch data to it, which is stored - * in the patch->fragments->{patch,size}. - */ - if (apply_binary_fragment(img, patch)) - return error("binary patch does not apply to '%s'", - name); - - /* verify that the result matches */ - hash_sha1_file(img->buf, img->len, blob_type, sha1); - if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix)) - return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", - name, patch->new_sha1_prefix, sha1_to_hex(sha1)); - } - - return 0; -} - -static int apply_fragments(struct image *img, struct patch *patch) -{ - struct fragment *frag = patch->fragments; - const char *name = patch->old_name ? patch->old_name : patch->new_name; - unsigned ws_rule = patch->ws_rule; - unsigned inaccurate_eof = patch->inaccurate_eof; - - if (patch->is_binary) - return apply_binary(img, patch); - - while (frag) { - if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) { - error("patch failed: %s:%ld", name, frag->oldpos); - if (!apply_with_reject) - return -1; - frag->rejected = 1; - } - frag = frag->next; - } - return 0; -} - -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf) -{ - if (!ce) - return 0; - - if (S_ISGITLINK(ce->ce_mode)) { - strbuf_grow(buf, 100); - strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1)); - } else { - enum object_type type; - unsigned long sz; - char *result; - - result = read_sha1_file(ce->sha1, &type, &sz); - if (!result) - return -1; - /* XXX read_sha1_file NUL-terminates */ - strbuf_attach(buf, result, sz, sz + 1); - } - return 0; -} - -static struct patch *in_fn_table(const char *name) -{ - struct string_list_item *item; - - if (name == NULL) - return NULL; - - item = string_list_lookup(name, &fn_table); - if (item != NULL) - return (struct patch *)item->util; - - return NULL; -} - -/* - * item->util in the filename table records the status of the path. - * Usually it points at a patch (whose result records the contents - * of it after applying it), but it could be PATH_WAS_DELETED for a - * path that a previously applied patch has already removed. - */ - #define PATH_TO_BE_DELETED ((struct patch *) -2) -#define PATH_WAS_DELETED ((struct patch *) -1) - -static int to_be_deleted(struct patch *patch) -{ - return patch == PATH_TO_BE_DELETED; -} - -static int was_deleted(struct patch *patch) -{ - return patch == PATH_WAS_DELETED; -} - -static void add_to_fn_table(struct patch *patch) -{ - struct string_list_item *item; - - /* - * Always add new_name unless patch is a deletion - * This should cover the cases for normal diffs, - * file creations and copies - */ - if (patch->new_name != NULL) { - item = string_list_insert(patch->new_name, &fn_table); - item->util = patch; - } - - /* - * store a failure on rename/deletion cases because - * later chunks shouldn't patch old names - */ - if ((patch->new_name == NULL) || (patch->is_rename)) { - item = string_list_insert(patch->old_name, &fn_table); - item->util = PATH_WAS_DELETED; - } -} - -static void prepare_fn_table(struct patch *patch) -{ - /* - * store information about incoming file deletion - */ - while (patch) { - if ((patch->new_name == NULL) || (patch->is_rename)) { - struct string_list_item *item; - item = string_list_insert(patch->old_name, &fn_table); - item->util = PATH_TO_BE_DELETED; - } - patch = patch->next; - } -} - -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce) -{ - struct strbuf buf = STRBUF_INIT; - struct image image; - size_t len; - char *img; - struct patch *tpatch; - - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) { - return error("patch %s has been renamed/deleted", - patch->old_name); - } - /* We have a patched copy in memory use that */ - strbuf_add(&buf, tpatch->result, tpatch->resultsize); - } else if (cached) { - if (read_file_or_gitlink(ce, &buf)) - return error("read of %s failed", patch->old_name); - } else if (patch->old_name) { - if (S_ISGITLINK(patch->old_mode)) { - if (ce) { - read_file_or_gitlink(ce, &buf); - } else { - /* - * There is no way to apply subproject - * patch without looking at the index. - */ - patch->fragments = NULL; - } - } else { - if (read_old_data(st, patch->old_name, &buf)) - return error("read of %s failed", patch->old_name); - } - } - - img = strbuf_detach(&buf, &len); - prepare_image(&image, img, len, !patch->is_binary); - - if (apply_fragments(&image, patch) < 0) - return -1; /* note with --reject this succeeds. */ - patch->result = image.buf; - patch->resultsize = image.len; - add_to_fn_table(patch); - free(image.line_allocated); - - if (0 < patch->is_delete && patch->resultsize) - return error("removal patch leaves file contents"); - - return 0; -} - -static int check_to_create_blob(const char *new_name, int ok_if_exists) -{ - struct stat nst; - if (!lstat(new_name, &nst)) { - if (S_ISDIR(nst.st_mode) || ok_if_exists) - return 0; - /* - * A leading component of new_name might be a symlink - * that is going to be removed with this patch, but - * still pointing at somewhere that has the path. - * In such a case, path "new_name" does not exist as - * far as git is concerned. - */ - if (has_symlink_leading_path(new_name, strlen(new_name))) - return 0; - - return error("%s: already exists in working directory", new_name); - } - else if ((errno != ENOENT) && (errno != ENOTDIR)) - return error("%s: %s", new_name, strerror(errno)); - return 0; -} - -static int verify_index_match(struct cache_entry *ce, struct stat *st) -{ - if (S_ISGITLINK(ce->ce_mode)) { - if (!S_ISDIR(st->st_mode)) - return -1; - return 0; - } - return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE); -} - -static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st) -{ - const char *old_name = patch->old_name; - struct patch *tpatch = NULL; - int stat_ret = 0; - unsigned st_mode = 0; - - /* - * Make sure that we do not have local modifications from the - * index when we are looking at the index. Also make sure - * we have the preimage file to be patched in the work tree, - * unless --cached, which tells git to apply only in the index. - */ - if (!old_name) - return 0; - - assert(patch->is_new <= 0); - - if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) { - if (was_deleted(tpatch)) - return error("%s: has been deleted/renamed", old_name); - st_mode = tpatch->new_mode; - } else if (!cached) { - stat_ret = lstat(old_name, st); - if (stat_ret && errno != ENOENT) - return error("%s: %s", old_name, strerror(errno)); - } - - if (to_be_deleted(tpatch)) - tpatch = NULL; - - if (check_index && !tpatch) { - int pos = cache_name_pos(old_name, strlen(old_name)); - if (pos < 0) { - if (patch->is_new < 0) - goto is_new; - return error("%s: does not exist in index", old_name); - } - *ce = active_cache[pos]; - if (stat_ret < 0) { - struct checkout costate; - /* checkout */ - costate.base_dir = ""; - costate.base_dir_len = 0; - costate.force = 0; - costate.quiet = 0; - costate.not_new = 0; - costate.refresh_cache = 1; - if (checkout_entry(*ce, &costate, NULL) || - lstat(old_name, st)) - return -1; - } - if (!cached && verify_index_match(*ce, st)) - return error("%s: does not match index", old_name); - if (cached) - st_mode = (*ce)->ce_mode; - } else if (stat_ret < 0) { - if (patch->is_new < 0) - goto is_new; - return error("%s: %s", old_name, strerror(errno)); - } - - if (!cached && !tpatch) - st_mode = ce_mode_from_stat(*ce, st->st_mode); - - if (patch->is_new < 0) - patch->is_new = 0; - if (!patch->old_mode) - patch->old_mode = st_mode; - if ((st_mode ^ patch->old_mode) & S_IFMT) - return error("%s: wrong type", old_name); - if (st_mode != patch->old_mode) - warning("%s has type %o, expected %o", - old_name, st_mode, patch->old_mode); - if (!patch->new_mode && !patch->is_delete) - patch->new_mode = st_mode; - return 0; - - is_new: - patch->is_new = 1; - patch->is_delete = 0; - patch->old_name = NULL; - return 0; -} - -static int check_patch(struct patch *patch) -{ - struct stat st; - const char *old_name = patch->old_name; - const char *new_name = patch->new_name; - const char *name = old_name ? old_name : new_name; - struct cache_entry *ce = NULL; - struct patch *tpatch; - int ok_if_exists; - int status; - - patch->rejected = 1; /* we will drop this after we succeed */ - - status = check_preimage(patch, &ce, &st); - if (status) - return status; - old_name = patch->old_name; - - if ((tpatch = in_fn_table(new_name)) && - (was_deleted(tpatch) || to_be_deleted(tpatch))) - /* - * A type-change diff is always split into a patch to - * delete old, immediately followed by a patch to - * create new (see diff.c::run_diff()); in such a case - * it is Ok that the entry to be deleted by the - * previous patch is still in the working tree and in - * the index. - */ - ok_if_exists = 1; - else - ok_if_exists = 0; - - if (new_name && - ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) { - if (check_index && - cache_name_pos(new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return error("%s: already exists in index", new_name); - if (!cached) { - int err = check_to_create_blob(new_name, ok_if_exists); - if (err) - return err; - } - if (!patch->new_mode) { - if (0 < patch->is_new) - patch->new_mode = S_IFREG | 0644; - else - patch->new_mode = patch->old_mode; - } - } - - if (new_name && old_name) { - int same = !strcmp(old_name, new_name); - if (!patch->new_mode) - patch->new_mode = patch->old_mode; - if ((patch->old_mode ^ patch->new_mode) & S_IFMT) - return error("new mode (%o) of %s does not match old mode (%o)%s%s", - patch->new_mode, new_name, patch->old_mode, - same ? "" : " of ", same ? "" : old_name); - } - - if (apply_data(patch, &st, ce) < 0) - return error("%s: patch does not apply", name); - patch->rejected = 0; - return 0; -} - -static int check_patch_list(struct patch *patch) -{ - int err = 0; - - prepare_fn_table(patch); - while (patch) { - if (apply_verbosely) - say_patch_name(stderr, - "Checking patch ", patch, "...\n"); - err |= check_patch(patch); - patch = patch->next; - } - return err; -} - -/* This function tries to read the sha1 from the current index */ -static int get_current_sha1(const char *path, unsigned char *sha1) -{ - int pos; - - if (read_cache() < 0) - return -1; - pos = cache_name_pos(path, strlen(path)); - if (pos < 0) - return -1; - hashcpy(sha1, active_cache[pos]->sha1); - return 0; -} - -/* Build an index that contains the just the files needed for a 3way merge */ -static void build_fake_ancestor(struct patch *list, const char *filename) -{ - struct patch *patch; - struct index_state result = { NULL }; - int fd; - - /* Once we start supporting the reverse patch, it may be - * worth showing the new sha1 prefix, but until then... - */ - for (patch = list; patch; patch = patch->next) { - const unsigned char *sha1_ptr; - unsigned char sha1[20]; - struct cache_entry *ce; - const char *name; - - name = patch->old_name ? patch->old_name : patch->new_name; - if (0 < patch->is_new) - continue; - else if (get_sha1(patch->old_sha1_prefix, sha1)) - /* git diff has no index line for mode/type changes */ - if (!patch->lines_added && !patch->lines_deleted) { - if (get_current_sha1(patch->new_name, sha1) || - get_current_sha1(patch->old_name, sha1)) - die("mode change for %s, which is not " - "in current HEAD", name); - sha1_ptr = sha1; - } else - die("sha1 information is lacking or useless " - "(%s).", name); - else - sha1_ptr = sha1; - - ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0); - if (!ce) - die("make_cache_entry failed for path '%s'", name); - if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD)) - die ("Could not add %s to temporary index", name); - } - - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0 || write_index(&result, fd) || close(fd)) - die ("Could not write temporary index to %s", filename); - - discard_index(&result); -} - -static void stat_patch_list(struct patch *patch) -{ - int files, adds, dels; - - for (files = adds = dels = 0 ; patch ; patch = patch->next) { - files++; - adds += patch->lines_added; - dels += patch->lines_deleted; - show_stats(patch); - } - - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); -} - -static void numstat_patch_list(struct patch *patch) -{ - for ( ; patch; patch = patch->next) { - const char *name; - name = patch->new_name ? patch->new_name : patch->old_name; - if (patch->is_binary) - printf("-\t-\t"); - else - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); - write_name_quoted(name, stdout, line_termination); - } -} - -static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name) -{ - if (mode) - printf(" %s mode %06o %s\n", newdelete, mode, name); - else - printf(" %s %s\n", newdelete, name); -} - -static void show_mode_change(struct patch *p, int show_name) -{ - if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) { - if (show_name) - printf(" mode change %06o => %06o %s\n", - p->old_mode, p->new_mode, p->new_name); - else - printf(" mode change %06o => %06o\n", - p->old_mode, p->new_mode); - } -} - -static void show_rename_copy(struct patch *p) -{ - const char *renamecopy = p->is_rename ? "rename" : "copy"; - const char *old, *new; - - /* Find common prefix */ - old = p->old_name; - new = p->new_name; - while (1) { - const char *slash_old, *slash_new; - slash_old = strchr(old, '/'); - slash_new = strchr(new, '/'); - if (!slash_old || - !slash_new || - slash_old - old != slash_new - new || - memcmp(old, new, slash_new - new)) - break; - old = slash_old + 1; - new = slash_new + 1; - } - /* p->old_name thru old is the common prefix, and old and new - * through the end of names are renames - */ - if (old != p->old_name) - printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy, - (int)(old - p->old_name), p->old_name, - old, new, p->score); - else - printf(" %s %s => %s (%d%%)\n", renamecopy, - p->old_name, p->new_name, p->score); - show_mode_change(p, 0); -} - -static void summary_patch_list(struct patch *patch) -{ - struct patch *p; - - for (p = patch; p; p = p->next) { - if (p->is_new) - show_file_mode_name("create", p->new_mode, p->new_name); - else if (p->is_delete) - show_file_mode_name("delete", p->old_mode, p->old_name); - else { - if (p->is_rename || p->is_copy) - show_rename_copy(p); - else { - if (p->score) { - printf(" rewrite %s (%d%%)\n", - p->new_name, p->score); - show_mode_change(p, 0); - } - else - show_mode_change(p, 1); - } - } - } -} - -static void patch_stats(struct patch *patch) -{ - int lines = patch->lines_added + patch->lines_deleted; - - if (lines > max_change) - max_change = lines; - if (patch->old_name) { - int len = quote_c_style(patch->old_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->old_name); - if (len > max_len) - max_len = len; - } - if (patch->new_name) { - int len = quote_c_style(patch->new_name, NULL, NULL, 0); - if (!len) - len = strlen(patch->new_name); - if (len > max_len) - max_len = len; - } -} - -static void remove_file(struct patch *patch, int rmdir_empty) -{ - if (update_index) { - if (remove_file_from_cache(patch->old_name) < 0) - die("unable to remove %s from index", patch->old_name); - } - if (!cached) { - if (S_ISGITLINK(patch->old_mode)) { - if (rmdir(patch->old_name)) - warning("unable to remove submodule %s", - patch->old_name); - } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { - remove_path(patch->old_name); - } - } -} - -static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size) -{ - struct stat st; - struct cache_entry *ce; - int namelen = strlen(path); - unsigned ce_size = cache_entry_size(namelen); - - if (!update_index) - return; - - ce = xcalloc(1, ce_size); - memcpy(ce->name, path, namelen); - ce->ce_mode = create_ce_mode(mode); - ce->ce_flags = namelen; - if (S_ISGITLINK(mode)) { - const char *s = buf; - - if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1)) - die("corrupt patch for subproject %s", path); - } else { - if (!cached) { - if (lstat(path, &st) < 0) - die_errno("unable to stat newly created file '%s'", - path); - fill_stat_cache_info(ce, &st); - } - if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0) - die("unable to create backing store for newly created file %s", path); - } - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) - die("unable to add cache entry for %s", path); -} - -static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) -{ - int fd; - struct strbuf nbuf = STRBUF_INIT; - - if (S_ISGITLINK(mode)) { - struct stat st; - if (!lstat(path, &st) && S_ISDIR(st.st_mode)) - return 0; - return mkdir(path, 0777); - } - - if (has_symlinks && S_ISLNK(mode)) - /* Although buf:size is counted string, it also is NUL - * terminated. - */ - return symlink(buf, path); - - fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); - if (fd < 0) - return -1; - - if (convert_to_working_tree(path, buf, size, &nbuf)) { - size = nbuf.len; - buf = nbuf.buf; - } - write_or_die(fd, buf, size); - strbuf_release(&nbuf); - - if (close(fd) < 0) - die_errno("closing file '%s'", path); - return 0; -} - -/* - * We optimistically assume that the directories exist, - * which is true 99% of the time anyway. If they don't, - * we create them and try again. - */ -static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size) -{ - if (cached) - return; - if (!try_create_file(path, mode, buf, size)) - return; - - if (errno == ENOENT) { - if (safe_create_leading_directories(path)) - return; - if (!try_create_file(path, mode, buf, size)) - return; - } - - if (errno == EEXIST || errno == EACCES) { - /* We may be trying to create a file where a directory - * used to be. - */ - struct stat st; - if (!lstat(path, &st) && (!S_ISDIR(st.st_mode) || !rmdir(path))) - errno = EEXIST; - } - - if (errno == EEXIST) { - unsigned int nr = getpid(); - - for (;;) { - char newpath[PATH_MAX]; - mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); - if (!try_create_file(newpath, mode, buf, size)) { - if (!rename(newpath, path)) - return; - unlink_or_warn(newpath); - break; - } - if (errno != EEXIST) - break; - ++nr; - } - } - die_errno("unable to write file '%s' mode %o", path, mode); -} - -static void create_file(struct patch *patch) -{ - char *path = patch->new_name; - unsigned mode = patch->new_mode; - unsigned long size = patch->resultsize; - char *buf = patch->result; - - if (!mode) - mode = S_IFREG | 0644; - create_one_file(path, mode, buf, size); - add_index_file(path, mode, buf, size); -} - -/* phase zero is to remove, phase one is to create */ -static void write_out_one_result(struct patch *patch, int phase) -{ - if (patch->is_delete > 0) { - if (phase == 0) - remove_file(patch, 1); - return; - } - if (patch->is_new > 0 || patch->is_copy) { - if (phase == 1) - create_file(patch); - return; - } - /* - * Rename or modification boils down to the same - * thing: remove the old, write the new - */ - if (phase == 0) - remove_file(patch, patch->is_rename); - if (phase == 1) - create_file(patch); -} - -static int write_out_one_reject(struct patch *patch) -{ - FILE *rej; - char namebuf[PATH_MAX]; - struct fragment *frag; - int cnt = 0; - - for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { - if (!frag->rejected) - continue; - cnt++; - } - - if (!cnt) { - if (apply_verbosely) - say_patch_name(stderr, - "Applied patch ", patch, " cleanly.\n"); - return 0; - } - - /* This should not happen, because a removal patch that leaves - * contents are marked "rejected" at the patch level. - */ - if (!patch->new_name) - die("internal error"); - - /* Say this even without --verbose */ - say_patch_name(stderr, "Applying patch ", patch, " with"); - fprintf(stderr, " %d rejects...\n", cnt); - - cnt = strlen(patch->new_name); - if (ARRAY_SIZE(namebuf) <= cnt + 5) { - cnt = ARRAY_SIZE(namebuf) - 5; - warning("truncating .rej filename to %.*s.rej", - cnt - 1, patch->new_name); - } - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); - - rej = fopen(namebuf, "w"); - if (!rej) - return error("cannot open %s: %s", namebuf, strerror(errno)); - - /* Normal git tools never deal with .rej, so do not pretend - * this is a git patch by saying --git nor give extended - * headers. While at it, maybe please "kompare" that wants - * the trailing TAB and some garbage at the end of line ;-). - */ - fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n", - patch->new_name, patch->new_name); - for (cnt = 1, frag = patch->fragments; - frag; - cnt++, frag = frag->next) { - if (!frag->rejected) { - fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt); - continue; - } - fprintf(stderr, "Rejected hunk #%d.\n", cnt); - fprintf(rej, "%.*s", frag->size, frag->patch); - if (frag->patch[frag->size-1] != '\n') - fputc('\n', rej); - } - fclose(rej); - return -1; -} - -static int write_out_results(struct patch *list, int skipped_patch) -{ - int phase; - int errs = 0; - struct patch *l; - - if (!list && !skipped_patch) - return error("No changes"); - - for (phase = 0; phase < 2; phase++) { - l = list; - while (l) { - if (l->rejected) - errs = 1; - else { - write_out_one_result(l, phase); - if (phase == 1 && write_out_one_reject(l)) - errs = 1; - } - l = l->next; - } - } - return errs; -} - -static struct lock_file lock_file; - -static struct string_list limit_by_name; -static int has_include; -static void add_name_limit(const char *name, int exclude) -{ - struct string_list_item *it; - - it = string_list_append(name, &limit_by_name); - it->util = exclude ? NULL : (void *) 1; -} - -static int use_patch(struct patch *p) -{ - const char *pathname = p->new_name ? p->new_name : p->old_name; - int i; - - /* Paths outside are not touched regardless of "--include" */ - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } - - /* See if it matches any of exclude/include rule */ - for (i = 0; i < limit_by_name.nr; i++) { - struct string_list_item *it = &limit_by_name.items[i]; - if (!fnmatch(it->string, pathname, 0)) - return (it->util != NULL); - } - - /* - * If we had any include, a path that does not match any rule is - * not used. Otherwise, we saw bunch of exclude rules (or none) - * and such a path is used. - */ - return !has_include; -} - - -static void prefix_one(char **name) -{ - char *old_name = *name; - if (!old_name) - return; - *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); - free(old_name); -} - -static void prefix_patches(struct patch *p) -{ - if (!prefix || p->is_toplevel_relative) - return; - for ( ; p; p = p->next) { - if (p->new_name == p->old_name) { - char *prefixed = p->new_name; - prefix_one(&prefixed); - p->new_name = p->old_name = prefixed; - } - else { - prefix_one(&p->new_name); - prefix_one(&p->old_name); - } - } -} - -#define INACCURATE_EOF (1<<0) -#define RECOUNT (1<<1) - -static int apply_patch(int fd, const char *filename, int options) -{ - size_t offset; - struct strbuf buf = STRBUF_INIT; - struct patch *list = NULL, **listp = &list; - int skipped_patch = 0; - - /* FIXME - memory leak when using multiple patch files as inputs */ - memset(&fn_table, 0, sizeof(struct string_list)); - patch_input_file = filename; - read_patch_file(&buf, fd); - offset = 0; - while (offset < buf.len) { - struct patch *patch; - int nr; - - patch = xcalloc(1, sizeof(*patch)); - patch->inaccurate_eof = !!(options & INACCURATE_EOF); - patch->recount = !!(options & RECOUNT); - nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); - if (nr < 0) - break; - if (apply_in_reverse) - reverse_patches(patch); - if (prefix) - prefix_patches(patch); - if (use_patch(patch)) { - patch_stats(patch); - *listp = patch; - listp = &patch->next; - } - else { - /* perhaps free it a bit better? */ - free(patch); - skipped_patch++; - } - offset += nr; - } - - if (whitespace_error && (ws_error_action == die_on_ws_error)) - apply = 0; - - update_index = check_index && apply; - if (update_index && newfd < 0) - newfd = hold_locked_index(&lock_file, 1); - - if (check_index) { - if (read_cache() < 0) - die("unable to read index file"); - } - - if ((check || apply) && - check_patch_list(list) < 0 && - !apply_with_reject) - exit(1); - - if (apply && write_out_results(list, skipped_patch)) - exit(1); - - if (fake_ancestor) - build_fake_ancestor(list, fake_ancestor); - - if (diffstat) - stat_patch_list(list); - - if (numstat) - numstat_patch_list(list); - - if (summary) - summary_patch_list(list); - - strbuf_release(&buf); - return 0; -} - -static int git_apply_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "apply.whitespace")) - return git_config_string(&apply_default_whitespace, var, value); - else if (!strcmp(var, "apply.ignorewhitespace")) - return git_config_string(&apply_default_ignorewhitespace, var, value); - return git_default_config(var, value, cb); -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - add_name_limit(arg, 1); - return 0; -} - -static int option_parse_include(const struct option *opt, - const char *arg, int unset) -{ - add_name_limit(arg, 0); - has_include = 1; - return 0; -} - -static int option_parse_p(const struct option *opt, - const char *arg, int unset) -{ - p_value = atoi(arg); - p_value_known = 1; - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_space_change(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - ws_ignore_action = ignore_ws_none; - else - ws_ignore_action = ignore_ws_change; - return 0; -} - -static int option_parse_whitespace(const struct option *opt, - const char *arg, int unset) -{ - const char **whitespace_option = opt->value; - - *whitespace_option = arg; - parse_whitespace_option(arg); - return 0; -} - -static int option_parse_directory(const struct option *opt, - const char *arg, int unset) -{ - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; - return 0; -} - -int cmd_apply(int argc, const char **argv, const char *unused_prefix) -{ - int i; - int errs = 0; - int is_not_gitdir; - int binary; - int force_apply = 0; - - const char *whitespace_option = NULL; - - struct option builtin_apply_options[] = { - { OPTION_CALLBACK, 0, "exclude", NULL, "path", - "don't apply changes matching the given path", - 0, option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", NULL, "path", - "apply changes matching the given path", - 0, option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, NULL, "num", - "remove leading slashes from traditional diff paths", - 0, option_parse_p }, - OPT_BOOLEAN(0, "no-add", &no_add, - "ignore additions made by the patch"), - OPT_BOOLEAN(0, "stat", &diffstat, - "instead of applying the patch, output diffstat for the input"), - { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary, - NULL, "old option, now no-op", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, - { OPTION_BOOLEAN, 0, "binary", &binary, - NULL, "old option, now no-op", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, - OPT_BOOLEAN(0, "numstat", &numstat, - "shows number of added and deleted lines in decimal notation"), - OPT_BOOLEAN(0, "summary", &summary, - "instead of applying the patch, output a summary for the input"), - OPT_BOOLEAN(0, "check", &check, - "instead of applying the patch, see if the patch is applicable"), - OPT_BOOLEAN(0, "index", &check_index, - "make sure the patch is applicable to the current index"), - OPT_BOOLEAN(0, "cached", &cached, - "apply a patch without touching the working tree"), - OPT_BOOLEAN(0, "apply", &force_apply, - "also apply the patch (use with --stat/--summary/--check)"), - OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor, - "build a temporary index based on embedded index information"), - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_INTEGER('C', NULL, &p_context, - "ensure at least lines of context match"), - { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action", - "detect new or modified lines that have whitespace errors", - 0, option_parse_whitespace }, - { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL, - "ignore changes in whitespace when finding context", - PARSE_OPT_NOARG, option_parse_space_change }, - { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL, - "ignore changes in whitespace when finding context", - PARSE_OPT_NOARG, option_parse_space_change }, - OPT_BOOLEAN('R', "reverse", &apply_in_reverse, - "apply the patch in reverse"), - OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, - "don't expect at least one line of context"), - OPT_BOOLEAN(0, "reject", &apply_with_reject, - "leave the rejected hunks in corresponding *.rej files"), - OPT__VERBOSE(&apply_verbosely), - OPT_BIT(0, "inaccurate-eof", &options, - "tolerate incorrectly detected missing new-line at the end of file", - INACCURATE_EOF), - OPT_BIT(0, "recount", &options, - "do not trust the line counts in the hunk headers", - RECOUNT), - { OPTION_CALLBACK, 0, "directory", NULL, "root", - "prepend to all filenames", - 0, option_parse_directory }, - OPT_END() - }; - - prefix = setup_git_directory_gently(&is_not_gitdir); - prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config, NULL); - if (apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - if (apply_default_ignorewhitespace) - parse_ignorewhitespace_option(apply_default_ignorewhitespace); - - argc = parse_options(argc, argv, prefix, builtin_apply_options, - apply_usage, 0); - - if (apply_with_reject) - apply = apply_verbosely = 1; - if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) - apply = 0; - if (check_index && is_not_gitdir) - die("--index outside a repository"); - if (cached) { - if (is_not_gitdir) - die("--cached outside a repository"); - check_index = 1; - } - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - int fd; - - if (!strcmp(arg, "-")) { - errs |= apply_patch(0, "", options); - read_stdin = 0; - continue; - } else if (0 < prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); - - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno("can't open patch '%s'", arg); - read_stdin = 0; - set_default_whitespace_mode(whitespace_option); - errs |= apply_patch(fd, arg, options); - close(fd); - } - set_default_whitespace_mode(whitespace_option); - if (read_stdin) - errs |= apply_patch(0, "", options); - if (whitespace_error) { - if (squelch_whitespace_errors && - squelch_whitespace_errors < whitespace_error) { - int squelched = - whitespace_error - squelch_whitespace_errors; - warning("squelched %d " - "whitespace error%s", - squelched, - squelched == 1 ? "" : "s"); - } - if (ws_error_action == die_on_ws_error) - die("%d line%s add%s whitespace errors.", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - if (applied_after_fixing_ws && apply) - warning("%d line%s applied after" - " fixing whitespace errors.", - applied_after_fixing_ws, - applied_after_fixing_ws == 1 ? "" : "s"); - else if (whitespace_error) - warning("%d line%s add%s whitespace errors.", - whitespace_error, - whitespace_error == 1 ? "" : "s", - whitespace_error == 1 ? "s" : ""); - } - - if (update_index) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return !!errs; -} diff --git a/builtin-archive.c b/builtin-archive.c deleted file mode 100644 index 6a887f5a9..000000000 --- a/builtin-archive.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2006 Franck Bui-Huu - * Copyright (c) 2006 Rene Scharfe - */ -#include "cache.h" -#include "builtin.h" -#include "archive.h" -#include "transport.h" -#include "parse-options.h" -#include "pkt-line.h" -#include "sideband.h" - -static void create_output_file(const char *output_file) -{ - int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (output_fd < 0) - die_errno("could not create archive file '%s'", output_file); - if (output_fd != 1) { - if (dup2(output_fd, 1) < 0) - die_errno("could not redirect output"); - else - close(output_fd); - } -} - -static int run_remote_archiver(int argc, const char **argv, - const char *remote, const char *exec) -{ - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; - struct transport *transport; - struct remote *_remote; - - _remote = remote_get(remote); - if (!_remote->url[0]) - die("git archive: Remote with no URL"); - transport = transport_get(_remote, _remote->url[0]); - transport_connect(transport, "git-upload-archive", exec, fd); - - for (i = 1; i < argc; i++) - packet_write(fd[1], "argument %s\n", argv[i]); - packet_flush(fd[1]); - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) - die("git archive: expected ACK/NAK, got EOF"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) - die("git archive: NACK %s", buf + 5); - die("git archive: protocol error"); - } - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) - die("git archive: expected a flush"); - - /* Now, start reading from fd[0] and spit it out to stdout */ - rv = recv_sideband("archive", fd[0], 1); - rv |= transport_disconnect(transport); - - return !!rv; -} - -static const char *format_from_name(const char *filename) -{ - const char *ext = strrchr(filename, '.'); - if (!ext) - return NULL; - ext++; - if (!strcasecmp(ext, "zip")) - return "--format=zip"; - return NULL; -} - -#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ - PARSE_OPT_KEEP_ARGV0 | \ - PARSE_OPT_KEEP_UNKNOWN | \ - PARSE_OPT_NO_INTERNAL_HELP ) - -int cmd_archive(int argc, const char **argv, const char *prefix) -{ - const char *exec = "git-upload-archive"; - const char *output = NULL; - const char *remote = NULL; - const char *format_option = NULL; - struct option local_opts[] = { - OPT_STRING('o', "output", &output, "file", - "write the archive to this file"), - OPT_STRING(0, "remote", &remote, "repo", - "retrieve the archive from remote repository "), - OPT_STRING(0, "exec", &exec, "cmd", - "path to the remote git-upload-archive command"), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, local_opts, NULL, - PARSE_OPT_KEEP_ALL); - - if (output) { - create_output_file(output); - format_option = format_from_name(output); - } - - /* - * We have enough room in argv[] to muck it in place, because - * --output must have been given on the original command line - * if we get to this point, and parse_options() must have eaten - * it, i.e. we can add back one element to the array. - * - * We add a fake --format option at the beginning, with the - * format inferred from our output filename. This way explicit - * --format options can override it, and the fake option is - * inserted before any "--" that might have been given. - */ - if (format_option) { - memmove(argv + 2, argv + 1, sizeof(*argv) * argc); - argv[1] = format_option; - argv[++argc] = NULL; - } - - if (remote) - return run_remote_archiver(argc, argv, remote, exec); - - setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - - return write_archive(argc, argv, prefix, 1); -} diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c deleted file mode 100644 index 5b226399e..000000000 --- a/builtin-bisect--helper.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "parse-options.h" -#include "bisect.h" - -static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-all", - NULL -}; - -int cmd_bisect__helper(int argc, const char **argv, const char *prefix) -{ - int next_all = 0; - struct option options[] = { - OPT_BOOLEAN(0, "next-all", &next_all, - "perform 'git bisect next'"), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, 0); - - if (!next_all) - usage_with_options(git_bisect_helper_usage, options); - - /* next-all */ - return bisect_next_all(prefix); -} diff --git a/builtin-blame.c b/builtin-blame.c deleted file mode 100644 index 10f7eacf6..000000000 --- a/builtin-blame.c +++ /dev/null @@ -1,2477 +0,0 @@ -/* - * Blame - * - * Copyright (c) 2006, Junio C Hamano - */ - -#include "cache.h" -#include "builtin.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree-walk.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "quote.h" -#include "xdiff-interface.h" -#include "cache-tree.h" -#include "string-list.h" -#include "mailmap.h" -#include "parse-options.h" -#include "utf8.h" - -static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file"; - -static const char *blame_opt_usage[] = { - blame_usage, - "", - "[rev-opts] are documented in git-rev-list(1)", - NULL -}; - -static int longest_file; -static int longest_author; -static int max_orig_digits; -static int max_digits; -static int max_score_digits; -static int show_root; -static int reverse; -static int blank_boundary; -static int incremental; -static int xdl_opts = XDF_NEED_MINIMAL; - -static enum date_mode blame_date_mode = DATE_ISO8601; -static size_t blame_date_width; - -static struct string_list mailmap; - -#ifndef DEBUG -#define DEBUG 0 -#endif - -/* stats */ -static int num_read_blob; -static int num_get_patch; -static int num_commits; - -#define PICKAXE_BLAME_MOVE 01 -#define PICKAXE_BLAME_COPY 02 -#define PICKAXE_BLAME_COPY_HARDER 04 -#define PICKAXE_BLAME_COPY_HARDEST 010 - -/* - * blame for a blame_entry with score lower than these thresholds - * is not passed to the parent using move/copy logic. - */ -static unsigned blame_move_score; -static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 - -/* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */ -#define METAINFO_SHOWN (1u<<12) -#define MORE_THAN_ONE_PATH (1u<<13) - -/* - * One blob in a commit that is being suspected - */ -struct origin { - int refcnt; - struct origin *previous; - struct commit *commit; - mmfile_t file; - unsigned char blob_sha1[20]; - char path[FLEX_ARRAY]; -}; - -/* - * Given an origin, prepare mmfile_t structure to be used by the - * diff machinery - */ -static void fill_origin_blob(struct origin *o, mmfile_t *file) -{ - if (!o->file.ptr) { - enum object_type type; - num_read_blob++; - file->ptr = read_sha1_file(o->blob_sha1, &type, - (unsigned long *)(&(file->size))); - if (!file->ptr) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), - o->path); - o->file = *file; - } - else - *file = o->file; -} - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static inline struct origin *origin_incref(struct origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(struct origin *o) -{ - if (o && --o->refcnt <= 0) { - if (o->previous) - origin_decref(o->previous); - free(o->file.ptr); - free(o); - } -} - -static void drop_origin_blob(struct origin *o) -{ - if (o->file.ptr) { - free(o->file.ptr); - o->file.ptr = NULL; - } -} - -/* - * Each group of lines is described by a blame_entry; it can be split - * as we pass blame to the parents. They form a linked list in the - * scoreboard structure, sorted by the target line number. - */ -struct blame_entry { - struct blame_entry *prev; - struct blame_entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - int lno; - - /* how many lines this group has */ - int num_lines; - - /* the commit that introduced this group into the final image */ - struct origin *suspect; - - /* true if the suspect is truly guilty; false while we have not - * checked if the group came from one of its parents. - */ - char guilty; - - /* true if the entry has been scanned for copies in the current parent - */ - char scanned; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - int s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; -}; - -/* - * The current state of the blame assignment. - */ -struct scoreboard { - /* the final commit (i.e. where we started digging from) */ - struct commit *final; - struct rev_info *revs; - const char *path; - - /* - * The contents in the final image. - * Used by many functions to obtain contents of the nth line, - * indexed with scoreboard.lineno[blame_entry.lno]. - */ - const char *final_buf; - unsigned long final_buf_size; - - /* linked list of blames */ - struct blame_entry *ent; - - /* look-up a line in the final buffer */ - int num_lines; - int *lineno; -}; - -static inline int same_suspect(struct origin *a, struct origin *b) -{ - if (a == b) - return 1; - if (a->commit != b->commit) - return 0; - return !strcmp(a->path, b->path); -} - -static void sanity_check_refcnt(struct scoreboard *); - -/* - * If two blame entries that are next to each other came from - * contiguous lines in the same origin (i.e. pair), - * merge them together. - */ -static void coalesce(struct scoreboard *sb) -{ - struct blame_entry *ent, *next; - - for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (same_suspect(ent->suspect, next->suspect) && - ent->guilty == next->guilty && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; - ent->next = next->next; - if (ent->next) - ent->next->prev = ent; - origin_decref(next->suspect); - free(next); - ent->score = 0; - next = ent; /* again */ - } - } - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); -} - -/* - * Given a commit and a path in it, create a new origin structure. - * The callers that add blame to the scoreboard should use - * get_origin() to obtain shared, refcounted copy instead of calling - * this function directly. - */ -static struct origin *make_origin(struct commit *commit, const char *path) -{ - struct origin *o; - o = xcalloc(1, sizeof(*o) + strlen(path) + 1); - o->commit = commit; - o->refcnt = 1; - strcpy(o->path, path); - return o; -} - -/* - * Locate an existing origin or create a new one. - */ -static struct origin *get_origin(struct scoreboard *sb, - struct commit *commit, - const char *path) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->suspect->commit == commit && - !strcmp(e->suspect->path, path)) - return origin_incref(e->suspect); - } - return make_origin(commit, path); -} - -/* - * Fill the blob_sha1 field of an origin if it hasn't, so that later - * call to fill_origin_blob() can use it to locate the data. blob_sha1 - * for an origin is also used to pass the blame for the entire file to - * the parent to detect the case where a child's blob is identical to - * that of its parent's. - */ -static int fill_blob_sha1(struct origin *origin) -{ - unsigned mode; - - if (!is_null_sha1(origin->blob_sha1)) - return 0; - if (get_tree_entry(origin->commit->object.sha1, - origin->path, - origin->blob_sha1, &mode)) - goto error_out; - if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) - goto error_out; - return 0; - error_out: - hashclr(origin->blob_sha1); - return -1; -} - -/* - * We have an origin -- check if the same path exists in the - * parent and return an origin structure to represent it. - */ -static struct origin *find_origin(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - const char *paths[2]; - - if (parent->util) { - /* - * Each commit object can cache one origin in that - * commit. This is a freestanding copy of origin and - * not refcounted. - */ - struct origin *cached = parent->util; - if (!strcmp(cached->path, origin->path)) { - /* - * The same path between origin and its parent - * without renaming -- the most common case. - */ - porigin = get_origin(sb, parent, cached->path); - - /* - * If the origin was newly created (i.e. get_origin - * would call make_origin if none is found in the - * scoreboard), it does not know the blob_sha1, - * so copy it. Otherwise porigin was in the - * scoreboard and already knows blob_sha1. - */ - if (porigin->refcnt == 1) - hashcpy(porigin->blob_sha1, cached->blob_sha1); - return porigin; - } - /* otherwise it was not very useful; free it */ - free(parent->util); - parent->util = NULL; - } - - /* See if the origin->path is different between parent - * and origin first. Most of the time they are the - * same and diff-tree is fairly efficient about this. - */ - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = 0; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = origin->path; - paths[1] = NULL; - - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - if (!diff_queued_diff.nr) { - /* The path is the same as parent */ - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, origin->blob_sha1); - } else { - /* - * Since origin->path is a pathspec, if the parent - * commit had it as a directory, we will see a whole - * bunch of deletion of files in the directory that we - * do not care about. - */ - int i; - struct diff_filepair *p = NULL; - for (i = 0; i < diff_queued_diff.nr; i++) { - const char *name; - p = diff_queued_diff.queue[i]; - name = p->one->path ? p->one->path : p->two->path; - if (!strcmp(name, origin->path)) - break; - } - if (!p) - die("internal error in blame::find_origin"); - switch (p->status) { - default: - die("internal error in blame::find_origin (%c)", - p->status); - case 'M': - porigin = get_origin(sb, parent, origin->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - case 'A': - case 'T': - /* Did not exist in parent, or type changed */ - break; - } - } - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - if (porigin) { - /* - * Create a freestanding copy that is not part of - * the refcounted origin found in the scoreboard, and - * cache it in the commit. - */ - struct origin *cached; - - cached = make_origin(porigin->commit, porigin->path); - hashcpy(cached->blob_sha1, porigin->blob_sha1); - parent->util = cached; - } - return porigin; -} - -/* - * We have an origin -- find the path that corresponds to it in its - * parent and return an origin structure to represent it. - */ -static struct origin *find_rename(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - int i; - const char *paths[2]; - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = DIFF_DETECT_RENAME; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = origin->path; - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - if (is_null_sha1(origin->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->two->path, origin->path)) { - porigin = get_origin(sb, parent, p->one->path); - hashcpy(porigin->blob_sha1, p->one->sha1); - break; - } - } - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - return porigin; -} - -/* - * Link in a new blame entry to the scoreboard. Entries that cover the - * same line range have been removed from the scoreboard previously. - */ -static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) -{ - struct blame_entry *ent, *prev = NULL; - - origin_incref(e->suspect); - - for (ent = sb->ent; ent && ent->lno < e->lno; ent = ent->next) - prev = ent; - - /* prev, if not NULL, is the last one that is below e */ - e->prev = prev; - if (prev) { - e->next = prev->next; - prev->next = e; - } - else { - e->next = sb->ent; - sb->ent = e; - } - if (e->next) - e->next->prev = e; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that is already on the linked list of the - * scoreboard. The origin of dst loses a refcnt while the origin of src - * gains one. - */ -static void dup_entry(struct blame_entry *dst, struct blame_entry *src) -{ - struct blame_entry *p, *n; - - p = dst->prev; - n = dst->next; - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->prev = p; - dst->next = n; - dst->score = 0; -} - -static const char *nth_line(struct scoreboard *sb, int lno) -{ - return sb->final_buf + sb->lineno[lno]; -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> - * <------------> - * <------------> - * <------------------> - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(struct blame_entry *split, - struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - int chunk_end_lno; - memset(split, 0, sizeof(struct blame_entry [3])); - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } - else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } - else - chunk_end_lno = e->lno + e->num_lines; - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, - * forget about the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * split_overlap() divided an existing blame e into up to three parts - * in split. Adjust the linked list of blames in the scoreboard to - * reflect the split. - */ -static void split_blame(struct scoreboard *sb, - struct blame_entry *split, - struct blame_entry *e) -{ - struct blame_entry *new_entry; - - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e) */ - dup_entry(e, &split[0]); - - /* The last part -- me */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - - /* ... and the middle part -- parent */ - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else if (!split[0].suspect && !split[2].suspect) - /* - * The parent covers the entire area; reuse storage for - * e and replace it with the parent. - */ - dup_entry(e, &split[1]); - else if (split[0].suspect) { - /* me and then parent */ - dup_entry(e, &split[0]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[1]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - else { - /* parent and then me */ - dup_entry(e, &split[1]); - - new_entry = xmalloc(sizeof(*new_entry)); - memcpy(new_entry, &(split[2]), sizeof(struct blame_entry)); - add_blame_entry(sb, new_entry); - } - - if (DEBUG) { /* sanity */ - struct blame_entry *ent; - int lno = sb->ent->lno, corrupt = 0; - - for (ent = sb->ent; ent; ent = ent->next) { - if (lno != ent->lno) - corrupt = 1; - if (ent->s_lno < 0) - corrupt = 1; - lno += ent->num_lines; - } - if (corrupt) { - lno = sb->ent->lno; - for (ent = sb->ent; ent; ent = ent->next) { - printf("L %8d l %8d n %8d\n", - lno, ent->lno, ent->num_lines); - lno = ent->lno + ent->num_lines; - } - die("oops"); - } - } -} - -/* - * After splitting the blame, the origins used by the - * on-stack blame_entry should lose one refcnt each. - */ -static void decref_split(struct blame_entry *split) -{ - int i; - - for (i = 0; i < 3; i++) - origin_decref(split[i].suspect); -} - -/* - * Helper for blame_chunk(). blame_entry e is known to overlap with - * the patch hunk; split it and pass blame to the parent. - */ -static void blame_overlap(struct scoreboard *sb, struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - struct blame_entry split[3]; - - split_overlap(split, e, tlno, plno, same, parent); - if (split[1].suspect) - split_blame(sb, split, e); - decref_split(split); -} - -/* - * Find the line number of the last line the target is suspected for. - */ -static int find_last_in_target(struct scoreboard *sb, struct origin *target) -{ - struct blame_entry *e; - int last_in_target = -1; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (last_in_target < e->s_lno + e->num_lines) - last_in_target = e->s_lno + e->num_lines; - } - return last_in_target; -} - -/* - * Process one hunk from the patch between the current suspect for - * blame_entry e and its parent. Find and split the overlap, and - * pass blame to the overlapping part to the parent. - */ -static void blame_chunk(struct scoreboard *sb, - int tlno, int plno, int same, - struct origin *target, struct origin *parent) -{ - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target)) - continue; - if (same <= e->s_lno) - continue; - if (tlno < e->s_lno + e->num_lines) - blame_overlap(sb, e, tlno, plno, same, parent); - } -} - -struct blame_chunk_cb_data { - struct scoreboard *sb; - struct origin *target; - struct origin *parent; - long plno; - long tlno; -}; - -static void blame_chunk_cb(void *data, long same, long p_next, long t_next) -{ - struct blame_chunk_cb_data *d = data; - blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent); - d->plno = p_next; - d->tlno = t_next; -} - -/* - * We are looking at the origin 'target' and aiming to pass blame - * for the lines it is suspected to its parent. Run diff to find - * which lines came from parent and pass blame for them. - */ -static int pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int last_in_target; - mmfile_t file_p, file_o; - struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 }; - xpparam_t xpp; - xdemitconf_t xecfg; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - fill_origin_blob(target, &file_o); - num_get_patch++; - - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = xdl_opts; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 0; - xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg); - /* The rest (i.e. anything after tlno) are the same as the parent */ - blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent); - - return 0; -} - -/* - * The lines in blame_entry after splitting blames many times can become - * very small and trivial, and at some point it becomes pointless to - * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any - * ordinary C program, and it is not worth to say it was copied from - * totally unrelated file in the parent. - * - * Compute how trivial the lines in the blame_entry are. - */ -static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) -{ - unsigned score; - const char *cp, *ep; - - if (e->score) - return e->score; - - score = 1; - cp = nth_line(sb, e->lno); - ep = nth_line(sb, e->lno + e->num_lines); - while (cp < ep) { - unsigned ch = *((unsigned char *)cp); - if (isalnum(ch)) - score++; - cp++; - } - e->score = score; - return score; -} - -/* - * best_so_far[] and this[] are both a split of an existing blame_entry - * that passes blame to the parent. Maintain best_so_far the best split - * so far, by comparing this and best_so_far and copying this into - * bst_so_far as needed. - */ -static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry *best_so_far, - struct blame_entry *this) -{ - int i; - - if (!this[1].suspect) - return; - if (best_so_far[1].suspect) { - if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) - return; - } - - for (i = 0; i < 3; i++) - origin_incref(this[i].suspect); - decref_split(best_so_far); - memcpy(best_so_far, this, sizeof(struct blame_entry [3])); -} - -/* - * We are looking at a part of the final image represented by - * ent (tlno and same are offset by ent->s_lno). - * tlno is where we are looking at in the final image. - * up to (but not including) same match preimage. - * plno is where we are looking at in the preimage. - * - * <-------------- final image ----------------------> - * <------ent------> - * ^tlno ^same - * <---------preimage-----> - * ^plno - * - * All line numbers are 0-based. - */ -static void handle_split(struct scoreboard *sb, - struct blame_entry *ent, - int tlno, int plno, int same, - struct origin *parent, - struct blame_entry *split) -{ - if (ent->num_lines <= tlno) - return; - if (tlno < same) { - struct blame_entry this[3]; - tlno += ent->s_lno; - same += ent->s_lno; - split_overlap(this, ent, tlno, plno, same, parent); - copy_split_if_better(sb, split, this); - decref_split(this); - } -} - -struct handle_split_cb_data { - struct scoreboard *sb; - struct blame_entry *ent; - struct origin *parent; - struct blame_entry *split; - long plno; - long tlno; -}; - -static void handle_split_cb(void *data, long same, long p_next, long t_next) -{ - struct handle_split_cb_data *d = data; - handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split); - d->plno = p_next; - d->tlno = t_next; -} - -/* - * Find the lines from parent that are the same as ent so that - * we can pass blames to it. file_p has the blob contents for - * the parent. - */ -static void find_copy_in_blob(struct scoreboard *sb, - struct blame_entry *ent, - struct origin *parent, - struct blame_entry *split, - mmfile_t *file_p) -{ - const char *cp; - int cnt; - mmfile_t file_o; - struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 }; - xpparam_t xpp; - xdemitconf_t xecfg; - - /* - * Prepare mmfile that contains only the lines in ent. - */ - cp = nth_line(sb, ent->lno); - file_o.ptr = (char *) cp; - cnt = ent->num_lines; - - while (cnt && cp < sb->final_buf + sb->final_buf_size) { - if (*cp++ == '\n') - cnt--; - } - file_o.size = cp - file_o.ptr; - - /* - * file_o is a part of final image we are annotating. - * file_p partially may match that image. - */ - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = xdl_opts; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 1; - memset(split, 0, sizeof(struct blame_entry [3])); - xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg); - /* remainder, if any, all match the preimage */ - handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); -} - -/* - * See if lines currently target is suspected for can be attributed to - * parent. - */ -static int find_move_in_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - int last_in_target, made_progress; - struct blame_entry *e, split[3]; - mmfile_t file_p; - - last_in_target = find_last_in_target(sb, target); - if (last_in_target < 0) - return 1; /* nothing remains for this target */ - - fill_origin_blob(parent, &file_p); - if (!file_p.ptr) - return 0; - - made_progress = 1; - while (made_progress) { - made_progress = 0; - for (e = sb->ent; e; e = e->next) { - if (e->guilty || !same_suspect(e->suspect, target) || - ent_score(sb, e) < blame_move_score) - continue; - find_copy_in_blob(sb, e, parent, split, &file_p); - if (split[1].suspect && - blame_move_score < ent_score(sb, &split[1])) { - split_blame(sb, split, e); - made_progress = 1; - } - decref_split(split); - } - } - return 0; -} - -struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; -}; - -/* - * Count the number of entries the target is suspected for, - * and prepare a list of entry and the best split. - */ -static struct blame_list *setup_blame_list(struct scoreboard *sb, - struct origin *target, - int min_score, - int *num_ents_p) -{ - struct blame_entry *e; - int num_ents, i; - struct blame_list *blame_list = NULL; - - for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - same_suspect(e->suspect, target) && - min_score < ent_score(sb, e)) - num_ents++; - if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = sb->ent, i = 0; e; e = e->next) - if (!e->scanned && !e->guilty && - same_suspect(e->suspect, target) && - min_score < ent_score(sb, e)) - blame_list[i++].ent = e; - } - *num_ents_p = num_ents; - return blame_list; -} - -/* - * Reset the scanned status on all entries. - */ -static void reset_scanned_flag(struct scoreboard *sb) -{ - struct blame_entry *e; - for (e = sb->ent; e; e = e->next) - e->scanned = 0; -} - -/* - * For lines target is suspected for, see if we can find code movement - * across file boundary from the parent commit. porigin is the path - * in the parent we already tried. - */ -static int find_copy_in_parent(struct scoreboard *sb, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) -{ - struct diff_options diff_opts; - const char *paths[1]; - int i, j; - int retval; - struct blame_list *blame_list; - int num_ents; - - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) - return 1; /* nothing remains for this target */ - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - - paths[0] = NULL; - diff_tree_setup_paths(paths, &diff_opts); - if (diff_setup_done(&diff_opts) < 0) - die("diff-setup"); - - /* Try "find copies harder" on new path if requested; - * we do not want to use diffcore_rename() actually to - * match things up; find_copies_harder is set only to - * force diff_tree_sha1() to feed all filepairs to diff_queue, - * and this code needs to be after diff_setup_done(), which - * usually makes find-copies-harder imply copy detection. - */ - if ((opt & PICKAXE_BLAME_COPY_HARDEST) - || ((opt & PICKAXE_BLAME_COPY_HARDER) - && (!porigin || strcmp(target->path, porigin->path)))) - DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); - - if (is_null_sha1(target->commit->object.sha1)) - do_diff_cache(parent->tree->object.sha1, &diff_opts); - else - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); - - if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) - diffcore_std(&diff_opts); - - retval = 0; - while (1) { - int made_progress = 0; - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (S_ISGITLINK(p->one->mode)) - continue; /* ignore git links */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - - norigin = get_origin(sb, parent, p->one->path); - hashcpy(norigin->blob_sha1, p->one->sha1); - fill_origin_blob(norigin, &file_p); - if (!file_p.ptr) - continue; - - for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, - norigin, this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); - } - origin_decref(norigin); - } - - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) { - split_blame(sb, split, blame_list[j].ent); - made_progress = 1; - } - else - blame_list[j].ent->scanned = 1; - decref_split(split); - } - free(blame_list); - - if (!made_progress) - break; - blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents); - if (!blame_list) { - retval = 1; - break; - } - } - reset_scanned_flag(sb); - diff_flush(&diff_opts); - diff_tree_release_paths(&diff_opts); - return retval; -} - -/* - * The blobs of origin and porigin exactly match, so everything - * origin is suspected for can be blamed on the parent. - */ -static void pass_whole_blame(struct scoreboard *sb, - struct origin *origin, struct origin *porigin) -{ - struct blame_entry *e; - - if (!porigin->file.ptr && origin->file.ptr) { - /* Steal its file */ - porigin->file = origin->file; - origin->file.ptr = NULL; - } - for (e = sb->ent; e; e = e->next) { - if (!same_suspect(e->suspect, origin)) - continue; - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } -} - -/* - * We pass blame from the current commit to its parents. We keep saying - * "parent" (and "porigin"), but what we mean is to find scapegoat to - * exonerate ourselves. - */ -static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) -{ - if (!reverse) - return commit->parents; - return lookup_decoration(&revs->children, &commit->object); -} - -static int num_scapegoats(struct rev_info *revs, struct commit *commit) -{ - int cnt; - struct commit_list *l = first_scapegoat(revs, commit); - for (cnt = 0; l; l = l->next) - cnt++; - return cnt; -} - -#define MAXSG 16 - -static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) -{ - struct rev_info *revs = sb->revs; - int i, pass, num_sg; - struct commit *commit = origin->commit; - struct commit_list *sg; - struct origin *sg_buf[MAXSG]; - struct origin *porigin, **sg_origin = sg_buf; - - num_sg = num_scapegoats(revs, commit); - if (!num_sg) - goto finish; - else if (num_sg < ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); - - /* - * The first pass looks for unrenamed path to optimize for - * common cases, then we look for renames in the second pass. - */ - for (pass = 0; pass < 2; pass++) { - struct origin *(*find)(struct scoreboard *, - struct commit *, struct origin *); - find = pass ? find_rename : find_origin; - - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct commit *p = sg->item; - int j, same; - - if (sg_origin[i]) - continue; - if (parse_commit(p)) - continue; - porigin = find(sb, p, origin); - if (!porigin) - continue; - if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) { - pass_whole_blame(sb, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; j < i; j++) - if (sg_origin[j] && - !hashcmp(sg_origin[j]->blob_sha1, - porigin->blob_sha1)) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - } - - num_commits++; - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (!origin->previous) { - origin_incref(porigin); - origin->previous = porigin; - } - if (pass_blame_to_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally find moves in parents' files. - */ - if (opt & PICKAXE_BLAME_MOVE) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (find_move_in_parent(sb, origin, porigin)) - goto finish; - } - - /* - * Optionally find copies from parents' files. - */ - if (opt & PICKAXE_BLAME_COPY) - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (find_copy_in_parent(sb, origin, sg->item, - porigin, opt)) - goto finish; - } - - finish: - for (i = 0; i < num_sg; i++) { - if (sg_origin[i]) { - drop_origin_blob(sg_origin[i]); - origin_decref(sg_origin[i]); - } - } - drop_origin_blob(origin); - if (sg_buf != sg_origin) - free(sg_origin); -} - -/* - * Information on commits, used for output. - */ -struct commit_info -{ - const char *author; - const char *author_mail; - unsigned long author_time; - const char *author_tz; - - /* filled only when asked for details */ - const char *committer; - const char *committer_mail; - unsigned long committer_time; - const char *committer_tz; - - const char *summary; -}; - -/* - * Parse author/committer line in the commit object buffer - */ -static void get_ac_line(const char *inbuf, const char *what, - int person_len, char *person, - int mail_len, char *mail, - unsigned long *time, const char **tz) -{ - int len, tzlen, maillen; - char *tmp, *endp, *timepos, *mailpos; - - tmp = strstr(inbuf, what); - if (!tmp) - goto error_out; - tmp += strlen(what); - endp = strchr(tmp, '\n'); - if (!endp) - len = strlen(tmp); - else - len = endp - tmp; - if (person_len <= len) { - error_out: - /* Ugh */ - *tz = "(unknown)"; - strcpy(person, *tz); - strcpy(mail, *tz); - *time = 0; - return; - } - memcpy(person, tmp, len); - - tmp = person; - tmp += len; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *tz = tmp+1; - tzlen = (person+len)-(tmp+1); - - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *time = strtoul(tmp, NULL, 10); - timepos = tmp; - - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - return; - mailpos = tmp + 1; - *tmp = 0; - maillen = timepos - tmp; - memcpy(mail, mailpos, maillen); - - if (!mailmap.nr) - return; - - /* - * mailmap expansion may make the name longer. - * make room by pushing stuff down. - */ - tmp = person + person_len - (tzlen + 1); - memmove(tmp, *tz, tzlen); - tmp[tzlen] = 0; - *tz = tmp; - - /* - * Now, convert both name and e-mail using mailmap - */ - if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { - /* Add a trailing '>' to email, since map_user returns plain emails - Note: It already has '<', since we replace from mail+1 */ - mailpos = memchr(mail, '\0', mail_len); - if (mailpos && mailpos-mail < mail_len - 1) { - *mailpos = '>'; - *(mailpos+1) = '\0'; - } - } -} - -static void get_commit_info(struct commit *commit, - struct commit_info *ret, - int detailed) -{ - int len; - char *tmp, *endp, *reencoded, *message; - static char author_name[1024]; - static char author_mail[1024]; - static char committer_name[1024]; - static char committer_mail[1024]; - static char summary_buf[1024]; - - /* - * We've operated without save_commit_buffer, so - * we now need to populate them for output. - */ - if (!commit->buffer) { - enum object_type type; - unsigned long size; - commit->buffer = - read_sha1_file(commit->object.sha1, &type, &size); - if (!commit->buffer) - die("Cannot read commit %s", - sha1_to_hex(commit->object.sha1)); - } - reencoded = reencode_commit_message(commit, NULL); - message = reencoded ? reencoded : commit->buffer; - ret->author = author_name; - ret->author_mail = author_mail; - get_ac_line(message, "\nauthor ", - sizeof(author_name), author_name, - sizeof(author_mail), author_mail, - &ret->author_time, &ret->author_tz); - - if (!detailed) { - free(reencoded); - return; - } - - ret->committer = committer_name; - ret->committer_mail = committer_mail; - get_ac_line(message, "\ncommitter ", - sizeof(committer_name), committer_name, - sizeof(committer_mail), committer_mail, - &ret->committer_time, &ret->committer_tz); - - ret->summary = summary_buf; - tmp = strstr(message, "\n\n"); - if (!tmp) { - error_out: - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - free(reencoded); - return; - } - tmp += 2; - endp = strchr(tmp, '\n'); - if (!endp) - endp = tmp + strlen(tmp); - len = endp - tmp; - if (len >= sizeof(summary_buf) || len == 0) - goto error_out; - memcpy(summary_buf, tmp, len); - summary_buf[len] = 0; - free(reencoded); -} - -/* - * To allow LF and other nonportable characters in pathnames, - * they are c-style quoted as needed. - */ -static void write_filename_info(const char *path) -{ - printf("filename "); - write_name_quoted(path, stdout, '\n'); -} - -/* - * Porcelain/Incremental format wants to show a lot of details per - * commit. Instead of repeating this every line, emit it only once, - * the first time each commit appears in the output. - */ -static int emit_one_suspect_detail(struct origin *suspect) -{ - struct commit_info ci; - - if (suspect->commit->object.flags & METAINFO_SHOWN) - return 0; - - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); - if (suspect->commit->object.flags & UNINTERESTING) - printf("boundary\n"); - if (suspect->previous) { - struct origin *prev = suspect->previous; - printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); - write_name_quoted(prev->path, stdout, '\n'); - } - return 1; -} - -/* - * The blame_entry is found to be guilty for the range. Mark it - * as such, and show it in incremental output. - */ -static void found_guilty_entry(struct blame_entry *ent) -{ - if (ent->guilty) - return; - ent->guilty = 1; - if (incremental) { - struct origin *suspect = ent->suspect; - - printf("%s %d %d %d\n", - sha1_to_hex(suspect->commit->object.sha1), - ent->s_lno + 1, ent->lno + 1, ent->num_lines); - emit_one_suspect_detail(suspect); - write_filename_info(suspect->path); - maybe_flush_or_die(stdout, "stdout"); - } -} - -/* - * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one blame_entry, and allow its current - * suspect to pass blames to its parents. - */ -static void assign_blame(struct scoreboard *sb, int opt) -{ - struct rev_info *revs = sb->revs; - - while (1) { - struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; - - /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ - - /* - * We will use this suspect later in the loop, - * so hold onto it in the meantime. - */ - origin_incref(suspect); - commit = suspect->commit; - if (!commit->object.parsed) - parse_commit(commit); - if (reverse || - (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age))) - pass_blame(sb, suspect, opt); - else { - commit->object.flags |= UNINTERESTING; - if (commit->object.parsed) - mark_parents_uninteresting(commit); - } - /* treat root commit as boundary */ - if (!commit->parents && !show_root) - commit->object.flags |= UNINTERESTING; - - /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (same_suspect(ent->suspect, suspect)) - found_guilty_entry(ent); - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } -} - -static const char *format_time(unsigned long time, const char *tz_str, - int show_raw_time) -{ - static char time_buf[128]; - const char *time_str; - int time_len; - int tz; - - if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); - } - else { - tz = atoi(tz_str); - time_str = show_date(time, tz, blame_date_mode); - time_len = strlen(time_str); - memcpy(time_buf, time_str, time_len); - memset(time_buf + time_len, ' ', blame_date_width - time_len); - } - return time_buf; -} - -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 - -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - char hex[41]; - - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - printf("%s%c%d %d %d\n", - hex, - ent->guilty ? ' ' : '*', // purely for debugging - ent->s_lno + 1, - ent->lno + 1, - ent->num_lines); - if (emit_one_suspect_detail(suspect) || - (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) - write_filename_info(suspect->path); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - if (cnt) - printf("%s %d %d\n", hex, - ent->s_lno + 1 + cnt, - ent->lno + 1 + cnt); - putchar('\t'); - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } - - if (sb->final_buf_size && cp[-1] != '\n') - putchar('\n'); -} - -static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) -{ - int cnt; - const char *cp; - struct origin *suspect = ent->suspect; - struct commit_info ci; - char hex[41]; - int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); - - get_commit_info(suspect->commit, &ci, 1); - strcpy(hex, sha1_to_hex(suspect->commit->object.sha1)); - - cp = nth_line(sb, ent->lno); - for (cnt = 0; cnt < ent->num_lines; cnt++) { - char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; - - if (suspect->commit->object.flags & UNINTERESTING) { - if (blank_boundary) - memset(hex, ' ', length); - else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) { - length--; - putchar('^'); - } - } - - printf("%.*s", length, hex); - if (opt & OUTPUT_ANNOTATE_COMPAT) - printf("\t(%10s\t%10s\t%d)", ci.author, - format_time(ci.author_time, ci.author_tz, - show_raw_time), - ent->lno + 1 + cnt); - else { - if (opt & OUTPUT_SHOW_SCORE) - printf(" %*d %02d", - max_score_digits, ent->score, - ent->suspect->refcnt); - if (opt & OUTPUT_SHOW_NAME) - printf(" %-*.*s", longest_file, longest_file, - suspect->path); - if (opt & OUTPUT_SHOW_NUMBER) - printf(" %*d", max_orig_digits, - ent->s_lno + 1 + cnt); - - if (!(opt & OUTPUT_NO_AUTHOR)) { - int pad = longest_author - utf8_strwidth(ci.author); - printf(" (%s%*s %10s", - ci.author, pad, "", - format_time(ci.author_time, - ci.author_tz, - show_raw_time)); - } - printf(" %*d) ", - max_digits, ent->lno + 1 + cnt); - } - do { - ch = *cp++; - putchar(ch); - } while (ch != '\n' && - cp < sb->final_buf + sb->final_buf_size); - } - - if (sb->final_buf_size && cp[-1] != '\n') - putchar('\n'); -} - -static void output(struct scoreboard *sb, int option) -{ - struct blame_entry *ent; - - if (option & OUTPUT_PORCELAIN) { - for (ent = sb->ent; ent; ent = ent->next) { - struct blame_entry *oth; - struct origin *suspect = ent->suspect; - struct commit *commit = suspect->commit; - if (commit->object.flags & MORE_THAN_ONE_PATH) - continue; - for (oth = ent->next; oth; oth = oth->next) { - if ((oth->suspect->commit != commit) || - !strcmp(oth->suspect->path, suspect->path)) - continue; - commit->object.flags |= MORE_THAN_ONE_PATH; - break; - } - } - } - - for (ent = sb->ent; ent; ent = ent->next) { - if (option & OUTPUT_PORCELAIN) - emit_porcelain(sb, ent); - else { - emit_other(sb, ent, option); - } - } -} - -/* - * To allow quick access to the contents of nth line in the - * final image, prepare an index in the scoreboard. - */ -static int prepare_lines(struct scoreboard *sb) -{ - const char *buf = sb->final_buf; - unsigned long len = sb->final_buf_size; - int num = 0, incomplete = 0, bol = 1; - - if (len && buf[len-1] != '\n') - incomplete++; /* incomplete line at the end */ - while (len--) { - if (bol) { - sb->lineno = xrealloc(sb->lineno, - sizeof(int *) * (num + 1)); - sb->lineno[num] = buf - sb->final_buf; - bol = 0; - } - if (*buf++ == '\n') { - num++; - bol = 1; - } - } - sb->lineno = xrealloc(sb->lineno, - sizeof(int *) * (num + incomplete + 1)); - sb->lineno[num + incomplete] = buf - sb->final_buf; - sb->num_lines = num + incomplete; - return sb->num_lines; -} - -/* - * Add phony grafts for use with -S; this is primarily to - * support git's cvsserver that wants to give a linear history - * to its clients. - */ -static int read_ancestry(const char *graft_file) -{ - FILE *fp = fopen(graft_file, "r"); - char buf[1024]; - if (!fp) - return -1; - while (fgets(buf, sizeof(buf), fp)) { - /* The format is just "Commit Parent1 Parent2 ...\n" */ - int len = strlen(buf); - struct commit_graft *graft = read_graft_line(buf, len); - if (graft) - register_commit_graft(graft, 0); - } - fclose(fp); - return 0; -} - -/* - * How many columns do we need to show line numbers in decimal? - */ -static int lineno_width(int lines) -{ - int i, width; - - for (width = 1, i = 10; i <= lines + 1; width++) - i *= 10; - return width; -} - -/* - * How many columns do we need to show line numbers, authors, - * and filenames? - */ -static void find_alignment(struct scoreboard *sb, int *option) -{ - int longest_src_lines = 0; - int longest_dst_lines = 0; - unsigned largest_score = 0; - struct blame_entry *e; - - for (e = sb->ent; e; e = e->next) { - struct origin *suspect = e->suspect; - struct commit_info ci; - int num; - - if (strcmp(suspect->path, sb->path)) - *option |= OUTPUT_SHOW_NAME; - num = strlen(suspect->path); - if (longest_file < num) - longest_file = num; - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - num = utf8_strwidth(ci.author); - if (longest_author < num) - longest_author = num; - } - num = e->s_lno + e->num_lines; - if (longest_src_lines < num) - longest_src_lines = num; - num = e->lno + e->num_lines; - if (longest_dst_lines < num) - longest_dst_lines = num; - if (largest_score < ent_score(sb, e)) - largest_score = ent_score(sb, e); - } - max_orig_digits = lineno_width(longest_src_lines); - max_digits = lineno_width(longest_dst_lines); - max_score_digits = lineno_width(largest_score); -} - -/* - * For debugging -- origin is refcounted, and this asserts that - * we do not underflow. - */ -static void sanity_check_refcnt(struct scoreboard *sb) -{ - int baa = 0; - struct blame_entry *ent; - - for (ent = sb->ent; ent; ent = ent->next) { - /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) { - fprintf(stderr, "%s in %s has negative refcnt %d\n", - ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), - ent->suspect->refcnt); - baa = 1; - } - } - if (baa) { - int opt = 0160; - find_alignment(sb, &opt); - output(sb, opt); - die("Baa %d!", baa); - } -} - -/* - * Used for the command line parsing; check if the path exists - * in the working tree. - */ -static int has_string_in_work_tree(const char *path) -{ - struct stat st; - return !lstat(path, &st); -} - -static unsigned parse_score(const char *arg) -{ - char *end; - unsigned long score = strtoul(arg, &end, 10); - if (*end) - return 0; - return score; -} - -static const char *add_prefix(const char *prefix, const char *path) -{ - return prefix_path(prefix, prefix ? strlen(prefix) : 0, path); -} - -/* - * Parsing of (comma separated) one item in the -L option - */ -static const char *parse_loc(const char *spec, - struct scoreboard *sb, long lno, - long begin, long *ret) -{ - char *term; - const char *line; - long num; - int reg_error; - regex_t regexp; - regmatch_t match[1]; - - /* Allow "-L ,+20" to mean starting at - * for 20 lines, or "-L ,-5" for 5 lines ending at - * . - */ - if (1 < begin && (spec[0] == '+' || spec[0] == '-')) { - num = strtol(spec + 1, &term, 10); - if (term != spec + 1) { - if (spec[0] == '-') - num = 0 - num; - if (0 < num) - *ret = begin + num - 2; - else if (!num) - *ret = begin; - else - *ret = begin + num; - return term; - } - return spec; - } - num = strtol(spec, &term, 10); - if (term != spec) { - *ret = num; - return term; - } - if (spec[0] != '/') - return spec; - - /* it could be a regexp of form /.../ */ - for (term = (char *) spec + 1; *term && *term != '/'; term++) { - if (*term == '\\') - term++; - } - if (*term != '/') - return spec; - - /* try [spec+1 .. term-1] as regexp */ - *term = 0; - begin--; /* input is in human terms */ - line = nth_line(sb, begin); - - if (!(reg_error = regcomp(®exp, spec + 1, REG_NEWLINE)) && - !(reg_error = regexec(®exp, line, 1, match, 0))) { - const char *cp = line + match[0].rm_so; - const char *nline; - - while (begin++ < lno) { - nline = nth_line(sb, begin); - if (line <= cp && cp < nline) - break; - line = nline; - } - *ret = begin; - regfree(®exp); - *term++ = '/'; - return term; - } - else { - char errbuf[1024]; - regerror(reg_error, ®exp, errbuf, 1024); - die("-L parameter '%s': %s", spec + 1, errbuf); - } -} - -/* - * Parsing of -L option - */ -static void prepare_blame_range(struct scoreboard *sb, - const char *bottomtop, - long lno, - long *bottom, long *top) -{ - const char *term; - - term = parse_loc(bottomtop, sb, lno, 1, bottom); - if (*term == ',') { - term = parse_loc(term + 1, sb, lno, *bottom + 1, top); - if (*term) - usage(blame_usage); - } - if (*term) - usage(blame_usage); -} - -static int git_blame_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "blame.showroot")) { - show_root = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "blame.blankboundary")) { - blank_boundary = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "blame.date")) { - if (!value) - return config_error_nonbool(var); - blame_date_mode = parse_date_format(value); - return 0; - } - return git_default_config(var, value, cb); -} - -/* - * Prepare a dummy commit that represents the work tree (or staged) item. - * Note that annotating work tree item never works in the reverse. - */ -static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) -{ - struct commit *commit; - struct origin *origin; - unsigned char head_sha1[20]; - struct strbuf buf = STRBUF_INIT; - const char *ident; - time_t now; - int size, len; - struct cache_entry *ce; - unsigned mode; - - if (get_sha1("HEAD", head_sha1)) - die("No such ref: HEAD"); - - time(&now); - commit = xcalloc(1, sizeof(*commit)); - commit->parents = xcalloc(1, sizeof(*commit->parents)); - commit->parents->item = lookup_commit_reference(head_sha1); - commit->object.parsed = 1; - commit->date = now; - commit->object.type = OBJ_COMMIT; - - origin = make_origin(commit, path); - - if (!contents_from || strcmp("-", contents_from)) { - struct stat st; - const char *read_from; - - if (contents_from) { - if (stat(contents_from, &st) < 0) - die_errno("Cannot stat '%s'", contents_from); - read_from = contents_from; - } - else { - if (lstat(path, &st) < 0) - die_errno("Cannot lstat '%s'", path); - read_from = path; - } - mode = canon_mode(st.st_mode); - switch (st.st_mode & S_IFMT) { - case S_IFREG: - if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) - die_errno("cannot open or read '%s'", read_from); - break; - case S_IFLNK: - if (strbuf_readlink(&buf, read_from, st.st_size) < 0) - die_errno("cannot readlink '%s'", read_from); - break; - default: - die("unsupported file type %s", read_from); - } - } - else { - /* Reading from stdin */ - contents_from = "standard input"; - mode = 0; - if (strbuf_read(&buf, 0, 0) < 0) - die_errno("failed to read from stdin"); - } - convert_to_git(path, buf.buf, buf.len, &buf, 0); - origin->file.ptr = buf.buf; - origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1); - commit->util = origin; - - /* - * Read the current index, replace the path entry with - * origin->blob_sha1 without mucking with its mode or type - * bits; we are not going to write this index out -- we just - * want to run "diff-index --cached". - */ - discard_cache(); - read_cache(); - - len = strlen(path); - if (!mode) { - int pos = cache_name_pos(path, len); - if (0 <= pos) - mode = active_cache[pos]->ce_mode; - else - /* Let's not bother reading from HEAD tree */ - mode = S_IFREG | 0644; - } - size = cache_entry_size(len); - ce = xcalloc(1, size); - hashcpy(ce->sha1, origin->blob_sha1); - memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(len, 0); - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - - /* - * We are not going to write this out, so this does not matter - * right now, but someday we might optimize diff-index --cached - * with cache-tree information. - */ - cache_tree_invalidate_path(active_cache_tree, path); - - commit->buffer = xmalloc(400); - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "Version of %s from %s\n", - sha1_to_hex(head_sha1), - ident, ident, path, contents_from ? contents_from : path); - return commit; -} - -static const char *prepare_final(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one positive commit in the - * revs->pending array. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (obj->flags & UNINTERESTING) - continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - return final_commit_name; -} - -static const char *prepare_initial(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one negative commit, and it must be - * the boundary. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (!(obj->flags & UNINTERESTING)) - continue; - while (obj->type == OBJ_TAG) - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig down to %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - if (!final_commit_name) - die("No commit to dig down to?"); - return final_commit_name; -} - -static int blame_copy_callback(const struct option *option, const char *arg, int unset) -{ - int *opt = option->value; - - /* - * -C enables copy from removed files; - * -C -C enables copy from existing files, but only - * when blaming a new file; - * -C -C -C enables copy from existing files for - * everybody - */ - if (*opt & PICKAXE_BLAME_COPY_HARDER) - *opt |= PICKAXE_BLAME_COPY_HARDEST; - if (*opt & PICKAXE_BLAME_COPY) - *opt |= PICKAXE_BLAME_COPY_HARDER; - *opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; - - if (arg) - blame_copy_score = parse_score(arg); - return 0; -} - -static int blame_move_callback(const struct option *option, const char *arg, int unset) -{ - int *opt = option->value; - - *opt |= PICKAXE_BLAME_MOVE; - - if (arg) - blame_move_score = parse_score(arg); - return 0; -} - -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset) -{ - const char **bottomtop = option->value; - if (!arg) - return -1; - if (*bottomtop) - die("More than one '-L n,m' option given"); - *bottomtop = arg; - return 0; -} - -int cmd_blame(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - const char *path; - struct scoreboard sb; - struct origin *o; - struct blame_entry *ent; - long dashdash_pos, bottom, top, lno; - const char *final_commit_name = NULL; - enum object_type type; - - static const char *bottomtop = NULL; - static int output_option = 0, opt = 0; - static int show_stats = 0; - static const char *revs_file = NULL; - static const char *contents_from = NULL; - static const struct option options[] = { - OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"), - OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"), - OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"), - OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"), - OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN), - OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT), - OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP), - OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME), - OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR), - OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE), - OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from instead of calling git-rev-list"), - OPT_STRING(0, "contents", &contents_from, "file", "Use 's contents as the final image"), - { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback }, - { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback }, - OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback), - OPT_END() - }; - - struct parse_opt_ctx_t ctx; - int cmd_is_annotate = !strcmp(argv[0], "annotate"); - - git_config(git_blame_config, NULL); - init_revisions(&revs, NULL); - revs.date_mode = blame_date_mode; - - save_commit_buffer = 0; - dashdash_pos = 0; - - parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_ARGV0); - for (;;) { - switch (parse_options_step(&ctx, options, blame_opt_usage)) { - case PARSE_OPT_HELP: - exit(129); - case PARSE_OPT_DONE: - if (ctx.argv[0]) - dashdash_pos = ctx.cpidx; - goto parse_done; - } - - if (!strcmp(ctx.argv[0], "--reverse")) { - ctx.argv[0] = "--children"; - reverse = 1; - } - parse_revision_opt(&revs, &ctx, options, blame_opt_usage); - } -parse_done: - argc = parse_options_end(&ctx); - - if (revs_file && read_ancestry(revs_file)) - die_errno("reading graft file '%s' failed", revs_file); - - if (cmd_is_annotate) { - output_option |= OUTPUT_ANNOTATE_COMPAT; - blame_date_mode = DATE_ISO8601; - } else { - blame_date_mode = revs.date_mode; - } - - /* The maximum width used to show the dates */ - switch (blame_date_mode) { - case DATE_RFC2822: - blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); - break; - case DATE_ISO8601: - blame_date_width = sizeof("2006-10-19 16:00:04 -0700"); - break; - case DATE_RAW: - blame_date_width = sizeof("1161298804 -0700"); - break; - case DATE_SHORT: - blame_date_width = sizeof("2006-10-19"); - break; - case DATE_RELATIVE: - /* "normal" is used as the fallback for "relative" */ - case DATE_LOCAL: - case DATE_NORMAL: - blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); - break; - } - blame_date_width -= 1; /* strip the null */ - - if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER)) - opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE | - PICKAXE_BLAME_COPY_HARDER); - - if (!blame_move_score) - blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - if (!blame_copy_score) - blame_copy_score = BLAME_DEFAULT_COPY_SCORE; - - /* - * We have collected options unknown to us in argv[1..unk] - * which are to be passed to revision machinery if we are - * going to do the "bottom" processing. - * - * The remaining are: - * - * (1) if dashdash_pos != 0, its either - * "blame [revisions] -- " or - * "blame -- " - * - * (2) otherwise, its one of the two: - * "blame [revisions] " - * "blame " - * - * Note that we must strip out from the arguments: we do not - * want the path pruning but we may want "bottom" processing. - */ - if (dashdash_pos) { - switch (argc - dashdash_pos - 1) { - case 2: /* (1b) */ - if (argc != 4) - usage_with_options(blame_opt_usage, options); - /* reorder for the new way: -- */ - argv[1] = argv[3]; - argv[3] = argv[2]; - argv[2] = "--"; - /* FALLTHROUGH */ - case 1: /* (1a) */ - path = add_prefix(prefix, argv[--argc]); - argv[argc] = NULL; - break; - default: - usage_with_options(blame_opt_usage, options); - } - } else { - if (argc < 2) - usage_with_options(blame_opt_usage, options); - path = add_prefix(prefix, argv[argc - 1]); - if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */ - path = add_prefix(prefix, argv[1]); - argv[1] = argv[2]; - } - argv[argc - 1] = "--"; - - setup_work_tree(); - if (!has_string_in_work_tree(path)) - die_errno("cannot stat path '%s'", path); - } - - revs.disable_stdin = 1; - setup_revisions(argc, argv, &revs, NULL); - memset(&sb, 0, sizeof(sb)); - - sb.revs = &revs; - if (!reverse) - final_commit_name = prepare_final(&sb); - else if (contents_from) - die("--contents and --children do not blend well."); - else - final_commit_name = prepare_initial(&sb); - - if (!sb.final) { - /* - * "--not A B -- path" without anything positive; - * do not default to HEAD, but use the working tree - * or "--contents". - */ - setup_work_tree(); - sb.final = fake_working_tree_commit(path, contents_from); - add_pending_object(&revs, &(sb.final->object), ":"); - } - else if (contents_from) - die("Cannot use --contents with final commit object name"); - - /* - * If we have bottom, this will mark the ancestors of the - * bottom commits we would reach while traversing as - * uninteresting. - */ - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - - if (is_null_sha1(sb.final->object.sha1)) { - char *buf; - o = sb.final->util; - buf = xmalloc(o->file.size + 1); - memcpy(buf, o->file.ptr, o->file.size + 1); - sb.final_buf = buf; - sb.final_buf_size = o->file.size; - } - else { - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); - - sb.final_buf = read_sha1_file(o->blob_sha1, &type, - &sb.final_buf_size); - if (!sb.final_buf) - die("Cannot read blob %s for path %s", - sha1_to_hex(o->blob_sha1), - path); - } - num_read_blob++; - lno = prepare_lines(&sb); - - bottom = top = 0; - if (bottomtop) - prepare_blame_range(&sb, bottomtop, lno, &bottom, &top); - if (bottom && top && top < bottom) { - long tmp; - tmp = top; top = bottom; bottom = tmp; - } - if (bottom < 1) - bottom = 1; - if (top < 1) - top = lno; - bottom--; - if (lno < top || lno < bottom) - die("file %s has only %lu lines", path, lno); - - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - - sb.ent = ent; - sb.path = path; - - read_mailmap(&mailmap, NULL); - - if (!incremental) - setup_pager(); - - assign_blame(&sb, opt); - - if (incremental) - return 0; - - coalesce(&sb); - - if (!(output_option & OUTPUT_PORCELAIN)) - find_alignment(&sb, &output_option); - - output(&sb, output_option); - free((void *)sb.final_buf); - for (ent = sb.ent; ent; ) { - struct blame_entry *e = ent->next; - free(ent); - ent = e; - } - - if (show_stats) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); - } - return 0; -} diff --git a/builtin-branch.c b/builtin-branch.c deleted file mode 100644 index a28a13986..000000000 --- a/builtin-branch.c +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Builtin "git branch" - * - * Copyright (c) 2006 Kristian Høgsberg - * Based on git-branch.sh by Junio C Hamano. - */ - -#include "cache.h" -#include "color.h" -#include "refs.h" -#include "commit.h" -#include "builtin.h" -#include "remote.h" -#include "parse-options.h" -#include "branch.h" -#include "diff.h" -#include "revision.h" - -static const char * const builtin_branch_usage[] = { - "git branch [options] [-r | -a] [--merged | --no-merged]", - "git branch [options] [-l] [-f] []", - "git branch [options] [-r] (-d | -D) ", - "git branch [options] (-m | -M) [] ", - NULL -}; - -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 - -static const char *head; -static unsigned char head_sha1[20]; - -static int branch_use_color = -1; -static char branch_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ - GIT_COLOR_RED, /* REMOTE */ - GIT_COLOR_NORMAL, /* LOCAL */ - GIT_COLOR_GREEN, /* CURRENT */ -}; -enum color_branch { - BRANCH_COLOR_RESET = 0, - BRANCH_COLOR_PLAIN = 1, - BRANCH_COLOR_REMOTE = 2, - BRANCH_COLOR_LOCAL = 3, - BRANCH_COLOR_CURRENT = 4, -}; - -static enum merge_filter { - NO_FILTER = 0, - SHOW_NOT_MERGED, - SHOW_MERGED, -} merge_filter; -static unsigned char merge_filter_ref[20]; - -static int parse_branch_color_slot(const char *var, int ofs) -{ - if (!strcasecmp(var+ofs, "plain")) - return BRANCH_COLOR_PLAIN; - if (!strcasecmp(var+ofs, "reset")) - return BRANCH_COLOR_RESET; - if (!strcasecmp(var+ofs, "remote")) - return BRANCH_COLOR_REMOTE; - if (!strcasecmp(var+ofs, "local")) - return BRANCH_COLOR_LOCAL; - if (!strcasecmp(var+ofs, "current")) - return BRANCH_COLOR_CURRENT; - return -1; -} - -static int git_branch_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "color.branch")) { - branch_use_color = git_config_colorbool(var, value, -1); - return 0; - } - if (!prefixcmp(var, "color.branch.")) { - int slot = parse_branch_color_slot(var, 13); - if (slot < 0) - return 0; - if (!value) - return config_error_nonbool(var); - color_parse(value, var, branch_colors[slot]); - return 0; - } - return git_color_default_config(var, value, cb); -} - -static const char *branch_get_color(enum color_branch ix) -{ - if (branch_use_color > 0) - return branch_colors[ix]; - return ""; -} - -static int branch_merged(int kind, const char *name, - struct commit *rev, struct commit *head_rev) -{ - /* - * This checks whether the merge bases of branch and HEAD (or - * the other branch this branch builds upon) contains the - * branch, which means that the branch has already been merged - * safely to HEAD (or the other branch). - */ - struct commit *reference_rev = NULL; - const char *reference_name = NULL; - int merged; - - if (kind == REF_LOCAL_BRANCH) { - struct branch *branch = branch_get(name); - unsigned char sha1[20]; - - if (branch && - branch->merge && - branch->merge[0] && - branch->merge[0]->dst && - (reference_name = - resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) - reference_rev = lookup_commit_reference(sha1); - } - if (!reference_rev) - reference_rev = head_rev; - - merged = in_merge_bases(rev, &reference_rev, 1); - - /* - * After the safety valve is fully redefined to "check with - * upstream, if any, otherwise with HEAD", we should just - * return the result of the in_merge_bases() above without - * any of the following code, but during the transition period, - * a gentle reminder is in order. - */ - if ((head_rev != reference_rev) && - in_merge_bases(rev, &head_rev, 1) != merged) { - if (merged) - warning("deleting branch '%s' that has been merged to\n" - " '%s', but it is not yet merged to HEAD.", - name, reference_name); - else - warning("not deleting branch '%s' that is not yet merged to\n" - " '%s', even though it is merged to HEAD.", - name, reference_name); - } - return merged; -} - -static int delete_branches(int argc, const char **argv, int force, int kinds) -{ - struct commit *rev, *head_rev = NULL; - unsigned char sha1[20]; - char *name = NULL; - const char *fmt, *remote; - int i; - int ret = 0; - struct strbuf bname = STRBUF_INIT; - - switch (kinds) { - case REF_REMOTE_BRANCH: - fmt = "refs/remotes/%s"; - remote = "remote "; - force = 1; - break; - case REF_LOCAL_BRANCH: - fmt = "refs/heads/%s"; - remote = ""; - break; - default: - die("cannot use -a with -d"); - } - - if (!force) { - head_rev = lookup_commit_reference(head_sha1); - if (!head_rev) - die("Couldn't look up commit object for HEAD"); - } - for (i = 0; i < argc; i++, strbuf_release(&bname)) { - strbuf_branchname(&bname, argv[i]); - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { - error("Cannot delete the branch '%s' " - "which you are currently on.", bname.buf); - ret = 1; - continue; - } - - free(name); - - name = xstrdup(mkpath(fmt, bname.buf)); - if (!resolve_ref(name, sha1, 1, NULL)) { - error("%sbranch '%s' not found.", - remote, bname.buf); - ret = 1; - continue; - } - - rev = lookup_commit_reference(sha1); - if (!rev) { - error("Couldn't look up commit object for '%s'", name); - ret = 1; - continue; - } - - if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error("The branch '%s' is not fully merged.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'.", bname.buf, bname.buf); - ret = 1; - continue; - } - - if (delete_ref(name, sha1, 0)) { - error("Error deleting %sbranch '%s'", remote, - bname.buf); - ret = 1; - } else { - struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (was %s).\n", remote, - bname.buf, - find_unique_abbrev(sha1, DEFAULT_ABBREV)); - strbuf_addf(&buf, "branch.%s", bname.buf); - if (git_config_rename_section(buf.buf, NULL) < 0) - warning("Update of config-file failed"); - strbuf_release(&buf); - } - } - - free(name); - - return(ret); -} - -struct ref_item { - char *name; - char *dest; - unsigned int kind, len; - struct commit *commit; -}; - -struct ref_list { - struct rev_info revs; - int index, alloc, maxwidth, verbose, abbrev; - struct ref_item *list; - struct commit_list *with_commit; - int kinds; -}; - -static char *resolve_symref(const char *src, const char *prefix) -{ - unsigned char sha1[20]; - int flag; - const char *dst, *cp; - - dst = resolve_ref(src, sha1, 0, &flag); - if (!(dst && (flag & REF_ISSYMREF))) - return NULL; - if (prefix && (cp = skip_prefix(dst, prefix))) - dst = cp; - return xstrdup(dst); -} - -static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) -{ - struct ref_list *ref_list = (struct ref_list*)(cb_data); - struct ref_item *newitem; - struct commit *commit; - int kind, i; - const char *prefix, *orig_refname = refname; - - static struct { - int kind; - const char *prefix; - int pfxlen; - } ref_kind[] = { - { REF_LOCAL_BRANCH, "refs/heads/", 11 }, - { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, - }; - - /* Detect kind */ - for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { - prefix = ref_kind[i].prefix; - if (strncmp(refname, prefix, ref_kind[i].pfxlen)) - continue; - kind = ref_kind[i].kind; - refname += ref_kind[i].pfxlen; - break; - } - if (ARRAY_SIZE(ref_kind) <= i) - return 0; - - /* Don't add types the caller doesn't want */ - if ((kind & ref_list->kinds) == 0) - return 0; - - commit = NULL; - if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { - commit = lookup_commit_reference_gently(sha1, 1); - if (!commit) - return error("branch '%s' does not point at a commit", refname); - - /* Filter with with_commit if specified */ - if (!is_descendant_of(commit, ref_list->with_commit)) - return 0; - - if (merge_filter != NO_FILTER) - add_pending_object(&ref_list->revs, - (struct object *)commit, refname); - } - - /* Resize buffer */ - if (ref_list->index >= ref_list->alloc) { - ref_list->alloc = alloc_nr(ref_list->alloc); - ref_list->list = xrealloc(ref_list->list, - ref_list->alloc * sizeof(struct ref_item)); - } - - /* Record the new item */ - newitem = &(ref_list->list[ref_list->index++]); - newitem->name = xstrdup(refname); - newitem->kind = kind; - newitem->commit = commit; - newitem->len = strlen(refname); - newitem->dest = resolve_symref(orig_refname, prefix); - /* adjust for "remotes/" */ - if (newitem->kind == REF_REMOTE_BRANCH && - ref_list->kinds != REF_REMOTE_BRANCH) - newitem->len += 8; - if (newitem->len > ref_list->maxwidth) - ref_list->maxwidth = newitem->len; - - return 0; -} - -static void free_ref_list(struct ref_list *ref_list) -{ - int i; - - for (i = 0; i < ref_list->index; i++) { - free(ref_list->list[i].name); - free(ref_list->list[i].dest); - } - free(ref_list->list); -} - -static int ref_cmp(const void *r1, const void *r2) -{ - struct ref_item *c1 = (struct ref_item *)(r1); - struct ref_item *c2 = (struct ref_item *)(r2); - - if (c1->kind != c2->kind) - return c1->kind - c2->kind; - return strcmp(c1->name, c2->name); -} - -static void fill_tracking_info(struct strbuf *stat, const char *branch_name, - int show_upstream_ref) -{ - int ours, theirs; - struct branch *branch = branch_get(branch_name); - - if (!stat_tracking_info(branch, &ours, &theirs)) { - if (branch && branch->merge && branch->merge[0]->dst && - show_upstream_ref) - strbuf_addf(stat, "[%s] ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); - return; - } - - strbuf_addch(stat, '['); - if (show_upstream_ref) - strbuf_addf(stat, "%s: ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); - if (!ours) - strbuf_addf(stat, "behind %d] ", theirs); - else if (!theirs) - strbuf_addf(stat, "ahead %d] ", ours); - else - strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); -} - -static int matches_merge_filter(struct commit *commit) -{ - int is_merged; - - if (merge_filter == NO_FILTER) - return 1; - - is_merged = !!(commit->object.flags & UNINTERESTING); - return (is_merged == (merge_filter == SHOW_MERGED)); -} - -static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current, char *prefix) -{ - char c; - int color; - struct commit *commit = item->commit; - struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - - if (!matches_merge_filter(commit)) - return; - - switch (item->kind) { - case REF_LOCAL_BRANCH: - color = BRANCH_COLOR_LOCAL; - break; - case REF_REMOTE_BRANCH: - color = BRANCH_COLOR_REMOTE; - break; - default: - color = BRANCH_COLOR_PLAIN; - break; - } - - c = ' '; - if (current) { - c = '*'; - color = BRANCH_COLOR_CURRENT; - } - - strbuf_addf(&name, "%s%s", prefix, item->name); - if (verbose) - strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), - maxwidth, name.buf, - branch_get_color(BRANCH_COLOR_RESET)); - else - strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), - name.buf, branch_get_color(BRANCH_COLOR_RESET)); - - if (item->dest) - strbuf_addf(&out, " -> %s", item->dest); - else if (verbose) { - struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; - const char *sub = " **** invalid ref ****"; - - commit = item->commit; - if (commit && !parse_commit(commit)) { - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &subject, &ctx); - sub = subject.buf; - } - - if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name, verbose > 1); - - strbuf_addf(&out, " %s %s%s", - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); - strbuf_release(&stat); - strbuf_release(&subject); - } - printf("%s\n", out.buf); - strbuf_release(&name); - strbuf_release(&out); -} - -static int calc_maxwidth(struct ref_list *refs) -{ - int i, w = 0; - for (i = 0; i < refs->index; i++) { - if (!matches_merge_filter(refs->list[i].commit)) - continue; - if (refs->list[i].len > w) - w = refs->list[i].len; - } - return w; -} - - -static void show_detached(struct ref_list *ref_list) -{ - struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1); - - if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { - struct ref_item item; - item.name = xstrdup("(no branch)"); - item.len = strlen(item.name); - item.kind = REF_LOCAL_BRANCH; - item.dest = NULL; - item.commit = head_commit; - if (item.len > ref_list->maxwidth) - ref_list->maxwidth = item.len; - print_ref_item(&item, ref_list->maxwidth, ref_list->verbose, ref_list->abbrev, 1, ""); - free(item.name); - } -} - -static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) -{ - int i; - struct ref_list ref_list; - - memset(&ref_list, 0, sizeof(ref_list)); - ref_list.kinds = kinds; - ref_list.verbose = verbose; - ref_list.abbrev = abbrev; - ref_list.with_commit = with_commit; - if (merge_filter != NO_FILTER) - init_revisions(&ref_list.revs, NULL); - for_each_rawref(append_ref, &ref_list); - if (merge_filter != NO_FILTER) { - struct commit *filter; - filter = lookup_commit_reference_gently(merge_filter_ref, 0); - filter->object.flags |= UNINTERESTING; - add_pending_object(&ref_list.revs, - (struct object *) filter, ""); - ref_list.revs.limited = 1; - prepare_revision_walk(&ref_list.revs); - if (verbose) - ref_list.maxwidth = calc_maxwidth(&ref_list); - } - - qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - - detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) - show_detached(&ref_list); - - for (i = 0; i < ref_list.index; i++) { - int current = !detached && - (ref_list.list[i].kind == REF_LOCAL_BRANCH) && - !strcmp(ref_list.list[i].name, head); - char *prefix = (kinds != REF_REMOTE_BRANCH && - ref_list.list[i].kind == REF_REMOTE_BRANCH) - ? "remotes/" : ""; - print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current, prefix); - } - - free_ref_list(&ref_list); -} - -static void rename_branch(const char *oldname, const char *newname, int force) -{ - struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; - unsigned char sha1[20]; - struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; - int recovery = 0; - - if (!oldname) - die("cannot rename the current branch while not on any."); - - if (strbuf_check_branch_ref(&oldref, oldname)) { - /* - * Bad name --- this could be an attempt to rename a - * ref that we used to allow to be created by accident. - */ - if (resolve_ref(oldref.buf, sha1, 1, NULL)) - recovery = 1; - else - die("Invalid branch name: '%s'", oldname); - } - - if (strbuf_check_branch_ref(&newref, newname)) - die("Invalid branch name: '%s'", newname); - - if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newref.buf + 11); - - strbuf_addf(&logmsg, "Branch: renamed %s to %s", - oldref.buf, newref.buf); - - if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) - die("Branch rename failed"); - strbuf_release(&logmsg); - - if (recovery) - warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); - - /* no need to pass logmsg here as HEAD didn't really move */ - if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) - die("Branch renamed to %s, but HEAD is not updated!", newname); - - strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); - strbuf_release(&oldref); - strbuf_addf(&newsection, "branch.%s", newref.buf + 11); - strbuf_release(&newref); - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) - die("Branch is renamed, but update of config-file failed"); - strbuf_release(&oldsection); - strbuf_release(&newsection); -} - -static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) -{ - merge_filter = ((opt->long_name[0] == 'n') - ? SHOW_NOT_MERGED - : SHOW_MERGED); - if (unset) - merge_filter = SHOW_NOT_MERGED; /* b/c for --no-merged */ - if (!arg) - arg = "HEAD"; - if (get_sha1(arg, merge_filter_ref)) - die("malformed object name %s", arg); - return 0; -} - -int cmd_branch(int argc, const char **argv, const char *prefix) -{ - int delete = 0, rename = 0, force_create = 0; - int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0; - enum branch_track track; - int kinds = REF_LOCAL_BRANCH; - struct commit_list *with_commit = NULL; - - struct option options[] = { - OPT_GROUP("Generic options"), - OPT__VERBOSE(&verbose), - OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", - 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_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", - REF_REMOTE_BRANCH), - { - OPTION_CALLBACK, 0, "contains", &with_commit, "commit", - "print only branches that contain the commit", - PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t)"HEAD", - }, - { - OPTION_CALLBACK, 0, "with", &with_commit, "commit", - "print only branches that contain the commit", - PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - parse_opt_with_commit, (intptr_t) "HEAD", - }, - OPT__ABBREV(&abbrev), - - OPT_GROUP("Specific git-branch actions:"), - OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches", - REF_REMOTE_BRANCH | REF_LOCAL_BRANCH), - OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1), - OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2), - OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), - OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), - OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"), - { - OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, - "commit", "print only not merged branches", - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - { - OPTION_CALLBACK, 0, "merged", &merge_filter_ref, - "commit", "print only merged branches", - PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, - opt_parse_merge_filter, (intptr_t) "HEAD", - }, - OPT_END(), - }; - - git_config(git_branch_config, NULL); - - if (branch_use_color == -1) - branch_use_color = git_use_color_default; - - track = git_branch_track; - - head = resolve_ref("HEAD", head_sha1, 0, NULL); - if (!head) - die("Failed to resolve HEAD as a valid ref."); - head = xstrdup(head); - if (!strcmp(head, "HEAD")) { - detached = 1; - } else { - if (prefixcmp(head, "refs/heads/")) - die("HEAD not found below refs/heads!"); - head += 11; - } - hashcpy(merge_filter_ref, head_sha1); - - argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, - 0); - if (!!delete + !!rename + !!force_create > 1) - usage_with_options(builtin_branch_usage, options); - - if (delete) - return delete_branches(argc, argv, delete > 1, kinds); - else if (argc == 0) - print_ref_list(kinds, detached, verbose, abbrev, with_commit); - else if (rename && (argc == 1)) - rename_branch(head, argv[0], rename > 1); - else if (rename && (argc == 2)) - rename_branch(argv[0], argv[1], rename > 1); - else if (argc <= 2) { - if (kinds != REF_LOCAL_BRANCH) - die("-a and -r options to 'git branch' do not make sense with a branch name"); - create_branch(head, argv[0], (argc == 2) ? argv[1] : head, - force_create, reflog, track); - } else - usage_with_options(builtin_branch_usage, options); - - return 0; -} diff --git a/builtin-bundle.c b/builtin-bundle.c deleted file mode 100644 index 2006cc5cd..000000000 --- a/builtin-bundle.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "bundle.h" - -/* - * Basic handler for bundle files to connect repositories via sneakernet. - * Invocation must include action. - * This function can create a bundle or provide information on an existing - * bundle supporting "fetch", "pull", and "ls-remote". - */ - -static const char builtin_bundle_usage[] = - "git bundle create \n" - " or: git bundle verify \n" - " or: git bundle list-heads [refname...]\n" - " or: git bundle unbundle [refname...]"; - -int cmd_bundle(int argc, const char **argv, const char *prefix) -{ - struct bundle_header header; - int nongit; - const char *cmd, *bundle_file; - int bundle_fd = -1; - char buffer[PATH_MAX]; - - if (argc < 3) - usage(builtin_bundle_usage); - - cmd = argv[1]; - bundle_file = argv[2]; - argc -= 2; - argv += 2; - - prefix = setup_git_directory_gently(&nongit); - if (prefix && bundle_file[0] != '/') { - snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); - bundle_file = buffer; - } - - memset(&header, 0, sizeof(header)); - if (strcmp(cmd, "create") && (bundle_fd = - read_bundle_header(bundle_file, &header)) < 0) - return 1; - - if (!strcmp(cmd, "verify")) { - close(bundle_fd); - if (verify_bundle(&header, 1)) - return 1; - fprintf(stderr, "%s is okay\n", bundle_file); - return 0; - } - if (!strcmp(cmd, "list-heads")) { - close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); - } - if (!strcmp(cmd, "create")) { - if (nongit) - die("Need a repository to create a bundle."); - return !!create_bundle(&header, bundle_file, argc, argv); - } else if (!strcmp(cmd, "unbundle")) { - if (nongit) - die("Need a repository to unbundle."); - return !!unbundle(&header, bundle_fd) || - list_bundle_refs(&header, argc, argv); - } else - usage(builtin_bundle_usage); -} diff --git a/builtin-cat-file.c b/builtin-cat-file.c deleted file mode 100644 index a933eaa04..000000000 --- a/builtin-cat-file.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "exec_cmd.h" -#include "tag.h" -#include "tree.h" -#include "builtin.h" -#include "parse-options.h" - -#define BATCH 1 -#define BATCH_CHECK 2 - -static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) -{ - /* the parser in tag.c is useless here. */ - const char *endp = buf + size; - const char *cp = buf; - - while (cp < endp) { - char c = *cp++; - if (c != '\n') - continue; - if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { - const char *tagger = cp; - - /* Found the tagger line. Copy out the contents - * of the buffer so far. - */ - write_or_die(1, buf, cp - buf); - - /* - * Do something intelligent, like pretty-printing - * the date. - */ - while (cp < endp) { - if (*cp++ == '\n') { - /* tagger to cp is a line - * that has ident and time. - */ - const char *sp = tagger; - char *ep; - unsigned long date; - long tz; - while (sp < cp && *sp != '>') - sp++; - if (sp == cp) { - /* give up */ - write_or_die(1, tagger, - cp - tagger); - break; - } - while (sp < cp && - !('0' <= *sp && *sp <= '9')) - sp++; - write_or_die(1, tagger, sp - tagger); - date = strtoul(sp, &ep, 10); - tz = strtol(ep, NULL, 10); - sp = show_date(date, tz, 0); - write_or_die(1, sp, strlen(sp)); - xwrite(1, "\n", 1); - break; - } - } - break; - } - if (cp < endp && *cp == '\n') - /* end of header */ - break; - } - /* At this point, we have copied out the header up to the end of - * the tagger line and cp points at one past \n. It could be the - * next header line after the tagger line, or it could be another - * \n that marks the end of the headers. We need to copy out the - * remainder as is. - */ - if (cp < endp) - write_or_die(1, cp, endp - cp); -} - -static int cat_one_file(int opt, const char *exp_type, const char *obj_name) -{ - unsigned char sha1[20]; - enum object_type type; - void *buf; - unsigned long size; - - if (get_sha1(obj_name, sha1)) - die("Not a valid object name %s", obj_name); - - buf = NULL; - switch (opt) { - case 't': - type = sha1_object_info(sha1, NULL); - if (type > 0) { - printf("%s\n", typename(type)); - return 0; - } - break; - - case 's': - type = sha1_object_info(sha1, &size); - if (type > 0) { - printf("%lu\n", size); - return 0; - } - break; - - case 'e': - return !has_sha1_file(sha1); - - case 'p': - type = sha1_object_info(sha1, NULL); - if (type < 0) - die("Not a valid object name %s", obj_name); - - /* custom pretty-print here */ - if (type == OBJ_TREE) { - const char *ls_args[3] = {"ls-tree", obj_name, NULL}; - return cmd_ls_tree(2, ls_args, NULL); - } - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - die("Cannot read object %s", obj_name); - if (type == OBJ_TAG) { - pprint_tag(sha1, buf, size); - return 0; - } - - /* otherwise just spit out the data */ - break; - case 0: - buf = read_object_with_reference(sha1, exp_type, &size, NULL); - break; - - default: - die("git cat-file: unknown option: %s", exp_type); - } - - if (!buf) - die("git cat-file %s: bad file", obj_name); - - write_or_die(1, buf, size); - return 0; -} - -static int batch_one_object(const char *obj_name, int print_contents) -{ - unsigned char sha1[20]; - enum object_type type = 0; - unsigned long size; - void *contents = contents; - - if (!obj_name) - return 1; - - if (get_sha1(obj_name, sha1)) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } - - if (print_contents == BATCH) - contents = read_sha1_file(sha1, &type, &size); - else - type = sha1_object_info(sha1, &size); - - if (type <= 0) { - printf("%s missing\n", obj_name); - fflush(stdout); - return 0; - } - - printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size); - fflush(stdout); - - if (print_contents == BATCH) { - write_or_die(1, contents, size); - printf("\n"); - fflush(stdout); - free(contents); - } - - return 0; -} - -static int batch_objects(int print_contents) -{ - struct strbuf buf = STRBUF_INIT; - - while (strbuf_getline(&buf, stdin, '\n') != EOF) { - int error = batch_one_object(buf.buf, print_contents); - if (error) - return error; - } - - return 0; -} - -static const char * const cat_file_usage[] = { - "git cat-file (-t|-s|-e|-p|) ", - "git cat-file (--batch|--batch-check) < ", - NULL -}; - -int cmd_cat_file(int argc, const char **argv, const char *prefix) -{ - int opt = 0, batch = 0; - const char *exp_type = NULL, *obj_name = NULL; - - const struct option options[] = { - OPT_GROUP(" can be one of: blob, tree, commit, tag"), - OPT_SET_INT('t', NULL, &opt, "show object type", 't'), - OPT_SET_INT('s', NULL, &opt, "show object size", 's'), - OPT_SET_INT('e', NULL, &opt, - "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 fed from the standard input", - BATCH), - OPT_SET_INT(0, "batch-check", &batch, - "show info about objects fed from the standard input", - BATCH_CHECK), - OPT_END() - }; - - git_config(git_default_config, NULL); - - if (argc != 3 && argc != 2) - usage_with_options(cat_file_usage, options); - - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); - - if (opt) { - if (argc == 1) - obj_name = argv[0]; - else - usage_with_options(cat_file_usage, options); - } - if (!opt && !batch) { - if (argc == 2) { - exp_type = argv[0]; - obj_name = argv[1]; - } else - usage_with_options(cat_file_usage, options); - } - if (batch && (opt || argc)) { - usage_with_options(cat_file_usage, options); - } - - if (batch) - return batch_objects(batch); - - return cat_one_file(opt, exp_type, obj_name); -} diff --git a/builtin-check-attr.c b/builtin-check-attr.c deleted file mode 100644 index 3016d29ca..000000000 --- a/builtin-check-attr.c +++ /dev/null @@ -1,123 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "attr.h" -#include "quote.h" -#include "parse-options.h" - -static int stdin_paths; -static const char * const check_attr_usage[] = { -"git check-attr attr... [--] pathname...", -"git check-attr --stdin attr... < ", -NULL -}; - -static int null_term_line; - -static const struct option check_attr_options[] = { - OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"), - OPT_BOOLEAN('z', NULL, &null_term_line, - "input paths are terminated by a null character"), - OPT_END() -}; - -static void check_attr(int cnt, struct git_attr_check *check, - const char** name, const char *file) -{ - int j; - if (git_checkattr(file, cnt, check)) - die("git_checkattr died"); - for (j = 0; j < cnt; j++) { - const char *value = check[j].value; - - if (ATTR_TRUE(value)) - value = "set"; - else if (ATTR_FALSE(value)) - value = "unset"; - else if (ATTR_UNSET(value)) - value = "unspecified"; - - quote_c_style(file, NULL, stdout, 0); - printf(": %s: %s\n", name[j], value); - } -} - -static void check_attr_stdin_paths(int cnt, struct git_attr_check *check, - const char** name) -{ - struct strbuf buf, nbuf; - int line_termination = null_term_line ? 0 : '\n'; - - strbuf_init(&buf, 0); - strbuf_init(&nbuf, 0); - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - check_attr(cnt, check, name, buf.buf); - maybe_flush_or_die(stdout, "attribute to stdout"); - } - strbuf_release(&buf); - strbuf_release(&nbuf); -} - -int cmd_check_attr(int argc, const char **argv, const char *prefix) -{ - struct git_attr_check *check; - int cnt, i, doubledash; - const char *errstr = NULL; - - argc = parse_options(argc, argv, prefix, check_attr_options, - check_attr_usage, PARSE_OPT_KEEP_DASHDASH); - if (!argc) - usage_with_options(check_attr_usage, check_attr_options); - - if (read_cache() < 0) { - die("invalid cache"); - } - - doubledash = -1; - for (i = 0; doubledash < 0 && i < argc; i++) { - if (!strcmp(argv[i], "--")) - doubledash = i; - } - - /* If there is no double dash, we handle only one attribute */ - if (doubledash < 0) { - cnt = 1; - doubledash = 0; - } else - cnt = doubledash; - doubledash++; - - if (cnt <= 0) - errstr = "No attribute specified"; - else if (stdin_paths && doubledash < argc) - errstr = "Can't specify files with --stdin"; - if (errstr) { - error("%s", errstr); - usage_with_options(check_attr_usage, check_attr_options); - } - - check = xcalloc(cnt, sizeof(*check)); - for (i = 0; i < cnt; i++) { - const char *name; - struct git_attr *a; - name = argv[i]; - a = git_attr(name); - if (!a) - return error("%s: not a valid attribute name", name); - check[i].attr = a; - } - - if (stdin_paths) - check_attr_stdin_paths(cnt, check, argv); - else { - for (i = doubledash; i < argc; i++) - check_attr(cnt, check, argv, argv[i]); - maybe_flush_or_die(stdout, "attribute to stdout"); - } - return 0; -} diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c deleted file mode 100644 index b106c65d8..000000000 --- a/builtin-check-ref-format.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * GIT - The information manager from hell - */ - -#include "cache.h" -#include "refs.h" -#include "builtin.h" -#include "strbuf.h" - -static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--print] \n" -" or: git check-ref-format --branch "; - -/* - * Replace each run of adjacent slashes in src with a single slash, - * and write the result to dst. - * - * This function is similar to normalize_path_copy(), but stripped down - * to meet check_ref_format's simpler needs. - */ -static void collapse_slashes(char *dst, const char *src) -{ - char ch; - char prev = '\0'; - - while ((ch = *src++) != '\0') { - if (prev == '/' && ch == prev) - continue; - - *dst++ = ch; - prev = ch; - } - *dst = '\0'; -} - -int cmd_check_ref_format(int argc, const char **argv, const char *prefix) -{ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_check_ref_format_usage); - - if (argc == 3 && !strcmp(argv[1], "--branch")) { - struct strbuf sb = STRBUF_INIT; - - if (strbuf_check_branch_ref(&sb, argv[2])) - die("'%s' is not a valid branch name", argv[2]); - printf("%s\n", sb.buf + 11); - exit(0); - } - if (argc == 3 && !strcmp(argv[1], "--print")) { - char *refname = xmalloc(strlen(argv[2]) + 1); - - if (check_ref_format(argv[2])) - exit(1); - collapse_slashes(refname, argv[2]); - printf("%s\n", refname); - exit(0); - } - if (argc != 2) - usage(builtin_check_ref_format_usage); - return !!check_ref_format(argv[1]); -} diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c deleted file mode 100644 index a7a5ee10f..000000000 --- a/builtin-checkout-index.c +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Check-out files from the "current cache directory" - * - * Copyright (C) 2005 Linus Torvalds - * - * Careful: order of argument flags does matter. For example, - * - * git checkout-index -a -f file.c - * - * Will first check out all files listed in the cache (but not - * overwrite any old ones), and then force-checkout "file.c" a - * second time (ie that one _will_ overwrite any old contents - * with the same filename). - * - * Also, just doing "git checkout-index" does nothing. You probably - * meant "git checkout-index -a". And if you want to force it, you - * want "git checkout-index -f -a". - * - * Intuitiveness is not the goal here. Repeatability is. The - * reason for the "no arguments means no work" thing is that - * from scripts you are supposed to be able to do things like - * - * find . -name '*.h' -print0 | xargs -0 git checkout-index -f -- - * - * or: - * - * find . -name '*.h' -print0 | git checkout-index -f -z --stdin - * - * which will force all existing *.h files to be replaced with - * their cached copies. If an empty command line implied "all", - * then this would force-refresh everything in the cache, which - * was not the point. - * - * Oh, and the "--" is just a good idea when you know the rest - * will be filenames. Just so that you wouldn't have a filename - * of "-a" causing problems (not possible in the above example, - * but get used to it in scripting!). - */ -#include "builtin.h" -#include "cache.h" -#include "quote.h" -#include "cache-tree.h" -#include "parse-options.h" - -#define CHECKOUT_ALL 4 -static int line_termination = '\n'; -static int checkout_stage; /* default to checkout stage0 */ -static int to_tempfile; -static char topath[4][PATH_MAX + 1]; - -static struct checkout state; - -static void write_tempfile_record(const char *name, int prefix_length) -{ - int i; - - if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); - } - } else - fputs(topath[checkout_stage], stdout); - - putchar('\t'); - write_name_quoted(name + prefix_length, stdout, line_termination); - - for (i = 0; i < 4; i++) { - topath[i][0] = 0; - } -} - -static int checkout_file(const char *name, int prefix_length) -{ - int namelen = strlen(name); - int pos = cache_name_pos(name, namelen); - int has_same_name = 0; - int did_checkout = 0; - int errs = 0; - - if (pos < 0) - pos = -pos - 1; - - while (pos < active_nr) { - struct cache_entry *ce = active_cache[pos]; - if (ce_namelen(ce) != namelen || - memcmp(ce->name, name, namelen)) - break; - has_same_name = 1; - pos++; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - did_checkout = 1; - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - } - - if (did_checkout) { - if (to_tempfile) - write_tempfile_record(name, prefix_length); - return errs > 0 ? -1 : 0; - } - - if (!state.quiet) { - fprintf(stderr, "git checkout-index: %s ", name); - if (!has_same_name) - fprintf(stderr, "is not in the cache"); - else if (checkout_stage) - fprintf(stderr, "does not exist at stage %d", - checkout_stage); - else - fprintf(stderr, "is unmerged"); - fputc('\n', stderr); - } - return -1; -} - -static void checkout_all(const char *prefix, int prefix_length) -{ - int i, errs = 0; - struct cache_entry *last_ce = NULL; - - for (i = 0; i < active_nr ; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce) != checkout_stage - && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) - continue; - if (prefix && *prefix && - (ce_namelen(ce) <= prefix_length || - memcmp(prefix, ce->name, prefix_length))) - continue; - if (last_ce && to_tempfile) { - if (ce_namelen(last_ce) != ce_namelen(ce) - || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name, prefix_length); - } - if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) - errs++; - last_ce = ce; - } - if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name, prefix_length); - if (errs) - /* we have already done our error reporting. - * exit with the same code as die(). - */ - exit(128); -} - -static const char * const builtin_checkout_index_usage[] = { - "git checkout-index [options] [--] ...", - NULL -}; - -static struct lock_file lock_file; - -static int option_parse_u(const struct option *opt, - const char *arg, int unset) -{ - int *newfd = opt->value; - - state.refresh_cache = 1; - if (*newfd < 0) - *newfd = hold_locked_index(&lock_file, 1); - return 0; -} - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - line_termination = '\n'; - else - line_termination = 0; - return 0; -} - -static int option_parse_prefix(const struct option *opt, - const char *arg, int unset) -{ - state.base_dir = arg; - state.base_dir_len = strlen(arg); - return 0; -} - -static int option_parse_stage(const struct option *opt, - const char *arg, int unset) -{ - if (!strcmp(arg, "all")) { - to_tempfile = 1; - checkout_stage = CHECKOUT_ALL; - } else { - int ch = arg[0]; - if ('1' <= ch && ch <= '3') - checkout_stage = arg[0] - '0'; - else - die("stage should be between 1 and 3 or all"); - } - return 0; -} - -int cmd_checkout_index(int argc, const char **argv, const char *prefix) -{ - int i; - int newfd = -1; - int all = 0; - int read_from_stdin = 0; - int prefix_length; - int force = 0, quiet = 0, not_new = 0; - struct option builtin_checkout_index_options[] = { - OPT_BOOLEAN('a', "all", &all, - "checks out all files in the index"), - OPT_BOOLEAN('f', "force", &force, - "forces overwrite of existing files"), - OPT__QUIET(&quiet), - OPT_BOOLEAN('n', "no-create", ¬_new, - "don't checkout new files"), - { OPTION_CALLBACK, 'u', "index", &newfd, NULL, - "update stat information in the index file", - PARSE_OPT_NOARG, option_parse_u }, - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_BOOLEAN(0, "stdin", &read_from_stdin, - "read list of paths from the standard input"), - OPT_BOOLEAN(0, "temp", &to_tempfile, - "write the content to temporary files"), - OPT_CALLBACK(0, "prefix", NULL, "string", - "when creating files, prepend ", - option_parse_prefix), - OPT_CALLBACK(0, "stage", NULL, NULL, - "copy out the files from named stage", - option_parse_stage), - OPT_END() - }; - - git_config(git_default_config, NULL); - state.base_dir = ""; - prefix_length = prefix ? strlen(prefix) : 0; - - if (read_cache() < 0) { - die("invalid cache"); - } - - argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, - builtin_checkout_index_usage, 0); - state.force = force; - state.quiet = quiet; - state.not_new = not_new; - - if (state.base_dir_len || to_tempfile) { - /* when --prefix is specified we do not - * want to update cache. - */ - if (state.refresh_cache) { - rollback_lock_file(&lock_file); - newfd = -1; - } - state.refresh_cache = 0; - } - - /* Check out named files first */ - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - const char *p; - - if (all) - die("git checkout-index: don't mix '--all' and explicit filenames"); - if (read_from_stdin) - die("git checkout-index: don't mix '--stdin' and explicit filenames"); - p = prefix_path(prefix, prefix_length, arg); - checkout_file(p, prefix_length); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); - } - - if (read_from_stdin) { - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; - - if (all) - die("git checkout-index: don't mix '--all' and '--stdin'"); - - while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; - if (line_termination && buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - p = prefix_path(prefix, prefix_length, buf.buf); - checkout_file(p, prefix_length); - if (p < buf.buf || p > buf.buf + buf.len) - free((char *)p); - } - strbuf_release(&nbuf); - strbuf_release(&buf); - } - - if (all) - checkout_all(prefix, prefix_length); - - if (0 <= newfd && - (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file))) - die("Unable to write new index file"); - return 0; -} diff --git a/builtin-checkout.c b/builtin-checkout.c deleted file mode 100644 index c5ab7835e..000000000 --- a/builtin-checkout.c +++ /dev/null @@ -1,853 +0,0 @@ -#include "cache.h" -#include "builtin.h" -#include "parse-options.h" -#include "refs.h" -#include "commit.h" -#include "tree.h" -#include "tree-walk.h" -#include "cache-tree.h" -#include "unpack-trees.h" -#include "dir.h" -#include "run-command.h" -#include "merge-recursive.h" -#include "branch.h" -#include "diff.h" -#include "revision.h" -#include "remote.h" -#include "blob.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "resolve-undo.h" - -static const char * const checkout_usage[] = { - "git checkout [options] ", - "git checkout [options] [] -- ...", - NULL, -}; - -struct checkout_opts { - int quiet; - int merge; - int force; - int writeout_stage; - int writeout_error; - - const char *new_branch; - int new_branch_log; - enum branch_track track; -}; - -static int post_checkout_hook(struct commit *old, struct commit *new, - int changed) -{ - return run_hook(NULL, "post-checkout", - sha1_to_hex(old ? old->object.sha1 : null_sha1), - sha1_to_hex(new ? new->object.sha1 : null_sha1), - changed ? "1" : "0", NULL); - /* "new" can be NULL when checking out from the index before - a commit exists. */ - -} - -static int update_some(const unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - int len; - struct cache_entry *ce; - - if (S_ISDIR(mode)) - return READ_TREE_RECURSIVE; - - len = baselen + strlen(pathname); - ce = xcalloc(1, cache_entry_size(len)); - hashcpy(ce->sha1, sha1); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, pathname, len - baselen); - ce->ce_flags = create_ce_flags(len, 0); - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); - return 0; -} - -static int read_tree_some(struct tree *tree, const char **pathspec) -{ - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); - - /* update the index with the given tree's info - * for all args, expanding wildcards, and exit - * with any non-zero return code. - */ - return 0; -} - -static int skip_same_name(struct cache_entry *ce, int pos) -{ - while (++pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) - ; /* skip */ - return pos; -} - -static int check_stage(int stage, struct cache_entry *ce, int pos) -{ - while (pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) { - if (ce_stage(active_cache[pos]) == stage) - return 0; - pos++; - } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); -} - -static int check_all_stages(struct cache_entry *ce, int pos) -{ - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, ce->name) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, ce->name) || - ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all three versions", - ce->name); - return 0; -} - -static int checkout_stage(int stage, struct cache_entry *ce, int pos, - struct checkout *state) -{ - while (pos < active_nr && - !strcmp(active_cache[pos]->name, ce->name)) { - if (ce_stage(active_cache[pos]) == stage) - return checkout_entry(active_cache[pos], state, NULL); - pos++; - } - return error("path '%s' does not have %s version", - ce->name, - (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]; - const char *path = ce->name; - mmfile_t ancestor, ours, theirs; - int status; - unsigned char sha1[20]; - mmbuffer_t result_buf; - - if (ce_stage(ce) != 1 || - active_nr <= pos + 2 || - strcmp(active_cache[pos+1]->name, path) || - ce_stage(active_cache[pos+1]) != 2 || - strcmp(active_cache[pos+2]->name, path) || - 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); - - status = ll_merge(&result_buf, path, &ancestor, - &ours, "ours", &theirs, "theirs", 0); - free(ancestor.ptr); - free(ours.ptr); - free(theirs.ptr); - if (status < 0 || !result_buf.ptr) { - free(result_buf.ptr); - return error("path '%s': cannot merge", path); - } - - /* - * NEEDSWORK: - * There is absolutely no reason to write this as a blob object - * and create a phony cache entry just to leak. This hack is - * primarily to get to the write_entry() machinery that massages - * the contents to work-tree format and writes out which only - * allows it for a cache entry. The code in write_entry() needs - * to be refactored to allow us to feed a - * instead of a cache entry. Such a refactoring would help - * merge_recursive as well (it also writes the merge result to the - * object database even when it may contain conflicts). - */ - if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, sha1)) - die("Unable to add merge result for '%s'", path); - ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), - sha1, - path, 2, 0); - if (!ce) - die("make_cache_entry failed for path '%s'", path); - status = checkout_entry(ce, state, NULL); - return status; -} - -static int checkout_paths(struct tree *source_tree, const char **pathspec, - struct checkout_opts *opts) -{ - int pos; - struct checkout state; - static char *ps_matched; - unsigned char rev[20]; - int flag; - struct commit *head; - int errs = 0; - int stage = opts->writeout_stage; - int merge = opts->merge; - int newfd; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - newfd = hold_locked_index(lock_file, 1); - if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); - - if (source_tree) - read_tree_some(source_tree, pathspec); - - for (pos = 0; pathspec[pos]; pos++) - ; - ps_matched = xcalloc(1, pos); - - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); - } - - if (report_path_error(ps_matched, pathspec, 0)) - return 1; - - /* "checkout -m path" to recreate conflicted state */ - if (opts->merge) - unmerge_cache(pathspec); - - /* Any unmerged paths? */ - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { - if (!ce_stage(ce)) - continue; - if (opts->force) { - warning("path '%s' is unmerged", ce->name); - } else if (stage) { - errs |= check_stage(stage, ce, pos); - } else if (opts->merge) { - errs |= check_all_stages(ce, pos); - } else { - errs = 1; - error("path '%s' is unmerged", ce->name); - } - pos = skip_same_name(ce, pos) - 1; - } - } - if (errs) - return 1; - - /* Now we are committed to check them out */ - memset(&state, 0, sizeof(state)); - state.force = 1; - state.refresh_cache = 1; - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { - if (!ce_stage(ce)) { - errs |= checkout_entry(ce, &state, NULL); - continue; - } - if (stage) - errs |= checkout_stage(stage, ce, pos, &state); - else if (merge) - errs |= checkout_merged(pos, &state); - pos = skip_same_name(ce, pos) - 1; - } - } - - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - resolve_ref("HEAD", rev, 0, &flag); - head = lookup_commit_reference_gently(rev, 1); - - errs |= post_checkout_hook(head, head, 0); - return errs; -} - -static void show_local_changes(struct object *head) -{ - struct rev_info rev; - /* I think we want full paths, even if we're in a subdirectory. */ - init_revisions(&rev, NULL); - rev.abbrev = 0; - rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; - if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); - add_pending_object(&rev, head, NULL); - run_diff_index(&rev, 0); -} - -static void describe_detached_head(char *msg, struct commit *commit) -{ - struct strbuf sb = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); - fprintf(stderr, "%s %s... %s\n", msg, - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); - strbuf_release(&sb); -} - -static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) -{ - struct unpack_trees_options opts; - struct tree_desc tree_desc; - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = -1; - opts.update = worktree; - opts.skip_unmerged = !worktree; - opts.reset = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = !o->quiet; - opts.src_index = &the_index; - opts.dst_index = &the_index; - parse_tree(tree); - init_tree_desc(&tree_desc, tree->buffer, tree->size); - switch (unpack_trees(1, &tree_desc, &opts)) { - case -2: - o->writeout_error = 1; - /* - * We return 0 nevertheless, as the index is all right - * and more importantly we have made best efforts to - * update paths in the work tree, and we cannot revert - * them. - */ - case 0: - return 0; - default: - return 128; - } -} - -struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ - struct commit *commit; /* The named commit */ -}; - -static void setup_branch_path(struct branch_info *branch) -{ - struct strbuf buf = STRBUF_INIT; - - strbuf_branchname(&buf, branch->name); - if (strcmp(buf.buf, branch->name)) - branch->name = xstrdup(buf.buf); - strbuf_splice(&buf, 0, 0, "refs/heads/", 11); - branch->path = strbuf_detach(&buf, NULL); -} - -static int merge_working_tree(struct checkout_opts *opts, - struct branch_info *old, struct branch_info *new) -{ - int ret; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - int newfd = hold_locked_index(lock_file, 1); - - if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); - - resolve_undo_clear(); - if (opts->force) { - ret = reset_tree(new->commit->tree, opts, 1); - if (ret) - return ret; - } else { - struct tree_desc trees[2]; - struct tree *tree; - struct unpack_trees_options topts; - - memset(&topts, 0, sizeof(topts)); - topts.head_idx = -1; - topts.src_index = &the_index; - topts.dst_index = &the_index; - - topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; - - refresh_cache(REFRESH_QUIET); - - if (unmerged_cache()) { - error("you need to resolve your current index first"); - return 1; - } - - /* 2-way merge to the new branch */ - topts.initial_checkout = is_cache_unborn(); - topts.update = 1; - topts.merge = 1; - topts.gently = opts->merge && old->commit; - topts.verbose_update = !opts->quiet; - topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - topts.dir->exclude_per_dir = ".gitignore"; - tree = parse_tree_indirect(old->commit ? - old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); - init_tree_desc(&trees[0], tree->buffer, tree->size); - tree = parse_tree_indirect(new->commit->object.sha1); - init_tree_desc(&trees[1], tree->buffer, tree->size); - - ret = unpack_trees(2, trees, &topts); - if (ret == -1) { - /* - * Unpack couldn't do a trivial merge; either - * give up or do a real merge, depending on - * whether the merge flag was used. - */ - struct tree *result; - struct tree *work; - struct merge_options o; - if (!opts->merge) - return 1; - - /* - * Without old->commit, the below is the same as - * the two-tree unpack we already tried and failed. - */ - if (!old->commit) - return 1; - - /* Do more real merge */ - - /* - * We update the index fully, then write the - * tree from the index, then merge the new - * branch with the current tree, with the old - * branch as the base. Then we reset the index - * (but not the working tree) to the new - * branch, leaving the working tree as the - * merged version, but skipping unmerged - * entries in the index. - */ - - add_files_to_cache(NULL, NULL, 0); - init_merge_options(&o); - o.verbosity = 0; - work = write_tree_from_memory(&o); - - ret = reset_tree(new->commit->tree, opts, 1); - if (ret) - return ret; - o.branch1 = new->name; - o.branch2 = "local"; - merge_trees(&o, new->commit->tree, work, - old->commit->tree, &result); - ret = reset_tree(new->commit->tree, opts, 0); - if (ret) - return ret; - } - } - - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - if (!opts->force && !opts->quiet) - show_local_changes(&new->commit->object); - - return 0; -} - -static void report_tracking(struct branch_info *new) -{ - struct strbuf sb = STRBUF_INIT; - struct branch *branch = branch_get(new->name); - - if (!format_tracking_info(branch, &sb)) - return; - fputs(sb.buf, stdout); - 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) -{ - struct strbuf msg = STRBUF_INIT; - const char *old_desc; - if (opts->new_branch) { - create_branch(old->name, opts->new_branch, new->name, 0, - opts->new_branch_log, opts->track); - new->name = opts->new_branch; - setup_branch_path(new); - } - - old_desc = old->name; - if (!old_desc && old->commit) - old_desc = sha1_to_hex(old->commit->object.sha1); - strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); - - if (new->path) { - create_symref("HEAD", new->path, msg.buf); - if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on '%s'\n", - new->name); - else - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->new_branch ? " a new" : "", - new->name); - } - } else if (strcmp(new->name, "HEAD")) { - update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); - if (!opts->quiet) { - if (old->path && advice_detached_head) - detach_advice(old->path, new->name); - describe_detached_head("HEAD is now at", new->commit); - } - } - remove_branch_state(); - strbuf_release(&msg); - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) - report_tracking(new); -} - -static int switch_branches(struct checkout_opts *opts, struct branch_info *new) -{ - int ret = 0; - struct branch_info old; - unsigned char rev[20]; - int flag; - memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); - old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) - old.path = NULL; - - if (old.path && !prefixcmp(old.path, "refs/heads/")) - old.name = old.path + strlen("refs/heads/"); - - if (!new->name) { - new->name = "HEAD"; - new->commit = old.commit; - if (!new->commit) - die("You are on a branch yet to be born"); - parse_commit(new->commit); - } - - ret = merge_working_tree(opts, &old, new); - if (ret) - return ret; - - /* - * If we were on a detached HEAD, but have now moved to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); - - update_refs_for_switch(opts, &old, new); - - ret = post_checkout_hook(old.commit, new->commit, 1); - return ret || opts->writeout_error; -} - -static int git_checkout_config(const char *var, const char *value, void *cb) -{ - return git_xmerge_config(var, value, cb); -} - -static int interactive_checkout(const char *revision, const char **pathspec, - struct checkout_opts *opts) -{ - return run_add_interactive(revision, "--patch=checkout", pathspec); -} - -struct tracking_name_data { - const char *name; - char *remote; - int unique; -}; - -static int check_tracking_name(const char *refname, const unsigned char *sha1, - int flags, void *cb_data) -{ - struct tracking_name_data *cb = cb_data; - const char *slash; - - if (prefixcmp(refname, "refs/remotes/")) - return 0; - slash = strchr(refname + 13, '/'); - if (!slash || strcmp(slash + 1, cb->name)) - return 0; - if (cb->remote) { - cb->unique = 0; - return 0; - } - cb->remote = xstrdup(refname); - return 0; -} - -static const char *unique_tracking_name(const char *name) -{ - struct tracking_name_data cb_data = { name, NULL, 1 }; - for_each_ref(check_tracking_name, &cb_data); - if (cb_data.unique) - return cb_data.remote; - free(cb_data.remote); - return NULL; -} - -int cmd_checkout(int argc, const char **argv, const char *prefix) -{ - struct checkout_opts opts; - unsigned char rev[20]; - const char *arg; - struct branch_info new; - struct tree *source_tree = NULL; - char *conflict_style = NULL; - int patch_mode = 0; - int dwim_new_local_branch = 1; - struct option options[] = { - OPT__QUIET(&opts.quiet), - OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), - OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_SET_INT('t', "track", &opts.track, "track", - BRANCH_TRACK_EXPLICIT), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", - 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", - 3), - OPT_BOOLEAN('f', "force", &opts.force, "force"), - OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), - OPT_STRING(0, "conflict", &conflict_style, "style", - "conflict style (merge or diff3)"), - OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), - { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL, - "second guess 'git checkout no-such-branch'", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_END(), - }; - int has_dash_dash; - - memset(&opts, 0, sizeof(opts)); - memset(&new, 0, sizeof(new)); - - git_config(git_checkout_config, NULL); - - opts.track = BRANCH_TRACK_UNSPECIFIED; - - argc = parse_options(argc, argv, prefix, options, checkout_usage, - PARSE_OPT_KEEP_DASHDASH); - - if (patch_mode && (opts.track > 0 || opts.new_branch - || opts.new_branch_log || opts.merge || opts.force)) - die ("--patch is incompatible with all other options"); - - /* --track without -b should DWIM */ - if (0 < opts.track && !opts.new_branch) { - const char *argv0 = argv[0]; - if (!argc || !strcmp(argv0, "--")) - die ("--track needs a branch name"); - if (!prefixcmp(argv0, "refs/")) - argv0 += 5; - if (!prefixcmp(argv0, "remotes/")) - argv0 += 8; - argv0 = strchr(argv0, '/'); - if (!argv0 || !argv0[1]) - die ("Missing branch name; try -b"); - opts.new_branch = argv0 + 1; - } - - if (conflict_style) { - opts.merge = 1; /* implied */ - git_xmerge_config("merge.conflictstyle", conflict_style, NULL); - } - - if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); - - /* - * case 1: git checkout -- [] - * - * must be a valid tree, everything after the '--' must be - * a path. - * - * case 2: git checkout -- [] - * - * everything after the '--' must be paths. - * - * case 3: git checkout [] - * - * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. - * - * With no paths, if is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * in one and only one remote, then this is a short-hand - * to fork local from that remote tracking branch. - * - * Otherwise shall not be ambiguous. - * - If it's *only* a reference, treat it like case (1). - * - If it's only a path, treat it like case (2). - * - else: fail. - * - */ - if (argc) { - if (!strcmp(argv[0], "--")) { /* case (2) */ - argv++; - argc--; - goto no_reference; - } - - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); - - if (!strcmp(arg, "-")) - arg = "@{-1}"; - - if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die("invalid reference: %s", arg); - if (!patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch && - !check_filename(NULL, arg) && - argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) - goto no_reference; - opts.new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } - else - goto no_reference; - } - - /* we can't end up being in (2) anymore, eat the argument */ - argv++; - argc--; - - new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); - - if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && - resolve_ref(new.path, rev, 1, NULL)) - ; - else - new.path = NULL; - parse_commit(new.commit); - source_tree = new.commit->tree; - } else - source_tree = parse_tree_indirect(rev); - - if (!source_tree) /* case (1): want a tree */ - die("reference is not a tree: %s", arg); - if (!has_dash_dash) {/* case (3 -> 1) */ - /* - * Do not complain the most common case - * git checkout branch - * even if there happen to be a file called 'branch'; - * it would be extremely annoying. - */ - if (argc) - verify_non_filename(NULL, arg); - } - else { - argv++; - argc--; - } - } - -no_reference: - - if (opts.track == BRANCH_TRACK_UNSPECIFIED) - opts.track = git_branch_track; - - if (argc) { - const char **pathspec = get_pathspec(prefix, argv); - - if (!pathspec) - die("invalid path specification"); - - if (patch_mode) - return interactive_checkout(new.name, pathspec, &opts); - - /* Checkout paths */ - if (opts.new_branch) { - if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); - } else { - die("git checkout: updating paths is incompatible with switching branches."); - } - } - - if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); - - return checkout_paths(source_tree, pathspec, &opts); - } - - if (patch_mode) - return interactive_checkout(new.name, NULL, &opts); - - if (opts.new_branch) { - struct strbuf buf = STRBUF_INIT; - if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die("git checkout: we do not like '%s' as a branch name.", - opts.new_branch); - if (!get_sha1(buf.buf, rev)) - die("git checkout: branch %s already exists", opts.new_branch); - strbuf_release(&buf); - } - - if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); - } - if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); - - return switch_branches(&opts, &new); -} diff --git a/builtin-clean.c b/builtin-clean.c deleted file mode 100644 index fac64e6cd..000000000 --- a/builtin-clean.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * "git clean" builtin command - * - * Copyright (C) 2007 Shawn Bohrer - * - * Based on git-clean.sh by Pavel Roskin - */ - -#include "builtin.h" -#include "cache.h" -#include "dir.h" -#include "parse-options.h" -#include "quote.h" - -static int force = -1; /* unset */ - -static const char *const builtin_clean_usage[] = { - "git clean [-d] [-f] [-n] [-q] [-x | -X] [--] ...", - NULL -}; - -static int git_clean_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "clean.requireforce")) - force = !git_config_bool(var, value); - return git_default_config(var, value, cb); -} - -int cmd_clean(int argc, const char **argv, const char *prefix) -{ - int i; - int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; - int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; - struct strbuf directory = STRBUF_INIT; - struct dir_struct dir; - static const char **pathspec; - struct strbuf buf = STRBUF_INIT; - const char *qname; - char *seen = NULL; - struct option options[] = { - OPT__QUIET(&quiet), - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force"), - OPT_BOOLEAN('d', NULL, &remove_directories, - "remove whole directories"), - OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), - OPT_BOOLEAN('X', NULL, &ignored_only, - "remove only ignored files"), - OPT_END() - }; - - git_config(git_clean_config, NULL); - if (force < 0) - force = 0; - else - config_set = 1; - - argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, - 0); - - memset(&dir, 0, sizeof(dir)); - if (ignored_only) - dir.flags |= DIR_SHOW_IGNORED; - - if (ignored && ignored_only) - die("-x and -X cannot be used together"); - - if (!show_only && !force) - die("clean.requireForce %s to true and neither -n nor -f given; " - "refusing to clean", config_set ? "set" : "defaults"); - - if (force > 1) - rm_flags = 0; - - dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; - - if (read_cache() < 0) - die("index file corrupt"); - - if (!ignored) - setup_standard_excludes(&dir); - - pathspec = get_pathspec(prefix, argv); - - fill_directory(&dir, pathspec); - - if (pathspec) - seen = xmalloc(argc > 0 ? argc : 1); - - for (i = 0; i < dir.nr; i++) { - struct dir_entry *ent = dir.entries[i]; - int len, pos; - int matches = 0; - struct cache_entry *ce; - struct stat st; - - /* - * Remove the '/' at the end that directory - * walking adds for directory entries. - */ - len = ent->len; - if (len && ent->name[len-1] == '/') - len--; - pos = cache_name_pos(ent->name, len); - if (0 <= pos) - continue; /* exact match */ - pos = -pos - 1; - if (pos < active_nr) { - ce = active_cache[pos]; - if (ce_namelen(ce) == len && - !memcmp(ce->name, ent->name, len)) - continue; /* Yup, this one exists unmerged */ - } - - /* - * we might have removed this as part of earlier - * recursive directory removal, so lstat() here could - * fail with ENOENT. - */ - if (lstat(ent->name, &st)) - continue; - - if (pathspec) { - memset(seen, 0, argc > 0 ? argc : 1); - matches = match_pathspec(pathspec, ent->name, len, - baselen, seen); - } - - if (S_ISDIR(st.st_mode)) { - strbuf_addstr(&directory, ent->name); - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || - (matches == MATCHED_EXACTLY))) { - printf("Would remove %s\n", qname); - } else if (remove_directories || - (matches == MATCHED_EXACTLY)) { - if (!quiet) - printf("Removing %s\n", qname); - if (remove_dir_recursively(&directory, - rm_flags) != 0) { - warning("failed to remove '%s'", qname); - errors++; - } - } else if (show_only) { - printf("Would not remove %s\n", qname); - } else { - printf("Not removing %s\n", qname); - } - strbuf_reset(&directory); - } else { - if (pathspec && !matches) - continue; - qname = quote_path_relative(ent->name, -1, &buf, prefix); - if (show_only) { - printf("Would remove %s\n", qname); - continue; - } else if (!quiet) { - printf("Removing %s\n", qname); - } - if (unlink(ent->name) != 0) { - warning("failed to remove '%s'", qname); - errors++; - } - } - } - free(seen); - - strbuf_release(&directory); - return (errors != 0); -} diff --git a/builtin-clone.c b/builtin-clone.c deleted file mode 100644 index 58bacbd55..000000000 --- a/builtin-clone.c +++ /dev/null @@ -1,671 +0,0 @@ -/* - * Builtin "git clone" - * - * Copyright (c) 2007 Kristian Høgsberg , - * 2008 Daniel Barkalow - * Based on git-commit.sh by Junio C Hamano and Linus Torvalds - * - * Clone a repository into a different directory that does not yet exist. - */ - -#include "cache.h" -#include "parse-options.h" -#include "fetch-pack.h" -#include "refs.h" -#include "tree.h" -#include "tree-walk.h" -#include "unpack-trees.h" -#include "transport.h" -#include "strbuf.h" -#include "dir.h" -#include "pack-refs.h" -#include "sigchain.h" -#include "branch.h" -#include "remote.h" -#include "run-command.h" - -/* - * Overall FIXMEs: - * - respect DB_ENVIRONMENT for .git/objects. - * - * Implementation notes: - * - dropping use-separate-remote and no-separate-remote compatibility - * - */ -static const char * const builtin_clone_usage[] = { - "git clone [options] [--] []", - NULL -}; - -static int option_quiet, option_no_checkout, option_bare, option_mirror; -static int option_local, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_reference, *option_depth; -static char *option_origin = NULL; -static char *option_branch = NULL; -static char *option_upload_pack = "git-upload-pack"; -static int option_verbose; -static int option_progress; - -static struct option builtin_clone_options[] = { - OPT__QUIET(&option_quiet), - OPT__VERBOSE(&option_verbose), - OPT_BOOLEAN(0, "progress", &option_progress, - "force progress reporting"), - OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, - "don't create a checkout"), - OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), - { OPTION_BOOLEAN, 0, "naked", &option_bare, NULL, - "create a bare repository", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - OPT_BOOLEAN(0, "mirror", &option_mirror, - "create a mirror repository (implies bare)"), - OPT_BOOLEAN('l', "local", &option_local, - "to clone from a local repository"), - OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks, - "don't use local hardlinks, always copy"), - OPT_BOOLEAN('s', "shared", &option_shared, - "setup as shared repository"), - OPT_BOOLEAN(0, "recursive", &option_recursive, - "initialize submodules in the clone"), - OPT_STRING(0, "template", &option_template, "path", - "path the template repository"), - OPT_STRING(0, "reference", &option_reference, "repo", - "reference repository"), - OPT_STRING('o', "origin", &option_origin, "branch", - "use instead of 'origin' to track upstream"), - OPT_STRING('b', "branch", &option_branch, "branch", - "checkout instead of the remote's HEAD"), - OPT_STRING('u', "upload-pack", &option_upload_pack, "path", - "path to git-upload-pack on the remote"), - OPT_STRING(0, "depth", &option_depth, "depth", - "create a shallow clone of that depth"), - - OPT_END() -}; - -static const char *argv_submodule[] = { - "submodule", "update", "--init", "--recursive", NULL -}; - -static char *get_repo_path(const char *repo, int *is_bundle) -{ - static char *suffix[] = { "/.git", ".git", "" }; - static char *bundle_suffix[] = { ".bundle", "" }; - struct stat st; - int i; - - for (i = 0; i < ARRAY_SIZE(suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { - *is_bundle = 0; - return xstrdup(make_nonrelative_path(path)); - } - } - - for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) { - const char *path; - path = mkpath("%s%s", repo, bundle_suffix[i]); - if (!stat(path, &st) && S_ISREG(st.st_mode)) { - *is_bundle = 1; - return xstrdup(make_nonrelative_path(path)); - } - } - - return NULL; -} - -static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) -{ - const char *end = repo + strlen(repo), *start; - char *dir; - - /* - * Strip trailing spaces, slashes and /.git - */ - while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) - end--; - if (end - repo > 5 && is_dir_sep(end[-5]) && - !strncmp(end - 4, ".git", 4)) { - end -= 5; - while (repo < end && is_dir_sep(end[-1])) - end--; - } - - /* - * Find last component, but be prepared that repo could have - * the form "remote.example.com:foo.git", i.e. no slash - * in the directory part. - */ - start = end; - while (repo < start && !is_dir_sep(start[-1]) && start[-1] != ':') - start--; - - /* - * Strip .{bundle,git}. - */ - if (is_bundle) { - if (end - start > 7 && !strncmp(end - 7, ".bundle", 7)) - end -= 7; - } else { - if (end - start > 4 && !strncmp(end - 4, ".git", 4)) - end -= 4; - } - - if (is_bare) { - struct strbuf result = STRBUF_INIT; - strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - dir = strbuf_detach(&result, NULL); - } else - dir = xstrndup(start, end - start); - /* - * Replace sequences of 'control' characters and whitespace - * with one ascii space, remove leading and trailing spaces. - */ - if (*dir) { - char *out = dir; - int prev_space = 1 /* strip leading whitespace */; - for (end = dir; *end; ++end) { - char ch = *end; - if ((unsigned char)ch < '\x20') - ch = '\x20'; - if (isspace(ch)) { - if (prev_space) - continue; - prev_space = 1; - } else - prev_space = 0; - *out++ = ch; - } - *out = '\0'; - if (out > dir && prev_space) - out[-1] = '\0'; - } - return dir; -} - -static void strip_trailing_slashes(char *dir) -{ - char *end = dir + strlen(dir); - - while (dir < end - 1 && is_dir_sep(end[-1])) - end--; - *end = '\0'; -} - -static void setup_reference(const char *repo) -{ - const char *ref_git; - char *ref_git_copy; - - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - ref_git = make_absolute_path(option_reference); - - if (is_directory(mkpath("%s/.git/objects", ref_git))) - ref_git = mkpath("%s/.git", ref_git); - else if (!is_directory(mkpath("%s/objects", ref_git))) - die("reference repository '%s' is not a local directory.", - option_reference); - - ref_git_copy = xstrdup(ref_git); - - add_to_alternates_file(ref_git_copy); - - remote = remote_get(ref_git_copy); - transport = transport_get(remote, ref_git_copy); - for (extra = transport_get_remote_refs(transport); extra; - extra = extra->next) - add_extra_ref(extra->name, extra->old_sha1, 0); - - transport_disconnect(transport); - - free(ref_git_copy); -} - -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) -{ - struct dirent *de; - struct stat buf; - int src_len, dest_len; - DIR *dir; - - dir = opendir(src->buf); - if (!dir) - die_errno("failed to open '%s'", src->buf); - - if (mkdir(dest->buf, 0777)) { - if (errno != EEXIST) - die_errno("failed to create directory '%s'", dest->buf); - else if (stat(dest->buf, &buf)) - die_errno("failed to stat '%s'", dest->buf); - else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory", dest->buf); - } - - strbuf_addch(src, '/'); - src_len = src->len; - strbuf_addch(dest, '/'); - dest_len = dest->len; - - while ((de = readdir(dir)) != NULL) { - strbuf_setlen(src, src_len); - strbuf_addstr(src, de->d_name); - strbuf_setlen(dest, dest_len); - strbuf_addstr(dest, de->d_name); - if (stat(src->buf, &buf)) { - warning ("failed to stat %s\n", src->buf); - continue; - } - if (S_ISDIR(buf.st_mode)) { - if (de->d_name[0] != '.') - copy_or_link_directory(src, dest); - continue; - } - - if (unlink(dest->buf) && errno != ENOENT) - die_errno("failed to unlink '%s'", dest->buf); - if (!option_no_hardlinks) { - if (!link(src->buf, dest->buf)) - continue; - if (option_local) - die_errno("failed to create link '%s'", dest->buf); - option_no_hardlinks = 1; - } - if (copy_file_with_time(dest->buf, src->buf, 0666)) - die_errno("failed to copy file to '%s'", dest->buf); - } - closedir(dir); -} - -static const struct ref *clone_local(const char *src_repo, - const char *dest_repo) -{ - const struct ref *ret; - struct strbuf src = STRBUF_INIT; - struct strbuf dest = STRBUF_INIT; - struct remote *remote; - struct transport *transport; - - if (option_shared) - add_to_alternates_file(src_repo); - else { - strbuf_addf(&src, "%s/objects", src_repo); - strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); - strbuf_release(&src); - strbuf_release(&dest); - } - - remote = remote_get(src_repo); - transport = transport_get(remote, src_repo); - ret = transport_get_remote_refs(transport); - transport_disconnect(transport); - return ret; -} - -static const char *junk_work_tree; -static const char *junk_git_dir; -static pid_t junk_pid; - -static void remove_junk(void) -{ - struct strbuf sb = STRBUF_INIT; - if (getpid() != junk_pid) - return; - if (junk_git_dir) { - strbuf_addstr(&sb, junk_git_dir); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } - if (junk_work_tree) { - strbuf_addstr(&sb, junk_work_tree); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); - } -} - -static void remove_junk_on_signal(int signo) -{ - remove_junk(); - sigchain_pop(signo); - raise(signo); -} - -static struct ref *wanted_peer_refs(const struct ref *refs, - struct refspec *refspec) -{ - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; - - get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror) - get_fetch_map(refs, tag_refspec, &tail, 0); - - return local_refs; -} - -static void write_remote_refs(const struct ref *local_refs) -{ - const struct ref *r; - - for (r = local_refs; r; r = r->next) - add_extra_ref(r->peer_ref->name, r->old_sha1, 0); - - pack_refs(PACK_REFS_ALL); - clear_extra_refs(); -} - -int cmd_clone(int argc, const char **argv, const char *prefix) -{ - int is_bundle = 0; - struct stat buf; - const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; - int dest_exists; - const struct ref *refs, *remote_head; - const struct ref *remote_head_points_at; - const struct ref *our_head_points_at; - struct ref *mapped_refs; - struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; - struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; - struct transport *transport = NULL; - char *src_ref_prefix = "refs/heads/"; - int err = 0; - - struct refspec *refspec; - const char *fetch_pattern; - - junk_pid = getpid(); - - argc = parse_options(argc, argv, prefix, builtin_clone_options, - builtin_clone_usage, 0); - - if (argc > 2) - usage_msg_opt("Too many arguments.", - builtin_clone_usage, builtin_clone_options); - - if (argc == 0) - usage_msg_opt("You must specify a repository to clone.", - builtin_clone_usage, builtin_clone_options); - - if (option_mirror) - option_bare = 1; - - if (option_bare) { - if (option_origin) - die("--bare and --origin %s options are incompatible.", - option_origin); - option_no_checkout = 1; - } - - if (!option_origin) - option_origin = "origin"; - - repo_name = argv[0]; - - path = get_repo_path(repo_name, &is_bundle); - if (path) - repo = xstrdup(make_nonrelative_path(repo_name)); - else if (!strchr(repo_name, ':')) - repo = xstrdup(make_absolute_path(repo_name)); - else - repo = repo_name; - - if (argc == 2) - dir = xstrdup(argv[1]); - else - dir = guess_dir_name(repo_name, is_bundle, option_bare); - strip_trailing_slashes(dir); - - dest_exists = !stat(dir, &buf); - if (dest_exists && !is_empty_dir(dir)) - die("destination path '%s' already exists and is not " - "an empty directory.", dir); - - strbuf_addf(&reflog_msg, "clone: from %s", repo); - - if (option_bare) - work_tree = NULL; - else { - work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && !stat(work_tree, &buf)) - die("working tree '%s' already exists.", work_tree); - } - - if (option_bare || work_tree) - git_dir = xstrdup(dir); - else { - work_tree = dir; - git_dir = xstrdup(mkpath("%s/.git", dir)); - } - - if (!option_bare) { - junk_work_tree = work_tree; - if (safe_create_leading_directories_const(work_tree) < 0) - die_errno("could not create leading directories of '%s'", - work_tree); - if (!dest_exists && mkdir(work_tree, 0755)) - die_errno("could not create work tree dir '%s'.", - work_tree); - set_git_work_tree(work_tree); - } - junk_git_dir = git_dir; - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); - - setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); - - if (safe_create_leading_directories_const(git_dir) < 0) - die("could not create leading directories of '%s'", git_dir); - set_git_dir(make_absolute_path(git_dir)); - - init_db(option_template, option_quiet ? INIT_DB_QUIET : 0); - - /* - * At this point, the config exists, so we do not need the - * environment variable. We actually need to unset it, too, to - * re-enable parsing of the global configs. - */ - unsetenv(CONFIG_ENVIRONMENT); - - if (option_reference) - setup_reference(git_dir); - - git_config(git_default_config, NULL); - - if (option_bare) { - if (option_mirror) - src_ref_prefix = "refs/"; - strbuf_addstr(&branch_top, src_ref_prefix); - - git_config_set("core.bare", "true"); - } else { - strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); - } - - strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); - - if (option_mirror || !option_bare) { - /* Configure the remote */ - strbuf_addf(&key, "remote.%s.fetch", option_origin); - git_config_set_multivar(key.buf, value.buf, "^$", 0); - strbuf_reset(&key); - - if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); - git_config_set(key.buf, "true"); - strbuf_reset(&key); - } - - strbuf_addf(&key, "remote.%s.url", option_origin); - git_config_set(key.buf, repo); - strbuf_reset(&key); - } - - fetch_pattern = value.buf; - refspec = parse_fetch_refspec(1, &fetch_pattern); - - strbuf_reset(&value); - - if (path && !is_bundle) { - refs = clone_local(path, git_dir); - mapped_refs = wanted_peer_refs(refs, refspec); - } else { - struct remote *remote = remote_get(argv[0]); - transport = transport_get(remote, remote->url[0]); - - if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to clone %s", transport->url); - - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); - - if (option_depth) - transport_set_option(transport, TRANS_OPT_DEPTH, - option_depth); - - if (option_quiet) - transport->verbose = -1; - else if (option_verbose) - transport->verbose = 1; - - if (option_progress) - transport->progress = 1; - - if (option_upload_pack) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, - option_upload_pack); - - refs = transport_get_remote_refs(transport); - if (refs) { - mapped_refs = wanted_peer_refs(refs, refspec); - transport_fetch_refs(transport, mapped_refs); - } - } - - if (refs) { - clear_extra_refs(); - - write_remote_refs(mapped_refs); - - remote_head = find_ref_by_name(refs, "HEAD"); - remote_head_points_at = - guess_remote_head(remote_head, mapped_refs, 0); - - if (option_branch) { - struct strbuf head = STRBUF_INIT; - strbuf_addstr(&head, src_ref_prefix); - strbuf_addstr(&head, option_branch); - our_head_points_at = - find_ref_by_name(mapped_refs, head.buf); - strbuf_release(&head); - - if (!our_head_points_at) { - warning("Remote branch %s not found in " - "upstream %s, using HEAD instead", - option_branch, option_origin); - our_head_points_at = remote_head_points_at; - } - } - else - our_head_points_at = remote_head_points_at; - } - else { - warning("You appear to have cloned an empty repository."); - our_head_points_at = NULL; - remote_head_points_at = NULL; - remote_head = NULL; - option_no_checkout = 1; - if (!option_bare) - install_branch_config(0, "master", option_origin, - "refs/heads/master"); - } - - if (remote_head_points_at && !option_bare) { - struct strbuf head_ref = STRBUF_INIT; - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - create_symref(head_ref.buf, - remote_head_points_at->peer_ref->name, - reflog_msg.buf); - } - - if (our_head_points_at) { - /* Local default branch link */ - create_symref("HEAD", our_head_points_at->name, NULL); - if (!option_bare) { - const char *head = skip_prefix(our_head_points_at->name, - "refs/heads/"); - update_ref(reflog_msg.buf, "HEAD", - our_head_points_at->old_sha1, - NULL, 0, DIE_ON_ERR); - install_branch_config(0, head, option_origin, - our_head_points_at->name); - } - } else if (remote_head) { - /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) { - update_ref(reflog_msg.buf, "HEAD", - remote_head->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); - our_head_points_at = remote_head; - } - } else { - /* Nothing to checkout out */ - if (!option_no_checkout) - warning("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n"); - option_no_checkout = 1; - } - - if (transport) { - transport_unlock_pack(transport); - transport_disconnect(transport); - } - - if (!option_no_checkout) { - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - struct unpack_trees_options opts; - struct tree *tree; - struct tree_desc t; - int fd; - - /* We need to be in the new work tree for the checkout */ - setup_work_tree(); - - fd = hold_locked_index(lock_file, 1); - - memset(&opts, 0, sizeof opts); - opts.update = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = !option_quiet; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - tree = parse_tree_indirect(our_head_points_at->old_sha1); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); - - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - - err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(our_head_points_at->old_sha1), "1", - NULL); - - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); - } - - strbuf_release(&reflog_msg); - strbuf_release(&branch_top); - strbuf_release(&key); - strbuf_release(&value); - junk_pid = 0; - return err; -} diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c deleted file mode 100644 index 90dac349a..000000000 --- a/builtin-commit-tree.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "commit.h" -#include "tree.h" -#include "builtin.h" -#include "utf8.h" - -/* - * FIXME! Share the code with "write-tree.c" - */ -static void check_valid(unsigned char *sha1, enum object_type expect) -{ - enum object_type type = sha1_object_info(sha1, NULL); - if (type < 0) - die("%s is not a valid object", sha1_to_hex(sha1)); - if (type != expect) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), - typename(expect)); -} - -static const char commit_tree_usage[] = "git commit-tree [-p ]* < changelog"; - -static void new_parent(struct commit *parent, struct commit_list **parents_p) -{ - unsigned char *sha1 = parent->object.sha1; - struct commit_list *parents; - for (parents = *parents_p; parents; parents = parents->next) { - if (parents->item == parent) { - error("duplicate parent %s ignored", sha1_to_hex(sha1)); - return; - } - parents_p = &parents->next; - } - commit_list_insert(parent, parents_p); -} - -static const char commit_utf8_warn[] = -"Warning: commit message does not conform to UTF-8.\n" -"You may want to amend it after fixing the message, or set the config\n" -"variable i18n.commitencoding to the encoding your project uses.\n"; - -int commit_tree(const char *msg, unsigned char *tree, - struct commit_list *parents, unsigned char *ret, - const char *author) -{ - int result; - int encoding_is_utf8; - struct strbuf buffer; - - check_valid(tree, OBJ_TREE); - - /* Not having i18n.commitencoding is the same as having utf-8 */ - encoding_is_utf8 = is_encoding_utf8(git_commit_encoding); - - strbuf_init(&buffer, 8192); /* should avoid reallocs for the headers */ - strbuf_addf(&buffer, "tree %s\n", sha1_to_hex(tree)); - - /* - * NOTE! This ordering means that the same exact tree merged with a - * different order of parents will be a _different_ changeset even - * if everything else stays the same. - */ - while (parents) { - struct commit_list *next = parents->next; - strbuf_addf(&buffer, "parent %s\n", - sha1_to_hex(parents->item->object.sha1)); - free(parents); - parents = next; - } - - /* Person/date information */ - if (!author) - author = git_author_info(IDENT_ERROR_ON_NO_NAME); - strbuf_addf(&buffer, "author %s\n", author); - strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); - if (!encoding_is_utf8) - strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); - strbuf_addch(&buffer, '\n'); - - /* And add the comment */ - strbuf_addstr(&buffer, msg); - - /* And check the encoding */ - if (encoding_is_utf8 && !is_utf8(buffer.buf)) - fprintf(stderr, commit_utf8_warn); - - result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); - strbuf_release(&buffer); - return result; -} - -int cmd_commit_tree(int argc, const char **argv, const char *prefix) -{ - int i; - struct commit_list *parents = NULL; - unsigned char tree_sha1[20]; - unsigned char commit_sha1[20]; - struct strbuf buffer = STRBUF_INIT; - - git_config(git_default_config, NULL); - - if (argc < 2 || !strcmp(argv[1], "-h")) - usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); - - for (i = 2; i < argc; i += 2) { - unsigned char sha1[20]; - const char *a, *b; - a = argv[i]; b = argv[i+1]; - if (!b || strcmp(a, "-p")) - usage(commit_tree_usage); - - if (get_sha1(b, sha1)) - die("Not a valid object name %s", b); - check_valid(sha1, OBJ_COMMIT); - new_parent(lookup_commit(sha1), &parents); - } - - if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); - - if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) { - printf("%s\n", sha1_to_hex(commit_sha1)); - return 0; - } - else - return 1; -} diff --git a/builtin-commit.c b/builtin-commit.c deleted file mode 100644 index 55676fd87..000000000 --- a/builtin-commit.c +++ /dev/null @@ -1,1310 +0,0 @@ -/* - * Builtin "git commit" - * - * Copyright (c) 2007 Kristian Høgsberg - * Based on git-commit.sh by Junio C Hamano and Linus Torvalds - */ - -#include "cache.h" -#include "cache-tree.h" -#include "color.h" -#include "dir.h" -#include "builtin.h" -#include "diff.h" -#include "diffcore.h" -#include "commit.h" -#include "revision.h" -#include "wt-status.h" -#include "run-command.h" -#include "refs.h" -#include "log-tree.h" -#include "strbuf.h" -#include "utf8.h" -#include "parse-options.h" -#include "string-list.h" -#include "rerere.h" -#include "unpack-trees.h" -#include "quote.h" - -static const char * const builtin_commit_usage[] = { - "git commit [options] [--] ...", - NULL -}; - -static const char * const builtin_status_usage[] = { - "git status [options] [--] ...", - NULL -}; - -static const char implicit_ident_advice[] = -"Your name and email address were configured automatically based\n" -"on your username and hostname. Please check that they are accurate.\n" -"You can suppress this message by setting them explicitly:\n" -"\n" -" git config --global user.name Your Name\n" -" git config --global user.email you@example.com\n" -"\n" -"If the identity used for this commit is wrong, you can fix it with:\n" -"\n" -" git commit --amend --author='Your Name '\n"; - -static unsigned char head_sha1[20]; - -static char *use_message_buffer; -static const char commit_editmsg[] = "COMMIT_EDITMSG"; -static struct lock_file index_lock; /* real index */ -static struct lock_file false_lock; /* used only for partial commits */ -static enum { - COMMIT_AS_IS = 1, - COMMIT_NORMAL, - COMMIT_PARTIAL, -} commit_style; - -static const char *logfile, *force_author; -static const char *template_file; -static char *edit_message, *use_message; -static char *author_name, *author_email, *author_date; -static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; -static char *untracked_files_arg, *force_date; -/* - * The default commit message cleanup mode will remove the lines - * beginning with # (shell comments) and leading and trailing - * whitespaces (empty lines or containing only whitespaces) - * if editor is used, and only the whitespaces if the message - * is specified explicitly. - */ -static enum { - CLEANUP_SPACE, - CLEANUP_NONE, - CLEANUP_ALL, -} cleanup_mode; -static char *cleanup_arg; - -static int use_editor = 1, initial_commit, in_merge, include_status = 1; -static const char *only_include_assumed; -static struct strbuf message; - -static int null_termination; -static enum { - STATUS_FORMAT_LONG, - STATUS_FORMAT_SHORT, - STATUS_FORMAT_PORCELAIN, -} status_format = STATUS_FORMAT_LONG; - -static int opt_parse_m(const struct option *opt, const char *arg, int unset) -{ - struct strbuf *buf = opt->value; - if (unset) - strbuf_setlen(buf, 0); - else { - strbuf_addstr(buf, arg); - strbuf_addstr(buf, "\n\n"); - } - return 0; -} - -static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet), - OPT__VERBOSE(&verbose), - - OPT_GROUP("Commit message options"), - OPT_FILENAME('F', "file", &logfile, "read log from file"), - OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), - OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"), - OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), - OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), - OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), - OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), - OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), - OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), - /* end commit message options */ - - OPT_GROUP("Commit contents options"), - OPT_BOOLEAN('a', "all", &all, "commit all changed files"), - OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"), - OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), - OPT_BOOLEAN('o', "only", &only, "commit only specified files"), - OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), - OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), - OPT_SET_INT(0, "short", &status_format, "show status concisely", - STATUS_FORMAT_SHORT), - OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), - OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), - /* end commit contents options */ - - OPT_END() -}; - -static void rollback_index_files(void) -{ - switch (commit_style) { - case COMMIT_AS_IS: - break; /* nothing to do */ - case COMMIT_NORMAL: - rollback_lock_file(&index_lock); - break; - case COMMIT_PARTIAL: - rollback_lock_file(&index_lock); - rollback_lock_file(&false_lock); - break; - } -} - -static int commit_index_files(void) -{ - int err = 0; - - switch (commit_style) { - case COMMIT_AS_IS: - break; /* nothing to do */ - case COMMIT_NORMAL: - err = commit_lock_file(&index_lock); - break; - case COMMIT_PARTIAL: - err = commit_lock_file(&index_lock); - rollback_lock_file(&false_lock); - break; - } - - return err; -} - -/* - * Take a union of paths in the index and the named tree (typically, "HEAD"), - * and return the paths that match the given pattern in list. - */ -static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const char **pattern) -{ - int i; - char *m; - - for (i = 0; pattern[i]; i++) - ; - m = xcalloc(1, i); - - if (with_tree) - overlay_tree_on_cache(with_tree, prefix); - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - struct string_list_item *item; - - if (ce->ce_flags & CE_UPDATE) - continue; - if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) - continue; - item = string_list_insert(ce->name, list); - if (ce_skip_worktree(ce)) - item->util = item; /* better a valid pointer than a fake one */ - } - - return report_path_error(m, pattern, prefix ? strlen(prefix) : 0); -} - -static void add_remove_files(struct string_list *list) -{ - int i; - for (i = 0; i < list->nr; i++) { - struct stat st; - struct string_list_item *p = &(list->items[i]); - - /* p->util is skip-worktree */ - if (p->util) - continue; - - if (!lstat(p->string, &st)) { - if (add_to_cache(p->string, &st, 0)) - die("updating files failed"); - } else - remove_file_from_cache(p->string); - } -} - -static void create_base_index(void) -{ - struct tree *tree; - struct unpack_trees_options opts; - struct tree_desc t; - - if (initial_commit) { - discard_cache(); - return; - } - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = 1; - opts.index_only = 1; - opts.merge = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - opts.fn = oneway_merge; - tree = parse_tree_indirect(head_sha1); - if (!tree) - die("failed to unpack HEAD tree object"); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - if (unpack_trees(1, &t, &opts)) - exit(128); /* We've already reported the error, finish dying */ -} - -static void refresh_cache_or_die(int refresh_flags) -{ - /* - * refresh_flags contains REFRESH_QUIET, so the only errors - * are for unmerged entries. - */ - if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN)) - die_resolve_conflict("commit"); -} - -static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) -{ - int fd; - struct string_list partial; - const char **pathspec = NULL; - int refresh_flags = REFRESH_QUIET; - - if (is_status) - refresh_flags |= REFRESH_UNMERGED; - if (interactive) { - if (interactive_add(argc, argv, prefix) != 0) - die("interactive add failed"); - if (read_cache_preload(NULL) < 0) - die("index file corrupt"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } - - if (*argv) - pathspec = get_pathspec(prefix, argv); - - if (read_cache_preload(pathspec) < 0) - die("index file corrupt"); - - /* - * Non partial, non as-is commit. - * - * (1) get the real index; - * (2) update the_index as necessary; - * (3) write the_index out to the real index (still locked); - * (4) return the name of the locked index file. - * - * The caller should run hooks on the locked real index, and - * (A) if all goes well, commit the real index; - * (B) on failure, rollback the real index. - */ - if (all || (also && pathspec && *pathspec)) { - int fd = hold_locked_index(&index_lock, 1); - add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) - die("unable to write new_index file"); - commit_style = COMMIT_NORMAL; - return index_lock.filename; - } - - /* - * As-is commit. - * - * (1) return the name of the real index file. - * - * The caller should run hooks on the real index, and run - * hooks on the real index, and create commit from the_index. - * We still need to refresh the index here. - */ - if (!pathspec || !*pathspec) { - fd = hold_locked_index(&index_lock, 1); - refresh_cache_or_die(refresh_flags); - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(&index_lock)) - die("unable to write new_index file"); - commit_style = COMMIT_AS_IS; - return get_index_file(); - } - - /* - * A partial commit. - * - * (0) find the set of affected paths; - * (1) get lock on the real index file; - * (2) update the_index with the given paths; - * (3) write the_index out to the real index (still locked); - * (4) get lock on the false index file; - * (5) reset the_index from HEAD; - * (6) update the_index the same way as (2); - * (7) write the_index out to the false index file; - * (8) return the name of the false index file (still locked); - * - * The caller should run hooks on the locked false index, and - * create commit from it. Then - * (A) if all goes well, commit the real index; - * (B) on failure, rollback the real index; - * In either case, rollback the false index. - */ - commit_style = COMMIT_PARTIAL; - - if (in_merge) - die("cannot do a partial commit during a merge."); - - memset(&partial, 0, sizeof(partial)); - partial.strdup_strings = 1; - if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec)) - exit(1); - - discard_cache(); - if (read_cache() < 0) - die("cannot read the index"); - - fd = hold_locked_index(&index_lock, 1); - add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&index_lock)) - die("unable to write new_index file"); - - fd = hold_lock_file_for_update(&false_lock, - git_path("next-index-%"PRIuMAX, - (uintmax_t) getpid()), - LOCK_DIE_ON_ERROR); - - create_base_index(); - add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); - - if (write_cache(fd, active_cache, active_nr) || - close_lock_file(&false_lock)) - die("unable to write temporary index file"); - - discard_cache(); - read_cache_from(false_lock.filename); - - return false_lock.filename; -} - -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, - struct wt_status *s) -{ - unsigned char sha1[20]; - - if (s->relative_paths) - s->prefix = prefix; - - if (amend) { - s->amend = 1; - s->reference = "HEAD^1"; - } - s->verbose = verbose; - s->index_file = index_file; - s->fp = fp; - s->nowarn = nowarn; - s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; - - wt_status_collect(s); - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(s, null_termination); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(s, null_termination); - break; - case STATUS_FORMAT_LONG: - wt_status_print(s); - break; - } - - return s->commitable; -} - -static int is_a_merge(const unsigned char *sha1) -{ - struct commit *commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - return !!(commit->parents && commit->parents->next); -} - -static const char sign_off_header[] = "Signed-off-by: "; - -static void determine_author_info(void) -{ - char *name, *email, *date; - - name = getenv("GIT_AUTHOR_NAME"); - email = getenv("GIT_AUTHOR_EMAIL"); - date = getenv("GIT_AUTHOR_DATE"); - - if (use_message && !renew_authorship) { - const char *a, *lb, *rb, *eol; - - a = strstr(use_message_buffer, "\nauthor "); - if (!a) - die("invalid commit: %s", use_message); - - lb = strstr(a + 8, " <"); - rb = strstr(a + 8, "> "); - eol = strchr(a + 8, '\n'); - if (!lb || !rb || !eol) - die("invalid commit: %s", use_message); - - name = xstrndup(a + 8, lb - (a + 8)); - email = xstrndup(lb + 2, rb - (lb + 2)); - date = xstrndup(rb + 2, eol - (rb + 2)); - } - - if (force_author) { - const char *lb = strstr(force_author, " <"); - const char *rb = strchr(force_author, '>'); - - if (!lb || !rb) - die("malformed --author parameter"); - name = xstrndup(force_author, lb - force_author); - email = xstrndup(lb + 2, rb - (lb + 2)); - } - - if (force_date) - date = force_date; - - author_name = name; - author_email = email; - author_date = date; -} - -static int ends_rfc2822_footer(struct strbuf *sb) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - -static int prepare_to_commit(const char *index_file, const char *prefix, - struct wt_status *s) -{ - struct stat statbuf; - int commitable, saved_color_setting; - struct strbuf sb = STRBUF_INIT; - char *buffer; - FILE *fp; - const char *hook_arg1 = NULL; - const char *hook_arg2 = NULL; - int ident_shown = 0; - - if (!no_verify && run_hook(index_file, "pre-commit", NULL)) - return 0; - - if (message.len) { - strbuf_addbuf(&sb, &message); - hook_arg1 = "message"; - } else if (logfile && !strcmp(logfile, "-")) { - if (isatty(0)) - fprintf(stderr, "(reading log message from standard input)\n"); - if (strbuf_read(&sb, 0, 0) < 0) - die_errno("could not read log from standard input"); - hook_arg1 = "message"; - } else if (logfile) { - if (strbuf_read_file(&sb, logfile, 0) < 0) - die_errno("could not read log file '%s'", - logfile); - hook_arg1 = "message"; - } else if (use_message) { - buffer = strstr(use_message_buffer, "\n\n"); - if (!buffer || buffer[2] == '\0') - die("commit has empty message"); - strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); - hook_arg1 = "commit"; - hook_arg2 = use_message; - } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) - die_errno("could not read MERGE_MSG"); - hook_arg1 = "merge"; - } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) - die_errno("could not read SQUASH_MSG"); - hook_arg1 = "squash"; - } else if (template_file && !stat(template_file, &statbuf)) { - if (strbuf_read_file(&sb, template_file, 0) < 0) - die_errno("could not read '%s'", template_file); - hook_arg1 = "template"; - } - - /* - * This final case does not modify the template message, - * it just sets the argument to the prepare-commit-msg hook. - */ - else if (in_merge) - hook_arg1 = "merge"; - - fp = fopen(git_path(commit_editmsg), "w"); - if (fp == NULL) - die_errno("could not open '%s'", git_path(commit_editmsg)); - - if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, 0); - - if (signoff) { - struct strbuf sob = STRBUF_INIT; - int i; - - strbuf_addstr(&sob, sign_off_header); - strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - strbuf_addch(&sob, '\n'); - for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(sb.buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(&sb)) - strbuf_addch(&sb, '\n'); - strbuf_addbuf(&sb, &sob); - } - strbuf_release(&sob); - } - - if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) - die_errno("could not write commit template"); - - strbuf_release(&sb); - - determine_author_info(); - - /* This checks if committer ident is explicitly given */ - git_committer_info(0); - if (use_editor && include_status) { - char *author_ident; - const char *committer_ident; - - if (in_merge) - fprintf(fp, - "#\n" - "# It looks like you may be committing a MERGE.\n" - "# If this is not correct, please remove the file\n" - "# %s\n" - "# and try again.\n" - "#\n", - git_path("MERGE_HEAD")); - - fprintf(fp, - "\n" - "# Please enter the commit message for your changes."); - if (cleanup_mode == CLEANUP_ALL) - fprintf(fp, - " Lines starting\n" - "# with '#' will be ignored, and an empty" - " message aborts the commit.\n"); - else /* CLEANUP_SPACE, that is. */ - fprintf(fp, - " Lines starting\n" - "# with '#' will be kept; you may remove them" - " yourself if you want to.\n" - "# An empty message aborts the commit.\n"); - if (only_include_assumed) - fprintf(fp, "# %s\n", only_include_assumed); - - author_ident = xstrdup(fmt_name(author_name, author_email)); - committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL")); - if (strcmp(author_ident, committer_ident)) - fprintf(fp, - "%s" - "# Author: %s\n", - ident_shown++ ? "" : "#\n", - author_ident); - free(author_ident); - - if (!user_ident_sufficiently_given()) - fprintf(fp, - "%s" - "# Committer: %s\n", - ident_shown++ ? "" : "#\n", - committer_ident); - - if (ident_shown) - fprintf(fp, "#\n"); - - saved_color_setting = s->use_color; - s->use_color = 0; - commitable = run_status(fp, index_file, prefix, 1, s); - s->use_color = saved_color_setting; - } else { - unsigned char sha1[20]; - const char *parent = "HEAD"; - - if (!active_nr && read_cache() < 0) - die("Cannot read index"); - - if (amend) - parent = "HEAD^1"; - - if (get_sha1(parent, sha1)) - commitable = !!active_nr; - else - commitable = index_differs_from(parent, 0); - } - - fclose(fp); - - if (!commitable && !in_merge && !allow_empty && - !(amend && is_a_merge(head_sha1))) { - run_status(stdout, index_file, prefix, 0, s); - return 0; - } - - /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke - * the editor and after we invoke run_status above. - */ - discard_cache(); - read_cache_from(index_file); - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) { - error("Error building trees"); - return 0; - } - - if (run_hook(index_file, "prepare-commit-msg", - git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) - return 0; - - if (use_editor) { - char index[PATH_MAX]; - const char *env[2] = { index, NULL }; - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path(commit_editmsg), NULL, env)) { - fprintf(stderr, - "Please supply the message using either -m or -F option.\n"); - exit(1); - } - } - - if (!no_verify && - run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { - return 0; - } - - return 1; -} - -/* - * Find out if the message in the strbuf contains only whitespace and - * Signed-off-by lines. - */ -static int message_is_empty(struct strbuf *sb) -{ - struct strbuf tmpl = STRBUF_INIT; - const char *nl; - int eol, i, start = 0; - - if (cleanup_mode == CLEANUP_NONE && sb->len) - return 0; - - /* See if the template is just a prefix of the message. */ - if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) { - stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - if (start + tmpl.len <= sb->len && - memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0) - start += tmpl.len; - } - strbuf_release(&tmpl); - - /* Check if the rest is just whitespace and Signed-of-by's. */ - for (i = start; i < sb->len; i++) { - nl = memchr(sb->buf + i, '\n', sb->len - i); - if (nl) - eol = nl - sb->buf; - else - eol = sb->len; - - if (strlen(sign_off_header) <= eol - i && - !prefixcmp(sb->buf + i, sign_off_header)) { - i = eol; - continue; - } - while (i < eol) - if (!isspace(sb->buf[i++])) - return 0; - } - - return 1; -} - -static const char *find_author_by_nickname(const char *name) -{ - struct rev_info revs; - struct commit *commit; - struct strbuf buf = STRBUF_INIT; - const char *av[20]; - int ac = 0; - - init_revisions(&revs, NULL); - strbuf_addf(&buf, "--author=%s", name); - av[++ac] = "--all"; - av[++ac] = "-i"; - av[++ac] = buf.buf; - av[++ac] = NULL; - setup_revisions(ac, av, &revs, NULL); - prepare_revision_walk(&revs); - commit = get_revision(&revs); - if (commit) { - struct pretty_print_context ctx = {0}; - ctx.date_mode = DATE_NORMAL; - strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, &ctx); - return strbuf_detach(&buf, NULL); - } - die("No existing author found with '%s'", name); -} - - -static void handle_untracked_files_arg(struct wt_status *s) -{ - if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die("Invalid untracked files mode '%s'", untracked_files_arg); -} - -static int parse_and_validate_options(int argc, const char *argv[], - const char * const usage[], - const char *prefix, - struct wt_status *s) -{ - int f = 0; - - argc = parse_options(argc, argv, prefix, builtin_commit_options, usage, - 0); - - if (force_author && !strchr(force_author, '>')) - force_author = find_author_by_nickname(force_author); - - if (force_author && renew_authorship) - die("Using both --reset-author and --author does not make sense"); - - if (logfile || message.len || use_message) - use_editor = 0; - if (edit_flag) - use_editor = 1; - if (!use_editor) - setenv("GIT_EDITOR", ":", 1); - - if (get_sha1("HEAD", head_sha1)) - initial_commit = 1; - - /* Sanity check options */ - if (amend && initial_commit) - die("You have nothing to amend."); - if (amend && in_merge) - die("You are in the middle of a merge -- cannot amend."); - - if (use_message) - f++; - if (edit_message) - f++; - if (logfile) - f++; - if (f > 1) - die("Only one of -c/-C/-F can be used."); - if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); - if (edit_message) - use_message = edit_message; - if (amend && !use_message) - use_message = "HEAD"; - if (!use_message && renew_authorship) - die("--reset-author can be used only with -C, -c or --amend."); - if (use_message) { - unsigned char sha1[20]; - static char utf8[] = "UTF-8"; - const char *out_enc; - char *enc, *end; - struct commit *commit; - - if (get_sha1(use_message, sha1)) - die("could not lookup commit %s", use_message); - commit = lookup_commit_reference(sha1); - if (!commit || parse_commit(commit)) - die("could not parse commit %s", use_message); - - enc = strstr(commit->buffer, "\nencoding"); - if (enc) { - end = strchr(enc + 10, '\n'); - enc = xstrndup(enc + 10, end - (enc + 10)); - } else { - enc = utf8; - } - out_enc = git_commit_encoding ? git_commit_encoding : utf8; - - if (strcmp(out_enc, enc)) - use_message_buffer = - reencode_string(commit->buffer, out_enc, enc); - - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (use_message_buffer == NULL) - use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); - } - - if (!!also + !!only + !!all + !!interactive > 1) - die("Only one of --include/--only/--all/--interactive can be used."); - if (argc == 0 && (also || (only && !amend))) - die("No paths with --include/--only does not make sense."); - if (argc == 0 && only && amend) - only_include_assumed = "Clever... amending the last one with dirty index."; - if (argc > 0 && !also && !only) - only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths..."; - if (!cleanup_arg || !strcmp(cleanup_arg, "default")) - cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "verbatim")) - cleanup_mode = CLEANUP_NONE; - else if (!strcmp(cleanup_arg, "whitespace")) - cleanup_mode = CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "strip")) - cleanup_mode = CLEANUP_ALL; - else - die("Invalid cleanup mode %s", cleanup_arg); - - handle_untracked_files_arg(s); - - if (all && argc > 0) - die("Paths with -a does not make sense."); - else if (interactive && argc > 0) - die("Paths with --interactive does not make sense."); - - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - if (status_format != STATUS_FORMAT_LONG) - dry_run = 1; - - return argc; -} - -static int dry_run_commit(int argc, const char **argv, const char *prefix, - struct wt_status *s) -{ - int commitable; - const char *index_file; - - index_file = prepare_index(argc, argv, prefix, 1); - commitable = run_status(stdout, index_file, prefix, 0, s); - rollback_index_files(); - - return commitable ? 0 : 1; -} - -static int parse_status_slot(const char *var, int offset) -{ - if (!strcasecmp(var+offset, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) - return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) - return WT_STATUS_NOBRANCH; - if (!strcasecmp(var+offset, "unmerged")) - return WT_STATUS_UNMERGED; - return -1; -} - -static int git_status_config(const char *k, const char *v, void *cb) -{ - struct wt_status *s = cb; - - if (!strcmp(k, "status.submodulesummary")) { - int is_bool; - s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); - if (is_bool && s->submodule_summary) - s->submodule_summary = -1; - return 0; - } - if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - s->use_color = git_config_colorbool(k, v, -1); - return 0; - } - if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { - int slot = parse_status_slot(k, 13); - if (slot < 0) - return 0; - if (!v) - return config_error_nonbool(k); - color_parse(v, k, s->color_palette[slot]); - return 0; - } - if (!strcmp(k, "status.relativepaths")) { - s->relative_paths = git_config_bool(k, v); - return 0; - } - if (!strcmp(k, "status.showuntrackedfiles")) { - if (!v) - return config_error_nonbool(k); - else if (!strcmp(v, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(v, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(v, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - return error("Invalid untracked files mode '%s'", v); - return 0; - } - return git_diff_ui_config(k, v, NULL); -} - -int cmd_status(int argc, const char **argv, const char *prefix) -{ - struct wt_status s; - unsigned char sha1[20]; - static struct option builtin_status_options[] = { - OPT__VERBOSE(&verbose), - OPT_SET_INT('s', "short", &status_format, - "show status concisely", STATUS_FORMAT_SHORT), - OPT_SET_INT(0, "porcelain", &status_format, - "show porcelain output format", - STATUS_FORMAT_PORCELAIN), - OPT_BOOLEAN('z', "null", &null_termination, - "terminate entries with NUL"), - { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, - "mode", - "show untracked files, optional modes: all, normal, no. (Default: all)", - PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_END(), - }; - - if (null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - - wt_status_prepare(&s); - git_config(git_status_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - argc = parse_options(argc, argv, prefix, - builtin_status_options, - builtin_status_usage, 0); - handle_untracked_files_arg(&s); - - if (*argv) - s.pathspec = get_pathspec(prefix, argv); - - read_cache(); - 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; - wt_status_collect(&s); - - if (s.relative_paths) - s.prefix = prefix; - if (s.use_color == -1) - s.use_color = git_use_color_default; - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - switch (status_format) { - case STATUS_FORMAT_SHORT: - wt_shortstatus_print(&s, null_termination); - break; - case STATUS_FORMAT_PORCELAIN: - wt_porcelain_print(&s, null_termination); - break; - case STATUS_FORMAT_LONG: - s.verbose = verbose; - wt_status_print(&s); - break; - } - return 0; -} - -static void print_summary(const char *prefix, const unsigned char *sha1) -{ - struct rev_info rev; - struct commit *commit; - struct strbuf format = STRBUF_INIT; - unsigned char junk_sha1[20]; - const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); - struct pretty_print_context pctx = {0}; - struct strbuf author_ident = STRBUF_INIT; - struct strbuf committer_ident = STRBUF_INIT; - - commit = lookup_commit(sha1); - if (!commit) - die("couldn't look up newly created commit"); - if (!commit || parse_commit(commit)) - die("could not parse newly created commit"); - - strbuf_addstr(&format, "format:%h] %s"); - - format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); - format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); - if (strbuf_cmp(&author_ident, &committer_ident)) { - strbuf_addstr(&format, "\n Author: "); - strbuf_addbuf_percentquote(&format, &author_ident); - } - if (!user_ident_sufficiently_given()) { - strbuf_addstr(&format, "\n Committer: "); - strbuf_addbuf_percentquote(&format, &committer_ident); - if (advice_implicit_identity) { - strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); - } - } - strbuf_release(&author_ident); - strbuf_release(&committer_ident); - - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - - rev.abbrev = 0; - rev.diff = 1; - rev.diffopt.output_format = - DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; - - rev.verbose_header = 1; - rev.show_root_diff = 1; - get_commit_format(format.buf, &rev); - rev.always_show_header = 0; - rev.diffopt.detect_rename = 1; - rev.diffopt.rename_limit = 100; - rev.diffopt.break_opt = 0; - diff_setup_done(&rev.diffopt); - - printf("[%s%s ", - !prefixcmp(head, "refs/heads/") ? - head + 11 : - !strcmp(head, "HEAD") ? - "detached HEAD" : - head, - initial_commit ? " (root-commit)" : ""); - - if (!log_tree_commit(&rev, commit)) { - struct pretty_print_context ctx = {0}; - struct strbuf buf = STRBUF_INIT; - ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format.buf + 7, &buf, &ctx); - printf("%s\n", buf.buf); - strbuf_release(&buf); - } - strbuf_release(&format); -} - -static int git_commit_config(const char *k, const char *v, void *cb) -{ - struct wt_status *s = cb; - - if (!strcmp(k, "commit.template")) - return git_config_pathname(&template_file, k, v); - if (!strcmp(k, "commit.status")) { - include_status = git_config_bool(k, v); - return 0; - } - - return git_status_config(k, v, s); -} - -int cmd_commit(int argc, const char **argv, const char *prefix) -{ - struct strbuf sb = STRBUF_INIT; - const char *index_file, *reflog_msg; - char *nl, *p; - unsigned char commit_sha1[20]; - struct ref_lock *ref_lock; - struct commit_list *parents = NULL, **pptr = &parents; - struct stat statbuf; - int allow_fast_forward = 1; - struct wt_status s; - - wt_status_prepare(&s); - git_config(git_commit_config, &s); - in_merge = file_exists(git_path("MERGE_HEAD")); - s.in_merge = in_merge; - - if (s.use_color == -1) - s.use_color = git_use_color_default; - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, - prefix, &s); - if (dry_run) { - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - return dry_run_commit(argc, argv, prefix, &s); - } - index_file = prepare_index(argc, argv, prefix, 0); - - /* Set up everything for writing the commit object. This includes - running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix, &s)) { - rollback_index_files(); - return 1; - } - - /* Determine parents */ - if (initial_commit) { - reflog_msg = "commit (initial)"; - } else if (amend) { - struct commit_list *c; - struct commit *commit; - - reflog_msg = "commit (amend)"; - commit = lookup_commit(head_sha1); - if (!commit || parse_commit(commit)) - die("could not parse HEAD commit"); - - for (c = commit->parents; c; c = c->next) - pptr = &commit_list_insert(c->item, pptr)->next; - } else if (in_merge) { - struct strbuf m = STRBUF_INIT; - FILE *fp; - - reflog_msg = "commit (merge)"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; - fp = fopen(git_path("MERGE_HEAD"), "r"); - if (fp == NULL) - die_errno("could not open '%s' for reading", - git_path("MERGE_HEAD")); - while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) - die("Corrupt MERGE_HEAD file (%s)", m.buf); - pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next; - } - fclose(fp); - strbuf_release(&m); - if (!stat(git_path("MERGE_MODE"), &statbuf)) { - if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0) - die_errno("could not read MERGE_MODE"); - if (!strcmp(sb.buf, "no-ff")) - allow_fast_forward = 0; - } - if (allow_fast_forward) - parents = reduce_heads(parents); - } else { - reflog_msg = "commit"; - pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next; - } - - /* Finally, get the commit message */ - strbuf_reset(&sb); - if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { - int saved_errno = errno; - rollback_index_files(); - die("could not read commit message: %s", strerror(saved_errno)); - } - - /* Truncate the message just before the diff, if any. */ - if (verbose) { - p = strstr(sb.buf, "\ndiff --git "); - if (p != NULL) - strbuf_setlen(&sb, p - sb.buf + 1); - } - - if (cleanup_mode != CLEANUP_NONE) - stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (message_is_empty(&sb)) { - rollback_index_files(); - fprintf(stderr, "Aborting commit due to empty commit message.\n"); - exit(1); - } - - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1, - fmt_ident(author_name, author_email, author_date, - IDENT_ERROR_ON_NO_NAME))) { - rollback_index_files(); - die("failed to write commit object"); - } - - ref_lock = lock_any_ref_for_update("HEAD", - initial_commit ? NULL : head_sha1, - 0); - - nl = strchr(sb.buf, '\n'); - if (nl) - strbuf_setlen(&sb, nl + 1 - sb.buf); - else - strbuf_addch(&sb, '\n'); - strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); - strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - - if (!ref_lock) { - rollback_index_files(); - die("cannot lock HEAD ref"); - } - if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) { - rollback_index_files(); - die("cannot update HEAD ref"); - } - - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); - unlink(git_path("SQUASH_MSG")); - - if (commit_index_files()) - die ("Repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full or quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover."); - - rerere(0); - run_hook(get_index_file(), "post-commit", NULL); - if (!quiet) - print_summary(prefix, commit_sha1); - - return 0; -} diff --git a/builtin-config.c b/builtin-config.c deleted file mode 100644 index 4bc46b15f..000000000 --- a/builtin-config.c +++ /dev/null @@ -1,496 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "color.h" -#include "parse-options.h" - -static const char *const builtin_config_usage[] = { - "git config [options]", - NULL -}; - -static char *key; -static regex_t *key_regexp; -static regex_t *regexp; -static int show_keys; -static int use_key_regexp; -static int do_all; -static int do_not_match; -static int seen; -static char delim = '='; -static char key_delim = ' '; -static char term = '\n'; - -static int use_global_config, use_system_config; -static const char *given_config_file; -static int actions, types; -static const char *get_color_slot, *get_colorbool_slot; -static int end_null; - -#define ACTION_GET (1<<0) -#define ACTION_GET_ALL (1<<1) -#define ACTION_GET_REGEXP (1<<2) -#define ACTION_REPLACE_ALL (1<<3) -#define ACTION_ADD (1<<4) -#define ACTION_UNSET (1<<5) -#define ACTION_UNSET_ALL (1<<6) -#define ACTION_RENAME_SECTION (1<<7) -#define ACTION_REMOVE_SECTION (1<<8) -#define ACTION_LIST (1<<9) -#define ACTION_EDIT (1<<10) -#define ACTION_SET (1<<11) -#define ACTION_SET_ALL (1<<12) -#define ACTION_GET_COLOR (1<<13) -#define ACTION_GET_COLORBOOL (1<<14) - -#define TYPE_BOOL (1<<0) -#define TYPE_INT (1<<1) -#define TYPE_BOOL_OR_INT (1<<2) -#define TYPE_PATH (1<<3) - -static struct option builtin_config_options[] = { - OPT_GROUP("Config file location"), - OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), - OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), - OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), - OPT_GROUP("Action"), - OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), - OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP), - OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL), - OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD), - OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL), - OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION), - OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION), - OPT_BIT('l', "list", &actions, "list all", ACTION_LIST), - OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT), - OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"), - OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"), - OPT_GROUP("Type"), - OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), - OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), - OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), - OPT_BIT(0, "path", &types, "value is a path (file or directory name)", TYPE_PATH), - OPT_GROUP("Other"), - OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), - OPT_END(), -}; - -static void check_argc(int argc, int min, int max) { - if (argc >= min && argc <= max) - return; - error("wrong number of arguments"); - usage_with_options(builtin_config_usage, builtin_config_options); -} - -static int show_all_config(const char *key_, const char *value_, void *cb) -{ - if (value_) - printf("%s%c%s%c", key_, delim, value_, term); - else - printf("%s%c", key_, term); - return 0; -} - -static int show_config(const char *key_, const char *value_, void *cb) -{ - char value[256]; - const char *vptr = value; - int must_free_vptr = 0; - int dup_error = 0; - - if (!use_key_regexp && strcmp(key_, key)) - return 0; - if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) - return 0; - if (regexp != NULL && - (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) - return 0; - - if (show_keys) { - if (value_) - printf("%s%c", key_, key_delim); - else - printf("%s", key_); - } - if (seen && !do_all) - dup_error = 1; - if (types == TYPE_INT) - sprintf(value, "%d", git_config_int(key_, value_?value_:"")); - else if (types == TYPE_BOOL) - vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, &is_bool); - if (is_bool) - vptr = v ? "true" : "false"; - else - sprintf(value, "%d", v); - } else if (types == TYPE_PATH) { - git_config_pathname(&vptr, key_, value_); - must_free_vptr = 1; - } - else - vptr = value_?value_:""; - seen++; - if (dup_error) { - error("More than one value for the key %s: %s", - key_, vptr); - } - else - printf("%s%c", vptr, term); - if (must_free_vptr) - /* If vptr must be freed, it's a pointer to a - * dynamically allocated buffer, it's safe to cast to - * const. - */ - free((char *)vptr); - - return 0; -} - -static int get_value(const char *key_, const char *regex_) -{ - int ret = -1; - char *tl; - char *global = NULL, *repo_config = NULL; - const char *system_wide = NULL, *local; - - local = config_exclusive_filename; - if (!local) { - const char *home = getenv("HOME"); - local = repo_config = git_pathdup("config"); - if (git_config_global() && home) - global = xstrdup(mkpath("%s/.gitconfig", home)); - if (git_config_system()) - system_wide = git_etc_gitconfig(); - } - - key = xstrdup(key_); - for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl) - *tl = tolower(*tl); - for (tl=key; *tl && *tl != '.'; ++tl) - *tl = tolower(*tl); - - if (use_key_regexp) { - key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(key_regexp, key, REG_EXTENDED)) { - fprintf(stderr, "Invalid key pattern: %s\n", key_); - goto free_strings; - } - } - - if (regex_) { - if (regex_[0] == '!') { - do_not_match = 1; - regex_++; - } - - regexp = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(regexp, regex_, REG_EXTENDED)) { - fprintf(stderr, "Invalid pattern: %s\n", regex_); - goto free_strings; - } - } - - if (do_all && system_wide) - git_config_from_file(show_config, system_wide, NULL); - if (do_all && global) - git_config_from_file(show_config, global, NULL); - git_config_from_file(show_config, local, NULL); - if (!do_all && !seen && global) - git_config_from_file(show_config, global, NULL); - if (!do_all && !seen && system_wide) - git_config_from_file(show_config, system_wide, NULL); - - free(key); - if (regexp) { - regfree(regexp); - free(regexp); - } - - if (do_all) - ret = !seen; - else - ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; - -free_strings: - free(repo_config); - free(global); - return ret; -} - -static char *normalize_value(const char *key, const char *value) -{ - char *normalized; - - if (!value) - return NULL; - - if (types == 0 || types == TYPE_PATH) - /* - * We don't do normalization for TYPE_PATH here: If - * the path is like ~/foobar/, we prefer to store - * "~/foobar/" in the config file, and to expand the ~ - * when retrieving the value. - */ - normalized = xstrdup(value); - else { - normalized = xmalloc(64); - if (types == TYPE_INT) { - int v = git_config_int(key, value); - sprintf(normalized, "%d", v); - } - else if (types == TYPE_BOOL) - sprintf(normalized, "%s", - git_config_bool(key, value) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool) - sprintf(normalized, "%d", v); - else - sprintf(normalized, "%s", v ? "true" : "false"); - } - } - - return normalized; -} - -static int get_color_found; -static const char *get_color_slot; -static const char *get_colorbool_slot; -static char parsed_color[COLOR_MAXLEN]; - -static int git_get_color_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, get_color_slot)) { - if (!value) - config_error_nonbool(var); - color_parse(value, var, parsed_color); - get_color_found = 1; - } - return 0; -} - -static void get_color(const char *def_color) -{ - get_color_found = 0; - parsed_color[0] = '\0'; - git_config(git_get_color_config, NULL); - - if (!get_color_found && def_color) - color_parse(def_color, "command line", parsed_color); - - fputs(parsed_color, stdout); -} - -static int stdout_is_tty; -static int get_colorbool_found; -static int get_diff_color_found; -static int git_get_colorbool_config(const char *var, const char *value, - void *cb) -{ - if (!strcmp(var, get_colorbool_slot)) { - get_colorbool_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "diff.color")) { - get_diff_color_found = - git_config_colorbool(var, value, stdout_is_tty); - } - if (!strcmp(var, "color.ui")) { - git_use_color_default = git_config_colorbool(var, value, stdout_is_tty); - return 0; - } - return 0; -} - -static int get_colorbool(int print) -{ - get_colorbool_found = -1; - get_diff_color_found = -1; - git_config(git_get_colorbool_config, NULL); - - if (get_colorbool_found < 0) { - if (!strcmp(get_colorbool_slot, "color.diff")) - get_colorbool_found = get_diff_color_found; - if (get_colorbool_found < 0) - get_colorbool_found = git_use_color_default; - } - - if (print) { - printf("%s\n", get_colorbool_found ? "true" : "false"); - return 0; - } else - return get_colorbool_found ? 0 : 1; -} - -int cmd_config(int argc, const char **argv, const char *unused_prefix) -{ - int nongit; - char *value; - const char *prefix = setup_git_directory_gently(&nongit); - - config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); - - argc = parse_options(argc, argv, prefix, builtin_config_options, - builtin_config_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - - if (use_global_config + use_system_config + !!given_config_file > 1) { - error("only one config file at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (use_global_config) { - char *home = getenv("HOME"); - if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; - } else { - die("$HOME not set"); - } - } - else if (use_system_config) - config_exclusive_filename = git_etc_gitconfig(); - else if (given_config_file) { - if (!is_absolute_path(given_config_file) && prefix) - config_exclusive_filename = prefix_filename(prefix, - strlen(prefix), - given_config_file); - else - config_exclusive_filename = given_config_file; - } - - if (end_null) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; - } - - if (HAS_MULTI_BITS(types)) { - error("only one type at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (get_color_slot) - actions |= ACTION_GET_COLOR; - if (get_colorbool_slot) - actions |= ACTION_GET_COLORBOOL; - - if ((get_color_slot || get_colorbool_slot) && types) { - error("--get-color and variable type are incoherent"); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (HAS_MULTI_BITS(actions)) { - error("only one action at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - if (actions == 0) - switch (argc) { - case 1: actions = ACTION_GET; break; - case 2: actions = ACTION_SET; break; - case 3: actions = ACTION_SET_ALL; break; - default: - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if (actions == ACTION_LIST) { - check_argc(argc, 0, 0); - if (git_config(show_all_config, NULL) < 0) { - if (config_exclusive_filename) - die_errno("unable to read config file '%s'", - config_exclusive_filename); - else - die("error processing config file(s)"); - } - } - else if (actions == ACTION_EDIT) { - check_argc(argc, 0, 0); - if (!config_exclusive_filename && nongit) - die("not in a git directory"); - git_config(git_default_config, NULL); - launch_editor(config_exclusive_filename ? - config_exclusive_filename : git_path("config"), - NULL, NULL); - } - else if (actions == ACTION_SET) { - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - return git_config_set(argv[0], value); - } - else if (actions == ACTION_SET_ALL) { - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, argv[2], 0); - } - else if (actions == ACTION_ADD) { - check_argc(argc, 2, 2); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, "^$", 0); - } - else if (actions == ACTION_REPLACE_ALL) { - check_argc(argc, 2, 3); - value = normalize_value(argv[0], argv[1]); - return git_config_set_multivar(argv[0], value, argv[2], 1); - } - else if (actions == ACTION_GET) { - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_GET_ALL) { - do_all = 1; - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_GET_REGEXP) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); - } - else if (actions == ACTION_UNSET) { - check_argc(argc, 1, 2); - if (argc == 2) - return git_config_set_multivar(argv[0], NULL, argv[1], 0); - else - return git_config_set(argv[0], NULL); - } - else if (actions == ACTION_UNSET_ALL) { - check_argc(argc, 1, 2); - return git_config_set_multivar(argv[0], NULL, argv[1], 1); - } - else if (actions == ACTION_RENAME_SECTION) { - int ret; - check_argc(argc, 2, 2); - ret = git_config_rename_section(argv[0], argv[1]); - if (ret < 0) - return ret; - if (ret == 0) - die("No such section!"); - } - else if (actions == ACTION_REMOVE_SECTION) { - int ret; - check_argc(argc, 1, 1); - ret = git_config_rename_section(argv[0], NULL); - if (ret < 0) - return ret; - if (ret == 0) - die("No such section!"); - } - else if (actions == ACTION_GET_COLOR) { - get_color(argv[0]); - } - else if (actions == ACTION_GET_COLORBOOL) { - if (argc == 1) - stdout_is_tty = git_config_bool("command line", argv[0]); - else if (argc == 0) - stdout_is_tty = isatty(1); - return get_colorbool(argc != 0); - } - - return 0; -} diff --git a/builtin-count-objects.c b/builtin-count-objects.c deleted file mode 100644 index 2bdd8ebde..000000000 --- a/builtin-count-objects.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Builtin "git count-objects". - * - * Copyright (c) 2006 Junio C Hamano - */ - -#include "cache.h" -#include "dir.h" -#include "builtin.h" -#include "parse-options.h" - -static void count_objects(DIR *d, char *path, int len, int verbose, - unsigned long *loose, - off_t *loose_size, - unsigned long *packed_loose, - unsigned long *garbage) -{ - struct dirent *ent; - while ((ent = readdir(d)) != NULL) { - char hex[41]; - unsigned char sha1[20]; - const char *cp; - int bad = 0; - - if (is_dot_or_dotdot(ent->d_name)) - continue; - for (cp = ent->d_name; *cp; cp++) { - int ch = *cp; - if (('0' <= ch && ch <= '9') || - ('a' <= ch && ch <= 'f')) - continue; - bad = 1; - break; - } - if (cp - ent->d_name != 38) - bad = 1; - else { - struct stat st; - memcpy(path + len + 3, ent->d_name, 38); - path[len + 2] = '/'; - path[len + 41] = 0; - if (lstat(path, &st) || !S_ISREG(st.st_mode)) - bad = 1; - else - (*loose_size) += xsize_t(on_disk_bytes(st)); - } - if (bad) { - if (verbose) { - error("garbage found: %.*s/%s", - len + 2, path, ent->d_name); - (*garbage)++; - } - continue; - } - (*loose)++; - if (!verbose) - continue; - memcpy(hex, path+len, 2); - memcpy(hex+2, ent->d_name, 38); - hex[40] = 0; - if (get_sha1_hex(hex, sha1)) - die("internal error"); - if (has_sha1_pack(sha1)) - (*packed_loose)++; - } -} - -static char const * const count_objects_usage[] = { - "git count-objects [-v]", - NULL -}; - -int cmd_count_objects(int argc, const char **argv, const char *prefix) -{ - int i, verbose = 0; - const char *objdir = get_object_directory(); - int len = strlen(objdir); - char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; - off_t loose_size = 0; - struct option opts[] = { - OPT__VERBOSE(&verbose), - OPT_END(), - }; - - argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0); - /* we do not take arguments other than flags for now */ - if (argc) - usage_with_options(count_objects_usage, opts); - memcpy(path, objdir, len); - if (len && objdir[len-1] != '/') - path[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - sprintf(path + len, "%02x", i); - d = opendir(path); - if (!d) - continue; - count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose, &garbage); - closedir(d); - } - if (verbose) { - struct packed_git *p; - unsigned long num_pack = 0; - off_t size_pack = 0; - if (!packed_git) - prepare_packed_git(); - for (p = packed_git; p; p = p->next) { - if (!p->pack_local) - continue; - if (open_pack_index(p)) - continue; - packed += p->num_objects; - size_pack += p->pack_size + p->index_size; - num_pack++; - } - printf("count: %lu\n", loose); - printf("size: %lu\n", (unsigned long) (loose_size / 1024)); - printf("in-pack: %lu\n", packed); - printf("packs: %lu\n", num_pack); - printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024)); - printf("prune-packable: %lu\n", packed_loose); - printf("garbage: %lu\n", garbage); - } - else - printf("%lu objects, %lu kilobytes\n", - loose, (unsigned long) (loose_size / 1024)); - return 0; -} diff --git a/builtin-describe.c b/builtin-describe.c deleted file mode 100644 index 71be2a936..000000000 --- a/builtin-describe.c +++ /dev/null @@ -1,396 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "parse-options.h" -#include "diff.h" - -#define SEEN (1u<<0) -#define MAX_TAGS (FLAG_BITS - 1) - -static const char * const describe_usage[] = { - "git describe [options] *", - "git describe [options] --dirty", - NULL -}; - -static int debug; /* Display lots of verbose info */ -static int all; /* Any valid ref can be used */ -static int tags; /* Allow lightweight tags */ -static int longformat; -static int abbrev = DEFAULT_ABBREV; -static int max_candidates = 10; -static int found_names; -static const char *pattern; -static int always; -static const char *dirty; - -/* diff-index command arguments to check if working tree is dirty. */ -static const char *diff_index_args[] = { - "diff-index", "--quiet", "HEAD", "--", NULL -}; - - -struct commit_name { - struct tag *tag; - int prio; /* annotated tag = 2, tag = 1, head = 0 */ - unsigned char sha1[20]; - char path[FLEX_ARRAY]; /* more */ -}; -static const char *prio_names[] = { - "head", "lightweight", "annotated", -}; - -static void add_to_known_names(const char *path, - struct commit *commit, - int prio, - const unsigned char *sha1) -{ - struct commit_name *e = commit->util; - if (!e || e->prio < prio) { - size_t len = strlen(path)+1; - free(e); - e = xmalloc(sizeof(struct commit_name) + len); - e->tag = NULL; - e->prio = prio; - hashcpy(e->sha1, sha1); - memcpy(e->path, path, len); - commit->util = e; - } - found_names = 1; -} - -static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - int might_be_tag = !prefixcmp(path, "refs/tags/"); - struct commit *commit; - struct object *object; - unsigned char peeled[20]; - int is_tag, prio; - - if (!all && !might_be_tag) - return 0; - - if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { - commit = lookup_commit_reference_gently(peeled, 1); - if (!commit) - return 0; - is_tag = !!hashcmp(sha1, commit->object.sha1); - } else { - commit = lookup_commit_reference_gently(sha1, 1); - object = parse_object(sha1); - if (!commit || !object) - return 0; - is_tag = object->type == OBJ_TAG; - } - - /* If --all, then any refs are used. - * If --tags, then any tags are used. - * Otherwise only annotated tags are used. - */ - if (might_be_tag) { - if (is_tag) - prio = 2; - else - prio = 1; - - if (pattern && fnmatch(pattern, path + 10, 0)) - prio = 0; - } - else - prio = 0; - - if (!all) { - if (!prio) - return 0; - } - add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1); - return 0; -} - -struct possible_tag { - struct commit_name *name; - int depth; - int found_order; - unsigned flag_within; -}; - -static int compare_pt(const void *a_, const void *b_) -{ - struct possible_tag *a = (struct possible_tag *)a_; - struct possible_tag *b = (struct possible_tag *)b_; - if (a->depth != b->depth) - return a->depth - b->depth; - if (a->found_order != b->found_order) - return a->found_order - b->found_order; - return 0; -} - -static unsigned long finish_depth_computation( - struct commit_list **list, - struct possible_tag *best) -{ - unsigned long seen_commits = 0; - while (*list) { - struct commit *c = pop_commit(list); - struct commit_list *parents = c->parents; - seen_commits++; - if (c->object.flags & best->flag_within) { - struct commit_list *a = *list; - while (a) { - struct commit *i = a->item; - if (!(i->object.flags & best->flag_within)) - break; - a = a->next; - } - if (!a) - break; - } else - best->depth++; - while (parents) { - struct commit *p = parents->item; - parse_commit(p); - if (!(p->object.flags & SEEN)) - insert_by_date(p, list); - p->object.flags |= c->object.flags; - parents = parents->next; - } - } - return seen_commits; -} - -static void display_name(struct commit_name *n) -{ - if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(n->sha1); - if (!n->tag || parse_tag(n->tag) || !n->tag->tag) - die("annotated tag %s not available", n->path); - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning("tag '%s' is really '%s' here", n->tag->tag, n->path); - } - - if (n->tag) - printf("%s", n->tag->tag); - else - printf("%s", n->path); -} - -static void show_suffix(int depth, const unsigned char *sha1) -{ - printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev)); -} - -static void describe(const char *arg, int last_one) -{ - unsigned char sha1[20]; - struct commit *cmit, *gave_up_on = NULL; - struct commit_list *list; - struct commit_name *n; - struct possible_tag all_matches[MAX_TAGS]; - unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; - unsigned long seen_commits = 0; - unsigned int unannotated_cnt = 0; - - if (get_sha1(arg, sha1)) - die("Not a valid object name %s", arg); - cmit = lookup_commit_reference(sha1); - if (!cmit) - die("%s is not a valid '%s' object", arg, commit_type); - - n = cmit->util; - if (n && (tags || all || n->prio == 2)) { - /* - * Exact match to an existing ref. - */ - display_name(n); - if (longformat) - show_suffix(0, n->tag ? n->tag->tagged->sha1 : sha1); - if (dirty) - printf("%s", dirty); - printf("\n"); - return; - } - - if (!max_candidates) - die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1)); - if (debug) - fprintf(stderr, "searching to describe %s\n", arg); - - list = NULL; - cmit->object.flags = SEEN; - commit_list_insert(cmit, &list); - while (list) { - struct commit *c = pop_commit(&list); - struct commit_list *parents = c->parents; - seen_commits++; - n = c->util; - if (n) { - if (!tags && !all && n->prio < 2) { - unannotated_cnt++; - } else if (match_cnt < max_candidates) { - struct possible_tag *t = &all_matches[match_cnt++]; - t->name = n; - t->depth = seen_commits - 1; - t->flag_within = 1u << match_cnt; - t->found_order = match_cnt; - c->object.flags |= t->flag_within; - if (n->prio == 2) - annotated_cnt++; - } - else { - gave_up_on = c; - break; - } - } - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = &all_matches[cur_match]; - if (!(c->object.flags & t->flag_within)) - t->depth++; - } - if (annotated_cnt && !list) { - if (debug) - fprintf(stderr, "finished search at %s\n", - sha1_to_hex(c->object.sha1)); - break; - } - while (parents) { - struct commit *p = parents->item; - parse_commit(p); - if (!(p->object.flags & SEEN)) - insert_by_date(p, &list); - p->object.flags |= c->object.flags; - parents = parents->next; - } - } - - if (!match_cnt) { - const unsigned char *sha1 = cmit->object.sha1; - if (always) { - printf("%s", find_unique_abbrev(sha1, abbrev)); - if (dirty) - printf("%s", dirty); - printf("\n"); - return; - } - if (unannotated_cnt) - die("No annotated tags can describe '%s'.\n" - "However, there were unannotated tags: try --tags.", - sha1_to_hex(sha1)); - else - die("No tags can describe '%s'.\n" - "Try --always, or create some tags.", - sha1_to_hex(sha1)); - } - - qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); - - if (gave_up_on) { - insert_by_date(gave_up_on, &list); - seen_commits--; - } - seen_commits += finish_depth_computation(&list, &all_matches[0]); - free_commit_list(list); - - if (debug) { - for (cur_match = 0; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = &all_matches[cur_match]; - fprintf(stderr, " %-11s %8d %s\n", - prio_names[t->name->prio], - t->depth, t->name->path); - } - fprintf(stderr, "traversed %lu commits\n", seen_commits); - if (gave_up_on) { - fprintf(stderr, - "more than %i tags found; listed %i most recent\n" - "gave up search at %s\n", - max_candidates, max_candidates, - sha1_to_hex(gave_up_on->object.sha1)); - } - } - - display_name(all_matches[0].name); - if (abbrev) - show_suffix(all_matches[0].depth, cmit->object.sha1); - if (dirty) - printf("%s", dirty); - printf("\n"); - - if (!last_one) - clear_commit_marks(cmit, -1); -} - -int cmd_describe(int argc, const char **argv, const char *prefix) -{ - int contains = 0; - struct option options[] = { - OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"), - OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"), - OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"), - OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"), - OPT_BOOLEAN(0, "long", &longformat, "always use long format"), - OPT__ABBREV(&abbrev), - OPT_SET_INT(0, "exact-match", &max_candidates, - "only output exact matches", 0), - OPT_INTEGER(0, "candidates", &max_candidates, - "consider most recent tags (default: 10)"), - OPT_STRING(0, "match", &pattern, "pattern", - "only consider tags matching "), - OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), - {OPTION_STRING, 0, "dirty", &dirty, "mark", - "append on dirty working tree (default: \"-dirty\")", - PARSE_OPT_OPTARG, NULL, (intptr_t) "-dirty"}, - OPT_END(), - }; - - argc = parse_options(argc, argv, prefix, options, describe_usage, 0); - if (max_candidates < 0) - max_candidates = 0; - else if (max_candidates > MAX_TAGS) - max_candidates = MAX_TAGS; - - save_commit_buffer = 0; - - if (longformat && abbrev == 0) - die("--long is incompatible with --abbrev=0"); - - if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char *)); - int i = 0; - args[i++] = "name-rev"; - args[i++] = "--name-only"; - args[i++] = "--no-undefined"; - if (always) - args[i++] = "--always"; - if (!all) { - args[i++] = "--tags"; - if (pattern) { - char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); - sprintf(s, "--refs=refs/tags/%s", pattern); - args[i++] = s; - } - } - memcpy(args + i, argv, argc * sizeof(char *)); - args[i + argc] = NULL; - return cmd_name_rev(i + argc, args, prefix); - } - - for_each_ref(get_name, NULL); - if (!found_names && !always) - die("No names found, cannot describe anything."); - - if (argc == 0) { - if (dirty && !cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, diff_index_args, prefix)) - dirty = NULL; - describe("HEAD", 1); - } else if (dirty) { - die("--dirty is incompatible with committishes"); - } else { - while (argc-- > 0) { - describe(*argv++, argc == 0); - } - } - return 0; -} diff --git a/builtin-diff-files.c b/builtin-diff-files.c deleted file mode 100644 index 5b64011de..000000000 --- a/builtin-diff-files.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "revision.h" -#include "builtin.h" - -static const char diff_files_usage[] = -"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [] [...]" -COMMON_DIFF_OPTIONS_HELP; - -int cmd_diff_files(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - int result; - unsigned options = 0; - - init_revisions(&rev, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - rev.abbrev = 0; - - argc = setup_revisions(argc, argv, &rev, NULL); - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--base")) - rev.max_count = 1; - else if (!strcmp(argv[1], "--ours")) - rev.max_count = 2; - else if (!strcmp(argv[1], "--theirs")) - rev.max_count = 3; - else if (!strcmp(argv[1], "-q")) - options |= DIFF_SILENT_ON_REMOVED; - else - usage(diff_files_usage); - argv++; argc--; - } - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * Make sure there are NO revision (i.e. pending object) parameter, - * rev.max_count is reasonable (0 <= n <= 3), and - * there is no other revision filtering parameters. - */ - if (rev.pending.nr || - rev.min_age != -1 || rev.max_age != -1 || - 3 < rev.max_count) - usage(diff_files_usage); - - /* - * "diff-files --base -p" should not combine merges because it - * was not asked to. "diff-files -c -p" should not densify - * (the user should ask with "diff-files --cc" explicitly). - */ - if (rev.max_count == -1 && !rev.combine_merges && - (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) - rev.combine_merges = rev.dense_combined_merges = 1; - - if (read_cache_preload(rev.diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - result = run_diff_files(&rev, options); - return diff_result_code(&rev.diffopt, result); -} diff --git a/builtin-diff-index.c b/builtin-diff-index.c deleted file mode 100644 index 04837494f..000000000 --- a/builtin-diff-index.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "revision.h" -#include "builtin.h" - -static const char diff_cache_usage[] = -"git diff-index [-m] [--cached] " -"[] [...]" -COMMON_DIFF_OPTIONS_HELP; - -int cmd_diff_index(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - int cached = 0; - int i; - int result; - - init_revisions(&rev, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - rev.abbrev = 0; - - argc = setup_revisions(argc, argv, &rev, NULL); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--cached")) - cached = 1; - else - usage(diff_cache_usage); - } - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * Make sure there is one revision (i.e. pending object), - * and there is no revision filtering parameters. - */ - if (rev.pending.nr != 1 || - rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) - usage(diff_cache_usage); - if (!cached) - setup_work_tree(); - if (read_cache() < 0) { - perror("read_cache"); - return -1; - } - result = run_diff_index(&rev, cached); - return diff_result_code(&rev.diffopt, result); -} diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c deleted file mode 100644 index 2380c2195..000000000 --- a/builtin-diff-tree.c +++ /dev/null @@ -1,170 +0,0 @@ -#include "cache.h" -#include "diff.h" -#include "commit.h" -#include "log-tree.h" -#include "builtin.h" - -static struct rev_info log_tree_opt; - -static int diff_tree_commit_sha1(const unsigned char *sha1) -{ - struct commit *commit = lookup_commit_reference(sha1); - if (!commit) - return -1; - return log_tree_commit(&log_tree_opt, commit); -} - -/* Diff one or more commits. */ -static int stdin_diff_commit(struct commit *commit, char *line, int len) -{ - unsigned char sha1[20]; - if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { - /* Graft the fake parents locally to the commit */ - int pos = 41; - struct commit_list **pptr, *parents; - - /* Free the real parent list */ - for (parents = commit->parents; parents; ) { - struct commit_list *tmp = parents->next; - free(parents); - parents = tmp; - } - commit->parents = NULL; - pptr = &(commit->parents); - while (line[pos] && !get_sha1_hex(line + pos, sha1)) { - struct commit *parent = lookup_commit(sha1); - if (parent) { - pptr = &commit_list_insert(parent, pptr)->next; - } - pos += 41; - } - } - return log_tree_commit(&log_tree_opt, commit); -} - -/* Diff two trees. */ -static int stdin_diff_trees(struct tree *tree1, char *line, int len) -{ - unsigned char sha1[20]; - struct tree *tree2; - if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1)) - return error("Need exactly two trees, separated by a space"); - tree2 = lookup_tree(sha1); - if (!tree2 || parse_tree(tree2)) - return -1; - printf("%s %s\n", sha1_to_hex(tree1->object.sha1), - sha1_to_hex(tree2->object.sha1)); - diff_tree_sha1(tree1->object.sha1, tree2->object.sha1, - "", &log_tree_opt.diffopt); - log_tree_diff_flush(&log_tree_opt); - return 0; -} - -static int diff_tree_stdin(char *line) -{ - int len = strlen(line); - unsigned char sha1[20]; - struct object *obj; - - if (!len || line[len-1] != '\n') - return -1; - line[len-1] = 0; - if (get_sha1_hex(line, sha1)) - return -1; - obj = lookup_unknown_object(sha1); - if (!obj || !obj->parsed) - obj = parse_object(sha1); - if (!obj) - return -1; - if (obj->type == OBJ_COMMIT) - return stdin_diff_commit((struct commit *)obj, line, len); - if (obj->type == OBJ_TREE) - return stdin_diff_trees((struct tree *)obj, line, len); - error("Object %s is a %s, not a commit or tree", - sha1_to_hex(sha1), typename(obj->type)); - return -1; -} - -static const char diff_tree_usage[] = -"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " -"[] [] [...]\n" -" -r diff recursively\n" -" --root include the initial commit as diff against /dev/null\n" -COMMON_DIFF_OPTIONS_HELP; - -int cmd_diff_tree(int argc, const char **argv, const char *prefix) -{ - int nr_sha1; - char line[1000]; - struct object *tree1, *tree2; - static struct rev_info *opt = &log_tree_opt; - int read_stdin = 0; - - init_revisions(opt, prefix); - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - opt->abbrev = 0; - opt->diff = 1; - opt->disable_stdin = 1; - argc = setup_revisions(argc, argv, opt, NULL); - - while (--argc > 0) { - const char *arg = *++argv; - - if (!strcmp(arg, "--stdin")) { - read_stdin = 1; - continue; - } - usage(diff_tree_usage); - } - - if (!opt->diffopt.output_format) - opt->diffopt.output_format = DIFF_FORMAT_RAW; - - /* - * NOTE! We expect "a ^b" to be equal to "a..b", so we - * reverse the order of the objects if the second one - * is marked UNINTERESTING. - */ - nr_sha1 = opt->pending.nr; - switch (nr_sha1) { - case 0: - if (!read_stdin) - usage(diff_tree_usage); - break; - case 1: - tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(tree1->sha1); - break; - case 2: - tree1 = opt->pending.objects[0].item; - tree2 = opt->pending.objects[1].item; - if (tree2->flags & UNINTERESTING) { - struct object *tmp = tree2; - tree2 = tree1; - tree1 = tmp; - } - diff_tree_sha1(tree1->sha1, - tree2->sha1, - "", &opt->diffopt); - log_tree_diff_flush(opt); - break; - } - - if (read_stdin) { - if (opt->diffopt.detect_rename) - opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | - DIFF_SETUP_USE_CACHE); - while (fgets(line, sizeof(line), stdin)) { - unsigned char sha1[20]; - - if (get_sha1_hex(line, sha1)) { - fputs(line, stdout); - fflush(stdout); - } - else - diff_tree_stdin(line); - } - } - - return diff_result_code(&opt->diffopt, 0); -} diff --git a/builtin-diff.c b/builtin-diff.c deleted file mode 100644 index ffcdd055c..000000000 --- a/builtin-diff.c +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Builtin "git diff" - * - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "color.h" -#include "commit.h" -#include "blob.h" -#include "tag.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" -#include "log-tree.h" -#include "builtin.h" - -struct blobinfo { - unsigned char sha1[20]; - const char *name; - unsigned mode; -}; - -static const char builtin_diff_usage[] = -"git diff {0,2} -- *"; - -static void stuff_change(struct diff_options *opt, - unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *old_name, - const char *new_name) -{ - struct diff_filespec *one, *two; - - if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) && - !hashcmp(old_sha1, new_sha1) && (old_mode == new_mode)) - return; - - if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { - unsigned tmp; - const unsigned char *tmp_u; - const char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u; - tmp_c = old_name; old_name = new_name; new_name = tmp_c; - } - - if (opt->prefix && - (strncmp(old_name, opt->prefix, opt->prefix_length) || - strncmp(new_name, opt->prefix, opt->prefix_length))) - return; - - one = alloc_filespec(old_name); - two = alloc_filespec(new_name); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); - - diff_queue(&diff_queued_diff, one, two); -} - -static int builtin_diff_b_f(struct rev_info *revs, - int argc, const char **argv, - struct blobinfo *blob, - const char *path) -{ - /* Blob vs file in the working tree*/ - struct stat st; - - if (argc > 1) - usage(builtin_diff_usage); - - if (lstat(path, &st)) - die_errno("failed to stat '%s'", path); - if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) - die("'%s': not a regular file or symlink", path); - - diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); - - if (blob[0].mode == S_IFINVALID) - blob[0].mode = canon_mode(st.st_mode); - - stuff_change(&revs->diffopt, - blob[0].mode, canon_mode(st.st_mode), - blob[0].sha1, null_sha1, - path, path); - diffcore_std(&revs->diffopt); - diff_flush(&revs->diffopt); - return 0; -} - -static int builtin_diff_blobs(struct rev_info *revs, - int argc, const char **argv, - struct blobinfo *blob) -{ - unsigned mode = canon_mode(S_IFREG | 0644); - - if (argc > 1) - usage(builtin_diff_usage); - - if (blob[0].mode == S_IFINVALID) - blob[0].mode = mode; - - if (blob[1].mode == S_IFINVALID) - blob[1].mode = mode; - - stuff_change(&revs->diffopt, - blob[0].mode, blob[1].mode, - blob[0].sha1, blob[1].sha1, - blob[0].name, blob[1].name); - diffcore_std(&revs->diffopt); - diff_flush(&revs->diffopt); - return 0; -} - -static int builtin_diff_index(struct rev_info *revs, - int argc, const char **argv) -{ - int cached = 0; - while (1 < argc) { - const char *arg = argv[1]; - if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) - cached = 1; - else - usage(builtin_diff_usage); - argv++; argc--; - } - if (!cached) - setup_work_tree(); - /* - * Make sure there is one revision (i.e. pending object), - * and there is no revision filtering parameters. - */ - if (revs->pending.nr != 1 || - revs->max_count != -1 || revs->min_age != -1 || - revs->max_age != -1) - usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - return run_diff_index(revs, cached); -} - -static int builtin_diff_tree(struct rev_info *revs, - int argc, const char **argv, - struct object_array_entry *ent) -{ - const unsigned char *(sha1[2]); - int swap = 0; - - if (argc > 1) - usage(builtin_diff_usage); - - /* We saw two trees, ent[0] and ent[1]. - * if ent[1] is uninteresting, they are swapped - */ - if (ent[1].item->flags & UNINTERESTING) - swap = 1; - sha1[swap] = ent[0].item->sha1; - sha1[1-swap] = ent[1].item->sha1; - diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt); - log_tree_diff_flush(revs); - return 0; -} - -static int builtin_diff_combined(struct rev_info *revs, - int argc, const char **argv, - struct object_array_entry *ent, - int ents) -{ - const unsigned char (*parent)[20]; - int i; - - if (argc > 1) - usage(builtin_diff_usage); - - if (!revs->dense_combined_merges && !revs->combine_merges) - revs->dense_combined_merges = revs->combine_merges = 1; - parent = xmalloc(ents * sizeof(*parent)); - for (i = 0; i < ents; i++) - hashcpy((unsigned char *)(parent + i), ent[i].item->sha1); - diff_tree_combined(parent[0], parent + 1, ents - 1, - revs->dense_combined_merges, revs); - return 0; -} - -static void refresh_index_quietly(void) -{ - struct lock_file *lock_file; - int fd; - - lock_file = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(lock_file, 0); - if (fd < 0) - return; - discard_cache(); - read_cache(); - refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - - if (active_cache_changed && - !write_cache(fd, active_cache, active_nr)) - commit_locked_index(lock_file); - - rollback_lock_file(lock_file); -} - -static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) -{ - int result; - unsigned int options = 0; - - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--base")) - revs->max_count = 1; - else if (!strcmp(argv[1], "--ours")) - revs->max_count = 2; - else if (!strcmp(argv[1], "--theirs")) - revs->max_count = 3; - else if (!strcmp(argv[1], "-q")) - options |= DIFF_SILENT_ON_REMOVED; - else if (!strcmp(argv[1], "-h")) - usage(builtin_diff_usage); - else - return error("invalid option: %s", argv[1]); - argv++; argc--; - } - - /* - * "diff --base" should not combine merges because it was not - * asked to. "diff -c" should not densify (if the user wants - * dense one, --cc can be explicitly asked for, or just rely - * on the default). - */ - if (revs->max_count == -1 && !revs->combine_merges && - (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - revs->combine_merges = revs->dense_combined_merges = 1; - - setup_work_tree(); - if (read_cache_preload(revs->diffopt.paths) < 0) { - perror("read_cache_preload"); - return -1; - } - result = run_diff_files(revs, options); - return diff_result_code(&revs->diffopt, result); -} - -int cmd_diff(int argc, const char **argv, const char *prefix) -{ - int i; - struct rev_info rev; - struct object_array_entry ent[100]; - int ents = 0, blobs = 0, paths = 0; - const char *path = NULL; - struct blobinfo blob[2]; - int nongit; - int result = 0; - - /* - * We could get N tree-ish in the rev.pending_objects list. - * Also there could be M blobs there, and P pathspecs. - * - * N=0, M=0: - * cache vs files (diff-files) - * N=0, M=2: - * compare two random blobs. P must be zero. - * N=0, M=1, P=1: - * compare a blob with a working tree file. - * - * N=1, M=0: - * tree vs cache (diff-index --cached) - * - * N=2, M=0: - * tree vs tree (diff-tree) - * - * N=0, M=0, P=2: - * compare two filesystem entities (aka --no-index). - * - * Other cases are errors. - */ - - prefix = setup_git_directory_gently(&nongit); - git_config(git_diff_ui_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - - /* If this is a no-index diff, just run it and exit there. */ - diff_no_index(&rev, argc, argv, nongit, prefix); - - /* Otherwise, we are doing the usual "git" diff */ - rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; - - /* Default to let external and textconv be used */ - DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); - DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); - - if (nongit) - die("Not a git repository"); - argc = setup_revisions(argc, argv, &rev, NULL); - if (!rev.diffopt.output_format) { - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); - } - - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - - /* - * If the user asked for our exit code then don't start a - * pager or we would end up reporting its exit code instead. - */ - if (!DIFF_OPT_TST(&rev.diffopt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); - - /* - * Do we have --cached and not have a pending object, then - * default to HEAD by hand. Eek. - */ - if (!rev.pending.nr) { - int i; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--")) - break; - else if (!strcmp(arg, "--cached") || - !strcmp(arg, "--staged")) { - add_head_to_pending(&rev); - if (!rev.pending.nr) - die("No HEAD commit to compare with (yet)"); - break; - } - } - } - - for (i = 0; i < rev.pending.nr; i++) { - struct object_array_entry *list = rev.pending.objects+i; - struct object *obj = list->item; - const char *name = list->name; - int flags = (obj->flags & UNINTERESTING); - if (!obj->parsed) - obj = parse_object(obj->sha1); - obj = deref_tag(obj, NULL, 0); - if (!obj) - die("invalid object '%s' given.", name); - if (obj->type == OBJ_COMMIT) - obj = &((struct commit *)obj)->tree->object; - if (obj->type == OBJ_TREE) { - if (ARRAY_SIZE(ent) <= ents) - die("more than %d trees given: '%s'", - (int) ARRAY_SIZE(ent), name); - obj->flags |= flags; - ent[ents].item = obj; - ent[ents].name = name; - ents++; - continue; - } - if (obj->type == OBJ_BLOB) { - if (2 <= blobs) - die("more than two blobs given: '%s'", name); - hashcpy(blob[blobs].sha1, obj->sha1); - blob[blobs].name = name; - blob[blobs].mode = list->mode; - blobs++; - continue; - - } - die("unhandled object '%s' given.", name); - } - if (rev.prune_data) { - const char **pathspec = rev.prune_data; - while (*pathspec) { - if (!path) - path = *pathspec; - paths++; - pathspec++; - } - } - - /* - * Now, do the arguments look reasonable? - */ - if (!ents) { - switch (blobs) { - case 0: - result = builtin_diff_files(&rev, argc, argv); - break; - case 1: - if (paths != 1) - usage(builtin_diff_usage); - result = builtin_diff_b_f(&rev, argc, argv, blob, path); - break; - case 2: - if (paths) - usage(builtin_diff_usage); - result = builtin_diff_blobs(&rev, argc, argv, blob); - break; - default: - usage(builtin_diff_usage); - } - } - else if (blobs) - usage(builtin_diff_usage); - else if (ents == 1) - result = builtin_diff_index(&rev, argc, argv); - else if (ents == 2) - result = builtin_diff_tree(&rev, argc, argv, ent); - else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) { - /* diff A...B where there is one sane merge base between - * A and B. We have ent[0] == merge-base, ent[1] == A, - * and ent[2] == B. Show diff between the base and B. - */ - ent[1] = ent[2]; - result = builtin_diff_tree(&rev, argc, argv, ent); - } - else - result = builtin_diff_combined(&rev, argc, argv, - ent, ents); - result = diff_result_code(&rev.diffopt, result); - if (1 < rev.diffopt.skip_stat_unmatch) - refresh_index_quietly(); - return result; -} diff --git a/builtin-fast-export.c b/builtin-fast-export.c deleted file mode 100644 index b0a4029c9..000000000 --- a/builtin-fast-export.c +++ /dev/null @@ -1,633 +0,0 @@ -/* - * "git fast-export" builtin command - * - * Copyright (C) 2007 Johannes E. Schindelin - */ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "object.h" -#include "tag.h" -#include "diff.h" -#include "diffcore.h" -#include "log-tree.h" -#include "revision.h" -#include "decorate.h" -#include "string-list.h" -#include "utf8.h" -#include "parse-options.h" - -static const char *fast_export_usage[] = { - "git fast-export [rev-list-opts]", - NULL -}; - -static int progress; -static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; -static int fake_missing_tagger; -static int no_data; - -static int parse_opt_signed_tag_mode(const struct option *opt, - const char *arg, int unset) -{ - if (unset || !strcmp(arg, "abort")) - signed_tag_mode = ABORT; - else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) - signed_tag_mode = VERBATIM; - else if (!strcmp(arg, "warn")) - signed_tag_mode = WARN; - else if (!strcmp(arg, "strip")) - signed_tag_mode = STRIP; - else - return error("Unknown signed-tag mode: %s", arg); - return 0; -} - -static int parse_opt_tag_of_filtered_mode(const struct option *opt, - const char *arg, int unset) -{ - if (unset || !strcmp(arg, "abort")) - tag_of_filtered_mode = ABORT; - else if (!strcmp(arg, "drop")) - tag_of_filtered_mode = DROP; - else if (!strcmp(arg, "rewrite")) - tag_of_filtered_mode = REWRITE; - else - return error("Unknown tag-of-filtered mode: %s", arg); - return 0; -} - -static struct decoration idnums; -static uint32_t last_idnum; - -static int has_unshown_parent(struct commit *commit) -{ - struct commit_list *parent; - - for (parent = commit->parents; parent; parent = parent->next) - if (!(parent->item->object.flags & SHOWN) && - !(parent->item->object.flags & UNINTERESTING)) - return 1; - return 0; -} - -/* Since intptr_t is C99, we do not use it here */ -static inline uint32_t *mark_to_ptr(uint32_t mark) -{ - return ((uint32_t *)NULL) + mark; -} - -static inline uint32_t ptr_to_mark(void * mark) -{ - return (uint32_t *)mark - (uint32_t *)NULL; -} - -static inline void mark_object(struct object *object, uint32_t mark) -{ - add_decoration(&idnums, object, mark_to_ptr(mark)); -} - -static inline void mark_next_object(struct object *object) -{ - mark_object(object, ++last_idnum); -} - -static int get_object_mark(struct object *object) -{ - void *decoration = lookup_decoration(&idnums, object); - if (!decoration) - return 0; - return ptr_to_mark(decoration); -} - -static void show_progress(void) -{ - static int counter = 0; - if (!progress) - return; - if ((++counter % progress) == 0) - printf("progress %d objects\n", counter); -} - -static void handle_object(const unsigned char *sha1) -{ - unsigned long size; - enum object_type type; - char *buf; - struct object *object; - - if (no_data) - return; - - if (is_null_sha1(sha1)) - return; - - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) - return; - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - mark_next_object(object); - - printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size); - if (size && fwrite(buf, size, 1, stdout) != 1) - die_errno ("Could not write blob '%s'", sha1_to_hex(sha1)); - printf("\n"); - - show_progress(); - - object->flags |= SHOWN; - free(buf); -} - -static void show_filemodify(struct diff_queue_struct *q, - struct diff_options *options, void *data) -{ - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filespec *ospec = q->queue[i]->one; - struct diff_filespec *spec = q->queue[i]->two; - - switch (q->queue[i]->status) { - case DIFF_STATUS_DELETED: - printf("D %s\n", spec->path); - break; - - case DIFF_STATUS_COPIED: - case DIFF_STATUS_RENAMED: - printf("%c \"%s\" \"%s\"\n", q->queue[i]->status, - ospec->path, spec->path); - - if (!hashcmp(ospec->sha1, spec->sha1) && - ospec->mode == spec->mode) - break; - /* fallthrough */ - - case DIFF_STATUS_TYPE_CHANGED: - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_ADDED: - /* - * Links refer to objects in another repositories; - * output the SHA-1 verbatim. - */ - if (no_data || S_ISGITLINK(spec->mode)) - printf("M %06o %s %s\n", spec->mode, - sha1_to_hex(spec->sha1), spec->path); - else { - struct object *object = lookup_object(spec->sha1); - printf("M %06o :%d %s\n", spec->mode, - get_object_mark(object), spec->path); - } - break; - - default: - die("Unexpected comparison status '%c' for %s, %s", - q->queue[i]->status, - ospec->path ? ospec->path : "none", - spec->path ? spec->path : "none"); - } - } -} - -static const char *find_encoding(const char *begin, const char *end) -{ - const char *needle = "\nencoding "; - char *bol, *eol; - - bol = memmem(begin, end ? end - begin : strlen(begin), - needle, strlen(needle)); - if (!bol) - return git_commit_encoding; - bol += strlen(needle); - eol = strchrnul(bol, '\n'); - *eol = '\0'; - return bol; -} - -static void handle_commit(struct commit *commit, struct rev_info *rev) -{ - int saved_output_format = rev->diffopt.output_format; - const char *author, *author_end, *committer, *committer_end; - const char *encoding, *message; - char *reencoded = NULL; - struct commit_list *p; - int i; - - rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; - - parse_commit(commit); - author = strstr(commit->buffer, "\nauthor "); - if (!author) - die ("Could not find author in commit %s", - sha1_to_hex(commit->object.sha1)); - author++; - author_end = strchrnul(author, '\n'); - committer = strstr(author_end, "\ncommitter "); - if (!committer) - die ("Could not find committer in commit %s", - sha1_to_hex(commit->object.sha1)); - committer++; - committer_end = strchrnul(committer, '\n'); - message = strstr(committer_end, "\n\n"); - encoding = find_encoding(committer_end, message); - if (message) - message += 2; - - if (commit->parents && - get_object_mark(&commit->parents->item->object) != 0) { - parse_commit(commit->parents->item); - diff_tree_sha1(commit->parents->item->tree->object.sha1, - commit->tree->object.sha1, "", &rev->diffopt); - } - else - diff_root_tree_sha1(commit->tree->object.sha1, - "", &rev->diffopt); - - /* Export the referenced blobs, and remember the marks. */ - for (i = 0; i < diff_queued_diff.nr; i++) - if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - handle_object(diff_queued_diff.queue[i]->two->sha1); - - mark_next_object(&commit->object); - if (!is_encoding_utf8(encoding)) - reencoded = reencode_string(message, "UTF-8", encoding); - if (!commit->parents) - printf("reset %s\n", (const char*)commit->util); - printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", - (const char *)commit->util, last_idnum, - (int)(author_end - author), author, - (int)(committer_end - committer), committer, - (unsigned)(reencoded - ? strlen(reencoded) : message - ? strlen(message) : 0), - reencoded ? reencoded : message ? message : ""); - free(reencoded); - - for (i = 0, p = commit->parents; p; p = p->next) { - int mark = get_object_mark(&p->item->object); - if (!mark) - continue; - if (i == 0) - printf("from :%d\n", mark); - else - printf("merge :%d\n", mark); - i++; - } - - log_tree_diff_flush(rev); - rev->diffopt.output_format = saved_output_format; - - printf("\n"); - - show_progress(); -} - -static void handle_tail(struct object_array *commits, struct rev_info *revs) -{ - struct commit *commit; - while (commits->nr) { - commit = (struct commit *)commits->objects[commits->nr - 1].item; - if (has_unshown_parent(commit)) - return; - handle_commit(commit, revs); - commits->nr--; - } -} - -static void handle_tag(const char *name, struct tag *tag) -{ - unsigned long size; - enum object_type type; - char *buf; - const char *tagger, *tagger_end, *message; - size_t message_size = 0; - struct object *tagged; - int tagged_mark; - struct commit *p; - - /* Trees have no identifer in fast-export output, thus we have no way - * to output tags of trees, tags of tags of trees, etc. Simply omit - * such tags. - */ - tagged = tag->tagged; - while (tagged->type == OBJ_TAG) { - tagged = ((struct tag *)tagged)->tagged; - } - if (tagged->type == OBJ_TREE) { - warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.", - sha1_to_hex(tag->object.sha1)); - return; - } - - buf = read_sha1_file(tag->object.sha1, &type, &size); - if (!buf) - die ("Could not read tag %s", sha1_to_hex(tag->object.sha1)); - message = memmem(buf, size, "\n\n", 2); - if (message) { - message += 2; - message_size = strlen(message); - } - tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8); - if (!tagger) { - if (fake_missing_tagger) - tagger = "tagger Unspecified Tagger " - " 0 +0000"; - else - tagger = ""; - tagger_end = tagger + strlen(tagger); - } else { - tagger++; - tagger_end = strchrnul(tagger, '\n'); - } - - /* handle signed tags */ - if (message) { - const char *signature = strstr(message, - "\n-----BEGIN PGP SIGNATURE-----\n"); - if (signature) - switch(signed_tag_mode) { - case ABORT: - die ("Encountered signed tag %s; use " - "--signed-tag= to handle it.", - sha1_to_hex(tag->object.sha1)); - case WARN: - warning ("Exporting signed tag %s", - sha1_to_hex(tag->object.sha1)); - /* fallthru */ - case VERBATIM: - break; - case STRIP: - message_size = signature + 1 - message; - break; - } - } - - /* handle tag->tagged having been filtered out due to paths specified */ - tagged = tag->tagged; - tagged_mark = get_object_mark(tagged); - if (!tagged_mark) { - switch(tag_of_filtered_mode) { - case ABORT: - die ("Tag %s tags unexported object; use " - "--tag-of-filtered-object= to handle it.", - sha1_to_hex(tag->object.sha1)); - case DROP: - /* Ignore this tag altogether */ - return; - case REWRITE: - if (tagged->type != OBJ_COMMIT) { - die ("Tag %s tags unexported %s!", - sha1_to_hex(tag->object.sha1), - typename(tagged->type)); - } - p = (struct commit *)tagged; - for (;;) { - if (p->parents && p->parents->next) - break; - if (p->object.flags & UNINTERESTING) - break; - if (!(p->object.flags & TREESAME)) - break; - if (!p->parents) - die ("Can't find replacement commit for tag %s\n", - sha1_to_hex(tag->object.sha1)); - p = p->parents->item; - } - tagged_mark = get_object_mark(&p->object); - } - } - - if (!prefixcmp(name, "refs/tags/")) - name += 10; - printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n", - name, tagged_mark, - (int)(tagger_end - tagger), tagger, - tagger == tagger_end ? "" : "\n", - (int)message_size, (int)message_size, message ? message : ""); -} - -static void get_tags_and_duplicates(struct object_array *pending, - struct string_list *extra_refs) -{ - struct tag *tag; - int i; - - for (i = 0; i < pending->nr; i++) { - struct object_array_entry *e = pending->objects + i; - unsigned char sha1[20]; - struct commit *commit = commit; - char *full_name; - - if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) - continue; - - switch (e->item->type) { - case OBJ_COMMIT: - commit = (struct commit *)e->item; - break; - case OBJ_TAG: - tag = (struct tag *)e->item; - - /* handle nested tags */ - while (tag && tag->object.type == OBJ_TAG) { - parse_object(tag->object.sha1); - string_list_append(full_name, extra_refs)->util = tag; - tag = (struct tag *)tag->tagged; - } - if (!tag) - die ("Tag %s points nowhere?", e->name); - switch(tag->object.type) { - case OBJ_COMMIT: - commit = (struct commit *)tag; - break; - case OBJ_BLOB: - handle_object(tag->object.sha1); - continue; - default: /* OBJ_TAG (nested tags) is already handled */ - warning("Tag points to object of unexpected type %s, skipping.", - typename(tag->object.type)); - continue; - } - break; - default: - warning("%s: Unexpected object of type %s, skipping.", - e->name, - typename(e->item->type)); - continue; - } - if (commit->util) - /* more than one name for the same object */ - string_list_append(full_name, extra_refs)->util = commit; - else - commit->util = full_name; - } -} - -static void handle_tags_and_duplicates(struct string_list *extra_refs) -{ - struct commit *commit; - int i; - - for (i = extra_refs->nr - 1; i >= 0; i--) { - const char *name = extra_refs->items[i].string; - struct object *object = extra_refs->items[i].util; - switch (object->type) { - case OBJ_TAG: - handle_tag(name, (struct tag *)object); - break; - case OBJ_COMMIT: - /* create refs pointing to already seen commits */ - commit = (struct commit *)object; - printf("reset %s\nfrom :%d\n\n", name, - get_object_mark(&commit->object)); - show_progress(); - break; - } - } -} - -static void export_marks(char *file) -{ - unsigned int i; - uint32_t mark; - struct object_decoration *deco = idnums.hash; - FILE *f; - int e = 0; - - f = fopen(file, "w"); - if (!f) - error("Unable to open marks file %s for writing.", file); - - for (i = 0; i < idnums.size; i++) { - if (deco->base && deco->base->type == 1) { - mark = ptr_to_mark(deco->decoration); - if (fprintf(f, ":%"PRIu32" %s\n", mark, - sha1_to_hex(deco->base->sha1)) < 0) { - e = 1; - break; - } - } - deco++; - } - - e |= ferror(f); - e |= fclose(f); - if (e) - error("Unable to write marks file %s.", file); -} - -static void import_marks(char *input_file) -{ - char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); - - while (fgets(line, sizeof(line), f)) { - uint32_t mark; - char *line_end, *mark_end; - unsigned char sha1[20]; - struct object *object; - - line_end = strchr(line, '\n'); - if (line[0] != ':' || !line_end) - die("corrupt mark line: %s", line); - *line_end = '\0'; - - mark = strtoumax(line + 1, &mark_end, 10); - if (!mark || mark_end == line + 1 - || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) - die("corrupt mark line: %s", line); - - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) - error("Object %s already has a mark", sha1); - - mark_object(object, mark); - if (last_idnum < mark) - last_idnum = mark; - - object->flags |= SHOWN; - } - fclose(f); -} - -int cmd_fast_export(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - struct object_array commits = { 0, 0, NULL }; - struct string_list extra_refs = { NULL, 0, 0, 0 }; - struct commit *commit; - char *export_filename = NULL, *import_filename = NULL; - struct option options[] = { - OPT_INTEGER(0, "progress", &progress, - "show progress after objects"), - OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", - "select handling of signed tags", - parse_opt_signed_tag_mode), - OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode", - "select handling of tags that tag filtered objects", - parse_opt_tag_of_filtered_mode), - OPT_STRING(0, "export-marks", &export_filename, "FILE", - "Dump marks to this file"), - OPT_STRING(0, "import-marks", &import_filename, "FILE", - "Import marks from this file"), - OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, - "Fake a tagger when tags lack one"), - { OPTION_NEGBIT, 0, "data", &no_data, NULL, - "Skip output of blob data", - PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, - OPT_END() - }; - - if (argc == 1) - usage_with_options (fast_export_usage, options); - - /* we handle encodings */ - git_config(git_default_config, NULL); - - init_revisions(&revs, prefix); - revs.topo_order = 1; - revs.show_source = 1; - revs.rewrite_parents = 1; - argc = setup_revisions(argc, argv, &revs, NULL); - argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0); - if (argc > 1) - usage_with_options (fast_export_usage, options); - - if (import_filename) - import_marks(import_filename); - - get_tags_and_duplicates(&revs.pending, &extra_refs); - - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - revs.diffopt.format_callback = show_filemodify; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); - while ((commit = get_revision(&revs))) { - if (has_unshown_parent(commit)) { - add_object_array(&commit->object, NULL, &commits); - } - else { - handle_commit(commit, &revs); - handle_tail(&commits, &revs); - } - } - - handle_tags_and_duplicates(&extra_refs); - - if (export_filename) - export_marks(export_filename); - - return 0; -} diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c deleted file mode 100644 index dbd8b7bcc..000000000 --- a/builtin-fetch-pack.c +++ /dev/null @@ -1,976 +0,0 @@ -#include "cache.h" -#include "refs.h" -#include "pkt-line.h" -#include "commit.h" -#include "tag.h" -#include "exec_cmd.h" -#include "pack.h" -#include "sideband.h" -#include "fetch-pack.h" -#include "remote.h" -#include "run-command.h" - -static int transfer_unpack_limit = -1; -static int fetch_unpack_limit = -1; -static int unpack_limit = 100; -static int prefer_ofs_delta = 1; -static struct fetch_pack_args args = { - /* .uploadpack = */ "git-upload-pack", -}; - -static const char fetch_pack_usage[] = -"git fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=] [--depth=] [--no-progress] [-v] [:] [...]"; - -#define COMPLETE (1U << 0) -#define COMMON (1U << 1) -#define COMMON_REF (1U << 2) -#define SEEN (1U << 3) -#define POPPED (1U << 4) - -static int marked; - -/* - * After sending this many "have"s if we do not get any new ACK , we - * give up traversing our history. - */ -#define MAX_IN_VAIN 256 - -static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_sideband; - -static void rev_list_push(struct commit *commit, int mark) -{ - if (!(commit->object.flags & mark)) { - commit->object.flags |= mark; - - if (!(commit->object.parsed)) - if (parse_commit(commit)) - return; - - insert_by_date(commit, &rev_list); - - if (!(commit->object.flags & COMMON)) - non_common_revs++; - } -} - -static int rev_list_insert_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - rev_list_push((struct commit *)o, SEEN); - - return 0; -} - -static int clear_marks(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), path, 0); - - if (o && o->type == OBJ_COMMIT) - clear_commit_marks((struct commit *)o, - COMMON | COMMON_REF | SEEN | POPPED); - return 0; -} - -/* - This function marks a rev and its ancestors as common. - In some cases, it is desirable to mark only the ancestors (for example - when only the server does not yet know that they are common). -*/ - -static void mark_common(struct commit *commit, - int ancestors_only, int dont_parse) -{ - if (commit != NULL && !(commit->object.flags & COMMON)) { - struct object *o = (struct object *)commit; - - if (!ancestors_only) - o->flags |= COMMON; - - if (!(o->flags & SEEN)) - rev_list_push(commit, SEEN); - else { - struct commit_list *parents; - - if (!ancestors_only && !(o->flags & POPPED)) - non_common_revs--; - if (!o->parsed && !dont_parse) - if (parse_commit(commit)) - return; - - for (parents = commit->parents; - parents; - parents = parents->next) - mark_common(parents->item, 0, dont_parse); - } - } -} - -/* - Get the next rev to send, ignoring the common. -*/ - -static const unsigned char *get_rev(void) -{ - struct commit *commit = NULL; - - while (commit == NULL) { - unsigned int mark; - struct commit_list *parents; - - if (rev_list == NULL || non_common_revs == 0) - return NULL; - - commit = rev_list->item; - if (!commit->object.parsed) - parse_commit(commit); - parents = commit->parents; - - commit->object.flags |= POPPED; - if (!(commit->object.flags & COMMON)) - non_common_revs--; - - if (commit->object.flags & COMMON) { - /* do not send "have", and ignore ancestors */ - commit = NULL; - mark = COMMON | SEEN; - } else if (commit->object.flags & COMMON_REF) - /* send "have", and ignore ancestors */ - mark = COMMON | SEEN; - else - /* send "have", also for its ancestors */ - mark = SEEN; - - while (parents) { - if (!(parents->item->object.flags & SEEN)) - rev_list_push(parents->item, mark); - if (mark & COMMON) - mark_common(parents->item, 1, 0); - parents = parents->next; - } - - rev_list = rev_list->next; - } - - return commit->object.sha1; -} - -enum ack_type { - NAK = 0, - ACK, - ACK_continue, - ACK_common, - ACK_ready -}; - -static void consume_shallow_list(int fd) -{ - if (args.stateless_rpc && args.depth > 0) { - /* If we sent a depth we will get back "duplicate" - * shallow and unshallow commands every time there - * is a block of have lines exchanged. - */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) - continue; - if (!prefixcmp(line, "unshallow ")) - continue; - die("git fetch-pack: expected shallow list"); - } - } -} - -static enum ack_type get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return NAK; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return ACK_continue; - if (strstr(line+45, "common")) - return ACK_common; - if (strstr(line+45, "ready")) - return ACK_ready; - return ACK; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - -static void send_request(int fd, struct strbuf *buf) -{ - if (args.stateless_rpc) { - send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); - packet_flush(fd); - } else - safe_write(fd, buf->buf, buf->len); -} - -static int find_common(int fd[2], unsigned char *result_sha1, - struct ref *refs) -{ - int fetching; - int count = 0, flushes = 0, retval; - const unsigned char *sha1; - unsigned in_vain = 0; - int got_continue = 0; - struct strbuf req_buf = STRBUF_INIT; - size_t state_len = 0; - - if (args.stateless_rpc && multi_ack == 1) - die("--stateless-rpc requires multi_ack_detailed"); - if (marked) - for_each_ref(clear_marks, NULL); - marked = 1; - - for_each_ref(rev_list_insert_ref, NULL); - - fetching = 0; - for ( ; refs ; refs = refs->next) { - unsigned char *remote = refs->old_sha1; - const char *remote_hex; - struct object *o; - - /* - * If that object is complete (i.e. it is an ancestor of a - * local ref), we tell them we have it but do not have to - * tell them about its ancestors, which they already know - * about. - * - * We use lookup_object here because we are only - * interested in the case we *know* the object is - * reachable and we have already scanned it. - */ - if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { - continue; - } - - remote_hex = sha1_to_hex(remote); - if (!fetching) { - struct strbuf c = STRBUF_INIT; - if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); - if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); - if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); - if (use_sideband == 1) strbuf_addstr(&c, " side-band"); - if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); - if (args.no_progress) strbuf_addstr(&c, " no-progress"); - if (args.include_tag) strbuf_addstr(&c, " include-tag"); - if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); - packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); - strbuf_release(&c); - } else - packet_buf_write(&req_buf, "want %s\n", remote_hex); - fetching++; - } - - if (!fetching) { - strbuf_release(&req_buf); - packet_flush(fd[1]); - return 1; - } - - if (is_repository_shallow()) - write_shallow_commits(&req_buf, 1); - if (args.depth > 0) - packet_buf_write(&req_buf, "deepen %d", args.depth); - packet_buf_flush(&req_buf); - state_len = req_buf.len; - - if (args.depth > 0) { - char line[1024]; - unsigned char sha1[20]; - - send_request(fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) { - if (get_sha1_hex(line + 8, sha1)) - die("invalid shallow line: %s", line); - register_shallow(sha1); - continue; - } - if (!prefixcmp(line, "unshallow ")) { - if (get_sha1_hex(line + 10, sha1)) - die("invalid unshallow line: %s", line); - if (!lookup_object(sha1)) - die("object not found: %s", line); - /* make sure that it is parsed as shallow */ - if (!parse_object(sha1)) - die("error in object: %s", line); - if (unregister_shallow(sha1)) - die("no shallow found: %s", line); - continue; - } - die("expected shallow/unshallow, got %s", line); - } - } else if (!args.stateless_rpc) - send_request(fd[1], &req_buf); - - if (!args.stateless_rpc) { - /* If we aren't using the stateless-rpc interface - * we don't need to retain the headers. - */ - strbuf_setlen(&req_buf, 0); - state_len = 0; - } - - flushes = 0; - retval = -1; - while ((sha1 = get_rev())) { - packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); - if (args.verbose) - fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); - in_vain++; - if (!(31 & ++count)) { - int ack; - - packet_buf_flush(&req_buf); - send_request(fd[1], &req_buf); - strbuf_setlen(&req_buf, state_len); - flushes++; - - /* - * We keep one window "ahead" of the other side, and - * will wait for an ACK only on the next one - */ - if (!args.stateless_rpc && count == 32) - continue; - - consume_shallow_list(fd[0]); - do { - ack = get_ack(fd[0], result_sha1); - if (args.verbose && ack) - fprintf(stderr, "got ack %d %s\n", ack, - sha1_to_hex(result_sha1)); - switch (ack) { - case ACK: - flushes = 0; - multi_ack = 0; - retval = 0; - goto done; - case ACK_common: - case ACK_ready: - case ACK_continue: { - struct commit *commit = - lookup_commit(result_sha1); - if (args.stateless_rpc - && ack == ACK_common - && !(commit->object.flags & COMMON)) { - /* We need to replay the have for this object - * on the next RPC request so the peer knows - * it is in common with us. - */ - const char *hex = sha1_to_hex(result_sha1); - packet_buf_write(&req_buf, "have %s\n", hex); - state_len = req_buf.len; - } - mark_common(commit, 0, 1); - retval = 0; - in_vain = 0; - got_continue = 1; - break; - } - } - } while (ack); - flushes--; - if (got_continue && MAX_IN_VAIN < in_vain) { - if (args.verbose) - fprintf(stderr, "giving up\n"); - break; /* give up */ - } - } - } -done: - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); - if (args.verbose) - fprintf(stderr, "done\n"); - if (retval != 0) { - multi_ack = 0; - flushes++; - } - strbuf_release(&req_buf); - - consume_shallow_list(fd[0]); - while (flushes || multi_ack) { - int ack = get_ack(fd[0], result_sha1); - if (ack) { - if (args.verbose) - fprintf(stderr, "got ack (%d) %s\n", ack, - sha1_to_hex(result_sha1)); - if (ack == ACK) - return 0; - multi_ack = 1; - continue; - } - flushes--; - } - /* it is no error to fetch into a completely empty repo */ - return count ? retval : 0; -} - -static struct commit_list *complete; - -static int mark_complete(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = parse_object(sha1); - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o->flags |= COMPLETE; - o = parse_object(t->tagged->sha1); - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - commit->object.flags |= COMPLETE; - insert_by_date(commit, &complete); - } - return 0; -} - -static void mark_recent_complete_commits(unsigned long cutoff) -{ - while (complete && cutoff <= complete->item->date) { - if (args.verbose) - fprintf(stderr, "Marking %s as complete\n", - sha1_to_hex(complete->item->object.sha1)); - pop_most_recent_commit(&complete, COMPLETE); - } -} - -static void filter_refs(struct ref **refs, int nr_match, char **match) -{ - struct ref **return_refs; - struct ref *newlist = NULL; - struct ref **newtail = &newlist; - struct ref *ref, *next; - struct ref *fastarray[32]; - - if (nr_match && !args.fetch_all) { - if (ARRAY_SIZE(fastarray) < nr_match) - return_refs = xcalloc(nr_match, sizeof(struct ref *)); - else { - return_refs = fastarray; - memset(return_refs, 0, sizeof(struct ref *) * nr_match); - } - } - else - return_refs = NULL; - - for (ref = *refs; ref; ref = next) { - next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && - check_ref_format(ref->name + 5)) - ; /* trash */ - else if (args.fetch_all && - (!args.depth || prefixcmp(ref->name, "refs/tags/") )) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - continue; - } - else { - int order = path_match(ref->name, nr_match, match); - if (order) { - return_refs[order-1] = ref; - continue; /* we will link it later */ - } - } - free(ref); - } - - if (!args.fetch_all) { - int i; - for (i = 0; i < nr_match; i++) { - ref = return_refs[i]; - if (ref) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - } - } - if (return_refs != fastarray) - free(return_refs); - } - *refs = newlist; -} - -static int everything_local(struct ref **refs, int nr_match, char **match) -{ - struct ref *ref; - int retval; - unsigned long cutoff = 0; - - save_commit_buffer = 0; - - for (ref = *refs; ref; ref = ref->next) { - struct object *o; - - o = parse_object(ref->old_sha1); - if (!o) - continue; - - /* We already have it -- which may mean that we were - * in sync with the other side at some time after - * that (it is OK if we guess wrong here). - */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; - } - } - - if (!args.depth) { - for_each_ref(mark_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); - } - - /* - * Mark all complete remote refs as common refs. - * Don't mark them common yet; the server has to be told so first. - */ - for (ref = *refs; ref; ref = ref->next) { - struct object *o = deref_tag(lookup_object(ref->old_sha1), - NULL, 0); - - if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) - continue; - - if (!(o->flags & SEEN)) { - rev_list_push((struct commit *)o, COMMON_REF | SEEN); - - mark_common((struct commit *)o, 1, 1); - } - } - - filter_refs(refs, nr_match, match); - - for (retval = 1, ref = *refs; ref ; ref = ref->next) { - const unsigned char *remote = ref->old_sha1; - unsigned char local[20]; - struct object *o; - - o = lookup_object(remote); - if (!o || !(o->flags & COMPLETE)) { - retval = 0; - if (!args.verbose) - continue; - fprintf(stderr, - "want %s (%s)\n", sha1_to_hex(remote), - ref->name); - continue; - } - - hashcpy(ref->new_sha1, local); - if (!args.verbose) - continue; - fprintf(stderr, - "already have %s (%s)\n", sha1_to_hex(remote), - ref->name); - } - return retval; -} - -static int sideband_demux(int in, int out, void *data) -{ - int *xd = data; - - int ret = recv_sideband("fetch-pack", xd[0], out); - close(out); - return ret; -} - -static int get_pack(int xd[2], char **pack_lockfile) -{ - struct async demux; - const char *argv[20]; - char keep_arg[256]; - char hdr_arg[256]; - const char **av; - int do_keep = args.keep_pack; - struct child_process cmd; - - memset(&demux, 0, sizeof(demux)); - if (use_sideband) { - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux.proc = sideband_demux; - demux.data = xd; - demux.out = -1; - if (start_async(&demux)) - die("fetch-pack: unable to fork off sideband" - " demultiplexer"); - } - else - demux.out = xd[0]; - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = argv; - av = argv; - *hdr_arg = 0; - if (!args.keep_pack && unpack_limit) { - struct pack_header header; - - if (read_pack_header(demux.out, &header)) - die("protocol error: bad pack header"); - snprintf(hdr_arg, sizeof(hdr_arg), - "--pack_header=%"PRIu32",%"PRIu32, - ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (ntohl(header.hdr_entries) < unpack_limit) - do_keep = 0; - else - do_keep = 1; - } - - if (do_keep) { - if (pack_lockfile) - cmd.out = -1; - *av++ = "index-pack"; - *av++ = "--stdin"; - if (!args.quiet && !args.no_progress) - *av++ = "-v"; - if (args.use_thin_pack) - *av++ = "--fix-thin"; - if (args.lock_pack || unpack_limit) { - int s = sprintf(keep_arg, - "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); - *av++ = keep_arg; - } - } - else { - *av++ = "unpack-objects"; - if (args.quiet) - *av++ = "-q"; - } - if (*hdr_arg) - *av++ = hdr_arg; - *av++ = NULL; - - cmd.in = demux.out; - cmd.git_cmd = 1; - if (start_command(&cmd)) - die("fetch-pack: unable to fork off %s", argv[0]); - if (do_keep && pack_lockfile) { - *pack_lockfile = index_pack_lockfile(cmd.out); - close(cmd.out); - } - - if (finish_command(&cmd)) - die("%s failed", argv[0]); - if (use_sideband && finish_async(&demux)) - die("error in sideband demultiplexer"); - return 0; -} - -static struct ref *do_fetch_pack(int fd[2], - const struct ref *orig_ref, - int nr_match, - char **match, - char **pack_lockfile) -{ - struct ref *ref = copy_ref_list(orig_ref); - unsigned char sha1[20]; - - if (is_repository_shallow() && !server_supports("shallow")) - die("Server does not support shallow clients"); - if (server_supports("multi_ack_detailed")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack_detailed\n"); - multi_ack = 2; - } - else if (server_supports("multi_ack")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack\n"); - multi_ack = 1; - } - if (server_supports("side-band-64k")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band-64k\n"); - use_sideband = 2; - } - else if (server_supports("side-band")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band\n"); - use_sideband = 1; - } - if (server_supports("ofs-delta")) { - if (args.verbose) - fprintf(stderr, "Server supports ofs-delta\n"); - } else - prefer_ofs_delta = 0; - if (everything_local(&ref, nr_match, match)) { - packet_flush(fd[1]); - goto all_done; - } - if (find_common(fd, sha1, ref) < 0) - if (!args.keep_pack) - /* When cloning, it is not unusual to have - * no common commit. - */ - warning("no common commits"); - - if (args.stateless_rpc) - packet_flush(fd[1]); - if (get_pack(fd, pack_lockfile)) - die("git fetch-pack: fetch failed."); - - all_done: - return ref; -} - -static int remove_duplicates(int nr_heads, char **heads) -{ - int src, dst; - - for (src = dst = 0; src < nr_heads; src++) { - /* If heads[src] is different from any of - * heads[0..dst], push it in. - */ - int i; - for (i = 0; i < dst; i++) { - if (!strcmp(heads[i], heads[src])) - break; - } - if (i < dst) - continue; - if (src != dst) - heads[dst] = heads[src]; - dst++; - } - return dst; -} - -static int fetch_pack_config(const char *var, const char *value, void *cb) -{ - if (strcmp(var, "fetch.unpacklimit") == 0) { - fetch_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "transfer.unpacklimit") == 0) { - transfer_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "repack.usedeltabaseoffset") == 0) { - prefer_ofs_delta = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - -static struct lock_file lock; - -static void fetch_pack_setup(void) -{ - static int did_setup; - if (did_setup) - return; - git_config(fetch_pack_config, NULL); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= fetch_unpack_limit) - unpack_limit = fetch_unpack_limit; - did_setup = 1; -} - -int cmd_fetch_pack(int argc, const char **argv, const char *prefix) -{ - int i, ret, nr_heads; - struct ref *ref = NULL; - char *dest = NULL, **heads; - int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; - struct child_process *conn; - - nr_heads = 0; - heads = NULL; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - args.uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - args.uploadpack = arg + 7; - continue; - } - if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) { - args.quiet = 1; - continue; - } - if (!strcmp("--keep", arg) || !strcmp("-k", arg)) { - args.lock_pack = args.keep_pack; - args.keep_pack = 1; - continue; - } - if (!strcmp("--thin", arg)) { - args.use_thin_pack = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - args.include_tag = 1; - continue; - } - if (!strcmp("--all", arg)) { - args.fetch_all = 1; - continue; - } - if (!strcmp("-v", arg)) { - args.verbose = 1; - continue; - } - if (!prefixcmp(arg, "--depth=")) { - args.depth = strtol(arg + 8, NULL, 0); - continue; - } - if (!strcmp("--no-progress", arg)) { - args.no_progress = 1; - continue; - } - if (!strcmp("--stateless-rpc", arg)) { - args.stateless_rpc = 1; - continue; - } - if (!strcmp("--lock-pack", arg)) { - args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; - continue; - } - usage(fetch_pack_usage); - } - dest = (char *)arg; - heads = (char **)(argv + i + 1); - nr_heads = argc - i - 1; - break; - } - if (!dest) - usage(fetch_pack_usage); - - if (args.stateless_rpc) { - conn = NULL; - fd[0] = 0; - fd[1] = 1; - } else { - conn = git_connect(fd, (char *)dest, args.uploadpack, - args.verbose ? CONNECT_VERBOSE : 0); - } - - get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL); - - ref = fetch_pack(&args, fd, conn, ref, dest, - nr_heads, heads, pack_lockfile_ptr); - if (pack_lockfile) { - printf("lock %s\n", pack_lockfile); - fflush(stdout); - } - close(fd[0]); - close(fd[1]); - if (finish_connect(conn)) - ref = NULL; - ret = !ref; - - if (!ret && nr_heads) { - /* If the heads to pull were given, we should have - * consumed all of them by matching the remote. - * Otherwise, 'git fetch remote no-such-ref' would - * silently succeed without issuing an error. - */ - for (i = 0; i < nr_heads; i++) - if (heads[i] && heads[i][0]) { - error("no such remote ref %s", heads[i]); - ret = 1; - } - } - while (ref) { - printf("%s %s\n", - sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; - } - - return ret; -} - -struct ref *fetch_pack(struct fetch_pack_args *my_args, - int fd[], struct child_process *conn, - const struct ref *ref, - const char *dest, - int nr_heads, - char **heads, - char **pack_lockfile) -{ - struct stat st; - struct ref *ref_cpy; - - fetch_pack_setup(); - if (&args != my_args) - memcpy(&args, my_args, sizeof(args)); - if (args.depth > 0) { - if (stat(git_path("shallow"), &st)) - st.st_mtime = 0; - } - - if (heads && nr_heads) - nr_heads = remove_duplicates(nr_heads, heads); - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } - ref_cpy = do_fetch_pack(fd, ref, nr_heads, heads, pack_lockfile); - - if (args.depth > 0) { - struct cache_time mtime; - struct strbuf sb = STRBUF_INIT; - char *shallow = git_path("shallow"); - int fd; - - mtime.sec = st.st_mtime; - mtime.nsec = ST_MTIME_NSEC(st); - if (stat(shallow, &st)) { - if (mtime.sec) - die("shallow file was removed during fetch"); - } else if (st.st_mtime != mtime.sec -#ifdef USE_NSEC - || ST_MTIME_NSEC(st) != mtime.nsec -#endif - ) - die("shallow file was changed during fetch"); - - fd = hold_lock_file_for_update(&lock, shallow, - LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(&sb, 0) - || write_in_full(fd, sb.buf, sb.len) != sb.len) { - unlink_or_warn(shallow); - rollback_lock_file(&lock); - } else { - commit_lock_file(&lock); - } - strbuf_release(&sb); - } - - reprepare_packed_git(); - return ref_cpy; -} diff --git a/builtin-fetch.c b/builtin-fetch.c deleted file mode 100644 index 8654fa7a2..000000000 --- a/builtin-fetch.c +++ /dev/null @@ -1,920 +0,0 @@ -/* - * "git fetch" - */ -#include "cache.h" -#include "refs.h" -#include "commit.h" -#include "builtin.h" -#include "string-list.h" -#include "remote.h" -#include "transport.h" -#include "run-command.h" -#include "parse-options.h" -#include "sigchain.h" - -static const char * const builtin_fetch_usage[] = { - "git fetch [options] [ ...]", - "git fetch [options] ", - "git fetch --multiple [options] [ | ]...", - "git fetch --all [options]", - NULL -}; - -enum { - TAGS_UNSET = 0, - TAGS_DEFAULT = 1, - TAGS_SET = 2 -}; - -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int tags = TAGS_DEFAULT; -static const char *depth; -static const char *upload_pack; -static struct strbuf default_rla = STRBUF_INIT; -static struct transport *transport; - -static struct option builtin_fetch_options[] = { - OPT__VERBOSITY(&verbosity), - OPT_BOOLEAN(0, "all", &all, - "fetch from all remotes"), - OPT_BOOLEAN('a', "append", &append, - "append to .git/FETCH_HEAD instead of overwriting"), - OPT_STRING(0, "upload-pack", &upload_pack, "PATH", - "path to upload pack on remote end"), - OPT_BOOLEAN('f', "force", &force, - "force overwrite of local branch"), - OPT_BOOLEAN('m', "multiple", &multiple, - "fetch from multiple remotes"), - OPT_SET_INT('t', "tags", &tags, - "fetch all tags and associated objects", TAGS_SET), - OPT_SET_INT('n', NULL, &tags, - "do not fetch all tags (--no-tags)", TAGS_UNSET), - OPT_BOOLEAN('p', "prune", &prune, - "prune tracking branches no longer on remote"), - OPT_BOOLEAN(0, "dry-run", &dry_run, - "dry run"), - OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), - OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, - "allow updating of HEAD ref"), - OPT_STRING(0, "depth", &depth, "DEPTH", - "deepen history of shallow clone"), - OPT_END() -}; - -static void unlock_pack(void) -{ - if (transport) - transport_unlock_pack(transport); -} - -static void unlock_pack_on_signal(int signo) -{ - unlock_pack(); - sigchain_pop(signo); - raise(signo); -} - -static void add_merge_config(struct ref **head, - const struct ref *remote_refs, - struct branch *branch, - struct ref ***tail) -{ - int i; - - for (i = 0; i < branch->merge_nr; i++) { - struct ref *rm, **old_tail = *tail; - struct refspec refspec; - - for (rm = *head; rm; rm = rm->next) { - if (branch_merge_matches(branch, i, rm->name)) { - rm->merge = 1; - break; - } - } - if (rm) - continue; - - /* - * Not fetched to a tracking branch? We need to fetch - * it anyway to allow this branch's "branch.$name.merge" - * to be honored by 'git pull', but we do not have to - * fail if branch.$name.merge is misconfigured to point - * at a nonexisting branch. If we were indeed called by - * 'git pull', it will notice the misconfiguration because - * there is no entry in the resulting FETCH_HEAD marked - * for merging. - */ - refspec.src = branch->merge[i]->src; - refspec.dst = NULL; - refspec.pattern = 0; - refspec.force = 0; - get_fetch_map(remote_refs, &refspec, tail, 1); - for (rm = *old_tail; rm; rm = rm->next) - rm->merge = 1; - } -} - -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail); - -static struct ref *get_ref_map(struct transport *transport, - struct refspec *refs, int ref_count, int tags, - int *autotags) -{ - int i; - struct ref *rm; - struct ref *ref_map = NULL; - struct ref **tail = &ref_map; - - const struct ref *remote_refs = transport_get_remote_refs(transport); - - if (ref_count || tags == TAGS_SET) { - for (i = 0; i < ref_count; i++) { - get_fetch_map(remote_refs, &refs[i], &tail, 0); - if (refs[i].dst && refs[i].dst[0]) - *autotags = 1; - } - /* Merge everything on the command line, but not --tags */ - for (rm = ref_map; rm; rm = rm->next) - rm->merge = 1; - if (tags == TAGS_SET) - get_fetch_map(remote_refs, tag_refspec, &tail, 0); - } else { - /* Use the defaults */ - struct remote *remote = transport->remote; - struct branch *branch = branch_get(NULL); - int has_merge = branch_has_merge_config(branch); - if (remote && (remote->fetch_refspec_nr || has_merge)) { - for (i = 0; i < remote->fetch_refspec_nr; i++) { - get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); - if (remote->fetch[i].dst && - remote->fetch[i].dst[0]) - *autotags = 1; - if (!i && !has_merge && ref_map && - !remote->fetch[0].pattern) - ref_map->merge = 1; - } - /* - * if the remote we're fetching from is the same - * as given in branch..remote, we add the - * ref given in branch..merge, too. - */ - if (has_merge && - !strcmp(branch->remote_name, remote->name)) - add_merge_config(&ref_map, remote_refs, branch, &tail); - } else { - ref_map = get_remote_ref(remote_refs, "HEAD"); - if (!ref_map) - die("Couldn't find remote ref HEAD"); - ref_map->merge = 1; - tail = &ref_map->next; - } - } - if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(transport, &ref_map, &tail); - ref_remove_duplicates(ref_map); - - return ref_map; -} - -#define STORE_REF_ERROR_OTHER 1 -#define STORE_REF_ERROR_DF_CONFLICT 2 - -static int s_update_ref(const char *action, - struct ref *ref, - int check_old) -{ - char msg[1024]; - char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; - - if (dry_run) - return 0; - if (!rla) - rla = default_rla.buf; - snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, 0); - if (!lock) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : - STORE_REF_ERROR_OTHER; - return 0; -} - -#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) -#define REFCOL_WIDTH 10 - -static int update_local_ref(struct ref *ref, - const char *remote, - char *display) -{ - struct commit *current = NULL, *updated; - enum object_type type; - struct branch *current_branch = branch_get(NULL); - const char *pretty_ref = prettify_refname(ref->name); - - *display = 0; - type = sha1_object_info(ref->new_sha1, NULL); - if (type < 0) - die("object %s not found", sha1_to_hex(ref->new_sha1)); - - if (!hashcmp(ref->old_sha1, ref->new_sha1)) { - if (verbosity > 0) - sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH, - "[up to date]", REFCOL_WIDTH, remote, - pretty_ref); - return 0; - } - - if (current_branch && - !strcmp(ref->name, current_branch->name) && - !(update_head_ok || is_bare_repository()) && - !is_null_sha1(ref->old_sha1)) { - /* - * If this is the head, and it's not okay to update - * 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, - pretty_ref); - return 1; - } - - if (!is_null_sha1(ref->old_sha1) && - !prefixcmp(ref->name, "refs/tags/")) { - 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, - pretty_ref, r ? " (unable to update local ref)" : ""); - return r; - } - - current = lookup_commit_reference_gently(ref->old_sha1, 1); - updated = lookup_commit_reference_gently(ref->new_sha1, 1); - if (!current || !updated) { - const char *msg; - const char *what; - int r; - if (!strncmp(ref->name, "refs/tags/", 10)) { - msg = "storing tag"; - what = "[new tag]"; - } - else { - msg = "storing head"; - what = "[new branch]"; - } - - r = s_update_ref(msg, ref, 0); - sprintf(display, "%c %-*s %-*s -> %s%s", r ? '!' : '*', - SUMMARY_WIDTH, what, REFCOL_WIDTH, remote, pretty_ref, - r ? " (unable to update local ref)" : ""); - return r; - } - - if (in_merge_bases(current, &updated, 1)) { - char quickref[83]; - int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, ".."); - 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, - pretty_ref, r ? " (unable to update local ref)" : ""); - return r; - } else if (force || ref->force) { - char quickref[84]; - int r; - strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); - strcat(quickref, "..."); - 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, - 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, - pretty_ref); - return 1; - } -} - -static int store_updated_refs(const char *raw_url, const char *remote_name, - struct ref *ref_map) -{ - FILE *fp; - struct commit *commit; - int url_len, i, note_len, shown_url = 0, rc = 0; - char note[1024]; - const char *what, *kind; - struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); - - fp = fopen(filename, "a"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - - if (raw_url) - url = transport_anonymize_url(raw_url); - else - url = xstrdup("foreign"); - for (rm = ref_map; rm; rm = rm->next) { - struct ref *ref = NULL; - - if (rm->peer_ref) { - ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); - strcpy(ref->name, rm->peer_ref->name); - hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); - hashcpy(ref->new_sha1, rm->old_sha1); - ref->force = rm->peer_ref->force; - } - - commit = lookup_commit_reference_gently(rm->old_sha1, 1); - if (!commit) - rm->merge = 0; - - if (!strcmp(rm->name, "HEAD")) { - kind = ""; - what = ""; - } - else if (!prefixcmp(rm->name, "refs/heads/")) { - kind = "branch"; - what = rm->name + 11; - } - else if (!prefixcmp(rm->name, "refs/tags/")) { - kind = "tag"; - what = rm->name + 10; - } - else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote branch"; - what = rm->name + 13; - } - else { - kind = ""; - what = rm->name; - } - - url_len = strlen(url); - for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) - ; - url_len = i + 1; - if (4 < i && !strncmp(".git", url + i - 3, 4)) - url_len = i - 3; - - note_len = 0; - if (*what) { - if (*kind) - note_len += sprintf(note + note_len, "%s ", - kind); - note_len += sprintf(note + note_len, "'%s' of ", what); - } - note[note_len] = '\0'; - fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(commit ? commit->object.sha1 : - rm->old_sha1), - rm->merge ? "" : "not-for-merge", - note); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - - if (ref) - rc |= update_local_ref(ref, what, note); - else - sprintf(note, "* %-*s %-*s -> FETCH_HEAD", - SUMMARY_WIDTH, *kind ? kind : "branch", - REFCOL_WIDTH, *what ? what : "HEAD"); - if (*note) { - if (verbosity >= 0 && !shown_url) { - fprintf(stderr, "From %.*s\n", - url_len, url); - shown_url = 1; - } - if (verbosity >= 0) - fprintf(stderr, " %s\n", note); - } - } - free(url); - fclose(fp); - if (rc & STORE_REF_ERROR_DF_CONFLICT) - error("some local refs could not be updated; try running\n" - " 'git remote prune %s' to remove any old, conflicting " - "branches", remote_name); - return rc; -} - -/* - * We would want to bypass the object transfer altogether if - * everything we are going to fetch already exists and is connected - * locally. - * - * The refs we are going to fetch are in ref_map. If running - * - * $ git rev-list --objects --stdin --not --all - * - * (feeding all the refs in ref_map on its standard input) - * does not error out, that means everything reachable from the - * refs we are going to fetch exists and is connected to some of - * our existing refs. - */ -static int quickfetch(struct ref *ref_map) -{ - struct child_process revlist; - struct ref *ref; - int err; - const char *argv[] = {"rev-list", - "--quiet", "--objects", "--stdin", "--not", "--all", NULL}; - - /* - * If we are deepening a shallow clone we already have these - * objects reachable. Running rev-list here will return with - * a good (0) exit status and we'll bypass the fetch that we - * really need to perform. Claiming failure now will ensure - * we perform the network exchange to deepen our history. - */ - if (depth) - return -1; - - if (!ref_map) - return 0; - - memset(&revlist, 0, sizeof(revlist)); - revlist.argv = argv; - revlist.git_cmd = 1; - revlist.no_stdout = 1; - revlist.no_stderr = 1; - revlist.in = -1; - - err = start_command(&revlist); - if (err) { - error("could not run rev-list"); - return err; - } - - /* - * If rev-list --stdin encounters an unknown commit, it terminates, - * which will cause SIGPIPE in the write loop below. - */ - sigchain_push(SIGPIPE, SIG_IGN); - - for (ref = ref_map; ref; ref = ref->next) { - if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_str_in_full(revlist.in, "\n") < 0) { - if (errno != EPIPE && errno != EINVAL) - error("failed write to rev-list: %s", strerror(errno)); - err = -1; - break; - } - } - - if (close(revlist.in)) { - error("failed to close rev-list's stdin: %s", strerror(errno)); - err = -1; - } - - sigchain_pop(SIGPIPE); - - return finish_command(&revlist) || err; -} - -static int fetch_refs(struct transport *transport, struct ref *ref_map) -{ - int ret = quickfetch(ref_map); - if (ret) - ret = transport_fetch_refs(transport, ref_map); - if (!ret) - ret |= store_updated_refs(transport->url, - transport->remote->name, - ref_map); - transport_unlock_pack(transport); - return ret; -} - -static int prune_refs(struct transport *transport, struct ref *ref_map) -{ - int result = 0; - struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map); - const char *dangling_msg = dry_run - ? " (%s will become dangling)\n" - : " (%s has become dangling)\n"; - - for (ref = stale_refs; ref; ref = ref->next) { - if (!dry_run) - result |= delete_ref(ref->name, NULL, 0); - if (verbosity >= 0) { - fprintf(stderr, " x %-*s %-*s -> %s\n", - SUMMARY_WIDTH, "[deleted]", - REFCOL_WIDTH, "(none)", prettify_refname(ref->name)); - warn_dangling_symref(stderr, dangling_msg, ref->name); - } - } - free_refs(stale_refs); - return result; -} - -static int add_existing(const char *refname, const unsigned char *sha1, - int flag, void *cbdata) -{ - struct string_list *list = (struct string_list *)cbdata; - struct string_list_item *item = string_list_insert(refname, list); - item->util = (void *)sha1; - return 0; -} - -static int will_fetch(struct ref **head, const unsigned char *sha1) -{ - struct ref *rm = *head; - while (rm) { - if (!hashcmp(rm->old_sha1, sha1)) - return 1; - rm = rm->next; - } - return 0; -} - -struct tag_data { - struct ref **head; - struct ref ***tail; -}; - -static int add_to_tail(struct string_list_item *item, void *cb_data) -{ - struct tag_data *data = (struct tag_data *)cb_data; - struct ref *rm = NULL; - - /* We have already decided to ignore this item */ - if (!item->util) - return 0; - - rm = alloc_ref(item->string); - rm->peer_ref = alloc_ref(item->string); - hashcpy(rm->old_sha1, item->util); - - **data->tail = rm; - *data->tail = &rm->next; - - return 0; -} - -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail) -{ - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list remote_refs = { NULL, 0, 0, 0 }; - struct tag_data data = {head, tail}; - const struct ref *ref; - struct string_list_item *item = NULL; - - for_each_ref(add_existing, &existing_refs); - for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { - if (prefixcmp(ref->name, "refs/tags")) - continue; - - /* - * The peeled ref always follows the matching base - * ref, so if we see a peeled ref that we don't want - * to fetch then we can mark the ref entry in the list - * as one to ignore by setting util to NULL. - */ - if (!strcmp(ref->name + strlen(ref->name) - 3, "^{}")) { - if (item && !has_sha1_file(ref->old_sha1) && - !will_fetch(head, ref->old_sha1) && - !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - item = NULL; - continue; - } - - /* - * If item is non-NULL here, then we previously saw a - * ref not followed by a peeled reference, so we need - * to check if it is a lightweight tag that we want to - * fetch. - */ - if (item && !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - - item = NULL; - - /* skip duplicates and refs that we already have */ - if (string_list_has_string(&remote_refs, ref->name) || - string_list_has_string(&existing_refs, ref->name)) - continue; - - item = string_list_insert(ref->name, &remote_refs); - item->util = (void *)ref->old_sha1; - } - string_list_clear(&existing_refs, 0); - - /* - * We may have a final lightweight tag that needs to be - * checked to see if it needs fetching. - */ - if (item && !has_sha1_file(item->util) && - !will_fetch(head, item->util)) - item->util = NULL; - - /* - * For all the tags in the remote_refs string list, call - * add_to_tail to add them to the list of refs to be fetched - */ - for_each_string_list(add_to_tail, &remote_refs, &data); - - string_list_clear(&remote_refs, 0); -} - -static void check_not_current_branch(struct ref *ref_map) -{ - struct branch *current_branch = branch_get(NULL); - - if (is_bare_repository() || !current_branch) - return; - - for (; ref_map; ref_map = ref_map->next) - if (ref_map->peer_ref && !strcmp(current_branch->refname, - ref_map->peer_ref->name)) - die("Refusing to fetch into current branch %s " - "of non-bare repository", current_branch->refname); -} - -static int do_fetch(struct transport *transport, - struct refspec *refs, int ref_count) -{ - struct string_list existing_refs = { NULL, 0, 0, 0 }; - struct string_list_item *peer_item = NULL; - struct ref *ref_map; - struct ref *rm; - int autotags = (transport->remote->fetch_tags == 1); - - for_each_ref(add_existing, &existing_refs); - - if (transport->remote->fetch_tags == 2 && tags != TAGS_UNSET) - tags = TAGS_SET; - if (transport->remote->fetch_tags == -1) - tags = TAGS_UNSET; - - if (!transport->get_refs_list || !transport->fetch) - die("Don't know how to fetch from %s", transport->url); - - /* if not appending, truncate FETCH_HEAD */ - if (!append && !dry_run) { - char *filename = git_path("FETCH_HEAD"); - FILE *fp = fopen(filename, "w"); - if (!fp) - return error("cannot open %s: %s\n", filename, strerror(errno)); - fclose(fp); - } - - ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); - if (!update_head_ok) - check_not_current_branch(ref_map); - - for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) { - peer_item = string_list_lookup(rm->peer_ref->name, - &existing_refs); - if (peer_item) - hashcpy(rm->peer_ref->old_sha1, - peer_item->util); - } - } - - if (tags == TAGS_DEFAULT && autotags) - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - if (fetch_refs(transport, ref_map)) { - free_refs(ref_map); - return 1; - } - if (prune) - prune_refs(transport, ref_map); - free_refs(ref_map); - - /* if neither --no-tags nor --tags was specified, do automated tag - * following ... */ - if (tags == TAGS_DEFAULT && autotags) { - struct ref **tail = &ref_map; - ref_map = NULL; - find_non_local_tags(transport, &ref_map, &tail); - if (ref_map) { - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); - transport_set_option(transport, TRANS_OPT_DEPTH, "0"); - fetch_refs(transport, ref_map); - } - free_refs(ref_map); - } - - return 0; -} - -static void set_option(const char *name, const char *value) -{ - int r = transport_set_option(transport, name, value); - if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s", - name, value, transport->url); - if (r > 0) - warning("Option \"%s\" is ignored for %s\n", - name, transport->url); -} - -static int get_one_remote_for_fetch(struct remote *remote, void *priv) -{ - struct string_list *list = priv; - if (!remote->skip_default_update) - string_list_append(remote->name, list); - return 0; -} - -struct remote_group_data { - const char *name; - struct string_list *list; -}; - -static int get_remote_group(const char *key, const char *value, void *priv) -{ - struct remote_group_data *g = priv; - - if (!prefixcmp(key, "remotes.") && - !strcmp(key + 8, g->name)) { - /* split list by white space */ - int space = strcspn(value, " \t\n"); - while (*value) { - if (space > 1) { - string_list_append(xstrndup(value, space), - g->list); - } - value += space + (value[space] != '\0'); - space = strcspn(value, " \t\n"); - } - } - - return 0; -} - -static int add_remote_or_group(const char *name, struct string_list *list) -{ - int prev_nr = list->nr; - struct remote_group_data g = { name, list }; - - git_config(get_remote_group, &g); - if (list->nr == prev_nr) { - struct remote *remote; - if (!remote_is_configured(name)) - return 0; - remote = remote_get(name); - string_list_append(remote->name, list); - } - return 1; -} - -static int fetch_multiple(struct string_list *list) -{ - int i, result = 0; - const char *argv[] = { "fetch", NULL, NULL, NULL, NULL, NULL, NULL }; - int argc = 1; - - if (dry_run) - argv[argc++] = "--dry-run"; - if (prune) - argv[argc++] = "--prune"; - if (verbosity >= 2) - argv[argc++] = "-v"; - if (verbosity >= 1) - argv[argc++] = "-v"; - else if (verbosity < 0) - argv[argc++] = "-q"; - - for (i = 0; i < list->nr; i++) { - const char *name = list->items[i].string; - argv[argc] = name; - if (verbosity >= 0) - printf("Fetching %s\n", name); - if (run_command_v_opt(argv, RUN_GIT_CMD)) { - error("Could not fetch %s", name); - result = 1; - } - } - - return result; -} - -static int fetch_one(struct remote *remote, int argc, const char **argv) -{ - int i; - static const char **refs = NULL; - int ref_nr = 0; - int exit_code; - - if (!remote) - die("Where do you want to fetch from today?"); - - transport = transport_get(remote, NULL); - if (verbosity >= 2) - transport->verbose = verbosity <= 3 ? verbosity : 3; - if (verbosity < 0) - transport->verbose = -1; - if (upload_pack) - set_option(TRANS_OPT_UPLOADPACK, upload_pack); - if (keep) - set_option(TRANS_OPT_KEEP, "yes"); - if (depth) - set_option(TRANS_OPT_DEPTH, depth); - - if (argc > 0) { - int j = 0; - refs = xcalloc(argc + 1, sizeof(const char *)); - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "tag")) { - char *ref; - i++; - if (i >= argc) - die("You need to specify a tag name."); - ref = xmalloc(strlen(argv[i]) * 2 + 22); - strcpy(ref, "refs/tags/"); - strcat(ref, argv[i]); - strcat(ref, ":refs/tags/"); - strcat(ref, argv[i]); - refs[j++] = ref; - } else - refs[j++] = argv[i]; - } - refs[j] = NULL; - ref_nr = j; - } - - sigchain_push_common(unlock_pack_on_signal); - atexit(unlock_pack); - exit_code = do_fetch(transport, - parse_fetch_refspec(ref_nr, refs), ref_nr); - transport_disconnect(transport); - transport = NULL; - return exit_code; -} - -int cmd_fetch(int argc, const char **argv, const char *prefix) -{ - int i; - struct string_list list = { NULL, 0, 0, 0 }; - struct remote *remote; - int result = 0; - - /* Record the command line for the reflog */ - strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); - - argc = parse_options(argc, argv, prefix, - builtin_fetch_options, builtin_fetch_usage, 0); - - if (all) { - if (argc == 1) - die("fetch --all does not take a repository argument"); - else if (argc > 1) - die("fetch --all does not make sense with refspecs"); - (void) for_each_remote(get_one_remote_for_fetch, &list); - result = fetch_multiple(&list); - } else if (argc == 0) { - /* No arguments -- use default remote */ - remote = remote_get(NULL); - result = fetch_one(remote, argc, argv); - } else if (multiple) { - /* All arguments are assumed to be remotes or groups */ - for (i = 0; i < argc; i++) - if (!add_remote_or_group(argv[i], &list)) - die("No such remote or remote group: %s", argv[i]); - result = fetch_multiple(&list); - } else { - /* Single remote or group */ - (void) add_remote_or_group(argv[0], &list); - if (list.nr > 1) { - /* More than one remote */ - if (argc > 1) - die("Fetching a group and specifying refspecs does not make sense"); - result = fetch_multiple(&list); - } else { - /* Zero or one remotes */ - remote = remote_get(argv[0]); - result = fetch_one(remote, argc-1, argv+1); - } - } - - /* All names were strdup()ed or strndup()ed */ - list.strdup_strings = 1; - string_list_clear(&list, 0); - - return result; -} diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c deleted file mode 100644 index 9d524000b..000000000 --- a/builtin-fmt-merge-msg.c +++ /dev/null @@ -1,381 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "tag.h" - -static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [--log|--no-log] [--file ]", - NULL -}; - -static int merge_summary; - -static int fmt_merge_msg_config(const char *key, const char *value, void *cb) -{ - static int found_merge_log = 0; - if (!strcmp("merge.log", key)) { - found_merge_log = 1; - merge_summary = git_config_bool(key, value); - } - if (!found_merge_log && !strcmp("merge.summary", key)) - merge_summary = git_config_bool(key, value); - return 0; -} - -struct list { - char **list; - void **payload; - unsigned nr, alloc; -}; - -static void append_to_list(struct list *list, char *value, void *payload) -{ - if (list->nr == list->alloc) { - list->alloc += 32; - list->list = xrealloc(list->list, sizeof(char *) * list->alloc); - list->payload = xrealloc(list->payload, - sizeof(char *) * list->alloc); - } - list->payload[list->nr] = payload; - list->list[list->nr++] = value; -} - -static int find_in_list(struct list *list, char *value) -{ - int i; - - for (i = 0; i < list->nr; i++) - if (!strcmp(list->list[i], value)) - return i; - - return -1; -} - -static void free_list(struct list *list) -{ - int i; - - if (list->alloc == 0) - return; - - for (i = 0; i < list->nr; i++) { - free(list->list[i]); - free(list->payload[i]); - } - free(list->list); - free(list->payload); - list->nr = list->alloc = 0; -} - -struct src_data { - struct list branch, tag, r_branch, generic; - int head_status; -}; - -static struct list srcs = { NULL, NULL, 0, 0}; -static struct list origins = { NULL, NULL, 0, 0}; - -static int handle_line(char *line) -{ - int i, len = strlen(line); - unsigned char *sha1; - char *src, *origin; - struct src_data *src_data; - int pulling_head = 0; - - if (len < 43 || line[40] != '\t') - return 1; - - if (!prefixcmp(line + 41, "not-for-merge")) - return 0; - - if (line[41] != '\t') - return 2; - - line[40] = 0; - sha1 = xmalloc(20); - i = get_sha1(line, sha1); - line[40] = '\t'; - if (i) - return 3; - - if (line[len - 1] == '\n') - line[len - 1] = 0; - line += 42; - - src = strstr(line, " of "); - if (src) { - *src = 0; - src += 4; - pulling_head = 0; - } else { - src = line; - pulling_head = 1; - } - - i = find_in_list(&srcs, src); - if (i < 0) { - i = srcs.nr; - append_to_list(&srcs, xstrdup(src), - xcalloc(1, sizeof(struct src_data))); - } - src_data = srcs.payload[i]; - - if (pulling_head) { - origin = xstrdup(src); - src_data->head_status |= 1; - } else if (!prefixcmp(line, "branch ")) { - origin = xstrdup(line + 7); - append_to_list(&src_data->branch, origin, NULL); - src_data->head_status |= 2; - } else if (!prefixcmp(line, "tag ")) { - origin = line; - append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); - src_data->head_status |= 2; - } else if (!prefixcmp(line, "remote branch ")) { - origin = xstrdup(line + 14); - append_to_list(&src_data->r_branch, origin, NULL); - src_data->head_status |= 2; - } else { - origin = xstrdup(src); - append_to_list(&src_data->generic, xstrdup(line), NULL); - src_data->head_status |= 2; - } - - if (!strcmp(".", src) || !strcmp(src, origin)) { - int len = strlen(origin); - if (origin[0] == '\'' && origin[len - 1] == '\'') { - origin = xmemdupz(origin + 1, len - 2); - } else { - origin = xstrdup(origin); - } - } else { - char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); - sprintf(new_origin, "%s of %s", origin, src); - origin = new_origin; - } - append_to_list(&origins, origin, sha1); - return 0; -} - -static void print_joined(const char *singular, const char *plural, - struct list *list, struct strbuf *out) -{ - if (list->nr == 0) - return; - if (list->nr == 1) { - strbuf_addf(out, "%s%s", singular, list->list[0]); - } else { - int i; - strbuf_addstr(out, plural); - for (i = 0; i < list->nr - 1; i++) - strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]); - strbuf_addf(out, " and %s", list->list[list->nr - 1]); - } -} - -static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit, - struct strbuf *out) -{ - int i, count = 0; - struct commit *commit; - struct object *branch; - struct list subjects = { NULL, NULL, 0, 0 }; - int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; - - branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); - if (!branch || branch->type != OBJ_COMMIT) - return; - - setup_revisions(0, NULL, rev, NULL); - rev->ignore_merges = 1; - add_pending_object(rev, branch, name); - add_pending_object(rev, &head->object, "^HEAD"); - head->object.flags |= UNINTERESTING; - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - while ((commit = get_revision(rev)) != NULL) { - char *oneline, *bol, *eol; - - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - count++; - if (subjects.nr > limit) - continue; - - bol = strstr(commit->buffer, "\n\n"); - if (bol) { - unsigned char c; - do { - c = *++bol; - } while (isspace(c)); - if (!c) - bol = NULL; - } - - if (!bol) { - append_to_list(&subjects, xstrdup(sha1_to_hex( - commit->object.sha1)), - NULL); - continue; - } - - eol = strchr(bol, '\n'); - if (eol) { - oneline = xmemdupz(bol, eol - bol); - } else { - oneline = xstrdup(bol); - } - append_to_list(&subjects, oneline, NULL); - } - - if (count > limit) - strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); - else - strbuf_addf(out, "\n* %s:\n", name); - - for (i = 0; i < subjects.nr; i++) - if (i >= limit) - strbuf_addf(out, " ...\n"); - else - strbuf_addf(out, " %s\n", subjects.list[i]); - - clear_commit_marks((struct commit *)branch, flags); - clear_commit_marks(head, flags); - free_commit_list(rev->commits); - rev->commits = NULL; - rev->pending.nr = 0; - - free_list(&subjects); -} - -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; - char *sep = ""; - unsigned char head_sha1[20]; - const char *current_branch; - - /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); - if (!current_branch) - die("No current branch"); - if (!prefixcmp(current_branch, "refs/heads/")) - current_branch += 11; - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p)) - die ("Error in line %d: %.*s", i, len, p); - } - - strbuf_addstr(out, "Merge "); - for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.payload[i]; - const char *subsep = ""; - - strbuf_addstr(out, sep); - sep = "; "; - - if (src_data->head_status == 1) { - strbuf_addstr(out, srcs.list[i]); - continue; - } - if (src_data->head_status == 3) { - subsep = ", "; - strbuf_addstr(out, "HEAD"); - } - if (src_data->branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch, - out); - } - if (src_data->r_branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("remote branch ", "remote branches ", - &src_data->r_branch, out); - } - if (src_data->tag.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag, out); - } - if (src_data->generic.nr) { - strbuf_addstr(out, subsep); - print_joined("commit ", "commits ", &src_data->generic, - out); - } - if (strcmp(".", srcs.list[i])) - strbuf_addf(out, " of %s", srcs.list[i]); - } - - if (!strcmp("master", current_branch)) - strbuf_addch(out, '\n'); - else - strbuf_addf(out, " into %s\n", current_branch); - - if (merge_summary) { - struct commit *head; - struct rev_info rev; - - head = lookup_commit(head_sha1); - init_revisions(&rev, NULL); - rev.commit_format = CMIT_FMT_ONELINE; - rev.ignore_merges = 1; - rev.limited = 1; - - for (i = 0; i < origins.nr; i++) - shortlog(origins.list[i], origins.payload[i], - head, &rev, limit, out); - } - return 0; -} - -int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) -{ - const char *inpath = NULL; - struct option options[] = { - OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"), - OPT_FILENAME('F', "file", &inpath, "file to read from"), - OPT_END() - }; - - FILE *in = stdin; - struct strbuf input = STRBUF_INIT, output = STRBUF_INIT; - int ret; - - git_config(fmt_merge_msg_config, NULL); - argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, - 0); - if (argc > 0) - usage_with_options(fmt_merge_msg_usage, options); - - if (inpath && strcmp(inpath, "-")) { - in = fopen(inpath, "r"); - if (!in) - die_errno("cannot open '%s'", inpath); - } - - if (strbuf_read(&input, fileno(in), 0) < 0) - die_errno("could not read input file"); - ret = fmt_merge_msg(merge_summary, &input, &output); - if (ret) - return ret; - write_in_full(STDOUT_FILENO, output.buf, output.len); - return 0; -} diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c deleted file mode 100644 index a5a83f146..000000000 --- a/builtin-for-each-ref.c +++ /dev/null @@ -1,955 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "refs.h" -#include "object.h" -#include "tag.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "quote.h" -#include "parse-options.h" -#include "remote.h" - -/* Quoting styles */ -#define QUOTE_NONE 0 -#define QUOTE_SHELL 1 -#define QUOTE_PERL 2 -#define QUOTE_PYTHON 4 -#define QUOTE_TCL 8 - -typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; - -struct atom_value { - const char *s; - unsigned long ul; /* used for sorting when not FIELD_STR */ -}; - -struct ref_sort { - struct ref_sort *next; - int atom; /* index into used_atom array */ - unsigned reverse : 1; -}; - -struct refinfo { - char *refname; - unsigned char objectname[20]; - struct atom_value *value; -}; - -static struct { - const char *name; - cmp_type cmp_type; -} valid_atom[] = { - { "refname" }, - { "objecttype" }, - { "objectsize", FIELD_ULONG }, - { "objectname" }, - { "tree" }, - { "parent" }, - { "numparent", FIELD_ULONG }, - { "object" }, - { "type" }, - { "tag" }, - { "author" }, - { "authorname" }, - { "authoremail" }, - { "authordate", FIELD_TIME }, - { "committer" }, - { "committername" }, - { "committeremail" }, - { "committerdate", FIELD_TIME }, - { "tagger" }, - { "taggername" }, - { "taggeremail" }, - { "taggerdate", FIELD_TIME }, - { "creator" }, - { "creatordate", FIELD_TIME }, - { "subject" }, - { "body" }, - { "contents" }, - { "upstream" }, -}; - -/* - * An atom is a valid field atom listed above, possibly prefixed with - * a "*" to denote deref_tag(). - * - * We parse given format string and sort specifiers, and make a list - * of properties that we need to extract out of objects. refinfo - * structure will hold an array of values extracted that can be - * indexed with the "atom number", which is an index into this - * array. - */ -static const char **used_atom; -static cmp_type *used_atom_type; -static int used_atom_cnt, sort_atom_limit, need_tagged; - -/* - * Used to parse format string and sort specifiers - */ -static int parse_atom(const char *atom, const char *ep) -{ - const char *sp; - int i, at; - - sp = atom; - if (*sp == '*' && sp < ep) - sp++; /* deref */ - if (ep <= sp) - die("malformed field name: %.*s", (int)(ep-atom), atom); - - /* Do we have the atom already used elsewhere? */ - for (i = 0; i < used_atom_cnt; i++) { - int len = strlen(used_atom[i]); - if (len == ep - atom && !memcmp(used_atom[i], atom, len)) - return i; - } - - /* Is the atom a valid one? */ - for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { - int len = strlen(valid_atom[i].name); - /* - * If the atom name has a colon, strip it and everything after - * it off - it specifies the format for this entry, and - * shouldn't be used for checking against the valid_atom - * table. - */ - const char *formatp = strchr(sp, ':'); - if (!formatp || ep < formatp) - formatp = ep; - if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) - break; - } - - if (ARRAY_SIZE(valid_atom) <= i) - die("unknown field name: %.*s", (int)(ep-atom), atom); - - /* Add it in, including the deref prefix */ - at = used_atom_cnt; - used_atom_cnt++; - used_atom = xrealloc(used_atom, - (sizeof *used_atom) * used_atom_cnt); - used_atom_type = xrealloc(used_atom_type, - (sizeof(*used_atom_type) * used_atom_cnt)); - used_atom[at] = xmemdupz(atom, ep - atom); - used_atom_type[at] = valid_atom[i].cmp_type; - return at; -} - -/* - * In a format string, find the next occurrence of %(atom). - */ -static const char *find_next(const char *cp) -{ - while (*cp) { - if (*cp == '%') { - /* %( is the start of an atom; - * %% is a quoted per-cent. - */ - if (cp[1] == '(') - return cp; - else if (cp[1] == '%') - cp++; /* skip over two % */ - /* otherwise this is a singleton, literal % */ - } - cp++; - } - return NULL; -} - -/* - * Make sure the format string is well formed, and parse out - * the used atoms. - */ -static int verify_format(const char *format) -{ - const char *cp, *sp; - for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *ep = strchr(sp, ')'); - if (!ep) - return error("malformed format string %s", sp); - /* sp points at "%(" and ep points at the closing ")" */ - parse_atom(sp + 2, ep); - cp = ep + 1; - } - return 0; -} - -/* - * Given an object name, read the object data and size, and return a - * "struct object". If the object data we are returning is also borrowed - * by the "struct object" representation, set *eaten as well---it is a - * signal from parse_object_buffer to us not to free the buffer. - */ -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) -{ - enum object_type type; - void *buf = read_sha1_file(sha1, &type, sz); - - if (buf) - *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); - else - *obj = NULL; - return buf; -} - -/* See grab_values */ -static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "objecttype")) - v->s = typename(obj->type); - else if (!strcmp(name, "objectsize")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); - v->ul = sz; - v->s = s; - } - else if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(obj->sha1)); - v->s = s; - } - } -} - -/* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct tag *tag = (struct tag *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tag")) - v->s = tag->tag; - else if (!strcmp(name, "type") && tag->tagged) - v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } - } -} - -static int num_parents(struct commit *commit) -{ - struct commit_list *parents; - int i; - - for (i = 0, parents = commit->parents; - parents; - parents = parents->next) - i++; - return i; -} - -/* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - struct commit *commit = (struct commit *) obj; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; - } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); - v->ul = num_parents(commit); - sprintf(s, "%lu", v->ul); - v->s = s; - } - else if (!strcmp(name, "parent")) { - int num = num_parents(commit); - int i; - struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { - struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; - } - if (!i) - *s = '\0'; - } - } -} - -static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) -{ - const char *eol; - while (*buf) { - if (!strncmp(buf, who, wholen) && - buf[wholen] == ' ') - return buf + wholen + 1; - eol = strchr(buf, '\n'); - if (!eol) - return ""; - eol++; - if (*eol == '\n') - return ""; /* end of header */ - buf = eol; - } - return ""; -} - -static const char *copy_line(const char *buf) -{ - const char *eol = strchrnul(buf, '\n'); - return xmemdupz(buf, eol - buf); -} - -static const char *copy_name(const char *buf) -{ - const char *cp; - for (cp = buf; *cp && *cp != '\n'; cp++) { - if (!strncmp(cp, " <", 2)) - return xmemdupz(buf, cp - buf); - } - return ""; -} - -static const char *copy_email(const char *buf) -{ - const char *email = strchr(buf, '<'); - const char *eoemail; - if (!email) - return ""; - eoemail = strchr(email, '>'); - if (!eoemail) - return ""; - return xmemdupz(email, eoemail + 1 - email); -} - -static void grab_date(const char *buf, struct atom_value *v, const char *atomname) -{ - const char *eoemail = strstr(buf, "> "); - char *zone; - unsigned long timestamp; - long tz; - enum date_mode date_mode = DATE_NORMAL; - const char *formatp; - - /* - * We got here because atomname ends in "date" or "date"; - * it's not possible that is not ":" because - * parse_atom() wouldn't have allowed it, so we can assume that no - * ":" means no format is specified, and use the default. - */ - formatp = strchr(atomname, ':'); - if (formatp != NULL) { - formatp++; - date_mode = parse_date_format(formatp); - } - - if (!eoemail) - goto bad; - timestamp = strtoul(eoemail + 2, &zone, 10); - if (timestamp == ULONG_MAX) - goto bad; - tz = strtol(zone, NULL, 10); - if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) - goto bad; - v->s = xstrdup(show_date(timestamp, tz, date_mode)); - v->ul = timestamp; - return; - bad: - v->s = ""; - v->ul = 0; -} - -/* See grab_values */ -static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - int wholen = strlen(who); - const char *wholine = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strncmp(who, name, wholen)) - continue; - if (name[wholen] != 0 && - strcmp(name + wholen, "name") && - strcmp(name + wholen, "email") && - prefixcmp(name + wholen, "date")) - continue; - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; /* no point looking for it */ - if (name[wholen] == 0) - v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) - v->s = copy_name(wholine); - else if (!strcmp(name + wholen, "email")) - v->s = copy_email(wholine); - else if (!prefixcmp(name + wholen, "date")) - grab_date(wholine, v, name); - } - - /* For a tag or a commit object, if "creator" or "creatordate" is - * requested, do something special. - */ - if (strcmp(who, "tagger") && strcmp(who, "committer")) - return; /* "author" for commit object is not wanted */ - if (!wholine) - wholine = find_wholine(who, wholen, buf, sz); - if (!wholine) - return; - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - - if (!prefixcmp(name, "creatordate")) - grab_date(wholine, v, name); - else if (!strcmp(name, "creator")) - v->s = copy_line(wholine); - } -} - -static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body) -{ - while (*buf) { - const char *eol = strchr(buf, '\n'); - if (!eol) - return; - if (eol[1] == '\n') { - buf = eol + 1; - break; /* found end of header */ - } - buf = eol + 1; - } - while (*buf == '\n') - buf++; - if (!*buf) - return; - *sub = buf; /* first non-empty line */ - buf = strchr(buf, '\n'); - if (!buf) { - *body = ""; - return; /* no body */ - } - while (*buf == '\n') - buf++; /* skip blank between subject and body */ - *body = buf; -} - -/* See grab_values */ -static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - int i; - const char *subpos = NULL, *bodypos = NULL; - - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &val[i]; - if (!!deref != (*name == '*')) - continue; - if (deref) - name++; - if (strcmp(name, "subject") && - strcmp(name, "body") && - strcmp(name, "contents")) - continue; - if (!subpos) - find_subpos(buf, sz, &subpos, &bodypos); - if (!subpos) - return; - - if (!strcmp(name, "subject")) - v->s = copy_line(subpos); - else if (!strcmp(name, "body")) - v->s = xstrdup(bodypos); - else if (!strcmp(name, "contents")) - v->s = xstrdup(subpos); - } -} - -/* 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) -{ - int i; - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &val[i]; - if (v->s == NULL) - v->s = ""; - } -} - -/* - * val is a list of atom_value to hold returned values. Extract - * the values for atoms in used_atom array out of (obj, buf, sz). - * when deref is false, (obj, buf, sz) is the object that is - * pointed at by the ref itself; otherwise it is the object the - * ref (which is a tag) refers to. - */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) -{ - grab_common_values(val, deref, obj, buf, sz); - switch (obj->type) { - case OBJ_TAG: - grab_tag_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("tagger", val, deref, obj, buf, sz); - break; - case OBJ_COMMIT: - grab_commit_values(val, deref, obj, buf, sz); - grab_sub_body_contents(val, deref, obj, buf, sz); - grab_person("author", val, deref, obj, buf, sz); - grab_person("committer", val, deref, obj, buf, sz); - break; - case OBJ_TREE: - // grab_tree_values(val, deref, obj, buf, sz); - break; - case OBJ_BLOB: - // grab_blob_values(val, deref, obj, buf, sz); - break; - default: - die("Eh? Object of type %d?", obj->type); - } -} - -/* - * Parse the object referred by ref, and grab needed value. - */ -static void populate_value(struct refinfo *ref) -{ - void *buf; - struct object *obj; - int eaten, i; - unsigned long size; - const unsigned char *tagged; - - ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); - - /* Fill in specials first */ - for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i]; - struct atom_value *v = &ref->value[i]; - int deref = 0; - const char *refname; - const char *formatp; - - if (*name == '*') { - deref = 1; - name++; - } - - if (!prefixcmp(name, "refname")) - refname = ref->refname; - else if (!prefixcmp(name, "upstream")) { - struct branch *branch; - /* only local branches may have an upstream */ - if (prefixcmp(ref->refname, "refs/heads/")) - continue; - branch = branch_get(ref->refname + 11); - - if (!branch || !branch->merge || !branch->merge[0] || - !branch->merge[0]->dst) - continue; - refname = branch->merge[0]->dst; - } - else - continue; - - formatp = strchr(name, ':'); - /* look for "short" refname format */ - if (formatp) { - formatp++; - if (!strcmp(formatp, "short")) - refname = shorten_unambiguous_ref(refname, - warn_ambiguous_refs); - else - die("unknown %.*s format %s", - (int)(formatp - name), name, formatp); - } - - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } - } - - for (i = 0; i < used_atom_cnt; i++) { - struct atom_value *v = &ref->value[i]; - if (v->s == NULL) - goto need_obj; - } - return; - - need_obj: - buf = get_obj(ref->objectname, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - - grab_values(ref->value, 0, obj, buf, size); - if (!eaten) - free(buf); - - /* 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 - * 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 - * is good to deal with chains of trust, but - * is not consistent with what deref_tag() does - * which peels the onion to the core. - */ - buf = get_obj(tagged, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(tagged), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(tagged), ref->refname); - grab_values(ref->value, 1, obj, buf, size); - if (!eaten) - free(buf); -} - -/* - * Given a ref, return the value for the atom. This lazily gets value - * out of the object by calling populate value. - */ -static void get_value(struct refinfo *ref, int atom, struct atom_value **v) -{ - if (!ref->value) { - populate_value(ref); - fill_missing_values(ref->value); - } - *v = &ref->value[atom]; -} - -struct grab_ref_cbdata { - struct refinfo **grab_array; - const char **grab_pattern; - int grab_cnt; -}; - -/* - * 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. - */ -static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct grab_ref_cbdata *cb = cb_data; - struct refinfo *ref; - int cnt; - - if (*cb->grab_pattern) { - const char **pattern; - int namelen = strlen(refname); - for (pattern = cb->grab_pattern; *pattern; pattern++) { - const char *p = *pattern; - int plen = strlen(p); - - if ((plen <= namelen) && - !strncmp(refname, p, plen) && - (refname[plen] == '\0' || - refname[plen] == '/' || - p[plen-1] == '/')) - break; - if (!fnmatch(p, refname, FNM_PATHNAME)) - break; - } - if (!*pattern) - return 0; - } - - /* 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. - */ - ref = xcalloc(1, sizeof(*ref)); - ref->refname = xstrdup(refname); - hashcpy(ref->objectname, sha1); - - cnt = cb->grab_cnt; - cb->grab_array = xrealloc(cb->grab_array, - sizeof(*cb->grab_array) * (cnt + 1)); - cb->grab_array[cnt++] = ref; - cb->grab_cnt = cnt; - return 0; -} - -static int cmp_ref_sort(struct ref_sort *s, struct refinfo *a, struct refinfo *b) -{ - struct atom_value *va, *vb; - int cmp; - cmp_type cmp_type = used_atom_type[s->atom]; - - get_value(a, s->atom, &va); - get_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: - cmp = strcmp(va->s, vb->s); - break; - default: - if (va->ul < vb->ul) - cmp = -1; - else if (va->ul == vb->ul) - cmp = 0; - else - cmp = 1; - break; - } - return (s->reverse) ? -cmp : cmp; -} - -static struct ref_sort *ref_sort; -static int compare_refs(const void *a_, const void *b_) -{ - struct refinfo *a = *((struct refinfo **)a_); - struct refinfo *b = *((struct refinfo **)b_); - struct ref_sort *s; - - for (s = ref_sort; s; s = s->next) { - int cmp = cmp_ref_sort(s, a, b); - if (cmp) - return cmp; - } - return 0; -} - -static void sort_refs(struct ref_sort *sort, struct refinfo **refs, int num_refs) -{ - ref_sort = sort; - qsort(refs, num_refs, sizeof(struct refinfo *), compare_refs); -} - -static void print_value(struct refinfo *ref, int atom, int quote_style) -{ - struct atom_value *v; - get_value(ref, atom, &v); - switch (quote_style) { - case QUOTE_NONE: - fputs(v->s, stdout); - break; - case QUOTE_SHELL: - sq_quote_print(stdout, v->s); - break; - case QUOTE_PERL: - perl_quote_print(stdout, v->s); - break; - case QUOTE_PYTHON: - python_quote_print(stdout, v->s); - break; - case QUOTE_TCL: - tcl_quote_print(stdout, v->s); - break; - } -} - -static int hex1(char ch) -{ - if ('0' <= ch && ch <= '9') - return ch - '0'; - else if ('a' <= ch && ch <= 'f') - return ch - 'a' + 10; - else if ('A' <= ch && ch <= 'F') - return ch - 'A' + 10; - return -1; -} -static int hex2(const char *cp) -{ - if (cp[0] && cp[1]) - return (hex1(cp[0]) << 4) | hex1(cp[1]); - else - return -1; -} - -static void emit(const char *cp, const char *ep) -{ - while (*cp && (!ep || cp < ep)) { - if (*cp == '%') { - if (cp[1] == '%') - cp++; - else { - int ch = hex2(cp + 1); - if (0 <= ch) { - putchar(ch); - cp += 3; - continue; - } - } - } - putchar(*cp); - cp++; - } -} - -static void show_ref(struct refinfo *info, const char *format, int quote_style) -{ - const char *cp, *sp, *ep; - - for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { - ep = strchr(sp, ')'); - if (cp < sp) - emit(cp, sp); - print_value(info, parse_atom(sp + 2, ep), quote_style); - } - if (*cp) { - sp = cp + strlen(cp); - emit(cp, sp); - } - putchar('\n'); -} - -static struct ref_sort *default_sort(void) -{ - static const char cstr_name[] = "refname"; - - struct ref_sort *sort = xcalloc(1, sizeof(*sort)); - - sort->next = NULL; - sort->atom = parse_atom(cstr_name, cstr_name + strlen(cstr_name)); - return sort; -} - -static int opt_parse_sort(const struct option *opt, const char *arg, int unset) -{ - struct ref_sort **sort_tail = opt->value; - struct ref_sort *s; - int len; - - if (!arg) /* should --no-sort void the list ? */ - return -1; - - *sort_tail = s = xcalloc(1, sizeof(*s)); - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - len = strlen(arg); - s->atom = parse_atom(arg, arg+len); - return 0; -} - -static char const * const for_each_ref_usage[] = { - "git for-each-ref [options] []", - NULL -}; - -int cmd_for_each_ref(int argc, const char **argv, const char *prefix) -{ - int i, num_refs; - const char *format = "%(objectname) %(objecttype)\t%(refname)"; - struct ref_sort *sort = NULL, **sort_tail = &sort; - int maxcount = 0, quote_style = 0; - struct refinfo **refs; - struct grab_ref_cbdata cbdata; - - struct option opts[] = { - OPT_BIT('s', "shell", "e_style, - "quote placeholders suitably for shells", QUOTE_SHELL), - OPT_BIT('p', "perl", "e_style, - "quote placeholders suitably for perl", QUOTE_PERL), - OPT_BIT(0 , "python", "e_style, - "quote placeholders suitably for python", QUOTE_PYTHON), - OPT_BIT(0 , "tcl", "e_style, - "quote placeholders suitably for tcl", QUOTE_TCL), - - OPT_GROUP(""), - OPT_INTEGER( 0 , "count", &maxcount, "show only matched refs"), - OPT_STRING( 0 , "format", &format, "format", "format to use for the output"), - OPT_CALLBACK(0 , "sort", sort_tail, "key", - "field name to sort on", &opt_parse_sort), - OPT_END(), - }; - - parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); - if (maxcount < 0) { - error("invalid --count argument: `%d'", maxcount); - usage_with_options(for_each_ref_usage, opts); - } - if (HAS_MULTI_BITS(quote_style)) { - error("more than one quoting style?"); - usage_with_options(for_each_ref_usage, opts); - } - if (verify_format(format)) - usage_with_options(for_each_ref_usage, opts); - - if (!sort) - sort = default_sort(); - sort_atom_limit = used_atom_cnt; - - /* for warn_ambiguous_refs */ - git_config(git_default_config, NULL); - - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.grab_pattern = argv; - for_each_rawref(grab_single_ref, &cbdata); - 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) - maxcount = num_refs; - for (i = 0; i < maxcount; i++) - show_ref(refs[i], format, quote_style); - return 0; -} diff --git a/builtin-fsck.c b/builtin-fsck.c deleted file mode 100644 index 0929c7f24..000000000 --- a/builtin-fsck.c +++ /dev/null @@ -1,684 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tag.h" -#include "refs.h" -#include "pack.h" -#include "cache-tree.h" -#include "tree-walk.h" -#include "fsck.h" -#include "parse-options.h" -#include "dir.h" - -#define REACHABLE 0x0001 -#define SEEN 0x0002 - -static int show_root; -static int show_tags; -static int show_unreachable; -static int include_reflogs = 1; -static int check_full = 1; -static int check_strict; -static int keep_cache_objects; -static unsigned char head_sha1[20]; -static const char *head_points_at; -static int errors_found; -static int write_lost_and_found; -static int verbose; -#define ERROR_OBJECT 01 -#define ERROR_REACHABLE 02 - -#ifdef NO_D_INO_IN_DIRENT -#define SORT_DIRENT 0 -#define DIRENT_SORT_HINT(de) 0 -#else -#define SORT_DIRENT 1 -#define DIRENT_SORT_HINT(de) ((de)->d_ino) -#endif - -static void objreport(struct object *obj, const char *severity, - const char *err, va_list params) -{ - fprintf(stderr, "%s in %s %s: ", - severity, typename(obj->type), sha1_to_hex(obj->sha1)); - vfprintf(stderr, err, params); - fputs("\n", stderr); -} - -__attribute__((format (printf, 2, 3))) -static int objerror(struct object *obj, const char *err, ...) -{ - va_list params; - va_start(params, err); - errors_found |= ERROR_OBJECT; - objreport(obj, "error", err, params); - va_end(params); - return -1; -} - -__attribute__((format (printf, 3, 4))) -static int fsck_error_func(struct object *obj, int type, const char *err, ...) -{ - va_list params; - va_start(params, err); - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", err, params); - va_end(params); - return (type == FSCK_WARN) ? 0 : 1; -} - -static struct object_array pending; - -static int mark_object(struct object *obj, int type, void *data) -{ - struct object *parent = data; - - if (!obj) { - printf("broken link from %7s %s\n", - typename(parent->type), sha1_to_hex(parent->sha1)); - printf("broken link from %7s %s\n", - (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); - errors_found |= ERROR_REACHABLE; - return 1; - } - - if (type != OBJ_ANY && obj->type != type) - objerror(parent, "wrong object type in link"); - - if (obj->flags & REACHABLE) - return 0; - obj->flags |= REACHABLE; - if (!obj->parsed) { - if (parent && !has_sha1_file(obj->sha1)) { - printf("broken link from %7s %s\n", - typename(parent->type), sha1_to_hex(parent->sha1)); - printf(" to %7s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); - errors_found |= ERROR_REACHABLE; - } - return 1; - } - - add_object_array(obj, (void *) parent, &pending); - return 0; -} - -static void mark_object_reachable(struct object *obj) -{ - mark_object(obj, OBJ_ANY, NULL); -} - -static int traverse_one_object(struct object *obj, struct object *parent) -{ - int result; - struct tree *tree = NULL; - - if (obj->type == OBJ_TREE) { - obj->parsed = 0; - tree = (struct tree *)obj; - if (parse_tree(tree) < 0) - return 1; /* error already displayed */ - } - result = fsck_walk(obj, mark_object, obj); - if (tree) { - free(tree->buffer); - tree->buffer = NULL; - } - return result; -} - -static int traverse_reachable(void) -{ - int result = 0; - while (pending.nr) { - struct object_array_entry *entry; - struct object *obj, *parent; - - entry = pending.objects + --pending.nr; - obj = entry->item; - parent = (struct object *) entry->name; - result |= traverse_one_object(obj, parent); - } - return !!result; -} - -static int mark_used(struct object *obj, int type, void *data) -{ - if (!obj) - return 1; - obj->used = 1; - return 0; -} - -/* - * Check a single reachable object - */ -static void check_reachable_object(struct object *obj) -{ - /* - * We obviously want the object to be parsed, - * except if it was in a pack-file and we didn't - * do a full fsck - */ - if (!obj->parsed) { - if (has_sha1_pack(obj->sha1)) - return; /* it is in pack - forget about it */ - printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - errors_found |= ERROR_REACHABLE; - return; - } -} - -/* - * Check a single unreachable object - */ -static void check_unreachable_object(struct object *obj) -{ - /* - * Missing unreachable object? Ignore it. It's not like - * we miss it (since it can't be reached), nor do we want - * to complain about it being unreachable (since it does - * not exist). - */ - if (!obj->parsed) - return; - - /* - * Unreachable object that exists? Show it if asked to, - * since this is something that is prunable. - */ - if (show_unreachable) { - printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); - return; - } - - /* - * "!used" means that nothing at all points to it, including - * other unreachable objects. In other words, it's the "tip" - * of some set of unreachable objects, usually a commit that - * got dropped. - * - * Such starting points are more interesting than some random - * set of unreachable objects, so we show them even if the user - * hasn't asked for _all_ unreachable objects. If you have - * deleted a branch by mistake, this is a prime candidate to - * start looking at, for example. - */ - if (!obj->used) { - printf("dangling %s %s\n", typename(obj->type), - sha1_to_hex(obj->sha1)); - if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", - obj->type == OBJ_COMMIT ? "commit" : "other", - sha1_to_hex(obj->sha1)); - FILE *f; - - if (safe_create_leading_directories(filename)) { - error("Could not create lost-found"); - return; - } - if (!(f = fopen(filename, "w"))) - die_errno("Could not open '%s'", filename); - if (obj->type == OBJ_BLOB) { - enum object_type type; - unsigned long size; - char *buf = read_sha1_file(obj->sha1, - &type, &size); - if (buf) { - if (fwrite(buf, size, 1, f) != 1) - die_errno("Could not write '%s'", - filename); - free(buf); - } - } else - fprintf(f, "%s\n", sha1_to_hex(obj->sha1)); - if (fclose(f)) - die_errno("Could not finish '%s'", - filename); - } - return; - } - - /* - * Otherwise? It's there, it's unreachable, and some other unreachable - * object points to it. Ignore it - it's not interesting, and we showed - * all the interesting cases above. - */ -} - -static void check_object(struct object *obj) -{ - if (verbose) - fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1)); - - if (obj->flags & REACHABLE) - check_reachable_object(obj); - else - check_unreachable_object(obj); -} - -static void check_connectivity(void) -{ - int i, max; - - /* Traverse the pending reachable objects */ - traverse_reachable(); - - /* Look up all the requirements, warn about missing objects.. */ - max = get_max_object_index(); - if (verbose) - fprintf(stderr, "Checking connectivity (%d objects)\n", max); - - for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); - - if (obj) - check_object(obj); - } -} - -static int fsck_sha1(const unsigned char *sha1) -{ - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } - if (obj->flags & SEEN) - return 0; - obj->flags |= SEEN; - - if (verbose) - fprintf(stderr, "Checking %s %s\n", - typename(obj->type), sha1_to_hex(obj->sha1)); - - if (fsck_walk(obj, mark_used, NULL)) - objerror(obj, "broken links"); - if (fsck_object(obj, check_strict, fsck_error_func)) - return -1; - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - - free(item->buffer); - item->buffer = NULL; - } - - if (obj->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *) obj; - - free(commit->buffer); - commit->buffer = NULL; - - if (!commit->parents && show_root) - printf("root %s\n", sha1_to_hex(commit->object.sha1)); - } - - if (obj->type == OBJ_TAG) { - struct tag *tag = (struct tag *) obj; - - if (show_tags && tag->tagged) { - printf("tagged %s %s", typename(tag->tagged->type), sha1_to_hex(tag->tagged->sha1)); - printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1)); - } - } - - return 0; -} - -/* - * This is the sorting chunk size: make it reasonably - * big so that we can sort well.. - */ -#define MAX_SHA1_ENTRIES (1024) - -struct sha1_entry { - unsigned long ino; - unsigned char sha1[20]; -}; - -static struct { - unsigned long nr; - struct sha1_entry *entry[MAX_SHA1_ENTRIES]; -} sha1_list; - -static int ino_compare(const void *_a, const void *_b) -{ - const struct sha1_entry *a = _a, *b = _b; - unsigned long ino1 = a->ino, ino2 = b->ino; - return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0; -} - -static void fsck_sha1_list(void) -{ - int i, nr = sha1_list.nr; - - if (SORT_DIRENT) - qsort(sha1_list.entry, nr, - sizeof(struct sha1_entry *), ino_compare); - for (i = 0; i < nr; i++) { - struct sha1_entry *entry = sha1_list.entry[i]; - unsigned char *sha1 = entry->sha1; - - sha1_list.entry[i] = NULL; - fsck_sha1(sha1); - free(entry); - } - sha1_list.nr = 0; -} - -static void add_sha1_list(unsigned char *sha1, unsigned long ino) -{ - struct sha1_entry *entry = xmalloc(sizeof(*entry)); - int nr; - - entry->ino = ino; - hashcpy(entry->sha1, sha1); - nr = sha1_list.nr; - if (nr == MAX_SHA1_ENTRIES) { - fsck_sha1_list(); - nr = 0; - } - sha1_list.entry[nr] = entry; - sha1_list.nr = ++nr; -} - -static void fsck_dir(int i, char *path) -{ - DIR *dir = opendir(path); - struct dirent *de; - - if (!dir) - return; - - if (verbose) - fprintf(stderr, "Checking directory %s\n", path); - - while ((de = readdir(dir)) != NULL) { - char name[100]; - unsigned char sha1[20]; - - if (is_dot_or_dotdot(de->d_name)) - continue; - if (strlen(de->d_name) == 38) { - sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, 39); - if (get_sha1_hex(name, sha1) < 0) - break; - add_sha1_list(sha1, DIRENT_SORT_HINT(de)); - continue; - } - if (!prefixcmp(de->d_name, "tmp_obj_")) - continue; - fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); - } - closedir(dir); -} - -static int default_refs; - -static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) -{ - struct object *obj; - - if (verbose) - fprintf(stderr, "Checking reflog %s->%s\n", - sha1_to_hex(osha1), sha1_to_hex(nsha1)); - - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - return 0; -} - -static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data) -{ - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); - return 0; -} - -static int is_branch(const char *refname) -{ - return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/"); -} - -static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *obj; - - obj = parse_object(sha1); - if (!obj) { - error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1)); - /* We'll continue with the rest despite the error.. */ - return 0; - } - if (obj->type != OBJ_COMMIT && is_branch(refname)) - error("%s: not a commit", refname); - default_refs++; - obj->used = 1; - mark_object_reachable(obj); - - return 0; -} - -static void get_default_heads(void) -{ - if (head_points_at && !is_null_sha1(head_sha1)) - fsck_handle_ref("HEAD", head_sha1, 0, NULL); - for_each_ref(fsck_handle_ref, NULL); - if (include_reflogs) - for_each_reflog(fsck_handle_reflog, NULL); - - /* - * Not having any default heads isn't really fatal, but - * it does mean that "--unreachable" no longer makes any - * sense (since in this case everything will obviously - * be unreachable by definition. - * - * Showing dangling objects is valid, though (as those - * dangling objects are likely lost heads). - * - * So we just print a warning about it, and clear the - * "show_unreachable" flag. - */ - if (!default_refs) { - fprintf(stderr, "notice: No default references\n"); - show_unreachable = 0; - } -} - -static void fsck_object_dir(const char *path) -{ - int i; - - if (verbose) - fprintf(stderr, "Checking object directory\n"); - - for (i = 0; i < 256; i++) { - static char dir[4096]; - sprintf(dir, "%s/%02x", path, i); - fsck_dir(i, dir); - } - fsck_sha1_list(); -} - -static int fsck_head_link(void) -{ - int flag; - int null_is_error = 0; - - if (verbose) - fprintf(stderr, "Checking HEAD link\n"); - - head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); - if (!head_points_at) - return error("Invalid HEAD"); - if (!strcmp(head_points_at, "HEAD")) - /* detached HEAD */ - null_is_error = 1; - else if (prefixcmp(head_points_at, "refs/heads/")) - return error("HEAD points to something strange (%s)", - head_points_at); - if (is_null_sha1(head_sha1)) { - if (null_is_error) - return error("HEAD: detached HEAD points at nothing"); - fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", - head_points_at + 11); - } - return 0; -} - -static int fsck_cache_tree(struct cache_tree *it) -{ - int i; - int err = 0; - - if (verbose) - fprintf(stderr, "Checking cache tree\n"); - - if (0 <= it->entry_count) { - struct object *obj = parse_object(it->sha1); - if (!obj) { - error("%s: invalid sha1 pointer in cache-tree", - sha1_to_hex(it->sha1)); - return 1; - } - mark_object_reachable(obj); - obj->used = 1; - if (obj->type != OBJ_TREE) - err |= objerror(obj, "non-tree in cache-tree"); - } - for (i = 0; i < it->subtree_nr; i++) - err |= fsck_cache_tree(it->down[i]->cache_tree); - return err; -} - -static char const * const fsck_usage[] = { - "git fsck [options] [...]", - NULL -}; - -static struct option fsck_opts[] = { - OPT__VERBOSE(&verbose), - OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"), - OPT_BOOLEAN(0, "tags", &show_tags, "report tags"), - OPT_BOOLEAN(0, "root", &show_root, "report root nodes"), - OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"), - OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"), - OPT_BOOLEAN(0, "full", &check_full, "also consider packs and alternate objects"), - OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), - OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, - "write dangling objects in .git/lost-found"), - OPT_END(), -}; - -int cmd_fsck(int argc, const char **argv, const char *prefix) -{ - int i, heads; - struct alternate_object_database *alt; - - errors_found = 0; - read_replace_refs = 0; - - argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); - if (write_lost_and_found) { - check_full = 1; - include_reflogs = 0; - } - - fsck_head_link(); - fsck_object_dir(get_object_directory()); - - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); - } - - if (check_full) { - struct packed_git *p; - - prepare_packed_git(); - for (p = packed_git; p; p = p->next) - /* verify gives error messages itself */ - verify_pack(p); - - for (p = packed_git; p; p = p->next) { - uint32_t j, num; - if (open_pack_index(p)) - continue; - num = p->num_objects; - for (j = 0; j < num; j++) - fsck_sha1(nth_packed_object_sha1(p, j)); - } - } - - heads = 0; - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - unsigned char sha1[20]; - if (!get_sha1(arg, sha1)) { - struct object *obj = lookup_object(sha1); - - /* Error is printed by lookup_object(). */ - if (!obj) - continue; - - obj->used = 1; - mark_object_reachable(obj); - heads++; - continue; - } - error("invalid parameter: expected sha1, got '%s'", arg); - } - - /* - * If we've not been given any explicit head information, do the - * default ones from .git/refs. We also consider the index file - * in this case (ie this implies --cache). - */ - if (!heads) { - get_default_heads(); - keep_cache_objects = 1; - } - - if (keep_cache_objects) { - read_cache(); - for (i = 0; i < active_nr; i++) { - unsigned int mode; - struct blob *blob; - struct object *obj; - - mode = active_cache[i]->ce_mode; - if (S_ISGITLINK(mode)) - continue; - blob = lookup_blob(active_cache[i]->sha1); - if (!blob) - continue; - obj = &blob->object; - obj->used = 1; - mark_object_reachable(obj); - } - if (active_cache_tree) - fsck_cache_tree(active_cache_tree); - } - - check_connectivity(); - return errors_found; -} diff --git a/builtin-gc.c b/builtin-gc.c deleted file mode 100644 index c304638b7..000000000 --- a/builtin-gc.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * git gc builtin command - * - * Cleanup unreachable files and optimize the repository. - * - * Copyright (c) 2007 James Bowes - * - * Based on git-gc.sh, which is - * - * Copyright (c) 2006 Shawn O. Pearce - */ - -#include "builtin.h" -#include "cache.h" -#include "parse-options.h" -#include "run-command.h" - -#define FAILED_RUN "failed to run %s" - -static const char * const builtin_gc_usage[] = { - "git gc [options]", - NULL -}; - -static int pack_refs = 1; -static int aggressive_window = 250; -static int gc_auto_threshold = 6700; -static int gc_auto_pack_limit = 50; -static const char *prune_expire = "2.weeks.ago"; - -#define MAX_ADD 10 -static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; -static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; -static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; -static const char *argv_rerere[] = {"rerere", "gc", NULL}; - -static int gc_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "gc.packrefs")) { - if (value && !strcmp(value, "notbare")) - pack_refs = -1; - else - pack_refs = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "gc.aggressivewindow")) { - aggressive_window = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.auto")) { - gc_auto_threshold = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.autopacklimit")) { - gc_auto_pack_limit = git_config_int(var, value); - return 0; - } - if (!strcmp(var, "gc.pruneexpire")) { - if (value && strcmp(value, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(value) >= now) - return error("Invalid %s: '%s'", var, value); - } - return git_config_string(&prune_expire, var, value); - } - return git_default_config(var, value, cb); -} - -static void append_option(const char **cmd, const char *opt, int max_length) -{ - int i; - - for (i = 0; cmd[i]; i++) - ; - - if (i + 2 >= max_length) - die("Too many options specified"); - cmd[i++] = opt; - cmd[i] = NULL; -} - -static int too_many_loose_objects(void) -{ - /* - * Quickly check if a "gc" is needed, by estimating how - * many loose objects there are. Because SHA-1 is evenly - * distributed, we can check only one and get a reasonable - * estimate. - */ - char path[PATH_MAX]; - const char *objdir = get_object_directory(); - DIR *dir; - struct dirent *ent; - int auto_threshold; - int num_loose = 0; - int needed = 0; - - if (gc_auto_threshold <= 0) - return 0; - - if (sizeof(path) <= snprintf(path, sizeof(path), "%s/17", objdir)) { - warning("insanely long object directory %.*s", 50, objdir); - return 0; - } - dir = opendir(path); - if (!dir) - return 0; - - auto_threshold = (gc_auto_threshold + 255) / 256; - while ((ent = readdir(dir)) != NULL) { - if (strspn(ent->d_name, "0123456789abcdef") != 38 || - ent->d_name[38] != '\0') - continue; - if (++num_loose > auto_threshold) { - needed = 1; - break; - } - } - closedir(dir); - return needed; -} - -static int too_many_packs(void) -{ - struct packed_git *p; - int cnt; - - if (gc_auto_pack_limit <= 0) - return 0; - - prepare_packed_git(); - for (cnt = 0, p = packed_git; p; p = p->next) { - if (!p->pack_local) - continue; - if (p->pack_keep) - continue; - /* - * Perhaps check the size of the pack and count only - * very small ones here? - */ - cnt++; - } - return gc_auto_pack_limit <= cnt; -} - -static int need_to_gc(void) -{ - /* - * Setting gc.auto to 0 or negative can disable the - * automatic gc. - */ - if (gc_auto_threshold <= 0) - return 0; - - /* - * If there are too many loose objects, but not too many - * packs, we run "repack -d -l". If there are too many packs, - * we run "repack -A -d -l". Otherwise we tell the caller - * there is no need. - */ - if (too_many_packs()) - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") ? - "-a" : "-A", - MAX_ADD); - else if (!too_many_loose_objects()) - return 0; - - if (run_hook(NULL, "pre-auto-gc", NULL)) - return 0; - return 1; -} - -int cmd_gc(int argc, const char **argv, const char *prefix) -{ - int aggressive = 0; - int auto_gc = 0; - int quiet = 0; - char buf[80]; - - struct option builtin_gc_options[] = { - OPT__QUIET(&quiet), - { OPTION_STRING, 0, "prune", &prune_expire, "date", - "prune unreferenced objects", - PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, - OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), - OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), - OPT_END() - }; - - git_config(gc_config, NULL); - - if (pack_refs < 0) - pack_refs = !is_bare_repository(); - - argc = parse_options(argc, argv, prefix, builtin_gc_options, - builtin_gc_usage, 0); - if (argc > 0) - usage_with_options(builtin_gc_usage, builtin_gc_options); - - if (aggressive) { - append_option(argv_repack, "-f", MAX_ADD); - append_option(argv_repack, "--depth=250", MAX_ADD); - if (aggressive_window > 0) { - sprintf(buf, "--window=%d", aggressive_window); - append_option(argv_repack, buf, MAX_ADD); - } - } - if (quiet) - append_option(argv_repack, "-q", MAX_ADD); - - if (auto_gc) { - /* - * Auto-gc should be least intrusive as possible. - */ - if (!need_to_gc()) - return 0; - fprintf(stderr, - "Auto packing the repository for optimum performance.%s\n", - quiet - ? "" - : (" You may also\n" - "run \"git gc\" manually. See " - "\"git help gc\" for more information.")); - } else - append_option(argv_repack, - prune_expire && !strcmp(prune_expire, "now") - ? "-a" : "-A", - MAX_ADD); - - if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_pack_refs[0]); - - if (run_command_v_opt(argv_reflog, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_reflog[0]); - - if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_repack[0]); - - if (prune_expire) { - argv_prune[2] = prune_expire; - if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_prune[0]); - } - - if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_rerere[0]); - - if (auto_gc && too_many_loose_objects()) - warning("There are too many unreachable loose objects; " - "run 'git prune' to remove them."); - - return 0; -} diff --git a/builtin-grep.c b/builtin-grep.c deleted file mode 100644 index 552ef1fac..000000000 --- a/builtin-grep.c +++ /dev/null @@ -1,1010 +0,0 @@ -/* - * Builtin "git grep" - * - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "blob.h" -#include "tree.h" -#include "commit.h" -#include "tag.h" -#include "tree-walk.h" -#include "builtin.h" -#include "parse-options.h" -#include "userdiff.h" -#include "grep.h" -#include "quote.h" -#include "dir.h" - -#ifndef NO_PTHREADS -#include "thread-utils.h" -#include -#endif - -static char const * const grep_usage[] = { - "git grep [options] [-e] [...] [[--] path...]", - NULL -}; - -static int use_threads = 1; - -#ifndef NO_PTHREADS -#define THREADS 8 -static pthread_t threads[THREADS]; - -static void *load_sha1(const unsigned char *sha1, unsigned long *size, - const char *name); -static void *load_file(const char *filename, size_t *sz); - -enum work_type {WORK_SHA1, WORK_FILE}; - -/* We use one producer thread and THREADS consumer - * threads. The producer adds struct work_items to 'todo' and the - * consumers pick work items from the same array. - */ -struct work_item -{ - enum work_type type; - char *name; - - /* if type == WORK_SHA1, then 'identifier' is a SHA1, - * otherwise type == WORK_FILE, and 'identifier' is a NUL - * terminated filename. - */ - void *identifier; - char done; - struct strbuf out; -}; - -/* In the range [todo_done, todo_start) in 'todo' we have work_items - * that have been or are processed by a consumer thread. We haven't - * written the result for these to stdout yet. - * - * The work_items in [todo_start, todo_end) are waiting to be picked - * up by a consumer thread. - * - * The ranges are modulo TODO_SIZE. - */ -#define TODO_SIZE 128 -static struct work_item todo[TODO_SIZE]; -static int todo_start; -static int todo_end; -static int todo_done; - -/* Has all work items been added? */ -static int all_work_added; - -/* This lock protects all the variables above. */ -static pthread_mutex_t grep_mutex; - -/* Used to serialize calls to read_sha1_file. */ -static pthread_mutex_t read_sha1_mutex; - -#define grep_lock() pthread_mutex_lock(&grep_mutex) -#define grep_unlock() pthread_mutex_unlock(&grep_mutex) -#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex) -#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex) - -/* Signalled when a new work_item is added to todo. */ -static pthread_cond_t cond_add; - -/* Signalled when the result from one work_item is written to - * stdout. - */ -static pthread_cond_t cond_write; - -/* Signalled when we are finished with everything. */ -static pthread_cond_t cond_result; - -static void add_work(enum work_type type, char *name, void *id) -{ - grep_lock(); - - while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { - pthread_cond_wait(&cond_write, &grep_mutex); - } - - todo[todo_end].type = type; - todo[todo_end].name = name; - todo[todo_end].identifier = id; - todo[todo_end].done = 0; - strbuf_reset(&todo[todo_end].out); - todo_end = (todo_end + 1) % ARRAY_SIZE(todo); - - pthread_cond_signal(&cond_add); - grep_unlock(); -} - -static struct work_item *get_work(void) -{ - struct work_item *ret; - - grep_lock(); - while (todo_start == todo_end && !all_work_added) { - pthread_cond_wait(&cond_add, &grep_mutex); - } - - if (todo_start == todo_end && all_work_added) { - ret = NULL; - } else { - ret = &todo[todo_start]; - todo_start = (todo_start + 1) % ARRAY_SIZE(todo); - } - grep_unlock(); - return ret; -} - -static void grep_sha1_async(struct grep_opt *opt, char *name, - const unsigned char *sha1) -{ - unsigned char *s; - s = xmalloc(20); - memcpy(s, sha1, 20); - add_work(WORK_SHA1, name, s); -} - -static void grep_file_async(struct grep_opt *opt, char *name, - const char *filename) -{ - add_work(WORK_FILE, name, xstrdup(filename)); -} - -static void work_done(struct work_item *w) -{ - int old_done; - - grep_lock(); - w->done = 1; - old_done = todo_done; - for(; todo[todo_done].done && todo_done != todo_start; - todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { - w = &todo[todo_done]; - write_or_die(1, w->out.buf, w->out.len); - free(w->name); - free(w->identifier); - } - - if (old_done != todo_done) - pthread_cond_signal(&cond_write); - - if (all_work_added && todo_done == todo_end) - pthread_cond_signal(&cond_result); - - grep_unlock(); -} - -static void *run(void *arg) -{ - int hit = 0; - struct grep_opt *opt = arg; - - while (1) { - struct work_item *w = get_work(); - if (!w) - break; - - opt->output_priv = w; - if (w->type == WORK_SHA1) { - unsigned long sz; - void* data = load_sha1(w->identifier, &sz, w->name); - - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else if (w->type == WORK_FILE) { - size_t sz; - void* data = load_file(w->identifier, &sz); - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else { - assert(0); - } - - work_done(w); - } - free_grep_patterns(arg); - free(arg); - - return (void*) (intptr_t) hit; -} - -static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size) -{ - struct work_item *w = opt->output_priv; - strbuf_add(&w->out, buf, size); -} - -static void start_threads(struct grep_opt *opt) -{ - int i; - - pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&read_sha1_mutex, NULL); - pthread_cond_init(&cond_add, NULL); - pthread_cond_init(&cond_write, NULL); - pthread_cond_init(&cond_result, NULL); - - for (i = 0; i < ARRAY_SIZE(todo); i++) { - strbuf_init(&todo[i].out, 0); - } - - for (i = 0; i < ARRAY_SIZE(threads); i++) { - int err; - struct grep_opt *o = grep_opt_dup(opt); - o->output = strbuf_out; - compile_grep_patterns(o); - err = pthread_create(&threads[i], NULL, run, o); - - if (err) - die("grep: failed to create thread: %s", - strerror(err)); - } -} - -static int wait_all(void) -{ - int hit = 0; - int i; - - grep_lock(); - all_work_added = 1; - - /* Wait until all work is done. */ - while (todo_done != todo_end) - pthread_cond_wait(&cond_result, &grep_mutex); - - /* Wake up all the consumer threads so they can see that there - * is no more work to do. - */ - pthread_cond_broadcast(&cond_add); - grep_unlock(); - - for (i = 0; i < ARRAY_SIZE(threads); i++) { - void *h; - pthread_join(threads[i], &h); - hit |= (int) (intptr_t) h; - } - - pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&read_sha1_mutex); - pthread_cond_destroy(&cond_add); - pthread_cond_destroy(&cond_write); - pthread_cond_destroy(&cond_result); - - return hit; -} -#else /* !NO_PTHREADS */ -#define read_sha1_lock() -#define read_sha1_unlock() - -static int wait_all(void) -{ - return 0; -} -#endif - -static int grep_config(const char *var, const char *value, void *cb) -{ - struct grep_opt *opt = cb; - - switch (userdiff_config(var, value)) { - case 0: break; - case -1: return -1; - default: return 0; - } - - if (!strcmp(var, "color.grep")) { - opt->color = git_config_colorbool(var, value, -1); - return 0; - } - if (!strcmp(var, "color.grep.match")) { - if (!value) - return config_error_nonbool(var); - color_parse(value, var, opt->color_match); - return 0; - } - return git_color_default_config(var, value, cb); -} - -/* - * Return non-zero if max_depth is negative or path has no more then max_depth - * slashes. - */ -static int accept_subdir(const char *path, int max_depth) -{ - if (max_depth < 0) - return 1; - - while ((path = strchr(path, '/')) != NULL) { - max_depth--; - if (max_depth < 0) - return 0; - path++; - } - return 1; -} - -/* - * Return non-zero if name is a subdirectory of match and is not too deep. - */ -static int is_subdir(const char *name, int namelen, - const char *match, int matchlen, int max_depth) -{ - if (matchlen > namelen || strncmp(name, match, matchlen)) - return 0; - - if (name[matchlen] == '\0') /* exact match */ - return 1; - - if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') - return accept_subdir(name + matchlen + 1, max_depth); - - return 0; -} - -/* - * git grep pathspecs are somewhat different from diff-tree pathspecs; - * pathname wildcards are allowed. - */ -static int pathspec_matches(const char **paths, const char *name, int max_depth) -{ - int namelen, i; - if (!paths || !*paths) - return accept_subdir(name, max_depth); - namelen = strlen(name); - for (i = 0; paths[i]; i++) { - const char *match = paths[i]; - int matchlen = strlen(match); - const char *cp, *meta; - - if (is_subdir(name, namelen, match, matchlen, max_depth)) - return 1; - if (!fnmatch(match, name, 0)) - return 1; - if (name[namelen-1] != '/') - continue; - - /* We are being asked if the directory ("name") is worth - * descending into. - * - * Find the longest leading directory name that does - * not have metacharacter in the pathspec; the name - * we are looking at must overlap with that directory. - */ - for (cp = match, meta = NULL; cp - match < matchlen; cp++) { - char ch = *cp; - if (ch == '*' || ch == '[' || ch == '?') { - meta = cp; - break; - } - } - if (!meta) - meta = cp; /* fully literal */ - - if (namelen <= meta - match) { - /* Looking at "Documentation/" and - * the pattern says "Documentation/howto/", or - * "Documentation/diff*.txt". The name we - * have should match prefix. - */ - if (!memcmp(match, name, namelen)) - return 1; - continue; - } - - if (meta - match < namelen) { - /* Looking at "Documentation/howto/" and - * the pattern says "Documentation/h*"; - * match up to "Do.../h"; this avoids descending - * into "Documentation/technical/". - */ - if (!memcmp(match, name, meta - match)) - return 1; - continue; - } - } - 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; - void *data = lock_and_read_sha1_file(sha1, &type, size); - - if (!data) - error("'%s': unable to read %s", name, sha1_to_hex(sha1)); - - return data; -} - -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, - const char *filename, int tree_name_len) -{ - struct strbuf pathbuf = STRBUF_INIT; - char *name; - - if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, -1, &pathbuf, - opt->prefix); - strbuf_insert(&pathbuf, 0, filename, tree_name_len); - } else { - strbuf_addstr(&pathbuf, filename); - } - - name = strbuf_detach(&pathbuf, NULL); - -#ifndef NO_PTHREADS - if (use_threads) { - grep_sha1_async(opt, name, sha1); - return 0; - } else -#endif - { - int hit; - unsigned long sz; - void *data = load_sha1(sha1, &sz, name); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - - free(data); - free(name); - return hit; - } -} - -static void *load_file(const char *filename, size_t *sz) -{ - struct stat st; - char *data; - int i; - - if (lstat(filename, &st) < 0) { - err_ret: - if (errno != ENOENT) - error("'%s': %s", filename, strerror(errno)); - return 0; - } - if (!S_ISREG(st.st_mode)) - return 0; - *sz = xsize_t(st.st_size); - i = open(filename, O_RDONLY); - if (i < 0) - goto err_ret; - data = xmalloc(*sz + 1); - if (st.st_size != read_in_full(i, data, *sz)) { - error("'%s': short read %s", filename, strerror(errno)); - close(i); - free(data); - return 0; - } - close(i); - data[*sz] = 0; - return data; -} - -static int grep_file(struct grep_opt *opt, const char *filename) -{ - struct strbuf buf = STRBUF_INIT; - char *name; - - if (opt->relative && opt->prefix_length) - quote_path_relative(filename, -1, &buf, opt->prefix); - else - strbuf_addstr(&buf, filename); - name = strbuf_detach(&buf, NULL); - -#ifndef NO_PTHREADS - if (use_threads) { - grep_file_async(opt, name, filename); - return 0; - } else -#endif - { - int hit; - size_t sz; - void *data = load_file(filename, &sz); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - - free(data); - free(name); - return hit; - } -} - -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) -{ - int hit = 0; - int nr; - read_cache(); - - for (nr = 0; nr < active_nr; nr++) { - struct cache_entry *ce = active_cache[nr]; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - /* - * If CE_VALID is on, we assume worktree file and its cache entry - * are identical, even if worktree file has been modified, so use - * cache version instead - */ - if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { - if (ce_stage(ce)) - continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0); - } - else - hit |= grep_file(opt, ce->name); - if (ce_stage(ce)) { - do { - nr++; - } while (nr < active_nr && - !strcmp(ce->name, active_cache[nr]->name)); - nr--; /* compensate for loop control */ - } - if (hit && opt->status_only) - break; - } - free_grep_patterns(opt); - return hit; -} - -static int grep_tree(struct grep_opt *opt, const char **paths, - struct tree_desc *tree, - const char *tree_name, const char *base) -{ - int len; - int hit = 0; - struct name_entry entry; - char *down; - int tn_len = strlen(tree_name); - struct strbuf pathbuf; - - strbuf_init(&pathbuf, PATH_MAX + tn_len); - - if (tn_len) { - strbuf_add(&pathbuf, tree_name, tn_len); - strbuf_addch(&pathbuf, ':'); - tn_len = pathbuf.len; - } - strbuf_addstr(&pathbuf, base); - len = pathbuf.len; - - while (tree_entry(tree, &entry)) { - int te_len = tree_entry_len(entry.path, entry.sha1); - pathbuf.len = len; - strbuf_add(&pathbuf, entry.path, te_len); - - if (S_ISDIR(entry.mode)) - /* Match "abc/" against pathspec to - * decide if we want to descend into "abc" - * directory. - */ - strbuf_addch(&pathbuf, '/'); - - down = pathbuf.buf + tn_len; - if (!pathspec_matches(paths, down, opt->max_depth)) - ; - else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); - else if (S_ISDIR(entry.mode)) { - enum object_type type; - struct tree_desc sub; - void *data; - unsigned long size; - - data = lock_and_read_sha1_file(entry.sha1, &type, &size); - if (!data) - die("unable to read tree (%s)", - sha1_to_hex(entry.sha1)); - init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, paths, &sub, tree_name, down); - free(data); - } - if (hit && opt->status_only) - break; - } - strbuf_release(&pathbuf); - return hit; -} - -static int grep_object(struct grep_opt *opt, const char **paths, - struct object *obj, const char *name) -{ - if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0); - if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { - struct tree_desc tree; - void *data; - unsigned long size; - int hit; - data = read_object_with_reference(obj->sha1, tree_type, - &size, NULL); - if (!data) - die("unable to read tree (%s)", sha1_to_hex(obj->sha1)); - init_tree_desc(&tree, data, size); - hit = grep_tree(opt, paths, &tree, name, ""); - free(data); - return hit; - } - 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) -{ - struct grep_opt *grep_opt = opt->value; - int value; - const char *endp; - - if (unset) { - grep_opt->pre_context = grep_opt->post_context = 0; - return 0; - } - value = strtol(arg, (char **)&endp, 10); - if (*endp) { - return error("switch `%c' expects a numerical value", - opt->short_name); - } - grep_opt->pre_context = grep_opt->post_context = value; - return 0; -} - -static int file_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - FILE *patterns; - int lno = 0; - struct strbuf sb = STRBUF_INIT; - - patterns = fopen(arg, "r"); - if (!patterns) - die_errno("cannot open '%s'", arg); - while (strbuf_getline(&sb, patterns, '\n') == 0) { - /* ignore empty line like grep does */ - if (sb.len == 0) - continue; - append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg, - ++lno, GREP_PATTERN); - } - fclose(patterns); - strbuf_release(&sb); - return 0; -} - -static int not_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT); - return 0; -} - -static int and_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND); - return 0; -} - -static int open_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN); - return 0; -} - -static int close_callback(const struct option *opt, const char *arg, int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN); - return 0; -} - -static int pattern_callback(const struct option *opt, const char *arg, - int unset) -{ - struct grep_opt *grep_opt = opt->value; - append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN); - return 0; -} - -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - -int cmd_grep(int argc, const char **argv, const char *prefix) -{ - int hit = 0; - int cached = 0; - int seen_dashdash = 0; - int external_grep_allowed__ignored; - struct grep_opt opt; - struct object_array list = { 0, 0, NULL }; - 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"), - OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case, - "case insensitive matching"), - OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp, - "match patterns only at word boundaries"), - OPT_SET_INT('a', "text", &opt.binary, - "process binary files as text", GREP_BINARY_TEXT), - OPT_SET_INT('I', NULL, &opt.binary, - "don't match patterns in binary files", - GREP_BINARY_NOMATCH), - { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth", - "descend at most levels", PARSE_OPT_NONEG, - NULL, 1 }, - OPT_GROUP(""), - OPT_BIT('E', "extended-regexp", &opt.regflags, - "use extended POSIX regular expressions", REG_EXTENDED), - OPT_NEGBIT('G', "basic-regexp", &opt.regflags, - "use basic POSIX regular expressions (default)", - REG_EXTENDED), - OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, - "interpret patterns as fixed strings"), - OPT_GROUP(""), - OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), - OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), - OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), - OPT_NEGBIT(0, "full-name", &opt.relative, - "show filenames relative to top directory", 1), - OPT_BOOLEAN('l', "files-with-matches", &opt.name_only, - "show only filenames instead of matching lines"), - OPT_BOOLEAN(0, "name-only", &opt.name_only, - "synonym for --files-with-matches"), - OPT_BOOLEAN('L', "files-without-match", - &opt.unmatch_name_only, - "show only the names of files without match"), - OPT_BOOLEAN('z', "null", &opt.null_following_name, - "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_GROUP(""), - OPT_CALLBACK('C', NULL, &opt, "n", - "show context lines before and after matches", - context_callback), - OPT_INTEGER('B', NULL, &opt.pre_context, - "show context lines before matches"), - OPT_INTEGER('A', NULL, &opt.post_context, - "show context lines after matches"), - OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", - context_callback), - OPT_BOOLEAN('p', "show-function", &opt.funcname, - "show a line with the function name before matches"), - OPT_GROUP(""), - OPT_CALLBACK('f', NULL, &opt, "file", - "read patterns from file", file_callback), - { OPTION_CALLBACK, 'e', NULL, &opt, "pattern", - "match ", PARSE_OPT_NONEG, pattern_callback }, - { OPTION_CALLBACK, 0, "and", &opt, NULL, - "combine patterns specified with -e", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, - OPT_BOOLEAN(0, "or", &dummy, ""), - { OPTION_CALLBACK, 0, "not", &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, - { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - open_callback }, - { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - close_callback }, - OPT_BOOLEAN('q', "quiet", &opt.status_only, - "indicate hit with exit status without output"), - OPT_BOOLEAN(0, "all-match", &opt.all_match, - "show only matches from files that match all patterns"), - OPT_GROUP(""), - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored, - "allow calling of grep(1) (ignored by this build)"), - { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, - OPT_END() - }; - - prefix = setup_git_directory_gently(&nongit); - - /* - * 'git grep -h', unlike 'git grep -h ', is a request - * to show usage information and exit. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(grep_usage, options); - - memset(&opt, 0, sizeof(opt)); - opt.prefix = prefix; - opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; - opt.relative = 1; - opt.pathname = 1; - opt.pattern_tail = &opt.pattern_list; - opt.regflags = REG_NEWLINE; - opt.max_depth = -1; - - strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); - opt.color = -1; - git_config(grep_config, &opt); - if (opt.color == -1) - opt.color = git_use_color_default; - - /* - * If there is no -- then the paths must exist in the working - * tree. If there is no explicit pattern specified with -e or - * -f, we take the first unrecognized non option to be the - * pattern, but then what follows it must be zero or more - * valid refs up to the -- (if exists), and then existing - * paths. If there is an explicit pattern, then the first - * unrecognized non option is the beginning of the refs list - * that continues up to the -- (if exists), and then paths. - */ - argc = parse_options(argc, argv, prefix, options, grep_usage, - PARSE_OPT_KEEP_DASHDASH | - 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(); - - /* - * 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, - GREP_PATTERN); - argv++; - argc--; - } - - if (!opt.pattern_list) - die("no pattern given."); - if (!opt.fixed && opt.ignore_case) - opt.regflags |= REG_ICASE; - if ((opt.regflags != REG_NEWLINE) && opt.fixed) - die("cannot mix --fixed-strings and regexp"); - -#ifndef NO_PTHREADS - if (online_cpus() == 1 || !grep_threads_ok(&opt)) - use_threads = 0; - - if (use_threads) - start_threads(&opt); -#else - use_threads = 0; -#endif - - compile_grep_patterns(&opt); - - /* Check revs and then paths */ - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - unsigned char sha1[20]; - /* Is it a rev? */ - if (!get_sha1(arg, sha1)) { - struct object *object = parse_object(sha1); - if (!object) - die("bad object %s", arg); - add_object_array(object, arg, &list); - continue; - } - if (!strcmp(arg, "--")) { - i++; - seen_dashdash = 1; - } - break; - } - - /* The rest are paths */ - if (!seen_dashdash) { - int j; - for (j = i; j < argc; j++) - verify_filename(prefix, argv[j]); - } - - if (i < argc) - paths = get_pathspec(prefix, argv + i); - else if (prefix) { - paths = xcalloc(2, sizeof(const char *)); - paths[0] = 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) - setup_work_tree(); - - hit = grep_cache(&opt, paths, cached); - if (use_threads) - hit |= wait_all(); - return !hit; - } - - if (cached) - die("both --cached and trees are given."); - - for (i = 0; i < list.nr; i++) { - struct object *real_obj; - real_obj = deref_tag(list.objects[i].item, NULL, 0); - if (grep_object(&opt, paths, real_obj, list.objects[i].name)) { - hit = 1; - if (opt.status_only) - break; - } - } - - if (use_threads) - hit |= wait_all(); - free_grep_patterns(&opt); - return !hit; -} diff --git a/builtin-hash-object.c b/builtin-hash-object.c deleted file mode 100644 index 6a5f5b5f0..000000000 --- a/builtin-hash-object.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - * Copyright (C) Junio C Hamano, 2005 - */ -#include "cache.h" -#include "blob.h" -#include "quote.h" -#include "parse-options.h" -#include "exec_cmd.h" - -static void hash_fd(int fd, const char *type, int write_object, const char *path) -{ - struct stat st; - unsigned char sha1[20]; - if (fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) - die(write_object - ? "Unable to add %s to database" - : "Unable to hash %s", path); - printf("%s\n", sha1_to_hex(sha1)); - maybe_flush_or_die(stdout, "hash to stdout"); -} - -static void hash_object(const char *path, const char *type, int write_object, - const char *vpath) -{ - int fd; - fd = open(path, O_RDONLY); - if (fd < 0) - die_errno("Cannot open '%s'", path); - hash_fd(fd, type, write_object, vpath); -} - -static void hash_stdin_paths(const char *type, int write_objects) -{ - struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; - - while (strbuf_getline(&buf, stdin, '\n') != EOF) { - if (buf.buf[0] == '"') { - strbuf_reset(&nbuf); - if (unquote_c_style(&nbuf, buf.buf, NULL)) - die("line is badly quoted"); - strbuf_swap(&buf, &nbuf); - } - hash_object(buf.buf, type, write_objects, buf.buf); - } - strbuf_release(&buf); - strbuf_release(&nbuf); -} - -static const char * const hash_object_usage[] = { - "git hash-object [-t ] [-w] [--path=|--no-filters] [--stdin] [--] ...", - "git hash-object --stdin-paths < ", - NULL -}; - -static const char *type; -static int write_object; -static int hashstdin; -static int stdin_paths; -static int no_filters; -static const char *vpath; - -static const struct option hash_object_options[] = { - OPT_STRING('t', NULL, &type, "type", "object type"), - OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), - OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), - OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), - OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"), - OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"), - OPT_END() -}; - -int cmd_hash_object(int argc, const char **argv, const char *prefix) -{ - int i; - int prefix_length = -1; - const char *errstr = NULL; - - type = blob_type; - - argc = parse_options(argc, argv, NULL, hash_object_options, - hash_object_usage, 0); - - if (write_object) { - prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); - } - - git_config(git_default_config, NULL); - - if (stdin_paths) { - if (hashstdin) - errstr = "Can't use --stdin-paths with --stdin"; - else if (argc) - errstr = "Can't specify files with --stdin-paths"; - else if (vpath) - errstr = "Can't use --stdin-paths with --path"; - else if (no_filters) - errstr = "Can't use --stdin-paths with --no-filters"; - } - else { - if (hashstdin > 1) - errstr = "Multiple --stdin arguments are not supported"; - if (vpath && no_filters) - errstr = "Can't use --path with --no-filters"; - } - - if (errstr) { - error("%s", errstr); - usage_with_options(hash_object_usage, hash_object_options); - } - - if (hashstdin) - hash_fd(0, type, write_object, vpath); - - for (i = 0 ; i < argc; i++) { - const char *arg = argv[i]; - - if (0 <= prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type, write_object, - no_filters ? NULL : vpath ? vpath : arg); - } - - if (stdin_paths) - hash_stdin_paths(type, write_object); - - return 0; -} diff --git a/builtin-help.c b/builtin-help.c deleted file mode 100644 index 3182a2bec..000000000 --- a/builtin-help.c +++ /dev/null @@ -1,466 +0,0 @@ -/* - * builtin-help.c - * - * Builtin help command - */ -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "common-cmds.h" -#include "parse-options.h" -#include "run-command.h" -#include "help.h" - -static struct man_viewer_list { - struct man_viewer_list *next; - char name[FLEX_ARRAY]; -} *man_viewer_list; - -static struct man_viewer_info_list { - struct man_viewer_info_list *next; - const char *info; - char name[FLEX_ARRAY]; -} *man_viewer_info_list; - -enum help_format { - HELP_FORMAT_NONE, - HELP_FORMAT_MAN, - HELP_FORMAT_INFO, - HELP_FORMAT_WEB, -}; - -static int show_all = 0; -static enum help_format help_format = HELP_FORMAT_NONE; -static struct option builtin_help_options[] = { - OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), - OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), - OPT_SET_INT('w', "web", &help_format, "show manual in web browser", - HELP_FORMAT_WEB), - OPT_SET_INT('i', "info", &help_format, "show info page", - HELP_FORMAT_INFO), - OPT_END(), -}; - -static const char * const builtin_help_usage[] = { - "git help [--all] [--man|--web|--info] [command]", - NULL -}; - -static enum help_format parse_help_format(const char *format) -{ - if (!strcmp(format, "man")) - return HELP_FORMAT_MAN; - if (!strcmp(format, "info")) - return HELP_FORMAT_INFO; - if (!strcmp(format, "web") || !strcmp(format, "html")) - return HELP_FORMAT_WEB; - die("unrecognized help format '%s'", format); -} - -static const char *get_man_viewer_info(const char *name) -{ - struct man_viewer_info_list *viewer; - - for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) - { - if (!strcasecmp(name, viewer->name)) - return viewer->info; - } - return NULL; -} - -static int check_emacsclient_version(void) -{ - struct strbuf buffer = STRBUF_INIT; - struct child_process ec_process; - const char *argv_ec[] = { "emacsclient", "--version", NULL }; - int version; - - /* emacsclient prints its version number on stderr */ - memset(&ec_process, 0, sizeof(ec_process)); - ec_process.argv = argv_ec; - ec_process.err = -1; - ec_process.stdout_to_stderr = 1; - if (start_command(&ec_process)) - return error("Failed to start emacsclient."); - - strbuf_read(&buffer, ec_process.err, 20); - close(ec_process.err); - - /* - * Don't bother checking return value, because "emacsclient --version" - * seems to always exits with code 1. - */ - finish_command(&ec_process); - - if (prefixcmp(buffer.buf, "emacsclient")) { - strbuf_release(&buffer); - return error("Failed to parse emacsclient version."); - } - - strbuf_remove(&buffer, 0, strlen("emacsclient")); - version = atoi(buffer.buf); - - if (version < 22) { - strbuf_release(&buffer); - return error("emacsclient version '%d' too old (< 22).", - version); - } - - strbuf_release(&buffer); - return 0; -} - -static void exec_woman_emacs(const char *path, const char *page) -{ - if (!check_emacsclient_version()) { - /* This works only with emacsclient version >= 22. */ - struct strbuf man_page = STRBUF_INIT; - - if (!path) - path = "emacsclient"; - strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_konqueror(const char *path, const char *page) -{ - const char *display = getenv("DISPLAY"); - if (display && *display) { - struct strbuf man_page = STRBUF_INIT; - const char *filename = "kfmclient"; - - /* It's simpler to launch konqueror using kfmclient. */ - if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; - } else - path = "kfmclient"; - strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_man(const char *path, const char *page) -{ - if (!path) - path = "man"; - execlp(path, "man", page, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); -} - -static void exec_man_cmd(const char *cmd, const char *page) -{ - struct strbuf shell_cmd = STRBUF_INIT; - strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); -} - -static void add_man_viewer(const char *name) -{ - struct man_viewer_list **p = &man_viewer_list; - size_t len = strlen(name); - - while (*p) - p = &((*p)->next); - *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); -} - -static int supported_man_viewer(const char *name, size_t len) -{ - return (!strncasecmp("man", name, len) || - !strncasecmp("woman", name, len) || - !strncasecmp("konqueror", name, len)); -} - -static void do_add_man_viewer_info(const char *name, - size_t len, - const char *value) -{ - struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - - strncpy(new->name, name, len); - new->info = xstrdup(value); - new->next = man_viewer_info_list; - man_viewer_info_list = new; -} - -static int add_man_viewer_path(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - do_add_man_viewer_info(name, len, value); - else - warning("'%s': path for unsupported man viewer.\n" - "Please consider using 'man..cmd' instead.", - name); - - return 0; -} - -static int add_man_viewer_cmd(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - warning("'%s': cmd for supported man viewer.\n" - "Please consider using 'man..path' instead.", - name); - else - do_add_man_viewer_info(name, len, value); - - return 0; -} - -static int add_man_viewer_info(const char *var, const char *value) -{ - const char *name = var + 4; - const char *subkey = strrchr(name, '.'); - - if (!subkey) - return 0; - - if (!strcmp(subkey, ".path")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_path(name, subkey - name, value); - } - if (!strcmp(subkey, ".cmd")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_cmd(name, subkey - name, value); - } - - return 0; -} - -static int git_help_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "help.format")) { - if (!value) - return config_error_nonbool(var); - help_format = parse_help_format(value); - return 0; - } - if (!strcmp(var, "man.viewer")) { - if (!value) - return config_error_nonbool(var); - add_man_viewer(value); - return 0; - } - if (!prefixcmp(var, "man.")) - return add_man_viewer_info(var, value); - - return git_default_config(var, value, cb); -} - -static struct cmdnames main_cmds, other_cmds; - -void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); - puts(common_cmds[i].help); - } -} - -static int is_git_command(const char *s) -{ - return is_in_cmdlist(&main_cmds, s) || - is_in_cmdlist(&other_cmds, s); -} - -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - -static const char *cmd_to_page(const char *git_cmd) -{ - if (!git_cmd) - return "git"; - else if (!prefixcmp(git_cmd, "git")) - return git_cmd; - else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); - else - return prepend("git", git_cmd); -} - -static void setup_man_path(void) -{ - struct strbuf new_path = STRBUF_INIT; - const char *old_path = getenv("MANPATH"); - - /* We should always put ':' after our path. If there is no - * old_path, the ':' at the end will let 'man' to try - * system-wide paths after ours to find the manual page. If - * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, system_path(GIT_MAN_PATH)); - strbuf_addch(&new_path, ':'); - if (old_path) - strbuf_addstr(&new_path, old_path); - - setenv("MANPATH", new_path.buf, 1); - - strbuf_release(&new_path); -} - -static void exec_viewer(const char *name, const char *page) -{ - const char *info = get_man_viewer_info(name); - - if (!strcasecmp(name, "man")) - exec_man_man(info, page); - else if (!strcasecmp(name, "woman")) - exec_woman_emacs(info, page); - else if (!strcasecmp(name, "konqueror")) - exec_man_konqueror(info, page); - else if (info) - exec_man_cmd(info, page); - else - warning("'%s': unknown man viewer.", name); -} - -static void show_man_page(const char *git_cmd) -{ - struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); - const char *fallback = getenv("GIT_MAN_VIEWER"); - - setup_man_path(); - for (viewer = man_viewer_list; viewer; viewer = viewer->next) - { - exec_viewer(viewer->name, page); /* will return when unable */ - } - if (fallback) - exec_viewer(fallback, page); - exec_viewer("man", page); - die("no man viewer handled the request"); -} - -static void show_info_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); - execlp("info", "info", "gitman", page, NULL); - die("no info viewer handled the request"); -} - -static void get_html_page_path(struct strbuf *page_path, const char *page) -{ - struct stat st; - const char *html_path = system_path(GIT_HTML_PATH); - - /* Check that we have a git documentation directory. */ - if (stat(mkpath("%s/git.html", html_path), &st) - || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); - - strbuf_init(page_path, 0); - strbuf_addf(page_path, "%s/%s.html", html_path, page); -} - -/* - * If open_html is not defined in a platform-specific way (see for - * example compat/mingw.h), we use the script web--browse to display - * HTML. - */ -#ifndef open_html -static void open_html(const char *path) -{ - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); -} -#endif - -static void show_html_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - struct strbuf page_path; /* it leaks but we exec bellow */ - - get_html_page_path(&page_path, page); - - open_html(page_path.buf); -} - -int cmd_help(int argc, const char **argv, const char *prefix) -{ - int nongit; - const char *alias; - enum help_format parsed_help_format; - load_command_list("git-", &main_cmds, &other_cmds); - - argc = parse_options(argc, argv, prefix, builtin_help_options, - builtin_help_usage, 0); - parsed_help_format = help_format; - - if (show_all) { - printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", &main_cmds, &other_cmds); - printf("%s\n", git_more_info_string); - return 0; - } - - if (!argv[0]) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); - return 0; - } - - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - - if (parsed_help_format != HELP_FORMAT_NONE) - help_format = parsed_help_format; - - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf("`git %s' is aliased to `%s'\n", argv[0], alias); - return 0; - } - - switch (help_format) { - case HELP_FORMAT_NONE: - case HELP_FORMAT_MAN: - show_man_page(argv[0]); - break; - case HELP_FORMAT_INFO: - show_info_page(argv[0]); - break; - case HELP_FORMAT_WEB: - show_html_page(argv[0]); - break; - } - - return 0; -} diff --git a/builtin-index-pack.c b/builtin-index-pack.c deleted file mode 100644 index b4cf8c53e..000000000 --- a/builtin-index-pack.c +++ /dev/null @@ -1,1045 +0,0 @@ -#include "cache.h" -#include "delta.h" -#include "pack.h" -#include "csum-file.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" -#include "progress.h" -#include "fsck.h" -#include "exec_cmd.h" - -static const char index_pack_usage[] = -"git index-pack [-v] [-o ] [{ ---keep | --keep= }] [--strict] { | --stdin [--fix-thin] [] }"; - -struct object_entry -{ - struct pack_idx_entry idx; - unsigned long size; - unsigned int hdr_size; - enum object_type type; - enum object_type real_type; -}; - -union delta_base { - unsigned char sha1[20]; - off_t offset; -}; - -struct base_data { - struct base_data *base; - struct base_data *child; - struct object_entry *obj; - void *data; - unsigned long size; -}; - -/* - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want - * to memcmp() only the first 20 bytes. - */ -#define UNION_BASE_SZ 20 - -#define FLAG_LINK (1u<<20) -#define FLAG_CHECKED (1u<<21) - -struct delta_entry -{ - union delta_base base; - int obj_no; -}; - -static struct object_entry *objects; -static struct delta_entry *deltas; -static struct base_data *base_cache; -static size_t base_cache_used; -static int nr_objects; -static int nr_deltas; -static int nr_resolved_deltas; - -static int from_stdin; -static int strict; -static int verbose; - -static struct progress *progress; - -/* We always read in 4kB chunks. */ -static unsigned char input_buffer[4096]; -static unsigned int input_offset, input_len; -static off_t consumed_bytes; -static git_SHA_CTX input_ctx; -static uint32_t input_crc32; -static int input_fd, output_fd, pack_fd; - -static int mark_link(struct object *obj, int type, void *data) -{ - if (!obj) - return -1; - - if (type != OBJ_ANY && obj->type != type) - die("object type mismatch at %s", sha1_to_hex(obj->sha1)); - - obj->flags |= FLAG_LINK; - return 0; -} - -/* The content of each linked object must have been checked - or it must be already present in the object database */ -static void check_object(struct object *obj) -{ - if (!obj) - return; - - if (!(obj->flags & FLAG_LINK)) - return; - - if (!(obj->flags & FLAG_CHECKED)) { - unsigned long size; - int type = sha1_object_info(obj->sha1, &size); - if (type != obj->type || type <= 0) - die("object of unexpected type"); - obj->flags |= FLAG_CHECKED; - return; - } -} - -static void check_objects(void) -{ - unsigned i, max; - - max = get_max_object_index(); - for (i = 0; i < max; i++) - check_object(get_indexed_object(i)); -} - - -/* Discard current buffer used content. */ -static void flush(void) -{ - if (input_offset) { - if (output_fd >= 0) - write_or_die(output_fd, input_buffer, input_offset); - git_SHA1_Update(&input_ctx, input_buffer, input_offset); - memmove(input_buffer, input_buffer + input_offset, input_len); - input_offset = 0; - } -} - -/* - * Make sure at least "min" bytes are available in the buffer, and - * return the pointer to the buffer. - */ -static void *fill(int min) -{ - if (min <= input_len) - return input_buffer + input_offset; - if (min > sizeof(input_buffer)) - die("cannot fill %d bytes", min); - flush(); - do { - ssize_t ret = xread(input_fd, input_buffer + input_len, - sizeof(input_buffer) - input_len); - if (ret <= 0) { - if (!ret) - die("early EOF"); - die_errno("read error on input"); - } - input_len += ret; - if (from_stdin) - display_throughput(progress, consumed_bytes + input_len); - } while (input_len < min); - return input_buffer; -} - -static void use(int bytes) -{ - if (bytes > input_len) - die("used more bytes than were available"); - input_crc32 = crc32(input_crc32, input_buffer + input_offset, bytes); - input_len -= bytes; - input_offset += bytes; - - /* make sure off_t is sufficiently large not to wrap */ - if (consumed_bytes > consumed_bytes + bytes) - die("pack too large for current definition of off_t"); - consumed_bytes += bytes; -} - -static const char *open_pack_file(const char *pack_name) -{ - if (from_stdin) { - input_fd = 0; - if (!pack_name) { - static char tmpfile[PATH_MAX]; - output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), - "pack/tmp_pack_XXXXXX"); - pack_name = xstrdup(tmpfile); - } else - output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); - if (output_fd < 0) - die_errno("unable to create '%s'", pack_name); - pack_fd = output_fd; - } else { - input_fd = open(pack_name, O_RDONLY); - if (input_fd < 0) - die_errno("cannot open packfile '%s'", pack_name); - output_fd = -1; - pack_fd = input_fd; - } - git_SHA1_Init(&input_ctx); - return pack_name; -} - -static void parse_pack_header(void) -{ - struct pack_header *hdr = fill(sizeof(struct pack_header)); - - /* Header consistency check */ - if (hdr->hdr_signature != htonl(PACK_SIGNATURE)) - die("pack signature mismatch"); - if (!pack_version_ok(hdr->hdr_version)) - die("pack version %"PRIu32" unsupported", - ntohl(hdr->hdr_version)); - - nr_objects = ntohl(hdr->hdr_entries); - use(sizeof(struct pack_header)); -} - -static NORETURN void bad_object(unsigned long offset, const char *format, - ...) __attribute__((format (printf, 2, 3))); - -static void bad_object(unsigned long offset, const char *format, ...) -{ - va_list params; - char buf[1024]; - - va_start(params, format); - vsnprintf(buf, sizeof(buf), format, params); - va_end(params); - die("pack has bad object at offset %lu: %s", offset, buf); -} - -static void free_base_data(struct base_data *c) -{ - if (c->data) { - free(c->data); - c->data = NULL; - base_cache_used -= c->size; - } -} - -static void prune_base_data(struct base_data *retain) -{ - struct base_data *b; - for (b = base_cache; - base_cache_used > delta_base_cache_limit && b; - b = b->child) { - if (b->data && b != retain) - free_base_data(b); - } -} - -static void link_base_data(struct base_data *base, struct base_data *c) -{ - if (base) - base->child = c; - else - base_cache = c; - - c->base = base; - c->child = NULL; - if (c->data) - base_cache_used += c->size; - prune_base_data(c); -} - -static void unlink_base_data(struct base_data *c) -{ - struct base_data *base = c->base; - if (base) - base->child = NULL; - else - base_cache = NULL; - free_base_data(c); -} - -static void *unpack_entry_data(unsigned long offset, unsigned long size) -{ - z_stream stream; - void *buf = xmalloc(size); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = buf; - stream.avail_out = size; - stream.next_in = fill(1); - stream.avail_in = input_len; - git_inflate_init(&stream); - - for (;;) { - int ret = git_inflate(&stream, 0); - use(input_len - stream.avail_in); - if (stream.total_out == size && ret == Z_STREAM_END) - break; - if (ret != Z_OK) - bad_object(offset, "inflate returned %d", ret); - stream.next_in = fill(1); - stream.avail_in = input_len; - } - git_inflate_end(&stream); - return buf; -} - -static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) -{ - unsigned char *p; - unsigned long size, c; - off_t base_offset; - unsigned shift; - void *data; - - obj->idx.offset = consumed_bytes; - input_crc32 = crc32(0, Z_NULL, 0); - - p = fill(1); - c = *p; - use(1); - obj->type = (c >> 4) & 7; - size = (c & 15); - shift = 4; - while (c & 0x80) { - p = fill(1); - c = *p; - use(1); - size += (c & 0x7f) << shift; - shift += 7; - } - obj->size = size; - - switch (obj->type) { - case OBJ_REF_DELTA: - hashcpy(delta_base->sha1, fill(20)); - use(20); - break; - case OBJ_OFS_DELTA: - memset(delta_base, 0, sizeof(*delta_base)); - p = fill(1); - c = *p; - use(1); - base_offset = c & 127; - while (c & 128) { - base_offset += 1; - if (!base_offset || MSB(base_offset, 7)) - bad_object(obj->idx.offset, "offset value overflow for delta base object"); - p = fill(1); - c = *p; - use(1); - base_offset = (base_offset << 7) + (c & 127); - } - delta_base->offset = obj->idx.offset - base_offset; - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) - bad_object(obj->idx.offset, "delta base offset is out of bound"); - break; - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - break; - default: - bad_object(obj->idx.offset, "unknown object type %d", obj->type); - } - obj->hdr_size = consumed_bytes - obj->idx.offset; - - data = unpack_entry_data(obj->idx.offset, obj->size); - obj->idx.crc32 = input_crc32; - return data; -} - -static void *get_data_from_pack(struct object_entry *obj) -{ - off_t from = obj[0].idx.offset + obj[0].hdr_size; - unsigned long len = obj[1].idx.offset - from; - unsigned long rdy = 0; - unsigned char *src, *data; - z_stream stream; - int st; - - src = xmalloc(len); - data = src; - do { - ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy); - if (n < 0) - die_errno("cannot pread pack file"); - if (!n) - die("premature end of pack file, %lu bytes missing", - len - rdy); - rdy += n; - } while (rdy < len); - data = xmalloc(obj->size); - memset(&stream, 0, sizeof(stream)); - stream.next_out = data; - stream.avail_out = obj->size; - stream.next_in = src; - stream.avail_in = len; - git_inflate_init(&stream); - while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK); - git_inflate_end(&stream); - if (st != Z_STREAM_END || stream.total_out != obj->size) - die("serious inflate inconsistency"); - free(src); - return data; -} - -static int find_delta(const union delta_base *base) -{ - int first = 0, last = nr_deltas; - - while (first < last) { - int next = (first + last) / 2; - struct delta_entry *delta = &deltas[next]; - int cmp; - - cmp = memcmp(base, &delta->base, UNION_BASE_SZ); - if (!cmp) - return next; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - return -first-1; -} - -static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index) -{ - int first = find_delta(base); - int last = first; - int end = nr_deltas - 1; - - if (first < 0) { - *first_index = 0; - *last_index = -1; - return; - } - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) - --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) - ++last; - *first_index = first; - *last_index = last; -} - -static void sha1_object(const void *data, unsigned long size, - enum object_type type, unsigned char *sha1) -{ - hash_sha1_file(data, size, typename(type), sha1); - if (has_sha1_file(sha1)) { - void *has_data; - enum object_type has_type; - unsigned long has_size; - has_data = read_sha1_file(sha1, &has_type, &has_size); - if (!has_data) - die("cannot read existing object %s", sha1_to_hex(sha1)); - if (size != has_size || type != has_type || - memcmp(data, has_data, size) != 0) - die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1)); - free(has_data); - } - if (strict) { - if (type == OBJ_BLOB) { - struct blob *blob = lookup_blob(sha1); - if (blob) - blob->object.flags |= FLAG_CHECKED; - else - die("invalid blob object %s", sha1_to_hex(sha1)); - } else { - struct object *obj; - int eaten; - void *buf = (void *) data; - - /* - * we do not need to free the memory here, as the - * buf is deleted by the caller. - */ - obj = parse_object_buffer(sha1, type, size, buf, &eaten); - if (!obj) - die("invalid %s", typename(type)); - if (fsck_object(obj, 1, fsck_error_function)) - die("Error in object"); - if (fsck_walk(obj, mark_link, NULL)) - die("Not all child objects of %s are reachable", sha1_to_hex(obj->sha1)); - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - item->buffer = NULL; - } - if (obj->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *) obj; - commit->buffer = NULL; - } - obj->flags |= FLAG_CHECKED; - } - } -} - -static void *get_base_data(struct base_data *c) -{ - if (!c->data) { - struct object_entry *obj = c->obj; - - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { - void *base = get_base_data(c->base); - void *raw = get_data_from_pack(obj); - c->data = patch_delta( - base, c->base->size, - raw, obj->size, - &c->size); - free(raw); - if (!c->data) - bad_object(obj->idx.offset, "failed to apply delta"); - } else { - c->data = get_data_from_pack(obj); - c->size = obj->size; - } - - base_cache_used += c->size; - prune_base_data(c); - } - return c->data; -} - -static void resolve_delta(struct object_entry *delta_obj, - struct base_data *base, struct base_data *result) -{ - void *base_data, *delta_data; - - delta_obj->real_type = base->obj->real_type; - delta_data = get_data_from_pack(delta_obj); - base_data = get_base_data(base); - result->obj = delta_obj; - result->data = patch_delta(base_data, base->size, - delta_data, delta_obj->size, &result->size); - free(delta_data); - if (!result->data) - bad_object(delta_obj->idx.offset, "failed to apply delta"); - sha1_object(result->data, result->size, delta_obj->real_type, - delta_obj->idx.sha1); - nr_resolved_deltas++; -} - -static void find_unresolved_deltas(struct base_data *base, - struct base_data *prev_base) -{ - int i, ref_first, ref_last, ofs_first, ofs_last; - - /* - * This is a recursive function. Those brackets should help reducing - * stack usage by limiting the scope of the delta_base union. - */ - { - union delta_base base_spec; - - hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, &ref_first, &ref_last); - - memset(&base_spec, 0, sizeof(base_spec)); - base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, &ofs_first, &ofs_last); - } - - if (ref_last == -1 && ofs_last == -1) { - free(base->data); - return; - } - - link_base_data(prev_base, base); - - for (i = ref_first; i <= ref_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_REF_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ref_last && ofs_last == -1) - free_base_data(base); - find_unresolved_deltas(&result, base); - } - } - - for (i = ofs_first; i <= ofs_last; i++) { - struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_OFS_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ofs_last) - free_base_data(base); - find_unresolved_deltas(&result, base); - } - } - - unlink_base_data(base); -} - -static int compare_delta_entry(const void *a, const void *b) -{ - const struct delta_entry *delta_a = a; - const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); -} - -/* Parse all objects and return the pack content SHA1 hash */ -static void parse_pack_objects(unsigned char *sha1) -{ - int i; - struct delta_entry *delta = deltas; - struct stat st; - - /* - * First pass: - * - find locations of all objects; - * - calculate SHA1 of all non-delta objects; - * - remember base (SHA1 or offset) for all deltas. - */ - if (verbose) - progress = start_progress( - from_stdin ? "Receiving objects" : "Indexing objects", - nr_objects); - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base); - obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { - nr_deltas++; - delta->obj_no = i; - delta++; - } else - sha1_object(data, obj->size, obj->type, obj->idx.sha1); - free(data); - display_progress(progress, i+1); - } - objects[i].idx.offset = consumed_bytes; - stop_progress(&progress); - - /* Check pack integrity */ - flush(); - git_SHA1_Final(sha1, &input_ctx); - if (hashcmp(fill(20), sha1)) - die("pack is corrupted (SHA1 mismatch)"); - use(20); - - /* If input_fd is a file, we should have reached its end now. */ - if (fstat(input_fd, &st)) - die_errno("cannot fstat packfile"); - if (S_ISREG(st.st_mode) && - lseek(input_fd, 0, SEEK_CUR) - input_len != st.st_size) - die("pack has junk at the end"); - - if (!nr_deltas) - return; - - /* Sort deltas by base SHA1/offset for fast searching */ - qsort(deltas, nr_deltas, sizeof(struct delta_entry), - compare_delta_entry); - - /* - * Second pass: - * - for all non-delta objects, look if it is used as a base for - * deltas; - * - if used as a base, uncompress the object and apply all deltas, - * recursively checking if the resulting object is used as a base - * for some more deltas. - */ - if (verbose) - progress = start_progress("Resolving deltas", nr_deltas); - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - struct base_data base_obj; - - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) - continue; - base_obj.obj = obj; - base_obj.data = NULL; - find_unresolved_deltas(&base_obj, NULL); - display_progress(progress, nr_resolved_deltas); - } -} - -static int write_compressed(struct sha1file *f, void *in, unsigned int size) -{ - z_stream stream; - unsigned long maxsize; - void *out; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - out = xmalloc(maxsize); - - /* Compress it */ - stream.next_in = in; - stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK); - deflateEnd(&stream); - - size = stream.total_out; - sha1write(f, out, size); - free(out); - return size; -} - -static struct object_entry *append_obj_to_pack(struct sha1file *f, - const unsigned char *sha1, void *buf, - unsigned long size, enum object_type type) -{ - struct object_entry *obj = &objects[nr_objects++]; - unsigned char header[10]; - unsigned long s = size; - int n = 0; - unsigned char c = (type << 4) | (s & 15); - s >>= 4; - while (s) { - header[n++] = c | 0x80; - c = s & 0x7f; - s >>= 7; - } - header[n++] = c; - crc32_begin(f); - sha1write(f, header, n); - obj[0].size = size; - obj[0].hdr_size = n; - obj[0].type = type; - obj[0].real_type = type; - obj[1].idx.offset = obj[0].idx.offset + n; - obj[1].idx.offset += write_compressed(f, buf, size); - obj[0].idx.crc32 = crc32_end(f); - sha1flush(f); - hashcpy(obj->idx.sha1, sha1); - return obj; -} - -static int delta_pos_compare(const void *_a, const void *_b) -{ - struct delta_entry *a = *(struct delta_entry **)_a; - struct delta_entry *b = *(struct delta_entry **)_b; - return a->obj_no - b->obj_no; -} - -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) -{ - struct delta_entry **sorted_by_pos; - int i, n = 0; - - /* - * Since many unresolved deltas may well be themselves base objects - * for more unresolved deltas, we really want to include the - * smallest number of base objects that would cover as much delta - * as possible by picking the - * trunc deltas first, allowing for other deltas to resolve without - * additional base objects. Since most base objects are to be found - * before deltas depending on them, a good heuristic is to start - * resolving deltas in the same order as their position in the pack. - */ - sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); - for (i = 0; i < nr_deltas; i++) { - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) - continue; - sorted_by_pos[n++] = &deltas[i]; - } - qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); - - for (i = 0; i < n; i++) { - struct delta_entry *d = sorted_by_pos[i]; - enum object_type type; - struct base_data base_obj; - - if (objects[d->obj_no].real_type != OBJ_REF_DELTA) - continue; - base_obj.data = read_sha1_file(d->base.sha1, &type, &base_obj.size); - if (!base_obj.data) - continue; - - if (check_sha1_signature(d->base.sha1, base_obj.data, - base_obj.size, typename(type))) - die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); - base_obj.obj = append_obj_to_pack(f, d->base.sha1, - base_obj.data, base_obj.size, type); - find_unresolved_deltas(&base_obj, NULL); - display_progress(progress, nr_resolved_deltas); - } - free(sorted_by_pos); -} - -static void final(const char *final_pack_name, const char *curr_pack_name, - const char *final_index_name, const char *curr_index_name, - const char *keep_name, const char *keep_msg, - unsigned char *sha1) -{ - const char *report = "pack"; - char name[PATH_MAX]; - int err; - - if (!from_stdin) { - close(input_fd); - } else { - fsync_or_die(output_fd, curr_pack_name); - err = close(output_fd); - if (err) - die_errno("error while closing pack file"); - } - - if (keep_msg) { - int keep_fd, keep_msg_len = strlen(keep_msg); - - if (!keep_name) - keep_fd = odb_pack_keep(name, sizeof(name), sha1); - else - keep_fd = open(keep_name, O_RDWR|O_CREAT|O_EXCL, 0600); - - if (keep_fd < 0) { - if (errno != EEXIST) - die_errno("cannot write keep file '%s'", - keep_name); - } else { - if (keep_msg_len > 0) { - write_or_die(keep_fd, keep_msg, keep_msg_len); - write_or_die(keep_fd, "\n", 1); - } - if (close(keep_fd) != 0) - die_errno("cannot close written keep file '%s'", - keep_name); - report = "keep"; - } - } - - if (final_pack_name != curr_pack_name) { - if (!final_pack_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.pack", - get_object_directory(), sha1_to_hex(sha1)); - final_pack_name = name; - } - if (move_temp_to_file(curr_pack_name, final_pack_name)) - die("cannot store pack file"); - } else if (from_stdin) - chmod(final_pack_name, 0444); - - if (final_index_name != curr_index_name) { - if (!final_index_name) { - snprintf(name, sizeof(name), "%s/pack/pack-%s.idx", - get_object_directory(), sha1_to_hex(sha1)); - final_index_name = name; - } - if (move_temp_to_file(curr_index_name, final_index_name)) - die("cannot store index file"); - } else - chmod(final_index_name, 0444); - - if (!from_stdin) { - printf("%s\n", sha1_to_hex(sha1)); - } else { - char buf[48]; - int len = snprintf(buf, sizeof(buf), "%s\t%s\n", - report, sha1_to_hex(sha1)); - write_or_die(1, buf, len); - - /* - * Let's just mimic git-unpack-objects here and write - * the last part of the input buffer to stdout. - */ - while (input_len) { - err = xwrite(1, input_buffer + input_offset, input_len); - if (err <= 0) - break; - input_len -= err; - input_offset += err; - } - } -} - -static int git_index_pack_config(const char *k, const char *v, void *cb) -{ - 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; - } - return git_default_config(k, v, cb); -} - -int cmd_index_pack(int argc, const char **argv, const char *prefix) -{ - int i, fix_thin_pack = 0; - const char *curr_pack, *curr_index; - const char *index_name = NULL, *pack_name = NULL; - const char *keep_name = NULL, *keep_msg = NULL; - char *index_name_buf = NULL, *keep_name_buf = NULL; - struct pack_idx_entry **idx_objects; - unsigned char pack_sha1[20]; - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(index_pack_usage); - - /* - * We wish to read the repository's config file if any, and - * for that it is necessary to call setup_git_directory_gently(). - * However if the cwd was inside .git/objects/pack/ then we need - * to go back there or all the pack name arguments will be wrong. - * And in that case we cannot rely on any prefix returned by - * setup_git_directory_gently() either. - */ - { - char cwd[PATH_MAX+1]; - int nongit; - - if (!getcwd(cwd, sizeof(cwd)-1)) - die("Unable to get current working directory"); - setup_git_directory_gently(&nongit); - git_config(git_index_pack_config, NULL); - if (chdir(cwd)) - die("Cannot come back to cwd"); - } - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!strcmp(arg, "--stdin")) { - from_stdin = 1; - } else if (!strcmp(arg, "--fix-thin")) { - fix_thin_pack = 1; - } else if (!strcmp(arg, "--strict")) { - strict = 1; - } else if (!strcmp(arg, "--keep")) { - keep_msg = ""; - } else if (!prefixcmp(arg, "--keep=")) { - keep_msg = arg + 7; - } else if (!prefixcmp(arg, "--pack_header=")) { - struct pack_header *hdr; - char *c; - - hdr = (struct pack_header *)input_buffer; - hdr->hdr_signature = htonl(PACK_SIGNATURE); - hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10)); - if (*c != ',') - die("bad %s", arg); - hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10)); - if (*c) - die("bad %s", arg); - input_len = sizeof(*hdr); - } else if (!strcmp(arg, "-v")) { - verbose = 1; - } else if (!strcmp(arg, "-o")) { - if (index_name || (i+1) >= argc) - usage(index_pack_usage); - index_name = argv[++i]; - } else if (!prefixcmp(arg, "--index-version=")) { - char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); - if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); - } else - usage(index_pack_usage); - continue; - } - - if (pack_name) - usage(index_pack_usage); - pack_name = arg; - } - - if (!pack_name && !from_stdin) - usage(index_pack_usage); - if (fix_thin_pack && !from_stdin) - die("--fix-thin cannot be used without --stdin"); - if (!index_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", - pack_name); - index_name_buf = xmalloc(len); - memcpy(index_name_buf, pack_name, len - 5); - strcpy(index_name_buf + len - 5, ".idx"); - index_name = index_name_buf; - } - if (keep_msg && !keep_name && pack_name) { - int len = strlen(pack_name); - if (!has_extension(pack_name, ".pack")) - die("packfile name '%s' does not end with '.pack'", - pack_name); - keep_name_buf = xmalloc(len); - memcpy(keep_name_buf, pack_name, len - 5); - strcpy(keep_name_buf + len - 5, ".keep"); - keep_name = keep_name_buf; - } - - curr_pack = open_pack_file(pack_name); - parse_pack_header(); - objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); - deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); - parse_pack_objects(pack_sha1); - if (nr_deltas == nr_resolved_deltas) { - stop_progress(&progress); - /* Flush remaining pack final 20-byte SHA1. */ - flush(); - } else { - if (fix_thin_pack) { - struct sha1file *f; - unsigned char read_sha1[20], tail_sha1[20]; - char msg[48]; - int nr_unresolved = nr_deltas - nr_resolved_deltas; - int nr_objects_initial = nr_objects; - if (nr_unresolved <= 0) - die("confusion beyond insanity"); - objects = xrealloc(objects, - (nr_objects + nr_unresolved + 1) - * sizeof(*objects)); - f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); - sprintf(msg, "completed with %d local objects", - nr_objects - nr_objects_initial); - stop_progress_msg(&progress, msg); - sha1close(f, tail_sha1, 0); - hashcpy(read_sha1, pack_sha1); - fixup_pack_header_footer(output_fd, pack_sha1, - curr_pack, nr_objects, - read_sha1, consumed_bytes-20); - if (hashcmp(read_sha1, tail_sha1) != 0) - die("Unexpected tail checksum for %s " - "(disk corruption?)", curr_pack); - } - if (nr_deltas != nr_resolved_deltas) - die("pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas); - } - free(deltas); - if (strict) - check_objects(); - - idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); - for (i = 0; i < nr_objects; i++) - idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); - free(idx_objects); - - final(pack_name, curr_pack, - index_name, curr_index, - keep_name, keep_msg, - pack_sha1); - free(objects); - free(index_name_buf); - free(keep_name_buf); - if (pack_name == NULL) - free((void *) curr_pack); - if (index_name == NULL) - free((void *) curr_index); - - return 0; -} diff --git a/builtin-init-db.c b/builtin-init-db.c deleted file mode 100644 index dd84caecb..000000000 --- a/builtin-init-db.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "parse-options.h" - -#ifndef DEFAULT_GIT_TEMPLATE_DIR -#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" -#endif - -#ifdef NO_TRUSTABLE_FILEMODE -#define TEST_FILEMODE 0 -#else -#define TEST_FILEMODE 1 -#endif - -static int init_is_bare_repository = 0; -static int init_shared_repository = -1; - -static void safe_create_dir(const char *dir, int share) -{ - if (mkdir(dir, 0777) < 0) { - if (errno != EEXIST) { - perror(dir); - exit(1); - } - } - else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group", dir); -} - -static void copy_templates_1(char *path, int baselen, - char *template, int template_baselen, - DIR *dir) -{ - struct dirent *de; - - /* Note: if ".git/hooks" file exists in the repository being - * re-initialized, /etc/core-git/templates/hooks/update would - * cause "git init" to fail here. I think this is sane but - * it means that the set of templates we ship by default, along - * with the way the namespace under .git/ is organized, should - * be really carefully chosen. - */ - safe_create_dir(path, 1); - while ((de = readdir(dir)) != NULL) { - struct stat st_git, st_template; - int namelen; - int exists = 0; - - if (de->d_name[0] == '.') - continue; - namelen = strlen(de->d_name); - if ((PATH_MAX <= baselen + namelen) || - (PATH_MAX <= template_baselen + namelen)) - die("insanely long template name %s", de->d_name); - memcpy(path + baselen, de->d_name, namelen+1); - memcpy(template + template_baselen, de->d_name, namelen+1); - if (lstat(path, &st_git)) { - if (errno != ENOENT) - die_errno("cannot stat '%s'", path); - } - else - exists = 1; - - if (lstat(template, &st_template)) - die_errno("cannot stat template '%s'", template); - - if (S_ISDIR(st_template.st_mode)) { - DIR *subdir = opendir(template); - int baselen_sub = baselen + namelen; - int template_baselen_sub = template_baselen + namelen; - if (!subdir) - die_errno("cannot opendir '%s'", template); - path[baselen_sub++] = - template[template_baselen_sub++] = '/'; - path[baselen_sub] = - template[template_baselen_sub] = 0; - copy_templates_1(path, baselen_sub, - template, template_baselen_sub, - subdir); - closedir(subdir); - } - else if (exists) - continue; - else if (S_ISLNK(st_template.st_mode)) { - char lnk[256]; - int len; - len = readlink(template, lnk, sizeof(lnk)); - if (len < 0) - die_errno("cannot readlink '%s'", template); - if (sizeof(lnk) <= len) - die("insanely long symlink %s", template); - lnk[len] = 0; - if (symlink(lnk, path)) - die_errno("cannot symlink '%s' '%s'", lnk, path); - } - else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path, template, st_template.st_mode)) - die_errno("cannot copy '%s' to '%s'", template, - path); - } - else - error("ignoring template %s", template); - } -} - -static void copy_templates(const char *template_dir) -{ - char path[PATH_MAX]; - char template_path[PATH_MAX]; - int template_len; - DIR *dir; - const char *git_dir = get_git_dir(); - int len = strlen(git_dir); - - if (!template_dir) - template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); - if (!template_dir) - template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); - if (!template_dir[0]) - return; - template_len = strlen(template_dir); - if (PATH_MAX <= (template_len+strlen("/config"))) - die("insanely long template path %s", template_dir); - strcpy(template_path, template_dir); - if (template_path[template_len-1] != '/') { - template_path[template_len++] = '/'; - template_path[template_len] = 0; - } - dir = opendir(template_path); - if (!dir) { - warning("templates not found %s", template_dir); - return; - } - - /* Make sure that template is from the correct vintage */ - strcpy(template_path + template_len, "config"); - repository_format_version = 0; - git_config_from_file(check_repository_format_version, - template_path, NULL); - template_path[template_len] = 0; - - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - warning("not copying templates of " - "a wrong format version %d from '%s'", - repository_format_version, - template_dir); - closedir(dir); - return; - } - - memcpy(path, git_dir, len); - if (len && path[len - 1] != '/') - path[len++] = '/'; - path[len] = 0; - copy_templates_1(path, len, - template_path, template_len, - dir); - closedir(dir); -} - -static int create_default_files(const char *template_path) -{ - const char *git_dir = get_git_dir(); - unsigned len = strlen(git_dir); - static char path[PATH_MAX]; - struct stat st1; - char repo_version_string[10]; - char junk[2]; - int reinit; - int filemode; - - if (len > sizeof(path)-50) - die("insane git directory %s", git_dir); - memcpy(path, git_dir, len); - - if (len && path[len-1] != '/') - path[len++] = '/'; - - /* - * Create .git/refs/{heads,tags} - */ - safe_create_dir(git_path("refs"), 1); - safe_create_dir(git_path("refs/heads"), 1); - safe_create_dir(git_path("refs/tags"), 1); - - /* First copy the templates -- we might have the default - * config file there, in which case we would want to read - * from it after installing. - */ - copy_templates(template_path); - - git_config(git_default_config, NULL); - is_bare_repository_cfg = init_is_bare_repository; - - /* reading existing config may have overwrote it */ - if (init_shared_repository != -1) - shared_repository = init_shared_repository; - - /* - * We would have created the above under user's umask -- under - * shared-repository settings, we would need to fix them up. - */ - if (shared_repository) { - adjust_shared_perm(get_git_dir()); - adjust_shared_perm(git_path("refs")); - adjust_shared_perm(git_path("refs/heads")); - adjust_shared_perm(git_path("refs/tags")); - } - - /* - * Create the default symlink from ".git/HEAD" to the "master" - * branch, if it does not exist yet. - */ - strcpy(path + len, "HEAD"); - reinit = (!access(path, R_OK) - || readlink(path, junk, sizeof(junk)-1) != -1); - if (!reinit) { - if (create_symref("HEAD", "refs/heads/master", NULL) < 0) - exit(1); - } - - /* This forces creation of new config file */ - sprintf(repo_version_string, "%d", GIT_REPO_VERSION); - git_config_set("core.repositoryformatversion", repo_version_string); - - path[len] = 0; - strcpy(path + len, "config"); - - /* Check filemode trustability */ - filemode = TEST_FILEMODE; - if (TEST_FILEMODE && !lstat(path, &st1)) { - struct stat st2; - filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) && - !lstat(path, &st2) && - st1.st_mode != st2.st_mode); - } - git_config_set("core.filemode", filemode ? "true" : "false"); - - if (is_bare_repository()) - git_config_set("core.bare", "true"); - else { - const char *work_tree = get_git_work_tree(); - git_config_set("core.bare", "false"); - /* allow template config file to override the default */ - if (log_all_ref_updates == -1) - git_config_set("core.logallrefupdates", "true"); - if (prefixcmp(git_dir, work_tree) || - strcmp(git_dir + strlen(work_tree), "/.git")) { - git_config_set("core.worktree", work_tree); - } - } - - if (!reinit) { - /* Check if symlink is supported in the work tree */ - path[len] = 0; - strcpy(path + len, "tXXXXXX"); - if (!close(xmkstemp(path)) && - !unlink(path) && - !symlink("testing", path) && - !lstat(path, &st1) && - S_ISLNK(st1.st_mode)) - unlink(path); /* good */ - else - git_config_set("core.symlinks", "false"); - - /* Check if the filesystem is case-insensitive */ - path[len] = 0; - strcpy(path + len, "CoNfIg"); - if (!access(path, F_OK)) - git_config_set("core.ignorecase", "true"); - } - - return reinit; -} - -int init_db(const char *template_dir, unsigned int flags) -{ - const char *sha1_dir; - char *path; - int len, reinit; - - safe_create_dir(get_git_dir(), 0); - - init_is_bare_repository = is_bare_repository(); - - /* Check to see if the repository version is right. - * Note that a newly created repository does not have - * config file, so this will not fail. What we are catching - * is an attempt to reinitialize new repository with an old tool. - */ - check_repository_format(); - - reinit = create_default_files(template_dir); - - sha1_dir = get_object_directory(); - len = strlen(sha1_dir); - path = xmalloc(len + 40); - memcpy(path, sha1_dir, len); - - safe_create_dir(sha1_dir, 1); - strcpy(path+len, "/pack"); - safe_create_dir(path, 1); - strcpy(path+len, "/info"); - safe_create_dir(path, 1); - - if (shared_repository) { - char buf[10]; - /* We do not spell "group" and such, so that - * the configuration can be read by older version - * of git. Note, we use octal numbers for new share modes, - * and compatibility values for PERM_GROUP and - * PERM_EVERYBODY. - */ - if (shared_repository < 0) - /* force to the mode value */ - sprintf(buf, "0%o", -shared_repository); - else if (shared_repository == PERM_GROUP) - sprintf(buf, "%d", OLD_PERM_GROUP); - else if (shared_repository == PERM_EVERYBODY) - sprintf(buf, "%d", OLD_PERM_EVERYBODY); - else - die("oops"); - git_config_set("core.sharedrepository", buf); - git_config_set("receive.denyNonFastforwards", "true"); - } - - if (!(flags & INIT_DB_QUIET)) - printf("%s%s Git repository in %s/\n", - reinit ? "Reinitialized existing" : "Initialized empty", - shared_repository ? " shared" : "", - get_git_dir()); - - return 0; -} - -static int guess_repository_type(const char *git_dir) -{ - char cwd[PATH_MAX]; - const char *slash; - - /* - * "GIT_DIR=. git init" is always bare. - * "GIT_DIR=`pwd` git init" too. - */ - if (!strcmp(".", git_dir)) - return 1; - if (!getcwd(cwd, sizeof(cwd))) - die_errno("cannot tell cwd"); - if (!strcmp(git_dir, cwd)) - return 1; - /* - * "GIT_DIR=.git or GIT_DIR=something/.git is usually not. - */ - if (!strcmp(git_dir, ".git")) - return 0; - slash = strrchr(git_dir, '/'); - if (slash && !strcmp(slash, "/.git")) - return 0; - - /* - * Otherwise it is often bare. At this point - * we are just guessing. - */ - return 1; -} - -static int shared_callback(const struct option *opt, const char *arg, int unset) -{ - *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP; - return 0; -} - -static const char *const init_db_usage[] = { - "git init [-q | --quiet] [--bare] [--template=] [--shared[=]] [directory]", - NULL -}; - -/* - * If you want to, you can share the DB area with any number of branches. - * That has advantages: you can save space by sharing all the SHA1 objects. - * On the other hand, it might just make lookup slower and messier. You - * be the judge. The default case is to have one DB per managed directory. - */ -int cmd_init_db(int argc, const char **argv, const char *prefix) -{ - const char *git_dir; - const char *template_dir = NULL; - unsigned int flags = 0; - const struct option init_db_options[] = { - OPT_STRING(0, "template", &template_dir, "template-directory", - "provide the directory from which templates will be used"), - OPT_SET_INT(0, "bare", &is_bare_repository_cfg, - "create a bare repository", 1), - { OPTION_CALLBACK, 0, "shared", &init_shared_repository, - "permissions", - "specify that the git repository is to be shared amongst several users", - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, - OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); - - if (argc == 1) { - int mkdir_tried = 0; - retry: - if (chdir(argv[0]) < 0) { - if (!mkdir_tried) { - int saved; - /* - * At this point we haven't read any configuration, - * and we know shared_repository should always be 0; - * but just in case we play safe. - */ - saved = shared_repository; - shared_repository = 0; - switch (safe_create_leading_directories_const(argv[0])) { - case -3: - errno = EEXIST; - /* fallthru */ - case -1: - die_errno("cannot mkdir %s", argv[0]); - break; - default: - break; - } - shared_repository = saved; - if (mkdir(argv[0], 0777) < 0) - die_errno("cannot mkdir %s", argv[0]); - mkdir_tried = 1; - goto retry; - } - die_errno("cannot chdir to %s", argv[0]); - } - } else if (0 < argc) { - usage(init_db_usage[0]); - } - if (is_bare_repository_cfg == 1) { - static char git_dir[PATH_MAX+1]; - - setenv(GIT_DIR_ENVIRONMENT, - getcwd(git_dir, sizeof(git_dir)), 0); - } - - if (init_shared_repository != -1) - shared_repository = init_shared_repository; - - /* - * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR - * without --bare. Catch the error early. - */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - if ((!git_dir || is_bare_repository_cfg == 1) - && getenv(GIT_WORK_TREE_ENVIRONMENT)) - die("%s (or --work-tree=) not allowed without " - "specifying %s (or --git-dir=)", - GIT_WORK_TREE_ENVIRONMENT, - GIT_DIR_ENVIRONMENT); - - /* - * Set up the default .git directory contents - */ - if (!git_dir) - git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - - if (is_bare_repository_cfg < 0) - is_bare_repository_cfg = guess_repository_type(git_dir); - - if (!is_bare_repository_cfg) { - if (git_dir) { - const char *git_dir_parent = strrchr(git_dir, '/'); - if (git_dir_parent) { - char *rel = xstrndup(git_dir, git_dir_parent - git_dir); - git_work_tree_cfg = xstrdup(make_absolute_path(rel)); - free(rel); - } - } - if (!git_work_tree_cfg) { - git_work_tree_cfg = xcalloc(PATH_MAX, 1); - if (!getcwd(git_work_tree_cfg, PATH_MAX)) - die_errno ("Cannot access current working directory"); - } - if (access(get_git_work_tree(), X_OK)) - die_errno ("Cannot access work tree '%s'", - get_git_work_tree()); - } - - set_git_dir(make_absolute_path(git_dir)); - - return init_db(template_dir, flags); -} diff --git a/builtin-log.c b/builtin-log.c deleted file mode 100644 index e0d5caa61..000000000 --- a/builtin-log.c +++ /dev/null @@ -1,1352 +0,0 @@ -/* - * Builtin "git log" and related commands (show, whatchanged) - * - * (C) Copyright 2006 Linus Torvalds - * 2006 Junio Hamano - */ -#include "cache.h" -#include "color.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "log-tree.h" -#include "builtin.h" -#include "tag.h" -#include "reflog-walk.h" -#include "patch-ids.h" -#include "run-command.h" -#include "shortlog.h" -#include "remote.h" -#include "string-list.h" -#include "parse-options.h" - -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode = NULL; - -static int default_show_root = 1; -static const char *fmt_patch_subject_prefix = "PATCH"; -static const char *fmt_pretty; - -static const char * const builtin_log_usage = - "git log [] [..] [[--] ...]\n" - " or: git show [options] ..."; - -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; - if (fmt_pretty) - get_commit_format(fmt_pretty, rev); - rev->verbose_header = 1; - DIFF_OPT_SET(&rev->diffopt, RECURSIVE); - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); - - if (default_date_mode) - rev->date_mode = parse_date_format(default_date_mode); - - /* - * Check for -h before setup_revisions(), or "git log -h" will - * fail when run without a git directory. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_log_usage); - argc = setup_revisions(argc, argv, rev, "HEAD"); - - if (!rev->show_notes_given && !rev->pretty_given) - rev->show_notes = 1; - - if (rev->diffopt.pickaxe || rev->diffopt.filter) - rev->always_show_header = 0; - if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) { - rev->always_show_header = 0; - if (rev->diffopt.nr_paths != 1) - usage("git logs can only follow renames on one pathname at a time"); - } - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--decorate")) { - 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 - die("invalid --decorate option: %s", arg); - } else if (!strcmp(arg, "--source")) { - rev->show_source = 1; - } else if (!strcmp(arg, "-h")) { - usage(builtin_log_usage); - } else - die("unrecognized argument: %s", arg); - } - if (decoration_style) { - rev->show_decorations = 1; - load_ref_decorations(decoration_style); - } -} - -/* - * This gives a rough estimate for how many commits we - * will print out in the list. - */ -static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) -{ - int n = 0; - - while (list) { - struct commit *commit = list->item; - unsigned int flags = commit->object.flags; - list = list->next; - if (!(flags & (TREESAME | UNINTERESTING))) - n++; - } - return n; -} - -static void show_early_header(struct rev_info *rev, const char *stage, int nr) -{ - if (rev->shown_one) { - rev->shown_one = 0; - if (rev->commit_format != CMIT_FMT_ONELINE) - putchar(rev->diffopt.line_termination); - } - printf("Final output: %d %s\n", nr, stage); -} - -static struct itimerval early_output_timer; - -static void log_show_early(struct rev_info *revs, struct commit_list *list) -{ - int i = revs->early_output; - int show_header = 1; - - sort_in_topological_order(&list, revs->lifo); - while (list && i) { - struct commit *commit = list->item; - switch (simplify_commit(revs, commit)) { - case commit_show: - if (show_header) { - int n = estimate_commit_count(revs, list); - show_early_header(revs, "incomplete", n); - show_header = 0; - } - log_tree_commit(revs, commit); - i--; - break; - case commit_ignore: - break; - case commit_error: - return; - } - list = list->next; - } - - /* Did we already get enough commits for the early output? */ - if (!i) - return; - - /* - * ..if no, then repeat it twice a second until we - * do. - * - * NOTE! We don't use "it_interval", because if the - * reader isn't listening, we want our output to be - * throttled by the writing, and not have the timer - * trigger every second even if we're blocked on a - * reader! - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 500000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void early_output(int signal) -{ - show_early_output = log_show_early; -} - -static void setup_early_output(struct rev_info *rev) -{ - struct sigaction sa; - - /* - * Set up the signal handler, minimally intrusively: - * we only set a single volatile integer word (not - * using sigatomic_t - trying to avoid unnecessary - * system dependencies and headers), and using - * SA_RESTART. - */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = early_output; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - /* - * If we can get the whole output in less than a - * tenth of a second, don't even bother doing the - * early-output thing.. - * - * This is a one-time-only trigger. - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 100000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void finish_early_output(struct rev_info *rev) -{ - int n = estimate_commit_count(rev, rev->commits); - signal(SIGALRM, SIG_IGN); - show_early_header(rev, "done", n); -} - -static int cmd_log_walk(struct rev_info *rev) -{ - struct commit *commit; - - if (rev->early_output) - setup_early_output(rev); - - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - - if (rev->early_output) - finish_early_output(rev); - - /* - * For --check and --exit-code, the exit code is based on CHECK_FAILED - * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to - * retain that state information if replacing rev->diffopt in this loop - */ - while ((commit = get_revision(rev)) != NULL) { - log_tree_commit(rev, commit); - if (!rev->reflog_info) { - /* we allow cycles in reflog ancestry */ - free(commit->buffer); - commit->buffer = NULL; - } - free_commit_list(commit->parents); - commit->parents = NULL; - } - if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && - DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { - return 02; - } - return diff_result_code(&rev->diffopt, 0); -} - -static int git_log_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "format.pretty")) - return git_config_string(&fmt_pretty, var, value); - if (!strcmp(var, "format.subjectprefix")) - 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.showroot")) { - default_show_root = git_config_bool(var, value); - return 0; - } - return git_diff_ui_config(var, value, cb); -} - -int cmd_whatchanged(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.diff = 1; - rev.simplify_history = 0; - cmd_log_init(argc, argv, prefix, &rev); - if (!rev.diffopt.output_format) - rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_walk(&rev); -} - -static void show_tagger(char *buf, int len, struct rev_info *rev) -{ - struct strbuf out = STRBUF_INIT; - - pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); - printf("%s", out.buf); - strbuf_release(&out); -} - -static int show_object(const unsigned char *sha1, int show_tag_object, - struct rev_info *rev) -{ - unsigned long size; - enum object_type type; - char *buf = read_sha1_file(sha1, &type, &size); - int offset = 0; - - if (!buf) - return error("Could not read object %s", sha1_to_hex(sha1)); - - if (show_tag_object) - while (offset < size && buf[offset] != '\n') { - int new_offset = offset + 1; - while (new_offset < size && buf[new_offset++] != '\n') - ; /* do nothing */ - if (!prefixcmp(buf + offset, "tagger ")) - show_tagger(buf + offset + 7, - new_offset - offset - 7, rev); - offset = new_offset; - } - - if (offset < size) - fwrite(buf + offset, size - offset, 1, stdout); - free(buf); - return 0; -} - -static int show_tree_object(const unsigned char *sha1, - const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); - return 0; -} - -int cmd_show(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - struct object_array_entry *objects; - int i, count, ret = 0; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.diff = 1; - rev.combine_merges = 1; - rev.dense_combined_merges = 1; - rev.always_show_header = 1; - rev.ignore_merges = 0; - rev.no_walk = 1; - cmd_log_init(argc, argv, prefix, &rev); - - count = rev.pending.nr; - objects = rev.pending.objects; - for (i = 0; i < count && !ret; i++) { - struct object *o = objects[i].item; - const char *name = objects[i].name; - switch (o->type) { - case OBJ_BLOB: - ret = show_object(o->sha1, 0, NULL); - break; - case OBJ_TAG: { - struct tag *t = (struct tag *)o; - - if (rev.shown_one) - putchar('\n'); - printf("%stag %s%s\n", - diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), - t->tag, - diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - ret = show_object(o->sha1, 1, &rev); - rev.shown_one = 1; - if (ret) - break; - o = parse_object(t->tagged->sha1); - if (!o) - ret = error("Could not read object %s", - sha1_to_hex(t->tagged->sha1)); - objects[i].item = o; - i--; - break; - } - case OBJ_TREE: - if (rev.shown_one) - putchar('\n'); - printf("%stree %s%s\n\n", - diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), - name, - diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, NULL, - show_tree_object, NULL); - rev.shown_one = 1; - break; - case OBJ_COMMIT: - rev.pending.nr = rev.pending.alloc = 0; - rev.pending.objects = NULL; - add_object_array(o, name, &rev.pending); - ret = cmd_log_walk(&rev); - break; - default: - ret = error("Unknown type: %d", o->type); - } - } - free(objects); - return ret; -} - -/* - * This is equivalent to "git log -g --abbrev-commit --pretty=oneline" - */ -int cmd_log_reflog(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - init_reflog_walk(&rev.reflog_info); - rev.abbrev_commit = 1; - rev.verbose_header = 1; - cmd_log_init(argc, argv, prefix, &rev); - - /* - * This means that we override whatever commit format the user gave - * on the cmd line. Sad, but cmd_log_init() currently doesn't - * allow us to set a different default. - */ - rev.commit_format = CMIT_FMT_ONELINE; - rev.use_terminator = 1; - rev.always_show_header = 1; - - /* - * We get called through "git reflog", so unlike the other log - * routines, we need to set up our pager manually.. - */ - setup_pager(); - - return cmd_log_walk(&rev); -} - -int cmd_log(int argc, const char **argv, const char *prefix) -{ - struct rev_info rev; - - git_config(git_log_config, NULL); - - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - init_revisions(&rev, prefix); - rev.always_show_header = 1; - cmd_log_init(argc, argv, prefix, &rev); - return cmd_log_walk(&rev); -} - -/* format-patch */ - -static const char *fmt_patch_suffix = ".patch"; -static int numbered = 0; -static int auto_number = 1; - -static char *default_attach = NULL; - -static char **extra_hdr; -static int extra_hdr_nr; -static int extra_hdr_alloc; - -static char **extra_to; -static int extra_to_nr; -static int extra_to_alloc; - -static char **extra_cc; -static int extra_cc_nr; -static int extra_cc_alloc; - -static void add_header(const char *value) -{ - int len = strlen(value); - while (len && value[len - 1] == '\n') - len--; - if (!strncasecmp(value, "to: ", 4)) { - ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc); - extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4); - return; - } - if (!strncasecmp(value, "cc: ", 4)) { - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4); - return; - } - ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc); - extra_hdr[extra_hdr_nr++] = xstrndup(value, len); -} - -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread = 0; -static int do_signoff = 0; - -static int git_format_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "format.headers")) { - if (!value) - die("format.headers without value"); - add_header(value); - return 0; - } - if (!strcmp(var, "format.suffix")) - return git_config_string(&fmt_patch_suffix, var, value); - if (!strcmp(var, "format.cc")) { - if (!value) - return config_error_nonbool(var); - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(value); - return 0; - } - if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { - return 0; - } - if (!strcmp(var, "format.numbered")) { - if (value && !strcasecmp(value, "auto")) { - auto_number = 1; - return 0; - } - numbered = git_config_bool(var, value); - auto_number = auto_number && numbered; - return 0; - } - if (!strcmp(var, "format.attach")) { - if (value && *value) - default_attach = xstrdup(value); - else - default_attach = xstrdup(git_version_string); - return 0; - } - if (!strcmp(var, "format.thread")) { - if (value && !strcasecmp(value, "deep")) { - thread = THREAD_DEEP; - return 0; - } - if (value && !strcasecmp(value, "shallow")) { - thread = THREAD_SHALLOW; - return 0; - } - thread = git_config_bool(var, value) && THREAD_SHALLOW; - return 0; - } - if (!strcmp(var, "format.signoff")) { - do_signoff = git_config_bool(var, value); - return 0; - } - - return git_log_config(var, value, cb); -} - -static FILE *realstdout = NULL; -static const char *output_directory = NULL; -static int outdir_offset; - -static int reopen_stdout(struct commit *commit, struct rev_info *rev) -{ - struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(fmt_patch_suffix) + 1; - - if (output_directory) { - strbuf_addstr(&filename, output_directory); - if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) - return error("name of output directory is too long"); - if (filename.buf[filename.len - 1] != '/') - strbuf_addch(&filename, '/'); - } - - get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); - - if (!DIFF_OPT_TST(&rev->diffopt, QUICK)) - fprintf(realstdout, "%s\n", filename.buf + outdir_offset); - - if (freopen(filename.buf, "w", stdout) == NULL) - return error("Cannot open patch file %s", filename.buf); - - strbuf_release(&filename); - return 0; -} - -static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix) -{ - struct rev_info check_rev; - struct commit *commit; - struct object *o1, *o2; - unsigned flags1, flags2; - - if (rev->pending.nr != 2) - die("Need exactly one range."); - - o1 = rev->pending.objects[0].item; - flags1 = o1->flags; - o2 = rev->pending.objects[1].item; - flags2 = o2->flags; - - if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die("Not a range."); - - init_patch_ids(ids); - - /* given a range a..b get all patch ids for b..a */ - init_revisions(&check_rev, prefix); - o1->flags ^= UNINTERESTING; - o2->flags ^= UNINTERESTING; - add_pending_object(&check_rev, o1, "o1"); - add_pending_object(&check_rev, o2, "o2"); - if (prepare_revision_walk(&check_rev)) - die("revision walk setup failed"); - - while ((commit = get_revision(&check_rev)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - add_commit_patch_id(commit, ids); - } - - /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); - o1->flags = flags1; - o2->flags = flags2; -} - -static void gen_message_id(struct rev_info *info, char *base) -{ - const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME); - const char *email_start = strrchr(committer, '<'); - const char *email_end = strrchr(committer, '>'); - struct strbuf buf = STRBUF_INIT; - if (!email_start || !email_end || email_start > email_end - 1) - die("Could not extract email from committer identity."); - strbuf_addf(&buf, "%s.%lu.git.%.*s", base, - (unsigned long) time(NULL), - (int)(email_end - email_start - 1), email_start + 1); - info->message_id = strbuf_detach(&buf, NULL); -} - -static void make_cover_letter(struct rev_info *rev, int use_stdout, - int numbered, int numbered_files, - struct commit *origin, - int nr, struct commit **list, struct commit *head) -{ - const char *committer; - const char *subject_start = NULL; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; - const char *extra_headers = rev->extra_headers; - struct shortlog log; - struct strbuf sb = STRBUF_INIT; - int i; - const char *encoding = "UTF-8"; - struct diff_options opts; - int need_8bit_cte = 0; - struct commit *commit = NULL; - - if (rev->commit_format != CMIT_FMT_EMAIL) - die("Cover letter needs email format"); - - committer = git_committer_info(0); - - if (!numbered_files) { - /* - * We fake a commit for the cover letter so we get the filename - * desired. - */ - commit = xcalloc(1, sizeof(*commit)); - commit->buffer = xmalloc(400); - snprintf(commit->buffer, 400, - "tree 0000000000000000000000000000000000000000\n" - "parent %s\n" - "author %s\n" - "committer %s\n\n" - "cover letter\n", - sha1_to_hex(head->object.sha1), committer, committer); - } - - if (!use_stdout && reopen_stdout(commit, rev)) - return; - - if (commit) { - - free(commit->buffer); - free(commit); - } - - log_write_email_headers(rev, head, &subject_start, &extra_headers, - &need_8bit_cte); - - for (i = 0; !need_8bit_cte && i < nr; i++) - if (has_non_ascii(list[i]->buffer)) - need_8bit_cte = 1; - - msg = body; - pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, - encoding); - pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers, - encoding, need_8bit_cte); - pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0); - printf("%s\n", sb.buf); - - strbuf_release(&sb); - - shortlog_init(&log); - log.wrap_lines = 1; - log.wrap = 72; - log.in1 = 2; - log.in2 = 4; - for (i = 0; i < nr; i++) - shortlog_add_commit(&log, list[i]); - - shortlog_output(&log); - - /* - * We can only do diffstat with a unique reference point - */ - if (!origin) - return; - - memcpy(&opts, &rev->diffopt, sizeof(opts)); - opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; - - diff_setup_done(&opts); - - diff_tree_sha1(origin->tree->object.sha1, - head->tree->object.sha1, - "", &opts); - diffcore_std(&opts); - diff_flush(&opts); - - printf("\n"); -} - -static const char *clean_message_id(const char *msg_id) -{ - char ch; - const char *a, *z, *m; - - m = msg_id; - while ((ch = *m) && (isspace(ch) || (ch == '<'))) - m++; - a = m; - z = NULL; - while ((ch = *m)) { - if (!isspace(ch) && (ch != '>')) - z = m; - m++; - } - if (!z) - die("insane in-reply-to: %s", msg_id); - if (++z == m) - return a; - return xmemdupz(a, z - a); -} - -static const char *set_outdir(const char *prefix, const char *output_directory) -{ - if (output_directory && is_absolute_path(output_directory)) - return output_directory; - - if (!prefix || !*prefix) { - if (output_directory) - return output_directory; - /* The user did not explicitly ask for "./" */ - outdir_offset = 2; - return "./"; - } - - outdir_offset = strlen(prefix); - if (!output_directory) - return prefix; - - return xstrdup(prefix_filename(prefix, outdir_offset, - output_directory)); -} - -static const char * const builtin_format_patch_usage[] = { - "git format-patch [options] [ | ]", - NULL -}; - -static int keep_subject = 0; - -static int keep_callback(const struct option *opt, const char *arg, int unset) -{ - ((struct rev_info *)opt->value)->total = -1; - keep_subject = 1; - return 0; -} - -static int subject_prefix = 0; - -static int subject_prefix_callback(const struct option *opt, const char *arg, - int unset) -{ - subject_prefix = 1; - ((struct rev_info *)opt->value)->subject_prefix = arg; - return 0; -} - -static int numbered_cmdline_opt = 0; - -static int numbered_callback(const struct option *opt, const char *arg, - int unset) -{ - *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; - if (unset) - auto_number = 0; - return 0; -} - -static int no_numbered_callback(const struct option *opt, const char *arg, - int unset) -{ - return numbered_callback(opt, arg, 1); -} - -static int output_directory_callback(const struct option *opt, const char *arg, - int unset) -{ - const char **dir = (const char **)opt->value; - if (*dir) - die("Two output directories?"); - *dir = arg; - return 0; -} - -static int thread_callback(const struct option *opt, const char *arg, int unset) -{ - int *thread = (int *)opt->value; - if (unset) - *thread = 0; - else if (!arg || !strcmp(arg, "shallow")) - *thread = THREAD_SHALLOW; - else if (!strcmp(arg, "deep")) - *thread = THREAD_DEEP; - else - return 1; - return 0; -} - -static int attach_callback(const struct option *opt, const char *arg, int unset) -{ - struct rev_info *rev = (struct rev_info *)opt->value; - if (unset) - rev->mime_boundary = NULL; - else if (arg) - rev->mime_boundary = arg; - else - rev->mime_boundary = git_version_string; - rev->no_inline = unset ? 0 : 1; - return 0; -} - -static int inline_callback(const struct option *opt, const char *arg, int unset) -{ - struct rev_info *rev = (struct rev_info *)opt->value; - if (unset) - rev->mime_boundary = NULL; - else if (arg) - rev->mime_boundary = arg; - else - rev->mime_boundary = git_version_string; - rev->no_inline = 0; - return 0; -} - -static int header_callback(const struct option *opt, const char *arg, int unset) -{ - add_header(arg); - return 0; -} - -static int cc_callback(const struct option *opt, const char *arg, int unset) -{ - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(arg); - return 0; -} - -int cmd_format_patch(int argc, const char **argv, const char *prefix) -{ - struct commit *commit; - struct commit **list = NULL; - struct rev_info rev; - int nr = 0, total, i; - int use_stdout = 0; - int start_number = -1; - int numbered_files = 0; /* _just_ numbers */ - int ignore_if_in_upstream = 0; - int cover_letter = 0; - int boundary_count = 0; - int no_binary_diff = 0; - struct commit *origin = NULL, *head = NULL; - const char *in_reply_to = NULL; - struct patch_ids ids; - char *add_signoff = NULL; - struct strbuf buf = STRBUF_INIT; - int use_patch_format = 0; - const struct option builtin_format_patch_options[] = { - { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, - "use [PATCH n/m] even with a single patch", - PARSE_OPT_NOARG, numbered_callback }, - { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, - "use [PATCH] even with multiple patches", - PARSE_OPT_NOARG, no_numbered_callback }, - OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"), - OPT_BOOLEAN(0, "stdout", &use_stdout, - "print patches to standard out"), - OPT_BOOLEAN(0, "cover-letter", &cover_letter, - "generate a cover letter"), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, - "use simple number sequence for output file names"), - OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx", - "use instead of '.patch'"), - OPT_INTEGER(0, "start-number", &start_number, - "start numbering patches at instead of 1"), - { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix", - "Use [] instead of [PATCH]", - PARSE_OPT_NONEG, subject_prefix_callback }, - { OPTION_CALLBACK, 'o', "output-directory", &output_directory, - "dir", "store resulting files in ", - PARSE_OPT_NONEG, output_directory_callback }, - { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, - "don't strip/add [PATCH]", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, - OPT_BOOLEAN(0, "no-binary", &no_binary_diff, - "don't output binary diffs"), - OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, - "don't include a patch matching a commit upstream"), - { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL, - "show patch format instead of default (patch + stat)", - PARSE_OPT_NONEG | PARSE_OPT_NOARG }, - OPT_GROUP("Messaging"), - { OPTION_CALLBACK, 0, "add-header", NULL, "header", - "add email header", PARSE_OPT_NONEG, - header_callback }, - { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header", - PARSE_OPT_NONEG, cc_callback }, - OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id", - "make first mail a reply to "), - { OPTION_CALLBACK, 0, "attach", &rev, "boundary", - "attach the patch", PARSE_OPT_OPTARG, - attach_callback }, - { OPTION_CALLBACK, 0, "inline", &rev, "boundary", - "inline the patch", - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - inline_callback }, - { OPTION_CALLBACK, 0, "thread", &thread, "style", - "enable message threading, styles: shallow, deep", - PARSE_OPT_OPTARG, thread_callback }, - OPT_END() - }; - - git_config(git_format_config, NULL); - init_revisions(&rev, prefix); - rev.commit_format = CMIT_FMT_EMAIL; - rev.verbose_header = 1; - rev.diff = 1; - rev.combine_merges = 0; - rev.ignore_merges = 1; - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); - - rev.subject_prefix = fmt_patch_subject_prefix; - - if (default_attach) { - rev.mime_boundary = default_attach; - rev.no_inline = 1; - } - - /* - * Parse the arguments before setup_revisions(), or something - * like "git format-patch -o a123 HEAD^.." may fail; a123 is - * possibly a valid SHA1. - */ - argc = parse_options(argc, argv, prefix, builtin_format_patch_options, - builtin_format_patch_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | - PARSE_OPT_KEEP_DASHDASH); - - if (do_signoff) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); - endpos = strchr(committer, '>'); - if (!endpos) - die("bogus committer info %s", committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); - } - - for (i = 0; i < extra_hdr_nr; i++) { - strbuf_addstr(&buf, extra_hdr[i]); - strbuf_addch(&buf, '\n'); - } - - if (extra_to_nr) - strbuf_addstr(&buf, "To: "); - for (i = 0; i < extra_to_nr; i++) { - if (i) - strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_to[i]); - if (i + 1 < extra_to_nr) - strbuf_addch(&buf, ','); - strbuf_addch(&buf, '\n'); - } - - if (extra_cc_nr) - strbuf_addstr(&buf, "Cc: "); - for (i = 0; i < extra_cc_nr; i++) { - if (i) - strbuf_addstr(&buf, " "); - strbuf_addstr(&buf, extra_cc[i]); - if (i + 1 < extra_cc_nr) - strbuf_addch(&buf, ','); - strbuf_addch(&buf, '\n'); - } - - rev.extra_headers = strbuf_detach(&buf, NULL); - - if (start_number < 0) - start_number = 1; - - /* - * If numbered is set solely due to format.numbered in config, - * and it would conflict with --keep-subject (-k) from the - * command line, reset "numbered". - */ - if (numbered && keep_subject && !numbered_cmdline_opt) - numbered = 0; - - if (numbered && keep_subject) - die ("-n and -k are mutually exclusive."); - if (keep_subject && subject_prefix) - die ("--subject-prefix and -k are mutually exclusive."); - - argc = setup_revisions(argc, argv, &rev, "HEAD"); - if (argc > 1) - die ("unrecognized argument: %s", argv[1]); - - if (rev.diffopt.output_format & DIFF_FORMAT_NAME) - die("--name-only does not make sense"); - if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS) - die("--name-status does not make sense"); - if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) - die("--check does not make sense"); - - if (!use_patch_format && - (!rev.diffopt.output_format || - rev.diffopt.output_format == DIFF_FORMAT_PATCH)) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; - - /* Always generate a patch */ - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - - if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) - DIFF_OPT_SET(&rev.diffopt, BINARY); - - if (!use_stdout) - output_directory = set_outdir(prefix, output_directory); - - if (output_directory) { - if (use_stdout) - die("standard output, or directory, which one?"); - if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno("Could not create directory '%s'", - output_directory); - } - - if (rev.pending.nr == 1) { - if (rev.max_count < 0 && !rev.show_root_diff) { - /* - * This is traditional behaviour of "git format-patch - * origin" that prepares what the origin side still - * does not have. - */ - rev.pending.objects[0].item->flags |= UNINTERESTING; - add_head_to_pending(&rev); - } - /* - * Otherwise, it is "format-patch -22 HEAD", and/or - * "format-patch --root HEAD". The user wants - * get_revision() to do the usual traversal. - */ - } - - /* - * We cannot move this anywhere earlier because we do want to - * know if --root was given explicitly from the command line. - */ - rev.show_root_diff = 1; - - if (cover_letter) { - /* remember the range */ - int i; - for (i = 0; i < rev.pending.nr; i++) { - struct object *o = rev.pending.objects[i].item; - if (!(o->flags & UNINTERESTING)) - head = (struct commit *)o; - } - /* We can't generate a cover letter without any patches */ - if (!head) - return 0; - } - - if (ignore_if_in_upstream) - get_patch_ids(&rev, &ids, prefix); - - if (!use_stdout) - realstdout = xfdopen(xdup(1), "w"); - - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); - rev.boundary = 1; - while ((commit = get_revision(&rev)) != NULL) { - if (commit->object.flags & BOUNDARY) { - boundary_count++; - origin = (boundary_count == 1) ? commit : NULL; - continue; - } - - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - if (ignore_if_in_upstream && - has_commit_patch_id(commit, &ids)) - continue; - - nr++; - list = xrealloc(list, nr * sizeof(list[0])); - list[nr - 1] = commit; - } - total = nr; - if (!keep_subject && auto_number && total > 1) - numbered = 1; - if (numbered) - rev.total = total + start_number - 1; - if (in_reply_to || thread || cover_letter) - rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); - if (in_reply_to) { - const char *msgid = clean_message_id(in_reply_to); - string_list_append(msgid, rev.ref_message_ids); - } - rev.numbered_files = numbered_files; - rev.patch_suffix = fmt_patch_suffix; - if (cover_letter) { - if (thread) - gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head); - total++; - start_number--; - } - rev.add_signoff = add_signoff; - while (0 <= --nr) { - int shown; - commit = list[nr]; - rev.nr = total - nr + (start_number - 1); - /* Make the second and subsequent mails replies to the first */ - if (thread) { - /* Have we already had a message ID? */ - if (rev.message_id) { - /* - * For deep threading: make every mail - * a reply to the previous one, no - * matter what other options are set. - * - * For shallow threading: - * - * Without --cover-letter and - * --in-reply-to, make every mail a - * reply to the one before. - * - * With --in-reply-to but no - * --cover-letter, make every mail a - * reply to the . - * - * With --cover-letter, make every - * mail but the cover letter a reply - * to the cover letter. The cover - * letter is a reply to the - * --in-reply-to, if specified. - */ - if (thread == THREAD_SHALLOW - && rev.ref_message_ids->nr > 0 - && (!cover_letter || rev.nr > 1)) - free(rev.message_id); - else - string_list_append(rev.message_id, - rev.ref_message_ids); - } - gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); - } - - if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, - &rev)) - die("Failed to create output files"); - shown = log_tree_commit(&rev, commit); - free(commit->buffer); - commit->buffer = NULL; - - /* We put one extra blank line between formatted - * patches and this flag is used by log-tree code - * to see if it needs to emit a LF before showing - * the log; when using one file per patch, we do - * not want the extra blank line. - */ - if (!use_stdout) - rev.shown_one = 0; - if (shown) { - if (rev.mime_boundary) - printf("\n--%s%s--\n\n\n", - mime_boundary_leader, - rev.mime_boundary); - else - printf("-- \n%s\n\n", git_version_string); - } - if (!use_stdout) - fclose(stdout); - } - free(list); - if (ignore_if_in_upstream) - free_patch_ids(&ids); - return 0; -} - -static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) -{ - unsigned char sha1[20]; - if (get_sha1(arg, sha1) == 0) { - struct commit *commit = lookup_commit_reference(sha1); - if (commit) { - commit->object.flags |= flags; - add_pending_object(revs, &commit->object, arg); - return 0; - } - } - return -1; -} - -static const char cherry_usage[] = -"git cherry [-v] [ [ []]]"; -int cmd_cherry(int argc, const char **argv, const char *prefix) -{ - struct rev_info revs; - struct patch_ids ids; - struct commit *commit; - struct commit_list *list = NULL; - struct branch *current_branch; - const char *upstream; - const char *head = "HEAD"; - const char *limit = NULL; - int verbose = 0; - - if (argc > 1 && !strcmp(argv[1], "-v")) { - verbose = 1; - argc--; - argv++; - } - - if (argc > 1 && !strcmp(argv[1], "-h")) - usage(cherry_usage); - - switch (argc) { - case 4: - limit = argv[3]; - /* FALLTHROUGH */ - case 3: - head = argv[2]; - /* FALLTHROUGH */ - case 2: - upstream = argv[1]; - break; - default: - current_branch = branch_get(NULL); - if (!current_branch || !current_branch->merge - || !current_branch->merge[0] - || !current_branch->merge[0]->dst) { - fprintf(stderr, "Could not find a tracked" - " remote branch, please" - " specify manually.\n"); - usage(cherry_usage); - } - - upstream = current_branch->merge[0]->dst; - } - - init_revisions(&revs, prefix); - revs.diff = 1; - revs.combine_merges = 0; - revs.ignore_merges = 1; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); - - if (add_pending_commit(head, &revs, 0)) - die("Unknown commit %s", head); - if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die("Unknown commit %s", upstream); - - /* Don't say anything if head and upstream are the same. */ - if (revs.pending.nr == 2) { - struct object_array_entry *o = revs.pending.objects; - if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0) - return 0; - } - - get_patch_ids(&revs, &ids, prefix); - - if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die("Unknown commit %s", limit); - - /* reverse the list of commits */ - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - while ((commit = get_revision(&revs)) != NULL) { - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - - commit_list_insert(commit, &list); - } - - while (list) { - char sign = '+'; - - commit = list->item; - if (has_commit_patch_id(commit, &ids)) - sign = '-'; - - if (verbose) { - struct strbuf buf = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - pretty_print_commit(CMIT_FMT_ONELINE, commit, - &buf, &ctx); - printf("%c %s %s\n", sign, - sha1_to_hex(commit->object.sha1), buf.buf); - strbuf_release(&buf); - } - else { - printf("%c %s\n", sign, - sha1_to_hex(commit->object.sha1)); - } - - list = list->next; - } - - free_patch_ids(&ids); - return 0; -} diff --git a/builtin-ls-files.c b/builtin-ls-files.c deleted file mode 100644 index b06506139..000000000 --- a/builtin-ls-files.c +++ /dev/null @@ -1,606 +0,0 @@ -/* - * This merges the file listing in the directory cache index - * with the actual working directory list, and shows different - * combinations of the two. - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "quote.h" -#include "dir.h" -#include "builtin.h" -#include "tree.h" -#include "parse-options.h" -#include "resolve-undo.h" -#include "string-list.h" - -static int abbrev; -static int show_deleted; -static int show_cached; -static int show_others; -static int show_stage; -static int show_unmerged; -static int show_resolve_undo; -static int show_modified; -static int show_killed; -static int show_valid_bit; -static int line_terminator = '\n'; - -static int prefix_len; -static int prefix_offset; -static const char **pathspec; -static int error_unmatch; -static char *ps_matched; -static const char *with_tree; -static int exc_given; - -static const char *tag_cached = ""; -static const char *tag_unmerged = ""; -static const char *tag_removed = ""; -static const char *tag_other = ""; -static const char *tag_killed = ""; -static const char *tag_modified = ""; -static const char *tag_skip_worktree = ""; -static const char *tag_resolve_undo = ""; - -static void show_dir_entry(const char *tag, struct dir_entry *ent) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ent->len) - die("git ls-files: internal error - directory entry not superset of prefix"); - - if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched)) - return; - - fputs(tag, stdout); - write_name_quoted(ent->name + offset, stdout, line_terminator); -} - -static void show_other_files(struct dir_struct *dir) -{ - int i; - - for (i = 0; i < dir->nr; i++) { - struct dir_entry *ent = dir->entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - show_dir_entry(tag_other, ent); - } -} - -static void show_killed_files(struct dir_struct *dir) -{ - int i; - for (i = 0; i < dir->nr; i++) { - struct dir_entry *ent = dir->entries[i]; - char *cp, *sp; - int pos, len, killed = 0; - - for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) { - sp = strchr(cp, '/'); - if (!sp) { - /* If ent->name is prefix of an entry in the - * cache, it will be killed. - */ - pos = cache_name_pos(ent->name, ent->len); - if (0 <= pos) - die("bug in show-killed-files"); - pos = -pos - 1; - while (pos < active_nr && - ce_stage(active_cache[pos])) - pos++; /* skip unmerged */ - if (active_nr <= pos) - break; - /* pos points at a name immediately after - * ent->name in the cache. Does it expect - * ent->name to be a directory? - */ - len = ce_namelen(active_cache[pos]); - if ((ent->len < len) && - !strncmp(active_cache[pos]->name, - ent->name, ent->len) && - active_cache[pos]->name[ent->len] == '/') - killed = 1; - break; - } - if (0 <= cache_name_pos(ent->name, sp - ent->name)) { - /* If any of the leading directories in - * ent->name is registered in the cache, - * ent->name will be killed. - */ - killed = 1; - break; - } - } - if (killed) - show_dir_entry(tag_killed, dir->entries[i]); - } -} - -static void show_ce_entry(const char *tag, struct cache_entry *ce) -{ - int len = prefix_len; - int offset = prefix_offset; - - if (len >= ce_namelen(ce)) - die("git ls-files: internal error - cache entry not superset of prefix"); - - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched)) - return; - - if (tag && *tag && show_valid_bit && - (ce->ce_flags & CE_VALID)) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; - } - tag = alttag; - } - - if (!show_stage) { - fputs(tag, stdout); - } else { - printf("%s%06o %s %d\t", - tag, - ce->ce_mode, - abbrev ? find_unique_abbrev(ce->sha1,abbrev) - : sha1_to_hex(ce->sha1), - ce_stage(ce)); - } - write_name_quoted(ce->name + offset, stdout, line_terminator); -} - -static int show_one_ru(struct string_list_item *item, void *cbdata) -{ - int offset = prefix_offset; - const char *path = item->string; - struct resolve_undo_info *ui = item->util; - int i, len; - - len = strlen(path); - if (len < prefix_len) - return 0; /* outside of the prefix */ - if (!match_pathspec(pathspec, path, len, prefix_len, ps_matched)) - return 0; /* uninterested */ - for (i = 0; i < 3; i++) { - if (!ui->mode[i]) - continue; - printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - abbrev - ? find_unique_abbrev(ui->sha1[i], abbrev) - : sha1_to_hex(ui->sha1[i]), - i + 1); - write_name_quoted(path + offset, stdout, line_terminator); - } - return 0; -} - -static void show_ru_info(const char *prefix) -{ - if (!the_index.resolve_undo) - return; - for_each_string_list(show_one_ru, the_index.resolve_undo, NULL); -} - -static void show_files(struct dir_struct *dir, const char *prefix) -{ - int i; - - /* For cached/deleted files we don't need to even do the readdir */ - if (show_others || show_killed) { - fill_directory(dir, pathspec); - if (show_others) - show_other_files(dir); - if (show_killed) - show_killed_files(dir); - } - if (show_cached | show_stage) { - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : - (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); - } - } - if (show_deleted | show_modified) { - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - struct stat st; - int err; - int dtype = ce_to_dtype(ce); - if (dir->flags & DIR_SHOW_IGNORED && - !excluded(dir, ce->name, &dtype)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - if (ce_skip_worktree(ce)) - continue; - err = lstat(ce->name, &st); - if (show_deleted && err) - show_ce_entry(tag_removed, ce); - if (show_modified && ce_modified(ce, &st, 0)) - show_ce_entry(tag_modified, ce); - } - } -} - -/* - * Prune the index to only contain stuff starting with "prefix" - */ -static void prune_cache(const char *prefix) -{ - int pos = cache_name_pos(prefix, prefix_len); - unsigned int first, last; - - if (pos < 0) - pos = -pos-1; - memmove(active_cache, active_cache + pos, - (active_nr - pos) * sizeof(struct cache_entry *)); - active_nr -= pos; - first = 0; - last = active_nr; - while (last > first) { - int next = (last + first) >> 1; - struct cache_entry *ce = active_cache[next]; - if (!strncmp(ce->name, prefix, prefix_len)) { - first = next+1; - continue; - } - last = next; - } - active_nr = last; -} - -static const char *verify_pathspec(const char *prefix) -{ - const char **p, *n, *prev; - unsigned long max; - - prev = NULL; - max = PATH_MAX; - for (p = pathspec; (n = *p) != NULL; p++) { - int i, len = 0; - for (i = 0; i < max; i++) { - char c = n[i]; - if (prev && prev[i] != c) - break; - if (!c || c == '*' || c == '?') - break; - if (c == '/') - len = i+1; - } - prev = n; - if (len < max) { - max = len; - if (!max) - break; - } - } - - if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) - die("git ls-files: cannot generate relative filenames containing '..'"); - - prefix_len = max; - return max ? xmemdupz(prev, max) : NULL; -} - -static void strip_trailing_slash_from_submodules(void) -{ - const char **p; - - for (p = pathspec; *p != NULL; p++) { - int len = strlen(*p), pos; - - if (len < 1 || (*p)[len - 1] != '/') - continue; - pos = cache_name_pos(*p, len - 1); - if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode)) - *p = xstrndup(*p, len - 1); - } -} - -/* - * Read the tree specified with --with-tree option - * (typically, HEAD) into stage #1 and then - * squash them down to stage #0. This is used for - * --error-unmatch to list and check the path patterns - * that were given from the command line. We are not - * going to write this index out. - */ -void overlay_tree_on_cache(const char *tree_name, const char *prefix) -{ - struct tree *tree; - unsigned char sha1[20]; - const char **match; - struct cache_entry *last_stage0 = NULL; - int i; - - if (get_sha1(tree_name, sha1)) - die("tree-ish %s not found.", tree_name); - tree = parse_tree_indirect(sha1); - if (!tree) - die("bad tree-ish %s", tree_name); - - /* Hoist the unmerged entries up to stage #3 to make room */ - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= CE_STAGEMASK; - } - - if (prefix) { - static const char *(matchbuf[2]); - matchbuf[0] = prefix; - matchbuf[1] = NULL; - match = matchbuf; - } else - match = NULL; - if (read_tree(tree, 1, match)) - die("unable to read tree entries %s", tree_name); - - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - switch (ce_stage(ce)) { - case 0: - last_stage0 = ce; - /* fallthru */ - default: - continue; - case 1: - /* - * If there is stage #0 entry for this, we do not - * need to show it. We use CE_UPDATE bit to mark - * such an entry. - */ - if (last_stage0 && - !strcmp(last_stage0->name, ce->name)) - ce->ce_flags |= CE_UPDATE; - } - } -} - -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset) -{ - /* - * Make sure all pathspec matched; otherwise it is an error. - */ - int num, errors = 0; - for (num = 0; pathspec[num]; num++) { - int other, found_dup; - - if (ps_matched[num]) - continue; - /* - * The caller might have fed identical pathspec - * twice. Do not barf on such a mistake. - */ - for (found_dup = other = 0; - !found_dup && pathspec[other]; - other++) { - if (other == num || !ps_matched[other]) - continue; - if (!strcmp(pathspec[other], pathspec[num])) - /* - * Ok, we have a match already. - */ - found_dup = 1; - } - if (found_dup) - continue; - - error("pathspec '%s' did not match any file(s) known to git.", - pathspec[num] + prefix_offset); - errors++; - } - return errors; -} - -static const char * const ls_files_usage[] = { - "git ls-files [options] []*", - NULL -}; - -static int option_parse_z(const struct option *opt, - const char *arg, int unset) -{ - line_terminator = unset ? '\n' : '\0'; - - return 0; -} - -static int option_parse_exclude(const struct option *opt, - const char *arg, int unset) -{ - struct exclude_list *list = opt->value; - - exc_given = 1; - add_exclude(arg, "", 0, list); - - return 0; -} - -static int option_parse_exclude_from(const struct option *opt, - const char *arg, int unset) -{ - struct dir_struct *dir = opt->value; - - exc_given = 1; - add_excludes_from_file(dir, arg); - - return 0; -} - -static int option_parse_exclude_standard(const struct option *opt, - const char *arg, int unset) -{ - struct dir_struct *dir = opt->value; - - exc_given = 1; - setup_standard_excludes(dir); - - return 0; -} - -int cmd_ls_files(int argc, const char **argv, const char *prefix) -{ - int require_work_tree = 0, show_tag = 0; - struct dir_struct dir; - struct option builtin_ls_files_options[] = { - { OPTION_CALLBACK, 'z', NULL, NULL, NULL, - "paths are separated with NUL character", - PARSE_OPT_NOARG, option_parse_z }, - OPT_BOOLEAN('t', NULL, &show_tag, - "identify the file status with tags"), - OPT_BOOLEAN('v', NULL, &show_valid_bit, - "use lowercase letters for 'assume unchanged' files"), - OPT_BOOLEAN('c', "cached", &show_cached, - "show cached files in the output (default)"), - OPT_BOOLEAN('d', "deleted", &show_deleted, - "show deleted files in the output"), - OPT_BOOLEAN('m', "modified", &show_modified, - "show modified files in the output"), - OPT_BOOLEAN('o', "others", &show_others, - "show other files in the output"), - OPT_BIT('i', "ignored", &dir.flags, - "show ignored files in the output", - DIR_SHOW_IGNORED), - OPT_BOOLEAN('s', "stage", &show_stage, - "show staged contents' object name in the output"), - OPT_BOOLEAN('k', "killed", &show_killed, - "show files on the filesystem that need to be removed"), - OPT_BIT(0, "directory", &dir.flags, - "show 'other' directories' name only", - DIR_SHOW_OTHER_DIRECTORIES), - OPT_NEGBIT(0, "empty-directory", &dir.flags, - "don't show empty directories", - DIR_HIDE_EMPTY_DIRECTORIES), - OPT_BOOLEAN('u', "unmerged", &show_unmerged, - "show unmerged files in the output"), - OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, - "show resolve-undo information"), - { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", - "skip files matching pattern", - 0, option_parse_exclude }, - { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file", - "exclude patterns are read from ", - 0, option_parse_exclude_from }, - OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file", - "read additional per-directory exclude patterns in "), - { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, - "add the standard git exclusions", - PARSE_OPT_NOARG, option_parse_exclude_standard }, - { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, - "make the output relative to the project top directory", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, - OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, - "if any is not in the index, treat this as an error"), - OPT_STRING(0, "with-tree", &with_tree, "tree-ish", - "pretend that paths removed since are still present"), - OPT__ABBREV(&abbrev), - OPT_END() - }; - - memset(&dir, 0, sizeof(dir)); - if (prefix) - prefix_offset = strlen(prefix); - git_config(git_default_config, NULL); - - if (read_cache() < 0) - die("index file corrupt"); - - argc = parse_options(argc, argv, prefix, builtin_ls_files_options, - ls_files_usage, 0); - if (show_tag || show_valid_bit) { - tag_cached = "H "; - tag_unmerged = "M "; - tag_removed = "R "; - tag_modified = "C "; - tag_other = "? "; - tag_killed = "K "; - tag_skip_worktree = "S "; - tag_resolve_undo = "U "; - } - if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) - require_work_tree = 1; - if (show_unmerged) - /* - * There's no point in showing unmerged unless - * you also show the stage information. - */ - show_stage = 1; - if (dir.exclude_per_dir) - exc_given = 1; - - if (require_work_tree && !is_inside_work_tree()) - setup_work_tree(); - - pathspec = get_pathspec(prefix, argv); - - /* be nice with submodule paths ending in a slash */ - if (pathspec) - strip_trailing_slash_from_submodules(); - - /* Verify that the pathspec matches the prefix */ - if (pathspec) - prefix = verify_pathspec(prefix); - - /* Treat unmatching pathspec elements as errors */ - if (pathspec && error_unmatch) { - int num; - for (num = 0; pathspec[num]; num++) - ; - ps_matched = xcalloc(1, num); - } - - if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) - die("ls-files --ignored needs some exclude pattern"); - - /* With no flags, we default to showing the cached files */ - if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified | show_resolve_undo)) - show_cached = 1; - - if (prefix) - prune_cache(prefix); - if (with_tree) { - /* - * Basic sanity check; show-stages and show-unmerged - * would not make any sense with this option. - */ - if (show_stage || show_unmerged) - die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, prefix); - } - show_files(&dir, prefix); - if (show_resolve_undo) - show_ru_info(prefix); - - if (ps_matched) { - int bad; - bad = report_path_error(ps_matched, pathspec, prefix_offset); - if (bad) - fprintf(stderr, "Did you forget to 'git add'?\n"); - - return bad ? 1 : 0; - } - - return 0; -} diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c deleted file mode 100644 index 70f5622d9..000000000 --- a/builtin-ls-remote.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "transport.h" -#include "remote.h" - -static const char ls_remote_usage[] = -"git ls-remote [--heads] [--tags] [-u | --upload-pack ] ..."; - -/* - * Is there one among the list of patterns that match the tail part - * of the path? - */ -static int tail_match(const char **pattern, const char *path) -{ - const char *p; - char pathbuf[PATH_MAX]; - - if (!pattern) - return 1; /* no restriction */ - - if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf)) - return error("insanely long ref %.*s...", 20, path); - while ((p = *(pattern++)) != NULL) { - if (!fnmatch(p, pathbuf, 0)) - return 1; - } - return 0; -} - -int cmd_ls_remote(int argc, const char **argv, const char *prefix) -{ - int i; - const char *dest = NULL; - int nongit; - unsigned flags = 0; - const char *uploadpack = NULL; - const char **pattern = NULL; - - struct remote *remote; - struct transport *transport; - const struct ref *ref; - - setup_git_directory_gently(&nongit); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg == '-') { - if (!prefixcmp(arg, "--upload-pack=")) { - uploadpack = arg + 14; - continue; - } - if (!prefixcmp(arg, "--exec=")) { - uploadpack = arg + 7; - continue; - } - if (!strcmp("--tags", arg) || !strcmp("-t", arg)) { - flags |= REF_TAGS; - continue; - } - if (!strcmp("--heads", arg) || !strcmp("-h", arg)) { - flags |= REF_HEADS; - continue; - } - if (!strcmp("--refs", arg)) { - flags |= REF_NORMAL; - continue; - } - usage(ls_remote_usage); - } - dest = arg; - i++; - break; - } - - if (!dest) - usage(ls_remote_usage); - - if (argv[i]) { - int j; - pattern = xcalloc(sizeof(const char *), argc - i + 1); - for (j = i; j < argc; j++) { - int len = strlen(argv[j]); - char *p = xmalloc(len + 3); - sprintf(p, "*/%s", argv[j]); - pattern[j - i] = p; - } - } - remote = remote_get(dest); - if (!remote->url_nr) - die("remote %s has no configured URL", dest); - transport = transport_get(remote, NULL); - if (uploadpack != NULL) - transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); - - ref = transport_get_remote_refs(transport); - if (transport_disconnect(transport)) - return 1; - for ( ; ref; ref = ref->next) { - if (!check_ref_type(ref, flags)) - continue; - if (!tail_match(pattern, ref->name)) - continue; - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); - } - return 0; -} diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c deleted file mode 100644 index 4484185af..000000000 --- a/builtin-ls-tree.c +++ /dev/null @@ -1,176 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - */ -#include "cache.h" -#include "blob.h" -#include "tree.h" -#include "commit.h" -#include "quote.h" -#include "builtin.h" -#include "parse-options.h" - -static int line_termination = '\n'; -#define LS_RECURSIVE 1 -#define LS_TREE_ONLY 2 -#define LS_SHOW_TREES 4 -#define LS_NAME_ONLY 8 -#define LS_SHOW_SIZE 16 -static int abbrev; -static int ls_options; -static const char **pathspec; -static int chomp_prefix; -static const char *ls_tree_prefix; - -static const char * const ls_tree_usage[] = { - "git ls-tree [] [path...]", - NULL -}; - -static int show_recursive(const char *base, int baselen, const char *pathname) -{ - const char **s; - - if (ls_options & LS_RECURSIVE) - return 1; - - s = pathspec; - if (!s) - return 0; - - for (;;) { - const char *spec = *s++; - int len, speclen; - - if (!spec) - return 0; - if (strncmp(base, spec, baselen)) - continue; - len = strlen(pathname); - spec += baselen; - speclen = strlen(spec); - if (speclen <= len) - continue; - if (memcmp(pathname, spec, len)) - continue; - return 1; - } -} - -static int show_tree(const unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage, void *context) -{ - int retval = 0; - const char *type = blob_type; - - if (S_ISGITLINK(mode)) { - /* - * Maybe we want to have some recursive version here? - * - * Something similar to this incomplete example: - * - if (show_subprojects(base, baselen, pathname)) - retval = READ_TREE_RECURSIVE; - * - */ - type = commit_type; - } else if (S_ISDIR(mode)) { - if (show_recursive(base, baselen, pathname)) { - retval = READ_TREE_RECURSIVE; - if (!(ls_options & LS_SHOW_TREES)) - return retval; - } - type = tree_type; - } - else if (ls_options & LS_TREE_ONLY) - return 0; - - if (chomp_prefix && - (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix))) - return 0; - - if (!(ls_options & LS_NAME_ONLY)) { - if (ls_options & LS_SHOW_SIZE) { - char size_text[24]; - if (!strcmp(type, blob_type)) { - unsigned long size; - if (sha1_object_info(sha1, &size) == OBJ_BAD) - strcpy(size_text, "BAD"); - else - snprintf(size_text, sizeof(size_text), - "%lu", size); - } else - strcpy(size_text, "-"); - printf("%06o %s %s %7s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1), - size_text); - } else - printf("%06o %s %s\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1)); - } - write_name_quotedpfx(base + chomp_prefix, baselen - chomp_prefix, - pathname, stdout, line_termination); - return retval; -} - -int cmd_ls_tree(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20]; - struct tree *tree; - int full_tree = 0; - const struct option ls_tree_options[] = { - OPT_BIT('d', NULL, &ls_options, "only show trees", - LS_TREE_ONLY), - OPT_BIT('r', NULL, &ls_options, "recurse into subtrees", - LS_RECURSIVE), - OPT_BIT('t', NULL, &ls_options, "show trees when recursing", - LS_SHOW_TREES), - OPT_SET_INT('z', NULL, &line_termination, - "terminate entries with NUL byte", 0), - OPT_BIT('l', "long", &ls_options, "include object size", - LS_SHOW_SIZE), - OPT_BIT(0, "name-only", &ls_options, "list only filenames", - LS_NAME_ONLY), - OPT_BIT(0, "name-status", &ls_options, "list only filenames", - LS_NAME_ONLY), - OPT_SET_INT(0, "full-name", &chomp_prefix, - "use full path names", 0), - OPT_BOOLEAN(0, "full-tree", &full_tree, - "list entire tree; not just current directory " - "(implies --full-name)"), - OPT__ABBREV(&abbrev), - OPT_END() - }; - - git_config(git_default_config, NULL); - ls_tree_prefix = prefix; - if (prefix && *prefix) - chomp_prefix = strlen(prefix); - - argc = parse_options(argc, argv, prefix, ls_tree_options, - ls_tree_usage, 0); - if (full_tree) { - ls_tree_prefix = prefix = NULL; - chomp_prefix = 0; - } - /* -d -r should imply -t, but -d by itself should not have to. */ - if ( (LS_TREE_ONLY|LS_RECURSIVE) == - ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) - ls_options |= LS_SHOW_TREES; - - if (argc < 1) - usage_with_options(ls_tree_usage, ls_tree_options); - if (get_sha1(argv[0], sha1)) - die("Not a valid object name %s", argv[0]); - - pathspec = get_pathspec(prefix, argv + 1); - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, pathspec, show_tree, NULL); - - return 0; -} diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c deleted file mode 100644 index a50ac2256..000000000 --- a/builtin-mailinfo.c +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Another stupid program, this one parsing the headers of an - * email to figure out authorship and subject - */ -#include "cache.h" -#include "builtin.h" -#include "utf8.h" -#include "strbuf.h" - -static FILE *cmitmsg, *patchfile, *fin, *fout; - -static int keep_subject; -static int keep_non_patch_brackets_in_subject; -static const char *metainfo_charset; -static struct strbuf line = STRBUF_INIT; -static struct strbuf name = STRBUF_INIT; -static struct strbuf email = STRBUF_INIT; - -static enum { - TE_DONTCARE, TE_QP, TE_BASE64, -} transfer_encoding; -static enum { - TYPE_TEXT, TYPE_OTHER, -} message_type; - -static struct strbuf charset = STRBUF_INIT; -static int patch_lines; -static struct strbuf **p_hdr_data, **s_hdr_data; -static int use_scissors; -static int use_inbody_headers = 1; - -#define MAX_HDR_PARSED 10 -#define MAX_BOUNDARIES 5 - -static void cleanup_space(struct strbuf *sb); - - -static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) -{ - struct strbuf *src = name; - if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || - strchr(name->buf, '<') || strchr(name->buf, '>')) - src = email; - else if (name == out) - return; - strbuf_reset(out); - strbuf_addbuf(out, src); -} - -static void parse_bogus_from(const struct strbuf *line) -{ - /* John Doe */ - - char *bra, *ket; - /* This is fallback, so do not bother if we already have an - * e-mail address. - */ - if (email.len) - return; - - bra = strchr(line->buf, '<'); - if (!bra) - return; - ket = strchr(bra, '>'); - if (!ket) - return; - - strbuf_reset(&email); - strbuf_add(&email, bra + 1, ket - bra - 1); - - strbuf_reset(&name); - strbuf_add(&name, line->buf, bra - line->buf); - strbuf_trim(&name); - get_sane_name(&name, &name, &email); -} - -static void handle_from(const struct strbuf *from) -{ - char *at; - size_t el; - struct strbuf f; - - strbuf_init(&f, from->len); - strbuf_addbuf(&f, from); - - at = strchr(f.buf, '@'); - if (!at) { - parse_bogus_from(from); - return; - } - - /* - * If we already have one email, don't take any confusing lines - */ - if (email.len && strchr(at + 1, '@')) { - strbuf_release(&f); - return; - } - - /* Pick up the string around '@', possibly delimited with <> - * pair; that is the email part. - */ - while (at > f.buf) { - char c = at[-1]; - if (isspace(c)) - break; - if (c == '<') { - at[-1] = ' '; - break; - } - at--; - } - el = strcspn(at, " \n\t\r\v\f>"); - strbuf_reset(&email); - strbuf_add(&email, at, el); - strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); - - /* The remainder is name. It could be - * - * - "John Doe " (a), or - * - "john.doe@xz (John Doe)" (b), or - * - "John (zzz) Doe (Comment)" (c) - * - * but we have removed the email part, so - * - * - remove extra spaces which could stay after email (case 'c'), and - * - trim from both ends, possibly removing the () pair at the end - * (cases 'a' and 'b'). - */ - cleanup_space(&f); - strbuf_trim(&f); - if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { - strbuf_remove(&f, 0, 1); - strbuf_setlen(&f, f.len - 1); - } - - get_sane_name(&name, &f, &email); - strbuf_release(&f); -} - -static void handle_header(struct strbuf **out, const struct strbuf *line) -{ - if (!*out) { - *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(*out, line->len); - } else - strbuf_reset(*out); - - strbuf_addbuf(*out, line); -} - -/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt - * to have enough heuristics to grok MIME encoded patches often found - * on our mailing lists. For example, we do not even treat header lines - * case insensitively. - */ - -static int slurp_attr(const char *line, const char *name, struct strbuf *attr) -{ - const char *ends, *ap = strcasestr(line, name); - size_t sz; - - if (!ap) { - strbuf_setlen(attr, 0); - return 0; - } - ap += strlen(name); - if (*ap == '"') { - ap++; - ends = "\""; - } - else - ends = "; \t"; - sz = strcspn(ap, ends); - strbuf_add(attr, ap, sz); - return 1; -} - -static struct strbuf *content[MAX_BOUNDARIES]; - -static struct strbuf **content_top = content; - -static void handle_content_type(struct strbuf *line) -{ - struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); - strbuf_init(boundary, line->len); - - if (!strcasestr(line->buf, "text/")) - message_type = TYPE_OTHER; - if (slurp_attr(line->buf, "boundary=", boundary)) { - strbuf_insert(boundary, 0, "--", 2); - if (++content_top > &content[MAX_BOUNDARIES]) { - fprintf(stderr, "Too many boundaries to handle\n"); - exit(1); - } - *content_top = boundary; - boundary = NULL; - } - slurp_attr(line->buf, "charset=", &charset); - - if (boundary) { - strbuf_release(boundary); - free(boundary); - } -} - -static void handle_content_transfer_encoding(const struct strbuf *line) -{ - if (strcasestr(line->buf, "base64")) - transfer_encoding = TE_BASE64; - else if (strcasestr(line->buf, "quoted-printable")) - transfer_encoding = TE_QP; - else - transfer_encoding = TE_DONTCARE; -} - -static int is_multipart_boundary(const struct strbuf *line) -{ - return (((*content_top)->len <= line->len) && - !memcmp(line->buf, (*content_top)->buf, (*content_top)->len)); -} - -static void cleanup_subject(struct strbuf *subject) -{ - size_t at = 0; - - while (at < subject->len) { - char *pos; - size_t remove; - - switch (subject->buf[at]) { - case 'r': case 'R': - if (subject->len <= at + 3) - break; - if (!memcmp(subject->buf + at + 1, "e:", 2)) { - strbuf_remove(subject, at, 3); - continue; - } - at++; - break; - case ' ': case '\t': case ':': - strbuf_remove(subject, at, 1); - continue; - case '[': - pos = strchr(subject->buf + at, ']'); - if (!pos) - break; - remove = pos - subject->buf + at + 1; - if (!keep_non_patch_brackets_in_subject || - (7 <= remove && - memmem(subject->buf + at, remove, "PATCH", 5))) - strbuf_remove(subject, at, remove); - else - at += remove; - continue; - } - break; - } - strbuf_trim(subject); -} - -static void cleanup_space(struct strbuf *sb) -{ - size_t pos, cnt; - for (pos = 0; pos < sb->len; pos++) { - if (isspace(sb->buf[pos])) { - sb->buf[pos] = ' '; - for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); - strbuf_remove(sb, pos + 1, cnt); - } - } -} - -static void decode_header(struct strbuf *line); -static const char *header[MAX_HDR_PARSED] = { - "From","Subject","Date", -}; - -static inline int cmp_header(const struct strbuf *line, const char *hdr) -{ - int len = strlen(hdr); - return !strncasecmp(line->buf, hdr, len) && line->len > len && - line->buf[len] == ':' && isspace(line->buf[len + 1]); -} - -static int check_header(const struct strbuf *line, - struct strbuf *hdr_data[], int overwrite) -{ - int i, ret = 0, len; - struct strbuf sb = STRBUF_INIT; - /* search for the interesting parts */ - for (i = 0; header[i]; i++) { - int len = strlen(header[i]); - if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { - /* Unwrap inline B and Q encoding, and optionally - * normalize the meta information to utf8. - */ - strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); - decode_header(&sb); - handle_header(&hdr_data[i], &sb); - ret = 1; - goto check_header_out; - } - } - - /* Content stuff */ - if (cmp_header(line, "Content-Type")) { - len = strlen("Content-Type: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - strbuf_insert(&sb, 0, "Content-Type: ", len); - handle_content_type(&sb); - ret = 1; - goto check_header_out; - } - if (cmp_header(line, "Content-Transfer-Encoding")) { - len = strlen("Content-Transfer-Encoding: "); - strbuf_add(&sb, line->buf + len, line->len - len); - decode_header(&sb); - handle_content_transfer_encoding(&sb); - ret = 1; - goto check_header_out; - } - - /* for inbody stuff */ - if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) { - ret = 1; /* Should this return 0? */ - goto check_header_out; - } - if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) { - for (i = 0; header[i]; i++) { - if (!memcmp("Subject", header[i], 7)) { - handle_header(&hdr_data[i], line); - ret = 1; - goto check_header_out; - } - } - } - -check_header_out: - strbuf_release(&sb); - return ret; -} - -static int is_rfc2822_header(const struct strbuf *line) -{ - /* - * The section that defines the loosest possible - * field name is "3.6.8 Optional fields". - * - * optional-field = field-name ":" unstructured CRLF - * field-name = 1*ftext - * ftext = %d33-57 / %59-126 - */ - int ch; - char *cp = line->buf; - - /* Count mbox From headers as headers */ - if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From ")) - return 1; - - while ((ch = *cp++)) { - if (ch == ':') - return 1; - if ((33 <= ch && ch <= 57) || - (59 <= ch && ch <= 126)) - continue; - break; - } - return 0; -} - -static int read_one_header_line(struct strbuf *line, FILE *in) -{ - /* Get the first part of the line. */ - if (strbuf_getline(line, in, '\n')) - return 0; - - /* - * Is it an empty line or not a valid rfc2822 header? - * If so, stop here, and return false ("not a header") - */ - strbuf_rtrim(line); - if (!line->len || !is_rfc2822_header(line)) { - /* Re-add the newline */ - strbuf_addch(line, '\n'); - return 0; - } - - /* - * Now we need to eat all the continuation lines.. - * Yuck, 2822 header "folding" - */ - for (;;) { - int peek; - struct strbuf continuation = STRBUF_INIT; - - peek = fgetc(in); ungetc(peek, in); - if (peek != ' ' && peek != '\t') - break; - if (strbuf_getline(&continuation, in, '\n')) - break; - continuation.buf[0] = '\n'; - strbuf_rtrim(&continuation); - strbuf_addbuf(line, &continuation); - } - - return 1; -} - -static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) -{ - const char *in = q_seg->buf; - int c; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, q_seg->len); - - while ((c = *in++) != 0) { - if (c == '=') { - int d = *in++; - if (d == '\n' || !d) - break; /* drop trailing newline */ - strbuf_addch(out, (hexval(d) << 4) | hexval(*in++)); - continue; - } - if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ - c = 0x20; - strbuf_addch(out, c); - } - return out; -} - -static struct strbuf *decode_b_segment(const struct strbuf *b_seg) -{ - /* Decode in..ep, possibly in-place to ot */ - int c, pos = 0, acc = 0; - const char *in = b_seg->buf; - struct strbuf *out = xmalloc(sizeof(struct strbuf)); - strbuf_init(out, b_seg->len); - - while ((c = *in++) != 0) { - if (c == '+') - c = 62; - else if (c == '/') - c = 63; - else if ('A' <= c && c <= 'Z') - c -= 'A'; - else if ('a' <= c && c <= 'z') - c -= 'a' - 26; - else if ('0' <= c && c <= '9') - c -= '0' - 52; - else - continue; /* garbage */ - switch (pos++) { - case 0: - acc = (c << 2); - break; - case 1: - strbuf_addch(out, (acc | (c >> 4))); - acc = (c & 15) << 4; - break; - case 2: - strbuf_addch(out, (acc | (c >> 2))); - acc = (c & 3) << 6; - break; - case 3: - strbuf_addch(out, (acc | c)); - acc = pos = 0; - break; - } - } - return out; -} - -/* - * When there is no known charset, guess. - * - * Right now we assume that if the target is UTF-8 (the default), - * and it already looks like UTF-8 (which includes US-ASCII as its - * subset, of course) then that is what it is and there is nothing - * to do. - * - * Otherwise, we default to assuming it is Latin1 for historical - * reasons. - */ -static const char *guess_charset(const struct strbuf *line, const char *target_charset) -{ - if (is_encoding_utf8(target_charset)) { - if (is_utf8(line->buf)) - return NULL; - } - return "ISO8859-1"; -} - -static void convert_to_utf8(struct strbuf *line, const char *charset) -{ - char *out; - - if (!charset || !*charset) { - charset = guess_charset(line, metainfo_charset); - if (!charset) - return; - } - - if (!strcasecmp(metainfo_charset, charset)) - return; - out = reencode_string(line->buf, metainfo_charset, charset); - if (!out) - die("cannot convert from %s to %s", - charset, metainfo_charset); - strbuf_attach(line, out, strlen(out), strlen(out)); -} - -static int decode_header_bq(struct strbuf *it) -{ - char *in, *ep, *cp; - struct strbuf outbuf = STRBUF_INIT, *dec; - struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; - int rfc2047 = 0; - - in = it->buf; - while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { - int encoding; - strbuf_reset(&charset_q); - strbuf_reset(&piecebuf); - rfc2047 = 1; - - if (in != ep) { - /* - * We are about to process an encoded-word - * that begins at ep, but there is something - * before the encoded word. - */ - char *scan; - for (scan = in; scan < ep; scan++) - if (!isspace(*scan)) - break; - - if (scan != ep || in == it->buf) { - /* - * We should not lose that "something", - * unless we have just processed an - * encoded-word, and there is only LWS - * before the one we are about to process. - */ - strbuf_add(&outbuf, in, ep - in); - } - } - /* E.g. - * ep : "=?iso-2022-jp?B?GyR...?= foo" - * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" - */ - ep += 2; - - if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) - goto decode_header_bq_out; - - if (cp + 3 - it->buf > it->len) - goto decode_header_bq_out; - strbuf_add(&charset_q, ep, cp - ep); - - encoding = cp[1]; - if (!encoding || cp[2] != '?') - goto decode_header_bq_out; - ep = strstr(cp + 3, "?="); - if (!ep) - goto decode_header_bq_out; - strbuf_add(&piecebuf, cp + 3, ep - cp - 3); - switch (tolower(encoding)) { - default: - goto decode_header_bq_out; - case 'b': - dec = decode_b_segment(&piecebuf); - break; - case 'q': - dec = decode_q_segment(&piecebuf, 1); - break; - } - if (metainfo_charset) - convert_to_utf8(dec, charset_q.buf); - - strbuf_addbuf(&outbuf, dec); - strbuf_release(dec); - free(dec); - in = ep + 2; - } - strbuf_addstr(&outbuf, in); - strbuf_reset(it); - strbuf_addbuf(it, &outbuf); -decode_header_bq_out: - strbuf_release(&outbuf); - strbuf_release(&charset_q); - strbuf_release(&piecebuf); - return rfc2047; -} - -static void decode_header(struct strbuf *it) -{ - if (decode_header_bq(it)) - return; - /* otherwise "it" is a straight copy of the input. - * This can be binary guck but there is no charset specified. - */ - if (metainfo_charset) - convert_to_utf8(it, ""); -} - -static void decode_transfer_encoding(struct strbuf *line) -{ - struct strbuf *ret; - - switch (transfer_encoding) { - case TE_QP: - ret = decode_q_segment(line, 0); - break; - case TE_BASE64: - ret = decode_b_segment(line); - break; - case TE_DONTCARE: - default: - return; - } - strbuf_reset(line); - strbuf_addbuf(line, ret); - strbuf_release(ret); - free(ret); -} - -static void handle_filter(struct strbuf *line); - -static int find_boundary(void) -{ - while (!strbuf_getline(&line, fin, '\n')) { - if (*content_top && is_multipart_boundary(&line)) - return 1; - } - return 0; -} - -static int handle_boundary(void) -{ - struct strbuf newline = STRBUF_INIT; - - strbuf_addch(&newline, '\n'); -again: - if (line.len >= (*content_top)->len + 2 && - !memcmp(line.buf + (*content_top)->len, "--", 2)) { - /* we hit an end boundary */ - /* pop the current boundary off the stack */ - strbuf_release(*content_top); - free(*content_top); - *content_top = NULL; - - /* technically won't happen as is_multipart_boundary() - will fail first. But just in case.. - */ - if (--content_top < content) { - fprintf(stderr, "Detected mismatched boundaries, " - "can't recover\n"); - exit(1); - } - handle_filter(&newline); - strbuf_release(&newline); - - /* skip to the next boundary */ - if (!find_boundary()) - return 0; - goto again; - } - - /* set some defaults */ - transfer_encoding = TE_DONTCARE; - strbuf_reset(&charset); - message_type = TYPE_TEXT; - - /* slurp in this section's info */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 0); - - strbuf_release(&newline); - /* replenish line */ - if (strbuf_getline(&line, fin, '\n')) - return 0; - strbuf_addch(&line, '\n'); - return 1; -} - -static inline int patchbreak(const struct strbuf *line) -{ - size_t i; - - /* Beginning of a "diff -" header? */ - if (!prefixcmp(line->buf, "diff -")) - return 1; - - /* CVS "Index: " line? */ - if (!prefixcmp(line->buf, "Index: ")) - return 1; - - /* - * "--- " starts patches without headers - * "---*" is a manual separator - */ - if (line->len < 4) - return 0; - - if (!prefixcmp(line->buf, "---")) { - /* space followed by a filename? */ - if (line->buf[3] == ' ' && !isspace(line->buf[4])) - return 1; - /* Just whitespace? */ - for (i = 3; i < line->len; i++) { - unsigned char c = line->buf[i]; - if (c == '\n') - return 1; - if (!isspace(c)) - break; - } - return 0; - } - return 0; -} - -static int is_scissors_line(const struct strbuf *line) -{ - size_t i, len = line->len; - int scissors = 0, gap = 0; - int first_nonblank = -1; - int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; - const char *buf = line->buf; - - for (i = 0; i < len; i++) { - if (isspace(buf[i])) { - if (in_perforation) { - perforation++; - gap++; - } - continue; - } - last_nonblank = i; - if (first_nonblank < 0) - first_nonblank = i; - if (buf[i] == '-') { - in_perforation = 1; - perforation++; - continue; - } - if (i + 1 < len && - (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { - in_perforation = 1; - perforation += 2; - scissors += 2; - i++; - continue; - } - in_perforation = 0; - } - - /* - * The mark must be at least 8 bytes long (e.g. "-- >8 --"). - * Even though there can be arbitrary cruft on the same line - * (e.g. "cut here"), in order to avoid misidentification, the - * perforation must occupy more than a third of the visible - * width of the line, and dashes and scissors must occupy more - * than half of the perforation. - */ - - visible = last_nonblank - first_nonblank + 1; - return (scissors && 8 <= visible && - visible < perforation * 3 && - gap * 2 < perforation); -} - -static int handle_commit_msg(struct strbuf *line) -{ - static int still_looking = 1; - - if (!cmitmsg) - return 0; - - if (still_looking) { - strbuf_ltrim(line); - if (!line->len) - return 0; - } - - if (use_inbody_headers && still_looking) { - still_looking = check_header(line, s_hdr_data, 0); - if (still_looking) - return 0; - } else - /* Only trim the first (blank) line of the commit message - * when ignoring in-body headers. - */ - still_looking = 0; - - /* normalize the log message to UTF-8. */ - if (metainfo_charset) - convert_to_utf8(line, charset.buf); - - if (use_scissors && is_scissors_line(line)) { - int i; - if (fseek(cmitmsg, 0L, SEEK_SET)) - die_errno("Could not rewind output message file"); - if (ftruncate(fileno(cmitmsg), 0)) - die_errno("Could not truncate output message file at scissors"); - still_looking = 1; - - /* - * We may have already read "secondary headers"; purge - * them to give ourselves a clean restart. - */ - for (i = 0; header[i]; i++) { - if (s_hdr_data[i]) - strbuf_release(s_hdr_data[i]); - s_hdr_data[i] = NULL; - } - return 0; - } - - if (patchbreak(line)) { - fclose(cmitmsg); - cmitmsg = NULL; - return 1; - } - - fputs(line->buf, cmitmsg); - return 0; -} - -static void handle_patch(const struct strbuf *line) -{ - fwrite(line->buf, 1, line->len, patchfile); - patch_lines++; -} - -static void handle_filter(struct strbuf *line) -{ - static int filter = 0; - - /* filter tells us which part we left off on */ - switch (filter) { - case 0: - if (!handle_commit_msg(line)) - break; - filter++; - case 1: - handle_patch(line); - break; - } -} - -static void handle_body(void) -{ - struct strbuf prev = STRBUF_INIT; - - /* Skip up to the first boundary */ - if (*content_top) { - if (!find_boundary()) - goto handle_body_out; - } - - do { - /* process any boundary lines */ - if (*content_top && is_multipart_boundary(&line)) { - /* flush any leftover */ - if (prev.len) { - handle_filter(&prev); - strbuf_reset(&prev); - } - if (!handle_boundary()) - goto handle_body_out; - } - - /* Unwrap transfer encoding */ - decode_transfer_encoding(&line); - - switch (transfer_encoding) { - case TE_BASE64: - case TE_QP: - { - struct strbuf **lines, **it, *sb; - - /* Prepend any previous partial lines */ - strbuf_insert(&line, 0, prev.buf, prev.len); - strbuf_reset(&prev); - - /* binary data most likely doesn't have newlines */ - if (message_type != TYPE_TEXT) { - handle_filter(&line); - break; - } - /* - * This is a decoded line that may contain - * multiple new lines. Pass only one chunk - * at a time to handle_filter() - */ - lines = strbuf_split(&line, '\n'); - for (it = lines; (sb = *it); it++) { - if (*(it + 1) == NULL) /* The last line */ - if (sb->buf[sb->len - 1] != '\n') { - /* Partial line, save it for later. */ - strbuf_addbuf(&prev, sb); - break; - } - handle_filter(sb); - } - /* - * The partial chunk is saved in "prev" and will be - * appended by the next iteration of read_line_with_nul(). - */ - strbuf_list_free(lines); - break; - } - default: - handle_filter(&line); - } - - } while (!strbuf_getwholeline(&line, fin, '\n')); - -handle_body_out: - strbuf_release(&prev); -} - -static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) -{ - const char *sp = data->buf; - while (1) { - char *ep = strchr(sp, '\n'); - int len; - if (!ep) - len = strlen(sp); - else - len = ep - sp; - fprintf(fout, "%s: %.*s\n", hdr, len, sp); - if (!ep) - break; - sp = ep + 1; - } -} - -static void handle_info(void) -{ - struct strbuf *hdr; - int i; - - for (i = 0; header[i]; i++) { - /* only print inbody headers if we output a patch file */ - if (patch_lines && s_hdr_data[i]) - hdr = s_hdr_data[i]; - else if (p_hdr_data[i]) - hdr = p_hdr_data[i]; - else - continue; - - if (!memcmp(header[i], "Subject", 7)) { - if (!keep_subject) { - cleanup_subject(hdr); - cleanup_space(hdr); - } - output_header_lines(fout, "Subject", hdr); - } else if (!memcmp(header[i], "From", 4)) { - cleanup_space(hdr); - handle_from(hdr); - fprintf(fout, "Author: %s\n", name.buf); - fprintf(fout, "Email: %s\n", email.buf); - } else { - cleanup_space(hdr); - fprintf(fout, "%s: %s\n", header[i], hdr->buf); - } - } - fprintf(fout, "\n"); -} - -static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) -{ - int peek; - fin = in; - fout = out; - - cmitmsg = fopen(msg, "w"); - if (!cmitmsg) { - perror(msg); - return -1; - } - patchfile = fopen(patch, "w"); - if (!patchfile) { - perror(patch); - fclose(cmitmsg); - return -1; - } - - p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data)); - s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data)); - - do { - peek = fgetc(in); - } while (isspace(peek)); - ungetc(peek, in); - - /* process the email header */ - while (read_one_header_line(&line, fin)) - check_header(&line, p_hdr_data, 1); - - handle_body(); - handle_info(); - - return 0; -} - -static int git_mailinfo_config(const char *var, const char *value, void *unused) -{ - if (prefixcmp(var, "mailinfo.")) - return git_default_config(var, value, unused); - if (!strcmp(var, "mailinfo.scissors")) { - use_scissors = git_config_bool(var, value); - return 0; - } - /* perhaps others here */ - return 0; -} - -static const char mailinfo_usage[] = - "git mailinfo [-k|-b] [-u | --encoding= | -n] [--scissors | --no-scissors] msg patch < mail >info"; - -int cmd_mailinfo(int argc, const char **argv, const char *prefix) -{ - const char *def_charset; - - /* NEEDSWORK: might want to do the optional .git/ directory - * discovery - */ - git_config(git_mailinfo_config, NULL); - - def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); - metainfo_charset = def_charset; - - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "-k")) - keep_subject = 1; - else if (!strcmp(argv[1], "-b")) - keep_non_patch_brackets_in_subject = 1; - else if (!strcmp(argv[1], "-u")) - metainfo_charset = def_charset; - else if (!strcmp(argv[1], "-n")) - metainfo_charset = NULL; - else if (!prefixcmp(argv[1], "--encoding=")) - metainfo_charset = argv[1] + 11; - else if (!strcmp(argv[1], "--scissors")) - use_scissors = 1; - else if (!strcmp(argv[1], "--no-scissors")) - use_scissors = 0; - else if (!strcmp(argv[1], "--no-inbody-headers")) - use_inbody_headers = 0; - else - usage(mailinfo_usage); - argc--; argv++; - } - - if (argc != 3) - usage(mailinfo_usage); - - return !!mailinfo(stdin, stdout, argv[1], argv[2]); -} diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c deleted file mode 100644 index 207e358ed..000000000 --- a/builtin-mailsplit.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Totally braindamaged mbox splitter program. - * - * It just splits a mbox into a list of files: "0001" "0002" .. - * so you can process them further from there. - */ -#include "cache.h" -#include "builtin.h" -#include "string-list.h" -#include "strbuf.h" - -static const char git_mailsplit_usage[] = -"git mailsplit [-d] [-f] [-b] -o [|...]"; - -static int is_from_line(const char *line, int len) -{ - const char *colon; - - if (len < 20 || memcmp("From ", line, 5)) - return 0; - - colon = line + len - 2; - line += 5; - for (;;) { - if (colon < line) - return 0; - if (*--colon == ':') - break; - } - - if (!isdigit(colon[-4]) || - !isdigit(colon[-2]) || - !isdigit(colon[-1]) || - !isdigit(colon[ 1]) || - !isdigit(colon[ 2])) - return 0; - - /* year */ - if (strtol(colon+3, NULL, 10) <= 90) - return 0; - - /* Ok, close enough */ - return 1; -} - -static struct strbuf buf = STRBUF_INIT; -static int keep_cr; - -/* Called with the first line (potentially partial) - * already in buf[] -- normally that should begin with - * the Unix "From " line. Write it into the specified - * file. - */ -static int split_one(FILE *mbox, const char *name, int allow_bare) -{ - FILE *output = NULL; - int fd; - int status = 0; - int is_bare = !is_from_line(buf.buf, buf.len); - - if (is_bare && !allow_bare) - goto corrupt; - - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) - die_errno("cannot open output file '%s'", name); - output = xfdopen(fd, "w"); - - /* Copy it out, while searching for a line that begins with - * "From " and having something that looks like a date format. - */ - for (;;) { - if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' && - buf.buf[buf.len-2] == '\r') { - strbuf_setlen(&buf, buf.len-2); - strbuf_addch(&buf, '\n'); - } - - if (fwrite(buf.buf, 1, buf.len, output) != buf.len) - die_errno("cannot write output"); - - if (strbuf_getwholeline(&buf, mbox, '\n')) { - if (feof(mbox)) { - status = 1; - break; - } - die_errno("cannot read mbox"); - } - if (!is_bare && is_from_line(buf.buf, buf.len)) - break; /* done with one message */ - } - fclose(output); - return status; - - corrupt: - if (output) - fclose(output); - unlink(name); - fprintf(stderr, "corrupt mailbox\n"); - exit(1); -} - -static int populate_maildir_list(struct string_list *list, const char *path) -{ - DIR *dir; - struct dirent *dent; - char name[PATH_MAX]; - char *subs[] = { "cur", "new", NULL }; - char **sub; - - for (sub = subs; *sub; ++sub) { - snprintf(name, sizeof(name), "%s/%s", path, *sub); - if ((dir = opendir(name)) == NULL) { - if (errno == ENOENT) - continue; - error("cannot opendir %s (%s)", name, strerror(errno)); - return -1; - } - - while ((dent = readdir(dir)) != NULL) { - if (dent->d_name[0] == '.') - continue; - snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name); - string_list_insert(name, list); - } - - closedir(dir); - } - - return 0; -} - -static int split_maildir(const char *maildir, const char *dir, - int nr_prec, int skip) -{ - char file[PATH_MAX]; - char name[PATH_MAX]; - int ret = -1; - int i; - struct string_list list = {NULL, 0, 0, 1}; - - if (populate_maildir_list(&list, maildir) < 0) - goto out; - - for (i = 0; i < list.nr; i++) { - FILE *f; - snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].string); - f = fopen(file, "r"); - if (!f) { - error("cannot open mail %s (%s)", file, strerror(errno)); - goto out; - } - - if (strbuf_getwholeline(&buf, f, '\n')) { - error("cannot read mail %s (%s)", file, strerror(errno)); - goto out; - } - - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); - split_one(f, name, 1); - - fclose(f); - } - - ret = skip; -out: - string_list_clear(&list, 1); - return ret; -} - -static int split_mbox(const char *file, const char *dir, int allow_bare, - int nr_prec, int skip) -{ - char name[PATH_MAX]; - int ret = -1; - int peek; - - FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); - int file_done = 0; - - if (!f) { - error("cannot open mbox %s", file); - goto out; - } - - do { - peek = fgetc(f); - } while (isspace(peek)); - ungetc(peek, f); - - if (strbuf_getwholeline(&buf, f, '\n')) { - /* empty stdin is OK */ - if (f != stdin) { - error("cannot read mbox %s", file); - goto out; - } - file_done = 1; - } - - while (!file_done) { - sprintf(name, "%s/%0*d", dir, nr_prec, ++skip); - file_done = split_one(f, name, allow_bare); - } - - if (f != stdin) - fclose(f); - - ret = skip; -out: - return ret; -} - -int cmd_mailsplit(int argc, const char **argv, const char *prefix) -{ - int nr = 0, nr_prec = 4, num = 0; - int allow_bare = 0; - const char *dir = NULL; - const char **argp; - static const char *stdin_only[] = { "-", NULL }; - - for (argp = argv+1; *argp; argp++) { - const char *arg = *argp; - - if (arg[0] != '-') - break; - /* do flags here */ - if ( arg[1] == 'd' ) { - nr_prec = strtol(arg+2, NULL, 10); - if (nr_prec < 3 || 10 <= nr_prec) - usage(git_mailsplit_usage); - continue; - } else if ( arg[1] == 'f' ) { - nr = strtol(arg+2, NULL, 10); - } else if ( arg[1] == 'h' ) { - usage(git_mailsplit_usage); - } else if ( arg[1] == 'b' && !arg[2] ) { - allow_bare = 1; - } else if (!strcmp(arg, "--keep-cr")) { - keep_cr = 1; - } else if ( arg[1] == 'o' && arg[2] ) { - dir = arg+2; - } else if ( arg[1] == '-' && !arg[2] ) { - argp++; /* -- marks end of options */ - break; - } else { - die("unknown option: %s", arg); - } - } - - if ( !dir ) { - /* Backwards compatibility: if no -o specified, accept - or just */ - switch (argc - (argp-argv)) { - case 1: - dir = argp[0]; - argp = stdin_only; - break; - case 2: - stdin_only[0] = argp[0]; - dir = argp[1]; - argp = stdin_only; - break; - default: - usage(git_mailsplit_usage); - } - } else { - /* New usage: if no more argument, parse stdin */ - if ( !*argp ) - argp = stdin_only; - } - - while (*argp) { - const char *arg = *argp++; - struct stat argstat; - int ret = 0; - - if (arg[0] == '-' && arg[1] == 0) { - ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); - if (ret < 0) { - error("cannot split patches from stdin"); - return 1; - } - num += (ret - nr); - nr = ret; - continue; - } - - if (stat(arg, &argstat) == -1) { - error("cannot stat %s (%s)", arg, strerror(errno)); - return 1; - } - - if (S_ISDIR(argstat.st_mode)) - ret = split_maildir(arg, dir, nr_prec, nr); - else - ret = split_mbox(arg, dir, allow_bare, nr_prec, nr); - - if (ret < 0) { - error("cannot split patches from %s", arg); - return 1; - } - num += (ret - nr); - nr = ret; - } - - printf("%d\n", num); - - return 0; -} diff --git a/builtin-merge-base.c b/builtin-merge-base.c deleted file mode 100644 index 54e7ec223..000000000 --- a/builtin-merge-base.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "parse-options.h" - -static int show_merge_base(struct commit **rev, int rev_nr, int show_all) -{ - struct commit_list *result; - - result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); - - if (!result) - return 1; - - while (result) { - printf("%s\n", sha1_to_hex(result->item->object.sha1)); - if (!show_all) - return 0; - result = result->next; - } - - return 0; -} - -static const char * const merge_base_usage[] = { - "git merge-base [-a|--all] ...", - NULL -}; - -static struct commit *get_commit_reference(const char *arg) -{ - unsigned char revkey[20]; - struct commit *r; - - if (get_sha1(arg, revkey)) - die("Not a valid object name %s", arg); - r = lookup_commit_reference(revkey); - if (!r) - die("Not a valid commit name %s", arg); - - return r; -} - -int cmd_merge_base(int argc, const char **argv, const char *prefix) -{ - struct commit **rev; - int rev_nr = 0; - int show_all = 0; - - struct option options[] = { - OPT_BOOLEAN('a', "all", &show_all, "outputs all common ancestors"), - OPT_END() - }; - - git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); - if (argc < 2) - usage_with_options(merge_base_usage, options); - rev = xmalloc(argc * sizeof(*rev)); - while (argc-- > 0) - rev[rev_nr++] = get_commit_reference(*argv++); - return show_merge_base(rev, rev_nr, show_all); -} diff --git a/builtin-merge-file.c b/builtin-merge-file.c deleted file mode 100644 index 1e70073a7..000000000 --- a/builtin-merge-file.c +++ /dev/null @@ -1,96 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "xdiff/xdiff.h" -#include "xdiff-interface.h" -#include "parse-options.h" - -static const char *const merge_file_usage[] = { - "git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2", - NULL -}; - -static int label_cb(const struct option *opt, const char *arg, int unset) -{ - static int label_count = 0; - const char **names = (const char **)opt->value; - - if (label_count >= 3) - return error("too many labels on the command line"); - names[label_count++] = arg; - return 0; -} - -int cmd_merge_file(int argc, const char **argv, const char *prefix) -{ - const char *names[3] = { NULL, NULL, NULL }; - mmfile_t mmfs[3]; - mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{XDF_NEED_MINIMAL}}; - int ret = 0, i = 0, to_stdout = 0; - int level = XDL_MERGE_ZEALOUS_ALNUM; - int style = 0, quiet = 0; - int favor = 0; - int nongit; - - struct option options[] = { - OPT_BOOLEAN('p', "stdout", &to_stdout, "send results to standard output"), - OPT_SET_INT(0, "diff3", &style, "use a diff3 based merge", XDL_MERGE_DIFF3), - OPT_SET_INT(0, "ours", &favor, "for conflicts, use our version", - XDL_MERGE_FAVOR_OURS), - OPT_SET_INT(0, "theirs", &favor, "for conflicts, use their version", - XDL_MERGE_FAVOR_THEIRS), - OPT__QUIET(&quiet), - OPT_CALLBACK('L', NULL, names, "name", - "set labels for file1/orig_file/file2", &label_cb), - OPT_END(), - }; - - prefix = setup_git_directory_gently(&nongit); - if (!nongit) { - /* Read the configuration file */ - git_config(git_xmerge_config, NULL); - if (0 <= git_xmerge_style) - style = git_xmerge_style; - } - - argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); - if (argc != 3) - usage_with_options(merge_file_usage, options); - if (quiet) { - if (!freopen("/dev/null", "w", stderr)) - return error("failed to redirect stderr to /dev/null: " - "%s\n", strerror(errno)); - } - - for (i = 0; i < 3; i++) { - if (!names[i]) - names[i] = argv[i]; - if (read_mmfile(mmfs + i, argv[i])) - return -1; - if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s\n", - argv[i]); - } - - ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xmp, XDL_MERGE_FLAGS(level, style, favor), &result); - - for (i = 0; i < 3; i++) - free(mmfs[i].ptr); - - if (ret >= 0) { - const char *filename = argv[0]; - FILE *f = to_stdout ? stdout : fopen(filename, "wb"); - - if (!f) - ret = error("Could not open %s for writing", filename); - else if (result.size && - fwrite(result.ptr, result.size, 1, f) != 1) - ret = error("Could not write to %s", filename); - else if (fclose(f)) - ret = error("Could not close %s", filename); - free(result.ptr); - } - - return ret; -} diff --git a/builtin-merge-index.c b/builtin-merge-index.c deleted file mode 100644 index 2c4cf5e55..000000000 --- a/builtin-merge-index.c +++ /dev/null @@ -1,111 +0,0 @@ -#include "cache.h" -#include "run-command.h" -#include "exec_cmd.h" - -static const char *pgm; -static int one_shot, quiet; -static int err; - -static int merge_entry(int pos, const char *path) -{ - int found; - const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL }; - char hexbuf[4][60]; - char ownbuf[4][60]; - - if (pos >= active_nr) - die("git merge-index: %s not in the cache", path); - found = 0; - do { - struct cache_entry *ce = active_cache[pos]; - int stage = ce_stage(ce); - - if (strcmp(ce->name, path)) - break; - found++; - strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ce->ce_mode); - arguments[stage] = hexbuf[stage]; - arguments[stage + 4] = ownbuf[stage]; - } while (++pos < active_nr); - if (!found) - die("git merge-index: %s not in the cache", path); - - if (run_command_v_opt(arguments, 0)) { - if (one_shot) - err++; - else { - if (!quiet) - die("merge program failed"); - exit(1); - } - } - return found; -} - -static void merge_file(const char *path) -{ - int pos = cache_name_pos(path, strlen(path)); - - /* - * If it already exists in the cache as stage0, it's - * already merged and there is nothing to do. - */ - if (pos < 0) - merge_entry(-pos-1, path); -} - -static void merge_all(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - i += merge_entry(i, ce->name)-1; - } -} - -int cmd_merge_index(int argc, const char **argv, const char *prefix) -{ - int i, force_file = 0; - - /* Without this we cannot rely on waitpid() to tell - * what happened to our children. - */ - signal(SIGCHLD, SIG_DFL); - - if (argc < 3) - usage("git merge-index [-o] [-q] (-a | [--] *)"); - - read_cache(); - - i = 1; - if (!strcmp(argv[i], "-o")) { - one_shot = 1; - i++; - } - if (!strcmp(argv[i], "-q")) { - quiet = 1; - i++; - } - pgm = argv[i++]; - for (; i < argc; i++) { - const char *arg = argv[i]; - if (!force_file && *arg == '-') { - if (!strcmp(arg, "--")) { - force_file = 1; - continue; - } - if (!strcmp(arg, "-a")) { - merge_all(); - continue; - } - die("git merge-index: unknown option %s", arg); - } - merge_file(arg); - } - if (err && !quiet) - die("merge program failed"); - return err; -} diff --git a/builtin-merge-ours.c b/builtin-merge-ours.c deleted file mode 100644 index 684411694..000000000 --- a/builtin-merge-ours.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Implementation of git-merge-ours.sh as builtin - * - * Copyright (c) 2007 Thomas Harning Jr - * Original: - * Original Copyright (c) 2005 Junio C Hamano - * - * Pretend we resolved the heads, but declare our tree trumps everybody else. - */ -#include "git-compat-util.h" -#include "builtin.h" - -static const char builtin_merge_ours_usage[] = - "git merge-ours ... -- HEAD ..."; - -static const char *diff_index_args[] = { - "diff-index", "--quiet", "--cached", "HEAD", "--", NULL -}; -#define NARGS (ARRAY_SIZE(diff_index_args) - 1) - -int cmd_merge_ours(int argc, const char **argv, const char *prefix) -{ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(builtin_merge_ours_usage); - - /* - * We need to exit with 2 if the index does not match our HEAD tree, - * because the current index is what we will be committing as the - * merge result. - */ - if (cmd_diff_index(NARGS, diff_index_args, prefix)) - exit(2); - exit(0); -} diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c deleted file mode 100644 index d8875d589..000000000 --- a/builtin-merge-recursive.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "merge-recursive.h" - -static const char *better_branch_name(const char *branch) -{ - static char githead_env[8 + 40 + 1]; - char *name; - - if (strlen(branch) != 40) - return branch; - sprintf(githead_env, "GITHEAD_%s", branch); - name = getenv(githead_env); - return name ? name : branch; -} - -int cmd_merge_recursive(int argc, const char **argv, const char *prefix) -{ - const unsigned char *bases[21]; - unsigned bases_count = 0; - int i, failed; - unsigned char h1[20], h2[20]; - struct merge_options o; - struct commit *result; - - init_merge_options(&o); - if (argv[0] && !suffixcmp(argv[0], "-subtree")) - o.subtree_shift = ""; - - if (argc < 4) - usagef("%s ... -- ...", argv[0]); - - for (i = 1; i < argc; ++i) { - const char *arg = argv[i]; - - if (!prefixcmp(arg, "--")) { - if (!arg[2]) - break; - if (!strcmp(arg+2, "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(arg+2, "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(arg+2, "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(arg+2, "subtree=")) - o.subtree_shift = arg + 10; - else - die("Unknown option %s", arg); - continue; - } - if (bases_count < ARRAY_SIZE(bases)-1) { - unsigned char *sha = xmalloc(20); - if (get_sha1(argv[i], sha)) - die("Could not parse object '%s'", argv[i]); - bases[bases_count++] = sha; - } - else - warning("Cannot handle more than %d bases. " - "Ignoring %s.", - (int)ARRAY_SIZE(bases)-1, argv[i]); - } - if (argc - i != 3) /* "--" "" "" */ - die("Not handling anything other than two heads merge."); - - o.branch1 = argv[++i]; - o.branch2 = argv[++i]; - - if (get_sha1(o.branch1, h1)) - die("Could not resolve ref '%s'", o.branch1); - if (get_sha1(o.branch2, h2)) - die("Could not resolve ref '%s'", o.branch2); - - o.branch1 = better_branch_name(o.branch1); - o.branch2 = better_branch_name(o.branch2); - - if (o.verbosity >= 3) - printf("Merging %s with %s\n", o.branch1, o.branch2); - - failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); - if (failed < 0) - return 128; /* die() error code */ - return failed; -} diff --git a/builtin-merge-tree.c b/builtin-merge-tree.c deleted file mode 100644 index a4a4f2ce4..000000000 --- a/builtin-merge-tree.c +++ /dev/null @@ -1,358 +0,0 @@ -#include "cache.h" -#include "tree-walk.h" -#include "xdiff-interface.h" -#include "blob.h" -#include "exec_cmd.h" - -static const char merge_tree_usage[] = "git merge-tree "; -static int resolve_directories = 1; - -struct merge_list { - struct merge_list *next; - struct merge_list *link; /* other stages for this object */ - - unsigned int stage : 2, - flags : 30; - unsigned int mode; - const char *path; - struct blob *blob; -}; - -static struct merge_list *merge_result, **merge_result_end = &merge_result; - -static void add_merge_entry(struct merge_list *entry) -{ - *merge_result_end = entry; - merge_result_end = &entry->next; -} - -static void merge_trees(struct tree_desc t[3], const char *base); - -static const char *explanation(struct merge_list *entry) -{ - switch (entry->stage) { - case 0: - return "merged"; - case 3: - return "added in remote"; - case 2: - if (entry->link) - return "added in both"; - return "added in local"; - } - - /* Existed in base */ - entry = entry->link; - if (!entry) - return "removed in both"; - - if (entry->link) - return "changed in both"; - - if (entry->stage == 3) - return "removed in local"; - return "removed in remote"; -} - -extern void *merge_file(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); - -static void *result(struct merge_list *entry, unsigned long *size) -{ - enum object_type type; - struct blob *base, *our, *their; - - if (!entry->stage) - return read_sha1_file(entry->blob->object.sha1, &type, size); - base = NULL; - if (entry->stage == 1) { - base = entry->blob; - entry = entry->link; - } - our = NULL; - if (entry && entry->stage == 2) { - our = entry->blob; - entry = entry->link; - } - their = NULL; - if (entry) - their = entry->blob; - return merge_file(entry->path, base, our, their, size); -} - -static void *origin(struct merge_list *entry, unsigned long *size) -{ - enum object_type type; - while (entry) { - if (entry->stage == 2) - return read_sha1_file(entry->blob->object.sha1, &type, size); - entry = entry->link; - } - return NULL; -} - -static int show_outf(void *priv_, mmbuffer_t *mb, int nbuf) -{ - int i; - for (i = 0; i < nbuf; i++) - printf("%.*s", (int) mb[i].size, mb[i].ptr); - return 0; -} - -static void show_diff(struct merge_list *entry) -{ - unsigned long size; - mmfile_t src, dst; - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - xpp.flags = XDF_NEED_MINIMAL; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 3; - ecb.outf = show_outf; - ecb.priv = NULL; - - src.ptr = origin(entry, &size); - if (!src.ptr) - size = 0; - src.size = size; - dst.ptr = result(entry, &size); - if (!dst.ptr) - size = 0; - dst.size = size; - xdi_diff(&src, &dst, &xpp, &xecfg, &ecb); - free(src.ptr); - free(dst.ptr); -} - -static void show_result_list(struct merge_list *entry) -{ - printf("%s\n", explanation(entry)); - do { - struct merge_list *link = entry->link; - static const char *desc[4] = { "result", "base", "our", "their" }; - printf(" %-6s %o %s %s\n", desc[entry->stage], entry->mode, sha1_to_hex(entry->blob->object.sha1), entry->path); - entry = link; - } while (entry); -} - -static void show_result(void) -{ - struct merge_list *walk; - - walk = merge_result; - while (walk) { - show_result_list(walk); - show_diff(walk); - walk = walk->next; - } -} - -/* An empty entry never compares same, not even to another empty entry */ -static int same_entry(struct name_entry *a, struct name_entry *b) -{ - return a->sha1 && - b->sha1 && - !hashcmp(a->sha1, b->sha1) && - a->mode == b->mode; -} - -static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) -{ - struct merge_list *res = xcalloc(1, sizeof(*res)); - - res->stage = stage; - res->path = path; - res->mode = mode; - res->blob = lookup_blob(sha1); - return res; -} - -static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) -{ - char *path = xmalloc(traverse_path_len(info, n) + 1); - return make_traverse_path(path, info, n); -} - -static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) -{ - struct merge_list *orig, *final; - const char *path; - - /* If it's already branch1, don't bother showing it */ - if (!branch1) - return; - - path = traverse_path(info, result); - orig = create_entry(2, branch1->mode, branch1->sha1, path); - final = create_entry(0, result->mode, result->sha1, path); - - final->link = orig; - - add_merge_entry(final); -} - -static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) -{ - char *newbase; - struct name_entry *p; - struct tree_desc t[3]; - void *buf0, *buf1, *buf2; - - if (!resolve_directories) - return 0; - p = n; - if (!p->mode) { - p++; - if (!p->mode) - p++; - } - if (!S_ISDIR(p->mode)) - return 0; - newbase = traverse_path(info, p); - buf0 = fill_tree_descriptor(t+0, n[0].sha1); - buf1 = fill_tree_descriptor(t+1, n[1].sha1); - buf2 = fill_tree_descriptor(t+2, n[2].sha1); - merge_trees(t, newbase); - - free(buf0); - free(buf1); - free(buf2); - free(newbase); - return 1; -} - - -static struct merge_list *link_entry(unsigned stage, const struct traverse_info *info, struct name_entry *n, struct merge_list *entry) -{ - const char *path; - struct merge_list *link; - - if (!n->mode) - return entry; - if (entry) - path = entry->path; - else - path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->sha1, path); - link->link = entry; - return link; -} - -static void unresolved(const struct traverse_info *info, struct name_entry n[3]) -{ - struct merge_list *entry = NULL; - - if (unresolved_directory(info, n)) - return; - - /* - * Do them in reverse order so that the resulting link - * list has the stages in order - link_entry adds new - * links at the front. - */ - entry = link_entry(3, info, n + 2, entry); - entry = link_entry(2, info, n + 1, entry); - entry = link_entry(1, info, n + 0, entry); - - add_merge_entry(entry); -} - -/* - * Merge two trees together (t[1] and t[2]), using a common base (t[0]) - * as the origin. - * - * This walks the (sorted) trees in lock-step, checking every possible - * name. Note that directories automatically sort differently from other - * files (see "base_name_compare"), so you'll never see file/directory - * conflicts, because they won't ever compare the same. - * - * IOW, if a directory changes to a filename, it will automatically be - * seen as the directory going away, and the filename being created. - * - * Think of this as a three-way diff. - * - * The output will be either: - * - successful merge - * "0 mode sha1 filename" - * NOTE NOTE NOTE! FIXME! We really really need to walk the index - * in parallel with this too! - * - * - conflict: - * "1 mode sha1 filename" - * "2 mode sha1 filename" - * "3 mode sha1 filename" - * where not all of the 1/2/3 lines may exist, of course. - * - * The successful merge rules are the same as for the three-way merge - * in git-read-tree. - */ -static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) -{ - /* Same in both? */ - if (same_entry(entry+1, entry+2)) { - if (entry[0].sha1) { - resolve(info, NULL, entry+1); - return mask; - } - } - - if (same_entry(entry+0, entry+1)) { - if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { - resolve(info, entry+1, entry+2); - return mask; - } - } - - if (same_entry(entry+0, entry+2)) { - if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - resolve(info, NULL, entry+1); - return mask; - } - } - - unresolved(info, entry); - return mask; -} - -static void merge_trees(struct tree_desc t[3], const char *base) -{ - struct traverse_info info; - - setup_traverse_info(&info, base); - info.fn = threeway_callback; - traverse_trees(3, t, &info); -} - -static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) -{ - unsigned char sha1[20]; - void *buf; - - if (get_sha1(rev, sha1)) - die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, sha1); - if (!buf) - die("%s is not a tree", rev); - return buf; -} - -int cmd_merge_tree(int argc, const char **argv, const char *prefix) -{ - struct tree_desc t[3]; - void *buf1, *buf2, *buf3; - - if (argc != 4) - usage(merge_tree_usage); - - buf1 = get_tree_descriptor(t+0, argv[1]); - buf2 = get_tree_descriptor(t+1, argv[2]); - buf3 = get_tree_descriptor(t+2, argv[3]); - merge_trees(t, ""); - free(buf1); - free(buf2); - free(buf3); - - show_result(); - return 0; -} diff --git a/builtin-merge.c b/builtin-merge.c deleted file mode 100644 index 3aaec7bed..000000000 --- a/builtin-merge.c +++ /dev/null @@ -1,1292 +0,0 @@ -/* - * Builtin "git merge" - * - * Copyright (c) 2008 Miklos Vajna - * - * Based on git-merge.sh by Junio C Hamano. - */ - -#include "cache.h" -#include "parse-options.h" -#include "builtin.h" -#include "run-command.h" -#include "diff.h" -#include "refs.h" -#include "commit.h" -#include "diffcore.h" -#include "revision.h" -#include "unpack-trees.h" -#include "cache-tree.h" -#include "dir.h" -#include "utf8.h" -#include "log-tree.h" -#include "color.h" -#include "rerere.h" -#include "help.h" -#include "merge-recursive.h" -#include "resolve-undo.h" - -#define DEFAULT_TWOHEAD (1<<0) -#define DEFAULT_OCTOPUS (1<<1) -#define NO_FAST_FORWARD (1<<2) -#define NO_TRIVIAL (1<<3) - -struct strategy { - const char *name; - unsigned attr; -}; - -static const char * const builtin_merge_usage[] = { - "git merge [options] ...", - "git merge [options] HEAD ", - NULL -}; - -static int show_diffstat = 1, option_log, squash; -static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only; -static int allow_trivial = 1, have_message; -static struct strbuf merge_msg; -static struct commit_list *remoteheads; -static unsigned char head[20], stash[20]; -static struct strategy **use_strategies; -static size_t use_strategies_nr, use_strategies_alloc; -static const char **xopts; -static size_t xopts_nr, xopts_alloc; -static const char *branch; -static int verbosity; -static int allow_rerere_auto; - -static struct strategy all_strategy[] = { - { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, - { "octopus", DEFAULT_OCTOPUS }, - { "resolve", 0 }, - { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, - { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, -}; - -static const char *pull_twohead, *pull_octopus; - -static int option_parse_message(const struct option *opt, - const char *arg, int unset) -{ - struct strbuf *buf = opt->value; - - if (unset) - strbuf_setlen(buf, 0); - else if (arg) { - strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg); - have_message = 1; - } else - return error("switch `m' requires a value"); - return 0; -} - -static struct strategy *get_strategy(const char *name) -{ - int i; - struct strategy *ret; - static struct cmdnames main_cmds, other_cmds; - static int loaded; - - if (!name) - return NULL; - - for (i = 0; i < ARRAY_SIZE(all_strategy); i++) - if (!strcmp(name, all_strategy[i].name)) - return &all_strategy[i]; - - if (!loaded) { - struct cmdnames not_strategies; - loaded = 1; - - memset(¬_strategies, 0, sizeof(struct cmdnames)); - load_command_list("git-merge-", &main_cmds, &other_cmds); - for (i = 0; i < main_cmds.cnt; i++) { - int j, found = 0; - struct cmdname *ent = main_cmds.names[i]; - for (j = 0; j < ARRAY_SIZE(all_strategy); j++) - if (!strncmp(ent->name, all_strategy[j].name, ent->len) - && !all_strategy[j].name[ent->len]) - found = 1; - if (!found) - add_cmdname(¬_strategies, ent->name, ent->len); - } - exclude_cmds(&main_cmds, ¬_strategies); - } - if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:"); - for (i = 0; i < main_cmds.cnt; i++) - fprintf(stderr, " %s", main_cmds.names[i]->name); - fprintf(stderr, ".\n"); - if (other_cmds.cnt) { - fprintf(stderr, "Available custom strategies are:"); - for (i = 0; i < other_cmds.cnt; i++) - fprintf(stderr, " %s", other_cmds.names[i]->name); - fprintf(stderr, ".\n"); - } - exit(1); - } - - ret = xcalloc(1, sizeof(struct strategy)); - ret->name = xstrdup(name); - return ret; -} - -static void append_strategy(struct strategy *s) -{ - ALLOC_GROW(use_strategies, use_strategies_nr + 1, use_strategies_alloc); - use_strategies[use_strategies_nr++] = s; -} - -static int option_parse_strategy(const struct option *opt, - const char *name, int unset) -{ - if (unset) - return 0; - - append_strategy(get_strategy(name)); - return 0; -} - -static int option_parse_x(const struct option *opt, - const char *arg, int unset) -{ - if (unset) - return 0; - - ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc); - xopts[xopts_nr++] = xstrdup(arg); - return 0; -} - -static int option_parse_n(const struct option *opt, - const char *arg, int unset) -{ - show_diffstat = unset; - return 0; -} - -static struct option builtin_merge_options[] = { - { OPTION_CALLBACK, 'n', NULL, NULL, NULL, - "do not show a diffstat at the end of the merge", - PARSE_OPT_NOARG, option_parse_n }, - OPT_BOOLEAN(0, "stat", &show_diffstat, - "show a diffstat at the end of the merge"), - OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), - OPT_BOOLEAN(0, "squash", &squash, - "create a single commit instead of doing a merge"), - OPT_BOOLEAN(0, "commit", &option_commit, - "perform a commit if the merge succeeds (default)"), - OPT_BOOLEAN(0, "ff", &allow_fast_forward, - "allow fast-forward (default)"), - OPT_BOOLEAN(0, "ff-only", &fast_forward_only, - "abort if fast-forward is not possible"), - OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), - OPT_CALLBACK('s', "strategy", &use_strategies, "strategy", - "merge strategy to use", option_parse_strategy), - OPT_CALLBACK('X', "strategy-option", &xopts, "option=value", - "option for selected merge strategy", option_parse_x), - OPT_CALLBACK('m', "message", &merge_msg, "message", - "message to be used for the merge commit (if any)", - option_parse_message), - OPT__VERBOSITY(&verbosity), - OPT_END() -}; - -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path("MERGE_HEAD")); - unlink(git_path("MERGE_MSG")); - unlink(git_path("MERGE_MODE")); -} - -static void save_state(void) -{ - int len; - struct child_process cp; - struct strbuf buffer = STRBUF_INIT; - const char *argv[] = {"stash", "create", NULL}; - - memset(&cp, 0, sizeof(cp)); - cp.argv = argv; - cp.out = -1; - cp.git_cmd = 1; - - if (start_command(&cp)) - die("could not run stash."); - len = strbuf_read(&buffer, cp.out, 1024); - close(cp.out); - - if (finish_command(&cp) || len < 0) - die("stash failed"); - else if (!len) - return; - strbuf_setlen(&buffer, buffer.len-1); - if (get_sha1(buffer.buf, stash)) - die("not a valid object: %s", buffer.buf); -} - -static void reset_hard(unsigned const char *sha1, int verbose) -{ - int i = 0; - const char *args[6]; - - args[i++] = "read-tree"; - if (verbose) - args[i++] = "-v"; - args[i++] = "--reset"; - args[i++] = "-u"; - args[i++] = sha1_to_hex(sha1); - args[i] = NULL; - - if (run_command_v_opt(args, RUN_GIT_CMD)) - die("read-tree failed"); -} - -static void restore_state(void) -{ - struct strbuf sb = STRBUF_INIT; - const char *args[] = { "stash", "apply", NULL, NULL }; - - if (is_null_sha1(stash)) - return; - - reset_hard(head, 1); - - args[2] = sha1_to_hex(stash); - - /* - * It is OK to ignore error here, for example when there was - * nothing to restore. - */ - run_command_v_opt(args, RUN_GIT_CMD); - - strbuf_release(&sb); - refresh_cache(REFRESH_QUIET); -} - -/* This is called when no merge was necessary. */ -static void finish_up_to_date(const char *msg) -{ - if (verbosity >= 0) - printf("%s%s\n", squash ? " (nothing to squash)" : "", msg); - drop_save(); -} - -static void squash_message(void) -{ - struct rev_info rev; - struct commit *commit; - struct strbuf out = STRBUF_INIT; - struct commit_list *j; - int fd; - struct pretty_print_context ctx = {0}; - - printf("Squash commit -- not updating HEAD\n"); - fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not write to '%s'", git_path("SQUASH_MSG")); - - init_revisions(&rev, NULL); - rev.ignore_merges = 1; - rev.commit_format = CMIT_FMT_MEDIUM; - - commit = lookup_commit(head); - commit->object.flags |= UNINTERESTING; - add_pending_object(&rev, &commit->object, NULL); - - for (j = remoteheads; j; j = j->next) - add_pending_object(&rev, &j->item->object, NULL); - - setup_revisions(0, NULL, &rev, NULL); - if (prepare_revision_walk(&rev)) - die("revision walk setup failed"); - - ctx.abbrev = rev.abbrev; - ctx.date_mode = rev.date_mode; - - strbuf_addstr(&out, "Squashed commit of the following:\n"); - while ((commit = get_revision(&rev)) != NULL) { - strbuf_addch(&out, '\n'); - strbuf_addf(&out, "commit %s\n", - sha1_to_hex(commit->object.sha1)); - pretty_print_commit(rev.commit_format, commit, &out, &ctx); - } - if (write(fd, out.buf, out.len) < 0) - die_errno("Writing SQUASH_MSG"); - if (close(fd)) - die_errno("Finishing SQUASH_MSG"); - strbuf_release(&out); -} - -static void finish(const unsigned char *new_head, const char *msg) -{ - struct strbuf reflog_message = STRBUF_INIT; - - if (!msg) - strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION")); - else { - if (verbosity >= 0) - printf("%s\n", msg); - strbuf_addf(&reflog_message, "%s: %s", - getenv("GIT_REFLOG_ACTION"), msg); - } - if (squash) { - squash_message(); - } else { - if (verbosity >= 0 && !merge_msg.len) - printf("No merge message -- not updating HEAD\n"); - else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; - update_ref(reflog_message.buf, "HEAD", - new_head, head, 0, - DIE_ON_ERR); - /* - * We ignore errors in 'gc --auto', since the - * user should see them. - */ - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); - } - } - if (new_head && show_diffstat) { - struct diff_options opts; - diff_setup(&opts); - opts.output_format |= - DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; - opts.detect_rename = DIFF_DETECT_RENAME; - if (diff_use_color_default > 0) - DIFF_OPT_SET(&opts, COLOR_DIFF); - if (diff_setup_done(&opts) < 0) - die("diff_setup_done failed"); - diff_tree_sha1(head, new_head, "", &opts); - diffcore_std(&opts); - diff_flush(&opts); - } - - /* Run a post-merge hook */ - run_hook(NULL, "post-merge", squash ? "1" : "0", NULL); - - strbuf_release(&reflog_message); -} - -/* Get the name for the merge commit's message. */ -static void merge_name(const char *remote, struct strbuf *msg) -{ - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; - struct strbuf buf = STRBUF_INIT; - struct strbuf bname = STRBUF_INIT; - const char *ptr; - char *found_ref; - int len, early; - - strbuf_branchname(&bname, remote); - remote = bname.buf; - - memset(branch_head, 0, sizeof(branch_head)); - remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); - if (!remote_head) - die("'%s' does not point to a commit", remote); - - if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { - if (!prefixcmp(found_ref, "refs/heads/")) { - strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", - sha1_to_hex(branch_head), remote); - goto cleanup; - } - if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", - sha1_to_hex(branch_head), remote); - goto cleanup; - } - } - - /* See if remote matches ^^^.. or ~ */ - for (len = 0, ptr = remote + strlen(remote); - remote < ptr && ptr[-1] == '^'; - ptr--) - len++; - if (len) - early = 1; - else { - early = 0; - ptr = strrchr(remote, '~'); - if (ptr) { - int seen_nonzero = 0; - - len++; /* count ~ */ - while (*++ptr && isdigit(*ptr)) { - seen_nonzero |= (*ptr != '0'); - len++; - } - if (*ptr) - len = 0; /* not ...~ */ - else if (seen_nonzero) - early = 1; - else if (len == 1) - early = 1; /* "name~" is "name~1"! */ - } - } - if (len) { - struct strbuf truname = STRBUF_INIT; - strbuf_addstr(&truname, "refs/heads/"); - strbuf_addstr(&truname, remote); - strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 0, NULL)) { - strbuf_addf(msg, - "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), - truname.buf + 11, - (early ? " (early part)" : "")); - strbuf_release(&truname); - goto cleanup; - } - } - - if (!strcmp(remote, "FETCH_HEAD") && - !access(git_path("FETCH_HEAD"), R_OK)) { - FILE *fp; - struct strbuf line = STRBUF_INIT; - char *ptr; - - fp = fopen(git_path("FETCH_HEAD"), "r"); - if (!fp) - die_errno("could not open '%s' for reading", - git_path("FETCH_HEAD")); - strbuf_getline(&line, fp, '\n'); - fclose(fp); - ptr = strstr(line.buf, "\tnot-for-merge\t"); - if (ptr) - strbuf_remove(&line, ptr-line.buf+1, 13); - strbuf_addbuf(msg, &line); - strbuf_release(&line); - goto cleanup; - } - strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); -cleanup: - strbuf_release(&buf); - strbuf_release(&bname); -} - -static int git_merge_config(const char *k, const char *v, void *cb) -{ - if (branch && !prefixcmp(k, "branch.") && - !prefixcmp(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { - const char **argv; - int argc; - char *buf; - - buf = xstrdup(v); - argc = split_cmdline(buf, &argv); - if (argc < 0) - die("Bad branch.%s.mergeoptions string", branch); - argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); - argc++; - parse_options(argc, argv, NULL, builtin_merge_options, - builtin_merge_usage, 0); - free(buf); - } - - if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) - show_diffstat = git_config_bool(k, v); - else if (!strcmp(k, "pull.twohead")) - return git_config_string(&pull_twohead, k, v); - else if (!strcmp(k, "pull.octopus")) - return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); - return git_diff_ui_config(k, v, cb); -} - -static int read_tree_trivial(unsigned char *common, unsigned char *head, - unsigned char *one) -{ - int i, nr_trees = 0; - struct tree *trees[MAX_UNPACK_TREES]; - struct tree_desc t[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = 2; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.trivial_merges_only = 1; - opts.merge = 1; - trees[nr_trees] = parse_tree_indirect(common); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(one); - if (!trees[nr_trees++]) - return -1; - opts.fn = threeway_merge; - cache_tree_free(&active_cache_tree); - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - return 0; -} - -static void write_tree_trivial(unsigned char *sha1) -{ - if (write_cache_as_tree(sha1, 0, NULL)) - die("git write-tree failed to write a tree"); -} - -static int try_merge_strategy(const char *strategy, struct commit_list *common, - const char *head_arg) -{ - const char **args; - int i = 0, x = 0, ret; - struct commit_list *j; - struct strbuf buf = STRBUF_INIT; - int index_fd; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - - index_fd = hold_locked_index(lock, 1); - refresh_cache(REFRESH_QUIET); - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - return error("Unable to write index."); - rollback_lock_file(lock); - - if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { - int clean; - struct commit *result; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; - struct commit_list *reversed = NULL; - struct merge_options o; - - if (remoteheads->next) { - error("Not handling anything other than two heads merge."); - return 2; - } - - init_merge_options(&o); - if (!strcmp(strategy, "subtree")) - o.subtree_shift = ""; - - for (x = 0; x < xopts_nr; x++) { - if (!strcmp(xopts[x], "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(xopts[x], "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(xopts[x], "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(xopts[x], "subtree=")) - o.subtree_shift = xopts[x]+8; - else - die("Unknown option for merge-recursive: -X%s", xopts[x]); - } - - o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; - - for (j = common; j; j = j->next) - commit_list_insert(j->item, &reversed); - - index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(&o, lookup_commit(head), - remoteheads->item, reversed, &result); - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); - rollback_lock_file(lock); - return clean ? 0 : 1; - } else { - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - resolve_undo_clear(); - return ret; - } -} - -static void count_diff_files(struct diff_queue_struct *q, - struct diff_options *opt, void *data) -{ - int *count = data; - - (*count) += q->nr; -} - -static int count_unmerged_entries(void) -{ - int i, ret = 0; - - for (i = 0; i < active_nr; i++) - if (ce_stage(active_cache[i])) - ret++; - - return ret; -} - -static int checkout_fast_forward(unsigned char *head, unsigned char *remote) -{ - struct tree *trees[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - struct tree_desc t[MAX_UNPACK_TREES]; - int i, fd, nr_trees = 0; - struct dir_struct dir; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - refresh_cache(REFRESH_QUIET); - - fd = hold_locked_index(lock_file, 1); - - memset(&trees, 0, sizeof(trees)); - memset(&opts, 0, sizeof(opts)); - memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - dir.exclude_per_dir = ".gitignore"; - opts.dir = &dir; - - opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.merge = 1; - opts.fn = twoway_merge; - opts.msgs = get_porcelain_error_msgs(); - - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(remote); - if (!trees[nr_trees++]) - return -1; - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); - return 0; -} - -static void split_merge_strategies(const char *string, struct strategy **list, - int *nr, int *alloc) -{ - char *p, *q, *buf; - - if (!string) - return; - - buf = xstrdup(string); - q = buf; - for (;;) { - p = strchr(q, ' '); - if (!p) { - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - free(buf); - return; - } else { - *p = '\0'; - ALLOC_GROW(*list, *nr + 1, *alloc); - (*list)[(*nr)++].name = xstrdup(q); - q = ++p; - } - } -} - -static void add_strategies(const char *string, unsigned attr) -{ - struct strategy *list = NULL; - int list_alloc = 0, list_nr = 0, i; - - memset(&list, 0, sizeof(list)); - split_merge_strategies(string, &list, &list_nr, &list_alloc); - if (list) { - for (i = 0; i < list_nr; i++) - append_strategy(get_strategy(list[i].name)); - return; - } - for (i = 0; i < ARRAY_SIZE(all_strategy); i++) - if (all_strategy[i].attr & attr) - append_strategy(&all_strategy[i]); - -} - -static int merge_trivial(void) -{ - unsigned char result_tree[20], result_commit[20]; - struct commit_list *parent = xmalloc(sizeof(*parent)); - - write_tree_trivial(result_tree); - printf("Wonderful.\n"); - parent->item = lookup_commit(head); - parent->next = xmalloc(sizeof(*parent->next)); - parent->next->item = remoteheads->item; - parent->next->next = NULL; - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); - finish(result_commit, "In-index merge"); - drop_save(); - return 0; -} - -static int finish_automerge(struct commit_list *common, - unsigned char *result_tree, - const char *wt_strategy) -{ - struct commit_list *parents = NULL, *j; - struct strbuf buf = STRBUF_INIT; - unsigned char result_commit[20]; - - free_commit_list(common); - if (allow_fast_forward) { - parents = remoteheads; - commit_list_insert(lookup_commit(head), &parents); - parents = reduce_heads(parents); - } else { - struct commit_list **pptr = &parents; - - pptr = &commit_list_insert(lookup_commit(head), - pptr)->next; - for (j = remoteheads; j; j = j->next) - pptr = &commit_list_insert(j->item, pptr)->next; - } - free_commit_list(remoteheads); - strbuf_addch(&merge_msg, '\n'); - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); - strbuf_addf(&buf, "Merge made by %s.", wt_strategy); - finish(result_commit, buf.buf); - strbuf_release(&buf); - drop_save(); - return 0; -} - -static int suggest_conflicts(void) -{ - FILE *fp; - int pos; - - fp = fopen(git_path("MERGE_MSG"), "a"); - if (!fp) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - fprintf(fp, "\nConflicts:\n"); - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - - if (ce_stage(ce)) { - fprintf(fp, "\t%s\n", ce->name); - while (pos + 1 < active_nr && - !strcmp(ce->name, - active_cache[pos + 1]->name)) - pos++; - } - } - fclose(fp); - rerere(allow_rerere_auto); - printf("Automatic merge failed; " - "fix conflicts and then commit the result.\n"); - return 1; -} - -static struct commit *is_old_style_invocation(int argc, const char **argv) -{ - struct commit *second_token = NULL; - if (argc > 2) { - unsigned char second_sha1[20]; - - if (get_sha1(argv[1], second_sha1)) - return NULL; - second_token = lookup_commit_reference_gently(second_sha1, 0); - if (!second_token) - die("'%s' is not a commit", argv[1]); - if (hashcmp(second_token->object.sha1, head)) - return NULL; - } - return second_token; -} - -static int evaluate_result(void) -{ - int cnt = 0; - struct rev_info rev; - - /* Check how many files differ. */ - init_revisions(&rev, ""); - setup_revisions(0, NULL, &rev, NULL); - rev.diffopt.output_format |= - DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = count_diff_files; - rev.diffopt.format_callback_data = &cnt; - run_diff_files(&rev, 0); - - /* - * Check how many unmerged entries are - * there. - */ - cnt += count_unmerged_entries(); - - return cnt; -} - -int cmd_merge(int argc, const char **argv, const char *prefix) -{ - unsigned char result_tree[20]; - struct strbuf buf = STRBUF_INIT; - const char *head_arg; - int flag, head_invalid = 0, i; - int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; - struct commit_list *common = NULL; - const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list **remotes = &remoteheads; - - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm ', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } - - resolve_undo_clear(); - /* - * Check if we are _not_ on a detached HEAD, i.e. if there is a - * current branch. - */ - branch = resolve_ref("HEAD", head, 0, &flag); - if (branch && !prefixcmp(branch, "refs/heads/")) - branch += 11; - if (is_null_sha1(head)) - head_invalid = 1; - - git_config(git_merge_config, NULL); - - /* for color.ui */ - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; - - argc = parse_options(argc, argv, prefix, builtin_merge_options, - builtin_merge_usage, 0); - if (verbosity < 0) - show_diffstat = 0; - - if (squash) { - if (!allow_fast_forward) - die("You cannot combine --squash with --no-ff."); - option_commit = 0; - } - - if (!allow_fast_forward && fast_forward_only) - die("You cannot combine --no-ff with --ff-only."); - - if (!argc) - usage_with_options(builtin_merge_usage, - builtin_merge_options); - - /* - * This could be traditional "merge HEAD ..." and - * the way we can tell it is to see if the second token is HEAD, - * but some people might have misused the interface and used a - * committish that is the same as HEAD there instead. - * Traditional format never would have "-m" so it is an - * additional safety measure to check for it. - */ - - if (!have_message && is_old_style_invocation(argc, argv)) { - strbuf_addstr(&merge_msg, argv[0]); - head_arg = argv[1]; - argv += 2; - argc -= 2; - } else if (head_invalid) { - struct object *remote_head; - /* - * If the merged head is a valid one there is no reason - * to forbid "git merge" into a branch yet to be born. - * We do the same for "git pull". - */ - if (argc != 1) - die("Can merge only exactly one commit into " - "empty head"); - if (squash) - die("Squash commit into empty head not supported yet"); - if (!allow_fast_forward) - die("Non-fast-forward commit does not make sense into " - "an empty head"); - remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); - if (!remote_head) - die("%s - not something we can merge", argv[0]); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); - return 0; - } else { - struct strbuf msg = STRBUF_INIT; - - /* We are invoked directly as the first-class UI. */ - head_arg = "HEAD"; - - /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. - */ - if (!have_message) { - for (i = 0; i < argc; i++) - merge_name(argv[i], &msg); - fmt_merge_msg(option_log, &msg, &merge_msg); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); - } - } - - if (head_invalid || !argc) - usage_with_options(builtin_merge_usage, - builtin_merge_options); - - strbuf_addstr(&buf, "merge"); - for (i = 0; i < argc; i++) - strbuf_addf(&buf, " %s", argv[i]); - setenv("GIT_REFLOG_ACTION", buf.buf, 0); - strbuf_reset(&buf); - - for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); - if (!o) - die("%s - not something we can merge", argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; - remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); - setenv(buf.buf, argv[i], 1); - strbuf_reset(&buf); - } - - if (!use_strategies) { - if (!remoteheads->next) - add_strategies(pull_twohead, DEFAULT_TWOHEAD); - else - add_strategies(pull_octopus, DEFAULT_OCTOPUS); - } - - for (i = 0; i < use_strategies_nr; i++) { - if (use_strategies[i]->attr & NO_FAST_FORWARD) - allow_fast_forward = 0; - if (use_strategies[i]->attr & NO_TRIVIAL) - allow_trivial = 0; - } - - if (!remoteheads->next) - common = get_merge_bases(lookup_commit(head), - remoteheads->item, 1); - else { - struct commit_list *list = remoteheads; - commit_list_insert(lookup_commit(head), &list); - common = get_octopus_merge_bases(list); - free(list); - } - - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0, - DIE_ON_ERR); - - if (!common) - ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads->next && !common->next && - common->item == remoteheads->item) { - /* - * If head can reach all the merge then we are up to date. - * but first the most common case of merging one remote. - */ - finish_up_to_date("Already up-to-date."); - return 0; - } else if (allow_fast_forward && !remoteheads->next && - !common->next && - !hashcmp(common->item->object.sha1, head)) { - /* Again the most common case of merging one remote. */ - struct strbuf msg = STRBUF_INIT; - struct object *o; - char hex[41]; - - strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV)); - - if (verbosity >= 0) - printf("Updating %s..%s\n", - hex, - find_unique_abbrev(remoteheads->item->object.sha1, - DEFAULT_ABBREV)); - strbuf_addstr(&msg, "Fast-forward"); - if (have_message) - strbuf_addstr(&msg, - " (no commit created; -m option ignored)"); - o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1), - 0, NULL, OBJ_COMMIT); - if (!o) - return 1; - - if (checkout_fast_forward(head, remoteheads->item->object.sha1)) - return 1; - - finish(o->sha1, msg.buf); - drop_save(); - return 0; - } else if (!remoteheads->next && common->next) - ; - /* - * We are not doing octopus and not fast-forward. Need - * a real merge. - */ - else if (!remoteheads->next && !common->next && option_commit) { - /* - * We are not doing octopus, not fast-forward, and have - * only one common. - */ - refresh_cache(REFRESH_QUIET); - if (allow_trivial && !fast_forward_only) { - /* See if it is really trivial. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); - printf("Trying really trivial in-index merge...\n"); - if (!read_tree_trivial(common->item->object.sha1, - head, remoteheads->item->object.sha1)) - return merge_trivial(); - printf("Nope.\n"); - } - } else { - /* - * An octopus. If we can reach all the remote we are up - * to date. - */ - int up_to_date = 1; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) { - struct commit_list *common_one; - - /* - * Here we *have* to calculate the individual - * merge_bases again, otherwise "git merge HEAD^ - * HEAD^^" would be missed. - */ - common_one = get_merge_bases(lookup_commit(head), - j->item, 1); - if (hashcmp(common_one->item->object.sha1, - j->item->object.sha1)) { - up_to_date = 0; - break; - } - } - if (up_to_date) { - finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; - } - } - - if (fast_forward_only) - die("Not possible to fast-forward, aborting."); - - /* We are going to make a new commit. */ - git_committer_info(IDENT_ERROR_ON_NO_NAME); - - /* - * At this point, we need a real merge. No matter what strategy - * we use, it would operate on the index, possibly affecting the - * working tree, and when resolved cleanly, have the desired - * tree in the index -- this means that the index must be in - * sync with the head commit. The strategies are responsible - * to ensure this. - */ - if (use_strategies_nr != 1) { - /* - * Stash away the local changes so that we can try more - * than one. - */ - save_state(); - } else { - memcpy(stash, null_sha1, 20); - } - - for (i = 0; i < use_strategies_nr; i++) { - int ret; - if (i) { - printf("Rewinding the tree to pristine...\n"); - restore_state(); - } - if (use_strategies_nr != 1) - printf("Trying merge strategy %s...\n", - use_strategies[i]->name); - /* - * Remember which strategy left the state in the working - * tree. - */ - wt_strategy = use_strategies[i]->name; - - ret = try_merge_strategy(use_strategies[i]->name, - common, head_arg); - if (!option_commit && !ret) { - merge_was_ok = 1; - /* - * This is necessary here just to avoid writing - * the tree, but later we will *not* exit with - * status code 1 because merge_was_ok is set. - */ - ret = 1; - } - - if (ret) { - /* - * The backend exits with 1 when conflicts are - * left to be resolved, with 2 when it does not - * handle the given merge at all. - */ - if (ret == 1) { - int cnt = evaluate_result(); - - if (best_cnt <= 0 || cnt <= best_cnt) { - best_strategy = use_strategies[i]->name; - best_cnt = cnt; - } - } - if (merge_was_ok) - break; - else - continue; - } - - /* Automerge succeeded. */ - write_tree_trivial(result_tree); - automerge_was_ok = 1; - break; - } - - /* - * If we have a resulting tree, that means the strategy module - * auto resolved the merge cleanly. - */ - if (automerge_was_ok) - return finish_automerge(common, result_tree, wt_strategy); - - /* - * Pick the result from the best strategy and have the user fix - * it up. - */ - if (!best_strategy) { - restore_state(); - if (use_strategies_nr > 1) - fprintf(stderr, - "No merge strategy handled the merge.\n"); - else - fprintf(stderr, "Merge with strategy %s failed.\n", - use_strategies[0]->name); - return 2; - } else if (best_strategy == wt_strategy) - ; /* We already have its result in the working tree. */ - else { - printf("Rewinding the tree to pristine...\n"); - restore_state(); - printf("Using the %s to prepare resolving by hand.\n", - best_strategy); - try_merge_strategy(best_strategy, common, head_arg); - } - - if (squash) - finish(NULL, NULL); - else { - int fd; - struct commit_list *j; - - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); - fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_HEAD")); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_HEAD")); - close(fd); - strbuf_addch(&merge_msg, '\n'); - fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MSG")); - if (write_in_full(fd, merge_msg.buf, merge_msg.len) != - merge_msg.len) - die_errno("Could not write to '%s'", git_path("MERGE_MSG")); - close(fd); - fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666); - if (fd < 0) - die_errno("Could not open '%s' for writing", - git_path("MERGE_MODE")); - strbuf_reset(&buf); - if (!allow_fast_forward) - strbuf_addf(&buf, "no-ff"); - if (write_in_full(fd, buf.buf, buf.len) != buf.len) - die_errno("Could not write to '%s'", git_path("MERGE_MODE")); - close(fd); - } - - if (merge_was_ok) { - fprintf(stderr, "Automatic merge went well; " - "stopped before committing as requested\n"); - return 0; - } else - return suggest_conflicts(); -} diff --git a/builtin-mktag.c b/builtin-mktag.c deleted file mode 100644 index 1cb0f3f2a..000000000 --- a/builtin-mktag.c +++ /dev/null @@ -1,179 +0,0 @@ -#include "cache.h" -#include "tag.h" -#include "exec_cmd.h" - -/* - * A signature file has a very simple fixed format: four lines - * of "object " + "type " + "tag " + - * "tagger ", followed by a blank line, a free-form tag - * message and a signature block that git itself doesn't care about, - * but that can be verified with gpg or similar. - * - * The first four lines are guaranteed to be at least 83 bytes: - * "object \n" is 48 bytes, "type tag\n" at 9 bytes is the - * shortest possible type-line, "tag .\n" at 6 bytes is the shortest - * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is - * the shortest possible tagger-line. - */ - -/* - * We refuse to tag something we can't verify. Just because. - */ -static int verify_object(const unsigned char *sha1, const char *expected_type) -{ - int ret = -1; - enum object_type type; - unsigned long size; - const unsigned char *repl; - void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); - - if (buffer) { - if (type == type_from_string(expected_type)) - ret = check_sha1_signature(repl, buffer, size, expected_type); - free(buffer); - } - return ret; -} - -#ifdef NO_C99_FORMAT -#define PD_FMT "%d" -#else -#define PD_FMT "%td" -#endif - -static int verify_tag(char *buffer, unsigned long size) -{ - int typelen; - char type[20]; - unsigned char sha1[20]; - const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb; - size_t len; - - if (size < 84) - return error("wanna fool me ? you obviously got the size wrong !"); - - buffer[size] = 0; - - /* Verify object line */ - object = buffer; - if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"", 0); - - if (get_sha1_hex(object + 7, sha1)) - return error("char%d: could not get SHA1 hash", 7); - - /* Verify type line */ - type_line = object + 48; - if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"", 47); - - /* Verify tag-line */ - tag_line = strchr(type_line, '\n'); - if (!tag_line) - return error("char" PD_FMT ": could not find next \"\\n\"", type_line - buffer); - tag_line++; - if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char" PD_FMT ": no \"tag \" found", tag_line - buffer); - - /* Get the actual type */ - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= sizeof(type)) - return error("char" PD_FMT ": type too long", type_line+5 - buffer); - - memcpy(type, type_line+5, typelen); - type[typelen] = 0; - - /* Verify that the object matches */ - if (verify_object(sha1, type)) - return error("char%d: could not verify object %s", 7, sha1_to_hex(sha1)); - - /* Verify the tag-name: we don't allow control characters or spaces in it */ - tag_line += 4; - for (;;) { - unsigned char c = *tag_line++; - if (c == '\n') - break; - if (c > ' ') - continue; - return error("char" PD_FMT ": could not verify tag name", tag_line - buffer); - } - - /* Verify the tagger line */ - tagger_line = tag_line; - - if (memcmp(tagger_line, "tagger ", 7)) - return error("char" PD_FMT ": could not find \"tagger \"", - tagger_line - buffer); - - /* - * Check for correct form for name and email - * i.e. " <" followed by "> " on _this_ line - * No angle brackets within the name or email address fields. - * No spaces within the email address field. - */ - tagger_line += 7; - if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || - strpbrk(tagger_line, "<>\n") != lb+1 || - strpbrk(lb+2, "><\n ") != rb) - return error("char" PD_FMT ": malformed tagger field", - tagger_line - buffer); - - /* Check for author name, at least one character, space is acceptable */ - if (lb == tagger_line) - return error("char" PD_FMT ": missing tagger name", - tagger_line - buffer); - - /* timestamp, 1 or more digits followed by space */ - tagger_line = rb + 2; - if (!(len = strspn(tagger_line, "0123456789"))) - return error("char" PD_FMT ": missing tag timestamp", - tagger_line - buffer); - tagger_line += len; - if (*tagger_line != ' ') - return error("char" PD_FMT ": malformed tag timestamp", - tagger_line - buffer); - tagger_line++; - - /* timezone, 5 digits [+-]hhmm, max. 1400 */ - if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && - strspn(tagger_line+1, "0123456789") == 4 && - tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char" PD_FMT ": malformed tag timezone", - tagger_line - buffer); - tagger_line += 6; - - /* Verify the blank line separating the header from the body */ - if (*tagger_line != '\n') - return error("char" PD_FMT ": trailing garbage in tag header", - tagger_line - buffer); - - /* The actual stuff afterwards we don't care about.. */ - return 0; -} - -#undef PD_FMT - -int cmd_mktag(int argc, const char **argv, const char *prefix) -{ - struct strbuf buf = STRBUF_INIT; - unsigned char result_sha1[20]; - - if (argc != 1) - usage("git mktag < signaturefile"); - - if (strbuf_read(&buf, 0, 4096) < 0) { - die_errno("could not read from stdin"); - } - - /* Verify it for some basic sanity: it needs to start with - "object \ntype\ntagger " */ - if (verify_tag(buf.buf, buf.len) < 0) - die("invalid tag signature file"); - - if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0) - die("unable to write tag file"); - - strbuf_release(&buf); - printf("%s\n", sha1_to_hex(result_sha1)); - return 0; -} diff --git a/builtin-mktree.c b/builtin-mktree.c deleted file mode 100644 index 098395fda..000000000 --- a/builtin-mktree.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * GIT - the stupid content tracker - * - * Copyright (c) Junio C Hamano, 2006, 2009 - */ -#include "builtin.h" -#include "quote.h" -#include "tree.h" -#include "parse-options.h" - -static struct treeent { - unsigned mode; - unsigned char sha1[20]; - int len; - char name[FLEX_ARRAY]; -} **entries; -static int alloc, used; - -static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) -{ - struct treeent *ent; - int len = strlen(path); - if (strchr(path, '/')) - die("path %s contains slash", path); - - if (alloc <= used) { - alloc = alloc_nr(used); - entries = xrealloc(entries, sizeof(*entries) * alloc); - } - ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); - ent->mode = mode; - ent->len = len; - hashcpy(ent->sha1, sha1); - memcpy(ent->name, path, len+1); -} - -static int ent_compare(const void *a_, const void *b_) -{ - struct treeent *a = *(struct treeent **)a_; - struct treeent *b = *(struct treeent **)b_; - return base_name_compare(a->name, a->len, a->mode, - b->name, b->len, b->mode); -} - -static void write_tree(unsigned char *sha1) -{ - struct strbuf buf; - size_t size; - int i; - - qsort(entries, used, sizeof(*entries), ent_compare); - for (size = i = 0; i < used; i++) - size += 32 + entries[i]->len; - - strbuf_init(&buf, size); - for (i = 0; i < used; i++) { - struct treeent *ent = entries[i]; - strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); - strbuf_add(&buf, ent->sha1, 20); - } - - write_sha1_file(buf.buf, buf.len, tree_type, sha1); -} - -static const char *mktree_usage[] = { - "git mktree [-z] [--missing] [--batch]", - NULL -}; - -static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing) -{ - char *ptr, *ntr; - unsigned mode; - enum object_type mode_type; /* object type derived from mode */ - enum object_type obj_type; /* object type derived from sha */ - char *path; - unsigned char sha1[20]; - - ptr = buf; - /* - * Read non-recursive ls-tree output format: - * mode SP type SP sha1 TAB name - */ - mode = strtoul(ptr, &ntr, 8); - if (ptr == ntr || !ntr || *ntr != ' ') - die("input format error: %s", buf); - ptr = ntr + 1; /* type */ - ntr = strchr(ptr, ' '); - if (!ntr || buf + len <= ntr + 40 || - ntr[41] != '\t' || - get_sha1_hex(ntr + 1, sha1)) - die("input format error: %s", buf); - - /* It is perfectly normal if we do not have a commit from a submodule */ - if (S_ISGITLINK(mode)) - allow_missing = 1; - - - *ntr++ = 0; /* now at the beginning of SHA1 */ - - path = ntr + 41; /* at the beginning of name */ - if (line_termination && path[0] == '"') { - struct strbuf p_uq = STRBUF_INIT; - if (unquote_c_style(&p_uq, path, NULL)) - die("invalid quoting"); - path = strbuf_detach(&p_uq, NULL); - } - - /* - * Object type is redundantly derivable three ways. - * These should all agree. - */ - mode_type = object_type(mode); - if (mode_type != type_from_string(ptr)) { - die("entry '%s' object type (%s) doesn't match mode type (%s)", - path, ptr, typename(mode_type)); - } - - /* Check the type of object identified by sha1 */ - obj_type = sha1_object_info(sha1, NULL); - if (obj_type < 0) { - if (allow_missing) { - ; /* no problem - missing objects are presumed to be of the right type */ - } else { - die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1)); - } - } else { - if (obj_type != mode_type) { - /* - * The object exists but is of the wrong type. - * This is a problem regardless of allow_missing - * because the new tree entry will never be correct. - */ - die("entry '%s' object %s is a %s but specified type was (%s)", - path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type)); - } - } - - append_to_tree(mode, sha1, path); -} - -int cmd_mktree(int ac, const char **av, const char *prefix) -{ - struct strbuf sb = STRBUF_INIT; - unsigned char sha1[20]; - int line_termination = '\n'; - int allow_missing = 0; - int is_batch_mode = 0; - int got_eof = 0; - - const struct option option[] = { - OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'), - OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1), - OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1), - OPT_END() - }; - - ac = parse_options(ac, av, prefix, option, mktree_usage, 0); - - while (!got_eof) { - while (1) { - if (strbuf_getline(&sb, stdin, line_termination) == EOF) { - got_eof = 1; - break; - } - if (sb.buf[0] == '\0') { - /* empty lines denote tree boundaries in batch mode */ - if (is_batch_mode) - break; - die("input format error: (blank line only valid in batch mode)"); - } - mktree_line(sb.buf, sb.len, line_termination, allow_missing); - } - if (is_batch_mode && got_eof && used < 1) { - /* - * Execution gets here if the last tree entry is terminated with a - * new-line. The final new-line has been made optional to be - * consistent with the original non-batch behaviour of mktree. - */ - ; /* skip creating an empty tree */ - } else { - write_tree(sha1); - puts(sha1_to_hex(sha1)); - fflush(stdout); - } - used=0; /* reset tree entry buffer for re-use in batch mode */ - } - strbuf_release(&sb); - exit(0); -} diff --git a/builtin-mv.c b/builtin-mv.c deleted file mode 100644 index c07f53b34..000000000 --- a/builtin-mv.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * "git mv" builtin command - * - * Copyright (C) 2006 Johannes Schindelin - */ -#include "cache.h" -#include "builtin.h" -#include "dir.h" -#include "cache-tree.h" -#include "string-list.h" -#include "parse-options.h" - -static const char * const builtin_mv_usage[] = { - "git mv [options] ... ", - NULL -}; - -static const char **copy_pathspec(const char *prefix, const char **pathspec, - int count, int base_name) -{ - int i; - const char **result = xmalloc((count + 1) * sizeof(const char *)); - memcpy(result, pathspec, count * sizeof(const char *)); - result[count] = NULL; - for (i = 0; i < count; i++) { - int length = strlen(result[i]); - int to_copy = length; - while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) - to_copy--; - if (to_copy != length || base_name) { - char *it = xmemdupz(result[i], to_copy); - result[i] = base_name ? strdup(basename(it)) : it; - } - } - return get_pathspec(prefix, result); -} - -static const char *add_slash(const char *path) -{ - int len = strlen(path); - if (path[len - 1] != '/') { - char *with_slash = xmalloc(len + 2); - memcpy(with_slash, path, len); - with_slash[len++] = '/'; - with_slash[len] = 0; - return with_slash; - } - return path; -} - -static struct lock_file lock_file; - -int cmd_mv(int argc, const char **argv, const char *prefix) -{ - int i, newfd; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; - struct option builtin_mv_options[] = { - OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), - OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), - OPT_END(), - }; - const char **source, **destination, **dest_path; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; - struct stat st; - struct string_list src_for_dst = {NULL, 0, 0, 0}; - - git_config(git_default_config, NULL); - - argc = parse_options(argc, argv, prefix, builtin_mv_options, - builtin_mv_usage, 0); - if (--argc < 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); - - newfd = hold_locked_index(&lock_file, 1); - if (read_cache() < 0) - die("index file corrupt"); - - source = copy_pathspec(prefix, argv, argc, 0); - modes = xcalloc(argc, sizeof(enum update_mode)); - dest_path = copy_pathspec(prefix, argv + argc, 1, 0); - - if (dest_path[0][0] == '\0') - /* special case: "." was normalized to "" */ - destination = copy_pathspec(dest_path[0], argv, argc, 1); - else if (!lstat(dest_path[0], &st) && - S_ISDIR(st.st_mode)) { - dest_path[0] = add_slash(dest_path[0]); - destination = copy_pathspec(dest_path[0], argv, argc, 1); - } else { - if (argc != 1) - usage_with_options(builtin_mv_usage, builtin_mv_options); - destination = dest_path; - } - - /* Checking */ - for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; - int length, src_is_dir; - const char *bad = NULL; - - if (show_only) - printf("Checking rename of '%s' to '%s'\n", src, dst); - - length = strlen(src); - if (lstat(src, &st) < 0) - bad = "bad source"; - else if (!strncmp(src, dst, length) && - (dst[length] == 0 || dst[length] == '/')) { - bad = "can not move directory into itself"; - } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) - bad = "cannot move directory over file"; - else if (src_is_dir) { - const char *src_w_slash = add_slash(src); - int len_w_slash = length + 1; - int first, last; - - modes[i] = WORKING_DIRECTORY; - - first = cache_name_pos(src_w_slash, len_w_slash); - if (first >= 0) - die ("Huh? %.*s is in index?", - len_w_slash, src_w_slash); - - first = -1 - first; - for (last = first; last < active_nr; last++) { - const char *path = active_cache[last]->name; - if (strncmp(path, src_w_slash, len_w_slash)) - break; - } - free((char *)src_w_slash); - - if (last - first < 1) - bad = "source directory is empty"; - else { - int j, dst_len; - - if (last - first > 0) { - source = xrealloc(source, - (argc + last - first) - * sizeof(char *)); - destination = xrealloc(destination, - (argc + last - first) - * sizeof(char *)); - modes = xrealloc(modes, - (argc + last - first) - * sizeof(enum update_mode)); - } - - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const char *path = - active_cache[first + j]->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, - path + length + 1); - modes[argc + j] = INDEX; - } - argc += last - first; - } - } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; - else if (lstat(dst, &st) == 0) { - bad = "destination exists"; - if (force) { - /* - * only files can overwrite each other: - * check both source and destination - */ - if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { - warning("%s; will overwrite!", bad); - bad = NULL; - } else - bad = "Cannot overwrite"; - } - } else if (string_list_has_string(&src_for_dst, dst)) - bad = "multiple sources for the same target"; - else - string_list_insert(dst, &src_for_dst); - - if (bad) { - if (ignore_errors) { - if (--argc > 0) { - memmove(source + i, source + i + 1, - (argc - i) * sizeof(char *)); - memmove(destination + i, - destination + i + 1, - (argc - i) * sizeof(char *)); - i--; - } - } else - die ("%s, source=%s, destination=%s", - bad, src, dst); - } - } - - for (i = 0; i < argc; i++) { - const char *src = source[i], *dst = destination[i]; - enum update_mode mode = modes[i]; - int pos; - if (show_only || verbose) - printf("Renaming %s to %s\n", src, dst); - if (!show_only && mode != INDEX && - rename(src, dst) < 0 && !ignore_errors) - die_errno ("renaming '%s' failed", src); - - if (mode == WORKING_DIRECTORY) - continue; - - pos = cache_name_pos(src, strlen(src)); - assert(pos >= 0); - if (!show_only) - rename_cache_entry_at(pos, dst); - } - - if (active_cache_changed) { - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(&lock_file)) - die("Unable to write new index file"); - } - - return 0; -} diff --git a/builtin-name-rev.c b/builtin-name-rev.c deleted file mode 100644 index 06a38ac8c..000000000 --- a/builtin-name-rev.c +++ /dev/null @@ -1,305 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "commit.h" -#include "tag.h" -#include "refs.h" -#include "parse-options.h" - -#define CUTOFF_DATE_SLOP 86400 /* one day */ - -typedef struct rev_name { - const char *tip_name; - int generation; - int distance; -} rev_name; - -static long cutoff = LONG_MAX; - -/* How many generations are maximally preferred over _one_ merge traversal? */ -#define MERGE_TRAVERSAL_WEIGHT 65535 - -static void name_rev(struct commit *commit, - const char *tip_name, int generation, int distance, - int deref) -{ - struct rev_name *name = (struct rev_name *)commit->util; - struct commit_list *parents; - int parent_number = 1; - - if (!commit->object.parsed) - parse_commit(commit); - - if (commit->date < cutoff) - return; - - if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; - - if (generation) - die("generation: %d, but deref?", generation); - } - - if (name == NULL) { - name = xmalloc(sizeof(rev_name)); - commit->util = name; - goto copy_data; - } else if (name->distance > distance) { -copy_data: - name->tip_name = tip_name; - name->generation = generation; - name->distance = distance; - } else - return; - - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~ */ - 1 + 2 + /* ^NN */ - 1); - - if (len > 2 && !strcmp(tip_name + len - 2, "^0")) - len -= 2; - if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); - else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); - - name_rev(parents->item, new_name, 0, - distance + MERGE_TRAVERSAL_WEIGHT, 0); - } else { - name_rev(parents->item, tip_name, generation + 1, - distance + 1, 0); - } - } -} - -struct name_ref_data { - int tags_only; - int name_only; - const char *ref_filter; -}; - -static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) -{ - struct object *o = parse_object(sha1); - struct name_ref_data *data = cb_data; - int deref = 0; - - if (data->tags_only && prefixcmp(path, "refs/tags/")) - return 0; - - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o = parse_object(t->tagged->sha1); - deref = 1; - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - - if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - - name_rev(commit, xstrdup(path), 0, 0, deref); - } - return 0; -} - -/* returns a static buffer */ -static const char *get_rev_name(const struct object *o) -{ - static char buffer[1024]; - struct rev_name *n; - struct commit *c; - - if (o->type != OBJ_COMMIT) - return NULL; - c = (struct commit *) o; - n = c->util; - if (!n) - return NULL; - - if (!n->generation) - return n->tip_name; - else { - int len = strlen(n->tip_name); - if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) - len -= 2; - snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name, - n->generation); - - return buffer; - } -} - -static void show_name(const struct object *obj, - const char *caller_name, - int always, int allow_undefined, int name_only) -{ - const char *name; - const unsigned char *sha1 = obj->sha1; - - if (!name_only) - printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1)); - name = get_rev_name(obj); - if (name) - printf("%s\n", name); - else if (allow_undefined) - printf("undefined\n"); - else if (always) - printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV)); - else - die("cannot describe '%s'", sha1_to_hex(sha1)); -} - -static char const * const name_rev_usage[] = { - "git name-rev [options] ( --all | --stdin | ... )", - NULL -}; - -static void name_rev_line(char *p, struct name_ref_data *data) -{ - int forty = 0; - char *p_start; - for (p_start = p; *p; p++) { -#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) - if (!ishex(*p)) - forty = 0; - else if (++forty == 40 && - !ishex(*(p+1))) { - unsigned char sha1[40]; - const char *name = NULL; - char c = *(p+1); - int p_len = p - p_start + 1; - - forty = 0; - - *(p+1) = 0; - if (!get_sha1(p - 39, sha1)) { - struct object *o = - lookup_object(sha1); - if (o) - name = get_rev_name(o); - } - *(p+1) = c; - - if (!name) - continue; - - if (data->name_only) - printf("%.*s%s", p_len - 40, p_start, name); - else - printf("%.*s (%s)", p_len, p_start, name); - p_start = p + 1; - } - } - - /* flush */ - if (p_start != p) - fwrite(p_start, p - p_start, 1, stdout); -} - -int cmd_name_rev(int argc, const char **argv, const char *prefix) -{ - struct object_array revs = { 0, 0, NULL }; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; - struct name_ref_data data = { 0, 0, NULL }; - struct option opts[] = { - OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), - OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"), - OPT_STRING(0, "refs", &data.ref_filter, "pattern", - "only use refs matching "), - OPT_GROUP(""), - OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), - OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), - OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), - OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), - OPT_END(), - }; - - git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (!!all + !!transform_stdin + !!argc > 1) { - error("Specify either a list, or --all, not both!"); - usage_with_options(name_rev_usage, opts); - } - if (all || transform_stdin) - cutoff = 0; - - for (; argc; argc--, argv++) { - unsigned char sha1[20]; - struct object *o; - struct commit *commit; - - if (get_sha1(*argv, sha1)) { - fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", - *argv); - continue; - } - - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", - *argv); - continue; - } - - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); - } - - if (cutoff) - cutoff = cutoff - CUTOFF_DATE_SLOP; - for_each_ref(name_ref, &data); - - if (transform_stdin) { - char buffer[2048]; - - while (!feof(stdin)) { - char *p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - name_rev_line(p, &data); - } - } else if (all) { - int i, max; - - max = get_max_object_index(); - for (i = 0; i < max; i++) { - struct object *obj = get_indexed_object(i); - if (!obj) - continue; - show_name(obj, NULL, - always, allow_undefined, data.name_only); - } - } else { - int i; - for (i = 0; i < revs.nr; i++) - show_name(revs.objects[i].item, revs.objects[i].name, - always, allow_undefined, data.name_only); - } - - return 0; -} diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c deleted file mode 100644 index e1d3adf40..000000000 --- a/builtin-pack-objects.c +++ /dev/null @@ -1,2375 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "attr.h" -#include "object.h" -#include "blob.h" -#include "commit.h" -#include "tag.h" -#include "tree.h" -#include "delta.h" -#include "pack.h" -#include "pack-revindex.h" -#include "csum-file.h" -#include "tree-walk.h" -#include "diff.h" -#include "revision.h" -#include "list-objects.h" -#include "progress.h" -#include "refs.h" - -#ifndef NO_PTHREADS -#include "thread-utils.h" -#include -#endif - -static const char pack_usage[] = - "git pack-objects [{ -q | --progress | --all-progress }]\n" - " [--all-progress-implied]\n" - " [--max-pack-size=N] [--local] [--incremental]\n" - " [--window=N] [--window-memory=N] [--depth=N]\n" - " [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset]\n" - " [--threads=N] [--non-empty] [--revs [--unpacked | --all]*]\n" - " [--reflog] [--stdout | base-name] [--include-tag]\n" - " [--keep-unreachable | --unpack-unreachable \n" - " [idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size); - if (!base_buf) - die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1)); - delta_buf = diff_delta(base_buf, base_size, - buf, size, &delta_size, 0); - if (!delta_buf || delta_size != entry->delta_size) - die("delta size changed"); - free(buf); - free(base_buf); - return delta_buf; -} - -static unsigned long do_compress(void **pptr, unsigned long size) -{ - z_stream stream; - void *in, *out; - unsigned long maxsize; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, pack_compression_level); - maxsize = deflateBound(&stream, size); - - in = *pptr; - out = xmalloc(maxsize); - *pptr = out; - - stream.next_in = in; - stream.avail_in = size; - stream.next_out = out; - stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK) - ; /* nothing */ - deflateEnd(&stream); - - free(in); - 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. - */ -static int check_pack_inflate(struct packed_git *p, - struct pack_window **w_curs, - off_t offset, - off_t len, - unsigned long expect) -{ - z_stream stream; - unsigned char fakebuf[4096], *in; - int st; - - memset(&stream, 0, sizeof(stream)); - git_inflate_init(&stream); - do { - in = use_pack(p, w_curs, offset, &stream.avail_in); - stream.next_in = in; - stream.next_out = fakebuf; - stream.avail_out = sizeof(fakebuf); - st = git_inflate(&stream, Z_FINISH); - offset += stream.next_in - in; - } while (st == Z_OK || st == Z_BUF_ERROR); - git_inflate_end(&stream); - return (st == Z_STREAM_END && - stream.total_out == expect && - stream.total_in == len) ? 0 : -1; -} - -static void copy_pack_data(struct sha1file *f, - struct packed_git *p, - struct pack_window **w_curs, - off_t offset, - off_t len) -{ - unsigned char *in; - unsigned int avail; - - while (len) { - in = use_pack(p, w_curs, offset, &avail); - if (avail > len) - avail = (unsigned int)len; - sha1write(f, in, avail); - offset += avail; - len -= avail; - } -} - -static unsigned long write_object(struct sha1file *f, - struct object_entry *entry, - off_t write_offset) -{ - unsigned long size, limit, datalen; - void *buf; - unsigned char header[10], dheader[10]; - unsigned hdrlen; - enum object_type type; - int usable_delta, to_reuse; - - if (!pack_to_stdout) - crc32_begin(f); - - type = entry->type; - - /* apply size limit if limited packsize and not first object */ - if (!pack_size_limit || !nr_written) - limit = 0; - else if (pack_size_limit <= write_offset) - /* - * the earlier object did not fit the limit; avoid - * mistaking this with unlimited (i.e. limit = 0). - */ - limit = 1; - else - limit = pack_size_limit - write_offset; - - if (!entry->delta) - usable_delta = 0; /* no delta */ - else if (!pack_size_limit) - usable_delta = 1; /* unlimited packfile */ - else if (entry->delta->idx.offset == (off_t)-1) - usable_delta = 0; /* base was written to another pack */ - else if (entry->delta->idx.offset) - usable_delta = 1; /* base already exists in this pack */ - else - usable_delta = 0; /* base could end up in another pack */ - - if (!reuse_object) - to_reuse = 0; /* explicit */ - else if (!entry->in_pack) - to_reuse = 0; /* can't reuse what we don't have */ - else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) - /* check_object() decided it for us ... */ - to_reuse = usable_delta; - /* ... but pack split may override that */ - else if (type != entry->in_pack_type) - to_reuse = 0; /* pack has delta which is unusable */ - else if (entry->delta) - to_reuse = 0; /* we want to pack afresh */ - else - to_reuse = 1; /* we have it in-pack undeltified, - * and we do not need to deltify it. - */ - - if (!to_reuse) { - no_reuse: - if (!usable_delta) { - buf = read_sha1_file(entry->idx.sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - /* - * make sure no cached delta data remains from a - * previous attempt before a pack split occurred. - */ - free(entry->delta_data); - entry->delta_data = NULL; - entry->z_delta_size = 0; - } else if (entry->delta_data) { - size = entry->delta_size; - buf = entry->delta_data; - entry->delta_data = NULL; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } else { - buf = get_delta(entry); - size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } - - if (entry->z_delta_size) - datalen = entry->z_delta_size; - else - datalen = do_compress(&buf, size); - - /* - * The object header is a byte of 'type' followed by zero or - * more bytes of length. - */ - hdrlen = encode_header(type, size, header); - - if (type == OBJ_OFS_DELTA) { - /* - * Deltas with relative base contain an additional - * encoding of the relative offset for the delta - * base from this object's position in the pack. - */ - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - } else if (type == OBJ_REF_DELTA) { - /* - * Deltas with a base reference contain - * an additional 20 bytes for the base sha1. - */ - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - free(buf); - return 0; - } - sha1write(f, header, hdrlen); - } - sha1write(f, buf, datalen); - free(buf); - } - else { - struct packed_git *p = entry->in_pack; - struct pack_window *w_curs = NULL; - struct revindex_entry *revidx; - off_t offset; - - if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - hdrlen = encode_header(type, entry->size, header); - - offset = entry->in_pack_offset; - revidx = find_pack_revindex(p, offset); - datalen = revidx[1].offset - offset; - if (!pack_to_stdout && p->index_version > 1 && - check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { - error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } - - offset += entry->in_pack_header_size; - datalen -= entry->in_pack_header_size; - if (!pack_to_stdout && p->index_version == 1 && - check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { - error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); - unuse_pack(&w_curs); - goto no_reuse; - } - - if (type == OBJ_OFS_DELTA) { - off_t ofs = entry->idx.offset - entry->delta->idx.offset; - unsigned pos = sizeof(dheader) - 1; - dheader[pos] = ofs & 127; - while (ofs >>= 7) - dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); - hdrlen += sizeof(dheader) - pos; - reused_delta++; - } else if (type == OBJ_REF_DELTA) { - if (limit && hdrlen + 20 + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); - hdrlen += 20; - reused_delta++; - } else { - if (limit && hdrlen + datalen + 20 >= limit) { - unuse_pack(&w_curs); - return 0; - } - sha1write(f, header, hdrlen); - } - copy_pack_data(f, p, &w_curs, offset, datalen); - unuse_pack(&w_curs); - reused++; - } - if (usable_delta) - written_delta++; - written++; - if (!pack_to_stdout) - entry->idx.crc32 = crc32_end(f); - return hdrlen + datalen; -} - -static int write_one(struct sha1file *f, - struct object_entry *e, - off_t *offset) -{ - unsigned long size; - - /* offset is non zero if object is written already. */ - if (e->idx.offset || e->preferred_base) - return -1; - - /* if we are deltified, write out base object first. */ - if (e->delta && !write_one(f, e->delta, offset)) - return 0; - - e->idx.offset = *offset; - size = write_object(f, e, *offset); - if (!size) { - e->idx.offset = 0; - return 0; - } - written_list[nr_written++] = &e->idx; - - /* make sure off_t is sufficiently large not to wrap */ - if (*offset > *offset + size) - die("pack too large for current definition of off_t"); - *offset += size; - 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; - struct sha1file *f; - off_t offset; - struct pack_header hdr; - uint32_t nr_remaining = nr_result; - time_t last_mtime = 0; - - if (progress > pack_to_stdout) - progress_state = start_progress("Writing objects", nr_result); - written_list = xmalloc(nr_objects * sizeof(*written_list)); - - do { - unsigned char sha1[20]; - char *pack_tmp_name = NULL; - - if (pack_to_stdout) { - f = sha1fd_throughput(1, "", progress_state); - } else { - char tmpname[PATH_MAX]; - int fd; - fd = odb_mkstemp(tmpname, sizeof(tmpname), - "pack/tmp_pack_XXXXXX"); - pack_tmp_name = xstrdup(tmpname); - f = sha1fd(fd, pack_tmp_name); - } - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_remaining); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); - nr_written = 0; - for (; i < nr_objects; i++) { - if (!write_one(f, objects + i, &offset)) - break; - display_progress(progress_state, written); - } - - /* - * Did we write the wrong # entries in the header? - * If so, rewrite it like in fast-import - */ - if (pack_to_stdout) { - sha1close(f, sha1, CSUM_CLOSE); - } else if (nr_written == nr_remaining) { - sha1close(f, sha1, CSUM_FSYNC); - } else { - int fd = sha1close(f, sha1, 0); - fixup_pack_header_footer(fd, sha1, pack_tmp_name, - nr_written, sha1, offset); - close(fd); - } - - 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)) - die_errno("unable to make temporary pack file readable"); - if (rename(pack_tmp_name, tmpname)) - die_errno("unable to rename temporary pack file"); - - /* - * Packs are runtime accessed in their mtime - * order since newer packs are more likely to contain - * younger objects. So if we are creating multiple - * packs then we should modify the mtime of later ones - * to preserve this property. - */ - if (stat(tmpname, &st) < 0) { - warning("failed to stat %s: %s", - tmpname, strerror(errno)); - } else if (!last_mtime) { - last_mtime = st.st_mtime; - } else { - struct utimbuf utb; - utb.actime = st.st_atime; - utb.modtime = --last_mtime; - if (utime(tmpname, &utb) < 0) - warning("failed utime() on %s: %s", - tmpname, strerror(errno)); - } - - snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", - base_name, sha1_to_hex(sha1)); - if (adjust_perm(idx_tmp_name, mode)) - die_errno("unable to make temporary index file readable"); - if (rename(idx_tmp_name, tmpname)) - die_errno("unable to rename temporary index file"); - - free((void *) idx_tmp_name); - free(pack_tmp_name); - puts(sha1_to_hex(sha1)); - } - - /* mark written objects as written to previous pack */ - for (j = 0; j < nr_written; j++) { - written_list[j]->offset = (off_t)-1; - } - nr_remaining -= nr_written; - } while (nr_remaining && i < nr_objects); - - free(written_list); - stop_progress(&progress_state); - if (written != nr_result) - die("wrote %"PRIu32" objects while expecting %"PRIu32, - written, nr_result); -} - -static int locate_object_entry_hash(const unsigned char *sha1) -{ - int i; - unsigned int ui; - memcpy(&ui, sha1, sizeof(unsigned int)); - i = ui % object_ix_hashsz; - while (0 < object_ix[i]) { - if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1)) - return i; - if (++i == object_ix_hashsz) - i = 0; - } - return -1 - i; -} - -static struct object_entry *locate_object_entry(const unsigned char *sha1) -{ - int i; - - if (!object_ix_hashsz) - return NULL; - - i = locate_object_entry_hash(sha1); - if (0 <= i) - return &objects[object_ix[i]-1]; - return NULL; -} - -static void rehash_objects(void) -{ - uint32_t i; - struct object_entry *oe; - - object_ix_hashsz = nr_objects * 3; - if (object_ix_hashsz < 1024) - object_ix_hashsz = 1024; - object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz); - memset(object_ix, 0, sizeof(int) * object_ix_hashsz); - for (i = 0, oe = objects; i < nr_objects; i++, oe++) { - int ix = locate_object_entry_hash(oe->idx.sha1); - if (0 <= ix) - continue; - ix = -1 - ix; - object_ix[ix] = i + 1; - } -} - -static unsigned name_hash(const char *name) -{ - unsigned c, hash = 0; - - if (!name) - return 0; - - /* - * This effectively just creates a sortable number from the - * last sixteen non-whitespace characters. Last characters - * count "most", so things that end in ".c" sort together. - */ - while ((c = *name++) != 0) { - if (isspace(c)) - continue; - hash = (hash >> 2) + (c << 24); - } - return hash; -} - -static void setup_delta_attr_check(struct git_attr_check *check) -{ - static struct git_attr *attr_delta; - - if (!attr_delta) - attr_delta = git_attr("delta"); - - check[0].attr = attr_delta; -} - -static int no_try_delta(const char *path) -{ - struct git_attr_check check[1]; - - setup_delta_attr_check(check); - if (git_checkattr(path, ARRAY_SIZE(check), check)) - return 0; - if (ATTR_FALSE(check->value)) - return 1; - return 0; -} - -static int add_object_entry(const unsigned char *sha1, enum object_type type, - const char *name, int exclude) -{ - struct object_entry *entry; - struct packed_git *p, *found_pack = NULL; - off_t found_offset = 0; - int ix; - unsigned hash = name_hash(name); - - ix = nr_objects ? locate_object_entry_hash(sha1) : -1; - if (ix >= 0) { - if (exclude) { - entry = objects + object_ix[ix] - 1; - if (!entry->preferred_base) - nr_result--; - entry->preferred_base = 1; - } - return 0; - } - - if (!exclude && local && has_loose_object_nonlocal(sha1)) - return 0; - - for (p = packed_git; p; p = p->next) { - off_t offset = find_pack_entry_one(sha1, p); - if (offset) { - if (!found_pack) { - found_offset = offset; - found_pack = p; - } - if (exclude) - break; - if (incremental) - return 0; - if (local && !p->pack_local) - return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) - return 0; - } - } - - if (nr_objects >= nr_alloc) { - nr_alloc = (nr_alloc + 1024) * 3 / 2; - objects = xrealloc(objects, nr_alloc * sizeof(*entry)); - } - - entry = objects + nr_objects++; - memset(entry, 0, sizeof(*entry)); - hashcpy(entry->idx.sha1, sha1); - entry->hash = hash; - if (type) - entry->type = type; - if (exclude) - entry->preferred_base = 1; - else - nr_result++; - if (found_pack) { - entry->in_pack = found_pack; - entry->in_pack_offset = found_offset; - } - - if (object_ix_hashsz * 3 <= nr_objects * 4) - rehash_objects(); - else - object_ix[-1 - ix] = nr_objects; - - display_progress(progress_state, nr_objects); - - if (name && no_try_delta(name)) - entry->no_try_delta = 1; - - return 1; -} - -struct pbase_tree_cache { - unsigned char sha1[20]; - int ref; - int temporary; - void *tree_data; - unsigned long tree_size; -}; - -static struct pbase_tree_cache *(pbase_tree_cache[256]); -static int pbase_tree_cache_ix(const unsigned char *sha1) -{ - return sha1[0] % ARRAY_SIZE(pbase_tree_cache); -} -static int pbase_tree_cache_ix_incr(int ix) -{ - return (ix+1) % ARRAY_SIZE(pbase_tree_cache); -} - -static struct pbase_tree { - struct pbase_tree *next; - /* This is a phony "cache" entry; we are not - * going to evict it nor find it through _get() - * mechanism -- this is for the toplevel node that - * would almost always change with any commit. - */ - struct pbase_tree_cache pcache; -} *pbase_tree; - -static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) -{ - struct pbase_tree_cache *ent, *nent; - void *data; - unsigned long size; - enum object_type type; - int neigh; - int my_ix = pbase_tree_cache_ix(sha1); - int available_ix = -1; - - /* pbase-tree-cache acts as a limited hashtable. - * your object will be found at your index or within a few - * slots after that slot if it is cached. - */ - for (neigh = 0; neigh < 8; neigh++) { - ent = pbase_tree_cache[my_ix]; - if (ent && !hashcmp(ent->sha1, sha1)) { - ent->ref++; - return ent; - } - else if (((available_ix < 0) && (!ent || !ent->ref)) || - ((0 <= available_ix) && - (!ent && pbase_tree_cache[available_ix]))) - available_ix = my_ix; - if (!ent) - break; - my_ix = pbase_tree_cache_ix_incr(my_ix); - } - - /* Did not find one. Either we got a bogus request or - * we need to read and perhaps cache. - */ - data = read_sha1_file(sha1, &type, &size); - if (!data) - return NULL; - if (type != OBJ_TREE) { - free(data); - return NULL; - } - - /* We need to either cache or return a throwaway copy */ - - if (available_ix < 0) - ent = NULL; - else { - ent = pbase_tree_cache[available_ix]; - my_ix = available_ix; - } - - if (!ent) { - nent = xmalloc(sizeof(*nent)); - nent->temporary = (available_ix < 0); - } - else { - /* evict and reuse */ - free(ent->tree_data); - nent = ent; - } - hashcpy(nent->sha1, sha1); - nent->tree_data = data; - nent->tree_size = size; - nent->ref = 1; - if (!nent->temporary) - pbase_tree_cache[my_ix] = nent; - return nent; -} - -static void pbase_tree_put(struct pbase_tree_cache *cache) -{ - if (!cache->temporary) { - cache->ref--; - return; - } - free(cache->tree_data); - free(cache); -} - -static int name_cmp_len(const char *name) -{ - int i; - for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++) - ; - return i; -} - -static void add_pbase_object(struct tree_desc *tree, - const char *name, - int cmplen, - const char *fullname) -{ - struct name_entry entry; - int cmp; - - while (tree_entry(tree,&entry)) { - if (S_ISGITLINK(entry.mode)) - continue; - cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 : - memcmp(name, entry.path, cmplen); - if (cmp > 0) - continue; - if (cmp < 0) - return; - if (name[cmplen] != '/') { - add_object_entry(entry.sha1, - object_type(entry.mode), - fullname, 1); - return; - } - if (S_ISDIR(entry.mode)) { - struct tree_desc sub; - struct pbase_tree_cache *tree; - const char *down = name+cmplen+1; - int downlen = name_cmp_len(down); - - tree = pbase_tree_get(entry.sha1); - if (!tree) - return; - init_tree_desc(&sub, tree->tree_data, tree->tree_size); - - add_pbase_object(&sub, down, downlen, fullname); - pbase_tree_put(tree); - } - } -} - -static unsigned *done_pbase_paths; -static int done_pbase_paths_num; -static int done_pbase_paths_alloc; -static int done_pbase_path_pos(unsigned hash) -{ - int lo = 0; - int hi = done_pbase_paths_num; - while (lo < hi) { - int mi = (hi + lo) / 2; - if (done_pbase_paths[mi] == hash) - return mi; - if (done_pbase_paths[mi] < hash) - hi = mi; - else - lo = mi + 1; - } - return -lo-1; -} - -static int check_pbase_path(unsigned hash) -{ - int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); - if (0 <= pos) - return 1; - pos = -pos - 1; - if (done_pbase_paths_alloc <= done_pbase_paths_num) { - done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc); - done_pbase_paths = xrealloc(done_pbase_paths, - done_pbase_paths_alloc * - sizeof(unsigned)); - } - done_pbase_paths_num++; - if (pos < done_pbase_paths_num) - memmove(done_pbase_paths + pos + 1, - done_pbase_paths + pos, - (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); - done_pbase_paths[pos] = hash; - return 0; -} - -static void add_preferred_base_object(const char *name) -{ - struct pbase_tree *it; - int cmplen; - unsigned hash = name_hash(name); - - if (!num_preferred_base || check_pbase_path(hash)) - return; - - cmplen = name_cmp_len(name); - for (it = pbase_tree; it; it = it->next) { - if (cmplen == 0) { - add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1); - } - else { - struct tree_desc tree; - init_tree_desc(&tree, it->pcache.tree_data, it->pcache.tree_size); - add_pbase_object(&tree, name, cmplen, name); - } - } -} - -static void add_preferred_base(unsigned char *sha1) -{ - struct pbase_tree *it; - void *data; - unsigned long size; - unsigned char tree_sha1[20]; - - if (window <= num_preferred_base++) - return; - - data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); - if (!data) - return; - - for (it = pbase_tree; it; it = it->next) { - if (!hashcmp(it->pcache.sha1, tree_sha1)) { - free(data); - return; - } - } - - it = xcalloc(1, sizeof(*it)); - it->next = pbase_tree; - pbase_tree = it; - - hashcpy(it->pcache.sha1, tree_sha1); - it->pcache.tree_data = data; - it->pcache.tree_size = size; -} - -static void cleanup_preferred_base(void) -{ - struct pbase_tree *it; - unsigned i; - - it = pbase_tree; - pbase_tree = NULL; - while (it) { - struct pbase_tree *this = it; - it = this->next; - free(this->pcache.tree_data); - free(this); - } - - for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { - if (!pbase_tree_cache[i]) - continue; - free(pbase_tree_cache[i]->tree_data); - free(pbase_tree_cache[i]); - pbase_tree_cache[i] = NULL; - } - - free(done_pbase_paths); - done_pbase_paths = NULL; - done_pbase_paths_num = done_pbase_paths_alloc = 0; -} - -static void check_object(struct object_entry *entry) -{ - if (entry->in_pack) { - struct packed_git *p = entry->in_pack; - struct pack_window *w_curs = NULL; - const unsigned char *base_ref = NULL; - struct object_entry *base_entry; - unsigned long used, used_0; - unsigned int avail; - off_t ofs; - unsigned char *buf, c; - - buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail); - - /* - * We want in_pack_type even if we do not reuse delta - * since non-delta representations could still be reused. - */ - used = unpack_object_header_buffer(buf, avail, - &entry->in_pack_type, - &entry->size); - if (used == 0) - goto give_up; - - /* - * Determine if this is a delta and if so whether we can - * reuse it or not. Otherwise let's find out as cheaply as - * possible what the actual type and size for this object is. - */ - switch (entry->in_pack_type) { - default: - /* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; - entry->in_pack_header_size = used; - if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB) - goto give_up; - unuse_pack(&w_curs); - return; - case OBJ_REF_DELTA: - if (reuse_delta && !entry->preferred_base) - base_ref = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); - entry->in_pack_header_size = used + 20; - break; - case OBJ_OFS_DELTA: - buf = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); - used_0 = 0; - c = buf[used_0++]; - ofs = c & 127; - while (c & 128) { - ofs += 1; - if (!ofs || MSB(ofs, 7)) { - error("delta base offset overflow in pack for %s", - sha1_to_hex(entry->idx.sha1)); - goto give_up; - } - c = buf[used_0++]; - ofs = (ofs << 7) + (c & 127); - } - ofs = entry->in_pack_offset - ofs; - if (ofs <= 0 || ofs >= entry->in_pack_offset) { - error("delta base offset out of bound for %s", - sha1_to_hex(entry->idx.sha1)); - goto give_up; - } - if (reuse_delta && !entry->preferred_base) { - struct revindex_entry *revidx; - revidx = find_pack_revindex(p, ofs); - if (!revidx) - goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); - } - entry->in_pack_header_size = used + used_0; - break; - } - - if (base_ref && (base_entry = locate_object_entry(base_ref))) { - /* - * If base_ref was set above that means we wish to - * reuse delta data, and we even found that base - * in the list of objects we want to pack. Goodie! - * - * Depth value does not matter - find_deltas() will - * never consider reused delta as the base object to - * deltify other objects against, in order to avoid - * circular deltas. - */ - entry->type = entry->in_pack_type; - entry->delta = base_entry; - entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; - unuse_pack(&w_curs); - return; - } - - if (entry->type) { - /* - * This must be a delta and we already know what the - * final object type is. Let's extract the actual - * object size from the delta header. - */ - entry->size = get_size_from_delta(p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); - if (entry->size == 0) - goto give_up; - unuse_pack(&w_curs); - return; - } - - /* - * No choice but to fall back to the recursive delta walk - * with sha1_object_info() to find about the object type - * at this point... - */ - give_up: - unuse_pack(&w_curs); - } - - entry->type = sha1_object_info(entry->idx.sha1, &entry->size); - /* - * The error condition is checked in prepare_pack(). This is - * to permit a missing preferred base object to be ignored - * as a preferred base. Doing so can result in a larger - * pack file, but the transfer will still take place. - */ -} - -static int pack_offset_sort(const void *_a, const void *_b) -{ - const struct object_entry *a = *(struct object_entry **)_a; - const struct object_entry *b = *(struct object_entry **)_b; - - /* avoid filesystem trashing with loose objects */ - if (!a->in_pack && !b->in_pack) - return hashcmp(a->idx.sha1, b->idx.sha1); - - if (a->in_pack < b->in_pack) - return -1; - if (a->in_pack > b->in_pack) - return 1; - return a->in_pack_offset < b->in_pack_offset ? -1 : - (a->in_pack_offset > b->in_pack_offset); -} - -static void get_object_details(void) -{ - uint32_t i; - struct object_entry **sorted_by_offset; - - sorted_by_offset = xcalloc(nr_objects, sizeof(struct object_entry *)); - for (i = 0; i < nr_objects; i++) - sorted_by_offset[i] = objects + i; - qsort(sorted_by_offset, nr_objects, sizeof(*sorted_by_offset), pack_offset_sort); - - for (i = 0; i < nr_objects; i++) - check_object(sorted_by_offset[i]); - - free(sorted_by_offset); -} - -/* - * We search for deltas in a list sorted by type, by filename hash, and then - * by size, so that we see progressively smaller and smaller files. - * That's because we prefer deltas to be from the bigger file - * to the smaller -- deletes are potentially cheaper, but perhaps - * more importantly, the bigger file is likely the more recent - * one. The deepest deltas are therefore the oldest objects which are - * less susceptible to be accessed often. - */ -static int type_size_sort(const void *_a, const void *_b) -{ - const struct object_entry *a = *(struct object_entry **)_a; - const struct object_entry *b = *(struct object_entry **)_b; - - if (a->type > b->type) - return -1; - if (a->type < b->type) - return 1; - if (a->hash > b->hash) - return -1; - if (a->hash < b->hash) - return 1; - if (a->preferred_base > b->preferred_base) - return -1; - if (a->preferred_base < b->preferred_base) - return 1; - if (a->size > b->size) - return -1; - if (a->size < b->size) - return 1; - return a < b ? -1 : (a > b); /* newest first */ -} - -struct unpacked { - struct object_entry *entry; - void *data; - struct delta_index *index; - unsigned depth; -}; - -static int delta_cacheable(unsigned long src_size, unsigned long trg_size, - unsigned long delta_size) -{ - if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) - return 0; - - if (delta_size < cache_max_small_delta_size) - return 1; - - /* cache delta, if objects are large enough compared to delta size */ - if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) - return 1; - - return 0; -} - -#ifndef NO_PTHREADS - -static pthread_mutex_t read_mutex; -#define read_lock() pthread_mutex_lock(&read_mutex) -#define read_unlock() pthread_mutex_unlock(&read_mutex) - -static pthread_mutex_t cache_mutex; -#define cache_lock() pthread_mutex_lock(&cache_mutex) -#define cache_unlock() pthread_mutex_unlock(&cache_mutex) - -static pthread_mutex_t progress_mutex; -#define progress_lock() pthread_mutex_lock(&progress_mutex) -#define progress_unlock() pthread_mutex_unlock(&progress_mutex) - -#else - -#define read_lock() (void)0 -#define read_unlock() (void)0 -#define cache_lock() (void)0 -#define cache_unlock() (void)0 -#define progress_lock() (void)0 -#define progress_unlock() (void)0 - -#endif - -static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth, unsigned long *mem_usage) -{ - struct object_entry *trg_entry = trg->entry; - struct object_entry *src_entry = src->entry; - unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz; - unsigned ref_depth; - enum object_type type; - void *delta_buf; - - /* Don't bother doing diffs between different types */ - if (trg_entry->type != src_entry->type) - return -1; - - /* - * We do not bother to try a delta that we discarded - * on an earlier try, but only when reusing delta data. - */ - if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && - trg_entry->in_pack_type != OBJ_REF_DELTA && - trg_entry->in_pack_type != OBJ_OFS_DELTA) - return 0; - - /* Let's not bust the allowed depth. */ - if (src->depth >= max_depth) - return 0; - - /* Now some size filtering heuristics. */ - trg_size = trg_entry->size; - if (!trg_entry->delta) { - max_size = trg_size/2 - 20; - ref_depth = 1; - } else { - max_size = trg_entry->delta_size; - ref_depth = trg->depth; - } - max_size = (uint64_t)max_size * (max_depth - src->depth) / - (max_depth - ref_depth + 1); - if (max_size == 0) - return 0; - src_size = src_entry->size; - sizediff = src_size < trg_size ? trg_size - src_size : 0; - if (sizediff >= max_size) - return 0; - if (trg_size < src_size / 32) - return 0; - - /* Load data if not already done */ - if (!trg->data) { - read_lock(); - trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz); - read_unlock(); - if (!trg->data) - die("object %s cannot be read", - sha1_to_hex(trg_entry->idx.sha1)); - if (sz != trg_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(trg_entry->idx.sha1), sz, trg_size); - *mem_usage += sz; - } - if (!src->data) { - read_lock(); - src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); - read_unlock(); - if (!src->data) - die("object %s cannot be read", - sha1_to_hex(src_entry->idx.sha1)); - if (sz != src_size) - die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(src_entry->idx.sha1), sz, src_size); - *mem_usage += sz; - } - if (!src->index) { - src->index = create_delta_index(src->data, src_size); - if (!src->index) { - static int warned = 0; - if (!warned++) - warning("suboptimal pack - out of memory"); - return 0; - } - *mem_usage += sizeof_delta_index(src->index); - } - - delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); - if (!delta_buf) - return 0; - - if (trg_entry->delta) { - /* Prefer only shallower same-sized deltas. */ - if (delta_size == trg_entry->delta_size && - src->depth + 1 >= trg->depth) { - free(delta_buf); - return 0; - } - } - - /* - * Handle memory allocation outside of the cache - * accounting lock. Compiler will optimize the strangeness - * away when NO_PTHREADS is defined. - */ - free(trg_entry->delta_data); - cache_lock(); - if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; - trg_entry->delta_data = NULL; - } - if (delta_cacheable(src_size, trg_size, delta_size)) { - delta_cache_size += delta_size; - cache_unlock(); - trg_entry->delta_data = xrealloc(delta_buf, delta_size); - } else { - cache_unlock(); - free(delta_buf); - } - - trg_entry->delta = src_entry; - trg_entry->delta_size = delta_size; - trg->depth = src->depth + 1; - - return 1; -} - -static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) -{ - struct object_entry *child = me->delta_child; - unsigned int m = n; - while (child) { - unsigned int c = check_delta_limit(child, n + 1); - if (m < c) - m = c; - child = child->delta_sibling; - } - return m; -} - -static unsigned long free_unpacked(struct unpacked *n) -{ - unsigned long freed_mem = sizeof_delta_index(n->index); - free_delta_index(n->index); - n->index = NULL; - if (n->data) { - freed_mem += n->entry->size; - free(n->data); - n->data = NULL; - } - n->entry = NULL; - n->depth = 0; - return freed_mem; -} - -static void find_deltas(struct object_entry **list, unsigned *list_size, - int window, int depth, unsigned *processed) -{ - uint32_t i, idx = 0, count = 0; - struct unpacked *array; - unsigned long mem_usage = 0; - - array = xcalloc(window, sizeof(struct unpacked)); - - for (;;) { - struct object_entry *entry; - struct unpacked *n = array + idx; - int j, max_depth, best_base = -1; - - progress_lock(); - if (!*list_size) { - progress_unlock(); - break; - } - entry = *list++; - (*list_size)--; - if (!entry->preferred_base) { - (*processed)++; - display_progress(progress_state, *processed); - } - progress_unlock(); - - mem_usage -= free_unpacked(n); - n->entry = entry; - - while (window_memory_limit && - mem_usage > window_memory_limit && - count > 1) { - uint32_t tail = (idx + window - count) % window; - mem_usage -= free_unpacked(array + tail); - count--; - } - - /* We do not compute delta to *create* objects we are not - * going to pack. - */ - if (entry->preferred_base) - goto next; - - /* - * If the current object is at pack edge, take the depth the - * objects that depend on the current object into account - * otherwise they would become too deep. - */ - max_depth = depth; - if (entry->delta_child) { - max_depth -= check_delta_limit(entry, 0); - if (max_depth <= 0) - goto next; - } - - j = window; - while (--j > 0) { - int ret; - uint32_t other_idx = idx + j; - struct unpacked *m; - if (other_idx >= window) - other_idx -= window; - m = array + other_idx; - if (!m->entry) - break; - ret = try_delta(n, m, max_depth, &mem_usage); - if (ret < 0) - break; - else if (ret > 0) - best_base = other_idx; - } - - /* - * If we decided to cache the delta data, then it is best - * to compress it right away. First because we have to do - * it anyway, and doing it here while we're threaded will - * save a lot of time in the non threaded write phase, - * as well as allow for caching more deltas within - * the same cache size limit. - * ... - * But only if not writing to stdout, since in that case - * the network is most likely throttling writes anyway, - * and therefore it is best to go to the write phase ASAP - * instead, as we can afford spending more time compressing - * between writes at that moment. - */ - if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size = do_compress(&entry->delta_data, - entry->delta_size); - cache_lock(); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; - cache_unlock(); - } - - /* if we made n a delta, and if n is already at max - * depth, leaving it in the window is pointless. we - * should evict it first. - */ - if (entry->delta && max_depth <= n->depth) - continue; - - /* - * Move the best delta base up in the window, after the - * currently deltified object, to keep it longer. It will - * be the first base object to be attempted next. - */ - if (entry->delta) { - struct unpacked swap = array[best_base]; - int dist = (window + idx - best_base) % window; - int dst = best_base; - while (dist--) { - int src = (dst + 1) % window; - array[dst] = array[src]; - dst = src; - } - array[dst] = swap; - } - - next: - idx++; - if (count + 1 < window) - count++; - if (idx >= window) - idx = 0; - } - - for (i = 0; i < window; ++i) { - free_delta_index(array[i].index); - free(array[i].data); - } - free(array); -} - -#ifndef NO_PTHREADS - -/* - * The main thread waits on the condition that (at least) one of the workers - * has stopped working (which is indicated in the .working member of - * struct thread_params). - * When a work thread has completed its work, it sets .working to 0 and - * signals the main thread and waits on the condition that .data_ready - * becomes 1. - */ - -struct thread_params { - pthread_t thread; - struct object_entry **list; - unsigned list_size; - unsigned remaining; - int window; - int depth; - int working; - int data_ready; - pthread_mutex_t mutex; - pthread_cond_t cond; - unsigned *processed; -}; - -static pthread_cond_t progress_cond; - -/* - * Mutex and conditional variable can't be statically-initialized on Windows. - */ -static void init_threaded_search(void) -{ - pthread_mutex_init(&read_mutex, NULL); - pthread_mutex_init(&cache_mutex, NULL); - pthread_mutex_init(&progress_mutex, NULL); - pthread_cond_init(&progress_cond, NULL); -} - -static void cleanup_threaded_search(void) -{ - pthread_cond_destroy(&progress_cond); - pthread_mutex_destroy(&read_mutex); - pthread_mutex_destroy(&cache_mutex); - pthread_mutex_destroy(&progress_mutex); -} - -static void *threaded_find_deltas(void *arg) -{ - struct thread_params *me = arg; - - while (me->remaining) { - find_deltas(me->list, &me->remaining, - me->window, me->depth, me->processed); - - progress_lock(); - me->working = 0; - pthread_cond_signal(&progress_cond); - progress_unlock(); - - /* - * We must not set ->data_ready before we wait on the - * condition because the main thread may have set it to 1 - * before we get here. In order to be sure that new - * work is available if we see 1 in ->data_ready, it - * was initialized to 0 before this thread was spawned - * and we reset it to 0 right away. - */ - pthread_mutex_lock(&me->mutex); - while (!me->data_ready) - pthread_cond_wait(&me->cond, &me->mutex); - me->data_ready = 0; - pthread_mutex_unlock(&me->mutex); - } - /* leave ->working 1 so that this doesn't get more work assigned */ - return NULL; -} - -static void ll_find_deltas(struct object_entry **list, unsigned list_size, - int window, int depth, unsigned *processed) -{ - struct thread_params *p; - int i, ret, active_threads = 0; - - init_threaded_search(); - - if (!delta_search_threads) /* --threads=0 means autodetect */ - delta_search_threads = online_cpus(); - if (delta_search_threads <= 1) { - find_deltas(list, &list_size, window, depth, processed); - cleanup_threaded_search(); - return; - } - if (progress > pack_to_stdout) - fprintf(stderr, "Delta compression using up to %d threads.\n", - delta_search_threads); - p = xcalloc(delta_search_threads, sizeof(*p)); - - /* Partition the work amongst work threads. */ - for (i = 0; i < delta_search_threads; i++) { - unsigned sub_size = list_size / (delta_search_threads - i); - - /* don't use too small segments or no deltas will be found */ - if (sub_size < 2*window && i+1 < delta_search_threads) - sub_size = 0; - - p[i].window = window; - p[i].depth = depth; - p[i].processed = processed; - p[i].working = 1; - p[i].data_ready = 0; - - /* try to split chunks on "path" boundaries */ - while (sub_size && sub_size < list_size && - list[sub_size]->hash && - list[sub_size]->hash == list[sub_size-1]->hash) - sub_size++; - - p[i].list = list; - p[i].list_size = sub_size; - p[i].remaining = sub_size; - - list += sub_size; - list_size -= sub_size; - } - - /* Start work threads. */ - for (i = 0; i < delta_search_threads; i++) { - if (!p[i].list_size) - continue; - pthread_mutex_init(&p[i].mutex, NULL); - pthread_cond_init(&p[i].cond, NULL); - ret = pthread_create(&p[i].thread, NULL, - threaded_find_deltas, &p[i]); - if (ret) - die("unable to create thread: %s", strerror(ret)); - active_threads++; - } - - /* - * Now let's wait for work completion. Each time a thread is done - * with its work, we steal half of the remaining work from the - * thread with the largest number of unprocessed objects and give - * it to that newly idle thread. This ensure good load balancing - * until the remaining object list segments are simply too short - * to be worth splitting anymore. - */ - while (active_threads) { - struct thread_params *target = NULL; - struct thread_params *victim = NULL; - unsigned sub_size = 0; - - progress_lock(); - for (;;) { - for (i = 0; !target && i < delta_search_threads; i++) - if (!p[i].working) - target = &p[i]; - if (target) - break; - pthread_cond_wait(&progress_cond, &progress_mutex); - } - - for (i = 0; i < delta_search_threads; i++) - if (p[i].remaining > 2*window && - (!victim || victim->remaining < p[i].remaining)) - victim = &p[i]; - if (victim) { - sub_size = victim->remaining / 2; - list = victim->list + victim->list_size - sub_size; - while (sub_size && list[0]->hash && - list[0]->hash == list[-1]->hash) { - list++; - sub_size--; - } - if (!sub_size) { - /* - * It is possible for some "paths" to have - * so many objects that no hash boundary - * might be found. Let's just steal the - * exact half in that case. - */ - sub_size = victim->remaining / 2; - list -= sub_size; - } - target->list = list; - victim->list_size -= sub_size; - victim->remaining -= sub_size; - } - target->list_size = sub_size; - target->remaining = sub_size; - target->working = 1; - progress_unlock(); - - pthread_mutex_lock(&target->mutex); - target->data_ready = 1; - pthread_cond_signal(&target->cond); - pthread_mutex_unlock(&target->mutex); - - if (!sub_size) { - pthread_join(target->thread, NULL); - pthread_cond_destroy(&target->cond); - pthread_mutex_destroy(&target->mutex); - active_threads--; - } - } - cleanup_threaded_search(); - free(p); -} - -#else -#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) -#endif - -static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data) -{ - unsigned char peeled[20]; - - if (!prefixcmp(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, peeled) && /* peelable? */ - !is_null_sha1(peeled) && /* annotated tag? */ - locate_object_entry(peeled)) /* object packed? */ - add_object_entry(sha1, OBJ_TAG, NULL, 0); - return 0; -} - -static void prepare_pack(int window, int depth) -{ - struct object_entry **delta_list; - uint32_t i, nr_deltas; - unsigned n; - - get_object_details(); - - /* - * If we're locally repacking then we need to be doubly careful - * from now on in order to make sure no stealth corruption gets - * propagated to the new pack. Clients receiving streamed packs - * should validate everything they get anyway so no need to incur - * the additional cost here in that case. - */ - if (!pack_to_stdout) - do_check_packed_object_crc = 1; - - if (!nr_objects || !window || !depth) - return; - - delta_list = xmalloc(nr_objects * sizeof(*delta_list)); - nr_deltas = n = 0; - - for (i = 0; i < nr_objects; i++) { - struct object_entry *entry = objects + i; - - if (entry->delta) - /* This happens if we decided to reuse existing - * delta from a pack. "reuse_delta &&" is implied. - */ - continue; - - if (entry->size < 50) - continue; - - if (entry->no_try_delta) - continue; - - if (!entry->preferred_base) { - nr_deltas++; - if (entry->type < 0) - die("unable to get type of object %s", - sha1_to_hex(entry->idx.sha1)); - } else { - if (entry->type < 0) { - /* - * This object is not found, but we - * don't have to include it anyway. - */ - continue; - } - } - - delta_list[n++] = entry; - } - - if (nr_deltas && n > 1) { - unsigned nr_done = 0; - if (progress) - progress_state = start_progress("Compressing objects", - nr_deltas); - qsort(delta_list, n, sizeof(*delta_list), type_size_sort); - ll_find_deltas(delta_list, n, window+1, depth, &nr_done); - stop_progress(&progress_state); - if (nr_done != nr_deltas) - die("inconsistency with delta count"); - } - free(delta_list); -} - -static int git_pack_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "pack.window")) { - window = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.windowmemory")) { - window_memory_limit = git_config_ulong(k, v); - return 0; - } - if (!strcmp(k, "pack.depth")) { - depth = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.compression")) { - int level = git_config_int(k, v); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - pack_compression_seen = 1; - return 0; - } - if (!strcmp(k, "pack.deltacachesize")) { - max_delta_cache_size = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.deltacachelimit")) { - cache_max_small_delta_size = git_config_int(k, v); - return 0; - } - if (!strcmp(k, "pack.threads")) { - delta_search_threads = git_config_int(k, v); - if (delta_search_threads < 0) - die("invalid number of threads specified (%d)", - delta_search_threads); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, ignoring %s", k); -#endif - 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")) { - pack_size_limit_cfg = git_config_ulong(k, v); - return 0; - } - return git_default_config(k, v, cb); -} - -static void read_object_list_from_stdin(void) -{ - char line[40 + 1 + PATH_MAX + 2]; - unsigned char sha1[20]; - - for (;;) { - if (!fgets(line, sizeof(line), stdin)) { - if (feof(stdin)) - break; - if (!ferror(stdin)) - die("fgets returned NULL, not EOF, not error!"); - if (errno != EINTR) - die_errno("fgets"); - clearerr(stdin); - continue; - } - if (line[0] == '-') { - if (get_sha1_hex(line+1, sha1)) - die("expected edge sha1, got garbage:\n %s", - line); - add_preferred_base(sha1); - continue; - } - if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage:\n %s", line); - - add_preferred_base_object(line+41); - add_object_entry(sha1, 0, line+41, 0); - } -} - -#define OBJECT_ADDED (1u<<20) - -static void show_commit(struct commit *commit, void *data) -{ - add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); - commit->object.flags |= OBJECT_ADDED; -} - -static void show_object(struct object *obj, const struct name_path *path, const char *last) -{ - char *name = path_name(path, last); - - add_preferred_base_object(name); - add_object_entry(obj->sha1, obj->type, name, 0); - obj->flags |= OBJECT_ADDED; - - /* - * We will have generated the hash from the name, - * but not saved a pointer to it - we can free it - */ - free((char *)name); -} - -static void show_edge(struct commit *commit) -{ - add_preferred_base(commit->object.sha1); -} - -struct in_pack_object { - off_t offset; - struct object *object; -}; - -struct in_pack { - int alloc; - int nr; - struct in_pack_object *array; -}; - -static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) -{ - in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p); - in_pack->array[in_pack->nr].object = object; - in_pack->nr++; -} - -/* - * Compare the objects in the offset order, in order to emulate the - * "git rev-list --objects" output that produced the pack originally. - */ -static int ofscmp(const void *a_, const void *b_) -{ - struct in_pack_object *a = (struct in_pack_object *)a_; - struct in_pack_object *b = (struct in_pack_object *)b_; - - if (a->offset < b->offset) - return -1; - else if (a->offset > b->offset) - return 1; - else - return hashcmp(a->object->sha1, b->object->sha1); -} - -static void add_objects_in_unpacked_packs(struct rev_info *revs) -{ - struct packed_git *p; - struct in_pack in_pack; - uint32_t i; - - memset(&in_pack, 0, sizeof(in_pack)); - - for (p = packed_git; p; p = p->next) { - const unsigned char *sha1; - struct object *o; - - if (!p->pack_local || p->pack_keep) - continue; - if (open_pack_index(p)) - die("cannot open pack index"); - - ALLOC_GROW(in_pack.array, - in_pack.nr + p->num_objects, - in_pack.alloc); - - for (i = 0; i < p->num_objects; i++) { - sha1 = nth_packed_object_sha1(p, i); - o = lookup_unknown_object(sha1); - if (!(o->flags & OBJECT_ADDED)) - mark_in_pack_object(o, p, &in_pack); - o->flags |= OBJECT_ADDED; - } - } - - if (in_pack.nr) { - qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]), - ofscmp); - for (i = 0; i < in_pack.nr; i++) { - struct object *o = in_pack.array[i].object; - add_object_entry(o->sha1, o->type, "", 0); - } - } - free(in_pack.array); -} - -static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) -{ - static struct packed_git *last_found = (void *)1; - struct packed_git *p; - - p = (last_found != (void *)1) ? last_found : packed_git; - - while (p) { - if ((!p->pack_local || p->pack_keep) && - find_pack_entry_one(sha1, p)) { - last_found = p; - return 1; - } - if (p == last_found) - p = packed_git; - else - p = p->next; - if (p == last_found) - p = p->next; - } - return 0; -} - -static void loosen_unused_packed_objects(struct rev_info *revs) -{ - struct packed_git *p; - uint32_t i; - const unsigned char *sha1; - - for (p = packed_git; p; p = p->next) { - if (!p->pack_local || p->pack_keep) - continue; - - if (open_pack_index(p)) - die("cannot open pack index"); - - for (i = 0; i < p->num_objects; i++) { - sha1 = nth_packed_object_sha1(p, i); - if (!locate_object_entry(sha1) && - !has_sha1_pack_kept_or_nonlocal(sha1)) - if (force_object_loose(sha1, p->mtime)) - die("unable to force loose object"); - } - } -} - -static void get_object_list(int ac, const char **av) -{ - struct rev_info revs; - char line[1000]; - int flags = 0; - - init_revisions(&revs, NULL); - save_commit_buffer = 0; - setup_revisions(ac, av, &revs, NULL); - - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = strlen(line); - if (len && line[len - 1] == '\n') - line[--len] = 0; - if (!len) - break; - if (*line == '-') { - if (!strcmp(line, "--not")) { - flags ^= UNINTERESTING; - continue; - } - die("not a rev '%s'", line); - } - if (handle_revision_arg(line, &revs, flags, 1)) - die("bad revision '%s'", line); - } - - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object, NULL); - - if (keep_unreachable) - add_objects_in_unpacked_packs(&revs); - if (unpack_unreachable) - 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; - int thin = 0; - int all_progress_implied = 0; - uint32_t i; - const char **rp_av; - int rp_ac_alloc = 64; - int rp_ac; - - read_replace_refs = 0; - - rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); - - rp_av[0] = "pack-objects"; - rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ - rp_ac = 2; - - git_config(git_pack_config, NULL); - if (!pack_compression_seen && core_compression_seen) - pack_compression_level = core_compression_level; - - progress = isatty(2); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (*arg != '-') - break; - - if (!strcmp("--non-empty", arg)) { - non_empty = 1; - continue; - } - if (!strcmp("--local", arg)) { - local = 1; - continue; - } - if (!strcmp("--incremental", arg)) { - incremental = 1; - continue; - } - if (!strcmp("--honor-pack-keep", arg)) { - ignore_packed_keep = 1; - continue; - } - if (!prefixcmp(arg, "--compression=")) { - char *end; - int level = strtoul(arg+14, &end, 0); - if (!arg[14] || *end) - usage(pack_usage); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", level); - pack_compression_level = level; - continue; - } - if (!prefixcmp(arg, "--max-pack-size=")) { - pack_size_limit_cfg = 0; - if (!git_parse_ulong(arg+16, &pack_size_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window=")) { - char *end; - window = strtoul(arg+9, &end, 0); - if (!arg[9] || *end) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--window-memory=")) { - if (!git_parse_ulong(arg+16, &window_memory_limit)) - usage(pack_usage); - continue; - } - if (!prefixcmp(arg, "--threads=")) { - char *end; - delta_search_threads = strtoul(arg+10, &end, 0); - if (!arg[10] || *end || delta_search_threads < 0) - usage(pack_usage); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, " - "ignoring %s", arg); -#endif - continue; - } - if (!prefixcmp(arg, "--depth=")) { - char *end; - depth = strtoul(arg+8, &end, 0); - if (!arg[8] || *end) - usage(pack_usage); - continue; - } - if (!strcmp("--progress", arg)) { - progress = 1; - continue; - } - if (!strcmp("--all-progress", arg)) { - progress = 2; - continue; - } - if (!strcmp("--all-progress-implied", arg)) { - all_progress_implied = 1; - continue; - } - if (!strcmp("-q", arg)) { - progress = 0; - continue; - } - if (!strcmp("--no-reuse-delta", arg)) { - reuse_delta = 0; - continue; - } - if (!strcmp("--no-reuse-object", arg)) { - reuse_object = reuse_delta = 0; - continue; - } - if (!strcmp("--delta-base-offset", arg)) { - allow_ofs_delta = 1; - continue; - } - if (!strcmp("--stdout", arg)) { - pack_to_stdout = 1; - continue; - } - if (!strcmp("--revs", arg)) { - use_internal_rev_list = 1; - continue; - } - if (!strcmp("--keep-unreachable", arg)) { - keep_unreachable = 1; - continue; - } - if (!strcmp("--unpack-unreachable", arg)) { - unpack_unreachable = 1; - continue; - } - if (!strcmp("--include-tag", arg)) { - include_tag = 1; - continue; - } - if (!strcmp("--unpacked", arg) || - !strcmp("--reflog", arg) || - !strcmp("--all", arg)) { - use_internal_rev_list = 1; - if (rp_ac >= rp_ac_alloc - 1) { - rp_ac_alloc = alloc_nr(rp_ac_alloc); - rp_av = xrealloc(rp_av, - rp_ac_alloc * sizeof(*rp_av)); - } - rp_av[rp_ac++] = arg; - continue; - } - if (!strcmp("--thin", arg)) { - use_internal_rev_list = 1; - thin = 1; - rp_av[1] = "--objects-edge"; - continue; - } - if (!prefixcmp(arg, "--index-version=")) { - char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) - die("bad %s", arg); - if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) - die("bad %s", arg); - continue; - } - if (!strcmp(arg, "--keep-true-parents")) { - grafts_replace_parents = 0; - continue; - } - usage(pack_usage); - } - - /* Traditionally "pack-objects [options] base extra" failed; - * we would however want to take refs parameter that would - * have been given to upstream rev-list ourselves, which means - * we somehow want to say what the base name is. So the - * syntax would be: - * - * pack-objects [options] base - * - * in other words, we would treat the first non-option as the - * base_name and send everything else to the internal revision - * walker. - */ - - if (!pack_to_stdout) - base_name = argv[i++]; - - if (pack_to_stdout != !base_name) - usage(pack_usage); - - if (!pack_to_stdout && !pack_size_limit) - pack_size_limit = pack_size_limit_cfg; - if (pack_to_stdout && pack_size_limit) - die("--max-pack-size cannot be used to build a pack for transfer."); - if (pack_size_limit && pack_size_limit < 1024*1024) { - warning("minimum pack size limit is 1 MiB"); - pack_size_limit = 1024*1024; - } - - if (!pack_to_stdout && thin) - die("--thin cannot be used to build an indexable pack."); - - if (keep_unreachable && unpack_unreachable) - die("--keep-unreachable and --unpack-unreachable are incompatible."); - - if (progress && all_progress_implied) - progress = 2; - - prepare_packed_git(); - - if (progress) - progress_state = start_progress("Counting objects", 0); - if (!use_internal_rev_list) - read_object_list_from_stdin(); - else { - rp_av[rp_ac] = NULL; - get_object_list(rp_ac, rp_av); - } - cleanup_preferred_base(); - if (include_tag && nr_result) - for_each_ref(add_ref_tag, NULL); - stop_progress(&progress_state); - - if (non_empty && !nr_result) - return 0; - if (nr_result) - prepare_pack(window, depth); - write_pack_file(); - if (progress) - fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32")," - " reused %"PRIu32" (delta %"PRIu32")\n", - written, written_delta, reused, reused_delta); - return 0; -} diff --git a/builtin-pack-redundant.c b/builtin-pack-redundant.c deleted file mode 100644 index 41e1615a2..000000000 --- a/builtin-pack-redundant.c +++ /dev/null @@ -1,696 +0,0 @@ -/* -* -* Copyright 2005, Lukas Sandstrom -* -* This file is licensed under the GPL v2. -* -*/ - -#include "cache.h" -#include "exec_cmd.h" - -#define BLKSIZE 512 - -static const char pack_redundant_usage[] = -"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; - -static int load_all_packs, verbose, alt_odb; - -struct llist_item { - struct llist_item *next; - const unsigned char *sha1; -}; -static struct llist { - struct llist_item *front; - struct llist_item *back; - size_t size; -} *all_objects; /* all objects which must be present in local packfiles */ - -static struct pack_list { - struct pack_list *next; - struct packed_git *pack; - struct llist *unique_objects; - struct llist *all_objects; -} *local_packs = NULL, *altodb_packs = NULL; - -struct pll { - struct pll *next; - struct pack_list *pl; -}; - -static struct llist_item *free_nodes; - -static inline void llist_item_put(struct llist_item *item) -{ - item->next = free_nodes; - free_nodes = item; -} - -static inline struct llist_item *llist_item_get(void) -{ - struct llist_item *new; - if ( free_nodes ) { - new = free_nodes; - free_nodes = free_nodes->next; - } else { - int i = 1; - new = xmalloc(sizeof(struct llist_item) * BLKSIZE); - for (; i < BLKSIZE; i++) - llist_item_put(&new[i]); - } - return new; -} - -static void llist_free(struct llist *list) -{ - while ((list->back = list->front)) { - list->front = list->front->next; - llist_item_put(list->back); - } - free(list); -} - -static inline void llist_init(struct llist **list) -{ - *list = xmalloc(sizeof(struct llist)); - (*list)->front = (*list)->back = NULL; - (*list)->size = 0; -} - -static struct llist * llist_copy(struct llist *list) -{ - struct llist *ret; - struct llist_item *new, *old, *prev; - - llist_init(&ret); - - if ((ret->size = list->size) == 0) - return ret; - - new = ret->front = llist_item_get(); - new->sha1 = list->front->sha1; - - old = list->front->next; - while (old) { - prev = new; - new = llist_item_get(); - prev->next = new; - new->sha1 = old->sha1; - old = old->next; - } - new->next = NULL; - ret->back = new; - - return ret; -} - -static inline struct llist_item *llist_insert(struct llist *list, - struct llist_item *after, - const unsigned char *sha1) -{ - struct llist_item *new = llist_item_get(); - new->sha1 = sha1; - new->next = NULL; - - if (after != NULL) { - new->next = after->next; - after->next = new; - if (after == list->back) - list->back = new; - } else {/* insert in front */ - if (list->size == 0) - list->back = new; - else - new->next = list->front; - list->front = new; - } - list->size++; - return new; -} - -static inline struct llist_item *llist_insert_back(struct llist *list, - const unsigned char *sha1) -{ - return llist_insert(list, list->back, sha1); -} - -static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, - const unsigned char *sha1, struct llist_item *hint) -{ - struct llist_item *prev = NULL, *l; - - l = (hint == NULL) ? list->front : hint; - while (l) { - int cmp = hashcmp(l->sha1, sha1); - if (cmp > 0) { /* we insert before this entry */ - return llist_insert(list, prev, sha1); - } - if (!cmp) { /* already exists */ - return l; - } - prev = l; - l = l->next; - } - /* insert at the end */ - return llist_insert_back(list, sha1); -} - -/* returns a pointer to an item in front of sha1 */ -static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint) -{ - struct llist_item *prev, *l; - -redo_from_start: - l = (hint == NULL) ? list->front : hint; - prev = NULL; - while (l) { - int cmp = hashcmp(l->sha1, sha1); - if (cmp > 0) /* not in list, since sorted */ - return prev; - if (!cmp) { /* found */ - if (prev == NULL) { - if (hint != NULL && hint != list->front) { - /* we don't know the previous element */ - hint = NULL; - goto redo_from_start; - } - list->front = l->next; - } else - prev->next = l->next; - if (l == list->back) - list->back = prev; - llist_item_put(l); - list->size--; - return prev; - } - prev = l; - l = l->next; - } - return prev; -} - -/* computes A\B */ -static void llist_sorted_difference_inplace(struct llist *A, - struct llist *B) -{ - struct llist_item *hint, *b; - - hint = NULL; - b = B->front; - - while (b) { - hint = llist_sorted_remove(A, b->sha1, hint); - b = b->next; - } -} - -static inline struct pack_list * pack_list_insert(struct pack_list **pl, - struct pack_list *entry) -{ - struct pack_list *p = xmalloc(sizeof(struct pack_list)); - memcpy(p, entry, sizeof(struct pack_list)); - p->next = *pl; - *pl = p; - return p; -} - -static inline size_t pack_list_size(struct pack_list *pl) -{ - size_t ret = 0; - while (pl) { - ret++; - pl = pl->next; - } - return ret; -} - -static struct pack_list * pack_list_difference(const struct pack_list *A, - const struct pack_list *B) -{ - struct pack_list *ret; - const struct pack_list *pl; - - if (A == NULL) - return NULL; - - pl = B; - while (pl != NULL) { - if (A->pack == pl->pack) - return pack_list_difference(A->next, B); - pl = pl->next; - } - ret = xmalloc(sizeof(struct pack_list)); - memcpy(ret, A, sizeof(struct pack_list)); - ret->next = pack_list_difference(A->next, B); - return ret; -} - -static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) -{ - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; - const unsigned char *p1_base, *p2_base; - struct llist_item *p1_hint = NULL, *p2_hint = NULL; - - p1_base = p1->pack->index_data; - p2_base = p2->pack->index_data; - p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8); - p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8); - p1_step = (p1->pack->index_version < 2) ? 24 : 20; - p2_step = (p2->pack->index_version < 2) ? 24 : 20; - - while (p1_off < p1->pack->num_objects * p1_step && - p2_off < p2->pack->num_objects * p2_step) - { - int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); - /* cmp ~ p1 - p2 */ - if (cmp == 0) { - p1_hint = llist_sorted_remove(p1->unique_objects, - p1_base + p1_off, p1_hint); - p2_hint = llist_sorted_remove(p2->unique_objects, - p1_base + p1_off, p2_hint); - p1_off += p1_step; - p2_off += p2_step; - continue; - } - if (cmp < 0) { /* p1 has the object, p2 doesn't */ - p1_off += p1_step; - } else { /* p2 has the object, p1 doesn't */ - p2_off += p2_step; - } - } -} - -static void pll_free(struct pll *l) -{ - struct pll *old; - struct pack_list *opl; - - while (l) { - old = l; - while (l->pl) { - opl = l->pl; - l->pl = opl->next; - free(opl); - } - l = l->next; - free(old); - } -} - -/* all the permutations have to be free()d at the same time, - * since they refer to each other - */ -static struct pll * get_permutations(struct pack_list *list, int n) -{ - struct pll *subset, *ret = NULL, *new_pll = NULL, *pll; - - if (list == NULL || pack_list_size(list) < n || n == 0) - return NULL; - - if (n == 1) { - while (list) { - new_pll = xmalloc(sizeof(pll)); - new_pll->pl = NULL; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - list = list->next; - } - return ret; - } - - while (list->next) { - subset = get_permutations(list->next, n - 1); - while (subset) { - new_pll = xmalloc(sizeof(pll)); - new_pll->pl = subset->pl; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - subset = subset->next; - } - list = list->next; - } - return ret; -} - -static int is_superset(struct pack_list *pl, struct llist *list) -{ - struct llist *diff; - - diff = llist_copy(list); - - while (pl) { - llist_sorted_difference_inplace(diff, pl->all_objects); - if (diff->size == 0) { /* we're done */ - llist_free(diff); - return 1; - } - pl = pl->next; - } - llist_free(diff); - return 0; -} - -static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) -{ - size_t ret = 0; - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; - const unsigned char *p1_base, *p2_base; - - p1_base = p1->index_data; - p2_base = p2->index_data; - p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8); - p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8); - p1_step = (p1->index_version < 2) ? 24 : 20; - p2_step = (p2->index_version < 2) ? 24 : 20; - - while (p1_off < p1->num_objects * p1_step && - p2_off < p2->num_objects * p2_step) - { - int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); - /* cmp ~ p1 - p2 */ - if (cmp == 0) { - ret++; - p1_off += p1_step; - p2_off += p2_step; - continue; - } - if (cmp < 0) { /* p1 has the object, p2 doesn't */ - p1_off += p1_step; - } else { /* p2 has the object, p1 doesn't */ - p2_off += p2_step; - } - } - return ret; -} - -/* another O(n^2) function ... */ -static size_t get_pack_redundancy(struct pack_list *pl) -{ - struct pack_list *subset; - size_t ret = 0; - - if (pl == NULL) - return 0; - - while ((subset = pl->next)) { - while (subset) { - ret += sizeof_union(pl->pack, subset->pack); - subset = subset->next; - } - pl = pl->next; - } - return ret; -} - -static inline off_t pack_set_bytecount(struct pack_list *pl) -{ - off_t ret = 0; - while (pl) { - ret += pl->pack->pack_size; - ret += pl->pack->index_size; - pl = pl->next; - } - return ret; -} - -static void minimize(struct pack_list **min) -{ - struct pack_list *pl, *unique = NULL, - *non_unique = NULL, *min_perm = NULL; - struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm; - struct llist *missing; - off_t min_perm_size = 0, perm_size; - int n; - - pl = local_packs; - while (pl) { - if (pl->unique_objects->size) - pack_list_insert(&unique, pl); - else - pack_list_insert(&non_unique, pl); - pl = pl->next; - } - /* find out which objects are missing from the set of unique packs */ - missing = llist_copy(all_objects); - pl = unique; - while (pl) { - llist_sorted_difference_inplace(missing, pl->all_objects); - pl = pl->next; - } - - /* return if there are no objects missing from the unique set */ - if (missing->size == 0) { - *min = unique; - return; - } - - /* find the permutations which contain all missing objects */ - for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) { - perm_all = perm = get_permutations(non_unique, n); - while (perm) { - if (is_superset(perm->pl, missing)) { - new_perm = xmalloc(sizeof(struct pll)); - memcpy(new_perm, perm, sizeof(struct pll)); - new_perm->next = perm_ok; - perm_ok = new_perm; - } - perm = perm->next; - } - if (perm_ok) - break; - pll_free(perm_all); - } - if (perm_ok == NULL) - die("Internal error: No complete sets found!"); - - /* find the permutation with the smallest size */ - perm = perm_ok; - while (perm) { - perm_size = pack_set_bytecount(perm->pl); - if (!min_perm_size || min_perm_size > perm_size) { - min_perm_size = perm_size; - min_perm = perm->pl; - } - perm = perm->next; - } - *min = min_perm; - /* add the unique packs to the list */ - pl = unique; - while (pl) { - pack_list_insert(min, pl); - pl = pl->next; - } -} - -static void load_all_objects(void) -{ - struct pack_list *pl = local_packs; - struct llist_item *hint, *l; - - llist_init(&all_objects); - - while (pl) { - hint = NULL; - l = pl->all_objects->front; - while (l) { - hint = llist_insert_sorted_unique(all_objects, - l->sha1, hint); - l = l->next; - } - pl = pl->next; - } - /* remove objects present in remote packs */ - pl = altodb_packs; - while (pl) { - llist_sorted_difference_inplace(all_objects, pl->all_objects); - pl = pl->next; - } -} - -/* this scales like O(n^2) */ -static void cmp_local_packs(void) -{ - struct pack_list *subset, *pl = local_packs; - - while ((subset = pl)) { - while ((subset = subset->next)) - cmp_two_packs(pl, subset); - pl = pl->next; - } -} - -static void scan_alt_odb_packs(void) -{ - struct pack_list *local, *alt; - - alt = altodb_packs; - while (alt) { - local = local_packs; - while (local) { - llist_sorted_difference_inplace(local->unique_objects, - alt->all_objects); - local = local->next; - } - llist_sorted_difference_inplace(all_objects, alt->all_objects); - alt = alt->next; - } -} - -static struct pack_list * add_pack(struct packed_git *p) -{ - struct pack_list l; - unsigned long off = 0, step; - const unsigned char *base; - - if (!p->pack_local && !(alt_odb || verbose)) - return NULL; - - l.pack = p; - llist_init(&l.all_objects); - - if (open_pack_index(p)) - return NULL; - - base = p->index_data; - base += 256 * 4 + ((p->index_version < 2) ? 4 : 8); - step = (p->index_version < 2) ? 24 : 20; - while (off < p->num_objects * step) { - llist_insert_back(l.all_objects, base + off); - off += step; - } - /* this list will be pruned in cmp_two_packs later */ - l.unique_objects = llist_copy(l.all_objects); - if (p->pack_local) - return pack_list_insert(&local_packs, &l); - else - return pack_list_insert(&altodb_packs, &l); -} - -static struct pack_list * add_pack_file(const char *filename) -{ - struct packed_git *p = packed_git; - - if (strlen(filename) < 40) - die("Bad pack filename: %s", filename); - - while (p) { - if (strstr(p->pack_name, filename)) - return add_pack(p); - p = p->next; - } - die("Filename %s not found in packed_git", filename); -} - -static void load_all(void) -{ - struct packed_git *p = packed_git; - - while (p) { - add_pack(p); - p = p->next; - } -} - -int cmd_pack_redundant(int argc, const char **argv, const char *prefix) -{ - int i; - struct pack_list *min, *red, *pl; - struct llist *ignore; - unsigned char *sha1; - char buf[42]; /* 40 byte sha1 + \n + \0 */ - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage(pack_redundant_usage); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "--all")) { - load_all_packs = 1; - continue; - } - if (!strcmp(arg, "--verbose")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "--alt-odb")) { - alt_odb = 1; - continue; - } - if (*arg == '-') - usage(pack_redundant_usage); - else - break; - } - - prepare_packed_git(); - - if (load_all_packs) - load_all(); - else - while (*(argv + i) != NULL) - add_pack_file(*(argv + i++)); - - if (local_packs == NULL) - die("Zero packs found!"); - - load_all_objects(); - - cmp_local_packs(); - if (alt_odb) - scan_alt_odb_packs(); - - /* ignore objects given on stdin */ - llist_init(&ignore); - if (!isatty(0)) { - while (fgets(buf, sizeof(buf), stdin)) { - sha1 = xmalloc(20); - if (get_sha1_hex(buf, sha1)) - die("Bad sha1 on stdin: %s", buf); - llist_insert_sorted_unique(ignore, sha1, NULL); - } - } - llist_sorted_difference_inplace(all_objects, ignore); - pl = local_packs; - while (pl) { - llist_sorted_difference_inplace(pl->unique_objects, ignore); - pl = pl->next; - } - - minimize(&min); - - if (verbose) { - fprintf(stderr, "There are %lu packs available in alt-odbs.\n", - (unsigned long)pack_list_size(altodb_packs)); - fprintf(stderr, "The smallest (bytewise) set of packs is:\n"); - pl = min; - while (pl) { - fprintf(stderr, "\t%s\n", pl->pack->pack_name); - pl = pl->next; - } - fprintf(stderr, "containing %lu duplicate objects " - "with a total size of %lukb.\n", - (unsigned long)get_pack_redundancy(min), - (unsigned long)pack_set_bytecount(min)/1024); - fprintf(stderr, "A total of %lu unique objects were considered.\n", - (unsigned long)all_objects->size); - fprintf(stderr, "Redundant packs (with indexes):\n"); - } - pl = red = pack_list_difference(local_packs, min); - while (pl) { - printf("%s\n%s\n", - sha1_pack_index_name(pl->pack->sha1), - pl->pack->pack_name); - pl = pl->next; - } - if (verbose) - fprintf(stderr, "%luMB of redundant packs in total.\n", - (unsigned long)pack_set_bytecount(red)/(1024*1024)); - - return 0; -} diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c deleted file mode 100644 index 091860b2e..000000000 --- a/builtin-pack-refs.c +++ /dev/null @@ -1,21 +0,0 @@ -#include "cache.h" -#include "parse-options.h" -#include "pack-refs.h" - -static char const * const pack_refs_usage[] = { - "git pack-refs [options]", - NULL -}; - -int cmd_pack_refs(int argc, const char **argv, const char *prefix) -{ - unsigned int flags = PACK_REFS_PRUNE; - struct option opts[] = { - OPT_BIT(0, "all", &flags, "pack everything", PACK_REFS_ALL), - OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE), - OPT_END(), - }; - if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) - usage_with_options(pack_refs_usage, opts); - return pack_refs(flags); -} diff --git a/builtin-patch-id.c b/builtin-patch-id.c deleted file mode 100644 index af0911e4b..000000000 --- a/builtin-patch-id.c +++ /dev/null @@ -1,85 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" - -static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) -{ - unsigned char result[20]; - char name[50]; - - if (!patchlen) - return; - - git_SHA1_Final(result, c); - memcpy(name, sha1_to_hex(id), 41); - printf("%s %s\n", sha1_to_hex(result), name); - git_SHA1_Init(c); -} - -static int remove_space(char *line) -{ - char *src = line; - char *dst = line; - unsigned char c; - - while ((c = *src++) != '\0') { - if (!isspace(c)) - *dst++ = c; - } - return dst - line; -} - -static void generate_id_list(void) -{ - static unsigned char sha1[20]; - static char line[1000]; - git_SHA_CTX ctx; - int patchlen = 0; - - git_SHA1_Init(&ctx); - while (fgets(line, sizeof(line), stdin) != NULL) { - unsigned char n[20]; - char *p = line; - int len; - - if (!memcmp(line, "diff-tree ", 10)) - p += 10; - else if (!memcmp(line, "commit ", 7)) - p += 7; - - if (!get_sha1_hex(p, n)) { - flush_current_id(patchlen, sha1, &ctx); - hashcpy(sha1, n); - patchlen = 0; - continue; - } - - /* Ignore commit comments */ - if (!patchlen && memcmp(line, "diff ", 5)) - continue; - - /* Ignore git-diff index header */ - if (!memcmp(line, "index ", 6)) - continue; - - /* Ignore line numbers when computing the SHA1 of the patch */ - if (!memcmp(line, "@@ -", 4)) - continue; - - /* Compute the sha without whitespace */ - len = remove_space(line); - patchlen += len; - git_SHA1_Update(&ctx, line, len); - } - flush_current_id(patchlen, sha1, &ctx); -} - -static const char patch_id_usage[] = "git patch-id < patch"; - -int cmd_patch_id(int argc, const char **argv, const char *prefix) -{ - if (argc != 1) - usage(patch_id_usage); - - generate_id_list(); - return 0; -} diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c deleted file mode 100644 index f9463deec..000000000 --- a/builtin-prune-packed.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "progress.h" -#include "parse-options.h" - -static const char * const prune_packed_usage[] = { - "git prune-packed [-n|--dry-run] [-q|--quiet]", - NULL -}; - -#define DRY_RUN 01 -#define VERBOSE 02 - -static struct progress *progress; - -static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) -{ - struct dirent *de; - char hex[40]; - - sprintf(hex, "%02x", i); - while ((de = readdir(dir)) != NULL) { - unsigned char sha1[20]; - if (strlen(de->d_name) != 38) - continue; - memcpy(hex+2, de->d_name, 38); - if (get_sha1_hex(hex, sha1)) - continue; - if (!has_sha1_pack(sha1)) - continue; - memcpy(pathname + len, de->d_name, 38); - if (opts & DRY_RUN) - printf("rm -f %s\n", pathname); - else - unlink_or_warn(pathname); - display_progress(progress, i + 1); - } - pathname[len] = 0; - rmdir(pathname); -} - -void prune_packed_objects(int opts) -{ - int i; - static char pathname[PATH_MAX]; - const char *dir = get_object_directory(); - int len = strlen(dir); - - if (opts == VERBOSE) - progress = start_progress_delay("Removing duplicate objects", - 256, 95, 2); - - if (len > PATH_MAX - 42) - die("impossible object directory"); - memcpy(pathname, dir, len); - if (len && pathname[len-1] != '/') - pathname[len++] = '/'; - for (i = 0; i < 256; i++) { - DIR *d; - - display_progress(progress, i + 1); - sprintf(pathname + len, "%02x/", i); - d = opendir(pathname); - if (!d) - continue; - prune_dir(i, d, pathname, len + 3, opts); - closedir(d); - } - stop_progress(&progress); -} - -int cmd_prune_packed(int argc, const char **argv, const char *prefix) -{ - int opts = isatty(2) ? VERBOSE : 0; - const struct option prune_packed_options[] = { - OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN), - OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, prune_packed_options, - prune_packed_usage, 0); - - prune_packed_objects(opts); - return 0; -} diff --git a/builtin-prune.c b/builtin-prune.c deleted file mode 100644 index 4675f6054..000000000 --- a/builtin-prune.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "builtin.h" -#include "reachable.h" -#include "parse-options.h" -#include "dir.h" - -static const char * const prune_usage[] = { - "git prune [-n] [-v] [--expire