From cb6020bb017405cc3e7f1faea6f30d4fd1b62e70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Dec 2009 00:20:48 -0800 Subject: [PATCH 001/118] Teach --[no-]rerere-autoupdate option to merge, revert and friends Introduce a command line option to override rerere.autoupdate configuration variable to make it more useful. Signed-off-by: Junio C Hamano --- Documentation/git-merge.txt | 7 ++++++- builtin-commit.c | 2 +- builtin-merge.c | 4 +++- builtin-rerere.c | 21 +++++++++++++++------ builtin-revert.c | 4 +++- git-am.sh | 6 +++++- git-rebase.sh | 6 +++++- parse-options.c | 7 +++++++ parse-options.h | 3 +++ rerere.c | 8 +++++--- rerere.h | 10 ++++++++-- t/t4200-rerere.sh | 15 +++++++++++++++ 12 files changed, 76 insertions(+), 17 deletions(-) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index e886c2ef54..67470311e2 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s ]... - [-m ] ... + [--[no-]rerere-autoupdate] [-m ] ... 'git merge' HEAD ... DESCRIPTION @@ -33,6 +33,11 @@ include::merge-options.txt[] used to give a good default for automated 'git merge' invocations. +--rerere-autoupdate:: +--no-rerere-autoupdate:: + Allow the rerere mechanism to update the index with the + result of auto-conflict resolution if possible. + ...:: Other branch heads to merge into our branch. You need at least one . Specifying more than one diff --git a/builtin-commit.c b/builtin-commit.c index e93a647c59..72e0f0b563 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -1150,7 +1150,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full or quota is\n" "not exceeded, and then \"git reset HEAD\" to recover."); - rerere(); + rerere(0); run_hook(get_index_file(), "post-commit", NULL); if (!quiet) print_summary(prefix, commit_sha1); diff --git a/builtin-merge.c b/builtin-merge.c index 56a1bb651f..c3faa6b9c3 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -52,6 +52,7 @@ static struct strategy **use_strategies; static size_t use_strategies_nr, use_strategies_alloc; static const char *branch; static int verbosity; +static int allow_rerere_auto; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -170,6 +171,7 @@ static struct option builtin_merge_options[] = { "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('m', "message", &merge_msg, "message", @@ -790,7 +792,7 @@ static int suggest_conflicts(void) } } fclose(fp); - rerere(); + rerere(allow_rerere_auto); printf("Automatic merge failed; " "fix conflicts and then commit the result.\n"); return 1; diff --git a/builtin-rerere.c b/builtin-rerere.c index 343d6cde48..7ec602cf55 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -101,15 +101,24 @@ static int diff_two(const char *file1, const char *label1, int cmd_rerere(int argc, const char **argv, const char *prefix) { struct string_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd; + int i, fd, flags = 0; + if (2 < argc) { + if (!strcmp(argv[1], "-h")) + usage(git_rerere_usage); + if (!strcmp(argv[1], "--rerere-autoupdate")) + flags = RERERE_AUTOUPDATE; + else if (!strcmp(argv[1], "--no-rerere-autoupdate")) + flags = RERERE_NOAUTOUPDATE; + if (flags) { + argc--; + argv++; + } + } if (argc < 2) - return rerere(); + return rerere(flags); - if (!strcmp(argv[1], "-h")) - usage(git_rerere_usage); - - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; diff --git a/builtin-revert.c b/builtin-revert.c index 151aa6a981..857ca2eefa 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 int allow_rerere_auto; static const char *me; @@ -57,6 +58,7 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), + OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_END(), }; @@ -395,7 +397,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", me, help_msg(commit->object.sha1)); - rerere(); + rerere(allow_rerere_auto); exit(1); } if (commit_lock_file(&msg_file) < 0) diff --git a/git-am.sh b/git-am.sh index 4838cdb9ed..2f46fda47b 100755 --- a/git-am.sh +++ b/git-am.sh @@ -30,6 +30,7 @@ skip skip the current patch abort restore the original branch and abort the patching operation. committer-date-is-author-date lie about committer date ignore-date use current timestamp for author date +rerere-autoupdate update the index with reused conflict resolution if possible rebasing* (internal use for git-rebase)" . git-sh-setup @@ -135,7 +136,7 @@ It does not apply to blobs recorded in its index." export GIT_MERGE_VERBOSITY=0 fi git-merge-recursive $orig_tree -- HEAD $his_tree || { - git rerere + git rerere $allow_rerere_autoupdate echo Failed to merge in the changes. exit 1 } @@ -293,6 +294,7 @@ resolvemsg= resume= scissors= no_inbody_headers= git_apply_opt= committer_date_is_author_date= ignore_date= +allow_rerere_autoupdate= while test $# != 0 do @@ -340,6 +342,8 @@ do committer_date_is_author_date=t ;; --ignore-date) ignore_date=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" ;; -q|--quiet) GIT_QUIET=t ;; --) diff --git a/git-rebase.sh b/git-rebase.sh index b121f4537c..398ea73716 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -50,6 +50,7 @@ diffstat=$(git config --bool rebase.stat) git_am_opt= rebase_root= force_rebase= +allow_rerere_autoupdate= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -118,7 +119,7 @@ call_merge () { return ;; 1) - git rerere + git rerere $allow_rerere_autoupdate die "$RESOLVEMSG" ;; 2) @@ -349,6 +350,9 @@ do -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) force_rebase=t ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; -*) usage ;; diff --git a/parse-options.c b/parse-options.c index f5594114ed..10ec21fb89 100644 --- a/parse-options.c +++ b/parse-options.c @@ -633,3 +633,10 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) commit_list_insert(commit, opt->value); return 0; } + +int parse_opt_tertiary(const struct option *opt, const char *arg, int unset) +{ + int *target = opt->value; + *target = unset ? 2 : 1; + return 0; +} diff --git a/parse-options.h b/parse-options.h index f295a2cf85..91c1500661 100644 --- a/parse-options.h +++ b/parse-options.h @@ -123,6 +123,8 @@ struct option { (h), PARSE_OPT_NOARG, NULL, (p) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, &parse_opt_tertiary } #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \ parse_opt_approxidate_cb } @@ -190,6 +192,7 @@ 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_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); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") diff --git a/rerere.c b/rerere.c index 29f95f657d..e0ac5bcaed 100644 --- a/rerere.c +++ b/rerere.c @@ -367,7 +367,7 @@ static int is_rerere_enabled(void) return 1; } -int setup_rerere(struct string_list *merge_rr) +int setup_rerere(struct string_list *merge_rr, int flags) { int fd; @@ -375,6 +375,8 @@ int setup_rerere(struct string_list *merge_rr) if (!is_rerere_enabled()) return -1; + if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) + rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); merge_rr_path = git_pathdup("MERGE_RR"); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, LOCK_DIE_ON_ERROR); @@ -382,12 +384,12 @@ int setup_rerere(struct string_list *merge_rr) return fd; } -int rerere(void) +int rerere(int flags) { struct string_list merge_rr = { NULL, 0, 0, 1 }; int fd; - fd = setup_rerere(&merge_rr); + fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; return do_plain_rerere(&merge_rr, fd); diff --git a/rerere.h b/rerere.h index 13313f3f2b..10a94a4ea1 100644 --- a/rerere.h +++ b/rerere.h @@ -3,9 +3,15 @@ #include "string-list.h" -extern int setup_rerere(struct string_list *); -extern int rerere(void); +#define RERERE_AUTOUPDATE 01 +#define RERERE_NOAUTOUPDATE 02 + +extern int setup_rerere(struct string_list *, int); +extern int rerere(int); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); +#define OPT_RERERE_AUTOUPDATE(v) OPT_UYN(0, "rerere-autoupdate", (v), \ + "update the index with reused conflict resolution if possible") + #endif diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index a6bc028a57..bb402c3780 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -217,7 +217,22 @@ test_expect_success 'rerere.autoupdate' ' git checkout version2 && test_must_fail git merge fifth && test 0 = $(git ls-files -u | wc -l) +' +test_expect_success 'merge --rerere-autoupdate' ' + git config --unset rerere.autoupdate + git reset --hard && + git checkout version2 && + test_must_fail git merge --rerere-autoupdate fifth && + test 0 = $(git ls-files -u | wc -l) +' + +test_expect_success 'merge --no-rerere-autoupdate' ' + git config rerere.autoupdate true + git reset --hard && + git checkout version2 && + test_must_fail git merge --no-rerere-autoupdate fifth && + test 2 = $(git ls-files -u | wc -l) ' test_done From 163f3925902446735b2f631dc44ab67882a93024 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Mon, 7 Dec 2009 05:22:58 +0100 Subject: [PATCH 002/118] t3404: Use test_commit to set up test repository Also adjust "expected" text to reflect the file contents generated by test_commit, which are slightly different than those generated by the old code. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/t3404-rebase-interactive.sh | 66 +++++++++++------------------------ 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3a37793c0d..778daf419c 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -16,53 +16,26 @@ set_fake_editor # set up two branches like this: # -# A - B - C - D - E +# A - B - C - D - E (master) # \ -# F - G - H +# F - G - H (branch1) # \ -# I +# I (branch2) # -# where B, D and G touch the same file. +# where A, B, D and G touch the same file. test_expect_success 'setup' ' - : > file1 && - git add file1 && - test_tick && - git commit -m A && - git tag A && - echo 1 > file1 && - test_tick && - git commit -m B file1 && - : > file2 && - git add file2 && - test_tick && - git commit -m C && - echo 2 > file1 && - test_tick && - git commit -m D file1 && - : > file3 && - git add file3 && - test_tick && - git commit -m E && + test_commit A file1 && + test_commit B file1 && + test_commit C file2 && + test_commit D file1 && + test_commit E file3 && git checkout -b branch1 A && - : > file4 && - git add file4 && - test_tick && - git commit -m F && - git tag F && - echo 3 > file1 && - test_tick && - git commit -m G file1 && - : > file5 && - git add file5 && - test_tick && - git commit -m H && + test_commit F file4 && + test_commit G file1 && + test_commit H file5 && git checkout -b branch2 F && - : > file6 && - git add file6 && - test_tick && - git commit -m I && - git tag I + test_commit I file6 ' test_expect_success 'no changes are a nop' ' @@ -111,19 +84,20 @@ test_expect_success 'exchange two commits' ' cat > expect << EOF diff --git a/file1 b/file1 -index e69de29..00750ed 100644 +index f70f10e..fd79235 100644 --- a/file1 +++ b/file1 -@@ -0,0 +1 @@ -+3 +@@ -1 +1 @@ +-A ++G EOF cat > expect2 << EOF <<<<<<< HEAD -2 +D ======= -3 ->>>>>>> b7ca976... G +G +>>>>>>> 91201e5... G EOF test_expect_success 'stop on conflicting pick' ' From 0205e72f088322a70a77643a7cd2d8b23ee07e14 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Mon, 7 Dec 2009 10:20:59 +0100 Subject: [PATCH 003/118] Add a command "fixup" to rebase --interactive The command is like "squash", except that it discards the commit message of the corresponding commit. Signed-off-by: Michael Haggerty Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 13 ++++++---- git-rebase--interactive.sh | 45 +++++++++++++++++++++++++++-------- t/lib-rebase.sh | 7 +++--- t/t3404-rebase-interactive.sh | 30 +++++++++++++++++++++++ 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index ca5e1e8653..9b648ece6e 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -382,9 +382,12 @@ If you just want to edit the commit message for a commit, replace the command "pick" with the command "reword". If you want to fold two or more commits into one, replace the command -"pick" with "squash" for the second and subsequent commit. If the -commits had different authors, it will attribute the squashed commit to -the author of the first commit. +"pick" for the second and subsequent commits with "squash" or "fixup". +If the commits had different authors, the folded commit will be +attributed to the author of the first commit. The suggested commit +message for the folded commit is the concatenation of the commit +messages of the first commit and of those with the "squash" command, +but omits the commit messages of commits with the "fixup" command. 'git-rebase' will stop when "pick" has been replaced with "edit" or when a command fails due to merge errors. When you are done editing @@ -512,8 +515,8 @@ Easy case: The changes are literally the same.:: Hard case: The changes are not the same.:: This happens if the 'subsystem' rebase had conflicts, or used - `\--interactive` to omit, edit, or squash commits; or if the - upstream used one of `commit \--amend`, `reset`, or + `\--interactive` to omit, edit, squash, or fixup commits; or + if the upstream used one of `commit \--amend`, `reset`, or `filter-branch`. diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 0bd3bf78b8..30de96ee1a 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -302,7 +302,10 @@ nth_string () { make_squash_message () { if test -f "$SQUASH_MSG"; then - COUNT=$(($(sed -n "s/^# This is [^0-9]*\([1-9][0-9]*\).*/\1/p" \ + # We want to be careful about matching only the commit + # message comment lines generated by this function. + # "[snrt][tdh]" matches the nth_string endings. + COUNT=$(($(sed -n "s/^# Th[^0-9]*\([1-9][0-9]*\)[snrt][tdh] commit message.*:/\1/p" \ < "$SQUASH_MSG" | sed -ne '$p')+1)) echo "# This is a combination of $COUNT commits." sed -e 1d -e '2,/^./{ @@ -315,10 +318,23 @@ make_squash_message () { echo git cat-file commit HEAD | sed -e '1,/^$/d' fi - echo - echo "# This is the $(nth_string $COUNT) commit message:" - echo - git cat-file commit $1 | sed -e '1,/^$/d' + case $1 in + squash) + echo + echo "# This is the $(nth_string $COUNT) commit message:" + echo + git cat-file commit $2 | sed -e '1,/^$/d' + ;; + fixup) + echo + echo "# The $(nth_string $COUNT) commit message will be skipped:" + echo + # Comment the lines of the commit message out using + # "# " rather than "# " to make them less likely to + # confuse the sed regexp above. + git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /' + ;; + esac } peek_next_command () { @@ -367,20 +383,28 @@ do_next () { warn exit 0 ;; - squash|s) - comment_for_reflog squash + squash|s|fixup|f) + case "$command" in + squash|s) + squash_style=squash + ;; + fixup|f) + squash_style=fixup + ;; + esac + comment_for_reflog $squash_style test -f "$DONE" && has_action "$DONE" || - die "Cannot 'squash' without a previous commit" + die "Cannot '$squash_style' without a previous commit" mark_action_done - make_squash_message $sha1 > "$MSG" + make_squash_message $squash_style $sha1 > "$MSG" failed=f author_script=$(get_author_ident_from_commit HEAD) output git reset --soft HEAD^ pick_one -n $sha1 || failed=t case "$(peek_next_command)" in - squash|s) + squash|s|fixup|f) USE_OUTPUT=output MSG_OPT=-F EDIT_OR_FILE="$MSG" @@ -768,6 +792,7 @@ first and then run 'git rebase --continue' again." # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit +# f, fixup = like "squash", but discard this commit's log message # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 62f452c8ea..f4dda02b5a 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -9,8 +9,9 @@ # # "[] []..." # -# If a line number is prefixed with "squash", "edit", or "reword", the -# respective line's command will be replaced with the specified one. +# If a line number is prefixed with "squash", "fixup", "edit", or +# "reword", the respective line's command will be replaced with the +# specified one. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh @@ -32,7 +33,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash|edit|reword) + squash|fixup|edit|reword) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 778daf419c..ea26115133 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -235,6 +235,36 @@ test_expect_success 'multi-squash only fires up editor once' ' test 1 = $(git show | grep ONCE | wc -l) ' +test_expect_success 'multi-fixup only fires up editor once' ' + git checkout -b multi-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D multi-fixup +' + +cat > expect-squash-fixup << EOF +B + +D + +ONCE +EOF + +test_expect_success 'squash and fixup generate correct log messages' ' + git checkout -b squash-fixup E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + git rebase -i $base && + git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && + test_cmp expect-squash-fixup actual-squash-fixup && + git checkout to-be-rebased && + git branch -D squash-fixup +' + test_expect_success 'squash works as expected' ' for n in one two three four do From be6ff8196d9890c1875a75b96320b863dd1fe815 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 17 Dec 2009 22:23:54 -0800 Subject: [PATCH 004/118] builtin-merge.c: use standard active_cache macros Instead of using the low-level index_state interface, use the bog standard active_cache and active_nr macros to access the cache entries when using the default one. Signed-off-by: Junio C Hamano --- builtin-merge.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index f1c84d759d..6cb804b6ce 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -618,11 +618,10 @@ static void count_diff_files(struct diff_queue_struct *q, static int count_unmerged_entries(void) { - const struct index_state *state = &the_index; int i, ret = 0; - for (i = 0; i < state->cache_nr; i++) - if (ce_stage(state->cache[i])) + for (i = 0; i < active_nr; i++) + if (ce_stage(active_cache[i])) ret++; return ret; From cfc5789ada444423232fa1533f401b5972eb3f6c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 00:30:51 -0800 Subject: [PATCH 005/118] resolve-undo: record resolved conflicts in a new index extension section When resolving a conflict using "git add" to create a stage #0 entry, or "git rm" to remove entries at higher stages, remove_index_entry_at() function is eventually called to remove unmerged (i.e. higher stage) entries from the index. Introduce a "resolve_undo_info" structure and keep track of the removed cache entries, and save it in a new index extension section in the index_state. Operations like "read-tree -m", "merge", "checkout [-m] " and "reset" are signs that recorded information in the index is no longer necessary. The data is removed from the index extension when operations start; they may leave conflicted entries in the index, and later user actions like "git add" will record their conflicted states afresh. Signed-off-by: Junio C Hamano --- Makefile | 2 + builtin-checkout.c | 2 + builtin-merge.c | 4 +- builtin-read-tree.c | 2 + cache.h | 2 + read-cache.c | 18 +++++++ resolve-undo.c | 117 ++++++++++++++++++++++++++++++++++++++++++++ resolve-undo.h | 14 ++++++ 8 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 resolve-undo.c create mode 100644 resolve-undo.h diff --git a/Makefile b/Makefile index 4a1e5bcc4d..762898ae7f 100644 --- a/Makefile +++ b/Makefile @@ -483,6 +483,7 @@ LIB_H += reflog-walk.h LIB_H += refs.h LIB_H += remote.h LIB_H += rerere.h +LIB_H += resolve-undo.h LIB_H += revision.h LIB_H += run-command.h LIB_H += sha1-lookup.h @@ -578,6 +579,7 @@ LIB_OBJS += refs.o LIB_OBJS += remote.o LIB_OBJS += replace_object.o LIB_OBJS += rerere.o +LIB_OBJS += resolve-undo.o LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o diff --git a/builtin-checkout.c b/builtin-checkout.c index 64f3a11ae1..a0fe7a4e6d 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -17,6 +17,7 @@ #include "blob.h" #include "xdiff-interface.h" #include "ll-merge.h" +#include "resolve-undo.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@ -370,6 +371,7 @@ static int merge_working_tree(struct checkout_opts *opts, 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) diff --git a/builtin-merge.c b/builtin-merge.c index 6cb804b6ce..6bc2f7af08 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -24,6 +24,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "resolve-undo.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -604,6 +605,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, discard_cache(); if (read_cache() < 0) die("failed to read the cache"); + resolve_undo_clear(); return ret; } } @@ -851,7 +853,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die("You are in the middle of a conflicted merge." " (index unmerged)"); - + resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 2a3a32cbfe..7d378b7548 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -13,6 +13,7 @@ #include "dir.h" #include "builtin.h" #include "parse-options.h" +#include "resolve-undo.h" static int nr_trees; static struct tree *trees[MAX_UNPACK_TREES]; @@ -122,6 +123,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("You need to resolve your current index first"); stage = opts.merge = 1; } + resolve_undo_clear(); for (i = 0; i < argc; i++) { const char *arg = argv[i]; diff --git a/cache.h b/cache.h index bf468e5235..310d9e672c 100644 --- a/cache.h +++ b/cache.h @@ -282,6 +282,7 @@ static inline int ce_to_dtype(const struct cache_entry *ce) struct index_state { struct cache_entry **cache; unsigned int cache_nr, cache_alloc, cache_changed; + struct string_list *resolve_undo; struct cache_tree *cache_tree; struct cache_time timestamp; void *alloc; @@ -336,6 +337,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen)) +#define resolve_undo_clear() resolve_undo_clear_index(&the_index) #endif enum object_type { diff --git a/read-cache.c b/read-cache.c index 1bbaf1cffb..9e0fb04075 100644 --- a/read-cache.c +++ b/read-cache.c @@ -14,6 +14,7 @@ #include "diffcore.h" #include "revision.h" #include "blob.h" +#include "resolve-undo.h" /* Index extensions. * @@ -26,6 +27,7 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ +#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUN" */ struct index_state the_index; @@ -449,6 +451,7 @@ int remove_index_entry_at(struct index_state *istate, int pos) { struct cache_entry *ce = istate->cache[pos]; + record_resolve_undo(istate, ce); remove_name_hash(ce); istate->cache_changed = 1; istate->cache_nr--; @@ -1170,6 +1173,9 @@ static int read_index_extension(struct index_state *istate, case CACHE_EXT_TREE: istate->cache_tree = cache_tree_read(data, sz); break; + case CACHE_EXT_RESOLVE_UNDO: + istate->resolve_undo = resolve_undo_read(data, sz); + break; default: if (*ext < 'A' || 'Z' < *ext) return error("index uses %.4s extension, which we do not understand", @@ -1349,6 +1355,7 @@ int is_index_unborn(struct index_state *istate) int discard_index(struct index_state *istate) { + resolve_undo_clear_index(istate); istate->cache_nr = 0; istate->cache_changed = 0; istate->timestamp.sec = 0; @@ -1574,6 +1581,17 @@ int write_index(struct index_state *istate, int newfd) if (err) return -1; } + if (istate->resolve_undo) { + struct strbuf sb = STRBUF_INIT; + + resolve_undo_write(&sb, istate->resolve_undo); + err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO, + sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) + return -1; + } if (ce_flush(&c, newfd) || fstat(newfd, &st)) return -1; diff --git a/resolve-undo.c b/resolve-undo.c new file mode 100644 index 0000000000..86e8547ca2 --- /dev/null +++ b/resolve-undo.c @@ -0,0 +1,117 @@ +#include "cache.h" +#include "resolve-undo.h" +#include "string-list.h" + +/* The only error case is to run out of memory in string-list */ +void record_resolve_undo(struct index_state *istate, struct cache_entry *ce) +{ + struct string_list_item *lost; + struct resolve_undo_info *ui; + struct string_list *resolve_undo; + int stage = ce_stage(ce); + + if (!stage) + return; + + if (!istate->resolve_undo) { + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + istate->resolve_undo = resolve_undo; + } + resolve_undo = istate->resolve_undo; + lost = string_list_insert(ce->name, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + hashcpy(ui->sha1[stage - 1], ce->sha1); + ui->mode[stage - 1] = ce->ce_mode; +} + +static int write_one(struct string_list_item *item, void *cbdata) +{ + struct strbuf *sb = cbdata; + struct resolve_undo_info *ui = item->util; + int i; + + if (!ui) + return 0; + strbuf_addstr(sb, item->string); + strbuf_addch(sb, 0); + for (i = 0; i < 3; i++) + strbuf_addf(sb, "%o%c", ui->mode[i], 0); + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + strbuf_add(sb, ui->sha1[i], 20); + } + return 0; +} + +void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) +{ + for_each_string_list(write_one, resolve_undo, sb); +} + +struct string_list *resolve_undo_read(void *data, unsigned long size) +{ + struct string_list *resolve_undo; + size_t len; + char *endptr; + int i; + + resolve_undo = xcalloc(1, sizeof(*resolve_undo)); + resolve_undo->strdup_strings = 1; + + while (size) { + struct string_list_item *lost; + struct resolve_undo_info *ui; + + len = strlen(data) + 1; + if (size <= len) + goto error; + lost = string_list_insert(data, resolve_undo); + if (!lost->util) + lost->util = xcalloc(1, sizeof(*ui)); + ui = lost->util; + size -= len; + data += len; + + for (i = 0; i < 3; i++) { + ui->mode[i] = strtoul(data, &endptr, 8); + if (!endptr || endptr == data || *endptr) + goto error; + len = (endptr + 1) - (char*)data; + if (size <= len) + goto error; + size -= len; + data += len; + } + + for (i = 0; i < 3; i++) { + if (!ui->mode[i]) + continue; + if (size < 20) + goto error; + hashcpy(ui->sha1[i], data); + size -= 20; + data += 20; + } + } + return resolve_undo; + +error: + string_list_clear(resolve_undo, 1); + error("Index records invalid resolve-undo information"); + return NULL; +} + +void resolve_undo_clear_index(struct index_state *istate) +{ + struct string_list *resolve_undo = istate->resolve_undo; + if (!resolve_undo) + return; + string_list_clear(resolve_undo, 1); + free(resolve_undo); + istate->resolve_undo = NULL; + istate->cache_changed = 1; +} diff --git a/resolve-undo.h b/resolve-undo.h new file mode 100644 index 0000000000..74194d0eba --- /dev/null +++ b/resolve-undo.h @@ -0,0 +1,14 @@ +#ifndef RESOLVE_UNDO_H +#define RESOLVE_UNDO_H + +struct resolve_undo_info { + unsigned int mode[3]; + unsigned char sha1[3][20]; +}; + +extern void record_resolve_undo(struct index_state *, struct cache_entry *); +extern void resolve_undo_write(struct strbuf *, struct string_list *); +extern struct string_list *resolve_undo_read(void *, unsigned long); +extern void resolve_undo_clear_index(struct index_state *); + +#endif From 9d9a2f4aba3650093bad952cd89e276cde4ed074 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 10:08:04 -0800 Subject: [PATCH 006/118] resolve-undo: basic tests Make sure that resolving a failed merge with git add records the conflicted state, committing the result keeps that state, and checking out another commit clears the state. "git ls-files" learns a new option --resolve-undo to show the recorded information. Signed-off-by: Junio C Hamano --- builtin-ls-files.c | 43 ++++++++++++++++++- t/t2030-unresolve-info.sh | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100755 t/t2030-unresolve-info.sh diff --git a/builtin-ls-files.c b/builtin-ls-files.c index c9a03e5427..ef3a06889a 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -11,6 +11,8 @@ #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; @@ -18,6 +20,7 @@ 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; @@ -37,6 +40,7 @@ static const char *tag_removed = ""; static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; +static const char *tag_resolve_undo = ""; static void show_dir_entry(const char *tag, struct dir_entry *ent) { @@ -155,6 +159,38 @@ static void show_ce_entry(const char *tag, struct cache_entry *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; @@ -454,6 +490,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) 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 }, @@ -490,6 +528,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) tag_modified = "C "; tag_other = "? "; tag_killed = "K "; + tag_resolve_undo = "U "; } if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) require_work_tree = 1; @@ -529,7 +568,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) /* With no flags, we default to showing the cached files */ if (!(show_stage | show_deleted | show_others | show_unmerged | - show_killed | show_modified)) + show_killed | show_modified | show_resolve_undo)) show_cached = 1; if (prefix) @@ -544,6 +583,8 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) overlay_tree_on_cache(with_tree, prefix); } show_files(&dir, prefix); + if (show_resolve_undo) + show_ru_info(prefix); if (ps_matched) { int bad; diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh new file mode 100755 index 0000000000..785c8b3fca --- /dev/null +++ b/t/t2030-unresolve-info.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +test_description='undoing resolution' + +. ./test-lib.sh + +check_resolve_undo () { + msg=$1 + shift + while case $# in + 0) break ;; + 1|2|3) die "Bug in check-resolve-undo test" ;; + esac + do + path=$1 + shift + for stage in 1 2 3 + do + sha1=$1 + shift + case "$sha1" in + '') continue ;; + esac + sha1=$(git rev-parse --verify "$sha1") + printf "100644 %s %s\t%s\n" $sha1 $stage $path + done + done >"$msg.expect" && + git ls-files --resolve-undo >"$msg.actual" && + test_cmp "$msg.expect" "$msg.actual" +} + +prime_resolve_undo () { + git reset --hard && + git checkout second^0 && + test_tick && + test_must_fail git merge third^0 && + echo merge does not leave anything && + check_resolve_undo empty && + echo different >file && + git add file && + echo resolving records && + check_resolve_undo recorded file initial:file second:file third:file +} + +test_expect_success setup ' + test_commit initial file first && + git branch side && + git branch another && + test_commit second file second && + git checkout side && + test_commit third file third && + git checkout another && + test_commit fourth file fourth && + git checkout master +' + +test_expect_success 'add records switch clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + git checkout second^0 && + echo switching clears && + check_resolve_undo cleared +' + +test_expect_success 'rm records reset clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + + echo merge clears upfront && + test_must_fail git merge fourth^0 && + check_resolve_undo nuked && + + git rm -f file && + echo resolving records && + check_resolve_undo recorded file initial:file HEAD:file fourth:file && + + git reset --hard && + echo resetting discards && + check_resolve_undo discarded +' + +test_done From 4a39f79d3482cd844443b4ef9a8ef9b3d72faa5b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 10:31:26 -0800 Subject: [PATCH 007/118] resolve-undo: allow plumbing to clear the information At the Porcelain level, operations such as merge that populate an initially cleanly merged index with conflicted entries clear the resolve-undo information upfront. Give scripted Porcelains a way to do the same, by implementing "update-index --clear-resolve-info". With this, a scripted Porcelain may "update-index --clear-resolve-info" first and repeatedly run "update-index --cacheinfo" to stuff unmerged entries to the index, to be resolved by the user with "git add" and stuff. Signed-off-by: Junio C Hamano --- builtin-update-index.c | 5 +++++ t/t2030-unresolve-info.sh | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/builtin-update-index.c b/builtin-update-index.c index a6b7f2d636..a19e78603c 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -9,6 +9,7 @@ #include "tree-walk.h" #include "builtin.h" #include "refs.h" +#include "resolve-undo.h" /* * Default to not allowing changes to the list of files. The @@ -703,6 +704,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(path, "--clear-resolve-undo")) { + resolve_undo_clear(); + continue; + } if (!strcmp(path, "-h") || !strcmp(path, "--help")) usage(update_index_usage); die("unknown option %s", path); diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index 785c8b3fca..984480271c 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -85,4 +85,16 @@ test_expect_success 'rm records reset clears' ' check_resolve_undo discarded ' +test_expect_success 'plumbing clears' ' + prime_resolve_undo && + test_tick && + git commit -m merged && + echo committing keeps && + check_resolve_undo kept file initial:file second:file third:file && + + echo plumbing clear && + git update-index --clear-resolve-undo && + check_resolve_undo cleared +' + test_done From 4421a8235783d0664faa9a1d45be114fd7ad8206 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 11:57:11 -0800 Subject: [PATCH 008/118] resolve-undo: "checkout -m path" uses resolve-undo information Once you resolved conflicts by "git add path", you cannot recreate the conflicted state with "git checkout -m path", because you lost information from higher stages in the index when you resolved them. Since we record the necessary information in the resolve-undo index extension these days, we can reproduce the unmerged state in the index and check it out. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 4 +++ cache.h | 1 + resolve-undo.c | 59 +++++++++++++++++++++++++++++++++++++++ resolve-undo.h | 2 ++ t/t2030-unresolve-info.sh | 11 ++++++++ 5 files changed, 77 insertions(+) diff --git a/builtin-checkout.c b/builtin-checkout.c index a0fe7a4e6d..bdef1aa386 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -235,6 +235,10 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, 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]; diff --git a/cache.h b/cache.h index 310d9e672c..f479f09190 100644 --- a/cache.h +++ b/cache.h @@ -338,6 +338,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen)) #define resolve_undo_clear() resolve_undo_clear_index(&the_index) +#define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec) #endif enum object_type { diff --git a/resolve-undo.c b/resolve-undo.c index 86e8547ca2..37d73cd949 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "dir.h" #include "resolve-undo.h" #include "string-list.h" @@ -115,3 +116,61 @@ void resolve_undo_clear_index(struct index_state *istate) istate->resolve_undo = NULL; istate->cache_changed = 1; } + +int unmerge_index_entry_at(struct index_state *istate, int pos) +{ + struct cache_entry *ce; + struct string_list_item *item; + struct resolve_undo_info *ru; + int i, err = 0; + + if (!istate->resolve_undo) + return pos; + + ce = istate->cache[pos]; + if (ce_stage(ce)) { + /* already unmerged */ + while ((pos < istate->cache_nr) && + ! strcmp(istate->cache[pos]->name, ce->name)) + pos++; + return pos - 1; /* return the last entry processed */ + } + item = string_list_lookup(ce->name, istate->resolve_undo); + if (!item) + return pos; + ru = item->util; + if (!ru) + return pos; + remove_index_entry_at(istate, pos); + for (i = 0; i < 3; i++) { + struct cache_entry *nce; + if (!ru->mode[i]) + continue; + nce = make_cache_entry(ru->mode[i], ru->sha1[i], + ce->name, i + 1, 0); + if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { + err = 1; + error("cannot unmerge '%s'", ce->name); + } + } + if (err) + return pos; + free(ru); + item->util = NULL; + return unmerge_index_entry_at(istate, pos); +} + +void unmerge_index(struct index_state *istate, const char **pathspec) +{ + int i; + + if (!istate->resolve_undo) + return; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) + continue; + i = unmerge_index_entry_at(istate, i); + } +} diff --git a/resolve-undo.h b/resolve-undo.h index 74194d0eba..e4e5c1b1ad 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -10,5 +10,7 @@ extern void record_resolve_undo(struct index_state *, struct cache_entry *); extern void resolve_undo_write(struct strbuf *, struct string_list *); extern struct string_list *resolve_undo_read(void *, unsigned long); extern void resolve_undo_clear_index(struct index_state *); +extern int unmerge_index_entry_at(struct index_state *, int); +extern void unmerge_index(struct index_state *, const char **); #endif diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index 984480271c..ea65f391c1 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -97,4 +97,15 @@ test_expect_success 'plumbing clears' ' check_resolve_undo cleared ' +test_expect_success 'add records checkout -m undoes' ' + prime_resolve_undo && + git diff HEAD && + git checkout --conflict=merge file && + echo checkout used the record and removed it && + check_resolve_undo removed && + echo the index and the work tree is unmerged again && + git diff >actual && + grep "^++<<<<<<<" actual +' + test_done From 8aa38563b22c84b06ea1fff9638cc1f44fda726f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 13:40:02 -0800 Subject: [PATCH 009/118] resolve-undo: teach "update-index --unresolve" to use resolve-undo info The update-index plumbing command had a hacky --unresolve implementation that was written back in the days when merge was the only way for users to end up with higher stages in the index, and assumed that stage #2 must have come from HEAD, stage #3 from MERGE_HEAD and didn't bother to compute the stage #1 information. There were several issues with this approach: - These days, merge is not the only command, and conflicts coming from commands like cherry-pick, "am -3", etc. cannot be recreated by looking at MERGE_HEAD; - For a conflict that came from a merge that had renames, picking up the same path from MERGE_HEAD and HEAD wouldn't help recreating it, either; - It may have been Ok not to recreate stage #1 back when it was written, because "diff --ours/--theirs" were the only availble ways to review conflicts and they don't need stage #1 information. "diff --cc" that was invented much later is a lot more useful way but it needs stage #1. We can use resolve-undo information recorded in the index extension to solve all of these issues. Signed-off-by: Junio C Hamano --- builtin-update-index.c | 13 ++++++++++++- cache.h | 1 + t/t2030-unresolve-info.sh | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/builtin-update-index.c b/builtin-update-index.c index a19e78603c..750db163b9 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -433,7 +433,18 @@ static int unresolve_one(const char *path) /* See if there is such entry in the index. */ pos = cache_name_pos(path, namelen); - if (pos < 0) { + if (0 <= pos) { + /* already merged */ + pos = unmerge_cache_entry_at(pos); + if (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_stage(ce) && + ce_namelen(ce) == namelen && + !memcmp(ce->name, path, namelen)) + return 0; + } + /* no resolve-undo information; fall back */ + } else { /* If there isn't, either it is unmerged, or * resolved as "removed" by mistake. We do not * want to do anything in the former case. diff --git a/cache.h b/cache.h index f479f09190..97b4a74d93 100644 --- a/cache.h +++ b/cache.h @@ -338,6 +338,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) #define cache_name_is_other(name, namelen) index_name_is_other(&the_index, (name), (namelen)) #define resolve_undo_clear() resolve_undo_clear_index(&the_index) +#define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at) #define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec) #endif diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index ea65f391c1..28e2eb1cec 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -108,4 +108,11 @@ test_expect_success 'add records checkout -m undoes' ' grep "^++<<<<<<<" actual ' +test_expect_success 'unmerge with plumbing' ' + prime_resolve_undo && + git update-index --unresolve file && + git ls-files -u >actual && + test $(wc -l Date: Fri, 25 Dec 2009 13:55:29 -0800 Subject: [PATCH 010/118] rerere: remove silly 1024-byte line limit Ever since 658f365 (Make git-rerere a builtin, 2006-12-20) rewrote it, it kept this line-length limit regression, even after we started using strbuf in the same function in 19b358e (Use strbuf API in buitin-rerere.c, 2007-09-06). Signed-off-by: Junio C Hamano --- rerere.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rerere.c b/rerere.c index 29f95f657d..88bb4f102c 100644 --- a/rerere.c +++ b/rerere.c @@ -87,12 +87,12 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output) { git_SHA_CTX ctx; - char buf[1024]; int hunk_no = 0; enum { RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL, } hunk = RR_CONTEXT; struct strbuf one = STRBUF_INIT, two = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; FILE *f = fopen(path, "r"); FILE *out = NULL; int wrerror = 0; @@ -111,20 +111,20 @@ static int handle_file(const char *path, if (sha1) git_SHA1_Init(&ctx); - while (fgets(buf, sizeof(buf), f)) { - if (!prefixcmp(buf, "<<<<<<< ")) { + while (!strbuf_getwholeline(&buf, f, '\n')) { + if (!prefixcmp(buf.buf, "<<<<<<< ")) { if (hunk != RR_CONTEXT) goto bad; hunk = RR_SIDE_1; - } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) { + } else if (!prefixcmp(buf.buf, "|||||||") && isspace(buf.buf[7])) { if (hunk != RR_SIDE_1) goto bad; hunk = RR_ORIGINAL; - } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + } else if (!prefixcmp(buf.buf, "=======") && isspace(buf.buf[7])) { if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL) goto bad; hunk = RR_SIDE_2; - } else if (!prefixcmp(buf, ">>>>>>> ")) { + } else if (!prefixcmp(buf.buf, ">>>>>>> ")) { if (hunk != RR_SIDE_2) goto bad; if (strbuf_cmp(&one, &two) > 0) @@ -147,13 +147,13 @@ static int handle_file(const char *path, strbuf_reset(&one); strbuf_reset(&two); } else if (hunk == RR_SIDE_1) - strbuf_addstr(&one, buf); + strbuf_addstr(&one, buf.buf); else if (hunk == RR_ORIGINAL) ; /* discard */ else if (hunk == RR_SIDE_2) - strbuf_addstr(&two, buf); + strbuf_addstr(&two, buf.buf); else if (out) - ferr_puts(buf, out, &wrerror); + ferr_puts(buf.buf, out, &wrerror); continue; bad: hunk = 99; /* force error exit */ @@ -161,6 +161,7 @@ static int handle_file(const char *path, } strbuf_release(&one); strbuf_release(&two); + strbuf_release(&buf); fclose(f); if (wrerror) From 23218bbd2ea7f919b93245489e544a55165ec466 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Tue, 22 Dec 2009 21:27:13 -0800 Subject: [PATCH 011/118] t7800-difftool: Set a bogus tool for use by tests If a difftool test has an error then running the git test suite may end up invoking a GUI diff tool. We now guard against this by setting a difftool.bogus-tool.cmd variable. The tests already used --tool=bogus-tool in various places so this is simply ensuring that nothing ever falls back and finds a real diff tool. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- t/t7800-difftool.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index fff6a6d0ea..707a0f54ae 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -36,6 +36,7 @@ restore_test_defaults() unset GIT_DIFFTOOL_NO_PROMPT git config diff.tool test-tool && git config difftool.test-tool.cmd 'cat $LOCAL' + git config difftool.bogus-tool.cmd false } prompt_given() @@ -71,7 +72,7 @@ test_expect_success 'custom commands' ' # Ensures that git-difftool ignores bogus --tool values test_expect_success 'difftool ignores bad --tool values' ' - diff=$(git difftool --no-prompt --tool=bogus-tool branch) + diff=$(git difftool --no-prompt --tool=bad-tool branch) test "$?" = 1 && test "$diff" = "" ' From 4cefa495ca91ad833084ebf3f73c77997920ae9b Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Tue, 22 Dec 2009 21:27:14 -0800 Subject: [PATCH 012/118] git-difftool: Add '--gui' for selecting a GUI tool Users might prefer to have git-difftool use a different tool when run from a Git GUI. This teaches git-difftool to honor 'diff.guitool' when the '--gui' option is specified. This allows users to configure their preferred command-line diff tool in 'diff.tool' and a GUI diff tool in 'diff.guitool'. Reference: http://article.gmane.org/gmane.comp.version-control.git/133386 Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- Documentation/git-difftool.txt | 9 +++++++++ git-difftool.perl | 13 ++++++++++++- t/t7800-difftool.sh | 12 ++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 8e9aed67d7..a5bce6278b 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -58,6 +58,12 @@ is set to the name of the temporary file containing the contents of the diff post-image. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$LOCAL`. +-g:: +--gui:: + When 'git-difftool' is invoked with the `-g` or `--gui` option + the default diff tool will be read from the configured + `diff.guitool` variable instead of `diff.tool`. + See linkgit:git-diff[1] for the full list of supported options. CONFIG VARIABLES @@ -68,6 +74,9 @@ difftool equivalents have not been defined. diff.tool:: The default diff tool to use. +diff.guitool:: + The default diff tool to use when `--gui` is specified. + difftool..path:: Override the path for the given tool. This is useful in case your tool is not in the PATH. diff --git a/git-difftool.perl b/git-difftool.perl index ba5e60a45e..8c836e4c76 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -15,13 +15,16 @@ use warnings; use Cwd qw(abs_path); use File::Basename qw(dirname); +require Git; + my $DIR = abs_path(dirname($0)); sub usage { print << 'USAGE'; -usage: git difftool [--tool=] [-y|--no-prompt] ["git diff" options] +usage: git difftool [-g|--gui] [-t|--tool=] [-y|--no-prompt] + ["git diff" options] USAGE exit 1; } @@ -63,6 +66,14 @@ sub generate_command $ENV{GIT_DIFF_TOOL} = substr($arg, 7); next; } + if ($arg eq '-g' || $arg eq '--gui') { + my $tool = Git::command_oneline('config', + 'diff.guitool'); + if (length($tool)) { + $ENV{GIT_DIFF_TOOL} = $tool; + } + next; + } if ($arg eq '-y' || $arg eq '--no-prompt') { $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; delete $ENV{GIT_DIFFTOOL_PROMPT}; diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 707a0f54ae..9bf6c98c54 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -19,6 +19,7 @@ remove_config_vars() { # Unset all config variables used by git-difftool git config --unset diff.tool + git config --unset diff.guitool git config --unset difftool.test-tool.cmd git config --unset difftool.prompt git config --unset merge.tool @@ -77,6 +78,17 @@ test_expect_success 'difftool ignores bad --tool values' ' test "$diff" = "" ' +test_expect_success 'difftool honors --gui' ' + git config merge.tool bogus-tool && + git config diff.tool bogus-tool && + git config diff.guitool test-tool && + + diff=$(git difftool --no-prompt --gui branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + # Specify the diff tool using $GIT_DIFF_TOOL test_expect_success 'GIT_DIFF_TOOL variable' ' git config --unset diff.tool From f59baa502f49c3f1579a82bc37d2bb3ce9e7b5da Mon Sep 17 00:00:00 2001 From: Nanako Shiraishi Date: Tue, 8 Dec 2009 12:13:14 +0900 Subject: [PATCH 013/118] rebase -i --autosquash: auto-squash commits Teach a new option, --autosquash, to the interactive rebase. When the commit log message begins with "!fixup ...", and there is a commit whose title begins with the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing come right after the commit to be modified, and change the action of the moved commit from pick to squash. Signed-off-by: Nanako Shiraishi Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 10 +++++ git-rebase--interactive.sh | 37 ++++++++++++++++++ t/t3415-rebase-autosquash.sh | 73 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100755 t/t3415-rebase-autosquash.sh diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 9b648ece6e..e2e61d3642 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -308,6 +308,16 @@ which makes little sense. root commits will be rewritten to have as parent instead. +--autosquash:: + When the commit log message begins with "squash! ..." (or + "fixup! ..."), and there is a commit whose title begins with + the same ..., automatically modify the todo list of rebase -i + so that the commit marked for quashing come right after the + commit to be modified, and change the action of the moved + commit from `pick` to `squash` (or `fixup`). ++ +This option is only valid when '--interactive' option is used. + include::merge-strategies.txt[] NOTES diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 30de96ee1a..935e9e1b1c 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -28,6 +28,7 @@ abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation root rebase all reachable commmits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i " . git-sh-setup @@ -46,6 +47,7 @@ ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= REBASE_ROOT= +AUTOSQUASH= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add ', and @@ -519,6 +521,37 @@ get_saved_options () { test -f "$DOTEST"/rebase-root && REBASE_ROOT=t } +# Rearrange the todo list that has both "pick sha1 msg" and +# "pick sha1 fixup!/squash! msg" appears in it so that the latter +# comes immediately after the former, and change "pick" to +# "fixup"/"squash". +rearrange_squash () { + sed -n -e 's/^pick \([0-9a-f]*\) \(squash\)! /\1 \2 /p' \ + -e 's/^pick \([0-9a-f]*\) \(fixup\)! /\1 \2 /p' \ + "$1" >"$1.sq" + test -s "$1.sq" || return + + used= + while read pick sha1 message + do + case " $used" in + *" $sha1 "*) continue ;; + esac + echo "$pick $sha1 $message" + while read squash action msg + do + case "$message" in + "$msg"*) + echo "$action $squash $action! $msg" + used="$used$squash " + ;; + esac + done <"$1.sq" + done >"$1.rearranged" <"$1" + cat "$1.rearranged" >"$1" + rm -f "$1.sq" "$1.rearranged" +} + while test $# != 0 do case "$1" in @@ -624,6 +657,9 @@ first and then run 'git rebase --continue' again." --root) REBASE_ROOT=t ;; + --autosquash) + AUTOSQUASH=t + ;; --onto) shift ONTO=$(git rev-parse --verify "$1") || @@ -783,6 +819,7 @@ first and then run 'git rebase --continue' again." fi test -s "$TODO" || echo noop >> "$TODO" + test -n "$AUTOSQUASH" && rearrange_squash "$TODO" cat >> "$TODO" << EOF # Rebase $SHORTREVISIONS onto $SHORTONTO diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh new file mode 100755 index 0000000000..b63f4e2d67 --- /dev/null +++ b/t/t3415-rebase-autosquash.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +test_description='auto squash' + +. ./test-lib.sh + +test_expect_success setup ' + echo 0 >file0 && + git add . && + test_tick && + git commit -m "initial commit" && + echo 0 >file1 && + echo 2 >file2 && + git add . && + test_tick && + git commit -m "first commit" && + echo 3 >file3 && + git add . && + test_tick && + git commit -m "second commit" && + git tag base +' + +test_expect_success 'auto fixup' ' + git reset --hard base && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "fixup! first" + + git tag final-fixup && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! first" + + git tag final-squash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! forst" + git tag final-missquash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 4 = $(wc -l Date: Fri, 8 Jan 2010 05:53:56 -0800 Subject: [PATCH 014/118] ident.c: remove unused variables d5cc2de (ident.c: Trim hint printed when gecos is empty., 2006-11-28) reworded the message used as printf() format and dropped "%s" from it; these two variables that hold the names of GIT_{AUTHOR,COMMITTER}_NAME environment variables haven't been used since then. Signed-off-by: Junio C Hamano --- ident.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ident.c b/ident.c index 26409b2a1b..e6c1798d98 100644 --- a/ident.c +++ b/ident.c @@ -168,8 +168,6 @@ static int copy(char *buf, size_t size, int offset, const char *src) return offset; } -static const char au_env[] = "GIT_AUTHOR_NAME"; -static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = "\n" "*** Please tell me who you are.\n" @@ -204,7 +202,7 @@ const char *fmt_ident(const char *name, const char *email, if ((warn_on_no_name || error_on_no_name) && name == git_default_name && env_hint) { - fprintf(stderr, env_hint, au_env, co_env); + fprintf(stderr, env_hint); env_hint = NULL; /* warn only once */ } if (error_on_no_name) From 472e746991db09fea7f11e4cb856c2cec9dc282b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 23:07:17 -0800 Subject: [PATCH 015/118] t3001: test ls-files -o ignored/dir When you have "t" directory that is marked as ignored in the top-level .gitignore file (or $GIT_DIR/info/exclude), running $ git ls-files -o --exclude-standard from the top-level correctly excludes files in "t" directory, but any of the following: $ git ls-files -o --exclude-standard t/ $ cd t && git ls-files -o --exclude-standard would show untracked files in that directory. Signed-off-by: Junio C Hamano --- t/t3001-ls-files-others-exclude.sh | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index c65bca8388..e3e4d714a1 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -153,4 +153,43 @@ test_expect_success 'negated exclude matches can override previous ones' ' grep "^a.1" output ' +test_expect_success 'subdirectory ignore (setup)' ' + mkdir -p top/l1/l2 && + ( + cd top && + git init && + echo /.gitignore >.gitignore && + echo l1 >>.gitignore && + echo l2 >l1/.gitignore && + >l1/l2/l1 + ) +' + +test_expect_success 'subdirectory ignore (toplevel)' ' + ( + cd top && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_success 'subdirectory ignore (l1/l2)' ' + ( + cd top/l1/l2 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + +test_expect_failure 'subdirectory ignore (l1)' ' + ( + cd top/l1 && + git ls-files -o --exclude-standard + ) >actual && + >expect && + test_cmp expect actual +' + test_done From 53cc5356fb591d0efa9d2725f8430afe5f5630b5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 19:14:07 -0800 Subject: [PATCH 016/118] read_directory_recursive(): refactor handling of a single path into a separate function Primarily because I want to reuse it in a separate function later, but this de-dents a huge function by one tabstop which by itself is an improvement as well. Signed-off-by: Junio C Hamano --- dir.c | 153 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 63 deletions(-) diff --git a/dir.c b/dir.c index d0999ba055..dec8365de7 100644 --- a/dir.c +++ b/dir.c @@ -625,6 +625,84 @@ static int get_dtype(struct dirent *de, const char *path, int len) return dtype; } +enum path_treatment { + path_ignored, + path_handled, + path_recurse, +}; + +static enum path_treatment treat_path(struct dir_struct *dir, + struct dirent *de, + char *path, int path_max, + int baselen, + const struct path_simplify *simplify, + int *len) +{ + int dtype, exclude; + + if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) + return path_ignored; + *len = strlen(de->d_name); + /* Ignore overly long pathnames! */ + if (*len + baselen + 8 > path_max) + return path_ignored; + memcpy(path + baselen, de->d_name, *len + 1); + *len += baselen; + if (simplify_away(path, *len, simplify)) + return path_ignored; + + dtype = DTYPE(de); + exclude = excluded(dir, path, &dtype); + if (exclude && (dir->flags & DIR_COLLECT_IGNORED) + && in_pathspec(path, *len, simplify)) + dir_add_ignored(dir, path, *len); + + /* + * Excluded? If we don't explicitly want to show + * ignored files, ignore it + */ + if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) + return path_ignored; + + if (dtype == DT_UNKNOWN) + dtype = get_dtype(de, path, *len); + + /* + * Do we want to see just the ignored files? + * We still need to recurse into directories, + * even if we don't ignore them, since the + * directory may contain files that we do.. + */ + if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { + if (dtype != DT_DIR) + return path_ignored; + } + + switch (dtype) { + default: + return path_ignored; + case DT_DIR: + memcpy(path + *len, "/", 2); + (*len)++; + switch (treat_directory(dir, path, *len, simplify)) { + case show_directory: + if (exclude != !!(dir->flags + & DIR_SHOW_IGNORED)) + return path_ignored; + break; + case recurse_into_directory: + return path_recurse; + case ignore_directory: + return path_ignored; + } + break; + case DT_REG: + case DT_LNK: + break; + } + return path_handled; +} + /* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git @@ -634,7 +712,10 @@ static int get_dtype(struct dirent *de, const char *path, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify) +static int read_directory_recursive(struct dir_struct *dir, + const char *base, int baselen, + int check_only, + const struct path_simplify *simplify) { DIR *fdir = opendir(*base ? base : "."); int contents = 0; @@ -645,70 +726,16 @@ static int read_directory_recursive(struct dir_struct *dir, const char *base, in memcpy(path, base, baselen); while ((de = readdir(fdir)) != NULL) { - int len, dtype; - int exclude; - - if (is_dot_or_dotdot(de->d_name) || - !strcmp(de->d_name, ".git")) + int len; + switch (treat_path(dir, de, path, sizeof(path), + baselen, simplify, &len)) { + case path_recurse: + contents += read_directory_recursive + (dir, path, len, 0, simplify); continue; - len = strlen(de->d_name); - /* Ignore overly long pathnames! */ - if (len + baselen + 8 > sizeof(path)) + case path_ignored: continue; - memcpy(path + baselen, de->d_name, len+1); - len = baselen + len; - if (simplify_away(path, len, simplify)) - continue; - - dtype = DTYPE(de); - exclude = excluded(dir, path, &dtype); - if (exclude && (dir->flags & DIR_COLLECT_IGNORED) - && in_pathspec(path, len, simplify)) - dir_add_ignored(dir, path,len); - - /* - * Excluded? If we don't explicitly want to show - * ignored files, ignore it - */ - if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) - continue; - - if (dtype == DT_UNKNOWN) - dtype = get_dtype(de, path, len); - - /* - * Do we want to see just the ignored files? - * We still need to recurse into directories, - * even if we don't ignore them, since the - * directory may contain files that we do.. - */ - if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { - if (dtype != DT_DIR) - continue; - } - - switch (dtype) { - default: - continue; - case DT_DIR: - memcpy(path + len, "/", 2); - len++; - switch (treat_directory(dir, path, len, simplify)) { - case show_directory: - if (exclude != !!(dir->flags - & DIR_SHOW_IGNORED)) - continue; - break; - case recurse_into_directory: - contents += read_directory_recursive(dir, - path, len, 0, simplify); - continue; - case ignore_directory: - continue; - } - break; - case DT_REG: - case DT_LNK: + case path_handled: break; } contents++; From 16e2cfa90993b23bda8ff1ffb958cac20e58b058 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 20:56:16 -0800 Subject: [PATCH 017/118] read_directory(): further split treat_path() The next caller I'll be adding won't have an access to struct dirent because it won't be reading from a directory stream. Split the main part of the function further into a separate function to make it usable by a caller without passing a dirent as long as it knows what type is feeding the function. Signed-off-by: Junio C Hamano --- dir.c | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/dir.c b/dir.c index dec8365de7..35cc89b07e 100644 --- a/dir.c +++ b/dir.c @@ -631,28 +631,12 @@ enum path_treatment { path_recurse, }; -static enum path_treatment treat_path(struct dir_struct *dir, - struct dirent *de, - char *path, int path_max, - int baselen, - const struct path_simplify *simplify, - int *len) +static enum path_treatment treat_one_path(struct dir_struct *dir, + char *path, int *len, + const struct path_simplify *simplify, + int dtype, struct dirent *de) { - int dtype, exclude; - - if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) - return path_ignored; - *len = strlen(de->d_name); - /* Ignore overly long pathnames! */ - if (*len + baselen + 8 > path_max) - return path_ignored; - memcpy(path + baselen, de->d_name, *len + 1); - *len += baselen; - if (simplify_away(path, *len, simplify)) - return path_ignored; - - dtype = DTYPE(de); - exclude = excluded(dir, path, &dtype); + int exclude = excluded(dir, path, &dtype); if (exclude && (dir->flags & DIR_COLLECT_IGNORED) && in_pathspec(path, *len, simplify)) dir_add_ignored(dir, path, *len); @@ -703,6 +687,30 @@ static enum path_treatment treat_path(struct dir_struct *dir, return path_handled; } +static enum path_treatment treat_path(struct dir_struct *dir, + struct dirent *de, + char *path, int path_max, + int baselen, + const struct path_simplify *simplify, + int *len) +{ + int dtype; + + if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git")) + return path_ignored; + *len = strlen(de->d_name); + /* Ignore overly long pathnames! */ + if (*len + baselen + 8 > path_max) + return path_ignored; + memcpy(path + baselen, de->d_name, *len + 1); + *len += baselen; + if (simplify_away(path, *len, simplify)) + return path_ignored; + + dtype = DTYPE(de); + return treat_one_path(dir, path, len, simplify, dtype, de); +} + /* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git From 48ffef966c762578eb818c0c54a7e11dd054f5db Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 23:05:41 -0800 Subject: [PATCH 018/118] ls-files: fix overeager pathspec optimization Given pathspecs that share a common prefix, ls-files optimized its call into recursive directory reader by starting at the common prefix directory. If you have a directory "t" with an untracked file "t/junk" in it, but the top-level .gitignore file told us to ignore "t/", this resulted in: $ git ls-files -o --exclude-standard $ git ls-files -o --exclude-standard t/ t/junk $ git ls-files -o --exclude-standard t/junk t/junk $ cd t && git ls-files -o --exclude-standard junk We could argue that you are overriding the ignore file by giving a patchspec that matches or being in that directory, but it is somewhat unexpected. Worse yet, these behave differently: $ git ls-files -o --exclude-standard t/ . $ git ls-files -o --exclude-standard t/ t/junk This patch changes the optimization so that it notices when the common prefix directory that it starts reading from is an ignored one. Signed-off-by: Junio C Hamano --- dir.c | 38 +++++++++++++++++++++++++++++- t/t3001-ls-files-others-exclude.sh | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/dir.c b/dir.c index 35cc89b07e..00d698d79f 100644 --- a/dir.c +++ b/dir.c @@ -813,6 +813,41 @@ static void free_simplify(struct path_simplify *simplify) free(simplify); } +static int treat_leading_path(struct dir_struct *dir, + const char *path, int len, + const struct path_simplify *simplify) +{ + char pathbuf[PATH_MAX]; + int baselen, blen; + const char *cp; + + while (len && path[len - 1] == '/') + len--; + if (!len) + return 1; + baselen = 0; + while (1) { + cp = path + baselen + !!baselen; + cp = memchr(cp, '/', path + len - cp); + if (!cp) + baselen = len; + else + baselen = cp - path; + memcpy(pathbuf, path, baselen); + pathbuf[baselen] = '\0'; + if (!is_directory(pathbuf)) + return 0; + if (simplify_away(pathbuf, baselen, simplify)) + return 0; + blen = baselen; + if (treat_one_path(dir, pathbuf, &blen, simplify, + DT_DIR, NULL) == path_ignored) + return 0; /* do not recurse into it */ + if (len <= baselen) + return 1; /* finished checking */ + } +} + int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec) { struct path_simplify *simplify; @@ -821,7 +856,8 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const char return dir->nr; simplify = create_simplify(pathspec); - read_directory_recursive(dir, path, len, 0, simplify); + if (!len || treat_leading_path(dir, path, len, simplify)) + read_directory_recursive(dir, path, len, 0, simplify); free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name); diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index e3e4d714a1..9e71260ad0 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -183,7 +183,7 @@ test_expect_success 'subdirectory ignore (l1/l2)' ' test_cmp expect actual ' -test_expect_failure 'subdirectory ignore (l1)' ' +test_expect_success 'subdirectory ignore (l1)' ' ( cd top/l1 && git ls-files -o --exclude-standard From db367136605868f5971d42501a48820ccdcb8b08 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sat, 9 Jan 2010 20:02:40 -0800 Subject: [PATCH 019/118] difftool--helper: Update copyright and remove distracting comments Some of the comments in git-difftool--helper are not needed because the code is sufficiently readable without them. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- git-difftool--helper.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 57e8e3256d..1b138083d3 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -3,9 +3,8 @@ # This script is typically launched by using the 'git difftool' # convenience command. # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009-2010 David Aguilar -# Load common functions from git-mergetool--lib TOOL_MODE=diff . git-mergetool--lib @@ -20,7 +19,6 @@ should_prompt () { fi } -# Sets up shell variables and runs a merge tool launch_merge_tool () { # Merged is the filename as it appears in the work tree # Local is the contents of a/filename @@ -39,7 +37,6 @@ launch_merge_tool () { read ans fi - # Run the appropriate merge tool command run_merge_tool "$merge_tool" } From 61ed71dcff8448b0700ef032aa1f962649306624 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sat, 9 Jan 2010 20:02:41 -0800 Subject: [PATCH 020/118] difftool--helper: Remove use of the GIT_MERGE_TOOL variable An undocumented mis-feature in git-difftool is that it allows you to specify a default difftool by setting GIT_MERGE_TOOL. This behavior was never documented and was included as an oversight back when git-difftool was maintained outside of git. git-mergetool never honored GIT_MERGE_TOOL so neither should git-difftool. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- git-difftool--helper.sh | 9 ++++----- t/t7800-difftool.sh | 9 --------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 1b138083d3..3621f28c79 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -40,11 +40,10 @@ launch_merge_tool () { run_merge_tool "$merge_tool" } -# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values -test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" -test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL" - -if test -z "$merge_tool"; then +# GIT_DIFF_TOOL indicates that --tool=... was specified +if test -n "$GIT_DIFF_TOOL"; then + merge_tool="$GIT_DIFF_TOOL" +else merge_tool="$(get_merge_tool)" || exit fi diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 9bf6c98c54..eca51a8fe8 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -32,7 +32,6 @@ restore_test_defaults() # Restores the test defaults used by several tests remove_config_vars unset GIT_DIFF_TOOL - unset GIT_MERGE_TOOL unset GIT_DIFFTOOL_PROMPT unset GIT_DIFFTOOL_NO_PROMPT git config diff.tool test-tool && @@ -107,15 +106,7 @@ test_expect_success 'GIT_DIFF_TOOL overrides' ' git config diff.tool bogus-tool && git config merge.tool bogus-tool && - GIT_MERGE_TOOL=test-tool && - export GIT_MERGE_TOOL && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - unset GIT_MERGE_TOOL && - - GIT_MERGE_TOOL=bogus-tool && GIT_DIFF_TOOL=test-tool && - export GIT_MERGE_TOOL && export GIT_DIFF_TOOL && diff=$(git difftool --no-prompt branch) && From 1c6f5b52b7b13bbc6cf404cb5ef9e64fda37655c Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sat, 9 Jan 2010 20:02:42 -0800 Subject: [PATCH 021/118] difftool: Allow specifying unconfigured commands with --extcmd git-difftool requires difftool..cmd configuration even when tools use the standard "$diffcmd $from $to" form. This teaches git-difftool to run these tools in lieu of configuration by allowing the command to be specified on the command line. Reference: http://article.gmane.org/gmane.comp.version-control.git/133377 Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- Documentation/git-difftool.txt | 5 +++++ git-difftool--helper.sh | 30 +++++++++++++++++++++++------- git-difftool.perl | 4 ++++ t/t7800-difftool.sh | 19 ++++++++++++++++++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index a5bce6278b..f67d2db761 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -58,6 +58,11 @@ is set to the name of the temporary file containing the contents of the diff post-image. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$LOCAL`. +--extcmd=:: + Specify a custom command for viewing diffs. + 'git-difftool' ignores the configured defaults and runs + `$command $LOCAL $REMOTE` when this option is specified. + -g:: --gui:: When 'git-difftool' is invoked with the `-g` or `--gui` option diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 3621f28c79..d806eaef54 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -19,6 +19,11 @@ should_prompt () { fi } +# Indicates that --extcmd=... was specified +use_ext_cmd () { + test -n "$GIT_DIFFTOOL_EXTCMD" +} + launch_merge_tool () { # Merged is the filename as it appears in the work tree # Local is the contents of a/filename @@ -33,18 +38,29 @@ launch_merge_tool () { # the user with the real $MERGED name before launching $merge_tool. if should_prompt; then printf "\nViewing: '$MERGED'\n" - printf "Hit return to launch '%s': " "$merge_tool" + if use_ext_cmd; then + printf "Hit return to launch '%s': " \ + "$GIT_DIFFTOOL_EXTCMD" + else + printf "Hit return to launch '%s': " "$merge_tool" + fi read ans fi - run_merge_tool "$merge_tool" + if use_ext_cmd; then + $GIT_DIFFTOOL_EXTCMD "$LOCAL" "$REMOTE" + else + run_merge_tool "$merge_tool" + fi + } -# GIT_DIFF_TOOL indicates that --tool=... was specified -if test -n "$GIT_DIFF_TOOL"; then - merge_tool="$GIT_DIFF_TOOL" -else - merge_tool="$(get_merge_tool)" || exit +if ! use_ext_cmd; then + if test -n "$GIT_DIFF_TOOL"; then + merge_tool="$GIT_DIFF_TOOL" + else + merge_tool="$(get_merge_tool)" || exit + fi fi # Launch the merge tool on each path provided by 'git diff' diff --git a/git-difftool.perl b/git-difftool.perl index 8c836e4c76..f8ff245756 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -62,6 +62,10 @@ sub generate_command $skip_next = 1; next; } + if ($arg =~ /^--extcmd=/) { + $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9); + next; + } if ($arg =~ /^--tool=/) { $ENV{GIT_DIFF_TOOL} = substr($arg, 7); next; diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index eca51a8fe8..8ee186a5fb 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -214,7 +214,24 @@ test_expect_success 'difftool..path' ' diff=$(git difftool --tool=tkdiff --no-prompt branch) && git config --unset difftool.tkdiff.path && lines=$(echo "$diff" | grep file | wc -l) && - test "$lines" -eq 1 + test "$lines" -eq 1 && + + restore_test_defaults +' + +test_expect_success 'difftool --extcmd=...' ' + diff=$(git difftool --no-prompt --extcmd=cat branch) && + + lines=$(echo "$diff" | wc -l) && + test "$lines" -eq 2 && + + lines=$(echo "$diff" | grep master | wc -l) && + test "$lines" -eq 1 && + + lines=$(echo "$diff" | grep branch | wc -l) && + test "$lines" -eq 1 && + + restore_test_defaults ' test_done From 3bdfd44309822d4a2c49d381999ad5cc7e61ed5f Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sat, 9 Jan 2010 20:02:43 -0800 Subject: [PATCH 022/118] git-diff.txt: Link to git-difftool Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- Documentation/git-diff.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 0ac711230e..723a64872f 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -157,6 +157,10 @@ $ git diff -R <2> rewrites (very expensive). <2> Output diff in reverse. +SEE ALSO +-------- +linkgit:git-difftool[1]:: + Show changes using common diff tools Author ------ From 1945237486851bc3a0c6d65eaeb21cb22cac2ea1 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:40 +0800 Subject: [PATCH 023/118] t5541-http-push.sh: add tests for non-fast-forward pushes Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- t/t5541-http-push.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 2a58d0cc9c..f49c7c4178 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -88,5 +88,28 @@ test_expect_success 'used receive-pack service' ' test_cmp exp act ' +test_expect_success 'non-fast-forward push fails' ' + cd "$ROOT_PATH"/test_repo_clone && + git checkout master && + echo "changed" > path2 && + git commit -a -m path2 --amend && + + HEAD=$(git rev-parse --verify HEAD) && + !(git push -v origin >output 2>&1) && + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + test $HEAD != $(git rev-parse --verify HEAD)) +' + +test_expect_failure 'non-fast-forward push show ref status' ' + grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output +' + +test_expect_failure 'non-fast-forward push shows help message' ' + grep \ +"To prevent you from losing history, non-fast-forward updates were rejected +Merge the remote changes before pushing again. See the '"'non-fast-forward'"' +section of '"'git push --help'"' for details." output +' + stop_httpd test_done From 7b69079be964ca0a09a1a7895455ba3df379984e Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:41 +0800 Subject: [PATCH 024/118] t5541-http-push.sh: add test for unmatched, non-fast-forwarded refs Some refs can only be matched to a remote ref with an explicit refspec. When such a ref is a non-fast-forward of its remote ref, test that pushing them (with the explicit refspec specified) fails with a non- fast-foward-type error (viz. printing of ref status and help message). Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- t/t5541-http-push.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index f49c7c4178..cc740fe124 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -111,5 +111,26 @@ Merge the remote changes before pushing again. See the '"'non-fast-forward'"' section of '"'git push --help'"' for details." output ' +test_expect_failure 'push fails for non-fast-forward refs unmatched by remote helper' ' + # create a dissimilarly-named remote ref so that git is unable to match the + # two refs (viz. local, remote) unless an explicit refspec is provided. + git push origin master:retsam + + echo "change changed" > path2 && + git commit -a -m path2 --amend && + + # push master too; this ensures there is at least one '"'push'"' command to + # the remote helper and triggers interaction with the helper. + !(git push -v origin +master master:retsam >output 2>&1) && + + grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output && + grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output && + + grep \ +"To prevent you from losing history, non-fast-forward updates were rejected +Merge the remote changes before pushing again. See the '"'non-fast-forward'"' +section of '"'git push --help'"' for details." output +' + stop_httpd test_done From 20e8b465a53e651cc3f50bd60f39d577ecdb7722 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:42 +0800 Subject: [PATCH 025/118] refactor ref status logic for pushing Move the logic that detects up-to-date and non-fast-forward refs to a new function in remote.[ch], set_ref_status_for_push(). Make transport_push() invoke set_ref_status_for_push() before invoking the push_refs() implementation. (As a side-effect, the push_refs() implementation in transport-helper.c now knows of non-fast-forward pushes.) Removed logic for detecting up-to-date refs from the push_refs() implementation in transport-helper.c, as transport_push() has already done so for it. Make cmd_send_pack() invoke set_ref_status_for_push() before invoking send_pack(), as transport_push() can't do it for send_pack() here. Mark the test on the return status of non-fast-forward push to fail. Git now exits with success, as transport.c::transport_push() does not check for refs with status REF_STATUS_REJECT_NONFASTFORWARD nor does it indicate rejected pushes with its return value. Mark the test for ref status to succeed. As mentioned earlier, refs might be marked as non-fast-forwards, triggering the push status printing mechanism in transport.c. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- builtin-send-pack.c | 53 +++++++++++--------------------------------- remote.c | 50 +++++++++++++++++++++++++++++++++++++++++ remote.h | 2 ++ t/t5541-http-push.sh | 4 ++-- transport-helper.c | 14 ++++++------ transport.c | 4 ++++ 6 files changed, 78 insertions(+), 49 deletions(-) diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 8fffdbf200..76c72065de 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -406,52 +406,22 @@ int send_pack(struct send_pack_args *args, */ new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { - - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!args->send_mirror) + if (!ref->peer_ref && !args->send_mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: + continue; + default: + ; /* do nothing */ + } + if (ref->deletion && !allow_deleting_refs) { ref->status = REF_STATUS_REJECT_NODELETE; continue; } - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; - continue; - } - - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - ref->nonfastforward = - !ref->deletion && - !is_null_sha1(ref->old_sha1) && - (!has_sha1_file(ref->old_sha1) - || !ref_newer(ref->new_sha1, ref->old_sha1)); - - if (ref->nonfastforward && !ref->force && !args->force_update) { - ref->status = REF_STATUS_REJECT_NONFASTFORWARD; - continue; - } if (!ref->deletion) new_refs++; @@ -673,6 +643,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; + set_ref_status_for_push(remote_refs, args.send_mirror, + args.force_update); + ret = send_pack(&args, fd, conn, remote_refs, &extra_have); if (helper_status) diff --git a/remote.c b/remote.c index e3afecdb10..c70181cdc6 100644 --- a/remote.c +++ b/remote.c @@ -1247,6 +1247,56 @@ int match_refs(struct ref *src, struct ref **dst, return 0; } +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update) +{ + struct ref *ref; + + for (ref = remote_refs; ref; ref = ref->next) { + if (ref->peer_ref) + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + else if (!send_mirror) + continue; + + ref->deletion = is_null_sha1(ref->new_sha1); + if (!ref->deletion && + !hashcmp(ref->old_sha1, ref->new_sha1)) { + ref->status = REF_STATUS_UPTODATE; + continue; + } + + /* This part determines what can overwrite what. + * The rules are: + * + * (0) you can always use --force or +A:B notation to + * selectively force individual ref pairs. + * + * (1) if the old thing does not exist, it is OK. + * + * (2) if you do not have the old thing, you are not allowed + * to overwrite it; you would not know what you are losing + * otherwise. + * + * (3) if both new and old are commit-ish, and new is a + * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. + */ + + ref->nonfastforward = + !ref->deletion && + !is_null_sha1(ref->old_sha1) && + (!has_sha1_file(ref->old_sha1) + || !ref_newer(ref->new_sha1, ref->old_sha1)); + + if (ref->nonfastforward && !ref->force && !force_update) { + ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + continue; + } + } +} + struct branch *branch_get(const char *name) { struct branch *ret; diff --git a/remote.h b/remote.h index 8b7ecf9197..6e13643cab 100644 --- a/remote.h +++ b/remote.h @@ -98,6 +98,8 @@ char *apply_refspecs(struct refspec *refspecs, int nr_refspec, int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int all); +void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, + int force_update); /* * Given a list of the remote refs and the specification of things to diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index cc740fe124..6d92196d24 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -88,7 +88,7 @@ test_expect_success 'used receive-pack service' ' test_cmp exp act ' -test_expect_success 'non-fast-forward push fails' ' +test_expect_failure 'non-fast-forward push fails' ' cd "$ROOT_PATH"/test_repo_clone && git checkout master && echo "changed" > path2 && @@ -100,7 +100,7 @@ test_expect_success 'non-fast-forward push fails' ' test $HEAD != $(git rev-parse --verify HEAD)) ' -test_expect_failure 'non-fast-forward push show ref status' ' +test_expect_success 'non-fast-forward push show ref status' ' grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output ' diff --git a/transport-helper.c b/transport-helper.c index 11f3d7ec52..7c9b569d94 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -329,16 +329,16 @@ static int push_refs(struct transport *transport, return 1; for (ref = remote_refs; ref; ref = ref->next) { - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!mirror) + if (!ref->peer_ref && !mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: continue; + default: + ; /* do nothing */ } if (force_all) diff --git a/transport.c b/transport.c index 3eea836a33..12c4423f79 100644 --- a/transport.c +++ b/transport.c @@ -887,6 +887,10 @@ int transport_push(struct transport *transport, return -1; } + set_ref_status_for_push(remote_refs, + flags & TRANSPORT_PUSH_MIRROR, + flags & TRANSPORT_PUSH_FORCE); + ret = transport->push_refs(transport, remote_refs, flags); if (!quiet || push_had_errors(remote_refs)) From 4232826771d5bdc4cc0bd21188b6ee5f3e700a52 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:43 +0800 Subject: [PATCH 026/118] transport.c::transport_push(): make ref status affect return value Use push_had_errors() to check the refs for errors and modify the return value. Mark the non-fast-forward push tests to succeed. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- t/t5541-http-push.sh | 4 ++-- transport.c | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 6d92196d24..979624d0dc 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -88,7 +88,7 @@ test_expect_success 'used receive-pack service' ' test_cmp exp act ' -test_expect_failure 'non-fast-forward push fails' ' +test_expect_success 'non-fast-forward push fails' ' cd "$ROOT_PATH"/test_repo_clone && git checkout master && echo "changed" > path2 && @@ -104,7 +104,7 @@ test_expect_success 'non-fast-forward push show ref status' ' grep "^ ! \[rejected\][ ]*master -> master (non-fast-forward)$" output ' -test_expect_failure 'non-fast-forward push shows help message' ' +test_expect_success 'non-fast-forward push shows help message' ' grep \ "To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes before pushing again. See the '"'non-fast-forward'"' diff --git a/transport.c b/transport.c index 12c4423f79..9b23989117 100644 --- a/transport.c +++ b/transport.c @@ -875,7 +875,7 @@ int transport_push(struct transport *transport, int verbose = flags & TRANSPORT_PUSH_VERBOSE; int quiet = flags & TRANSPORT_PUSH_QUIET; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; - int ret; + int ret, err; if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -892,8 +892,11 @@ int transport_push(struct transport *transport, flags & TRANSPORT_PUSH_FORCE); ret = transport->push_refs(transport, remote_refs, flags); + err = push_had_errors(remote_refs); - if (!quiet || push_had_errors(remote_refs)) + ret |= err; + + if (!quiet || err) print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); From 08d63a422ba7293119865e6cbbc3a34619be32f7 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:44 +0800 Subject: [PATCH 027/118] transport-helper.c::push_refs(): ignore helper-reported status if ref is not to be pushed If the status of a ref is REF_STATUS_NONE, the remote helper will not be told to push the ref (via a 'push' command). However, the remote helper may still act on these refs. If the helper does act on the ref, and prints a status for it, ignore the report (ie. don't overwrite the status of the ref with it, nor the message in the remote_status member) if the reported status is 'no match'. This allows the user to be alerted to more "interesting" ref statuses, like REF_STATUS_NONFASTFORWARD. Cc: Jeff King Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- t/t5541-http-push.sh | 2 +- transport-helper.c | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 979624d0dc..83a8e14c6c 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -111,7 +111,7 @@ Merge the remote changes before pushing again. See the '"'non-fast-forward'"' section of '"'git push --help'"' for details." output ' -test_expect_failure 'push fails for non-fast-forward refs unmatched by remote helper' ' +test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' ' # create a dissimilarly-named remote ref so that git is unable to match the # two refs (viz. local, remote) unless an explicit refspec is provided. git push origin master:retsam diff --git a/transport-helper.c b/transport-helper.c index 7c9b569d94..71a1e50ee7 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -430,6 +430,15 @@ static int push_refs(struct transport *transport, continue; } + if (ref->status != REF_STATUS_NONE) { + /* + * Earlier, the ref was marked not to be pushed, so ignore the ref + * status reported by the remote helper if the latter is 'no match'. + */ + if (status == REF_STATUS_NONE) + continue; + } + ref->status = status; ref->remote_status = msg; } From c1ceea1d273925fe6ecb0824e7ea08eb6e6e2635 Mon Sep 17 00:00:00 2001 From: Tay Ray Chuan Date: Fri, 8 Jan 2010 10:12:45 +0800 Subject: [PATCH 028/118] transport-helper.c::push_refs(): emit "no refs" error message Emit an error message when remote_refs is not set. This behaviour is consistent with that of builtin-send-pack.c and http-push.c. Signed-off-by: Tay Ray Chuan Signed-off-by: Junio C Hamano --- transport-helper.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/transport-helper.c b/transport-helper.c index 71a1e50ee7..8c0b575f32 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -321,8 +321,11 @@ static int push_refs(struct transport *transport, struct child_process *helper; struct ref *ref; - if (!remote_refs) + if (!remote_refs) { + fprintf(stderr, "No refs in common and none specified; doing nothing.\n" + "Perhaps you should specify a branch such as 'master'.\n"); return 0; + } helper = get_helper(transport); if (!data->push) From 48cc95ed4a5c4554f8914af3f300558da31a22dc Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 8 Jan 2010 22:36:40 -0500 Subject: [PATCH 029/118] Test update-index for a gitlink to a .git file Check that update-index recognizes a submodule that uses a .git file. Currently it works when the .git file specifies an absolute path, but not when it specifies a relative path. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- t/t2104-update-index-gitfile.sh | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 t/t2104-update-index-gitfile.sh diff --git a/t/t2104-update-index-gitfile.sh b/t/t2104-update-index-gitfile.sh new file mode 100755 index 0000000000..ba719846a6 --- /dev/null +++ b/t/t2104-update-index-gitfile.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2010 Brad King +# + +test_description='git update-index for gitlink to .git file. +' + +. ./test-lib.sh + +test_expect_success 'submodule with absolute .git file' ' + mkdir sub1 && + (cd sub1 && + git init && + REAL="$(pwd)/.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git && + test_commit first) +' + +test_expect_success 'add gitlink to absolute .git file' ' + git update-index --add -- sub1 +' + +test_expect_success 'submodule with relative .git file' ' + mkdir sub2 && + (cd sub2 && + git init && + mv .git .real && + echo "gitdir: .real" >.git && + test_commit first) +' + +test_expect_failure 'add gitlink to relative .git file' ' + git update-index --add -- sub2 +' + +test_done From 40c813e00c8de5725c7a6bb127fadedcce1f788f Mon Sep 17 00:00:00 2001 From: Brad King Date: Fri, 8 Jan 2010 22:36:41 -0500 Subject: [PATCH 030/118] Handle relative paths in submodule .git files Commit 842abf0 (Teach resolve_gitlink_ref() about the .git file, 2008-02-20) taught resolve_gitlink_ref() to call read_gitfile_gently() to resolve .git files. In this commit teach read_gitfile_gently() to interpret a relative path in a .git file with respect to the file location. This change allows update-index to recognize a submodule that uses a relative path in its .git file. It previously failed because the relative path was wrongly interpreted with respect to the superproject directory. Signed-off-by: Brad King Signed-off-by: Junio C Hamano --- setup.c | 22 +++++++++++++++++++--- t/t2104-update-index-gitfile.sh | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/setup.c b/setup.c index 2cf0f19937..7fc4251713 100644 --- a/setup.c +++ b/setup.c @@ -252,6 +252,8 @@ static int check_repository_format_gently(int *nongit_ok) const char *read_gitfile_gently(const char *path) { char *buf; + char *dir; + const char *slash; struct stat st; int fd; size_t len; @@ -276,9 +278,23 @@ const char *read_gitfile_gently(const char *path) if (len < 9) die("No path in gitfile: %s", path); buf[len] = '\0'; - if (!is_git_directory(buf + 8)) - die("Not a git repository: %s", buf + 8); - path = make_absolute_path(buf + 8); + dir = buf + 8; + + if (!is_absolute_path(dir) && (slash = strrchr(path, '/'))) { + size_t pathlen = slash+1 - path; + size_t dirlen = pathlen + len - 8; + dir = xmalloc(dirlen + 1); + strncpy(dir, path, pathlen); + strncpy(dir + pathlen, buf + 8, len - 8); + dir[dirlen] = '\0'; + free(buf); + buf = dir; + } + + if (!is_git_directory(dir)) + die("Not a git repository: %s", dir); + path = make_absolute_path(dir); + free(buf); return path; } diff --git a/t/t2104-update-index-gitfile.sh b/t/t2104-update-index-gitfile.sh index ba719846a6..641607d89a 100755 --- a/t/t2104-update-index-gitfile.sh +++ b/t/t2104-update-index-gitfile.sh @@ -31,7 +31,7 @@ test_expect_success 'submodule with relative .git file' ' test_commit first) ' -test_expect_failure 'add gitlink to relative .git file' ' +test_expect_success 'add gitlink to relative .git file' ' git update-index --add -- sub2 ' From 91c38a21089c4b30d35f392386c752a017ac6db0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 07:39:11 -0800 Subject: [PATCH 031/118] ident.c: check explicit identity for name and email separately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bb1ae3f (commit: Show committer if automatic, 2008-05-04) added a logic to check both name and email were given explicitly by the end user, but it assumed that fmt_ident() is never called before git_default_user_config() is called, which was fragile. The former calls setup_ident() and fills the "default" name and email, so the check in the config parser would have mistakenly said both are given even if only user.name was provided. Make the logic more robust by keeping track of name and email separately. Signed-off-by: Junio C Hamano Acked-by: Santi Béjar --- builtin-commit.c | 2 +- cache.h | 3 +++ config.c | 6 ++---- ident.c | 7 ++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 073fe90ba1..f4974b5542 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -624,7 +624,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, author_ident); free(author_ident); - if (!user_ident_explicitly_given) + if (user_ident_explicitly_given != IDENT_ALL_GIVEN) fprintf(fp, "%s" "# Committer: %s\n", diff --git a/cache.h b/cache.h index bf468e5235..16c8e8df41 100644 --- a/cache.h +++ b/cache.h @@ -925,6 +925,9 @@ extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) extern char git_default_email[MAX_GITNAME]; extern char git_default_name[MAX_GITNAME]; +#define IDENT_NAME_GIVEN 01 +#define IDENT_MAIL_GIVEN 02 +#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) extern int user_ident_explicitly_given; extern const char *git_commit_encoding; diff --git a/config.c b/config.c index 37385ce9d3..fa1a0c0299 100644 --- a/config.c +++ b/config.c @@ -528,8 +528,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_name, value, sizeof(git_default_name)); - if (git_default_email[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_NAME_GIVEN; return 0; } @@ -537,8 +536,7 @@ static int git_default_user_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); strlcpy(git_default_email, value, sizeof(git_default_email)); - if (git_default_name[0]) - user_ident_explicitly_given = 1; + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return 0; } diff --git a/ident.c b/ident.c index e6c1798d98..e67c5ad1e8 100644 --- a/ident.c +++ b/ident.c @@ -249,9 +249,10 @@ const char *git_author_info(int flag) const char *git_committer_info(int flag) { - if (getenv("GIT_COMMITTER_NAME") && - getenv("GIT_COMMITTER_EMAIL")) - user_ident_explicitly_given = 1; + if (getenv("GIT_COMMITTER_NAME")) + user_ident_explicitly_given |= IDENT_NAME_GIVEN; + if (getenv("GIT_COMMITTER_EMAIL")) + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), From 99178c831e72ba80b9edd5455b03070758c55526 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Jan 2010 08:01:10 -0800 Subject: [PATCH 032/118] ident.c: treat $EMAIL as giving user.email identity explicitly The environment variable EMAIL has been honored since 28a94f8 (Fall back to $EMAIL for missing GIT_AUTHOR_EMAIL and GIT_COMMITTER_EMAIL, 2007-04-28) as the end-user's wish to use the address as the identity. When we use it, we should say we are explicitly given email by the user. Signed-off-by: Junio C Hamano --- ident.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ident.c b/ident.c index e67c5ad1e8..d4f614543d 100644 --- a/ident.c +++ b/ident.c @@ -85,10 +85,11 @@ static void setup_ident(void) if (!git_default_email[0]) { const char *email = getenv("EMAIL"); - if (email && email[0]) + if (email && email[0]) { strlcpy(git_default_email, email, sizeof(git_default_email)); - else { + user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + } else { if (!pw) pw = getpwuid(getuid()); if (!pw) From a5487ddf0f8a8190b0722cf46dbe4cdd6ca9fa51 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sun, 10 Jan 2010 14:07:52 +0100 Subject: [PATCH 033/118] start_command: report child process setup errors to the parent's stderr When the child process's environment is set up in start_command(), error messages were written to wherever the parent redirected the child's stderr channel. However, even if the parent redirected the child's stderr, errors during this setup process, including the exec itself, are usually an indication of a problem in the parent's environment. Therefore, the error messages should go to the parent's stderr. Redirection of the child's error messages is usually only used to redirect hook error messages during client-server exchanges. In these cases, hook setup errors could be regarded as information leak. This patch makes a copy of stderr if necessary and uses a special die routine that is used for all die() calls in the child that sends the errors messages to the parent's stderr. The trace call that reported a failed execvp is removed (because it writes to stderr) and replaced by die_errno() with special treatment of ENOENT. The improvement in the error message can be seen with this sequence: mkdir .git/hooks/pre-commit git commit Previously, the error message was error: cannot run .git/hooks/pre-commit: No such file or directory and now it is fatal: cannot exec '.git/hooks/pre-commit': Permission denied Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- run-command.c | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/run-command.c b/run-command.c index cf2d8f7fae..02c7bfba8f 100644 --- a/run-command.c +++ b/run-command.c @@ -15,6 +15,30 @@ static inline void dup_devnull(int to) close(fd); } +#ifndef WIN32 +static int child_err = 2; + +static NORETURN void die_child(const char *err, va_list params) +{ + char msg[4096]; + int len = vsnprintf(msg, sizeof(msg), err, params); + if (len > sizeof(msg)) + len = sizeof(msg); + + write(child_err, "fatal: ", 7); + write(child_err, msg, len); + write(child_err, "\n", 1); + exit(128); +} + +static inline void set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags >= 0) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} +#endif + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -79,6 +103,17 @@ fail_pipe: fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { + /* + * Redirect the channel to write syscall error messages to + * before redirecting the process's stderr so that all die() + * in subsequent call paths use the parent's stderr. + */ + if (cmd->no_stderr || need_err) { + child_err = dup(2); + set_cloexec(child_err); + } + set_die_routine(die_child); + if (cmd->no_stdin) dup_devnull(0); else if (need_in) { @@ -126,9 +161,14 @@ fail_pipe: } else { execvp(cmd->argv[0], (char *const*) cmd->argv); } - trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0], - strerror(errno)); - exit(127); + /* + * Do not check for cmd->silent_exec_failure; the parent + * process will check it when it sees this exit code. + */ + if (errno == ENOENT) + exit(127); + else + die_errno("cannot exec '%s'", cmd->argv[0]); } if (cmd->pid < 0) error("cannot fork() for %s: %s", cmd->argv[0], From ab0b41daf62ec3076e980fcad492b1997b35f22b Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sun, 10 Jan 2010 14:08:45 +0100 Subject: [PATCH 034/118] run-command: move wait_or_whine earlier We want to reuse it from start_command. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- run-command.c | 84 +++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/run-command.c b/run-command.c index 02c7bfba8f..dccac37c4b 100644 --- a/run-command.c +++ b/run-command.c @@ -39,6 +39,48 @@ static inline void set_cloexec(int fd) } #endif +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) +{ + int status, code = -1; + pid_t waiting; + int failed_errno = 0; + + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + ; /* nothing */ + + if (waiting < 0) { + failed_errno = errno; + error("waitpid for %s failed: %s", argv0, strerror(errno)); + } else if (waiting != pid) { + error("waitpid is confused (%s)", argv0); + } else if (WIFSIGNALED(status)) { + code = WTERMSIG(status); + error("%s died of signal %d", argv0, code); + /* + * This return value is chosen so that code & 0xff + * mimics the exit code that a POSIX shell would report for + * a program that died from this signal. + */ + code -= 128; + } else if (WIFEXITED(status)) { + code = WEXITSTATUS(status); + /* + * Convert special exit code when execvp failed. + */ + if (code == 127) { + code = -1; + failed_errno = ENOENT; + if (!silent_exec_failure) + error("cannot run %s: %s", argv0, + strerror(ENOENT)); + } + } else { + error("waitpid is confused (%s)", argv0); + } + errno = failed_errno; + return code; +} + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -272,48 +314,6 @@ fail_pipe: return 0; } -static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) -{ - int status, code = -1; - pid_t waiting; - int failed_errno = 0; - - while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) - ; /* nothing */ - - if (waiting < 0) { - failed_errno = errno; - error("waitpid for %s failed: %s", argv0, strerror(errno)); - } else if (waiting != pid) { - error("waitpid is confused (%s)", argv0); - } else if (WIFSIGNALED(status)) { - code = WTERMSIG(status); - error("%s died of signal %d", argv0, code); - /* - * This return value is chosen so that code & 0xff - * mimics the exit code that a POSIX shell would report for - * a program that died from this signal. - */ - code -= 128; - } else if (WIFEXITED(status)) { - code = WEXITSTATUS(status); - /* - * Convert special exit code when execvp failed. - */ - if (code == 127) { - code = -1; - failed_errno = ENOENT; - if (!silent_exec_failure) - error("cannot run %s: %s", argv0, - strerror(ENOENT)); - } - } else { - error("waitpid is confused (%s)", argv0); - } - errno = failed_errno; - return code; -} - int finish_command(struct child_process *cmd) { return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure); From 2b541bf8be2bbd6cc8daf8e3d5d4a8ee30b2ce4e Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Sun, 10 Jan 2010 14:11:22 +0100 Subject: [PATCH 035/118] start_command: detect execvp failures early Previously, failures during execvp could be detected only by finish_command. However, in some situations it is beneficial for the parent process to know earlier that the child process will not run. The idea to use a pipe to signal failures to the parent process and the test case were lifted from patches by Ilari Liusvaara. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Makefile | 1 + run-command.c | 47 +++++++++++++++++++++++++++++++++++++++++- t/t0061-run-command.sh | 14 +++++++++++++ test-run-command.c | 35 +++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100755 t/t0061-run-command.sh create mode 100644 test-run-command.c diff --git a/Makefile b/Makefile index 87fc7ff476..22c1546e2f 100644 --- a/Makefile +++ b/Makefile @@ -1785,6 +1785,7 @@ 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-run-command$X TEST_PROGRAMS += test-sha1$X TEST_PROGRAMS += test-sigchain$X diff --git a/run-command.c b/run-command.c index dccac37c4b..efe9fe4138 100644 --- a/run-command.c +++ b/run-command.c @@ -17,6 +17,12 @@ static inline void dup_devnull(int to) #ifndef WIN32 static int child_err = 2; +static int child_notifier = -1; + +static void notify_parent(void) +{ + write(child_notifier, "", 1); +} static NORETURN void die_child(const char *err, va_list params) { @@ -142,6 +148,11 @@ fail_pipe: trace_argv_printf(cmd->argv, "trace: run_command:"); #ifndef WIN32 +{ + int notify_pipe[2]; + if (pipe(notify_pipe)) + notify_pipe[0] = notify_pipe[1] = -1; + fflush(NULL); cmd->pid = fork(); if (!cmd->pid) { @@ -156,6 +167,11 @@ fail_pipe: } set_die_routine(die_child); + close(notify_pipe[0]); + set_cloexec(notify_pipe[1]); + child_notifier = notify_pipe[1]; + atexit(notify_parent); + if (cmd->no_stdin) dup_devnull(0); else if (need_in) { @@ -196,8 +212,16 @@ fail_pipe: unsetenv(*cmd->env); } } - if (cmd->preexec_cb) + if (cmd->preexec_cb) { + /* + * We cannot predict what the pre-exec callback does. + * Forgo parent notification. + */ + close(child_notifier); + child_notifier = -1; + cmd->preexec_cb(); + } if (cmd->git_cmd) { execv_git_cmd(cmd->argv); } else { @@ -215,6 +239,27 @@ fail_pipe: if (cmd->pid < 0) error("cannot fork() for %s: %s", cmd->argv[0], strerror(failed_errno = errno)); + + /* + * Wait for child's execvp. If the execvp succeeds (or if fork() + * failed), EOF is seen immediately by the parent. Otherwise, the + * child process sends a single byte. + * Note that use of this infrastructure is completely advisory, + * therefore, we keep error checks minimal. + */ + close(notify_pipe[1]); + if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) { + /* + * At this point we know that fork() succeeded, but execvp() + * failed. Errors have been reported to our stderr. + */ + wait_or_whine(cmd->pid, cmd->argv[0], + cmd->silent_exec_failure); + failed_errno = errno; + cmd->pid = -1; + } + close(notify_pipe[0]); +} #else { int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh new file mode 100755 index 0000000000..10b26e4d8e --- /dev/null +++ b/t/t0061-run-command.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (c) 2009 Ilari Liusvaara +# + +test_description='Test run command' + +. ./test-lib.sh + +test_expect_success 'start_command reports ENOENT' ' + test-run-command start-command-ENOENT ./does-not-exist +' + +test_done diff --git a/test-run-command.c b/test-run-command.c new file mode 100644 index 0000000000..0612bfa7cd --- /dev/null +++ b/test-run-command.c @@ -0,0 +1,35 @@ +/* + * test-run-command.c: test run command API. + * + * (C) 2009 Ilari Liusvaara + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "git-compat-util.h" +#include "run-command.h" +#include +#include + +int main(int argc, char **argv) +{ + struct child_process proc; + + memset(&proc, 0, sizeof(proc)); + + if (argc < 3) + return 1; + proc.argv = (const char **)argv+2; + + if (!strcmp(argv[1], "start-command-ENOENT")) { + if (start_command(&proc) < 0 && errno == ENOENT) + return 0; + fprintf(stderr, "FAIL %s\n", argv[1]); + return 1; + } + + fprintf(stderr, "check usage\n"); + return 1; +} From 27d6b08536838fff0e3568a57cc622ca1c39bf01 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 14:34:53 -0800 Subject: [PATCH 036/118] rerere: refactor rerere logic to make it independent from I/O This splits the handle_file() function into in-core part and I/O parts of the logic to create the preimage, so that we can compute the conflict identifier without having to use temporary files. Earlier, I thought the output from handle_file() should also be refactored, but it is always about writing preimage (or thisimage) that is used for later three-way merge, so it is saner to keep it to always write to FILE *. Signed-off-by: Junio C Hamano --- rerere.c | 111 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/rerere.c b/rerere.c index 88bb4f102c..db1d42f1b6 100644 --- a/rerere.c +++ b/rerere.c @@ -83,8 +83,37 @@ static inline void ferr_puts(const char *s, FILE *fp, int *err) ferr_write(s, strlen(s), fp, err); } -static int handle_file(const char *path, - unsigned char *sha1, const char *output) +struct rerere_io { + int (*getline)(struct strbuf *, struct rerere_io *); + FILE *output; + int wrerror; + /* some more stuff */ +}; + +static void rerere_io_putstr(const char *str, struct rerere_io *io) +{ + if (io->output) + ferr_puts(str, io->output, &io->wrerror); +} + +static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io) +{ + if (io->output) + ferr_write(mem, sz, io->output, &io->wrerror); +} + +struct rerere_io_file { + struct rerere_io io; + FILE *input; +}; + +static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_file *io = (struct rerere_io_file *)io_; + return strbuf_getwholeline(sb, io->input, '\n'); +} + +static int handle_path(unsigned char *sha1, struct rerere_io *io) { git_SHA_CTX ctx; int hunk_no = 0; @@ -93,25 +122,11 @@ static int handle_file(const char *path, } hunk = RR_CONTEXT; struct strbuf one = STRBUF_INIT, two = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; - FILE *f = fopen(path, "r"); - FILE *out = NULL; - int wrerror = 0; - - if (!f) - return error("Could not open %s", path); - - if (output) { - out = fopen(output, "w"); - if (!out) { - fclose(f); - return error("Could not write %s", output); - } - } if (sha1) git_SHA1_Init(&ctx); - while (!strbuf_getwholeline(&buf, f, '\n')) { + while (!io->getline(&buf, io)) { if (!prefixcmp(buf.buf, "<<<<<<< ")) { if (hunk != RR_CONTEXT) goto bad; @@ -131,13 +146,11 @@ static int handle_file(const char *path, strbuf_swap(&one, &two); hunk_no++; hunk = RR_CONTEXT; - if (out) { - ferr_puts("<<<<<<<\n", out, &wrerror); - ferr_write(one.buf, one.len, out, &wrerror); - ferr_puts("=======\n", out, &wrerror); - ferr_write(two.buf, two.len, out, &wrerror); - ferr_puts(">>>>>>>\n", out, &wrerror); - } + rerere_io_putstr("<<<<<<<\n", io); + rerere_io_putmem(one.buf, one.len, io); + rerere_io_putstr("=======\n", io); + rerere_io_putmem(two.buf, two.len, io); + rerere_io_putstr(">>>>>>>\n", io); if (sha1) { git_SHA1_Update(&ctx, one.buf ? one.buf : "", one.len + 1); @@ -152,8 +165,8 @@ static int handle_file(const char *path, ; /* discard */ else if (hunk == RR_SIDE_2) strbuf_addstr(&two, buf.buf); - else if (out) - ferr_puts(buf.buf, out, &wrerror); + else + rerere_io_putstr(buf.buf, io); continue; bad: hunk = 99; /* force error exit */ @@ -163,21 +176,49 @@ static int handle_file(const char *path, strbuf_release(&two); strbuf_release(&buf); - fclose(f); - if (wrerror) - error("There were errors while writing %s (%s)", - path, strerror(wrerror)); - if (out && fclose(out)) - wrerror = error("Failed to flush %s: %s", - path, strerror(errno)); if (sha1) git_SHA1_Final(sha1, &ctx); - if (hunk != RR_CONTEXT) { + if (hunk != RR_CONTEXT) + return -1; + return hunk_no; +} + +static int handle_file(const char *path, unsigned char *sha1, const char *output) +{ + int hunk_no = 0; + struct rerere_io_file io; + + memset(&io, 0, sizeof(io)); + io.io.getline = rerere_file_getline; + io.input = fopen(path, "r"); + io.io.wrerror = 0; + if (!io.input) + return error("Could not open %s", path); + + if (output) { + io.io.output = fopen(output, "w"); + if (!io.io.output) { + fclose(io.input); + return error("Could not write %s", output); + } + } + + hunk_no = handle_path(sha1, (struct rerere_io *)&io); + + fclose(io.input); + if (io.io.wrerror) + error("There were errors while writing %s (%s)", + path, strerror(io.io.wrerror)); + if (io.io.output && fclose(io.io.output)) + io.io.wrerror = error("Failed to flush %s: %s", + path, strerror(errno)); + + if (hunk_no < 0) { if (output) unlink_or_warn(output); return error("Could not parse conflict hunks in %s", path); } - if (wrerror) + if (io.io.wrerror) return -1; return hunk_no; } From dea4562bf5d1c27cd6c01b9cb65c3a8b8ee99a69 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 25 Dec 2009 15:51:32 -0800 Subject: [PATCH 037/118] rerere forget path: forget recorded resolution After you find out an earlier resolution you told rerere to use was a mismerge, there is no easy way to clear it. A new subcommand "forget" can be used to tell git to forget a recorded resolution, so that you can redo the merge from scratch. Signed-off-by: Junio C Hamano --- builtin-rerere.c | 2 + rerere.c | 133 ++++++++++++++++++++++++++++++++++++++ rerere.h | 1 + t/t2030-unresolve-info.sh | 25 +++++++ 4 files changed, 161 insertions(+) diff --git a/builtin-rerere.c b/builtin-rerere.c index 2be9ffb77b..0253abf9b6 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -110,6 +110,8 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "-h")) usage(git_rerere_usage); + else if (!strcmp(argv[1], "forget")) + return rerere_forget(argv + 2); fd = setup_rerere(&merge_rr); if (fd < 0) diff --git a/rerere.c b/rerere.c index db1d42f1b6..d92990a6bb 100644 --- a/rerere.c +++ b/rerere.c @@ -3,6 +3,9 @@ #include "rerere.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" +#include "dir.h" +#include "resolve-undo.h" +#include "ll-merge.h" /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */ static int rerere_enabled = -1; @@ -223,6 +226,87 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output return hunk_no; } +struct rerere_io_mem { + struct rerere_io io; + struct strbuf input; +}; + +static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_mem *io = (struct rerere_io_mem *)io_; + char *ep; + size_t len; + + strbuf_release(sb); + if (!io->input.len) + return -1; + ep = strchrnul(io->input.buf, '\n'); + if (*ep == '\n') + ep++; + len = ep - io->input.buf; + strbuf_add(sb, io->input.buf, len); + strbuf_remove(&io->input, 0, len); + return 0; +} + +static int handle_cache(const char *path, unsigned char *sha1, const char *output) +{ + mmfile_t mmfile[3]; + mmbuffer_t result = {NULL, 0}; + struct cache_entry *ce; + int pos, len, i, hunk_no; + struct rerere_io_mem io; + + /* + * Reproduce the conflicted merge in-core + */ + len = strlen(path); + pos = cache_name_pos(path, len); + if (0 <= pos) + return -1; + pos = -pos - 1; + + for (i = 0; i < 3; i++) { + enum object_type type; + unsigned long size; + + mmfile[i].size = 0; + mmfile[i].ptr = NULL; + if (active_nr <= pos) + break; + ce = active_cache[pos++]; + if (ce_namelen(ce) != len || memcmp(ce->name, path, len) + || ce_stage(ce) != i + 1) + break; + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; + } + for (i = 0; i < 3; i++) { + if (!mmfile[i].ptr && !mmfile[i].size) + mmfile[i].ptr = xstrdup(""); + } + ll_merge(&result, path, &mmfile[0], + &mmfile[1], "ours", + &mmfile[2], "theirs", 0); + for (i = 0; i < 3; i++) + free(mmfile[i].ptr); + + memset(&io, 0, sizeof(&io)); + io.io.getline = rerere_mem_getline; + if (output) + io.io.output = fopen(output, "w"); + else + io.io.output = NULL; + strbuf_init(&io.input, 0); + strbuf_attach(&io.input, result.ptr, result.size, result.size); + + hunk_no = handle_path(sha1, (struct rerere_io *)&io); + strbuf_release(&io.input); + if (io.io.output) + fclose(io.io.output); + return hunk_no; +} + static int find_conflict(struct string_list *conflict) { int i; @@ -434,3 +518,52 @@ int rerere(void) return 0; return do_plain_rerere(&merge_rr, fd); } + +static int rerere_forget_one_path(const char *path, struct string_list *rr) +{ + const char *filename; + char *hex; + unsigned char sha1[20]; + int ret; + + ret = handle_cache(path, sha1, NULL); + if (ret < 1) + return error("Could not parse conflict hunks in '%s'", path); + hex = xstrdup(sha1_to_hex(sha1)); + filename = rerere_path(hex, "postimage"); + if (unlink(filename)) + return (errno == ENOENT + ? error("no remembered resolution for %s", path) + : error("cannot unlink %s: %s", filename, strerror(errno))); + + handle_cache(path, sha1, rerere_path(hex, "preimage")); + fprintf(stderr, "Updated preimage for '%s'\n", path); + + + string_list_insert(path, rr)->util = hex; + fprintf(stderr, "Forgot resolution for %s\n", path); + return 0; +} + +int rerere_forget(const char **pathspec) +{ + int i, fd; + struct string_list conflict = { NULL, 0, 0, 1 }; + struct string_list merge_rr = { NULL, 0, 0, 1 }; + + if (read_cache() < 0) + return error("Could not read index"); + + fd = setup_rerere(&merge_rr); + + unmerge_cache(pathspec); + find_conflict(&conflict); + for (i = 0; i < conflict.nr; i++) { + struct string_list_item *it = &conflict.items[i]; + if (!match_pathspec(pathspec, it->string, strlen(it->string), + 0, NULL)) + continue; + rerere_forget_one_path(it->string, &merge_rr); + } + return write_rr(&merge_rr, fd); +} diff --git a/rerere.h b/rerere.h index 13313f3f2b..36560ff2f5 100644 --- a/rerere.h +++ b/rerere.h @@ -7,5 +7,6 @@ extern int setup_rerere(struct string_list *); extern int rerere(void); extern const char *rerere_path(const char *hex, const char *file); extern int has_rerere_resolution(const char *hex); +extern int rerere_forget(const char **); #endif diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index 28e2eb1cec..a38bd6df84 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -115,4 +115,29 @@ test_expect_success 'unmerge with plumbing' ' test $(wc -l actual && + echo "$rerere_id file" >expect && + test_cmp expect actual +' + test_done From 7cceca5cccdcf1f0f9caa80b82d26fcff65e6fdf Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Tue, 12 Jan 2010 11:33:48 +1300 Subject: [PATCH 038/118] Add 'git rev-parse --show-toplevel' option. Shows the absolute path of the top-level working directory. Signed-off-by: Steven Drake Signed-off-by: Junio C Hamano --- Documentation/git-rev-parse.txt | 3 +++ builtin-rev-parse.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 82045a2522..dc829b333d 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -112,6 +112,9 @@ OPTIONS --remotes:: Show tag refs found in `$GIT_DIR/refs/remotes`. +--show-toplevel:: + Show the absolute path of the top-level directory. + --show-prefix:: When the command is invoked from a subdirectory, show the path of the current directory relative to the top-level diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 37d0233521..cbe5b428ad 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -581,6 +581,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_remote_ref(show_reference, NULL); continue; } + if (!strcmp(arg, "--show-toplevel")) { + const char *work_tree = get_git_work_tree(); + if (work_tree) + puts(work_tree); + continue; + } if (!strcmp(arg, "--show-prefix")) { if (prefix) puts(prefix); From 91dc602de9579fe8dc29814819904e2b5a4e92e1 Mon Sep 17 00:00:00 2001 From: Steven Drake Date: Tue, 12 Jan 2010 11:34:34 +1300 Subject: [PATCH 039/118] Use $(git rev-parse --show-toplevel) in cd_to_toplevel(). rev-parse --show-toplevel gives the absolute (aka "physical") path of the toplevel directory and is more portable as 'cd -P' is not supported by all shell implementations. This is also closer to what setup_work_tree() does. Signed-off-by: Steven Drake Signed-off-by: Junio C Hamano --- git-sh-setup.sh | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/git-sh-setup.sh b/git-sh-setup.sh index dfcb8078f5..d56426dd39 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -120,20 +120,11 @@ is_bare_repository () { } cd_to_toplevel () { - cdup=$(git rev-parse --show-cdup) - if test ! -z "$cdup" - then - # The "-P" option says to follow "physical" directory - # structure instead of following symbolic links. When cdup is - # "../", this means following the ".." entry in the current - # directory instead textually removing a symlink path element - # from the PWD shell variable. The "-P" behavior is more - # consistent with the C-style chdir used by most of Git. - cd -P "$cdup" || { - echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" - exit 1 - } - fi + cdup=$(git rev-parse --show-toplevel) && + cd "$cdup" || { + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" + exit 1 + } } require_work_tree () { From c5e558a80ad27774b9984258a31fbf46a1d7c152 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Mon, 11 Jan 2010 02:59:54 +0000 Subject: [PATCH 040/118] Remove empty directories when checking out a commit with fewer submodules Change the unlink_entry function to use rmdir to remove submodule directories. Currently we try to use unlink, which will never succeed. Of course rmdir will only succeed for empty (i.e. not checked out) submodule directories. Behaviour if a submodule is checked out stays essentially the same: print a warning message and keep the submodule directory. Signed-off-by: Peter Collingbourne Signed-off-by: Junio C Hamano --- t/t7400-submodule-basic.sh | 9 +++++++++ unpack-trees.c | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index a0cc99ab9f..1a4dc5f893 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -299,6 +299,15 @@ test_expect_success 'ls-files gracefully handles trailing slash' ' ' +test_expect_success 'moving to a commit without submodule does not leave empty dir' ' + rm -rf init && + mkdir init && + git reset --hard && + git checkout initial && + test ! -d init && + git checkout second +' + test_expect_success 'submodule warns' ' git submodule no-such-submodule 2> output.err && diff --git a/unpack-trees.c b/unpack-trees.c index dd5999c356..b69847d6c9 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -61,8 +61,16 @@ static void unlink_entry(struct cache_entry *ce) { if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (unlink_or_warn(ce->name)) - return; + if (S_ISGITLINK(ce->ce_mode)) { + if (rmdir(ce->name)) { + warning("unable to rmdir %s: %s", + ce->name, strerror(errno)); + return; + } + } + else + if (unlink_or_warn(ce->name)) + return; schedule_dir_for_removal(ce->name, ce_namelen(ce)); } From eb80042c6a631aee74bd0d1b1dd845784e9788cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 11 Jan 2010 17:41:01 +0700 Subject: [PATCH 041/118] Add missing #include to support TIOCGWINSZ on Solaris MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Linux TIOCGWINSZ is defined somehwere in ioctl.h, which is already included. On Solaris we also need to include termios.h. Without this term_columns() in help.c will think TIOCGWINSZ is not supported and always return 80 columns. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- git-compat-util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/git-compat-util.h b/git-compat-util.h index 5c596875c2..a979e41c15 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -96,6 +96,7 @@ #include #include #include +#include #ifndef NO_SYS_SELECT_H #include #endif From ebdc302e3e930ec7f3f6e818ecc815a7a5f01781 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:20:55 -0800 Subject: [PATCH 042/118] bisect.c: mark file-local function static Signed-off-by: Junio C Hamano --- bisect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bisect.c b/bisect.c index f1a1f84aa0..5c03398841 100644 --- a/bisect.c +++ b/bisect.c @@ -593,7 +593,7 @@ struct commit_list *filter_skipped(struct commit_list *list, * is increased by one between each call, but that should not matter * for this application. */ -int get_prn(int count) { +static int get_prn(int count) { count = count * 1103515245 + 12345; return ((unsigned)(count/65536) % PRN_MODULO); } From f1c92c6369511396ab3a409b5c9957066b72f6a3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:21:18 -0800 Subject: [PATCH 043/118] builtin-rev-list.c: mark file-local function static Signed-off-by: Junio C Hamano --- bisect.h | 2 -- builtin-rev-list.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bisect.h b/bisect.h index 82f8fc1910..0862ce56d7 100644 --- a/bisect.h +++ b/bisect.h @@ -27,8 +27,6 @@ struct rev_list_info { const char *header_prefix; }; -extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); - extern int bisect_next_all(const char *prefix); extern int estimate_bisect_steps(int all); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index cd97ded4d2..c924b3a2c7 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -253,7 +253,7 @@ static void print_var_int(const char *var, int val) printf("%s=%d\n", var, val); } -int show_bisect_vars(struct rev_list_info *info, int reaches, int all) +static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { int cnt, flags = info->bisect_show_flags; char hex[41] = ""; From cc5711424b7ae36276a40c06ede5d95f87ca20f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:23:35 -0800 Subject: [PATCH 044/118] pretty.c: mark file-local function static Signed-off-by: Junio C Hamano --- commit.h | 1 - pretty.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/commit.h b/commit.h index e5332efcfc..24128d7a2a 100644 --- a/commit.h +++ b/commit.h @@ -73,7 +73,6 @@ struct pretty_print_context struct reflog_walk_info *reflog_info; }; -extern int non_ascii(int); extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern char *reencode_commit_message(const struct commit *commit, diff --git a/pretty.c b/pretty.c index 8f5bd1ab7f..9001379a9d 100644 --- a/pretty.c +++ b/pretty.c @@ -83,7 +83,7 @@ static int get_one_line(const char *msg) } /* High bit set, or ISO-2022-INT */ -int non_ascii(int ch) +static int non_ascii(int ch) { return !isascii(ch) || ch == '\033'; } From a26345b6085340ebd61e156aa8154a80196bee0f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 10 Jan 2010 22:39:36 -0800 Subject: [PATCH 045/118] grep: optimize built-in grep by skipping lines that do not hit The internal "grep" engine we use checks for hits line-by-line, instead of letting the underlying regexec()/fixmatch() routines scan for the first match from the rest of the buffer. This was a major source of overhead compared to the external grep. Introduce a "look-ahead" mechanism to find the next line that would potentially match by using regexec()/fixmatch() in the remainder of the text to skip unmatching lines, and use it when the query criteria is simple enough (i.e. punt for an advanced grep boolean expression like "lines that have both X and Y but not Z" for now) and we are not running under "-v" (aka "--invert-match") option. Note that "-L" (aka "--files-without-match") is not a reason to disable this optimization. Under the option, we are interested if the file has any hit at all, and that is what we determine reliably with or without the optimization. Signed-off-by: Junio C Hamano --- grep.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/grep.c b/grep.c index 5d162dae6e..03ffcd4042 100644 --- a/grep.c +++ b/grep.c @@ -608,6 +608,65 @@ static void show_pre_context(struct grep_opt *opt, const char *name, char *buf, } } +static int should_lookahead(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->extended) + return 0; /* punt for too complex stuff */ + if (opt->invert) + return 0; + for (p = opt->pattern_list; p; p = p->next) { + if (p->token != GREP_PATTERN) + return 0; /* punt for "header only" and stuff */ + } + return 1; +} + +static int look_ahead(struct grep_opt *opt, + unsigned long *left_p, + unsigned *lno_p, + char **bol_p) +{ + unsigned lno = *lno_p; + char *bol = *bol_p; + struct grep_pat *p; + char *sp, *last_bol; + regoff_t earliest = -1; + + for (p = opt->pattern_list; p; p = p->next) { + int hit; + regmatch_t m; + + if (p->fixed) + hit = !fixmatch(p->pattern, bol, &m); + else + hit = !regexec(&p->regexp, bol, 1, &m, 0); + if (!hit || m.rm_so < 0 || m.rm_eo < 0) + continue; + if (earliest < 0 || m.rm_so < earliest) + earliest = m.rm_so; + } + + if (earliest < 0) { + *bol_p = bol + *left_p; + *left_p = 0; + return 1; + } + for (sp = bol + earliest; bol < sp && sp[-1] != '\n'; sp--) + ; /* find the beginning of the line */ + last_bol = sp; + + for (sp = bol; sp < last_bol; sp++) { + if (*sp == '\n') + lno++; + } + *left_p -= last_bol - bol; + *bol_p = last_bol; + *lno_p = lno; + return 0; +} + static int grep_buffer_1(struct grep_opt *opt, const char *name, char *buf, unsigned long size, int collect_hits) { @@ -617,6 +676,7 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, unsigned last_hit = 0; int binary_match_only = 0; unsigned count = 0; + int try_lookahead = 0; enum grep_context ctx = GREP_CONTEXT_HEAD; xdemitconf_t xecfg; @@ -645,11 +705,26 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, opt->priv = &xecfg; } } + try_lookahead = should_lookahead(opt); while (left) { char *eol, ch; int hit; + /* + * look_ahead() skips quicly to the line that possibly + * has the next hit; don't call it if we need to do + * something more than just skipping the current line + * in response to an unmatch for the current line. E.g. + * inside a post-context window, we will show the current + * line as a context around the previous hit when it + * doesn't hit. + */ + if (try_lookahead + && !(last_hit + && lno <= last_hit + opt->post_context) + && look_ahead(opt, &left, &lno, &bol)) + break; eol = end_of_line(bol, &left); ch = *eol; *eol = 0; From 83e41e2e61f535d5b432437d4e8ac89c710fc482 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:26:08 -0800 Subject: [PATCH 046/118] http.c: mark file-local functions static Signed-off-by: Junio C Hamano --- http.c | 10 ++++++++-- http.h | 9 --------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/http.c b/http.c index 5c3efb96d9..deab59551d 100644 --- a/http.c +++ b/http.c @@ -651,7 +651,7 @@ static void closedown_active_slot(struct active_request_slot *slot) slot->in_use = 0; } -void release_active_slot(struct active_request_slot *slot) +static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); if (slot->curl && curl_session_count > min_curl_sessions) { @@ -834,7 +834,13 @@ int http_get_strbuf(const char *url, struct strbuf *result, int options) return http_request(url, result, HTTP_REQUEST_STRBUF, options); } -int http_get_file(const char *url, const char *filename, int options) +/* + * Downloads an url and stores the result in the given file. + * + * If a previous interrupted download is detected (i.e. a previous temporary + * file is still around) the download is resumed. + */ +static int http_get_file(const char *url, const char *filename, int options) { int ret; struct strbuf tmpfile = STRBUF_INIT; diff --git a/http.h b/http.h index f828e1d806..5c9441c10c 100644 --- a/http.h +++ b/http.h @@ -81,7 +81,6 @@ extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); -extern void release_active_slot(struct active_request_slot *slot); #ifdef USE_CURL_MULTI extern void fill_active_slots(void); @@ -135,14 +134,6 @@ extern char *get_remote_object_url(const char *url, const char *hex, */ int http_get_strbuf(const char *url, struct strbuf *result, int options); -/* - * Downloads an url and stores the result in the given file. - * - * If a previous interrupted download is detected (i.e. a previous temporary - * file is still around) the download is resumed. - */ -int http_get_file(const char *url, const char *filename, int options); - /* * Prints an error message using error() containing url and curl_errorstr, * and returns ret. From 61b97df7d9da21acbd8179e6700f35c8366df9ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:27:31 -0800 Subject: [PATCH 047/118] entry.c: mark file-local function static Signed-off-by: Junio C Hamano --- cache.h | 3 --- entry.c | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 3f9ee86147..30b9048dfe 100644 --- a/cache.h +++ b/cache.h @@ -473,9 +473,6 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); -/* "careful lstat()" */ -extern int check_path(const char *path, int len, struct stat *st, int skiplen); - #define REFRESH_REALLY 0x0001 /* ignore_valid */ #define REFRESH_UNMERGED 0x0002 /* allow unmerged */ #define REFRESH_QUIET 0x0004 /* be quiet about it */ diff --git a/entry.c b/entry.c index 06d24f14c6..55b988e22b 100644 --- a/entry.c +++ b/entry.c @@ -179,7 +179,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout * This is like 'lstat()', except it refuses to follow symlinks * in the path, after skipping "skiplen". */ -int check_path(const char *path, int len, struct stat *st, int skiplen) +static int check_path(const char *path, int len, struct stat *st, int skiplen) { const char *slash = path + len; From 41064ebc4920fadb6afafe5f09865d2558921c00 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:28:45 -0800 Subject: [PATCH 048/118] parse-options.c: mark file-local function static Signed-off-by: Junio C Hamano --- parse-options.c | 7 +++++-- parse-options.h | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/parse-options.c b/parse-options.c index f5594114ed..7bbed5f3e2 100644 --- a/parse-options.c +++ b/parse-options.c @@ -3,6 +3,9 @@ #include "cache.h" #include "commit.h" +static int parse_options_usage(const char * const *usagestr, + const struct option *opts); + #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -560,8 +563,8 @@ void usage_msg_opt(const char *msg, usage_with_options(usagestr, options); } -int parse_options_usage(const char * const *usagestr, - const struct option *opts) +static int parse_options_usage(const char * const *usagestr, + const struct option *opts) { return usage_with_options_internal(usagestr, opts, 0); } diff --git a/parse-options.h b/parse-options.h index f295a2cf85..72fa36011b 100644 --- a/parse-options.h +++ b/parse-options.h @@ -171,9 +171,6 @@ struct parse_opt_ctx_t { const char *prefix; }; -extern int parse_options_usage(const char * const *usagestr, - const struct option *opts); - extern void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, int flags); From 87b29e5a5ab02f10505fca567d027b57d2a9314e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:29:35 -0800 Subject: [PATCH 049/118] read-cache.c: mark file-local functions static Signed-off-by: Junio C Hamano --- cache.h | 2 -- read-cache.c | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 30b9048dfe..e7bb6b795e 100644 --- a/cache.h +++ b/cache.h @@ -445,7 +445,6 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ #define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); -extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern int remove_index_entry_at(struct index_state *, int pos); extern void remove_marked_cache_entries(struct index_state *istate); @@ -615,7 +614,6 @@ static inline void hashclr(unsigned char *hash) { memset(hash, 0, 20); } -extern int is_empty_blob_sha1(const unsigned char *sha1); #define EMPTY_TREE_SHA1_HEX \ "4b825dc642cb6eb9a060e54bf8d69288fbee4904" diff --git a/read-cache.c b/read-cache.c index 9033dd3ab9..9f4f44cb62 100644 --- a/read-cache.c +++ b/read-cache.c @@ -15,6 +15,8 @@ #include "revision.h" #include "blob.h" +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); + /* Index extensions. * * The first letter should be 'A'..'Z' for extensions that are not @@ -156,7 +158,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st) return 0; } -int is_empty_blob_sha1(const unsigned char *sha1) +static int is_empty_blob_sha1(const unsigned char *sha1) { static const unsigned char empty_blob_sha1[20] = { 0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b, @@ -1141,7 +1143,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p return has_errors; } -struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) +static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really) { return refresh_cache_ent(&the_index, ce, really, NULL); } From 5092d3ec21ab335e5908fd8abfe99bbc13812606 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:30:36 -0800 Subject: [PATCH 050/118] remote-curl.c: mark file-local function static Signed-off-by: Junio C Hamano --- remote-curl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote-curl.c b/remote-curl.c index 28b2a316d3..b76dcb2e88 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -317,7 +317,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, } #ifndef NO_CURL_IOCTL -curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) { struct rpc_state *rpc = clientp; From 758e915b8a220ebe967edf745eb699b30d501993 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:31:06 -0800 Subject: [PATCH 051/118] quote.c: mark file-local function static Signed-off-by: Junio C Hamano --- quote.c | 2 +- quote.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/quote.c b/quote.c index 848d174cc5..acb6bf929f 100644 --- a/quote.c +++ b/quote.c @@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) } } -char *sq_dequote_step(char *arg, char **next) +static char *sq_dequote_step(char *arg, char **next) { char *dst = arg; char *src = arg; diff --git a/quote.h b/quote.h index 66730f2bff..f83eb233c4 100644 --- a/quote.h +++ b/quote.h @@ -45,7 +45,6 @@ extern char *sq_dequote(char *); * next argument that should be passed as first parameter. When there * is no more argument to be dequoted, "next" is updated to point to NULL. */ -extern char *sq_dequote_step(char *arg, char **next); extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); From cb58c932a58e704b840ede37c55ce3e59a9e7544 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:31:58 -0800 Subject: [PATCH 052/118] submodule.c: mark file-local function static Signed-off-by: Junio C Hamano --- submodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodule.c b/submodule.c index 86aad653b7..3007f7d5a6 100644 --- a/submodule.c +++ b/submodule.c @@ -5,7 +5,7 @@ #include "commit.h" #include "revision.h" -int add_submodule_odb(const char *path) +static int add_submodule_odb(const char *path) { struct strbuf objects_directory = STRBUF_INIT; struct alternate_object_database *alt_odb; From 5e133b8cf9b9b7f9483c47d19ffb40e10827ae51 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 22:32:29 -0800 Subject: [PATCH 053/118] utf8.c: mark file-local function static Signed-off-by: Junio C Hamano --- utf8.c | 2 +- utf8.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/utf8.c b/utf8.c index 7ddff23fa7..ab326ac83e 100644 --- a/utf8.c +++ b/utf8.c @@ -163,7 +163,7 @@ static int git_wcwidth(ucs_char_t ch) * If the string was not a valid UTF-8, *start pointer is set to NULL * and the return value is undefined. */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) +static ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p) { unsigned char *s = (unsigned char *)*start; ucs_char_t ch; diff --git a/utf8.h b/utf8.h index ae30ae4c6e..c9738d83d9 100644 --- a/utf8.h +++ b/utf8.h @@ -3,7 +3,6 @@ typedef unsigned int ucs_char_t; /* assuming 32bit int */ -ucs_char_t pick_one_utf8_char(const char **start, size_t *remainder_p); int utf8_width(const char **start, size_t *remainder_p); int utf8_strwidth(const char *string); int is_utf8(const char *text); From 42b3b0061478f262118036286455a8f09a0d3fff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:11:22 -0800 Subject: [PATCH 054/118] mailmap.c: remove unused function map_email() is not used anywhere. Signed-off-by: Junio C Hamano --- mailmap.c | 5 ----- mailmap.h | 1 - 2 files changed, 6 deletions(-) diff --git a/mailmap.c b/mailmap.c index f167c005bf..b68c1fec9c 100644 --- a/mailmap.c +++ b/mailmap.c @@ -243,8 +243,3 @@ int map_user(struct string_list *map, debug_mm("map_user: --\n"); return 0; } - -int map_email(struct string_list *map, const char *email, char *name, int maxlen) -{ - return map_user(map, (char *)email, 0, name, maxlen); -} diff --git a/mailmap.h b/mailmap.h index 4b2ca3a7de..d5c3664322 100644 --- a/mailmap.h +++ b/mailmap.h @@ -4,7 +4,6 @@ int read_mailmap(struct string_list *map, char **repo_abbrev); void clear_mailmap(struct string_list *map); -int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen); int map_user(struct string_list *mailmap, char *email, int maxlen_email, char *name, int maxlen_name); From 356521ab22fb76e17aa8dc9993ff81fade49e11c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:17:56 -0800 Subject: [PATCH 055/118] sha1_file.c: remove unused function has_pack_file() is not used anywhere. Signed-off-by: Junio C Hamano --- cache.h | 1 - sha1_file.c | 8 -------- 2 files changed, 9 deletions(-) diff --git a/cache.h b/cache.h index e7bb6b795e..90edb5b261 100644 --- a/cache.h +++ b/cache.h @@ -683,7 +683,6 @@ extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); extern int has_loose_object_nonlocal(const unsigned char *sha1); -extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); extern const signed char hexval_table[256]; diff --git a/sha1_file.c b/sha1_file.c index 63981fb3fd..7086760dbe 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2458,14 +2458,6 @@ int has_pack_index(const unsigned char *sha1) return 1; } -int has_pack_file(const unsigned char *sha1) -{ - struct stat st; - if (stat(sha1_pack_name(sha1), &st)) - return 0; - return 1; -} - int has_sha1_pack(const unsigned char *sha1) { struct pack_entry e; From 229d8107470ded5e5e6c43cf5daeabf382cce9d1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:16:26 -0800 Subject: [PATCH 056/118] strbuf.c: remove unused function strbuf_tolower() is not used anywhere. Signed-off-by: Junio C Hamano --- strbuf.c | 7 ------- strbuf.h | 1 - 2 files changed, 8 deletions(-) diff --git a/strbuf.c b/strbuf.c index a6153dca27..3fa81b3365 100644 --- a/strbuf.c +++ b/strbuf.c @@ -91,13 +91,6 @@ void strbuf_ltrim(struct strbuf *sb) sb->buf[sb->len] = '\0'; } -void strbuf_tolower(struct strbuf *sb) -{ - int i; - for (i = 0; i < sb->len; i++) - sb->buf[i] = tolower(sb->buf[i]); -} - struct strbuf **strbuf_split(const struct strbuf *sb, int delim) { int alloc = 2, pos = 0; diff --git a/strbuf.h b/strbuf.h index fa07ecf094..b37f06a34b 100644 --- a/strbuf.h +++ b/strbuf.h @@ -81,7 +81,6 @@ extern void strbuf_trim(struct strbuf *); extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -extern void strbuf_tolower(struct strbuf *); extern struct strbuf **strbuf_split(const struct strbuf *, int delim); extern void strbuf_list_free(struct strbuf **); From f64b4856243a9ea9445068a0989c71a8915c3862 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Tue, 12 Jan 2010 16:38:34 +0100 Subject: [PATCH 057/118] lib-rebase: Provide clearer debugging info about what the editor did (For testing "rebase -i"): Output the "rebase -i" command script before and after the edits, to make it clearer what the editor did. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/lib-rebase.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index f4dda02b5a..0fce5952ce 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -29,6 +29,7 @@ test -z "$EXPECT_COUNT" || test -z "$FAKE_LINES" && exit grep -v '^#' < "$1" > "$1".tmp rm -f "$1" +echo 'rebase -i script before editing:' cat "$1".tmp action=pick for line in $FAKE_LINES; do @@ -36,12 +37,12 @@ for line in $FAKE_LINES; do squash|fixup|edit|reword) action="$line";; *) - echo sed -n "${line}s/^pick/$action/p" - sed -n "${line}p" < "$1".tmp sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" action=pick;; esac done +echo 'rebase -i script after editing:' +cat "$1" EOF test_set_editor "$(pwd)/fake-editor.sh" From 05c95dbe44b42f9c3f7efe6793d311a26e1b8181 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Tue, 12 Jan 2010 16:38:35 +0100 Subject: [PATCH 058/118] lib-rebase: Allow comments and blank lines to be added to the rebase script (For testing "rebase -i"): Support new action types in $FAKE_LINES to allow comments and blank lines to be added to the "rebase -i" command list. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/lib-rebase.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 0fce5952ce..0db8250c58 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -5,13 +5,20 @@ # - override the commit message with $FAKE_COMMIT_MESSAGE, # - amend the commit message with $FAKE_COMMIT_AMEND # - check that non-commit messages have a certain line count with $EXPECT_COUNT -# - rewrite a rebase -i script with $FAKE_LINES in the form +# - rewrite a rebase -i script as directed by $FAKE_LINES. +# $FAKE_LINES consists of a sequence of words separated by spaces. +# The following word combinations are possible: # -# "[] []..." +# "" -- add a "pick" line with the SHA1 taken from the +# specified line. # -# If a line number is prefixed with "squash", "fixup", "edit", or -# "reword", the respective line's command will be replaced with the -# specified one. +# " " -- add a line with the specified command +# ("squash", "fixup", "edit", or "reword") and the SHA1 taken +# from the specified line. +# +# "#" -- Add a comment line. +# +# ">" -- Add a blank line. set_fake_editor () { echo "#!$SHELL_PATH" >fake-editor.sh @@ -36,6 +43,10 @@ for line in $FAKE_LINES; do case $line in squash|fixup|edit|reword) action="$line";; + "#") + echo '# comment' >> "$1";; + ">") + echo >> "$1";; *) sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" action=pick;; From 234b3dae2fe83ae7df2c82194cd5f5eb01fd166d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Tue, 12 Jan 2010 16:38:36 +0100 Subject: [PATCH 059/118] rebase-i: Ignore comments and blank lines in peek_next_command Previously, blank lines and/or comments within a series of squash/fixup commands would confuse "git rebase -i" into thinking that the series was finished. It would therefore require the user to edit the commit message for the squash/fixup commits seen so far. Then, after continuing, it would ask the user to edit the commit message again. Ignore comments and blank lines within a group of squash/fixup commands, allowing them to be processed in one go. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 30de96ee1a..55c451ade4 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -338,7 +338,7 @@ make_squash_message () { } peek_next_command () { - sed -n "1s/ .*$//p" < "$TODO" + sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO" } do_next () { diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index ea26115133..d9382e41d3 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -265,6 +265,30 @@ test_expect_success 'squash and fixup generate correct log messages' ' git branch -D squash-fixup ' +test_expect_success 'squash ignores comments' ' + git checkout -b skip-comments E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="# 1 # squash 2 # squash 3 # squash 4 #" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-comments +' + +test_expect_success 'squash ignores blank lines' ' + git checkout -b skip-blank-lines E && + base=$(git rev-parse HEAD~4) && + FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="> 1 > squash 2 > squash 3 > squash 4 >" \ + EXPECT_HEADER_COUNT=4 \ + git rebase -i $base && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D skip-blank-lines +' + test_expect_success 'squash works as expected' ' for n in one two three four do From 6b02de3b9dc4ac8374cea4964e993ec6636d781c Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Tue, 12 Jan 2010 20:53:29 +0100 Subject: [PATCH 060/118] Improve error message when a transport helper was not found Perviously, the error message was: git: 'remote-foo' is not a git-command. See 'git --help'. By not treating the transport helper as a git command, a more suitable error is reported: fatal: Unable to find remote helper for 'foo' Signed-off-by: Ilari Liusvaara Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- transport-helper.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/transport-helper.c b/transport-helper.c index 6ece0d9875..7dce4a4cac 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -102,6 +102,7 @@ static struct child_process *get_helper(struct transport *transport) int refspec_nr = 0; int refspec_alloc = 0; int duped; + int code; if (data->helper) return data->helper; @@ -111,13 +112,18 @@ static struct child_process *get_helper(struct transport *transport) helper->out = -1; helper->err = 0; helper->argv = xcalloc(4, sizeof(*helper->argv)); - strbuf_addf(&buf, "remote-%s", data->name); + strbuf_addf(&buf, "git-remote-%s", data->name); helper->argv[0] = strbuf_detach(&buf, NULL); helper->argv[1] = transport->remote->name; helper->argv[2] = remove_ext_force(transport->url); - helper->git_cmd = 1; - if (start_command(helper)) - die("Unable to run helper: git %s", helper->argv[0]); + helper->git_cmd = 0; + helper->silent_exec_failure = 1; + code = start_command(helper); + if (code < 0 && errno == ENOENT) + die("Unable to find remote helper for '%s'", data->name); + else if (code != 0) + exit(code); + data->helper = helper; data->no_disconnect_req = 0; From d38a30df7dd54c5c6883af1de1a03ec7d523cee5 Mon Sep 17 00:00:00 2001 From: Matthieu Moy Date: Tue, 12 Jan 2010 10:54:44 +0100 Subject: [PATCH 061/118] Be more user-friendly when refusing to do something because of conflict. Various commands refuse to run in the presence of conflicts (commit, merge, pull, cherry-pick/revert). They all used to provide rough, and inconsistant error messages. A new variable advice.resolveconflict is introduced, and allows more verbose messages, pointing the user to the appropriate solution. For commit, the error message used to look like this: $ git commit foo.txt: needs merge foo.txt: unmerged (c34a92682e0394bc0d6f4d4a67a8e2d32395c169) foo.txt: unmerged (3afcd75de8de0bb5076942fcb17446be50451030) foo.txt: unmerged (c9785d77b76dfe4fb038bf927ee518f6ae45ede4) error: Error building trees The "need merge" line is given by refresh_cache. We add the IN_PORCELAIN option to make the output more consistant with the other porcelain commands, and catch the error in return, to stop with a clean error message. The next lines were displayed by a call to cache_tree_update(), which is not reached anymore if we noticed the conflict. The new output looks like: U foo.txt fatal: 'commit' is not possible because you have unmerged files. Please, fix them up in the work tree, and then use 'git add/rm ' as appropriate to mark resolution and make a commit, or use 'git commit -a'. Pull is slightly modified to abort immediately if $GIT_DIR/MERGE_HEAD exists instead of waiting for merge to complain. The behavior of merge and the test-case are slightly modified to reflect the usual flow: start with conflicts, fix them, and afterwards get rid of MERGE_HEAD, with different error messages at each stage. Signed-off-by: Matthieu Moy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ advice.c | 16 ++++++++++++++++ advice.h | 5 +++++ builtin-commit.c | 14 ++++++++++++-- builtin-merge.c | 19 ++++++++++++++----- builtin-revert.c | 15 ++++++++++++++- git-pull.sh | 25 +++++++++++++++++++++++-- t/t3030-merge-recursive.sh | 6 ++++-- t/t3501-revert-cherry-pick.sh | 2 +- 9 files changed, 93 insertions(+), 13 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a1e36d7e42..9c27686fab 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -130,6 +130,10 @@ advice.*:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwritting local changes. Default: true. + resolveConflict:: + Advices shown by various commands when conflicts + prevent the operation from being performed. + Default: true. -- core.fileMode:: diff --git a/advice.c b/advice.c index cb666acc3c..3309521f45 100644 --- a/advice.c +++ b/advice.c @@ -3,6 +3,7 @@ int advice_push_nonfastforward = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; +int advice_resolve_conflict = 1; static struct { const char *name; @@ -11,6 +12,7 @@ static struct { { "pushnonfastforward", &advice_push_nonfastforward }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, + { "resolveconflict", &advice_resolve_conflict }, }; int git_default_advice_config(const char *var, const char *value) @@ -27,3 +29,17 @@ int git_default_advice_config(const char *var, const char *value) return 0; } + +void NORETURN die_resolve_conflict(const char *me) +{ + if (advice_resolve_conflict) + /* + * Message used both when 'git commit' fails and when + * other commands doing a merge do. + */ + die("'%s' is not possible because you have unmerged files.\n" + "Please, fix them up in the work tree, and then use 'git add/rm ' as\n" + "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me); + else + die("'%s' is not possible because you have unmerged files.", me); +} diff --git a/advice.h b/advice.h index 3de5000d8c..acd5fdde3f 100644 --- a/advice.h +++ b/advice.h @@ -1,10 +1,15 @@ #ifndef ADVICE_H #define ADVICE_H +#include "git-compat-util.h" + extern int advice_push_nonfastforward; extern int advice_status_hints; extern int advice_commit_before_merge; +extern int advice_resolve_conflict; int git_default_advice_config(const char *var, const char *value); +extern void NORETURN die_resolve_conflict(const char *me); + #endif /* ADVICE_H */ diff --git a/builtin-commit.c b/builtin-commit.c index f54772f74a..c58712c539 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -219,6 +219,16 @@ static void create_base_index(void) 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; @@ -258,7 +268,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int if (all || (also && pathspec && *pathspec)) { int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache(refresh_flags); + 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"); @@ -277,7 +287,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); - refresh_cache(refresh_flags); + 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"); diff --git a/builtin-merge.c b/builtin-merge.c index f1c84d759d..79a35c305b 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -847,11 +847,20 @@ int cmd_merge(int argc, const char **argv, const char *prefix) const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (file_exists(git_path("MERGE_HEAD"))) - die("You have not concluded your merge. (MERGE_HEAD exists)"); - if (read_cache_unmerged()) - die("You are in the middle of a conflicted merge." - " (index unmerged)"); + 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)."); + } /* * Check if we are _not_ on a detached HEAD, i.e. if there is a diff --git a/builtin-revert.c b/builtin-revert.c index 151aa6a981..d14dde3fbf 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -233,6 +233,19 @@ static struct tree *empty_tree(void) return tree; } +static NORETURN void die_dirty_index(const char *me) +{ + if (read_cache_unmerged()) { + die_resolve_conflict(me); + } else { + if (advice_commit_before_merge) + die("Your local changes would be overwritten by %s.\n" + "Please, commit your changes or stash them to proceed.", me); + else + die("Your local changes would be overwritten by %s.\n", me); + } +} + static int revert_or_cherry_pick(int argc, const char **argv) { unsigned char head[20]; @@ -269,7 +282,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); if (index_differs_from("HEAD", 0)) - die ("Dirty index: cannot %s", me); + die_dirty_index(me); } discard_cache(); diff --git a/git-pull.sh b/git-pull.sh index 9e69ada413..54ce0af2d4 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -13,8 +13,29 @@ set_reflog_action "pull $*" require_work_tree cd_to_toplevel -test -z "$(git ls-files -u)" || - die "You are in the middle of a conflicted merge." + +die_conflict () { + git diff-index --cached --name-status -r --ignore-submodules HEAD -- + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "Pull is not possible because you have unmerged files. +Please, fix them up in the work tree, and then use 'git add/rm ' +as appropriate to mark resolution, or use 'git commit -a'." + else + die "Pull is not possible because you have unmerged files." + fi +} + +die_merge () { + if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then + die "You have not concluded your merge (MERGE_HEAD exists). +Please, commit your changes before you can merge." + else + die "You have not concluded your merge (MERGE_HEAD exists)." + fi +} + +test -z "$(git ls-files -u)" || die_conflict +test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= log_arg= verbosity= diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9b3fa2bdcd..9929f82021 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -276,11 +276,13 @@ test_expect_success 'fail if the index has unresolved entries' ' test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && + grep "not possible because you have unmerged files" out && + git add -u && + test_must_fail git merge "$c5" 2> out && grep "You have not concluded your merge" out && rm -f .git/MERGE_HEAD && test_must_fail git merge "$c5" 2> out && - grep "You are in the middle of a conflicted merge" out - + grep "Your local changes to .* would be overwritten by merge." out ' test_expect_success 'merge-recursive remove conflict' ' diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index bb4cf00d78..7f858151d4 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -66,7 +66,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - grep "Dirty index" errors + grep "Your local changes would be overwritten by " errors ' From bbc09c22b9f7784b1aab71d4876227956e6e8f4f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 12 Jan 2010 19:06:41 -0800 Subject: [PATCH 062/118] grep: rip out support for external grep We still allow people to pass --[no-]ext-grep on the command line, but the option is ignored. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 - Makefile | 10 -- builtin-grep.c | 307 +-------------------------------------- grep.h | 1 - t/t7002-grep.sh | 6 +- 5 files changed, 8 insertions(+), 324 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a1e36d7e42..6a96d9d23e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -645,14 +645,6 @@ color.grep:: `never`), never. When set to `true` or `auto`, use color only when the output is written to the terminal. Defaults to `false`. -color.grep.external:: - The string value of this variable is passed to an external 'grep' - command as a command line option if match highlighting is turned - on. If set to an empty string, no option is passed at all, - turning off coloring for external 'grep' calls; this is the default. - For GNU grep, set it to `--color=always` to highlight matches even - when a pager is used. - color.grep.match:: Use customized color for matches. The value of this variable may be specified as in color.branch.. It is passed using diff --git a/Makefile b/Makefile index 4a1e5bcc4d..a4b922e070 100644 --- a/Makefile +++ b/Makefile @@ -185,10 +185,6 @@ all:: # is a simplified version of the merge sort used in glibc. This is # recommended if Git triggers O(n^2) behavior in your platform's qsort(). # -# Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call -# your external grep (e.g., if your system lacks grep, if its grep is -# broken, or spawning external process is slower than built-in grep git has). -# # Define UNRELIABLE_FSTAT if your system's fstat does not return the same # information on a not yet closed file that lstat would return for the same # file after it was closed. @@ -777,7 +773,6 @@ ifeq ($(uname_S),SunOS) NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease - NO_EXTERNAL_GREP = YesPlease THREADED_DELTA_SEARCH = YesPlease ifeq ($(uname_R),5.7) NEEDS_RESOLV = YesPlease @@ -895,7 +890,6 @@ ifeq ($(uname_S),IRIX) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@ -915,7 +909,6 @@ ifeq ($(uname_S),IRIX64) # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease - NO_EXTERNAL_GREP = UnfortunatelyYes SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@ -1322,9 +1315,6 @@ endif ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif -ifdef NO_EXTERNAL_GREP - BASIC_CFLAGS += -DNO_EXTERNAL_GREP -endif ifdef UNRELIABLE_FSTAT BASIC_CFLAGS += -DUNRELIABLE_FSTAT endif diff --git a/builtin-grep.c b/builtin-grep.c index a5b6719a1a..3d6ebb586d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -15,14 +15,6 @@ #include "grep.h" #include "quote.h" -#ifndef NO_EXTERNAL_GREP -#ifdef __unix__ -#define NO_EXTERNAL_GREP 0 -#else -#define NO_EXTERNAL_GREP 1 -#endif -#endif - static char const * const grep_usage[] = { "git grep [options] [-e] [...] [[--] path...]", NULL @@ -42,8 +34,6 @@ static int grep_config(const char *var, const char *value, void *cb) opt->color = git_config_colorbool(var, value, -1); return 0; } - if (!strcmp(var, "color.grep.external")) - return git_config_string(&(opt->color_external), var, value); if (!strcmp(var, "color.grep.match")) { if (!value) return config_error_nonbool(var); @@ -215,292 +205,12 @@ static int grep_file(struct grep_opt *opt, const char *filename) return i; } -#if !NO_EXTERNAL_GREP -static int exec_grep(int argc, const char **argv) -{ - pid_t pid; - int status; - - argv[argc] = NULL; - pid = fork(); - if (pid < 0) - return pid; - if (!pid) { - execvp("grep", (char **) argv); - exit(255); - } - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } - if (WIFEXITED(status)) { - if (!WEXITSTATUS(status)) - return 1; - return 0; - } - return -1; -} - -#define MAXARGS 1000 -#define ARGBUF 4096 -#define push_arg(a) do { \ - if (nr < MAXARGS) argv[nr++] = (a); \ - else die("maximum number of args exceeded"); \ - } while (0) - -/* - * If you send a singleton filename to grep, it does not give - * the name of the file. GNU grep has "-H" but we would want - * that behaviour in a portable way. - * - * So we keep two pathnames in argv buffer unsent to grep in - * the main loop if we need to do more than one grep. - */ -static int flush_grep(struct grep_opt *opt, - int argc, int arg0, const char **argv, int *kept) -{ - int status; - int count = argc - arg0; - const char *kept_0 = NULL; - - if (count <= 2) { - /* - * Because we keep at least 2 paths in the call from - * the main loop (i.e. kept != NULL), and MAXARGS is - * far greater than 2, this usually is a call to - * conclude the grep. However, the user could attempt - * to overflow the argv buffer by giving too many - * options to leave very small number of real - * arguments even for the call in the main loop. - */ - if (kept) - die("insanely many options to grep"); - - /* - * If we have two or more paths, we do not have to do - * anything special, but we need to push /dev/null to - * get "-H" behaviour of GNU grep portably but when we - * are not doing "-l" nor "-L" nor "-c". - */ - if (count == 1 && - !opt->name_only && - !opt->unmatch_name_only && - !opt->count) { - argv[argc++] = "/dev/null"; - argv[argc] = NULL; - } - } - - else if (kept) { - /* - * Called because we found many paths and haven't finished - * iterating over the cache yet. We keep two paths - * for the concluding call. argv[argc-2] and argv[argc-1] - * has the last two paths, so save the first one away, - * replace it with NULL while sending the list to grep, - * and recover them after we are done. - */ - *kept = 2; - kept_0 = argv[argc-2]; - argv[argc-2] = NULL; - argc -= 2; - } - - if (opt->pre_context || opt->post_context) { - /* - * grep handles hunk marks between files, but we need to - * do that ourselves between multiple calls. - */ - if (opt->show_hunk_mark) - write_or_die(1, "--\n", 3); - else - opt->show_hunk_mark = 1; - } - - status = exec_grep(argc, argv); - - if (kept_0) { - /* - * Then recover them. Now the last arg is beyond the - * terminating NULL which is at argc, and the second - * from the last is what we saved away in kept_0 - */ - argv[arg0++] = kept_0; - argv[arg0] = argv[argc+1]; - } - return status; -} - -static void grep_add_color(struct strbuf *sb, const char *escape_seq) -{ - size_t orig_len = sb->len; - - while (*escape_seq) { - if (*escape_seq == 'm') - strbuf_addch(sb, ';'); - else if (*escape_seq != '\033' && *escape_seq != '[') - strbuf_addch(sb, *escape_seq); - escape_seq++; - } - if (sb->len > orig_len && sb->buf[sb->len - 1] == ';') - strbuf_setlen(sb, sb->len - 1); -} - -static int external_grep(struct grep_opt *opt, const char **paths, int cached) -{ - int i, nr, argc, hit, len, status; - const char *argv[MAXARGS+1]; - char randarg[ARGBUF]; - char *argptr = randarg; - struct grep_pat *p; - - if (opt->extended || (opt->relative && opt->prefix_length)) - return -1; - len = nr = 0; - push_arg("grep"); - if (opt->fixed) - push_arg("-F"); - if (opt->linenum) - push_arg("-n"); - if (!opt->pathname) - push_arg("-h"); - if (opt->regflags & REG_EXTENDED) - push_arg("-E"); - if (opt->ignore_case) - push_arg("-i"); - if (opt->binary == GREP_BINARY_NOMATCH) - push_arg("-I"); - if (opt->word_regexp) - push_arg("-w"); - if (opt->name_only) - push_arg("-l"); - if (opt->unmatch_name_only) - push_arg("-L"); - if (opt->null_following_name) - /* in GNU grep git's "-z" translates to "-Z" */ - push_arg("-Z"); - if (opt->count) - push_arg("-c"); - if (opt->post_context || opt->pre_context) { - if (opt->post_context != opt->pre_context) { - if (opt->pre_context) { - push_arg("-B"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->pre_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - if (opt->post_context) { - push_arg("-A"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - else { - push_arg("-C"); - len += snprintf(argptr, sizeof(randarg)-len, - "%u", opt->post_context) + 1; - if (sizeof(randarg) <= len) - die("maximum length of args exceeded"); - push_arg(argptr); - argptr += len; - } - } - for (p = opt->pattern_list; p; p = p->next) { - push_arg("-e"); - push_arg(p->pattern); - } - if (opt->color) { - struct strbuf sb = STRBUF_INIT; - - grep_add_color(&sb, opt->color_match); - setenv("GREP_COLOR", sb.buf, 1); - - strbuf_reset(&sb); - strbuf_addstr(&sb, "mt="); - grep_add_color(&sb, opt->color_match); - strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se="); - setenv("GREP_COLORS", sb.buf, 1); - - strbuf_release(&sb); - - if (opt->color_external && strlen(opt->color_external) > 0) - push_arg(opt->color_external); - } else { - unsetenv("GREP_COLOR"); - unsetenv("GREP_COLORS"); - } - unsetenv("GREP_OPTIONS"); - - hit = 0; - argc = nr; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - char *name; - int kept; - if (!S_ISREG(ce->ce_mode)) - continue; - if (!pathspec_matches(paths, ce->name, opt->max_depth)) - continue; - name = ce->name; - if (name[0] == '-') { - int len = ce_namelen(ce); - name = xmalloc(len + 3); - memcpy(name, "./", 2); - memcpy(name + 2, ce->name, len + 1); - } - argv[argc++] = name; - if (MAXARGS <= argc) { - status = flush_grep(opt, argc, nr, argv, &kept); - if (0 < status) - hit = 1; - argc = nr + kept; - } - if (ce_stage(ce)) { - do { - i++; - } while (i < active_nr && - !strcmp(ce->name, active_cache[i]->name)); - i--; /* compensate for loop control */ - } - } - if (argc > nr) { - status = flush_grep(opt, argc, nr, argv, NULL); - if (0 < status) - hit = 1; - } - return hit; -} -#endif - -static int grep_cache(struct grep_opt *opt, const char **paths, int cached, - int external_grep_allowed) +static int grep_cache(struct grep_opt *opt, const char **paths, int cached) { int hit = 0; int nr; read_cache(); -#if !NO_EXTERNAL_GREP - /* - * Use the external "grep" command for the case where - * we grep through the checked-out files. It tends to - * be a lot more optimized - */ - if (!cached && external_grep_allowed) { - hit = external_grep(opt, paths, cached); - if (hit >= 0) - return hit; - hit = 0; - } -#endif - for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) @@ -697,8 +407,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; int cached = 0; - int external_grep_allowed = 1; int seen_dashdash = 0; + int external_grep_allowed__ignored; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; @@ -780,13 +490,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "all-match", &opt.all_match, "show only matches from files that match all patterns"), OPT_GROUP(""), -#if NO_EXTERNAL_GREP - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (ignored by this build)"), -#else - OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, - "allow calling of grep(1) (default)"), -#endif + 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() @@ -837,8 +542,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc--; } - if ((opt.color && !opt.color_external) || opt.funcname) - external_grep_allowed = 0; if (!opt.pattern_list) die("no pattern given."); if (!opt.fixed && opt.ignore_case) @@ -884,7 +587,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!list.nr) { if (!cached) setup_work_tree(); - return !grep_cache(&opt, paths, cached, external_grep_allowed); + return !grep_cache(&opt, paths, cached); } if (cached) diff --git a/grep.h b/grep.h index 75370f60d7..0c61b00cbf 100644 --- a/grep.h +++ b/grep.h @@ -85,7 +85,6 @@ struct grep_opt { int max_depth; int funcname; char color_match[COLOR_MAXLEN]; - const char *color_external; int regflags; unsigned pre_context; unsigned post_context; diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index abd14bf819..c369cdbe9b 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -302,8 +302,8 @@ test_expect_success 'grep -C1, hunk mark between files' ' test_cmp expected actual ' -test_expect_success 'grep -C1 --no-ext-grep, hunk mark between files' ' - git grep -C1 --no-ext-grep "^[yz]" >actual && +test_expect_success 'grep -C1 hunk mark between files' ' + git grep -C1 "^[yz]" >actual && test_cmp expected actual ' @@ -359,7 +359,7 @@ test_expect_success 'log grep (6)' ' test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && - test "$(git grep --no-ext-grep test)" = "t/t:test" && + test "$(git grep test)" = "t/t:test" && git update-index --no-assume-unchanged t/t && git checkout t/t ' From 885d211e714b3787cd059e347694f9b24e1db1f3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 12 Jan 2010 19:21:46 -0800 Subject: [PATCH 063/118] grep: rip out pessimization to use fixmatch() Even when running without the -F (--fixed-strings) option, we checked the pattern and used fixmatch() codepath when it does not contain any regex magic. Finding fixed strings with strstr() surely must be faster than running the regular expression crud. Not so. It turns out that on some libc implementations, using the regcomp()/regexec() pair is a lot faster than running strstr() and strcasestr() the fixmatch() codepath uses. Drop the optimization and use the fixmatch() codepath only when the user explicitly asked for it with the -F option. Signed-off-by: Junio C Hamano --- grep.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/grep.c b/grep.c index 62723da134..8e1f7de771 100644 --- a/grep.c +++ b/grep.c @@ -29,13 +29,6 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, p->next = NULL; } -static int is_fixed(const char *s) -{ - while (*s && !is_regex_special(*s)) - s++; - return !*s; -} - static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; @@ -43,7 +36,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; - if (opt->fixed || is_fixed(p->pattern)) + if (opt->fixed) p->fixed = 1; if (opt->regflags & REG_ICASE) p->fixed = 0; From 81d2caefedefa40557baf7af846580f35de3f995 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 12 Jan 2010 12:09:54 -0800 Subject: [PATCH 064/118] strbuf_addbuf(): allow passing the same buf to dst and src If sb and sb2 are the same (i.e. doubling the string), the underlying strbuf_add() can make sb2->buf invalid by calling strbuf_grow(sb) at the beginning; if realloc(3) done by strbuf_grow() needs to move the string, strbuf_add() will read from an already freed buffer. Signed-off-by: Junio C Hamano --- strbuf.h | 1 + 1 file changed, 1 insertion(+) diff --git a/strbuf.h b/strbuf.h index fa07ecf094..4971743a24 100644 --- a/strbuf.h +++ b/strbuf.h @@ -105,6 +105,7 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) { strbuf_add(sb, s, strlen(s)); } static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) { + strbuf_grow(sb, sb2->len); strbuf_add(sb, sb2->buf, sb2->len); } extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); From 1d621fea180e2612facb190b758bf03dc79e4423 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:40 +0100 Subject: [PATCH 065/118] rebase -i: Make the condition for an "if" more transparent Test $no_ff separately rather than testing it indirectly by gluing it onto a comparison of two SHA1s. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 55c451ade4..db10dd7b29 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -164,7 +164,8 @@ pick_one () { parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test "$no_ff$current_sha1" = "$parent_sha1"; then + if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" + then output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) From 50438340bcadd790956b5081b8a16cba28156e22 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:41 +0100 Subject: [PATCH 066/118] rebase -i: Remove dead code This branch of the "if" is only executed if $no_ff is empty, which only happens if $1 was not '-n'. (This code has been dead since 1d25c8cf82eead72e11287d574ef72d3ebec0db1.) Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index db10dd7b29..aae94d2f6f 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -167,7 +167,6 @@ pick_one () { if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" then output git reset --hard $sha1 - test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) output warn Fast-forward to $sha1 else From aa7eaff8b1514ef1241c2f5867670b495b33c455 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:42 +0100 Subject: [PATCH 067/118] rebase -i: Inline expression Inline expression when generating output rather than overwriting the "sha1" local variable with a short SHA1. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index aae94d2f6f..6fd3105b54 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -167,8 +167,7 @@ pick_one () { if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" then output git reset --hard $sha1 - sha1=$(git rev-parse --short $sha1) - output warn Fast-forward to $sha1 + output warn Fast-forward to $(git rev-parse --short $sha1) else output git cherry-pick "$@" fi From 699f13ca9af6b045826b8d44c2f3870affd7823d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:43 +0100 Subject: [PATCH 068/118] rebase -i: Use "test -n" instead of "test ! -z" It is a tiny bit simpler. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 6fd3105b54..2e1b2fa3cc 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -156,7 +156,7 @@ pick_one () { output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - if test ! -z "$REBASE_ROOT" + if test -n "$REBASE_ROOT" then output git cherry-pick "$@" return From bdb011ade4db4a50cde13b5ec870c3dccff7e093 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:44 +0100 Subject: [PATCH 069/118] rebase -i: Use symbolic constant $MSG consistently The filename constant $MSG was previously used in some places and written out literally in others. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 2e1b2fa3cc..efd5749973 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -129,8 +129,8 @@ make_patch () { echo "Root commit" ;; esac > "$DOTEST"/patch - test -f "$DOTEST"/message || - git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message + test -f "$MSG" || + git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" test -f "$DOTEST"/author-script || get_author_ident_from_commit "$1" > "$DOTEST"/author-script } @@ -341,7 +341,7 @@ peek_next_command () { } do_next () { - rm -f "$DOTEST"/message "$DOTEST"/author-script \ + rm -f "$MSG" "$DOTEST"/author-script \ "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in @@ -559,7 +559,7 @@ first and then run 'git rebase --continue' again." die "Cannot rewind the HEAD" fi export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$DOTEST"/message -e || { + git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } From 80883bb30a9c0c41f3b4975874f8f5e527396543 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:45 +0100 Subject: [PATCH 070/118] rebase -i: Document how temporary files are used Add documentation, inferred by reverse-engineering, about how git-rebase--interactive.sh uses many of its temporary files. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 41 +++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index efd5749973..acc92c439d 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -34,11 +34,45 @@ root rebase all reachable commmits up to the root(s) require_work_tree DOTEST="$GIT_DIR/rebase-merge" + +# The file containing rebase commands, comments, and empty lines. +# This file is created by "git rebase -i" then edited by the user. As +# the lines are processed, they are removed from the front of this +# file and written to the tail of $DONE. TODO="$DOTEST"/git-rebase-todo + +# The rebase command lines that have already been processed. A line +# is moved here when it is first handled, before any associated user +# actions. DONE="$DOTEST"/done + +# The commit message that is planned to be used for any changes that +# need to be committed following a user interaction. MSG="$DOTEST"/message + +# The file into which is accumulated the suggested commit message for +# squash/fixup commands. When the first of a series of squash/fixups +# is seen, the file is created and the commit message from the +# previous commit and from the first squash/fixup commit are written +# to it. The commit message for each subsequent squash/fixup commit +# is appended to the file as it is processed. +# +# The first line of the file is of the form +# # This is a combination of $COUNT commits. +# where $COUNT is the number of commits whose messages have been +# written to the file so far (including the initial "pick" commit). +# Each time that a commit message is processed, this line is read and +# updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash + +# $REWRITTEN is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $HEAD and +# $UPSTREAM. They are not necessarily rewritten, but their children +# might be. This ensures that commits on merged, but otherwise +# unrelated side branches are left alone. (Think "X" in the man page's +# example.) REWRITTEN="$DOTEST"/rewritten + DROPPED="$DOTEST"/dropped PRESERVE_MERGES= STRATEGY= @@ -683,13 +717,6 @@ first and then run 'git rebase --continue' again." test t = "$VERBOSE" && : > "$DOTEST"/verbose if test t = "$PRESERVE_MERGES" then - # $REWRITTEN contains files for each commit that is - # reachable by at least one merge base of $HEAD and - # $UPSTREAM. They are not necessarily rewritten, but - # their children might be. - # This ensures that commits on merged, but otherwise - # unrelated side branches are left alone. (Think "X" - # in the man page's example.) if test -z "$REBASE_ROOT" then mkdir "$REWRITTEN" && From 0aac0de4feb569ed17d501f04f17ecb8801dbad9 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:46 +0100 Subject: [PATCH 071/118] rebase -i: Introduce a constant AUTHOR_SCRIPT Add a constant AUTHOR_SCRIPT, holding the filename of the $DOTEST/author_script file, and document how this temporary file is used. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index acc92c439d..17641d444e 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -74,6 +74,12 @@ SQUASH_MSG="$DOTEST"/message-squash REWRITTEN="$DOTEST"/rewritten DROPPED="$DOTEST"/dropped + +# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE that will be used for the commit that is currently +# being rebased. +AUTHOR_SCRIPT="$DOTEST"/author-script + PRESERVE_MERGES= STRATEGY= ONTO= @@ -165,8 +171,8 @@ make_patch () { esac > "$DOTEST"/patch test -f "$MSG" || git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" - test -f "$DOTEST"/author-script || - get_author_ident_from_commit "$1" > "$DOTEST"/author-script + test -f "$AUTHOR_SCRIPT" || + get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } die_with_patch () { @@ -375,8 +381,7 @@ peek_next_command () { } do_next () { - rm -f "$MSG" "$DOTEST"/author-script \ - "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -452,7 +457,7 @@ do_next () { rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac - echo "$author_script" > "$DOTEST"/author-script + echo "$author_script" > "$AUTHOR_SCRIPT" if test $failed = f then # This is like --amend, but with a different message @@ -579,7 +584,7 @@ do then : Nothing to commit -- skip this else - . "$DOTEST"/author-script || + . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= if test -f "$DOTEST"/amend From a4049ae7ac97cb8887e9cd499c4dd0fed0eb968d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:47 +0100 Subject: [PATCH 072/118] rebase -i: Introduce a constant AMEND Add a constant AMEND holding the filename of the $DOTEST/amend file, and document how this temporary file is used. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 17641d444e..31f9b3b038 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -80,6 +80,14 @@ DROPPED="$DOTEST"/dropped # being rebased. AUTHOR_SCRIPT="$DOTEST"/author-script +# When an "edit" rebase command is being processed, the SHA1 of the +# commit to be edited is recorded in this file. When "git rebase +# --continue" is executed, if there are any staged changes then they +# will be amended to the HEAD commit, but only provided the HEAD +# commit is still the commit to be edited. When any other rebase +# command is processed, this file is deleted. +AMEND="$DOTEST"/amend + PRESERVE_MERGES= STRATEGY= ONTO= @@ -381,7 +389,7 @@ peek_next_command () { } do_next () { - rm -f "$MSG" "$AUTHOR_SCRIPT" "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -409,7 +417,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 - git rev-parse --verify HEAD > "$DOTEST"/amend + git rev-parse --verify HEAD > "$AMEND" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -587,10 +595,10 @@ do . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= - if test -f "$DOTEST"/amend + if test -f "$AMEND" then amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$DOTEST"/amend) || + test "$amend" = $(cat "$AMEND") || die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." From 959c0d06eafd7723517c953e80ee1a60881c373b Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:48 +0100 Subject: [PATCH 073/118] t3404: Test the commit count in commit messages generated by "rebase -i" The first line of commit messages generated for "rebase -i" squash/fixup commits includes a count of the number of commits that are being combined. Add machinery to check that this count is correct, and add such a check to some test cases. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/lib-rebase.sh | 6 +++++- t/t3404-rebase-interactive.sh | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 0db8250c58..2d922ae43c 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -2,9 +2,10 @@ # After setting the fake editor with this function, you can # -# - override the commit message with $FAKE_COMMIT_MESSAGE, +# - override the commit message with $FAKE_COMMIT_MESSAGE # - amend the commit message with $FAKE_COMMIT_AMEND # - check that non-commit messages have a certain line count with $EXPECT_COUNT +# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT # - rewrite a rebase -i script as directed by $FAKE_LINES. # $FAKE_LINES consists of a sequence of words separated by spaces. # The following word combinations are possible: @@ -25,6 +26,9 @@ set_fake_editor () { cat >> fake-editor.sh <<\EOF case "$1" in */COMMIT_EDITMSG) + test -z "$EXPECT_HEADER_COUNT" || + test "$EXPECT_HEADER_COUNT" = $(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1") || + exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index d9382e41d3..0335b781a0 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -135,7 +135,8 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 && + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=two \ + git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) ' @@ -230,6 +231,7 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' test_expect_success 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) @@ -239,6 +241,7 @@ test_expect_success 'multi-fixup only fires up editor once' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && @@ -258,6 +261,7 @@ test_expect_success 'squash and fixup generate correct log messages' ' git checkout -b squash-fixup E && base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && test_cmp expect-squash-fixup actual-squash-fixup && @@ -297,7 +301,8 @@ test_expect_success 'squash works as expected' ' git commit -m $n done && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=two \ + git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' From 5065ed296abc1c0b66ad7c5e963e048cb90b6ee6 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:49 +0100 Subject: [PATCH 074/118] rebase -i: Improve consistency of commit count in generated commit messages Use the numeral "2" instead of the word "two" when two commits are being interactively squashed. This makes the treatment consistent with that for higher numbers of commits. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 31f9b3b038..702c979414 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -360,7 +360,7 @@ make_squash_message () { }' <"$SQUASH_MSG" else COUNT=2 - echo "# This is a combination of two commits." + echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo git cat-file commit HEAD | sed -e '1,/^$/d' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 0335b781a0..05117091eb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -135,7 +135,7 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=two \ + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) @@ -301,7 +301,7 @@ test_expect_success 'squash works as expected' ' git commit -m $n done && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=two \ + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' From f99e269c44a7f07699a92c7e31d31ed93dafb409 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:50 +0100 Subject: [PATCH 075/118] rebase -i: Simplify commit counting for generated commit messages Read the old count from the first line of the old commit message rather than counting the number of commit message blocks in the file. This is simpler, faster, and more robust (e.g., it cannot be confused by strange commit message contents). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 702c979414..d60e059838 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -349,11 +349,9 @@ nth_string () { make_squash_message () { if test -f "$SQUASH_MSG"; then - # We want to be careful about matching only the commit - # message comment lines generated by this function. - # "[snrt][tdh]" matches the nth_string endings. - COUNT=$(($(sed -n "s/^# Th[^0-9]*\([1-9][0-9]*\)[snrt][tdh] commit message.*:/\1/p" \ - < "$SQUASH_MSG" | sed -ne '$p')+1)) + COUNT=$(($(sed -n \ + -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ + -e "q" < "$SQUASH_MSG")+1)) echo "# This is a combination of $COUNT commits." sed -e 1d -e '2,/^./{ /^$/d @@ -376,9 +374,6 @@ make_squash_message () { echo echo "# The $(nth_string $COUNT) commit message will be skipped:" echo - # Comment the lines of the commit message out using - # "# " rather than "# " to make them less likely to - # confuse the sed regexp above. git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /' ;; esac From ee0a4afbe2baeec2bfdf3d8e7abc77f24294298d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:51 +0100 Subject: [PATCH 076/118] rebase -i: Extract a function "commit_message" ...instead of repeating the same short but slightly obscure blob of code in several places. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index d60e059838..1499a67bba 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -118,6 +118,11 @@ output () { esac } +# Output the commit message for the specified commit. +commit_message () { + git cat-file commit "$1" | sed "1,/^$/d" +} + run_pre_rebase_hook () { if test -z "$OK_TO_SKIP_PRE_REBASE" && test -x "$GIT_DIR/hooks/pre-rebase" @@ -178,7 +183,7 @@ make_patch () { ;; esac > "$DOTEST"/patch test -f "$MSG" || - git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" + commit_message "$1" > "$MSG" test -f "$AUTHOR_SCRIPT" || get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } @@ -316,7 +321,7 @@ pick_one_preserving_merges () { # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ @@ -361,20 +366,20 @@ make_squash_message () { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - git cat-file commit HEAD | sed -e '1,/^$/d' + commit_message HEAD fi case $1 in squash) echo echo "# This is the $(nth_string $COUNT) commit message:" echo - git cat-file commit $2 | sed -e '1,/^$/d' + commit_message $2 ;; fixup) echo echo "# The $(nth_string $COUNT) commit message will be skipped:" echo - git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /' + commit_message $2 | sed -e 's/^/# /' ;; esac } From 5c5d059a0d8d2b0341077f7468dd480352775e1c Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:52 +0100 Subject: [PATCH 077/118] rebase -i: Handle the author script all in one place in do_next This change has no practical effect but makes the code easier to follow. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1499a67bba..122ba314d6 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -447,6 +447,8 @@ do_next () { make_squash_message $squash_style $sha1 > "$MSG" failed=f author_script=$(get_author_ident_from_commit HEAD) + echo "$author_script" > "$AUTHOR_SCRIPT" + eval "$author_script" output git reset --soft HEAD^ pick_one -n $sha1 || failed=t case "$(peek_next_command)" in @@ -465,11 +467,9 @@ do_next () { rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac - echo "$author_script" > "$AUTHOR_SCRIPT" if test $failed = f then # This is like --amend, but with a different message - eval "$author_script" GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ From 7756ecffe7d914fdbb42629a408d6da78b9794c1 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:53 +0100 Subject: [PATCH 078/118] rebase -i: Extract function do_with_author Call it instead of repeating similar code blocks in several places. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 122ba314d6..7d30829506 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -203,6 +203,15 @@ has_action () { sane_grep '^[^#]' "$1" >/dev/null } +# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE exported from the current environment. +do_with_author () { + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + "$@" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac @@ -324,11 +333,8 @@ pick_one_preserving_merges () { msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - output git merge $STRATEGY -m "$msg" \ - $new_parents + if ! do_with_author output \ + git merge $STRATEGY -m "$msg" $new_parents then printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" @@ -470,11 +476,9 @@ do_next () { if test $failed = f then # This is like --amend, but with a different message - GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || failed=t + do_with_author $USE_OUTPUT git commit --no-verify \ + $MSG_OPT "$EDIT_OR_FILE" || + failed=t fi if test $failed = t then @@ -605,8 +609,7 @@ first and then run 'git rebase --continue' again." git reset --soft HEAD^ || die "Cannot rewind the HEAD" fi - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$MSG" -e || { + do_with_author git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } From bde1a68624c59d891ec02fda473c3aef98ec9358 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:54 +0100 Subject: [PATCH 079/118] rebase -i: Change function make_squash_message into update_squash_message Alter the file $SQUASH_MSG in place rather than outputting the new message then juggling it around. Change the function name accordingly. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 7d30829506..d8c3c9aac9 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -358,21 +358,26 @@ nth_string () { esac } -make_squash_message () { +update_squash_message () { if test -f "$SQUASH_MSG"; then + mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit COUNT=$(($(sed -n \ -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ - -e "q" < "$SQUASH_MSG")+1)) - echo "# This is a combination of $COUNT commits." - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$SQUASH_MSG" + -e "q" < "$SQUASH_MSG".bak)+1)) + { + echo "# This is a combination of $COUNT commits." + sed -e 1d -e '2,/^./{ + /^$/d + }' <"$SQUASH_MSG".bak + } >$SQUASH_MSG else COUNT=2 - echo "# This is a combination of 2 commits." - echo "# The first commit's message is:" - echo - commit_message HEAD + { + echo "# This is a combination of 2 commits." + echo "# The first commit's message is:" + echo + commit_message HEAD + } >$SQUASH_MSG fi case $1 in squash) @@ -387,7 +392,7 @@ make_squash_message () { echo commit_message $2 | sed -e 's/^/# /' ;; - esac + esac >>$SQUASH_MSG } peek_next_command () { @@ -450,7 +455,7 @@ do_next () { die "Cannot '$squash_style' without a previous commit" mark_action_done - make_squash_message $squash_style $sha1 > "$MSG" + update_squash_message $squash_style $sha1 failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" @@ -460,16 +465,16 @@ do_next () { case "$(peek_next_command)" in squash|s|fixup|f) USE_OUTPUT=output + cp "$SQUASH_MSG" "$MSG" || exit MSG_OPT=-F EDIT_OR_FILE="$MSG" - cp "$MSG" "$SQUASH_MSG" ;; *) USE_OUTPUT= MSG_OPT= EDIT_OR_FILE=-e - rm -f "$SQUASH_MSG" || exit - cp "$MSG" "$GIT_DIR"/SQUASH_MSG + cp "$SQUASH_MSG" "$MSG" || exit + mv "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac From a25eb13909088f04f87acec26c522cc555f6b4a9 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:55 +0100 Subject: [PATCH 080/118] rebase -i: For fixup commands without squashes, do not start editor If the "rebase -i" commands include a series of fixup commands without any squash commands, then commit the combined commit using the commit message of the corresponding "pick" without starting up the commit-message editor. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 81 +++++++++++++++++++++++------------ t/t3404-rebase-interactive.sh | 7 ++- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index d8c3c9aac9..1569a7ad91 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -65,6 +65,13 @@ MSG="$DOTEST"/message # updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash +# If the current series of squash/fixups has not yet included a squash +# command, then this file exists and holds the commit message of the +# original "pick" commit. (If the series ends without a "squash" +# command, then this can be used as the commit message of the combined +# commit without opening the editor.) +FIXUP_MSG="$DOTEST"/message-fixup + # $REWRITTEN is the name of a directory containing files for each # commit that is reachable by at least one merge base of $HEAD and # $UPSTREAM. They are not necessarily rewritten, but their children @@ -358,7 +365,7 @@ nth_string () { esac } -update_squash_message () { +update_squash_messages () { if test -f "$SQUASH_MSG"; then mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit COUNT=$(($(sed -n \ @@ -371,16 +378,18 @@ update_squash_message () { }' <"$SQUASH_MSG".bak } >$SQUASH_MSG else + commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" COUNT=2 { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - commit_message HEAD + cat "$FIXUP_MSG" } >$SQUASH_MSG fi case $1 in squash) + rm -f "$FIXUP_MSG" echo echo "# This is the $(nth_string $COUNT) commit message:" echo @@ -455,7 +464,7 @@ do_next () { die "Cannot '$squash_style' without a previous commit" mark_action_done - update_squash_message $squash_style $sha1 + update_squash_messages $squash_style $sha1 failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" @@ -464,34 +473,52 @@ do_next () { pick_one -n $sha1 || failed=t case "$(peek_next_command)" in squash|s|fixup|f) - USE_OUTPUT=output - cp "$SQUASH_MSG" "$MSG" || exit - MSG_OPT=-F - EDIT_OR_FILE="$MSG" + # This is an intermediate commit; its message will only be + # used in case of trouble. So use the long version: + if test $failed = f + then + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + failed=t + fi + if test $failed = t + then + cp "$SQUASH_MSG" "$MSG" || exit + # After any kind of hiccup, prevent committing without + # opening the commit message editor: + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $sha1... $rest" + die_with_patch $sha1 "" + fi ;; *) - USE_OUTPUT= - MSG_OPT= - EDIT_OR_FILE=-e - cp "$SQUASH_MSG" "$MSG" || exit - mv "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG || exit + # This is the final command of this squash/fixup group + if test $failed = f + then + if test -f "$FIXUP_MSG" + then + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + failed=t + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + failed=t + fi + fi + rm -f "$FIXUP_MSG" + if test $failed = t + then + mv "$SQUASH_MSG" "$MSG" || exit + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $sha1... $rest" + die_with_patch $sha1 "" + fi + rm -f "$SQUASH_MSG" ;; esac - if test $failed = f - then - # This is like --amend, but with a different message - do_with_author $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || - failed=t - fi - if test $failed = t - then - cp "$MSG" "$GIT_DIR"/MERGE_MSG - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi ;; *) warn "Unknown command: $command $sha1 $rest" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 05117091eb..175a86c2cb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -237,14 +237,13 @@ test_expect_success 'multi-squash only fires up editor once' ' test 1 = $(git show | grep ONCE | wc -l) ' -test_expect_success 'multi-fixup only fires up editor once' ' +test_expect_success 'multi-fixup does not fire up editor' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ - EXPECT_HEADER_COUNT=4 \ + FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && - test 1 = $(git show | grep ONCE | wc -l) && + test 0 = $(git show | grep NEVER | wc -l) && git checkout to-be-rebased && git branch -D multi-fixup ' From 6c4c44c45881260877d7d9fd113fd75b74d7777c Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:56 +0100 Subject: [PATCH 081/118] t3404: Set up more of the test repo in the "setup" step ...and reuse these pre-created branches in tests rather than creating duplicates. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/t3404-rebase-interactive.sh | 51 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 175a86c2cb..a119ce0d37 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -14,15 +14,20 @@ that the result still makes sense. set_fake_editor -# set up two branches like this: +# Set up the repository like this: # -# A - B - C - D - E (master) +# one - two - three - four (conflict-branch) +# / +# A - B - C - D - E (master) +# | \ +# | F - G - H (branch1) +# | \ +# \ I (branch2) # \ -# F - G - H (branch1) -# \ -# I (branch2) +# J - K - L - M (no-conflict-branch) # -# where A, B, D and G touch the same file. +# where A, B, D and G all touch file1, and one, two, three, four all +# touch file "conflict". test_expect_success 'setup' ' test_commit A file1 && @@ -36,9 +41,20 @@ test_expect_success 'setup' ' test_commit H file5 && git checkout -b branch2 F && test_commit I file6 + git checkout -b conflict-branch A && + for n in one two three four + do + test_commit $n conflict + done && + git checkout -b no-conflict-branch A && + for n in J K L M + do + test_commit $n file$n + done ' test_expect_success 'no changes are a nop' ' + git checkout branch2 && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse HEAD) @@ -97,7 +113,7 @@ cat > expect2 << EOF D ======= G ->>>>>>> 91201e5... G +>>>>>>> 51047de... G EOF test_expect_success 'stop on conflicting pick' ' @@ -293,12 +309,7 @@ test_expect_success 'squash ignores blank lines' ' ' test_expect_success 'squash works as expected' ' - for n in one two three four - do - echo $n >> file$n && - git add file$n && - git commit -m $n - done && + git checkout -b squash-works no-conflict-branch && one=$(git rev-parse HEAD~3) && FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ git rebase -i HEAD~3 && @@ -306,12 +317,7 @@ test_expect_success 'squash works as expected' ' ' test_expect_success 'interrupted squash works as expected' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="1 squash 3 2" && @@ -328,12 +334,7 @@ test_expect_success 'interrupted squash works as expected' ' ' test_expect_success 'interrupted squash works as expected (case 2)' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash2 conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="3 squash 1 2" && From 6bdcd0d2fcca7c3985a078c8d343a863136fb675 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:57 +0100 Subject: [PATCH 082/118] rebase -i: Retain user-edited commit messages after squash/fixup conflicts When a squash/fixup fails due to a conflict, the user is required to edit the commit message. Previously, if further squash/fixup commands followed the conflicting squash/fixup, this user-edited message was discarded and a new automatically-generated commit message was suggested. Change the handling of conflicts within squash/fixup command series: Whenever the user is required to intervene, consider the resulting commit to be a new basis for the following squash/fixups and use its commit message in later suggested combined commit messages. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 66 ++++++++++++++--------------------- t/t3404-rebase-interactive.sh | 36 +++++++++++++++++++ 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1569a7ad91..b835a29759 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -408,6 +408,21 @@ peek_next_command () { sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO" } +# A squash/fixup has failed. Prepare the long version of the squash +# commit message, then die_with_patch. This code path requires the +# user to edit the combined commit message for all commits that have +# been squashed/fixedup so far. So also erase the old squash +# messages, effectively causing the combined commit to be used as the +# new basis for any further squash/fixups. Args: sha1 rest +die_failed_squash() { + mv "$SQUASH_MSG" "$MSG" || exit + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $1... $2" + die_with_patch $1 "" +} + do_next () { rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" @@ -465,58 +480,31 @@ do_next () { mark_action_done update_squash_messages $squash_style $sha1 - failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" eval "$author_script" output git reset --soft HEAD^ - pick_one -n $sha1 || failed=t + pick_one -n $sha1 || die_failed_squash $sha1 "$rest" case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if test $failed = f - then - do_with_author output git commit --no-verify -F "$SQUASH_MSG" || - failed=t - fi - if test $failed = t - then - cp "$SQUASH_MSG" "$MSG" || exit - # After any kind of hiccup, prevent committing without - # opening the commit message editor: - rm -f "$FIXUP_MSG" - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group - if test $failed = f + if test -f "$FIXUP_MSG" then - if test -f "$FIXUP_MSG" - then - do_with_author git commit --no-verify -F "$FIXUP_MSG" || - failed=t - else - cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --no-verify -e || - failed=t - fi + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + die_failed_squash $sha1 "$rest" + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + die_failed_squash $sha1 "$rest" fi - rm -f "$FIXUP_MSG" - if test $failed = t - then - mv "$SQUASH_MSG" "$MSG" || exit - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi - rm -f "$SQUASH_MSG" + rm -f "$SQUASH_MSG" "$FIXUP_MSG" ;; esac ;; diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index a119ce0d37..4e3513709e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -264,6 +264,42 @@ test_expect_success 'multi-fixup does not fire up editor' ' git branch -D multi-fixup ' +test_expect_success 'commit message used after conflict' ' + git checkout -b conflict-fixup conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 fixup 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-fixup +' + +test_expect_success 'commit message retained after conflict' ' + git checkout -b conflict-squash conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 squash 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 2 = $(git show | grep TWICE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-squash +' + cat > expect-squash-fixup << EOF B From 0a0416a34a7ef5c64f4e0226371e4cab8c1ba982 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Jan 2010 12:35:31 -0500 Subject: [PATCH 083/118] strbuf_expand: convert "%%" to "%" The only way to safely quote arbitrary text in a pretty-print user format is to replace instances of "%" with "%x25". This is slightly unreadable, and many users would expect "%%" to produce a single "%", as that is what printf format specifiers do. This patch converts "%%" to "%" for all users of strbuf_expand(): (1) git-daemon interpolated paths (2) pretty-print user formats (3) merge driver command lines Case (1) was already doing the conversion itself outside of strbuf_expand(). Case (2) is the intended beneficiary of this patch. Case (3) users probably won't notice, but as this is user-facing behavior, consistently providing the quoting mechanism makes sense. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 1 + Documentation/technical/api-strbuf.txt | 4 ++++ daemon.c | 1 - strbuf.c | 6 ++++++ t/t6006-rev-list-format.sh | 7 +++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 53a9168ba7..1686a54d22 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -134,6 +134,7 @@ The placeholders are: - '%C(...)': color specification, as described in color.branch.* config option - '%m': left, right or boundary mark - '%n': newline +- '%%': a raw '%' - '%x00': print a byte from a hex code - '%w([[,[,]]])': switch line wrapping, like the -w option of linkgit:git-shortlog[1]. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index a0e0f850f8..3b1da10f26 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -199,6 +199,10 @@ character if the letter `n` appears after a `%`. The function returns the length of the placeholder recognized and `strbuf_expand()` skips over it. + +The format `%%` is automatically expanded to a single `%` as a quoting +mechanism; callers do not need to handle the `%` placeholder themselves, +and the callback function will not be invoked for this placeholder. ++ All other characters (non-percent and not skipped ones) are copied verbatim to the strbuf. If the callback returned zero, meaning that the placeholder is unknown, then the percent sign is copied, too. diff --git a/daemon.c b/daemon.c index 5783e24011..51d9d6b8ac 100644 --- a/daemon.c +++ b/daemon.c @@ -147,7 +147,6 @@ static char *path_ok(char *directory) { "IP", ip_address }, { "P", tcp_port }, { "D", directory }, - { "%", "%" }, { NULL } }; diff --git a/strbuf.c b/strbuf.c index a6153dca27..6cbc1fcfd8 100644 --- a/strbuf.c +++ b/strbuf.c @@ -227,6 +227,12 @@ void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, break; format = percent + 1; + if (*format == '%') { + strbuf_addch(sb, '%'); + format++; + continue; + } + consumed = fn(sb, format, context); if (consumed) format += consumed; diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 571931588e..b0047d3c6b 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -19,6 +19,13 @@ test_cmp expect.$1 output.$1 " } +test_format percent %%h <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +%h +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +%h +EOF + test_format hash %H%n%h <<'EOF' commit 131a310eb913d107dd3c09a65d1651175898735d 131a310eb913d107dd3c09a65d1651175898735d From 361df5df77255321b2ca409d892b4c24b7b0441d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Jan 2010 12:36:42 -0500 Subject: [PATCH 084/118] strbuf: add strbuf_addbuf_percentquote This is handy for creating strings which will be fed to printf() or strbuf_expand(). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/technical/api-strbuf.txt | 7 +++++++ strbuf.c | 11 +++++++++++ strbuf.h | 1 + 3 files changed, 19 insertions(+) diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 3b1da10f26..afe2759951 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -218,6 +218,13 @@ which can be used by the programmer of the callback as she sees fit. placeholder and replacement string. The array needs to be terminated by an entry with placeholder set to NULL. +`strbuf_addbuf_percentquote`:: + + Append the contents of one strbuf to another, quoting any + percent signs ("%") into double-percents ("%%") in the + destination. This is useful for literal data to be fed to either + strbuf_expand or to the *printf family of functions. + `strbuf_addf`:: Add a formatted string to the buffer. diff --git a/strbuf.c b/strbuf.c index 6cbc1fcfd8..af9130e52d 100644 --- a/strbuf.c +++ b/strbuf.c @@ -257,6 +257,17 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, return 0; } +void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src) +{ + int i, len = src->len; + + for (i = 0; i < len; i++) { + if (src->buf[i] == '%') + strbuf_addch(dst, '%'); + strbuf_addch(dst, src->buf[i]); + } +} + size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) { size_t res; diff --git a/strbuf.h b/strbuf.h index fa07ecf094..84ac9424b5 100644 --- a/strbuf.h +++ b/strbuf.h @@ -116,6 +116,7 @@ struct strbuf_expand_dict_entry { const char *value; }; extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); +extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); __attribute__((format (printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); From 49ff9a7a02266a1b96e2236bc8f8d95e4b9507dd Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Jan 2010 12:39:51 -0500 Subject: [PATCH 085/118] commit: show interesting ident information in summary There are a few cases of user identity information that we consider interesting: (1) When the author and committer identities do not match. (2) When the committer identity was picked automatically from the username, hostname and GECOS information. In these cases, we already show the information in the commit message template. However, users do not always see that template because they might use "-m" or "-F". With this patch, we show these interesting cases after the commit, along with the subject and change summary. The new output looks like: $ git commit \ -m "federalist papers" \ --author='Publius ' [master 3d226a7] federalist papers Author: Publius 1 files changed, 1 insertions(+), 0 deletions(-) for case (1), and: $ git config --global --unset user.name $ git config --global --unset user.email $ git commit -m foo [master 7c2a927] foo Committer: Jeff King Your name and email address were configured automatically based on your username and hostname. Please check that they are accurate. You can suppress this message by setting them explicitly: git config --global user.name Your Name git config --global user.email you@example.com If the identity used for this commit is wrong, you can fix it with: git commit --amend --author='Your Name ' 1 files changed, 1 insertions(+), 0 deletions(-) for case (2). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-commit.c | 40 +++++++++++++++++++++++++++++++++++++--- t/t7501-commit.sh | 6 +++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index f54772f74a..b923038b0a 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -35,7 +35,20 @@ static const char * const builtin_status_usage[] = { 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], merge_head_sha1[20]; + static char *use_message_buffer; static const char commit_editmsg[] = "COMMIT_EDITMSG"; static struct lock_file index_lock; /* real index */ @@ -957,9 +970,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; struct commit *commit; - static const char *format = "format:%h] %s"; + 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) @@ -967,6 +983,23 @@ static void print_summary(const char *prefix, const unsigned char *sha1) 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_explicitly_given) { + strbuf_addstr(&format, "\n Committer: "); + strbuf_addbuf_percentquote(&format, &committer_ident); + 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); @@ -977,7 +1010,8 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; - get_commit_format(format, &rev); + get_commit_format(format.buf, &rev); + strbuf_release(&format); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; @@ -996,7 +1030,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) struct pretty_print_context ctx = {0}; struct strbuf buf = STRBUF_INIT; ctx.date_mode = DATE_NORMAL; - format_commit_message(commit, format + 7, &buf, &ctx); + format_commit_message(commit, format.buf + 7, &buf, &ctx); printf("%s\n", buf.buf); strbuf_release(&buf); } diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index a603f6d21a..0166d35e51 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -117,7 +117,11 @@ test_expect_success \ test_expect_success \ "overriding author from command line" \ "echo 'gak' >file && \ - git commit -m 'author' --author 'Rubber Duck ' -a" + git commit -m 'author' --author 'Rubber Duck ' -a >output 2>&1" + +test_expect_success \ + "commit --author output mentions author" \ + "grep Rubber.Duck output" test_expect_success PERL \ "interactive add" \ From b706fcfe93262e485976ed2bc648b779cc47981f Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 13 Jan 2010 15:17:08 -0500 Subject: [PATCH 086/118] commit: allow suppression of implicit identity advice We now nag the user with a giant warning when their identity was pulled from the username, hostname, and gecos information, in case it is not correct. Most users will suppress this by simply setting up their information correctly. However, there may be some users who consciously want to use that information, because having the value change from host to host contains useful information. These users can now set advice.implicitidentity to false to suppress the message. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ advice.c | 2 ++ advice.h | 1 + builtin-commit.c | 6 ++++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index a1e36d7e42..6f70cc953e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -130,6 +130,10 @@ advice.*:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwritting local changes. Default: true. + implicitIdentity:: + Advice on how to set your identity configuration when + your information is guessed from the system username and + domain name. Default: true. -- core.fileMode:: diff --git a/advice.c b/advice.c index cb666acc3c..8f7de0e9ed 100644 --- a/advice.c +++ b/advice.c @@ -3,6 +3,7 @@ int advice_push_nonfastforward = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; +int advice_implicit_identity = 1; static struct { const char *name; @@ -11,6 +12,7 @@ static struct { { "pushnonfastforward", &advice_push_nonfastforward }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, + { "implicitidentity", &advice_implicit_identity }, }; int git_default_advice_config(const char *var, const char *value) diff --git a/advice.h b/advice.h index 3de5000d8c..728ab90ef1 100644 --- a/advice.h +++ b/advice.h @@ -4,6 +4,7 @@ extern int advice_push_nonfastforward; extern int advice_status_hints; extern int advice_commit_before_merge; +extern int advice_implicit_identity; int git_default_advice_config(const char *var, const char *value); diff --git a/builtin-commit.c b/builtin-commit.c index b923038b0a..a73a532f2f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -994,8 +994,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1) if (!user_ident_explicitly_given) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); - strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice); + if (advice_implicit_identity) { + strbuf_addch(&format, '\n'); + strbuf_addstr(&format, implicit_ident_advice); + } } strbuf_release(&author_ident); strbuf_release(&committer_ident); From 8db751a8f953bf1a571e1fd825d1a4219e65b501 Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Thu, 14 Jan 2010 07:44:19 +0300 Subject: [PATCH 087/118] fast-import: tag may point to any object type If you tried to export the official git repository, and then to import it back then git-fast-import would die complaining that "Mark :1 not a commit". Accordingly to a generated crash file, Mark 1 is not a commit but a blob, which is pointed by junio-gpg-pub tag. Because git-tag allows to create such tags, git-fast-import should import them. Signed-off-by: Dmitry Potapov Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- fast-import.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fast-import.c b/fast-import.c index 6faaaacb68..36c5a8ebd9 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2225,6 +2225,7 @@ static void parse_new_tag(void) struct tag *t; uintmax_t from_mark = 0; unsigned char sha1[20]; + enum object_type type; /* Obtain the new tag name from the rest of our command */ sp = strchr(command_buf.buf, ' ') + 1; @@ -2245,19 +2246,18 @@ static void parse_new_tag(void) s = lookup_branch(from); if (s) { hashcpy(sha1, s->sha1); + type = OBJ_COMMIT; } else if (*from == ':') { struct object_entry *oe; from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); - if (oe->type != OBJ_COMMIT) - die("Mark :%" PRIuMAX " not a commit", from_mark); + type = oe->type; hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; char *buf; - buf = read_object_with_reference(sha1, - commit_type, &size, sha1); + buf = read_sha1_file(sha1, &type, &size); if (!buf || size < 46) die("Not a valid commit: %s", from); free(buf); @@ -2282,7 +2282,7 @@ static void parse_new_tag(void) "object %s\n" "type %s\n" "tag %s\n", - sha1_to_hex(sha1), commit_type, t->name); + sha1_to_hex(sha1), typename(type), t->name); if (tagger) strbuf_addf(&new_data, "tagger %s\n", tagger); From 7e622650d756955a94f546866eddd51506aa93a3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 15 Jan 2010 12:50:54 -0800 Subject: [PATCH 088/118] grep: prepare to run outside of a work tree This moves the call to setup_git_directory() for running "grep" from the "git" wrapper to the implementation of the "grep" subcommand. A new variable "use_index" is always true at this stage in the series, and when it is on, we require that we are in a directory that is under git control. To make sure we die the same way, we make a second call into setup_git_directory() when we detect this situation. Signed-off-by: Junio C Hamano --- builtin-grep.c | 7 +++++++ git.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin-grep.c b/builtin-grep.c index 3d6ebb586d..229555d52d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -414,6 +414,7 @@ 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"), @@ -497,6 +498,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. @@ -534,6 +537,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, diff --git a/git.c b/git.c index 11544cdb40..ad074735fc 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 }, { "help", cmd_help }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, From 308162372d0aa202ff45743e02253e20a4fac4d7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 15 Jan 2010 12:52:40 -0800 Subject: [PATCH 089/118] grep --no-index: allow use of "git grep" outside a git repository Just like some people wanted diff features that are not found in other people's diff implementations outside of a git repository and added --no-index mode to the command, this adds --no-index mode to the "git grep" command. Also, inside a git repository, --no-index mode allows you to grep in untracked (but not ignored) files. Signed-off-by: Junio C Hamano --- builtin-grep.c | 26 +++++++++++++++++++++++++ t/t7002-grep.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/builtin-grep.c b/builtin-grep.c index 229555d52d..12833733db 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" static char const * const grep_usage[] = { "git grep [options] [-e] [...] [[--] path...]", @@ -320,6 +321,21 @@ 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); + free_grep_patterns(opt); + return hit; +} + static int context_callback(const struct option *opt, const char *arg, int unset) { @@ -418,6 +434,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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"), @@ -591,6 +609,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } + if (!use_index) { + if (cached) + die("--cached cannot be used with --no-index."); + if (list.nr) + die("--no-index cannot be used with revs."); + return !grep_directory(&opt, paths); + } + if (!list.nr) { if (!cached) setup_work_tree(); diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index c369cdbe9b..7eceb086be 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -426,4 +426,56 @@ test_expect_success 'grep -Fi' ' test_cmp expected actual ' +test_expect_success 'outside of git repository' ' + rm -fr non && + mkdir -p non/git/sub && + echo hello >non/git/file1 && + echo world >non/git/sub/file2 && + echo ".*o*" >non/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >non/expect.full && + echo file2:world >non/expect.sub + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git grep o && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full + cd sub && + test_must_fail git grep o && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + +test_expect_success 'inside git repository but with --no-index' ' + rm -fr is && + mkdir -p is/git/sub && + echo hello >is/git/file1 && + echo world >is/git/sub/file2 && + echo ".*o*" >is/git/.gitignore && + { + echo file1:hello && + echo sub/file2:world + } >is/expect.full && + : >is/expect.empty && + echo file2:world >is/expect.sub + ( + cd is/git && + git init && + test_must_fail git grep o >../actual.full && + test_cmp ../expect.empty ../actual.full && + git grep --no-index o >../actual.full && + test_cmp ../expect.full ../actual.full && + cd sub && + test_must_fail git grep o >../../actual.sub && + test_cmp ../../expect.empty ../../actual.sub && + git grep --no-index o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) +' + test_done From a9e11220c2656cc5d7baac4d0735c04f9be46438 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 15 Jan 2010 14:03:42 -0800 Subject: [PATCH 090/118] t7800-difftool.sh: Simplify the --extcmd test Instead of running 'grep', 'echo', and 'wc' we simply compare git-difftool's output against a known good value. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- t/t7800-difftool.sh | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 8ee186a5fb..1d9e07b0d8 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -15,6 +15,9 @@ if ! test_have_prereq PERL; then test_done fi +LF=' +' + remove_config_vars() { # Unset all config variables used by git-difftool @@ -219,19 +222,13 @@ test_expect_success 'difftool..path' ' restore_test_defaults ' -test_expect_success 'difftool --extcmd=...' ' +test_expect_success 'difftool --extcmd=cat' ' diff=$(git difftool --no-prompt --extcmd=cat branch) && + test "$diff" = branch"$LF"master - lines=$(echo "$diff" | wc -l) && - test "$lines" -eq 2 && - lines=$(echo "$diff" | grep master | wc -l) && - test "$lines" -eq 1 && - lines=$(echo "$diff" | grep branch | wc -l) && - test "$lines" -eq 1 && - restore_test_defaults ' test_done From f47f1e2ce8b4022113120b32decb4436341dc3aa Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 15 Jan 2010 14:03:43 -0800 Subject: [PATCH 091/118] difftool: Add '-x' and as an alias for '--extcmd' This adds '-x' as a shorthand for the '--extcmd' option. Arguments to '--extcmd' can be specified separately, which was not originally possible. This also fixes the brief help text so that it mentions both '-x' and '--extcmd'. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- Documentation/git-difftool.txt | 3 ++- git-difftool.perl | 21 ++++++++++++++------- t/t7800-difftool.sh | 8 ++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index f67d2db761..5c68cff905 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,7 +7,7 @@ git-difftool - Show changes using common diff tools SYNOPSIS -------- -'git difftool' [--tool=] [-y|--no-prompt|--prompt] [<'git diff' options>] +'git difftool' [] {0,2} [--] [...] DESCRIPTION ----------- @@ -58,6 +58,7 @@ is set to the name of the temporary file containing the contents of the diff post-image. `$BASE` is provided for compatibility with custom merge tool commands and has the same value as `$LOCAL`. +-x :: --extcmd=:: Specify a custom command for viewing diffs. 'git-difftool' ignores the configured defaults and runs diff --git a/git-difftool.perl b/git-difftool.perl index f8ff245756..d975d072db 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # # This is a wrapper around the GIT_EXTERNAL_DIFF-compatible # git-difftool--helper script. @@ -23,8 +23,9 @@ my $DIR = abs_path(dirname($0)); sub usage { print << 'USAGE'; -usage: git difftool [-g|--gui] [-t|--tool=] [-y|--no-prompt] - ["git diff" options] +usage: git difftool [-t|--tool=] [-x|--extcmd=] + [-y|--no-prompt] [-g|--gui] + ['git diff' options] USAGE exit 1; } @@ -62,14 +63,20 @@ sub generate_command $skip_next = 1; next; } - if ($arg =~ /^--extcmd=/) { - $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9); - next; - } if ($arg =~ /^--tool=/) { $ENV{GIT_DIFF_TOOL} = substr($arg, 7); next; } + if ($arg eq '-x' || $arg eq '--extcmd') { + usage() if $#ARGV <= $idx; + $ENV{GIT_DIFFTOOL_EXTCMD} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--extcmd=/) { + $ENV{GIT_DIFFTOOL_EXTCMD} = substr($arg, 9); + next; + } if ($arg eq '-g' || $arg eq '--gui') { my $tool = Git::command_oneline('config', 'diff.guitool'); diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 1d9e07b0d8..69e1c34159 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -225,8 +225,16 @@ test_expect_success 'difftool..path' ' test_expect_success 'difftool --extcmd=cat' ' diff=$(git difftool --no-prompt --extcmd=cat branch) && test "$diff" = branch"$LF"master +' +test_expect_success 'difftool --extcmd cat' ' + diff=$(git difftool --no-prompt --extcmd cat branch) && + test "$diff" = branch"$LF"master +' +test_expect_success 'difftool -x cat' ' + diff=$(git difftool --no-prompt -x cat branch) && + test "$diff" = branch"$LF"master ' From 9f3d54d193d9edcf443c9dd62789af5e8e47635c Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 15 Jan 2010 14:03:44 -0800 Subject: [PATCH 092/118] difftool: Use eval to expand '--extcmd' expressions It was not possible to pass quoted commands to '--extcmd'. By using 'eval' we ensure that expressions with spaces and quotes are supported. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- git-difftool--helper.sh | 3 +-- t/t7800-difftool.sh | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index d806eaef54..69f6bcebcb 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -48,11 +48,10 @@ launch_merge_tool () { fi if use_ext_cmd; then - $GIT_DIFFTOOL_EXTCMD "$LOCAL" "$REMOTE" + eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' else run_merge_tool "$merge_tool" fi - } if ! use_ext_cmd; then diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 69e1c34159..a183f1db4a 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -235,8 +235,21 @@ test_expect_success 'difftool --extcmd cat' ' test_expect_success 'difftool -x cat' ' diff=$(git difftool --no-prompt -x cat branch) && test "$diff" = branch"$LF"master +' +test_expect_success 'difftool --extcmd echo arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) + test "$diff" = file +' +test_expect_success 'difftool --extcmd cat arg1' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) + test "$diff" = master +' + +test_expect_success 'difftool --extcmd cat arg2' ' + diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) + test "$diff" = branch ' test_done From c8a5672ea5cf593708b29f8279bd651c16047c6e Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 15 Jan 2010 19:10:03 -0800 Subject: [PATCH 093/118] difftool: Update copyright notices to list each year separately This is http://www.gnu.org/licenses/gpl-howto.html advises. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- git-difftool--helper.sh | 2 +- t/t7800-difftool.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 69f6bcebcb..e43b5d64de 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -3,7 +3,7 @@ # This script is typically launched by using the 'git difftool' # convenience command. # -# Copyright (c) 2009-2010 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar TOOL_MODE=diff . git-mergetool--lib diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index a183f1db4a..fad5472257 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (c) 2009 David Aguilar +# Copyright (c) 2009, 2010 David Aguilar # test_description='git-difftool From e9fcd1e2121100d43d2d212eb6c6f1fc82aade1d Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Sat, 16 Jan 2010 23:45:31 +0200 Subject: [PATCH 094/118] Add push --set-upstream Frequent complaint is lack of easy way to set up upstream (tracking) references for git pull to work as part of push command. So add switch --set-upstream (-u) to do just that. Signed-off-by: Jeff King Signed-off-by: Ilari Liusvaara Signed-off-by: Junio C Hamano --- Documentation/git-push.txt | 9 ++++- builtin-push.c | 2 ++ t/t5523-push-upstream.sh | 69 ++++++++++++++++++++++++++++++++++++++ transport.c | 56 +++++++++++++++++++++++++++++++ transport.h | 1 + 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100755 t/t5523-push-upstream.sh diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index e3eb1e8f19..2a5394b832 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=] - [--repo=] [-f | --force] [-v | --verbose] + [--repo=] [-f | --force] [-v | --verbose] [-u | --set-upstream] [ ...] DESCRIPTION @@ -122,6 +122,13 @@ nor in any Push line of the corresponding remotes file---see below). the name "origin" is used. For this latter case, this option can be used to override the name "origin". In other words, the difference between these two commands + +-u:: +--set-upstream:: + For every branch that is up to date or successfully pushed, add + upstream (tracking) reference, used by argument-less + linkgit:git-pull[1] and other commands. For more information, + see 'branch..merge' in linkgit:git-config[1]. + -------------------------- git push public #1 diff --git a/builtin-push.c b/builtin-push.c index 28a26e7db2..5df66081a6 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -218,6 +218,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", "receive pack program"), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"), + OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status", + TRANSPORT_PUSH_SET_UPSTREAM), OPT_END() }; diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh new file mode 100755 index 0000000000..00da70763b --- /dev/null +++ b/t/t5523-push-upstream.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='push with --set-upstream' +. ./test-lib.sh + +test_expect_success 'setup bare parent' ' + git init --bare parent && + git remote add upstream parent +' + +test_expect_success 'setup local commit' ' + echo content >file && + git add file && + git commit -m one +' + +check_config() { + (echo $2; echo $3) >expect.$1 + (git config branch.$1.remote + git config branch.$1.merge) >actual.$1 + test_cmp expect.$1 actual.$1 +} + +test_expect_success 'push -u master:master' ' + git push -u upstream master:master && + check_config master upstream refs/heads/master +' + +test_expect_success 'push -u master:other' ' + git push -u upstream master:other && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u --dry-run master:otherX' ' + git push -u --dry-run upstream master:otherX && + check_config master upstream refs/heads/other +' + +test_expect_success 'push -u master2:master2' ' + git branch master2 && + git push -u upstream master2:master2 && + check_config master2 upstream refs/heads/master2 +' + +test_expect_success 'push -u master2:other2' ' + git push -u upstream master2:other2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u :master2' ' + git push -u upstream :master2 && + check_config master2 upstream refs/heads/other2 +' + +test_expect_success 'push -u --all' ' + git branch all1 && + git branch all2 && + git push -u --all && + check_config all1 upstream refs/heads/all1 && + check_config all2 upstream refs/heads/all2 +' + +test_expect_success 'push -u HEAD' ' + git checkout -b headbranch && + git push -u upstream HEAD && + check_config headbranch upstream refs/heads/headbranch +' + +test_done diff --git a/transport.c b/transport.c index b5332c018b..8cc287d442 100644 --- a/transport.c +++ b/transport.c @@ -8,6 +8,7 @@ #include "bundle.h" #include "dir.h" #include "refs.h" +#include "branch.h" /* rsync support */ @@ -135,6 +136,53 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } +static void set_upstreams(struct transport *transport, struct ref *refs, + int pretend) +{ + struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + const char *localname; + const char *tmp; + const char *remotename; + unsigned char sha[20]; + int flag = 0; + /* + * Check suitability for tracking. Must be successful / + * already up-to-date ref create/modify (not delete). + */ + if (ref->status != REF_STATUS_OK && + ref->status != REF_STATUS_UPTODATE) + continue; + if (!ref->peer_ref) + continue; + if (!ref->new_sha1 || is_null_sha1(ref->new_sha1)) + continue; + + /* Follow symbolic refs (mainly for HEAD). */ + localname = ref->peer_ref->name; + remotename = ref->name; + tmp = resolve_ref(localname, sha, 1, &flag); + if (tmp && flag & REF_ISSYMREF && + !prefixcmp(tmp, "refs/heads/")) + localname = tmp; + + /* Both source and destination must be local branches. */ + if (!localname || prefixcmp(localname, "refs/heads/")) + continue; + if (!remotename || prefixcmp(remotename, "refs/heads/")) + continue; + + if (!pretend) + install_branch_config(BRANCH_CONFIG_VERBOSE, + localname + 11, transport->remote->name, + remotename); + else + printf("Would set upstream of '%s' to '%s' of '%s'\n", + localname + 11, remotename + 11, + transport->remote->name); + } +} + static const char *rsync_url(const char *url) { return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url; @@ -974,6 +1022,10 @@ int transport_push(struct transport *transport, verify_remote_names(refspec_nr, refspec); if (transport->push) { + /* Maybe FIXME. But no important transport uses this case. */ + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + die("This transport does not support using --set-upstream"); + return transport->push(transport, refspec_nr, refspec, flags); } else if (transport->push_refs) { struct ref *remote_refs = @@ -983,6 +1035,7 @@ int transport_push(struct transport *transport, int verbose = flags & TRANSPORT_PUSH_VERBOSE; int quiet = flags & TRANSPORT_PUSH_QUIET; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; + int pretend = flags & TRANSPORT_PUSH_DRY_RUN; int ret; if (flags & TRANSPORT_PUSH_ALL) @@ -1002,6 +1055,9 @@ int transport_push(struct transport *transport, verbose | porcelain, porcelain, nonfastforward); + if (flags & TRANSPORT_PUSH_SET_UPSTREAM) + set_upstreams(transport, remote_refs, pretend); + if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) diff --git a/transport.h b/transport.h index 97ba2519dd..c4314dd59b 100644 --- a/transport.h +++ b/transport.h @@ -91,6 +91,7 @@ struct transport { #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 #define TRANSPORT_PUSH_QUIET 64 +#define TRANSPORT_PUSH_SET_UPSTREAM 128 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); From 56932249cf7d327115eae608d550e3a8cca4ea06 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Fri, 15 Jan 2010 21:12:15 +0100 Subject: [PATCH 095/118] Windows: disable Python Python is not commonly installed on Windows machines, so we should disable it there by default. Signed-off-by: Erik Faye-Lund Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 57045dee89..6933555660 100644 --- a/Makefile +++ b/Makefile @@ -995,6 +995,7 @@ ifeq ($(uname_S),Windows) OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease NO_CURL = YesPlease + NO_PYTHON = YesPlease NO_PTHREADS = YesPlease BLK_SHA1 = YesPlease @@ -1045,6 +1046,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo NO_REGEX = YesPlease + NO_PYTHON = YesPlease BLK_SHA1 = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" From 928500e00e0a73a11f5b2296d9baa82dafa7a1a8 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 15 Jan 2010 21:12:16 +0100 Subject: [PATCH 096/118] Windows: boost startup by avoiding a static dependency on shell32.dll This DLL is only needed to invoke the browser in a "git help" call. By looking up the only function that we need at runtime, we can avoid the startup costs of this DLL. DLL usage can be profiled with Microsoft's Dependency Walker. For example, a call to "git diff-files" loaded before: 19 DLLs after: 9 DLLs As a result, the runtime of 'make -j2 test' went down from 16:00min to 12:40min on one of my boxes. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 0d73f15fa8..2afc978dfd 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3,8 +3,6 @@ #include #include "../strbuf.h" -#include - static int err_win_to_posix(DWORD winerr) { int error = ENOSYS; @@ -1338,8 +1336,22 @@ static const char *make_backslash_path(const char *path) void mingw_open_html(const char *unixpath) { const char *htmlpath = make_backslash_path(unixpath); + typedef HINSTANCE (WINAPI *T)(HWND, const char *, + const char *, const char *, const char *, INT); + T ShellExecute; + HMODULE shell32; + + shell32 = LoadLibrary("shell32.dll"); + if (!shell32) + die("cannot load shell32.dll"); + ShellExecute = (T)GetProcAddress(shell32, "ShellExecuteA"); + if (!ShellExecute) + die("cannot run browser"); + printf("Launching default browser to display HTML ...\n"); ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); + + FreeLibrary(shell32); } int link(const char *oldpath, const char *newpath) From 3e34d6657733430164ef67ab2f000fa3d10d51b5 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 15 Jan 2010 21:12:17 +0100 Subject: [PATCH 097/118] Windows: simplify the pipe(2) implementation Our implementation of pipe() must create non-inheritable handles for the reason that when a child process is started, there is no opportunity to close the unneeded pipe ends in the child (on POSIX this is done between fork() and exec()). Previously, we used the _pipe() function provided by Microsoft's C runtime (which creates inheritable handles) and then turned the handles into non-inheritable handles using the DuplicateHandle() API. Simplify the procedure by using the CreatePipe() API, which can create non-inheritable handles right from the beginning. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.c | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 2afc978dfd..162d1ff283 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -299,46 +299,25 @@ int gettimeofday(struct timeval *tv, void *tz) int pipe(int filedes[2]) { - int fd; - HANDLE h[2], parent; + HANDLE h[2]; - if (_pipe(filedes, 8192, 0) < 0) - return -1; - - parent = GetCurrentProcess(); - - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[0]), - parent, &h[0], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); + /* this creates non-inheritable handles */ + if (!CreatePipe(&h[0], &h[1], NULL, 8192)) { + errno = err_win_to_posix(GetLastError()); return -1; } - if (!DuplicateHandle (parent, (HANDLE)_get_osfhandle(filedes[1]), - parent, &h[1], 0, FALSE, DUPLICATE_SAME_ACCESS)) { - close(filedes[0]); - close(filedes[1]); - CloseHandle(h[0]); - return -1; - } - fd = _open_osfhandle((int)h[0], O_NOINHERIT); - if (fd < 0) { - close(filedes[0]); - close(filedes[1]); + filedes[0] = _open_osfhandle((int)h[0], O_NOINHERIT); + if (filedes[0] < 0) { CloseHandle(h[0]); CloseHandle(h[1]); return -1; } - close(filedes[0]); - filedes[0] = fd; - fd = _open_osfhandle((int)h[1], O_NOINHERIT); - if (fd < 0) { + filedes[1] = _open_osfhandle((int)h[1], O_NOINHERIT); + if (filedes[0] < 0) { close(filedes[0]); - close(filedes[1]); CloseHandle(h[1]); return -1; } - close(filedes[1]); - filedes[1] = fd; return 0; } From 75301f90159a505f3683a5eba10174928dc30fb1 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 15 Jan 2010 21:12:18 +0100 Subject: [PATCH 098/118] Windows: avoid the "dup dance" when spawning a child process When stdin, stdout, or stderr must be redirected for a child process that on Windows is spawned using one of the spawn() functions of Microsoft's C runtime, then there is no choice other than to 1. make a backup copy of fd 0,1,2 with dup 2. dup2 the redirection source fd into 0,1,2 3. spawn 4. dup2 the backup back into 0,1,2 5. close the backup copy and the redirection source We used this idiom as well -- but we are not using the spawn() functions anymore! Instead, we have our own implementation. We had hardcoded that stdin, stdout, and stderr of the child process were inherited from the parent's fds 0, 1, and 2. But we can actually specify any fd. With this patch, the fds to inherit are passed from start_command()'s WIN32 section to our spawn implementation. This way, we can avoid the backup copies of the fds. The backup copies were a bug waiting to surface: The OS handles underlying the dup()ed fds were inherited by the child process (but were not associated with a file descriptor in the child). Consequently, the file or pipe represented by the OS handle remained open even after the backup copy was closed in the parent process until the child exited. Since our implementation of pipe() creates non-inheritable OS handles, we still dup() file descriptors in start_command() because dup() happens to create inheritable duplicates. (A nice side effect is that the fd cleanup in start_command is the same for Windows and Unix and remains unchanged.) Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.c | 25 +++++++++++++------ compat/mingw.h | 3 ++- run-command.c | 67 ++++++++++++++++++++++---------------------------- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 162d1ff283..73762473c5 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -615,8 +615,8 @@ static int env_compare(const void *a, const void *b) return strcasecmp(*ea, *eb); } -static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, - int prepend_cmd) +static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env, + int prepend_cmd, int fhin, int fhout, int fherr) { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -652,9 +652,9 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = (HANDLE) _get_osfhandle(0); - si.hStdOutput = (HANDLE) _get_osfhandle(1); - si.hStdError = (HANDLE) _get_osfhandle(2); + si.hStdInput = (HANDLE) _get_osfhandle(fhin); + si.hStdOutput = (HANDLE) _get_osfhandle(fhout); + si.hStdError = (HANDLE) _get_osfhandle(fherr); /* concatenate argv, quoting args as we go */ strbuf_init(&args, 0); @@ -709,7 +709,14 @@ static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, return (pid_t)pi.hProcess; } -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) +static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env, + int prepend_cmd) +{ + return mingw_spawnve_fd(cmd, argv, env, prepend_cmd, 0, 1, 2); +} + +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr) { pid_t pid; char **path = get_path_split(); @@ -731,13 +738,15 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env) pid = -1; } else { - pid = mingw_spawnve(iprog, argv, env, 1); + pid = mingw_spawnve_fd(iprog, argv, env, 1, + fhin, fhout, fherr); free(iprog); } argv[0] = argv0; } else - pid = mingw_spawnve(prog, argv, env, 0); + pid = mingw_spawnve_fd(prog, argv, env, 0, + fhin, fhout, fherr); free(prog); } free_path_split(path); diff --git a/compat/mingw.h b/compat/mingw.h index b3d299f5bc..a105dc9a27 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -220,7 +220,8 @@ int mingw_fstat(int fd, struct stat *buf); int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env); +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp diff --git a/run-command.c b/run-command.c index cf2d8f7fae..d270664026 100644 --- a/run-command.c +++ b/run-command.c @@ -8,12 +8,14 @@ static inline void close_pair(int fd[2]) close(fd[1]); } +#ifndef WIN32 static inline void dup_devnull(int to) { int fd = open("/dev/null", O_RDWR); dup2(fd, to); close(fd); } +#endif int start_command(struct child_process *cmd) { @@ -135,42 +137,30 @@ fail_pipe: strerror(failed_errno = errno)); #else { - int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ + int fhin = 0, fhout = 1, fherr = 2; const char **sargv = cmd->argv; char **env = environ; - if (cmd->no_stdin) { - s0 = dup(0); - dup_devnull(0); - } else if (need_in) { - s0 = dup(0); - dup2(fdin[0], 0); - } else if (cmd->in) { - s0 = dup(0); - dup2(cmd->in, 0); - } + if (cmd->no_stdin) + fhin = open("/dev/null", O_RDWR); + else if (need_in) + fhin = dup(fdin[0]); + else if (cmd->in) + fhin = dup(cmd->in); - if (cmd->no_stderr) { - s2 = dup(2); - dup_devnull(2); - } else if (need_err) { - s2 = dup(2); - dup2(fderr[1], 2); - } + if (cmd->no_stderr) + fherr = open("/dev/null", O_RDWR); + else if (need_err) + fherr = dup(fderr[1]); - if (cmd->no_stdout) { - s1 = dup(1); - dup_devnull(1); - } else if (cmd->stdout_to_stderr) { - s1 = dup(1); - dup2(2, 1); - } else if (need_out) { - s1 = dup(1); - dup2(fdout[1], 1); - } else if (cmd->out > 1) { - s1 = dup(1); - dup2(cmd->out, 1); - } + if (cmd->no_stdout) + fhout = open("/dev/null", O_RDWR); + else if (cmd->stdout_to_stderr) + fhout = dup(fherr); + else if (need_out) + fhout = dup(fdout[1]); + else if (cmd->out > 1) + fhout = dup(cmd->out); if (cmd->dir) die("chdir in start_command() not implemented"); @@ -181,7 +171,8 @@ fail_pipe: cmd->argv = prepare_git_cmd(cmd->argv); } - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, + fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); @@ -192,12 +183,12 @@ fail_pipe: free(cmd->argv); cmd->argv = sargv; - if (s0 >= 0) - dup2(s0, 0), close(s0); - if (s1 >= 0) - dup2(s1, 1), close(s1); - if (s2 >= 0) - dup2(s2, 2), close(s2); + if (fhin != 0) + close(fhin); + if (fhout != 1) + close(fhout); + if (fherr != 2) + close(fherr); } #endif From b6f714f89a2abb591e6d46595f43bc8d4d356a72 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Fri, 15 Jan 2010 21:12:19 +0100 Subject: [PATCH 099/118] MSVC: Fix an "incompatible pointer types" compiler warning In particular, the following warning is issued while compiling compat/msvc.c: ...mingw.c(223) : warning C4133: 'function' : incompatible \ types - from '_stati64 *' to '_stat64 *' which relates to a call of _fstati64() in the mingw_fstat() function definition. This is caused by various layers of macro magic and attempts to avoid macro redefinition compiler warnings. For example, the call to _fstati64() mentioned above is actually a call to _fstat64(), and expects a pointer to a struct _stat64 rather than the struct _stati64 which is passed to mingw_fstat(). The definition of struct _stati64 given in compat/msvc.h had the same "shape" as the definition of struct _stat64, so the call to _fstat64() does not actually cause any runtime errors, but the structure types are indeed incompatible. In order to avoid the compiler warning, we add declarations for the mingw_lstat() and mingw_fstat() functions and supporting macros to msvc.h, suppressing the corresponding declarations in mingw.h, so that we can use the appropriate structure type (and function) names from the msvc headers. Signed-off-by: Ramsay Jones Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.h | 4 +++- compat/msvc.h | 42 +++++++++++++++++------------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/compat/mingw.h b/compat/mingw.h index a105dc9a27..afe12ea554 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -209,13 +209,15 @@ int mingw_getpagesize(void); * mingw_fstat() instead of fstat() on Windows. */ #define off_t off64_t -#define stat _stati64 #define lseek _lseeki64 +#ifndef ALREADY_DECLARED_STAT_FUNCS +#define stat _stati64 int mingw_lstat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat #define _stati64(x,y) mingw_lstat(x,y) +#endif int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime diff --git a/compat/msvc.h b/compat/msvc.h index 9c753a560f..023aba0238 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -21,30 +21,22 @@ static __inline int strcasecmp (const char *s1, const char *s2) } #undef ERROR -#undef stat -#undef _stati64 -#include "compat/mingw.h" -#undef stat -#define stat _stati64 -#define _stat64(x,y) mingw_lstat(x,y) -/* - Even though _stati64 is normally just defined at _stat64 - on Windows, we specify it here as a proper struct to avoid - compiler warnings about macro redefinition due to magic in - mingw.h. Struct taken from ReactOS (GNU GPL license). -*/ -struct _stati64 { - _dev_t st_dev; - _ino_t st_ino; - unsigned short st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - __int64 st_size; - time_t st_atime; - time_t st_mtime; - time_t st_ctime; -}; +/* Use mingw_lstat() instead of lstat()/stat() and mingw_fstat() instead + * of fstat(). We add the declaration of these functions here, suppressing + * the corresponding declarations in mingw.h, so that we can use the + * appropriate structure type (and function) names from the msvc headers. + */ +#define stat _stat64 +int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_fstat(int fd, struct stat *buf); +#define fstat mingw_fstat +#define lstat mingw_lstat +#define _stat64(x,y) mingw_lstat(x,y) +#define ALREADY_DECLARED_STAT_FUNCS + +#include "compat/mingw.h" + +#undef ALREADY_DECLARED_STAT_FUNCS + #endif From 52eb5173ac0a84c4cd4545176871a7dd8a4da91f Mon Sep 17 00:00:00 2001 From: Ramkumar Ramachandra Date: Sat, 16 Jan 2010 23:35:38 +0530 Subject: [PATCH 100/118] Documentation: Update git core tutorial clarifying reference to scripts Back when the git core tutorial was written, porcelain commands were shell scripts. This patch adds a paragraph explaining this. Signed-off-by: Ramkumar Ramachandra Signed-off-by: Junio C Hamano --- Documentation/gitcore-tutorial.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index f762dca440..cfc26c63f5 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -27,6 +27,14 @@ interfaces on top of it called "porcelain". You may not want to use the plumbing directly very often, but it can be good to know what the plumbing does for when the porcelain isn't flushing. +Back when this document was originally written, many porcelain +commands were shell scripts. For simplicity, it still uses them as +examples to illustrate how plumbing is fit together to form the +porcelain commands. The source tree includes some of these scripts in +contrib/examples/ for reference. Although these are not implemented as +shell scripts anymore, the description of what the plumbing layer +commands do is still valid. + [NOTE] Deeper technical details are often marked as Notes, which you can skip on your first reading. From 44626dc7d562d23b54d969bb73ddeda12d5602e9 Mon Sep 17 00:00:00 2001 From: "Andrzej K. Haczewski" Date: Fri, 15 Jan 2010 21:12:20 +0100 Subject: [PATCH 101/118] MSVC: Windows-native implementation for subset of Pthreads API This patch implements native to Windows subset of pthreads API used by Git. It allows to remove Pthreads for Win32 dependency for MSVC, msysgit and Cygwin. [J6t: If the MinGW build was built as part of the msysgit build environment, then threading was already enabled because the pthreads-win32 package is available in msysgit. With this patch, we can now enable threaded code unconditionally.] Signed-off-by: Andrzej K. Haczewski Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- Makefile | 13 ++--- builtin-pack-objects.c | 31 ++++++++++-- compat/mingw.c | 2 +- compat/mingw.h | 5 ++ compat/win32/pthread.c | 110 +++++++++++++++++++++++++++++++++++++++++ compat/win32/pthread.h | 67 +++++++++++++++++++++++++ 6 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 compat/win32/pthread.c create mode 100644 compat/win32/pthread.h diff --git a/Makefile b/Makefile index 6933555660..7f5814c7a3 100644 --- a/Makefile +++ b/Makefile @@ -478,6 +478,7 @@ LIB_H += commit.h LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h +LIB_H += compat/win32/pthread.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -996,15 +997,15 @@ ifeq ($(uname_S),Windows) NO_REGEX = YesPlease NO_CURL = YesPlease NO_PYTHON = YesPlease - NO_PTHREADS = YesPlease BLK_SHA1 = YesPlease + THREADED_DELTA_SEARCH = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib lib = @@ -1048,9 +1049,11 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_REGEX = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + THREADED_DELTA_SEARCH = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o + COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \ + compat/win32/pthread.o EXTLIBS += -lws2_32 X = .exe ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) @@ -1060,10 +1063,8 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) EXTLIBS += /mingw/lib/libz.a NO_R_TO_GCC_LINKER = YesPlease INTERNAL_QSORT = YesPlease - THREADED_DELTA_SEARCH = YesPlease else NO_CURL = YesPlease - NO_PTHREADS = YesPlease endif endif diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 4429d53a1e..890f45cf20 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1256,15 +1256,15 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, #ifdef THREADED_DELTA_SEARCH -static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER; +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 = PTHREAD_MUTEX_INITIALIZER; +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 = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t progress_mutex; #define progress_lock() pthread_mutex_lock(&progress_mutex) #define progress_unlock() pthread_mutex_unlock(&progress_mutex) @@ -1591,7 +1591,26 @@ struct thread_params { unsigned *processed; }; -static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; +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) { @@ -1630,10 +1649,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, 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) @@ -1748,6 +1770,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, active_threads--; } } + cleanup_threaded_search(); free(p); } diff --git a/compat/mingw.c b/compat/mingw.c index 73762473c5..74ffc1834f 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -3,7 +3,7 @@ #include #include "../strbuf.h" -static int err_win_to_posix(DWORD winerr) +int err_win_to_posix(DWORD winerr) { int error = ENOSYS; switch(winerr) { diff --git a/compat/mingw.h b/compat/mingw.h index afe12ea554..e254fb4e06 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -310,3 +310,8 @@ struct mingw_dirent #define readdir(x) mingw_readdir(x) struct dirent *mingw_readdir(DIR *dir); #endif // !NO_MINGW_REPLACE_READDIR + +/* + * Used by Pthread API implementation for Windows + */ +extern int err_win_to_posix(DWORD winerr); diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c new file mode 100644 index 0000000000..631c0a46ea --- /dev/null +++ b/compat/win32/pthread.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2009 Andrzej K. Haczewski + * + * DISCLAMER: 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. + */ + +#include "../../git-compat-util.h" +#include "pthread.h" + +#include +#include + +static unsigned __stdcall win32_start_routine(void *arg) +{ + pthread_t *thread = arg; + thread->arg = thread->start_routine(thread->arg); + return 0; +} + +int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg) +{ + thread->arg = arg; + thread->start_routine = start_routine; + thread->handle = (HANDLE) + _beginthreadex(NULL, 0, win32_start_routine, thread, 0, NULL); + + if (!thread->handle) + return errno; + else + return 0; +} + +int win32_pthread_join(pthread_t *thread, void **value_ptr) +{ + DWORD result = WaitForSingleObject(thread->handle, INFINITE); + switch (result) { + case WAIT_OBJECT_0: + if (value_ptr) + *value_ptr = thread->arg; + return 0; + case WAIT_ABANDONED: + return EINVAL; + default: + return err_win_to_posix(GetLastError()); + } +} + +int pthread_cond_init(pthread_cond_t *cond, const void *unused) +{ + cond->waiters = 0; + + cond->sema = CreateSemaphore(NULL, 0, LONG_MAX, NULL); + if (!cond->sema) + die("CreateSemaphore() failed"); + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cond) +{ + CloseHandle(cond->sema); + cond->sema = NULL; + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex) +{ + InterlockedIncrement(&cond->waiters); + + /* + * Unlock external mutex and wait for signal. + * NOTE: we've held mutex locked long enough to increment + * waiters count above, so there's no problem with + * leaving mutex unlocked before we wait on semaphore. + */ + LeaveCriticalSection(mutex); + + /* let's wait - ignore return value */ + WaitForSingleObject(cond->sema, INFINITE); + + /* we're done waiting, so make sure we decrease waiters count */ + InterlockedDecrement(&cond->waiters); + + /* lock external mutex again */ + EnterCriticalSection(mutex); + + return 0; +} + +int pthread_cond_signal(pthread_cond_t *cond) +{ + /* + * Access to waiters count is atomic; see "Interlocked Variable Access" + * http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx + */ + int have_waiters = cond->waiters > 0; + + /* + * Signal only when there are waiters + */ + if (have_waiters) + return ReleaseSemaphore(cond->sema, 1, NULL) ? + 0 : err_win_to_posix(GetLastError()); + else + return 0; +} diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h new file mode 100644 index 0000000000..b8e1bcb046 --- /dev/null +++ b/compat/win32/pthread.h @@ -0,0 +1,67 @@ +/* + * Header used to adapt pthread-based POSIX code to Windows API threads. + * + * Copyright (C) 2009 Andrzej K. Haczewski + */ + +#ifndef PTHREAD_H +#define PTHREAD_H + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +/* + * Defines that adapt Windows API threads to pthreads API + */ +#define pthread_mutex_t CRITICAL_SECTION + +#define pthread_mutex_init(a,b) InitializeCriticalSection((a)) +#define pthread_mutex_destroy(a) DeleteCriticalSection((a)) +#define pthread_mutex_lock EnterCriticalSection +#define pthread_mutex_unlock LeaveCriticalSection + +/* + * Implement simple condition variable for Windows threads, based on ACE + * implementation. + * + * See original implementation: http://bit.ly/1vkDjo + * ACE homepage: http://www.cse.wustl.edu/~schmidt/ACE.html + * See also: http://www.cse.wustl.edu/~schmidt/win32-cv-1.html + */ +typedef struct { + volatile LONG waiters; + HANDLE sema; +} pthread_cond_t; + +extern int pthread_cond_init(pthread_cond_t *cond, const void *unused); + +extern int pthread_cond_destroy(pthread_cond_t *cond); + +extern int pthread_cond_wait(pthread_cond_t *cond, CRITICAL_SECTION *mutex); + +extern int pthread_cond_signal(pthread_cond_t *cond); + +/* + * Simple thread creation implementation using pthread API + */ +typedef struct { + HANDLE handle; + void *(*start_routine)(void*); + void *arg; +} pthread_t; + +extern int pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg); + +/* + * To avoid the need of copying a struct, we use small macro wrapper to pass + * pointer to win32_pthread_join instead. + */ +#define pthread_join(a, b) win32_pthread_join(&(a), (b)) + +extern int win32_pthread_join(pthread_t *thread, void **value_ptr); + +#endif /* PTHREAD_H */ From a6d15bc33529f923b205930d0a58fe6226570dc1 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Fri, 15 Jan 2010 21:12:21 +0100 Subject: [PATCH 102/118] Do not use date.c:tm_to_time_t() from compat/mingw.c To implement gettimeofday(), a broken-down UTC time was requested from the system using GetSystemTime(), then tm_to_time_t() was used to convert it to a time_t because it does not look at the current timezone, which mktime() would do. Use GetSystemTimeAsFileTime() and a different conversion path to avoid this back-reference from the compatibility layer to the generic code. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 74ffc1834f..ab65f77ab9 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -140,12 +140,20 @@ int mingw_open (const char *filename, int oflags, ...) return fd; } -static inline time_t filetime_to_time_t(const FILETIME *ft) +/* + * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. + * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. + */ +static inline long long filetime_to_hnsec(const FILETIME *ft) { long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; - winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ - winTime /= 10000000; /* Nano to seconds resolution */ - return (time_t)winTime; + /* Windows to Unix Epoch conversion */ + return winTime - 116444736000000000LL; +} + +static inline time_t filetime_to_time_t(const FILETIME *ft) +{ + return (time_t)(filetime_to_hnsec(ft) / 10000000); } /* We keep the do_lstat code in a separate function to avoid recursion. @@ -281,19 +289,13 @@ int mkstemp(char *template) int gettimeofday(struct timeval *tv, void *tz) { - SYSTEMTIME st; - struct tm tm; - GetSystemTime(&st); - tm.tm_year = st.wYear-1900; - tm.tm_mon = st.wMonth-1; - tm.tm_mday = st.wDay; - tm.tm_hour = st.wHour; - tm.tm_min = st.wMinute; - tm.tm_sec = st.wSecond; - tv->tv_sec = tm_to_time_t(&tm); - if (tv->tv_sec < 0) - return -1; - tv->tv_usec = st.wMilliseconds*1000; + FILETIME ft; + long long hnsec; + + GetSystemTimeAsFileTime(&ft); + hnsec = filetime_to_hnsec(&ft); + tv->tv_sec = hnsec / 10000000; + tv->tv_usec = (hnsec % 10000000) / 10; return 0; } From fc6f19fe2b49928dcb4d2dfac88ca38a47d64cde Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Jan 2010 00:57:51 -0800 Subject: [PATCH 103/118] commit.c::print_summary: do not release the format string too early When we are showing a clean merge, log_tree_commit() won't show the header and we would need the format string to format the commit summary ourselves. 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 a73a532f2f..7f61e87ebd 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -1013,7 +1013,6 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; get_commit_format(format.buf, &rev); - strbuf_release(&format); rev.always_show_header = 0; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 100; @@ -1036,6 +1035,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) printf("%s\n", buf.buf); strbuf_release(&buf); } + strbuf_release(&format); } static int git_commit_config(const char *k, const char *v, void *cb) From 5aeb3a3a838b2cb03d250f3376cf9c41f4d4608e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Jan 2010 13:54:28 -0800 Subject: [PATCH 104/118] user_ident_sufficiently_given(): refactor the logic to be usable from elsewhere Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- cache.h | 1 + ident.c | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/builtin-commit.c b/builtin-commit.c index f4974b5542..b76f327e1f 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -624,7 +624,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, author_ident); free(author_ident); - if (user_ident_explicitly_given != IDENT_ALL_GIVEN) + if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", diff --git a/cache.h b/cache.h index 16c8e8df41..f7a287cbc6 100644 --- a/cache.h +++ b/cache.h @@ -929,6 +929,7 @@ extern char git_default_name[MAX_GITNAME]; #define IDENT_MAIL_GIVEN 02 #define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) extern int user_ident_explicitly_given; +extern int user_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; diff --git a/ident.c b/ident.c index d4f614543d..96b56e66d1 100644 --- a/ident.c +++ b/ident.c @@ -259,3 +259,12 @@ const char *git_committer_info(int flag) getenv("GIT_COMMITTER_DATE"), flag); } + +int user_ident_sufficiently_given(void) +{ +#ifndef WINDOWS + return (user_ident_explicitly_given & IDENT_MAIL_GIVEN); +#else + return (user_ident_explicitly_given == IDENT_ALL_GIVEN); +#endif +} From 1a893064d7b403625896a2c8bdab39f0f7db61d5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Jan 2010 13:59:36 -0800 Subject: [PATCH 105/118] user_ident_sufficiently_given(): refactor the logic to be usable from elsewhere Signed-off-by: Junio C Hamano --- builtin-commit.c | 4 ++-- cache.h | 1 + ident.c | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index 7f61e87ebd..29dc3df786 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -602,7 +602,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, author_ident); free(author_ident); - if (!user_ident_explicitly_given) + if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", @@ -991,7 +991,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } - if (!user_ident_explicitly_given) { + if (!user_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { diff --git a/cache.h b/cache.h index bf468e5235..63e0701dee 100644 --- a/cache.h +++ b/cache.h @@ -926,6 +926,7 @@ extern const char *config_exclusive_filename; extern char git_default_email[MAX_GITNAME]; extern char git_default_name[MAX_GITNAME]; extern int user_ident_explicitly_given; +extern int user_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; diff --git a/ident.c b/ident.c index 26409b2a1b..248f769fd3 100644 --- a/ident.c +++ b/ident.c @@ -259,3 +259,8 @@ const char *git_committer_info(int flag) getenv("GIT_COMMITTER_DATE"), flag); } + +int user_ident_sufficiently_given(void) +{ + return user_ident_explicitly_given; +} From c76189875b35ca04d42df915cd902a33fdbcb9b0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:15:12 -0800 Subject: [PATCH 106/118] object.c: remove unused functions object_list_append() and object_list_length}() are not used anywhere. Signed-off-by: Junio C Hamano --- object.c | 21 --------------------- object.h | 5 ----- 2 files changed, 26 deletions(-) diff --git a/object.c b/object.c index fe8eaaf19f..3ca92c4c4d 100644 --- a/object.c +++ b/object.c @@ -217,27 +217,6 @@ struct object_list *object_list_insert(struct object *item, return new_list; } -void object_list_append(struct object *item, - struct object_list **list_p) -{ - while (*list_p) { - list_p = &((*list_p)->next); - } - *list_p = xmalloc(sizeof(struct object_list)); - (*list_p)->next = NULL; - (*list_p)->item = item; -} - -unsigned object_list_length(struct object_list *list) -{ - unsigned ret = 0; - while (list) { - list = list->next; - ret++; - } - return ret; -} - int object_list_contains(struct object_list *list, struct object *obj) { while (list) { diff --git a/object.h b/object.h index 89dd0c47a6..82877c831c 100644 --- a/object.h +++ b/object.h @@ -72,11 +72,6 @@ struct object *lookup_unknown_object(const unsigned char *sha1); struct object_list *object_list_insert(struct object *item, struct object_list **list_p); -void object_list_append(struct object *item, - struct object_list **list_p); - -unsigned object_list_length(struct object_list *list); - int object_list_contains(struct object_list *list, struct object *obj); /* Object array handling .. */ From 64161a6b23920c96485131fb51d82d8ca13b4e1d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 21:18:20 -0800 Subject: [PATCH 107/118] symlinks.c: remove unused functions invalidate_lstat_cache() and clear_lstat_cache() are not used anywhere. Signed-off-by: Junio C Hamano --- cache.h | 2 -- symlinks.c | 31 ------------------------------- 2 files changed, 33 deletions(-) diff --git a/cache.h b/cache.h index 90edb5b261..b4b2ba70f6 100644 --- a/cache.h +++ b/cache.h @@ -782,8 +782,6 @@ extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); extern int has_symlink_or_noent_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); -extern void invalidate_lstat_cache(const char *name, int len); -extern void clear_lstat_cache(void); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); diff --git a/symlinks.c b/symlinks.c index 7b0a86d357..8860120011 100644 --- a/symlinks.c +++ b/symlinks.c @@ -179,37 +179,6 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, return ret_flags; } -/* - * Invalidate the given 'name' from the cache, if 'name' matches - * completely with the cache. - */ -void invalidate_lstat_cache(const char *name, int len) -{ - int match_len, previous_slash; - struct cache_def *cache = &default_cache; /* FIXME */ - - match_len = longest_path_match(name, len, cache->path, cache->len, - &previous_slash); - if (len == match_len) { - if ((cache->track_flags & FL_DIR) && previous_slash > 0) { - cache->path[previous_slash] = '\0'; - cache->len = previous_slash; - cache->flags = FL_DIR; - } else { - reset_lstat_cache(cache); - } - } -} - -/* - * Completely clear the contents of the cache - */ -void clear_lstat_cache(void) -{ - struct cache_def *cache = &default_cache; /* FIXME */ - reset_lstat_cache(cache); -} - #define USE_ONLY_LSTAT 0 /* From 34f3999206e8ea41b9e4cf48e30ab1149e01d8a5 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 18 Jan 2010 22:55:07 +0100 Subject: [PATCH 108/118] grep: NUL terminate input from a file Internally "git grep" runs regexec(3) that expects its input string to be NUL terminated. When searching inside blob data, read_sha1_file() automatically gives such a buffer, but builtin-grep.c forgot to put the NUL at the end, even though it allocated enough space for it. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- builtin-grep.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin-grep.c b/builtin-grep.c index d79a6260a4..63dc31c45c 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -207,6 +207,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + data[sz] = 0; if (opt->relative && opt->prefix_length) filename = quote_path_relative(filename, -1, &buf, opt->prefix); i = grep_buffer(opt, filename, data, sz); From 837d395a5c0b98ab938d71db8e2b6b9f69ddcc4d Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Mon, 18 Jan 2010 13:06:28 -0500 Subject: [PATCH 109/118] Replace parse_blob() with an explanatory comment parse_blob() has never actually been used; it has served simply to avoid having a confusing gap in the API. Instead of leaving it, put in a comment that explains what "parsing a blob" entails (making sure the object is actually readable), and why code might care whether a blob has been parsed or not. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- blob.c | 21 --------------------- blob.h | 9 ++++++++- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/blob.c b/blob.c index bd7d078e1a..ae320bd8fa 100644 --- a/blob.c +++ b/blob.c @@ -23,24 +23,3 @@ int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size) item->object.parsed = 1; return 0; } - -int parse_blob(struct blob *item) -{ - enum object_type type; - void *buffer; - unsigned long size; - int ret; - - if (item->object.parsed) - return 0; - buffer = read_sha1_file(item->object.sha1, &type, &size); - if (!buffer) - return error("Could not read %s", - sha1_to_hex(item->object.sha1)); - if (type != OBJ_BLOB) - return error("Object %s not a blob", - sha1_to_hex(item->object.sha1)); - ret = parse_blob_buffer(item, buffer, size); - free(buffer); - return ret; -} diff --git a/blob.h b/blob.h index ea5d9e9f8b..59b394eea3 100644 --- a/blob.h +++ b/blob.h @@ -13,6 +13,13 @@ struct blob *lookup_blob(const unsigned char *sha1); int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size); -int parse_blob(struct blob *item); +/** + * Blobs do not contain references to other objects and do not have + * structured data that needs parsing. However, code may use the + * "parsed" bit in the struct object for a blob to determine whether + * its content has been found to actually be available, so + * parse_blob_buffer() is used (by object.c) to flag that the object + * has been read successfully from the database. + **/ #endif /* BLOB_H */ From 6f9752d2e29ecbd5d9489e57bfcd28fb030cba97 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jan 2010 18:16:15 -0800 Subject: [PATCH 110/118] Update draft release notes to 1.6.6.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.6.1.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt index 4c88bebb90..406fbc4e0f 100644 --- a/Documentation/RelNotes-1.6.6.1.txt +++ b/Documentation/RelNotes-1.6.6.1.txt @@ -4,12 +4,27 @@ Git v1.6.6.1 Release Notes Fixes since v1.6.6 ------------------ + * "git branch -a name" wasn't diagnosed as an error. + + * "git fast-import" choked when fed a tag that do not point at a + commit. + + * "git grep" finding from work tree files could have fed garbage to + the underlying regexec(3). + + * "git grep -L" didn't show empty files (they should never match, and + they should always appear in -L output as unmatching). + * http-backend was not listed in the command list in the documentation. + * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile + + * "git checkout -m some-branch" while on an unborn branch crashed. + Other minor documentation updates are included. -- exec >/var/tmp/1 -O=v1.6.6-4-gd828fdb +O=v1.6.6-39-g6304c40 echo O=$(git describe maint) git shortlog --no-merges $O..maint From d07430f98cfd6fae3f5ee91c2fa314f1cacf6f33 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jan 2010 18:16:50 -0800 Subject: [PATCH 111/118] Update draft release notes to 1.7.0 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.7.0.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt index 321ae973b7..7a49b475da 100644 --- a/Documentation/RelNotes-1.7.0.txt +++ b/Documentation/RelNotes-1.7.0.txt @@ -50,8 +50,14 @@ Updates since v1.6.6 (portability) + * Some more MSVC portability patches for msysgit port. + + * Minimum Pthreads emulation for msysgit port. + (performance) + * More performance improvement patches for msysgit port. + (usability, bells and whistles) * More commands learned "--quiet" and "--[no-]progress" options. @@ -66,6 +72,11 @@ Updates since v1.6.6 * HTTP transfer can use authentication scheme other than basic (i.e./e.g. digest). + * Switching from a version of superproject that used to have a submodule + to another version of superproject that no longer has it did not remove + the submodule directory when it should (namely, when you are not + interested in the submodule at all and didn't clone/checkout). + * "git checkout A...B" is a way to detach HEAD at the merge base between A and B. @@ -87,6 +98,10 @@ Updates since v1.6.6 * Use of "git reset --merge" has become easier when resetting away a conflicted mess left in the work tree. + * "git rerere" had rerere.autoupdate configuration but there was no way + to countermand it from the command line; --no-rerere-autoupdate option + given to "merge", "revert", etc. fixes this. + * "git status" learned "-s(hort)" output format. (developers) @@ -102,6 +117,6 @@ release, unless otherwise noted. -- exec >/var/tmp/1 -O=v1.6.6-242-gf287c65 +O=v1.6.6-263-ge33fd3c echo O=$(git describe master) git shortlog --no-merges $O..master ^maint From 8de65185e873d361ede4d6994ef257e4ac55f37d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jan 2010 21:14:41 -0800 Subject: [PATCH 112/118] Fix mis-backport of t7002 The original patch that became cfe370c (grep: do not segfault when -f is used, 2009-10-16), was made against "maint" or newer branch back then, but the fix addressed the issue that was present as far as in 1.6.4 series. The maintainer backported the patch to the 1.6.4 maintenance branch, but failed to notice that the new tests assumed the setup done by the script in "maint", which did quite a lot more than the same test script in 1.6.4 series, and the output didn't match the expected result. This should fix it. Signed-off-by: Junio C Hamano --- t/t7002-grep.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 5f91d82297..7a5f28a326 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -25,13 +25,17 @@ test_expect_success setup ' echo foo mmap bar_mmap echo foo_mmap bar mmap baz } >file && + echo vvv >v && echo ww w >w && echo x x xx x >x && echo y yy >y && echo zzz > z && mkdir t && echo test >t/t && - git add file w x y z t/t hello.c && + echo vvv >t/v && + mkdir t/a && + echo vvv >t/a/v && + git add . && test_tick && git commit -m initial ' From 6329bade6632a9df4b37dd3a7f8d641eed74bbf5 Mon Sep 17 00:00:00 2001 From: David Ripton Date: Tue, 19 Jan 2010 07:13:33 -0800 Subject: [PATCH 113/118] bisect: fix singular/plural grammar nit Remove the trailing 's' from "revisions" and "steps" when there is only one. Signed-off-by: David Ripton Signed-off-by: Junio C Hamano --- bisect.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bisect.c b/bisect.c index dc18db8af9..b779a9589a 100644 --- a/bisect.c +++ b/bisect.c @@ -956,7 +956,7 @@ int bisect_next_all(const char *prefix) { struct rev_info revs; struct commit_list *tried; - int reaches = 0, all = 0, nr; + int reaches = 0, all = 0, nr, steps; const unsigned char *bisect_rev; char bisect_rev_hex[41]; @@ -998,8 +998,10 @@ int bisect_next_all(const char *prefix) } nr = all - reaches - 1; - printf("Bisecting: %d revisions left to test after this " - "(roughly %d steps)\n", nr, estimate_bisect_steps(all)); + steps = estimate_bisect_steps(all); + printf("Bisecting: %d revision%s left to test after this " + "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"), + steps, (steps == 1 ? "" : "s")); return bisect_checkout(bisect_rev_hex); } From 8b770a2a24456089c0dd0230035a7d88aee7e26a Mon Sep 17 00:00:00 2001 From: Tarmigan Casebolt Date: Sun, 17 Jan 2010 00:19:24 -0800 Subject: [PATCH 114/118] ident.c: replace fprintf with fputs to suppress compiler warning Compiling today's pu gave ... CC ident.o CC levenshtein.o ident.c: In function 'fmt_ident': ident.c:206: warning: format not a string literal and no format arguments CC list-objects.o ... This warning seems to have appeared first in 18e95f279ec6 (ident.c: remove unused variables) which removed additional fprintf arguments. Suppress this warning by using fputs instead of fprintf. Signed-off-by: Tarmigan Casebolt Signed-off-by: Junio C Hamano --- ident.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ident.c b/ident.c index 96b56e66d1..9e2438826d 100644 --- a/ident.c +++ b/ident.c @@ -203,7 +203,7 @@ const char *fmt_ident(const char *name, const char *email, if ((warn_on_no_name || error_on_no_name) && name == git_default_name && env_hint) { - fprintf(stderr, env_hint); + fputs(env_hint, stderr); env_hint = NULL; /* warn only once */ } if (error_on_no_name) From 4256f36c58238a1b2317372b37be889d75a93198 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Tue, 19 Jan 2010 16:39:12 +0100 Subject: [PATCH 115/118] Makefile: honor NO_CURL when setting REMOTE_CURL_* variables Previously, these variables were set before there was a chance to set NO_CURL. This made a difference only during 'make install', because by installing $(REMOTE_CURL_ALIASES), the rule tries to access $(REMOTE_CURL_PRIMARY), which was never installed. On Windows, this fails; on Unix, stale symbolic links are created. Signed-off-by: Johannes Sixt Acked-by: Ilari Liusvaara Signed-off-by: Junio C Hamano --- Makefile | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 45a23a4dc8..b7f56b7eaa 100644 --- a/Makefile +++ b/Makefile @@ -424,16 +424,6 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-whatchanged$X -ifdef NO_CURL -REMOTE_CURL_PRIMARY = -REMOTE_CURL_ALIASES = -REMOTE_CURL_NAMES = -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) -endif - # what 'all' will build and 'install' will install in gitexecdir, # excluding programs for built-in commands ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) @@ -1112,6 +1102,9 @@ endif ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL + REMOTE_CURL_PRIMARY = + REMOTE_CURL_ALIASES = + REMOTE_CURL_NAMES = else ifdef CURLDIR # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case. @@ -1120,6 +1113,9 @@ else else CURL_LIBCURL = -lcurl endif + 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 curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" From 35eabd1579726d594e84fc8328a5c87693dd065a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jan 2010 12:44:59 -0800 Subject: [PATCH 116/118] Git 1.6.5.8 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.5.8.txt | 28 ++++++++++++++++++++++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes-1.6.5.8.txt diff --git a/Documentation/RelNotes-1.6.5.8.txt b/Documentation/RelNotes-1.6.5.8.txt new file mode 100644 index 0000000000..8b24bebb96 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.8.txt @@ -0,0 +1,28 @@ +Git v1.6.5.8 Release Notes +========================== + +Fixes since v1.6.5.7 +-------------------- + +* "git count-objects" did not handle packfiles that are bigger than 4G on + platforms with 32-bit off_t. + +* "git rebase -i" did not abort cleanly if it failed to launch the editor. + +* "git blame" did not work well when commit lacked the author name. + +* "git fast-import" choked when handling a tag that points at an object + that is not a commit. + +* "git reset --hard" did not work correctly when GIT_WORK_TREE environment + variable is used to point at the root of the true work tree. + +* "git grep" fed a buffer that is not NUL-terminated to underlying + regexec(). + +* "git checkout -m other" while on a branch that does not have any commit + segfaulted, instead of failing. + +* "git branch -a other" should have diagnosed the command as an error. + +Other minor documentation updates are also included. diff --git a/Documentation/git.txt b/Documentation/git.txt index ff31095093..46558c8a49 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.5.7/git.html[documentation for release 1.6.5.7] +* link:v1.6.5.8/git.html[documentation for release 1.6.5.8] * release notes for + link:RelNotes-1.6.5.8.txt[1.6.5.8], link:RelNotes-1.6.5.7.txt[1.6.5.7], link:RelNotes-1.6.5.6.txt[1.6.5.6], link:RelNotes-1.6.5.5.txt[1.6.5.5], diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index e918ffe326..52b058a586 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.5.7 +DEF_VER=v1.6.5.8 LF=' ' diff --git a/RelNotes b/RelNotes index b1e74fb27b..f60e868f47 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.5.7.txt \ No newline at end of file +Documentation/RelNotes-1.6.5.8.txt \ No newline at end of file From 9504f3d3d2e62cb6fba9e7c61c6fe69dec9053a4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jan 2010 13:29:21 -0800 Subject: [PATCH 117/118] Git 1.6.6.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.6.1.txt | 19 +++++++++++++------ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt index 406fbc4e0f..f1d0a4ae2d 100644 --- a/Documentation/RelNotes-1.6.6.1.txt +++ b/Documentation/RelNotes-1.6.6.1.txt @@ -4,8 +4,16 @@ Git v1.6.6.1 Release Notes Fixes since v1.6.6 ------------------ + * "git blame" did not work well when commit lacked the author name. + * "git branch -a name" wasn't diagnosed as an error. + * "git count-objects" did not handle packfiles that are bigger than 4G on + platforms with 32-bit off_t. + + * "git checkout -m other" while on a branch that does not have any commit + segfaulted, instead of failing. + * "git fast-import" choked when fed a tag that do not point at a commit. @@ -15,6 +23,11 @@ Fixes since v1.6.6 * "git grep -L" didn't show empty files (they should never match, and they should always appear in -L output as unmatching). + * "git rebase -i" did not abort cleanly if it failed to launch the editor. + + * "git reset --hard" did not work correctly when GIT_WORK_TREE environment + variable is used to point at the root of the true work tree. + * http-backend was not listed in the command list in the documentation. * Building on FreeBSD (both 7 and 8) needs OLD_ICONV set in the Makefile @@ -22,9 +35,3 @@ Fixes since v1.6.6 * "git checkout -m some-branch" while on an unborn branch crashed. Other minor documentation updates are included. - --- -exec >/var/tmp/1 -O=v1.6.6-39-g6304c40 -echo O=$(git describe maint) -git shortlog --no-merges $O..maint diff --git a/Documentation/git.txt b/Documentation/git.txt index c05efdaa64..b6df39ba36 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.6/git.html[documentation for release 1.6.6] +* link:v1.6.6.1/git.html[documentation for release 1.6.6.1] * release notes for + link:RelNotes-1.6.6.1.txt[1.6.6.1], link:RelNotes-1.6.6.txt[1.6.6]. * link:v1.6.5.8/git.html[documentation for release 1.6.5.8] diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1628d986fe..4e0adcade9 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.6 +DEF_VER=v1.6.6.1 LF=' ' From 23418ea95f83177df19bfe18af33650b87ec2a8a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 11 Jan 2010 23:52:47 -0800 Subject: [PATCH 118/118] date.c: mark file-local function static Signed-off-by: Junio C Hamano --- date.c | 2 +- git-compat-util.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/date.c b/date.c index 5d05ef61cf..45f3684cee 100644 --- a/date.c +++ b/date.c @@ -9,7 +9,7 @@ /* * This is like mktime, but without normalization of tm_wday and tm_yday. */ -time_t tm_to_time_t(const struct tm *tm) +static time_t tm_to_time_t(const struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 diff --git a/git-compat-util.h b/git-compat-util.h index 5c596875c2..85dea123a4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -198,7 +198,6 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern int prefixcmp(const char *str, const char *prefix); -extern time_t tm_to_time_t(const struct tm *tm); static inline const char *skip_prefix(const char *str, const char *prefix) {