From c82d7117a1f499b43e21e0a4589a080edadaf706 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 28 Dec 2006 02:35:34 -0500 Subject: [PATCH 001/195] Improve merge performance by avoiding in-index merges. In the early days of Git we performed a 3-way read-tree based merge before attempting any specific merge strategy, as our core merge strategies of merge-one-file and merge-recursive were slower script based programs which took far longer to execute. This was a good performance optimization in the past, as most merges were able to be handled strictly by `read-tree -m -u`. However now that merge-recursive is a C based program which performs a full 3-way read-tree before it starts running we need to pay the cost of the 3-way read-tree twice if we have to do any sort of file level merging. This slows down some classes of simple merges which `read-tree -m -u` could not handle but which merge-recursive does automatically. For a really trivial merge which can be handled entirely by `read-tree -m -u`, skipping the read-tree and just going directly into merge-recursive saves on average 50 ms on my PowerPC G4 system. May sound odd, but it does appear to be true. In a really simple merge which needs to use merge-recursive to handle a file that was modified on both branches, skipping the read-tree in git-merge saves on average almost 100 ms (on the same PowerPC G4) as we avoid doing some work twice. We only avoid `read-tree -m -u` if the only strategy to use is merge-recursive, as not all merge strategies perform as well as merge-recursive does. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-merge.sh | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/git-merge.sh b/git-merge.sh index 477002910e..1c4f6693f5 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -298,24 +298,30 @@ f,*) ;; ?,1,*,) # We are not doing octopus, not fast forward, and have only - # one common. See if it is really trivial. - git var GIT_COMMITTER_IDENT >/dev/null || exit - - echo "Trying really trivial in-index merge..." + # one common. git-update-index --refresh 2>/dev/null - if git-read-tree --trivial -m -u -v $common $head "$1" && - result_tree=$(git-write-tree) - then - echo "Wonderful." - result_commit=$( - echo "$merge_msg" | - git-commit-tree $result_tree -p HEAD -p "$1" - ) || exit - finish "$result_commit" "In-index merge" - dropsave - exit 0 - fi - echo "Nope." + case " $use_strategies " in + *' recursive '*|*' recur '*) + : run merge later + ;; + *) + # See if it is really trivial. + git var GIT_COMMITTER_IDENT >/dev/null || exit + echo "Trying really trivial in-index merge..." + if git-read-tree --trivial -m -u -v $common $head "$1" && + result_tree=$(git-write-tree) + then + echo "Wonderful." + result_commit=$( + echo "$merge_msg" | + git-commit-tree $result_tree -p HEAD -p "$1" + ) || exit + finish "$result_commit" "In-index merge" + dropsave + exit 0 + fi + echo "Nope." + esac ;; *) # An octopus. If we can reach all the remote we are up to date. From 9fde9401a9c3974a407f302d60a1b75e8787f715 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 12 Jan 2007 12:44:08 -0800 Subject: [PATCH 002/195] Define cd_to_toplevel shell function in git-sh-setup Signed-off-by: Junio C Hamano --- git-sh-setup.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 57f7f77776..6b1c1423eb 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -36,6 +36,17 @@ is_bare_repository () { esac } +cd_to_toplevel () { + cdup=$(git-rev-parse --show-cdup) + if test ! -z "$cdup" + then + cd "$cdup" || { + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" + exit 1 + } + fi +} + require_work_tree () { test $(is_bare_repository) = false || die "fatal: $0 cannot be used without a working tree." From 514c09fdcfef6385f1a61ee52344794356c99986 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 12 Jan 2007 12:49:05 -0800 Subject: [PATCH 003/195] Use cd_to_toplevel in scripts that implement it by hand. This converts scripts that do "cd $(rev-parse --show-cdup)" by hand to use cd_to_toplevel. I think git-fetch does not have to go to the toplevel, but that should be dealt with in a separate patch. Signed-off-by: Junio C Hamano --- git-checkout.sh | 6 +----- git-commit.sh | 22 ++++++++-------------- git-fetch.sh | 6 +----- git-reset.sh | 6 +----- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index a2b8e4fa4a..66e40b90eb 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -135,11 +135,7 @@ fi # We are switching branches and checking out trees, so # we *NEED* to be at the toplevel. -cdup=$(git-rev-parse --show-cdup) -if test ! -z "$cdup" -then - cd "$cdup" -fi +cd_to_toplevel [ -z "$new" ] && new=$old && new_name="$old_name" diff --git a/git-commit.sh b/git-commit.sh index eddd863015..9fdf234b52 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -316,22 +316,16 @@ esac ################################################################ # Prepare index to have a tree to be committed -TOP=`git-rev-parse --show-cdup` -if test -z "$TOP" -then - TOP=./ -fi - case "$all,$also" in t,) save_index && ( - cd "$TOP" - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE + cd_to_toplevel && + GIT_INDEX_FILE="$NEXT_INDEX" && + export GIT_INDEX_FILE && git-diff-files --name-only -z | git-update-index --remove -z --stdin - ) + ) || exit ;; ,t) save_index && @@ -339,11 +333,11 @@ t,) git-diff-files --name-only -z -- "$@" | ( - cd "$TOP" - GIT_INDEX_FILE="$NEXT_INDEX" - export GIT_INDEX_FILE + cd_to_toplevel && + GIT_INDEX_FILE="$NEXT_INDEX" && + export GIT_INDEX_FILE && git-update-index --remove -z --stdin - ) + ) || exit ;; ,) case "$#" in diff --git a/git-fetch.sh b/git-fetch.sh index c58704d794..87b940b85b 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -5,12 +5,8 @@ USAGE=' ...' SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action "fetch $*" +cd_to_toplevel ;# probably unnecessary... -TOP=$(git-rev-parse --show-cdup) -if test ! -z "$TOP" -then - cd "$TOP" -fi . git-parse-remote _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" diff --git a/git-reset.sh b/git-reset.sh index b9045bc762..91c7e6e664 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -53,11 +53,7 @@ then exit fi -TOP=$(git-rev-parse --show-cdup) -if test ! -z "$TOP" -then - cd "$TOP" -fi +cd_to_toplevel if test "$reset_type" = "--hard" then From 533b70390e540de4e0faed4823ee561c8368e5ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 12 Jan 2007 12:52:03 -0800 Subject: [PATCH 004/195] Allow whole-tree operations to be started from a subdirectory This updates five commands (merge, pull, rebase, revert and cherry-pick) so that they can be started from a subdirectory. This may not actually be what we want to do. These commands are inherently whole-tree operations, and an inexperienced user may mistakenly expect a "git pull" from a subdirectory would merge only the subdirectory the command started from. Signed-off-by: Junio C Hamano --- git-merge.sh | 4 +++- git-pull.sh | 4 +++- git-rebase.sh | 3 +++ git-revert.sh | 3 +++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/git-merge.sh b/git-merge.sh index 3eef048efc..7de83dc76c 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -5,12 +5,14 @@ USAGE='[-n] [--no-commit] [--squash] [-s ] [-m=] +' +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action "merge $*" require_work_tree +cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in a middle of conflicted merge." + die "You are in the middle of a conflicted merge." LF=' ' diff --git a/git-pull.sh b/git-pull.sh index e9826fc4ce..959261757c 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -6,12 +6,14 @@ USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [] ...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action "pull $*" require_work_tree +cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in a middle of conflicted merge." + die "You are in the middle of a conflicted merge." strategy_args= no_summary= no_commit= squash= while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac diff --git a/git-rebase.sh b/git-rebase.sh index 98f9558145..c8bd0f99d1 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -27,9 +27,12 @@ Example: git-rebase master~1 topic / --> / D---E---F---G master D---E---F---G master ' + +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action rebase require_work_tree +cd_to_toplevel RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". diff --git a/git-revert.sh b/git-revert.sh index fcca3ebb90..224e6540ca 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -19,8 +19,11 @@ case "$0" in echo >&2 "What are you talking about?" exit 1 ;; esac + +SUBDIRECTORY_OK=Yes ;# we will cd up . git-sh-setup require_work_tree +cd_to_toplevel no_commit= while case "$#" in 0) break ;; esac From 6c2833284d84d1128f44dbfd846c81ef93f07a3c Mon Sep 17 00:00:00 2001 From: Doug Maxey Date: Sun, 10 Dec 2006 14:31:46 -0600 Subject: [PATCH 005/195] [PATCH] gitk: add current directory to main window title This can help people keep track of which gitk is which, when they have several on the screen. Signed-off-by: Doug Maxey Signed-off-by: Paul Mackerras --- gitk | 1 + 1 file changed, 1 insertion(+) diff --git a/gitk b/gitk index 3dabc69516..33bac1f3f6 100755 --- a/gitk +++ b/gitk @@ -6293,6 +6293,7 @@ set stuffsaved 0 set patchnum 0 setcoords makewindow +wm title . "[file tail $argv0]: [file tail [pwd]]" readrefs if {$cmdline_files ne {} || $revtreeargs ne {}} { From 5024baa43790bf0ea12905b47c8922e3b256ae61 Mon Sep 17 00:00:00 2001 From: Peter Baumann Date: Tue, 9 Jan 2007 15:30:19 +0100 Subject: [PATCH 006/195] [PATCH] Make gitk work when launched in a subdirectory Make gitk use git-rev-parse --git-dir to find the repository. Signed-off-by: Peter Baumann Signed-off-by: Paul Mackerras --- gitk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitk b/gitk index 33bac1f3f6..031c829f26 100755 --- a/gitk +++ b/gitk @@ -12,7 +12,7 @@ proc gitdir {} { if {[info exists env(GIT_DIR)]} { return $env(GIT_DIR) } else { - return ".git" + return [exec git rev-parse --git-dir] } } From f7e68b2967182f14547125d1369f37ad4d83187e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 12 Jan 2007 17:32:38 -0800 Subject: [PATCH 007/195] Use log output encoding in --pretty=email headers. Private functions add_rfc2047() and pretty_print_commit() assumed they are only emitting UTF-8. Signed-off-by: Junio C Hamano --- commit.c | 84 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/commit.c b/commit.c index 496d37aa02..9b2b842e7d 100644 --- a/commit.c +++ b/commit.c @@ -464,20 +464,29 @@ static int get_one_line(const char *msg, unsigned long len) return ret; } -static int is_rfc2047_special(char ch) +/* High bit set, or ISO-2022-INT */ +static int non_ascii(int ch) { - return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_')); + ch = (ch & 0xff); + return ((ch & 0x80) || (ch == 0x1b)); } -static int add_rfc2047(char *buf, const char *line, int len) +static int is_rfc2047_special(char ch) +{ + return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_')); +} + +static int add_rfc2047(char *buf, const char *line, int len, + const char *encoding) { char *bp = buf; int i, needquote; - static const char q_utf8[] = "=?utf-8?q?"; + char q_encoding[128]; + const char *q_encoding_fmt = "=?%s?q?"; for (i = needquote = 0; !needquote && i < len; i++) { - unsigned ch = line[i]; - if (ch & 0x80) + int ch = line[i]; + if (non_ascii(ch)) needquote++; if ((i + 1 < len) && (ch == '=' && line[i+1] == '?')) @@ -486,8 +495,11 @@ static int add_rfc2047(char *buf, const char *line, int len) if (!needquote) return sprintf(buf, "%.*s", len, line); - memcpy(bp, q_utf8, sizeof(q_utf8)-1); - bp += sizeof(q_utf8)-1; + i = snprintf(q_encoding, sizeof(q_encoding), q_encoding_fmt, encoding); + if (sizeof(q_encoding) < i) + die("Insanely long encoding name %s", encoding); + memcpy(bp, q_encoding, i); + bp += i; for (i = 0; i < len; i++) { unsigned ch = line[i] & 0xFF; if (is_rfc2047_special(ch)) { @@ -505,7 +517,8 @@ static int add_rfc2047(char *buf, const char *line, int len) } static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, - const char *line, int relative_date) + const char *line, int relative_date, + const char *encoding) { char *date; int namelen; @@ -533,7 +546,8 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, filler = ""; strcpy(buf, "From: "); ret = strlen(buf); - ret += add_rfc2047(buf + ret, line, display_name_length); + ret += add_rfc2047(buf + ret, line, display_name_length, + encoding); memcpy(buf + ret, name_tail, namelen - display_name_length); ret += namelen - display_name_length; buf[ret++] = '\n'; @@ -668,21 +682,18 @@ static char *replace_encoding_header(char *buf, char *encoding) return buf; } -static char *logmsg_reencode(const struct commit *commit) +static char *logmsg_reencode(const struct commit *commit, + char *output_encoding) { char *encoding; char *out; - char *output_encoding = (git_log_output_encoding - ? git_log_output_encoding - : git_commit_encoding); + char *utf8 = "utf-8"; - if (!output_encoding) - output_encoding = "utf-8"; - else if (!*output_encoding) + if (!*output_encoding) return NULL; encoding = get_header(commit, "encoding"); if (!encoding) - return NULL; + encoding = utf8; if (!strcmp(encoding, output_encoding)) out = strdup(commit->buffer); else @@ -691,7 +702,8 @@ static char *logmsg_reencode(const struct commit *commit) if (out) out = replace_encoding_header(out, output_encoding); - free(encoding); + if (encoding != utf8) + free(encoding); if (!out) return NULL; return out; @@ -711,8 +723,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, int parents_shown = 0; const char *msg = commit->buffer; int plain_non_ascii = 0; - char *reencoded = logmsg_reencode(commit); + char *reencoded; + char *encoding; + encoding = (git_log_output_encoding + ? git_log_output_encoding + : git_commit_encoding); + if (!encoding) + encoding = "utf-8"; + reencoded = logmsg_reencode(commit, encoding); if (reencoded) msg = reencoded; @@ -738,7 +757,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, i + 1 < len && msg[i+1] == '\n') in_body = 1; } - else if (ch & 0x80) { + else if (non_ascii(ch)) { plain_non_ascii = 1; break; } @@ -797,13 +816,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, offset += add_user_info("Author", fmt, buf + offset, line + 7, - relative_date); + relative_date, + encoding); if (!memcmp(line, "committer ", 10) && (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) offset += add_user_info("Commit", fmt, buf + offset, line + 10, - relative_date); + relative_date, + encoding); continue; } @@ -826,7 +847,8 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, int slen = strlen(subject); memcpy(buf + offset, subject, slen); offset += slen; - offset += add_rfc2047(buf + offset, line, linelen); + offset += add_rfc2047(buf + offset, line, linelen, + encoding); } else { memset(buf + offset, ' ', indent); @@ -837,11 +859,17 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, if (fmt == CMIT_FMT_ONELINE) break; if (subject && plain_non_ascii) { - static const char header[] = - "Content-Type: text/plain; charset=UTF-8\n" + int sz; + char header[512]; + const char *header_fmt = + "Content-Type: text/plain; charset=%s\n" "Content-Transfer-Encoding: 8bit\n"; - memcpy(buf + offset, header, sizeof(header)-1); - offset += sizeof(header)-1; + sz = snprintf(header, sizeof(header), header_fmt, + encoding); + if (sizeof(header) < sz) + die("Encoding name %s too long", encoding); + memcpy(buf + offset, header, sz); + offset += sz; } if (after_subject) { int slen = strlen(after_subject); From a731ec5eb827767e0f054641ab1eacc632113c59 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Jan 2007 01:20:53 -0800 Subject: [PATCH 008/195] t3901: test "format-patch | am" pipe with i18n This checks combinations of i18n.commitencoding (declares what encoding you are feeding commit-tree to make commits) and i18n.logoutputencoding (instructs what encoding to emit the commit message out to log output, including e-mail format) to make sure the "format-patch | am" pipe used in git-rebase works correctly. I suspect "git cherry-pick" and "git rebase --merge" may fail similar tests. We'll see. Signed-off-by: Junio C Hamano --- t/t3901-8859-1.txt | 4 ++ t/t3901-i18n-patch.sh | 154 ++++++++++++++++++++++++++++++++++++++++++ t/t3901-utf8.txt | 4 ++ 3 files changed, 162 insertions(+) create mode 100755 t/t3901-8859-1.txt create mode 100755 t/t3901-i18n-patch.sh create mode 100755 t/t3901-utf8.txt diff --git a/t/t3901-8859-1.txt b/t/t3901-8859-1.txt new file mode 100755 index 0000000000..38c21a6a7f --- /dev/null +++ b/t/t3901-8859-1.txt @@ -0,0 +1,4 @@ +: to be sourced in t3901 -- this is latin-1 +GIT_AUTHOR_NAME=" " && +GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME && +export GIT_AUTHOR_NAME GIT_COMMITTER_NAME diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh new file mode 100755 index 0000000000..7fecfe98d2 --- /dev/null +++ b/t/t3901-i18n-patch.sh @@ -0,0 +1,154 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='i18n settings and format-patch | am pipe' + +. ./test-lib.sh + +test_expect_success setup ' + git-repo-config i18n.commitencoding UTF-8 && + + # use UTF-8 in author and committer name to match the + # i18n.commitencoding settings + . ../t3901-utf8.txt && + + test_tick && + echo "$GIT_AUTHOR_NAME" >mine && + git add mine && + git commit -s -m "Initial commit" && + + test_tick && + echo Hello world >mine && + git add mine && + git commit -s -m "Second on main" && + + # the first commit on the side branch is UTF-8 + test_tick && + git checkout -b side master^ && + echo Another file >yours && + git add yours && + git commit -s -m "Second on side" && + + # the second one on the side branch is ISO-8859-1 + git-repo-config i18n.commitencoding ISO-8859-1 && + # use author and committer name in ISO-8859-1 to match it. + . ../t3901-8859-1.txt && + test_tick && + echo Yet another >theirs && + git add theirs && + git commit -s -m "Third on side" && + + # Back to default + git-repo-config i18n.commitencoding UTF-8 +' + +test_expect_success 'format-patch output (ISO-8859-1)' ' + git-repo-config i18n.logoutputencoding ISO-8859-1 && + + git format-patch --stdout master..HEAD^ >out-l1 && + git format-patch --stdout HEAD^ >out-l2 && + grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l1 && + grep "^From: =?ISO-8859-1?q?=C1=E9=ED_=F3=FA?=" out-l1 && + grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l2 && + grep "^From: =?ISO-8859-1?q?=C1=E9=ED_=F3=FA?=" out-l2 +' + +test_expect_success 'format-patch output (UTF-8)' ' + git repo-config i18n.logoutputencoding UTF-8 && + + git format-patch --stdout master..HEAD^ >out-u1 && + git format-patch --stdout HEAD^ >out-u2 && + grep "^Content-Type: text/plain; charset=UTF-8" out-u1 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-u1 && + grep "^Content-Type: text/plain; charset=UTF-8" out-u2 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-u2 +' + +test_expect_success 'rebase (UTF-8)' ' + # We want the result of rebase in UTF-8 + git-repo-config i18n.commitencoding UTF-8 && + + # The test is about logoutputencoding not affecting the + # final outcome -- it is used internally to generate the + # patch and the log. + + git repo-config i18n.logoutputencoding UTF-8 && + + # The result will be committed by GIT_COMMITTER_NAME -- + # we want UTF-8 encoded name. + . ../t3901-utf8.txt && + git checkout -b test && + git-rebase master && + + # Check the results. + git format-patch --stdout HEAD~2..HEAD^ >out-r1 && + git format-patch --stdout HEAD^ >out-r2 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 + + ! git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && + ! git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" +' + +test_expect_success 'rebase (ISO-8859-1)' ' + git-repo-config i18n.commitencoding UTF-8 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-utf8.txt && + + git reset --hard side && + git-rebase master && + + git repo-config i18n.logoutputencoding UTF-8 && + git format-patch --stdout HEAD~2..HEAD^ >out-r1 && + git format-patch --stdout HEAD^ >out-r2 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && + + ! git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && + ! git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" +' + +test_expect_success 'rebase (ISO-8859-1)' ' + # In this test we want ISO-8859-1 encoded commits as the result + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-8859-1.txt && + + git reset --hard side && + git-rebase master && + + # Make sure characters are not corrupted. + git repo-config i18n.logoutputencoding UTF-8 && + git format-patch --stdout HEAD~2..HEAD^ >out-r1 && + git format-patch --stdout HEAD^ >out-r2 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && + + git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && + git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" +' + +test_expect_success 'rebase (UTF-8)' ' + # This is pathological -- use UTF-8 as intermediate form + # to get ISO-8859-1 results. + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding UTF-8 && + . ../t3901-8859-1.txt && + + git reset --hard side && + git-rebase master && + + # Make sure characters are not corrupted. + git repo-config i18n.logoutputencoding UTF-8 && + git format-patch --stdout HEAD~2..HEAD^ >out-r1 && + git format-patch --stdout HEAD^ >out-r2 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && + + git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && + git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" +' + +test_done diff --git a/t/t3901-utf8.txt b/t/t3901-utf8.txt new file mode 100755 index 0000000000..5f5205cd02 --- /dev/null +++ b/t/t3901-utf8.txt @@ -0,0 +1,4 @@ +: to be sourced in t3901 -- this is utf8 +GIT_AUTHOR_NAME="Áéí óú" && +GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME && +export GIT_AUTHOR_NAME GIT_COMMITTER_NAME From 5ac2715f2eaacc7c76ac03680a0d7a16a30946f2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Jan 2007 13:33:07 -0800 Subject: [PATCH 009/195] Consistent message encoding while reusing log from an existing commit. The following commands can reuse log message from an existing commit while creating a new commit: git-cherry-pick git-rebase (both with and without --merge) git-commit (-c and -C) When the original commit was made in a different encoding from the current i18n.commitencoding, "cat-file commit" would give a string that is inconsistent with what the resulting commit will claim to be in. Replace them with "git show -s --encoding". "git-rebase" without --merge is "git format-patch" piped to "git am" in essence, and has been taken care of before this commit. Signed-off-by: Junio C Hamano --- git-commit.sh | 7 +++++-- git-revert.sh | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/git-commit.sh b/git-commit.sh index eddd863015..b6387239dd 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -435,7 +435,9 @@ then fi elif test "$use_commit" != "" then - git-cat-file commit "$use_commit" | sed -e '1,/^$/d' + encoding=$(git repo-config i18n.commitencoding || echo UTF-8) + git show -s --pretty=raw --encoding="$encoding" "$use_commit" | + sed -e '1,/^$/d' -e 's/^ //' elif test -f "$GIT_DIR/MERGE_MSG" then cat "$GIT_DIR/MERGE_MSG" @@ -497,7 +499,8 @@ then q } ' - set_author_env=`git-cat-file commit "$use_commit" | + encoding=$(git repo-config i18n.commitencoding || echo UTF-8) + set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" | LANG=C LC_ALL=C sed -ne "$pick_author_script"` eval "$set_author_env" export GIT_AUTHOR_NAME diff --git a/git-revert.sh b/git-revert.sh index fcca3ebb90..fcbefb4e68 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -78,6 +78,8 @@ prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) || git-rev-parse --verify "$commit^2" >/dev/null 2>&1 && die "Cannot run $me a multi-parent commit." +encoding=$(git repo-config i18n.commitencoding || echo UTF-8) + # "commit" is an existing commit. We would want to apply # the difference it introduces since its first parent "prev" # on top of the current HEAD if we are cherry-pick. Or the @@ -85,10 +87,11 @@ git-rev-parse --verify "$commit^2" >/dev/null 2>&1 && case "$me" in revert) - git-rev-list --pretty=oneline --max-count=1 $commit | + git show -s --pretty=oneline --encoding="$encoding" $commit | sed -e ' s/^[^ ]* /Revert "/ - s/$/"/' + s/$/"/ + ' echo echo "This reverts commit $commit." test "$rev" = "$commit" || @@ -117,14 +120,17 @@ cherry-pick) q }' - set_author_env=`git-cat-file commit "$commit" | + + logmsg=`git show -s --pretty=raw --encoding="$encoding" "$commit"` + set_author_env=`echo "$logmsg" | LANG=C LC_ALL=C sed -ne "$pick_author_script"` eval "$set_author_env" export GIT_AUTHOR_NAME export GIT_AUTHOR_EMAIL export GIT_AUTHOR_DATE - git-cat-file commit $commit | sed -e '1,/^$/d' + echo "$logmsg" | + sed -e '1,/^$/d' -e 's/^ //' case "$replay" in '') echo "(cherry picked from commit $commit)" From c34c6008bcf2c66e17a97acc89be1144a6216f3f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Jan 2007 13:34:44 -0800 Subject: [PATCH 010/195] More tests in t3901. This adds tests for "cherry-pick" and "rebase --merge" (and indirectly "commit -C" since it is used in the latter) to make sure they create a new commit with correct encoding. Signed-off-by: Junio C Hamano --- t/t3901-i18n-patch.sh | 175 +++++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 37 deletions(-) diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 7fecfe98d2..eda0e2d729 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -7,6 +7,29 @@ test_description='i18n settings and format-patch | am pipe' . ./test-lib.sh +check_encoding () { + # Make sure characters are not corrupted + cnt="$1" header="$2" i=1 j=0 bad=0 + while test "$i" -le $cnt + do + git format-patch --encoding=UTF-8 --stdout HEAD~$i..HEAD~$j | + grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" && + git-cat-file commit HEAD~$j | + case "$header" in + 8859) + grep "^encoding ISO-8859-1" ;; + *) + ! grep "^encoding ISO-8859-1" ;; + esac || { + bad=1 + break + } + j=$i + i=$(($i+1)) + done + (exit $bad) +} + test_expect_success setup ' git-repo-config i18n.commitencoding UTF-8 && @@ -66,7 +89,7 @@ test_expect_success 'format-patch output (UTF-8)' ' grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-u2 ' -test_expect_success 'rebase (UTF-8)' ' +test_expect_success 'rebase (U/U)' ' # We want the result of rebase in UTF-8 git-repo-config i18n.commitencoding UTF-8 && @@ -82,17 +105,10 @@ test_expect_success 'rebase (UTF-8)' ' git checkout -b test && git-rebase master && - # Check the results. - git format-patch --stdout HEAD~2..HEAD^ >out-r1 && - git format-patch --stdout HEAD^ >out-r2 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 - - ! git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && - ! git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" + check_encoding 2 ' -test_expect_success 'rebase (ISO-8859-1)' ' +test_expect_success 'rebase (U/L)' ' git-repo-config i18n.commitencoding UTF-8 && git repo-config i18n.logoutputencoding ISO-8859-1 && . ../t3901-utf8.txt && @@ -100,17 +116,10 @@ test_expect_success 'rebase (ISO-8859-1)' ' git reset --hard side && git-rebase master && - git repo-config i18n.logoutputencoding UTF-8 && - git format-patch --stdout HEAD~2..HEAD^ >out-r1 && - git format-patch --stdout HEAD^ >out-r2 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && - - ! git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && - ! git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" + check_encoding 2 ' -test_expect_success 'rebase (ISO-8859-1)' ' +test_expect_success 'rebase (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git-repo-config i18n.commitencoding ISO-8859-1 && git repo-config i18n.logoutputencoding ISO-8859-1 && @@ -119,18 +128,10 @@ test_expect_success 'rebase (ISO-8859-1)' ' git reset --hard side && git-rebase master && - # Make sure characters are not corrupted. - git repo-config i18n.logoutputencoding UTF-8 && - git format-patch --stdout HEAD~2..HEAD^ >out-r1 && - git format-patch --stdout HEAD^ >out-r2 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && - - git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && - git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" + check_encoding 2 8859 ' -test_expect_success 'rebase (UTF-8)' ' +test_expect_success 'rebase (L/U)' ' # This is pathological -- use UTF-8 as intermediate form # to get ISO-8859-1 results. git-repo-config i18n.commitencoding ISO-8859-1 && @@ -140,15 +141,115 @@ test_expect_success 'rebase (UTF-8)' ' git reset --hard side && git-rebase master && - # Make sure characters are not corrupted. - git repo-config i18n.logoutputencoding UTF-8 && - git format-patch --stdout HEAD~2..HEAD^ >out-r1 && - git format-patch --stdout HEAD^ >out-r2 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r1 && - grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-r2 && + check_encoding 2 8859 +' - git-cat-file commit HEAD | grep "^encoding ISO-8859-1" && - git-cat-file commit HEAD^ | grep "^encoding ISO-8859-1" +test_expect_success 'cherry-pick(U/U)' ' + # Both the commitencoding and logoutputencoding is set to UTF-8. + + git-repo-config i18n.commitencoding UTF-8 && + git repo-config i18n.logoutputencoding UTF-8 && + . ../t3901-utf8.txt && + + git reset --hard master && + git cherry-pick side^ && + git cherry-pick side && + EDITOR=: VISUAL=: git revert HEAD && + + check_encoding 3 +' + +test_expect_success 'cherry-pick(L/L)' ' + # Both the commitencoding and logoutputencoding is set to ISO-8859-1 + + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-8859-1.txt && + + git reset --hard master && + git cherry-pick side^ && + git cherry-pick side && + EDITOR=: VISUAL=: git revert HEAD && + + check_encoding 3 8859 +' + +test_expect_success 'cherry-pick(U/L)' ' + # Commitencoding is set to UTF-8 but logoutputencoding is ISO-8859-1 + + git-repo-config i18n.commitencoding UTF-8 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-utf8.txt && + + git reset --hard master && + git cherry-pick side^ && + git cherry-pick side && + EDITOR=: VISUAL=: git revert HEAD && + + check_encoding 3 +' + +test_expect_success 'cherry-pick(L/U)' ' + # Again, the commitencoding is set to ISO-8859-1 but + # logoutputencoding is set to UTF-8. + + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding UTF-8 && + . ../t3901-8859-1.txt && + + git reset --hard master && + git cherry-pick side^ && + git cherry-pick side && + EDITOR=: VISUAL=: git revert HEAD && + + check_encoding 3 8859 +' + +test_expect_success 'rebase --merge (U/U)' ' + git-repo-config i18n.commitencoding UTF-8 && + git repo-config i18n.logoutputencoding UTF-8 && + . ../t3901-utf8.txt && + + git reset --hard side && + git-rebase --merge master && + + check_encoding 2 +' + +test_expect_success 'rebase --merge (U/L)' ' + git-repo-config i18n.commitencoding UTF-8 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-utf8.txt && + + git reset --hard side && + git-rebase --merge master && + + check_encoding 2 +' + +test_expect_success 'rebase --merge (L/L)' ' + # In this test we want ISO-8859-1 encoded commits as the result + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding ISO-8859-1 && + . ../t3901-8859-1.txt && + + git reset --hard side && + git-rebase --merge master && + + check_encoding 2 8859 +' + +test_expect_success 'rebase --merge (L/U)' ' + # This is pathological -- use UTF-8 as intermediate form + # to get ISO-8859-1 results. + git-repo-config i18n.commitencoding ISO-8859-1 && + git repo-config i18n.logoutputencoding UTF-8 && + . ../t3901-8859-1.txt && + + git reset --hard side && + git-rebase --merge master && + + check_encoding 2 8859 ' test_done From dcbc7bbe393d6bfee0ac36cc97fbc51ebf112ed6 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 13 Jan 2007 21:23:55 -0500 Subject: [PATCH 011/195] simplify the "no changes added to commit" message Suggesting the use of [-a|-i|-o] with git-commit is unnecessarily complex and confusing. In this context -o is totally useless and -i requires extra arguments which are not mentioned. The only sensible hint (besides reading the man page but let's not go there) is "commit -a". Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- wt-status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wt-status.c b/wt-status.c index daba9a6105..b7250e4310 100644 --- a/wt-status.c +++ b/wt-status.c @@ -335,7 +335,7 @@ void wt_status_print(struct wt_status *s) if (s->amend) printf("# No changes\n"); else if (s->workdir_dirty) - printf("no changes added to commit (use \"git add\" and/or \"git commit [-a|-i|-o]\")\n"); + printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); else if (s->workdir_untracked) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) From 38434f2eed45f42ed706d07564079c23ee686511 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 03:22:47 -0500 Subject: [PATCH 012/195] Hide output about SVN::Core not being found during tests. If the user doesn't have SVN::Core installed or working then the SVN tests properly turn themselves off. But the user doesn't need to know that SVN::Core isn't loadable as a Perl module. Unless of course they are trying to debug the test, so lets relegate the Perl failures to --verbose only. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/lib-git-svn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index a0f2814083..bb1d7b84bc 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -26,7 +26,7 @@ use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); system(qw/svnadmin create --fs-type fsfs/, '$svnrepo') == 0 or exit(41); -" +" >&3 2>&4 x=$? if test $x -ne 0 then From e6e2bd6201d32342df7a713c847161ab296885ea Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 01:01:49 -0500 Subject: [PATCH 013/195] Remove read_or_die in favor of better error messages. Originally I introduced read_or_die for the purpose of reading the pack header and trailer, and I was too lazy to print proper error messages. Linus Torvalds : > For a read error, at the very least you have to say WHICH FILE > couldn't be read, because it's usually a matter of some file just > being too short, not some system-wide problem. and of course Linus is right. Make it so. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- cache.h | 1 - sha1_file.c | 6 ++++-- write_or_die.c | 12 ------------ 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/cache.h b/cache.h index c482c32a03..620b6a4ed4 100644 --- a/cache.h +++ b/cache.h @@ -434,7 +434,6 @@ extern char *git_log_output_encoding; extern int copy_fd(int ifd, int ofd); extern int read_in_full(int fd, void *buf, size_t count); -extern void read_or_die(int fd, void *buf, size_t count); extern int write_in_full(int fd, const void *buf, size_t count); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); diff --git a/sha1_file.c b/sha1_file.c index 2a5be53fac..1b1c0f7b4d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -572,7 +572,8 @@ static void open_packed_git(struct packed_git *p) die("cannot set FD_CLOEXEC"); /* Verify we recognize this pack file format. */ - read_or_die(p->pack_fd, &hdr, sizeof(hdr)); + if (read_in_full(p->pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + die("file %s is far too short to be a packfile", p->pack_name); if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) die("file %s is not a GIT packfile", p->pack_name); if (!pack_version_ok(hdr.hdr_version)) @@ -588,7 +589,8 @@ static void open_packed_git(struct packed_git *p) num_packed_objects(p)); if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1) die("end of packfile %s is unavailable", p->pack_name); - read_or_die(p->pack_fd, sha1, sizeof(sha1)); + if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1)) + die("packfile %s signature is unavailable", p->pack_name); idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40; if (hashcmp(sha1, idx_sha1)) die("packfile %s does not match index", p->pack_name); diff --git a/write_or_die.c b/write_or_die.c index 4e8183e93e..046e79d485 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -17,18 +17,6 @@ int read_in_full(int fd, void *buf, size_t count) return total; } -void read_or_die(int fd, void *buf, size_t count) -{ - ssize_t loaded; - - loaded = read_in_full(fd, buf, count); - if (loaded != count) { - if (loaded < 0) - die("read error (%s)", strerror(errno)); - die("read error: end of file"); - } -} - int write_in_full(int fd, const void *buf, size_t count) { const char *p = buf; From 63889639bbb47399b5ede784b1afe48679d52bb2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:33 -0500 Subject: [PATCH 014/195] Remove unnecessary call_depth parameter in merge-recursive. Because the output_indent always matches the call_depth value there is no reason to pass around the call_depth to the merge function during each recursive invocation. This is a simple refactoring that will make the code easier to follow later on as I start to add output verbosity controls. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index b4acbb7408..8738f090c3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -70,13 +70,13 @@ struct stage_data static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; -static int output_indent = 0; +static int call_depth = 0; static void output(const char *fmt, ...) { va_list args; int i; - for (i = output_indent; i--;) + for (i = call_depth; i--;) fputs(" ", stdout); va_start(args, fmt); vfprintf(stdout, fmt, args); @@ -87,7 +87,7 @@ static void output(const char *fmt, ...) static void output_commit_title(struct commit *commit) { int i; - for (i = output_indent; i--;) + for (i = call_depth; i--;) fputs(" ", stdout); if (commit->util) printf("virtual %s\n", (char *)commit->util); @@ -1095,7 +1095,6 @@ static int merge(struct commit *h1, struct commit *h2, const char *branch1, const char *branch2, - int call_depth /* =0 */, struct commit_list *ca, struct commit **result) { @@ -1129,7 +1128,7 @@ static int merge(struct commit *h1, } for (iter = ca; iter; iter = iter->next) { - output_indent = call_depth + 1; + call_depth++; /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is @@ -1141,17 +1140,16 @@ static int merge(struct commit *h1, merge(merged_common_ancestors, iter->item, "Temporary merge branch 1", "Temporary merge branch 2", - call_depth + 1, NULL, &merged_common_ancestors); - output_indent = call_depth; + call_depth--; if (!merged_common_ancestors) die("merge returned no commit"); } discard_cache(); - if (call_depth == 0) { + if (!call_depth) { read_cache(); index_only = 0; } else @@ -1239,7 +1237,7 @@ int main(int argc, char *argv[]) struct commit *ancestor = get_ref(bases[i]); ca = commit_list_insert(ancestor, &ca); } - clean = merge(h1, h2, branch1, branch2, 0, ca, &result); + clean = merge(h1, h2, branch1, branch2, ca, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || From 8c3275abcacb83ea3f4c4f4ceb2376799fc282bd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:48 -0500 Subject: [PATCH 015/195] Allow the user to control the verbosity of merge-recursive. Junio C Hamano writes: > > I think the output from merge-recursive can be categorized into 5 > verbosity levels: > > 1. "CONFLICT", "Rename", "Adding here instead due to D/F conflict" > (outermost) > > 2. "Auto-merged successfully" (outermost) > > 3. The first "Merging X with Y". > > 4. outermost "Merging:\ntitle1\ntitle2". > > 5. outermost "found N common ancestors\nancestor1\nancestor2\n..." > and anything from inner merge. > > I would prefer the default verbosity level to be 2 (that is, show > both 1 and 2). and this change makes it so. I think level 3 is probably pointless as its only one line of output above level 2, but I can see how some users may want to view it but not view the slightly more verbose output of level 4. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/config.txt | 7 +++ merge-recursive.c | 100 ++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index f7dba8977f..faa17ba848 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -321,6 +321,13 @@ merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. +merge.verbosity:: + Controls the amount of output shown by the recursive merge + strategy. Level 0 outputs nothing except a final error + message if conflicts were detected. Level 1 outputs only + conflicts, 2 outputs conflicts and file changes. Level 5 and + above outputs debugging information. The default is level 2. + pack.window:: The size of the window used by gitlink:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. diff --git a/merge-recursive.c b/merge-recursive.c index 8738f090c3..ef9932a68c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -71,17 +71,25 @@ static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; +static int verbosity = 2; -static void output(const char *fmt, ...) +static int show (int v) +{ + return (!call_depth && verbosity >= v) || verbosity >= 5; +} + +static void output(int v, const char *fmt, ...) { va_list args; - int i; - for (i = call_depth; i--;) - fputs(" ", stdout); va_start(args, fmt); - vfprintf(stdout, fmt, args); + if (show(v)) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + vfprintf(stdout, fmt, args); + fputc('\n', stdout); + } va_end(args); - fputc('\n', stdout); } static void output_commit_title(struct commit *commit) @@ -640,13 +648,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (path_list_has_path(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output("%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (path_list_has_path(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output("%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -660,7 +668,7 @@ static void conflict_rename_dir(struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output("Renaming %s to %s instead", ren1->pair->one->path, new_path); + output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); @@ -673,7 +681,7 @@ static void conflict_rename_rename_2(struct rename *ren1, { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output("Renaming %s to %s and %s to %s instead", + output(1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path, 0); @@ -766,7 +774,7 @@ static int process_renames(struct path_list *a_renames, ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { clean_merge = 0; - output("CONFLICT (rename/rename): " + output(1, "CONFLICT (rename/rename): " "Rename %s->%s in branch %s " "rename %s->%s in %s", src, ren1_dst, branch1, @@ -781,13 +789,13 @@ static int process_renames(struct path_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output("Renaming %s->%s", src, ren1_dst); + output(1, "Renaming %s->%s", src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (content): merge conflict in %s", + output(1, "CONFLICT (content): merge conflict in %s", ren1_dst); clean_merge = 0; @@ -818,14 +826,14 @@ static int process_renames(struct path_list *a_renames, if (path_list_has_path(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output("CONFLICT (rename/directory): Rename %s->%s in %s " + output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); conflict_rename_dir(ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { clean_merge = 0; - output("CONFLICT (rename/delete): Rename %s->%s in %s " + output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -834,18 +842,18 @@ static int process_renames(struct path_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output("CONFLICT (rename/add): Rename %s->%s in %s. " + output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output("Adding as %s instead", new_path); + output(1, "Adding as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output("CONFLICT (rename/rename): Rename %s->%s in %s. " + output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " "Rename %s->%s in %s", ren1_src, ren1_dst, branch1, ren2->pair->one->path, ren2->pair->two->path, branch2); @@ -870,11 +878,11 @@ static int process_renames(struct path_list *a_renames, a_branch, b_branch); if (mfi.merge || !mfi.clean) - output("Renaming %s => %s", ren1_src, ren1_dst); + output(1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (rename/modify): Merge conflict in %s", + output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); clean_merge = 0; @@ -922,20 +930,20 @@ static int process_entry(const char *path, struct stage_data *entry, /* Deleted in both or deleted in one and * unchanged in the other */ if (a_sha) - output("Removing %s", path); + output(2, "Removing %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { /* Deleted in one and changed in the other */ clean_merge = 0; if (!a_sha) { - output("CONFLICT (delete/modify): %s deleted in %s " + output(1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, branch1, branch2, branch2, path); update_file(0, b_sha, b_mode, path); } else { - output("CONFLICT (delete/modify): %s deleted in %s " + output(1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", path, branch2, branch1, branch1, path); @@ -968,13 +976,13 @@ static int process_entry(const char *path, struct stage_data *entry, if (path_list_has_path(¤t_directory_set, path)) { const char *new_path = unique_path(path, add_branch); clean_merge = 0; - output("CONFLICT (%s): There is a directory with name %s in %s. " + output(1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output("Adding %s", path); + output(2, "Adding %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -988,7 +996,7 @@ static int process_entry(const char *path, struct stage_data *entry, reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output("Auto-merging %s", path); + output(2, "Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); o.mode = o_mode; @@ -1004,7 +1012,7 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (%s): Merge conflict in %s", + output(1, "CONFLICT (%s): Merge conflict in %s", reason, path); if (index_only) @@ -1028,7 +1036,7 @@ static int merge_trees(struct tree *head, { int code, clean; if (sha_eq(common->object.sha1, merge->object.sha1)) { - output("Already uptodate!"); + output(0, "Already uptodate!"); *result = head; return 1; } @@ -1103,18 +1111,22 @@ static int merge(struct commit *h1, struct tree *mrtree; int clean; - output("Merging:"); - output_commit_title(h1); - output_commit_title(h2); + if (show(4)) { + output(4, "Merging:"); + output_commit_title(h1); + output_commit_title(h2); + } if (!ca) { ca = get_merge_bases(h1, h2, 1); ca = reverse_commit_list(ca); } - output("found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); + if (show(5)) { + output(5, "found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + } merged_common_ancestors = pop_commit(&ca); if (merged_common_ancestors == NULL) { @@ -1196,6 +1208,15 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } +static int merge_config(const char *var, const char *value) +{ + if (!strcasecmp(var, "merge.verbosity")) { + verbosity = git_config_int(var, value); + return 0; + } + return git_default_config(var, value); +} + int main(int argc, char *argv[]) { static const char *bases[20]; @@ -1207,7 +1228,9 @@ int main(int argc, char *argv[]) struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; - git_config(git_default_config); /* core.filemode */ + git_config(merge_config); + if (getenv("GIT_MERGE_VERBOSITY")) + verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); @@ -1229,7 +1252,8 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - printf("Merging %s with %s\n", branch1, branch2); + if (show(3)) + printf("Merging %s with %s\n", branch1, branch2); index_fd = hold_lock_file_for_update(lock, get_index_file(), 1); From 66a155bc129b12f1f13be8e3f20e57db5ace0e6f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:53 -0500 Subject: [PATCH 016/195] Enable output buffering in merge-recursive. Buffering all message output until a merge invocation is complete is necessary to prevent intereferring with a progress meter that would indicate the number of files completely merged, and how many remain. This change does not introduce a progress meter, but merely lays the groundwork to buffer the output. To aid debugging output buffering is only enabled if verbosity is lower than 5. When using verbosity levels above 5 the user is probably debugging the merge program itself and does not want to see the output delayed, especially if they are stepping through portions of the code in a debugger. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/merge-recursive.c b/merge-recursive.c index ef9932a68c..9237a57f8e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -67,11 +67,19 @@ struct stage_data unsigned processed:1; }; +struct output_buffer +{ + struct output_buffer *next; + char *str; +}; + static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; +static int buffer_output = 1; +static struct output_buffer *output_list, *output_end; static int show (int v) { @@ -82,7 +90,16 @@ static void output(int v, const char *fmt, ...) { va_list args; va_start(args, fmt); - if (show(v)) { + if (buffer_output && show(v)) { + struct output_buffer *b = xmalloc(sizeof(*b)); + nfvasprintf(&b->str, fmt, args); + b->next = NULL; + if (output_end) + output_end->next = b; + else + output_list = b; + output_end = b; + } else if (show(v)) { int i; for (i = call_depth; i--;) fputs(" ", stdout); @@ -92,9 +109,27 @@ static void output(int v, const char *fmt, ...) va_end(args); } +static void flush_output() +{ + struct output_buffer *b, *n; + for (b = output_list; b; b = n) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + fputs(b->str, stdout); + fputc('\n', stdout); + n = b->next; + free(b->str); + free(b); + } + output_list = NULL; + output_end = NULL; +} + static void output_commit_title(struct commit *commit) { int i; + flush_output(); for (i = call_depth; i--;) fputs(" ", stdout); if (commit->util) @@ -1175,6 +1210,7 @@ static int merge(struct commit *h1, commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); } + flush_output(); return clean; } @@ -1252,6 +1288,8 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); + if (verbosity >= 5) + buffer_output = 0; if (show(3)) printf("Merging %s with %s\n", branch1, branch2); From 3f6ee2d15ab4be8690c17c0af0338b8495f6f706 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:58 -0500 Subject: [PATCH 017/195] Display a progress meter during merge-recursive. Because large merges on slow systems can take up to a minute to execute we should try to keep the user entertained with a progress meter to let them know how far we have progressed through the current merge. The progress meter considers each entry in the in-memory index to be a unit, which means a single recursive merge will double the number of units in the progress meter. Files which are unmerged after the 3-way tree merge are also considered a unit within the progress meter. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 73 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 9237a57f8e..966d8e987f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -79,6 +79,11 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; static int buffer_output = 1; +static int do_progress = 1; +static unsigned last_percent; +static unsigned merged_cnt; +static unsigned total_cnt; +static volatile sig_atomic_t progress_update; static struct output_buffer *output_list, *output_end; static int show (int v) @@ -153,6 +158,39 @@ static void output_commit_title(struct commit *commit) } } +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static void display_progress() +{ + unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0; + if (progress_update || percent != last_percent) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, merged_cnt, total_cnt); + progress_update = 0; + last_percent = percent; + } +} + static struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh) { @@ -315,11 +353,14 @@ static struct path_list *get_unmerged(void) int i; unmerged->strdup_paths = 1; + total_cnt += active_nr; - for (i = 0; i < active_nr; i++) { + for (i = 0; i < active_nr; i++, merged_cnt++) { struct path_list_item *item; struct stage_data *e; struct cache_entry *ce = active_cache[i]; + if (do_progress) + display_progress(); if (!ce_stage(ce)) continue; @@ -1096,13 +1137,15 @@ static int merge_trees(struct tree *head, re_merge = get_renames(merge, common, head, merge, entries); clean = process_renames(re_head, re_merge, branch1, branch2); - for (i = 0; i < entries->nr; i++) { + total_cnt += entries->nr; + for (i = 0; i < entries->nr; i++, merged_cnt++) { const char *path = entries->items[i].path; struct stage_data *e = entries->items[i].util; - if (e->processed) - continue; - if (!process_entry(path, e, branch1, branch2)) + if (!e->processed + && !process_entry(path, e, branch1, branch2)) clean = 0; + if (do_progress) + display_progress(); } path_list_clear(re_merge, 0); @@ -1210,6 +1253,15 @@ static int merge(struct commit *h1, commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); } + if (!call_depth && do_progress) { + /* Make sure we end at 100% */ + if (!total_cnt) + total_cnt = 1; + merged_cnt = total_cnt; + progress_update = 1; + display_progress(); + fputc('\n', stderr); + } flush_output(); return clean; } @@ -1279,6 +1331,12 @@ int main(int argc, char *argv[]) } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); + if (verbosity >= 5) { + buffer_output = 0; + do_progress = 0; + } + else + do_progress = isatty(1); branch1 = argv[++i]; branch2 = argv[++i]; @@ -1288,8 +1346,9 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - if (verbosity >= 5) - buffer_output = 0; + + if (do_progress) + setup_progress_signal(); if (show(3)) printf("Merging %s with %s\n", branch1, branch2); From 89f40be294363ce4d14ed6931a65561a4e8e9140 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 03:11:28 -0500 Subject: [PATCH 018/195] Convert output messages in merge-recursive to past tense. Now that we are showing the output messages for verbosity levels <5 after all actions have been performed (due to the progress meter running during the actions) it can be confusing to see messages in the present tense when the user is looking at a '100% done' message right above them. Converting the messages to past tense will appear more correct in this case, and shouldn't affect a developer who is debugging the application and running it at a verbosity level >=5. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 966d8e987f..fa320eb6b1 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -724,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (path_list_has_path(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s added as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (path_list_has_path(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s adding as %s instead", + output(1, "%s is a directory in %s added as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -744,7 +744,7 @@ static void conflict_rename_dir(struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); + output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); @@ -757,7 +757,7 @@ static void conflict_rename_rename_2(struct rename *ren1, { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renaming %s to %s and %s to %s instead", + output(1, "Renamed %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path, 0); @@ -865,10 +865,10 @@ static int process_renames(struct path_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); + output(1, "Renamed %s->%s", src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (content): merge conflict in %s", @@ -902,14 +902,14 @@ static int process_renames(struct path_list *a_renames, if (path_list_has_path(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s " " directory %s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); conflict_rename_dir(ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { clean_merge = 0; - output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " + output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -918,19 +918,19 @@ static int process_renames(struct path_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output(1, "Adding as %s instead", new_path); + output(1, "Added as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " - "Rename %s->%s in %s", + output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. " + "Renamed %s->%s in %s", ren1_src, ren1_dst, branch1, ren2->pair->one->path, ren2->pair->two->path, branch2); conflict_rename_rename_2(ren1, branch1, ren2, branch2); @@ -954,9 +954,9 @@ static int process_renames(struct path_list *a_renames, a_branch, b_branch); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); + output(1, "Renamed %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); @@ -1006,7 +1006,7 @@ static int process_entry(const char *path, struct stage_data *entry, /* Deleted in both or deleted in one and * unchanged in the other */ if (a_sha) - output(2, "Removing %s", path); + output(2, "Removed %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { @@ -1053,12 +1053,12 @@ static int process_entry(const char *path, struct stage_data *entry, const char *new_path = unique_path(path, add_branch); clean_merge = 0; output(1, "CONFLICT (%s): There is a directory with name %s in %s. " - "Adding %s as %s", + "Added %s as %s", conf, path, other_branch, path, new_path); remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output(2, "Adding %s", path); + output(2, "Added %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -1072,7 +1072,7 @@ static int process_entry(const char *path, struct stage_data *entry, reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output(2, "Auto-merging %s", path); + output(2, "Auto-merged %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); o.mode = o_mode; From adb7ba6b116c0ec26e058b9b41a5a528d123323f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 14 Jan 2007 18:23:22 -0800 Subject: [PATCH 019/195] git log documentation: teach - form. We say "this shows only the most often used ones"; so instead of teaching --max-number= form, list - form which is much easier to type. Signed-off-by: Junio C Hamano --- Documentation/git-log.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index e9f746bbd4..60610f91f4 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -27,7 +27,7 @@ OPTIONS include::pretty-formats.txt[] ---max-count=:: +-:: Limits the number of commits to show. ..:: From c14261eaa297504799e3b21ecbd751edbae912c0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sun, 14 Jan 2007 22:44:18 -0500 Subject: [PATCH 020/195] some doc updates 1) talk about "git merge" instead of "git pull ." 2) suggest "git repo-config" instead of directly editing config files 3) echo "URL: blah" > .git/remotes/foo is obsolete and should be "git repo-config remote.foo.url blah" 4) support for partial URL prefix has been removed (see commit ea560e6d64374ec1f6c163c276319a3da21a1345) so drop mention of it. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- Documentation/core-tutorial.txt | 42 ++++++++++----------------------- Documentation/everyday.txt | 3 +-- Documentation/git-pull.txt | 3 ++- Documentation/git-rerere.txt | 10 ++++---- Documentation/tutorial.txt | 20 +++++++--------- 5 files changed, 29 insertions(+), 49 deletions(-) diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 0cd33fb5b7..51dd6c6164 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -1129,46 +1129,26 @@ juggle multiple lines of development simultaneously. Of course, you will pay the price of more disk usage to hold multiple working trees, but disk space is cheap these days. -[NOTE] -You could even pull from your own repository by -giving '.' as parameter to `git pull`. This -is useful when you want to merge a local branch (or more, if you -are making an Octopus) into the current branch. - It is likely that you will be pulling from the same remote repository from time to time. As a short hand, you can store -the remote repository URL in a file under .git/remotes/ -directory, like this: +the remote repository URL in the local repository's config file +like this: ------------------------------------------------ -$ mkdir -p .git/remotes/ -$ cat >.git/remotes/linus <<\EOF -URL: http://www.kernel.org/pub/scm/git/git.git/ -EOF ------------------------------------------------- - -and use the filename to `git pull` instead of the full URL. -The URL specified in such file can even be a prefix -of a full URL, like this: - ------------------------------------------------- -$ cat >.git/remotes/jgarzik <<\EOF -URL: http://www.kernel.org/pub/scm/linux/git/jgarzik/ -EOF +$ git repo-config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/ ------------------------------------------------ +and use the "linus" keyword with `git pull` instead of the full URL. Examples. . `git pull linus` . `git pull linus tag v0.99.1` -. `git pull jgarzik/netdev-2.6.git/ e100` the above are equivalent to: . `git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD` . `git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1` -. `git pull http://www.kernel.org/pub/.../jgarzik/netdev-2.6.git e100` How does the merge work? @@ -1546,7 +1526,8 @@ on that project and has an own "public repository" goes like this: 1. Prepare your work repository, by `git clone` the public repository of the "project lead". The URL used for the - initial cloning is stored in `.git/remotes/origin`. + initial cloning is stored in the remote.origin.url + configuration variable. 2. Prepare a public repository accessible to others, just like the "project lead" person does. @@ -1586,14 +1567,15 @@ like this: 1. Prepare your work repository, by `git clone` the public repository of the "project lead" (or a "subsystem maintainer", if you work on a subsystem). The URL used for - the initial cloning is stored in `.git/remotes/origin`. + the initial cloning is stored in the remote.origin.url + configuration variable. 2. Do your work in your repository on 'master' branch. 3. Run `git fetch origin` from the public repository of your upstream every once in a while. This does only the first half of `git pull` but does not merge. The head of the - public repository is stored in `.git/refs/heads/origin`. + public repository is stored in `.git/refs/remotes/origin/master`. 4. Use `git cherry origin` to see which ones of your patches were accepted, and/or use `git rebase origin` to port your @@ -1681,11 +1663,11 @@ $ git reset --hard master~2 You can make sure 'git show-branch' matches the state before those two 'git merge' you just did. Then, instead of running -two 'git merge' commands in a row, you would pull these two +two 'git merge' commands in a row, you would merge these two branch heads (this is known as 'making an Octopus'): ------------ -$ git pull . commit-fix diff-fix +$ git merge commit-fix diff-fix $ git show-branch ! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. @@ -1701,7 +1683,7 @@ $ git show-branch Note that you should not do Octopus because you can. An octopus is a valid thing to do and often makes it easier to view the -commit history if you are pulling more than two independent +commit history if you are merging more than two independent changes at the same time. However, if you have merge conflicts with any of the branches you are merging in and need to hand resolve, that is an indication that the development happened in diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 4e83994c58..ca36a76da6 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -148,8 +148,7 @@ modification will be caught if you do `git commit -a` later. <8> redo the commit undone in the previous step, using the message you originally wrote. <9> switch to the master branch. -<10> merge a topic branch into your master branch. You can also use -`git pull . alsa-audio`, i.e. pull from the local repository. +<10> merge a topic branch into your master branch. <11> review commit logs; other forms to limit output can be combined and include `\--max-count=10` (show 10 commits), `\--until=2005-12-10`, etc. diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 13be992006..a90b764cc9 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -52,7 +52,8 @@ git pull origin next:: git pull . fixes enhancements:: Bundle local branch `fixes` and `enhancements` on top of - the current branch, making an Octopus merge. + the current branch, making an Octopus merge. This `git pull .` + syntax is equivalent to `git merge`. git pull -s ours . obsolete:: Merge local branch `obsolete` into the current branch, diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index b57a72bdd7..08a055713c 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -81,7 +81,7 @@ One way to do it is to pull master into the topic branch: ------------ $ git checkout topic - $ git pull . master + $ git merge master o---*---o---+ topic / / @@ -103,10 +103,10 @@ in which case the final commit graph would look like this: ------------ $ git checkout topic - $ git pull . master + $ git merge master $ ... work on both topic and master branches $ git checkout master - $ git pull . topic + $ git merge topic o---*---o---+---o---o topic / / \ @@ -126,11 +126,11 @@ top of the tip before the test merge: ------------ $ git checkout topic - $ git pull . master + $ git merge master $ git reset --hard HEAD^ ;# rewind the test merge $ ... work on both topic and master branches $ git checkout master - $ git pull . topic + $ git merge topic o---*---o-------o---o topic / \ diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index d2bf0b905a..8325c5e53a 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -11,15 +11,13 @@ diff" with: $ man git-diff ------------------------------------------------ -It is a good idea to introduce yourself to git before doing any -operation. The easiest way to do so is: +It is a good idea to introduce yourself to git with your name and +public email address before doing any operation. The easiest +way to do so is: ------------------------------------------------ -$ cat >~/.gitconfig <<\EOF -[user] - name = Your Name Comes Here - email = you@yourdomain.example.com -EOF +$ git repo-config --global user.name "Your Name Comes Here" +$ git repo-config --global user.email you@yourdomain.example.com ------------------------------------------------ @@ -211,7 +209,7 @@ at this point the two branches have diverged, with different changes made in each. To merge the changes made in experimental into master, run ------------------------------------------------ -$ git pull . experimental +$ git merge experimental ------------------------------------------------ If the changes don't conflict, you're done. If there are conflicts, @@ -316,14 +314,14 @@ shows a list of all the changes that Bob made since he branched from Alice's master branch. After examining those changes, and possibly fixing things, Alice -could pull the changes into her master branch: +could merge the changes into her master branch: ------------------------------------- $ git checkout master -$ git pull . bob-incoming +$ git merge bob-incoming ------------------------------------- -The last command is a pull from the "bob-incoming" branch in Alice's +The last command is a merge from the "bob-incoming" branch in Alice's own repository. Alice could also perform both steps at once with: From dccd0c2abdb958daf6f168ba925b67441dc6be61 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 13 Jan 2007 17:27:52 -0500 Subject: [PATCH 021/195] Always perfer annotated tags in git-describe. Several people have suggested that its always better to describe a commit using an annotated tag, and to only use a lightweight tag if absolutely no annotated tag matches the input commit. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index a8c98cea16..ad672aa8ee 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -138,6 +138,7 @@ static void describe(const char *arg, int last_one) commit_list_insert(cmit, &list); while (list) { struct commit *c = pop_commit(&list); + struct commit_list *parents = c->parents; n = match(c); if (n) { struct possible_tag *p = xmalloc(sizeof(*p)); @@ -148,17 +149,17 @@ static void describe(const char *arg, int last_one) else all_matches = p; cur_match = p; - } else { - struct commit_list *parents = c->parents; - while (parents) { - struct commit *p = parents->item; - parse_commit(p); - if (!(p->object.flags & SEEN)) { - p->object.flags |= SEEN; - insert_by_date(p, &list); - } - parents = parents->next; + if (n->prio == 2) + continue; + } + while (parents) { + struct commit *p = parents->item; + parse_commit(p); + if (!(p->object.flags & SEEN)) { + p->object.flags |= SEEN; + insert_by_date(p, &list); } + parents = parents->next; } } @@ -181,7 +182,8 @@ static void describe(const char *arg, int last_one) while ((!min_match || cur_match->depth < min_match->depth) && get_revision(&revs)) cur_match->depth++; - if (!min_match || cur_match->depth < min_match->depth) + if (!min_match || (cur_match->depth < min_match->depth + && cur_match->name->prio >= min_match->name->prio)) min_match = cur_match; free_commit_list(revs.commits); } From c3e3cd4bf8c94cc2f4fa8d8f7751553037e06004 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 13 Jan 2007 17:28:16 -0500 Subject: [PATCH 022/195] Hash tags by commit SHA1 in git-describe. If a project has a very large number of tags then git-describe will spend a good part of its time looping over the tags testing them one at a time to determine if it matches a given commit. For 10 tags this is not a big deal, but for hundreds of tags the time could become considerable if we don't find an exact match for the input commit and we need to walk back along the history chain. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index ad672aa8ee..582ef023f7 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -12,20 +12,20 @@ static const char describe_usage[] = static int all; /* Default to annotated tags only */ static int tags; /* But allow any tags if --tags is specified */ - static int abbrev = DEFAULT_ABBREV; -static int names, allocs; +static unsigned int names[256], allocs[256]; static struct commit_name { struct commit *commit; int prio; /* annotated tag = 2, tag = 1, head = 0 */ char path[FLEX_ARRAY]; /* more */ -} **name_array = NULL; +} **name_array[256]; static struct commit_name *match(struct commit *cmit) { - int i = names; - struct commit_name **p = name_array; + unsigned char m = cmit->object.sha1[0]; + unsigned int i = names[m]; + struct commit_name **p = name_array[m]; while (i-- > 0) { struct commit_name *n = *p++; @@ -42,17 +42,19 @@ static void add_to_known_names(const char *path, int idx; int len = strlen(path)+1; struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); + unsigned char m = commit->object.sha1[0]; name->commit = commit; name->prio = prio; memcpy(name->path, path, len); - idx = names; - if (idx >= allocs) { - allocs = (idx + 50) * 3 / 2; - name_array = xrealloc(name_array, allocs*sizeof(*name_array)); + idx = names[m]; + if (idx >= allocs[m]) { + allocs[m] = (idx + 50) * 3 / 2; + name_array[m] = xrealloc(name_array[m], + allocs[m] * sizeof(*name_array)); } - name_array[idx] = name; - names = ++idx; + name_array[m][idx] = name; + names[m] = ++idx; } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) @@ -121,9 +123,12 @@ static void describe(const char *arg, int last_one) die("%s is not a valid '%s' object", arg, commit_type); if (!initialized) { + unsigned int m; initialized = 1; for_each_ref(get_name, NULL); - qsort(name_array, names, sizeof(*name_array), compare_names); + for (m = 0; m < ARRAY_SIZE(name_array); m++) + qsort(name_array[m], names[m], + sizeof(*name_array[m]), compare_names); } n = match(cmit); From 910c0d7b5ea09d55f769062abd9b9fe3af904a23 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 13 Jan 2007 17:29:00 -0500 Subject: [PATCH 023/195] Use binary searching on large buckets in git-describe. If a project has a really huge number of tags (such as several thousand tags) then we are likely to have nearly a hundred tags in some buckets. Scanning those buckets as linked lists could take a large amount of time if done repeatedly during history traversal. Since we are searching for a unique commit SHA1 we can sort all tags by commit SHA1 and perform a binary search within the bucket. Once we identify a particular tag as matching this commit we walk backwards within the bucket matches to make sure we pick up the highest priority tag for that commit, as the binary search may have landed us in the middle of a set of tags which point at the same commit. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index 582ef023f7..5d6865b165 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -23,14 +23,24 @@ static struct commit_name { static struct commit_name *match(struct commit *cmit) { - unsigned char m = cmit->object.sha1[0]; - unsigned int i = names[m]; - struct commit_name **p = name_array[m]; + unsigned char level0 = cmit->object.sha1[0]; + struct commit_name **p = name_array[level0]; + unsigned int hi = names[level0]; + unsigned int lo = 0; - while (i-- > 0) { - struct commit_name *n = *p++; - if (n->commit == cmit) - return n; + while (lo < hi) { + unsigned int mi = (lo + hi) / 2; + int cmp = hashcmp(p[mi]->commit->object.sha1, + cmit->object.sha1); + if (!cmp) { + while (mi && p[mi - 1]->commit == cmit) + mi--; + return p[mi]; + } + if (cmp > 0) + hi = mi; + else + lo = mi+1; } return NULL; } @@ -95,7 +105,10 @@ static int compare_names(const void *_a, const void *_b) struct commit_name *b = *(struct commit_name **)_b; unsigned long a_date = a->commit->date; unsigned long b_date = b->commit->date; + int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); + if (cmp) + return cmp; if (a->prio != b->prio) return b->prio - a->prio; return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; From 8713ab307940c37906631efb8ef96be37963f81c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 13 Jan 2007 17:30:53 -0500 Subject: [PATCH 024/195] Improve git-describe performance by reducing revision listing. My prior version of git-describe ran very slowly on even reasonably sized projects like git.git and linux.git as it tended to identify a large number of possible tags and then needed to generate the revision list for each of those tags to sort them and select the best tag to describe the input commit. All we really need is the number of commits in the input revision which are not in the tag. We can generate these counts during the revision walking and tag matching loop by assigning a color to each tag and coloring the commits as we walk them. This limits us to identifying no more than 26 possible tags, as there is limited space available within the flags field of struct commit. The limitation of 26 possible tags is hopefully not going to be a problem in real usage, as most projects won't create 26 maintenance releases and merge them back into a development trunk after the development trunk was tagged with a release candidate tag. If that does occur git-describe will start to revert to its old behavior of using the newer maintenance release tag to describe the development trunk, rather than the development trunk's own tag. The suggested workaround would be to retag the development trunk's tip. However since even 26 possible tags can take a while to generate a description for on some projects I'm defaulting the limit to 10 but offering the user --candidates to increase the number of possible matches if they need a more accurate result. I specifically chose 10 for the default as it seems unlikely projects will have more than 10 maintenance releases merged into a development trunk before retagging the development trunk, and it seems to perform about the same on linux.git as v1.4.4.4 git-describe. A large amount of debugging information was also added during the development of this change, so I've left it in to be toggled on with --debug. It may be useful to the end user to help them understand why git-describe took one particular tag over another. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-describe.txt | 30 ++++++++ builtin-describe.c | 125 ++++++++++++++++++++------------- 2 files changed, 106 insertions(+), 49 deletions(-) diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index 2700f35bdb..b87783cf09 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -35,6 +35,16 @@ OPTIONS Instead of using the default 8 hexadecimal digits as the abbreviated object name, use digits. +--candidates=:: + Instead of considering only the 10 most recent tags as + candidates to describe the input committish consider + up to candidates. Increasing above 10 will take + slightly longer but may produce a more accurate result. + +--debug:: + Verbosely display information about the searching strategy + being employed to standard error. The tag name will still + be printed to standard out. EXAMPLES -------- @@ -63,6 +73,26 @@ the output shows the reference path as well: [torvalds@g5 git]$ git describe --all HEAD^ heads/lt/describe-g975b +SEARCH STRATEGY +--------------- + +For each committish supplied "git describe" will first look for +a tag which tags exactly that commit. Annotated tags will always +be preferred over lightweight tags, and tags with newer dates will +always be preferred over tags with older dates. If an exact match +is found, its name will be output and searching will stop. + +If an exact match was not found "git describe" will walk back +through the commit history to locate an ancestor commit which +has been tagged. The ancestor's tag will be output along with an +abbreviation of the input committish's SHA1. + +If multiple tags were found during the walk then the tag which +has the fewest commits different from the input committish will be +selected and output. Here fewest commits different is defined as +the number of commits which would be shown by "git log tag..input" +will be the smallest number of commits possible. + Author ------ diff --git a/builtin-describe.c b/builtin-describe.c index 5d6865b165..421658d3be 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -2,17 +2,19 @@ #include "commit.h" #include "tag.h" #include "refs.h" -#include "diff.h" -#include "diffcore.h" -#include "revision.h" #include "builtin.h" +#define SEEN (1u<<0) +#define MAX_TAGS (FLAG_BITS - 1) + static const char describe_usage[] = "git-describe [--all] [--tags] [--abbrev=] *"; +static int debug; /* Display lots of verbose info */ static int all; /* Default to annotated tags only */ static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; +static int max_candidates = 10; static unsigned int names[256], allocs[256]; static struct commit_name { @@ -115,19 +117,21 @@ static int compare_names(const void *_a, const void *_b) } struct possible_tag { - struct possible_tag *next; struct commit_name *name; unsigned long depth; + unsigned flag_within; }; static void describe(const char *arg, int last_one) { unsigned char sha1[20]; - struct commit *cmit; + struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; static int initialized = 0; struct commit_name *n; - struct possible_tag *all_matches, *min_match, *cur_match; + struct possible_tag all_matches[MAX_TAGS], *min_match; + unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; + unsigned long seen_commits = 0; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -150,71 +154,85 @@ static void describe(const char *arg, int last_one) return; } + if (debug) + fprintf(stderr, "searching to describe %s\n", arg); + list = NULL; - all_matches = NULL; - cur_match = NULL; + cmit->object.flags = SEEN; commit_list_insert(cmit, &list); while (list) { struct commit *c = pop_commit(&list); struct commit_list *parents = c->parents; + seen_commits++; n = match(c); if (n) { - struct possible_tag *p = xmalloc(sizeof(*p)); - p->name = n; - p->next = NULL; - if (cur_match) - cur_match->next = p; - else - all_matches = p; - cur_match = p; - if (n->prio == 2) - continue; + if (match_cnt < max_candidates) { + struct possible_tag *t = &all_matches[match_cnt++]; + t->name = n; + t->depth = seen_commits - 1; + t->flag_within = 1u << match_cnt; + c->object.flags |= t->flag_within; + if (n->prio == 2) + annotated_cnt++; + } + else { + gave_up_on = c; + break; + } + } + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + if (!(c->object.flags & t->flag_within)) + t->depth++; + } + if (annotated_cnt && !list) { + if (debug) + fprintf(stderr, "finished search at %s\n", + sha1_to_hex(c->object.sha1)); + break; } while (parents) { struct commit *p = parents->item; parse_commit(p); - if (!(p->object.flags & SEEN)) { - p->object.flags |= SEEN; + if (!(p->object.flags & SEEN)) insert_by_date(p, &list); - } + p->object.flags |= c->object.flags; parents = parents->next; } } + free_commit_list(list); - if (!all_matches) + if (!match_cnt) die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1)); - min_match = NULL; - for (cur_match = all_matches; cur_match; cur_match = cur_match->next) { - struct rev_info revs; - struct commit *tagged = cur_match->name->commit; - - clear_commit_marks(cmit, -1); - init_revisions(&revs, NULL); - tagged->object.flags |= UNINTERESTING; - add_pending_object(&revs, &tagged->object, NULL); - add_pending_object(&revs, &cmit->object, NULL); - - prepare_revision_walk(&revs); - cur_match->depth = 0; - while ((!min_match || cur_match->depth < min_match->depth) - && get_revision(&revs)) - cur_match->depth++; - if (!min_match || (cur_match->depth < min_match->depth - && cur_match->name->prio >= min_match->name->prio)) - min_match = cur_match; - free_commit_list(revs.commits); + min_match = &all_matches[0]; + for (cur_match = 1; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + if (t->depth < min_match->depth + && t->name->prio >= min_match->name->prio) + min_match = t; + } + if (debug) { + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + fprintf(stderr, " %c %8lu %s\n", + min_match == t ? '*' : ' ', + t->depth, t->name->path); + } + fprintf(stderr, "traversed %lu commits\n", seen_commits); + if (gave_up_on) { + fprintf(stderr, + "more than %i tags found; listed %i most recent\n" + "gave up search at %s\n", + max_candidates, max_candidates, + sha1_to_hex(gave_up_on->object.sha1)); + } } printf("%s-g%s\n", min_match->name->path, find_unique_abbrev(cmit->object.sha1, abbrev)); - if (!last_one) { - for (cur_match = all_matches; cur_match; cur_match = min_match) { - min_match = cur_match->next; - free(cur_match); - } - clear_commit_marks(cmit, SEEN); - } + if (!last_one) + clear_commit_marks(cmit, -1); } int cmd_describe(int argc, const char **argv, const char *prefix) @@ -226,6 +244,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix) if (*arg != '-') break; + else if (!strcmp(arg, "--debug")) + debug = 1; else if (!strcmp(arg, "--all")) all = 1; else if (!strcmp(arg, "--tags")) @@ -235,6 +255,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix) if (abbrev < MINIMUM_ABBREV || 40 < abbrev) abbrev = DEFAULT_ABBREV; } + else if (!strncmp(arg, "--candidates=", 13)) { + max_candidates = strtoul(arg + 13, NULL, 10); + if (max_candidates < 1) + max_candidates = 1; + else if (max_candidates > MAX_TAGS) + max_candidates = MAX_TAGS; + } else usage(describe_usage); } From 5312ab11fbf7bac28b671510ac3734a3e604d9fa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 13 Jan 2007 18:37:32 -0800 Subject: [PATCH 025/195] Add describe test. ... with help from Shawn. Signed-off-by: Junio C Hamano --- t/t6120-describe.sh | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100755 t/t6120-describe.sh diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh new file mode 100755 index 0000000000..3e9edda1ca --- /dev/null +++ b/t/t6120-describe.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='test describe + + B + .--------------o----o----o----x + / / / + o----o----o----o----o----. / + \ A c / + .------------o---o---o + D e +' +. ./test-lib.sh + +check_describe () { + expect="$1" + shift + R=$(git describe "$@") && + test_expect_success "describe $*" ' + case "$R" in + $expect) echo happy ;; + *) echo "Oops - $R is not $expect"; + false ;; + esac + ' +} + +test_expect_success setup ' + + test_tick && + echo one >file && git-add file && git-commit -m initial && + one=$(git-rev-parse HEAD) && + + test_tick && + echo two >file && git-add file && git-commit -m second && + two=$(git-rev-parse HEAD) && + + test_tick && + echo three >file && git-add file && git-commit -m third && + + test_tick && + echo A >file && git-add file && git-commit -m A && + test_tick && + git-tag -a -m A A && + + test_tick && + echo c >file && git-add file && git-commit -m c && + test_tick && + git-tag c && + + git reset --hard $two && + test_tick && + echo B >side && git-add side && git-commit -m B && + test_tick && + git-tag -a -m B B && + + test_tick && + git-merge -m Merged c && + merged=$(git-rev-parse HEAD) && + + git reset --hard $two && + test_tick && + echo D >another && git-add another && git-commit -m D && + test_tick && + git-tag -a -m D D && + + test_tick && + echo DD >another && git commit -a -m another && + + test_tick && + git-tag e && + + test_tick && + echo DDD >another && git commit -a -m "yet another" && + + test_tick && + git-merge -m Merged $merged && + + test_tick && + echo X >file && echo X >side && git-add file side && + git-commit -m x + +' + +check_describe A-* HEAD +check_describe A-* HEAD^ +check_describe D-* HEAD^^ +check_describe A-* HEAD^^2 +check_describe B HEAD^^2^ + +check_describe A-* --tags HEAD +check_describe A-* --tags HEAD^ +check_describe D-* --tags HEAD^^ +check_describe A-* --tags HEAD^^2 +check_describe B --tags HEAD^^2^ + +test_done From cf69fd49ec815780080dc6a4ee237eee5ffe8745 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 04:37:44 -0500 Subject: [PATCH 026/195] Correct priority of lightweight tags in git-describe. We really want to always favor an annotated tag over a lightweight tag when describing a commit. Unfortunately git-describe wasn't doing this as it was favoring the depth attribute of a possible_tag over the priority. Now priority is the highest sort and we only consider a lightweight tag if no annotated tags were identified. Rather than searching for the minimum tag using a simple loop we now sort them using a stable sort algorithm, this way the possible tags display in order if --debug gets used. The stable sort helps to preseve the inherit topology/date order that we obtain during our search loop. This fix allows the tests in t6120-describe.sh to pass. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index 421658d3be..e38c899251 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -22,6 +22,9 @@ static struct commit_name { int prio; /* annotated tag = 2, tag = 1, head = 0 */ char path[FLEX_ARRAY]; /* more */ } **name_array[256]; +static const char *prio_names[] = { + "head", "lightweight", "annotated", +}; static struct commit_name *match(struct commit *cmit) { @@ -118,10 +121,24 @@ static int compare_names(const void *_a, const void *_b) struct possible_tag { struct commit_name *name; - unsigned long depth; + int depth; + int found_order; unsigned flag_within; }; +static int compare_pt(const void *a_, const void *b_) +{ + struct possible_tag *a = (struct possible_tag *)a_; + struct possible_tag *b = (struct possible_tag *)b_; + if (a->name->prio != b->name->prio) + return b->name->prio - a->name->prio; + if (a->depth != b->depth) + return a->depth - b->depth; + if (a->found_order != b->found_order) + return a->found_order - b->found_order; + return 0; +} + static void describe(const char *arg, int last_one) { unsigned char sha1[20]; @@ -129,9 +146,10 @@ static void describe(const char *arg, int last_one) struct commit_list *list; static int initialized = 0; struct commit_name *n; - struct possible_tag all_matches[MAX_TAGS], *min_match; + struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; + int found = 0; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -171,6 +189,7 @@ static void describe(const char *arg, int last_one) t->name = n; t->depth = seen_commits - 1; t->flag_within = 1u << match_cnt; + t->found_order = found++; c->object.flags |= t->flag_within; if (n->prio == 2) annotated_cnt++; @@ -205,18 +224,12 @@ static void describe(const char *arg, int last_one) if (!match_cnt) die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1)); - min_match = &all_matches[0]; - for (cur_match = 1; cur_match < match_cnt; cur_match++) { - struct possible_tag *t = &all_matches[cur_match]; - if (t->depth < min_match->depth - && t->name->prio >= min_match->name->prio) - min_match = t; - } + qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); if (debug) { for (cur_match = 0; cur_match < match_cnt; cur_match++) { struct possible_tag *t = &all_matches[cur_match]; - fprintf(stderr, " %c %8lu %s\n", - min_match == t ? '*' : ' ', + fprintf(stderr, " %-11s %8d %s\n", + prio_names[t->name->prio], t->depth, t->name->path); } fprintf(stderr, "traversed %lu commits\n", seen_commits); @@ -228,7 +241,7 @@ static void describe(const char *arg, int last_one) sha1_to_hex(gave_up_on->object.sha1)); } } - printf("%s-g%s\n", min_match->name->path, + printf("%s-g%s\n", all_matches[0].name->path, find_unique_abbrev(cmit->object.sha1, abbrev)); if (!last_one) From e7eb50347bba54ab52f9740a3bbb6e833bdadf6e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 22:16:55 -0500 Subject: [PATCH 027/195] Remove hash in git-describe in favor of util slot. Currently we don't use the util field of struct commit but we want fast access to the highest priority name that references any given commit object during our matching loop. A really simple approach is to just store the name directly in the util field. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 76 ++++++++-------------------------------------- 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index e38c899251..e7b8f95c81 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -16,60 +16,27 @@ static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; static int max_candidates = 10; -static unsigned int names[256], allocs[256]; -static struct commit_name { - struct commit *commit; +struct commit_name { int prio; /* annotated tag = 2, tag = 1, head = 0 */ char path[FLEX_ARRAY]; /* more */ -} **name_array[256]; +}; static const char *prio_names[] = { "head", "lightweight", "annotated", }; -static struct commit_name *match(struct commit *cmit) -{ - unsigned char level0 = cmit->object.sha1[0]; - struct commit_name **p = name_array[level0]; - unsigned int hi = names[level0]; - unsigned int lo = 0; - - while (lo < hi) { - unsigned int mi = (lo + hi) / 2; - int cmp = hashcmp(p[mi]->commit->object.sha1, - cmit->object.sha1); - if (!cmp) { - while (mi && p[mi - 1]->commit == cmit) - mi--; - return p[mi]; - } - if (cmp > 0) - hi = mi; - else - lo = mi+1; - } - return NULL; -} - static void add_to_known_names(const char *path, struct commit *commit, int prio) { - int idx; - int len = strlen(path)+1; - struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); - unsigned char m = commit->object.sha1[0]; - - name->commit = commit; - name->prio = prio; - memcpy(name->path, path, len); - idx = names[m]; - if (idx >= allocs[m]) { - allocs[m] = (idx + 50) * 3 / 2; - name_array[m] = xrealloc(name_array[m], - allocs[m] * sizeof(*name_array)); + struct commit_name *e = commit->util; + if (!e || e->prio < prio) { + size_t len = strlen(path)+1; + free(e); + e = xmalloc(sizeof(struct commit_name) + len); + e->prio = prio; + memcpy(e->path, path, len); + commit->util = e; } - name_array[m][idx] = name; - names[m] = ++idx; } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) @@ -104,21 +71,6 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void return 0; } -static int compare_names(const void *_a, const void *_b) -{ - struct commit_name *a = *(struct commit_name **)_a; - struct commit_name *b = *(struct commit_name **)_b; - unsigned long a_date = a->commit->date; - unsigned long b_date = b->commit->date; - int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); - - if (cmp) - return cmp; - if (a->prio != b->prio) - return b->prio - a->prio; - return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; -} - struct possible_tag { struct commit_name *name; int depth; @@ -158,15 +110,11 @@ static void describe(const char *arg, int last_one) die("%s is not a valid '%s' object", arg, commit_type); if (!initialized) { - unsigned int m; initialized = 1; for_each_ref(get_name, NULL); - for (m = 0; m < ARRAY_SIZE(name_array); m++) - qsort(name_array[m], names[m], - sizeof(*name_array[m]), compare_names); } - n = match(cmit); + n = cmit->util; if (n) { printf("%s\n", n->path); return; @@ -182,7 +130,7 @@ static void describe(const char *arg, int last_one) struct commit *c = pop_commit(&list); struct commit_list *parents = c->parents; seen_commits++; - n = match(c); + n = c->util; if (n) { if (match_cnt < max_candidates) { struct possible_tag *t = &all_matches[match_cnt++]; From 5fe3acc43ddb30c61f0adc922f1f08ccc117d2c1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 14 Jan 2007 21:31:30 -0800 Subject: [PATCH 028/195] Documentation: merge-output is not too verbose now. We've squelched output from merge-recursive, and git-merge when used with recursive does not attempt the trivial one first anymore, so there won't be "Trying ... Nope." messages now. Signed-off-by: Junio C Hamano --- Documentation/core-tutorial.txt | 9 ++------- Documentation/git-reset.txt | 4 ---- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 51dd6c6164..7317489cfc 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -906,18 +906,13 @@ of it as it can automatically (which in this case is just merge the `example` file, which had no differences in the `mybranch` branch), and say: ---------------- - Trying really trivial in-index merge... - fatal: Merge requires file-level merging - Nope. - ... Auto-merging hello CONFLICT (content): Merge conflict in hello Automatic merge failed; fix up by hand ---------------- -which is way too verbose, but it basically tells you that it failed the -really trivial merge ("Simple merge") and did an "Automatic merge" -instead, but that too failed due to conflicts in `hello`. +It tells you that it did an "Automatic merge", which +failed due to conflicts in `hello`. Not to worry. It left the (trivial) conflict in `hello` in the same form you should already be well used to if you've ever used CVS, so let's just diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 4f424782eb..04475a9216 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -121,10 +121,6 @@ Undo a merge or pull:: + ------------ $ git pull <1> -Trying really trivial in-index merge... -fatal: Merge requires file-level merging -Nope. -... Auto-merging nitfol CONFLICT (content): Merge conflict in nitfol Automatic merge failed/prevented; fix up by hand From acb4441e0dabaaa666ecae11fdafc27c3691e763 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 14 Jan 2007 22:00:02 -0800 Subject: [PATCH 029/195] Use merge-recursive in git-revert/git-cherry-pick This makes revert and cherry-pick to use merge-recursive, to allow them to notice renames. A pair of test scripts demonstrate that an old change before a rename happened can be applied (reverted) after a rename with cherry-pick (with revert). Signed-off-by: Junio C Hamano --- git-revert.sh | 29 +++++++--------- t/t3501-revert-cherry-pick.sh | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) create mode 100755 t/t3501-revert-cherry-pick.sh diff --git a/git-revert.sh b/git-revert.sh index 71cbcbc2b8..c74af99a51 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -151,32 +151,27 @@ esac >.msg # and $prev on top of us (when reverting), or the change between # $prev and $commit on top of us (when cherry-picking or replaying). -echo >&2 "First trying simple merge strategy to $me." -git-read-tree -m -u --aggressive $base $head $next && +git-merge-recursive $base -- $head $next && result=$(git-write-tree 2>/dev/null) || { - echo >&2 "Simple $me fails; trying Automatic $me." - git-merge-index -o git-merge-one-file -a || { - mv -f .msg "$GIT_DIR/MERGE_MSG" - { - echo ' + mv -f .msg "$GIT_DIR/MERGE_MSG" + { + echo ' Conflicts: ' git ls-files --unmerged | sed -e 's/^[^ ]* / /' | uniq - } >>"$GIT_DIR/MERGE_MSG" - echo >&2 "Automatic $me failed. After resolving the conflicts," - echo >&2 "mark the corrected paths with 'git-add '" - echo >&2 "and commit the result." - case "$me" in - cherry-pick) + } >>"$GIT_DIR/MERGE_MSG" + echo >&2 "Automatic $me failed. After resolving the conflicts," + echo >&2 "mark the corrected paths with 'git-add '" + echo >&2 "and commit the result." + case "$me" in + cherry-pick) echo >&2 "You may choose to use the following when making" echo >&2 "the commit:" echo >&2 "$set_author_env" - esac - exit 1 - } - result=$(git-write-tree) || exit + esac + exit 1 } echo >&2 "Finished one $me." diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh new file mode 100755 index 0000000000..552af1c4d2 --- /dev/null +++ b/t/t3501-revert-cherry-pick.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +test_description='test cherry-pick and revert with renames + + -- + + rename2: renames oops to opos + + rename1: renames oops to spoo + + added: adds extra line to oops + ++ initial: has lines in oops + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for l in a b c d e f g h i j k l m n o + do + echo $l$l$l$l$l$l$l$l$l + done >oops && + + test_tick && + git add oops && + git commit -m initial && + git tag initial && + + test_tick && + echo "Add extra line at the end" >>oops && + git commit -a -m added && + git tag added && + + test_tick && + git mv oops spoo && + git commit -m rename1 && + git tag rename1 && + + test_tick && + git checkout -b side initial && + git mv oops opos && + git commit -m rename2 && + git tag rename2 +' + +test_expect_success 'cherry-pick after renaming branch' ' + + git checkout rename2 && + EDITOR=: VISUAL=: git cherry-pick added && + test -f opos && + grep "Add extra line at the end" opos + +' + +test_expect_success 'revert after renaming branch' ' + + git checkout rename1 && + EDITOR=: VISUAL=: git revert added && + test -f spoo && + ! grep "Add extra line at the end" spoo + +' + +test_done From 6e2931a8ed887bd75ed68085a7e04bc88574d69e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 15 Jan 2007 01:41:22 -0500 Subject: [PATCH 030/195] Use nice names in conflict markers during cherry-pick/revert. Always call the current HEAD 'HEAD', and name the patch being cherry-picked or reverted by its oneline subject rather than its SHA1. This matches git am's behavior and is done because users most commonly are cherry-picking by SHA1 rather than by ref name. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- git-revert.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/git-revert.sh b/git-revert.sh index c74af99a51..bb8f1ca24a 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -146,6 +146,12 @@ cherry-pick) esac >.msg +eval GITHEAD_$head=HEAD +eval GITHEAD_$next='`git show -s \ + --pretty=oneline --encoding="$encoding" "$commit" | + sed -e "s/^[^ ]* //"`' +export GITHEAD_$head GITHEAD_$next + # This three way merge is an interesting one. We are at # $head, and would want to apply the change between $commit # and $prev on top of us (when reverting), or the change between From 15261e3b33de3b49cc0a7d1b36b8685e02931ad7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 15 Jan 2007 14:43:03 -0800 Subject: [PATCH 031/195] git reflog expire: document --stale-fix option. Signed-off-by: Junio C Hamano --- Documentation/git-reflog.txt | 2 +- builtin-reflog.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 55a24d3266..1138865896 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -9,7 +9,7 @@ git-reflog - Manage reflog information SYNOPSIS -------- [verse] -'git-reflog' expire [--dry-run] +'git-reflog' expire [--dry-run] [--stale-fix] [--expire=