From b74e8cbd80c13257c8c0a68341e9aff9926d31f2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 15 Jan 2007 22:56:34 -0800 Subject: [PATCH 01/68] git-fetch: split fetch_main into fetch_dumb and fetch_native Signed-off-by: Junio C Hamano --- git-fetch.sh | 174 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 69 deletions(-) diff --git a/git-fetch.sh b/git-fetch.sh index ca984e739a..3e01265160 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -268,7 +268,101 @@ then fi fi -fetch_main () { +fetch_native () { + reflist="$1" + refs= + rref= + + for ref in $reflist + do + refs="$refs$LF$ref" + + # These are relative path from $GIT_DIR, typically starting at refs/ + # but may be HEAD + if expr "z$ref" : 'z\.' >/dev/null + then + not_for_merge=t + ref=$(expr "z$ref" : 'z\.\(.*\)') + else + not_for_merge= + fi + if expr "z$ref" : 'z+' >/dev/null + then + single_force=t + ref=$(expr "z$ref" : 'z+\(.*\)') + else + single_force= + fi + remote_name=$(expr "z$ref" : 'z\([^:]*\):') + local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)') + + rref="$rref$LF$remote_name" + done + + ( : subshell because we muck with IFS + IFS=" $LF" + ( + git-fetch-pack --thin $exec $keep $shallow_depth "$remote" $rref || + echo failed "$remote" + ) | + ( + trap ' + if test -n "$keepfile" && test -f "$keepfile" + then + rm -f "$keepfile" + fi + ' 0 + + keepfile= + while read sha1 remote_name + do + case "$sha1" in + failed) + echo >&2 "Fetch failure: $remote" + exit 1 ;; + # special line coming from index-pack with the pack name + pack) + continue ;; + keep) + keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" + continue ;; + esac + found= + single_force= + for ref in $refs + do + case "$ref" in + +$remote_name:*) + single_force=t + not_for_merge= + found="$ref" + break ;; + .+$remote_name:*) + single_force=t + not_for_merge=t + found="$ref" + break ;; + .$remote_name:*) + not_for_merge=t + found="$ref" + break ;; + $remote_name:*) + not_for_merge= + found="$ref" + break ;; + esac + done + local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') + append_fetch_head "$sha1" "$remote" \ + "$remote_name" "$remote_nick" "$local_name" \ + "$not_for_merge" || exit + done + ) + ) || exit + +} + +fetch_dumb () { reflist="$1" refs= rref= @@ -360,9 +454,6 @@ fetch_main () { rsync_slurped_objects=t } ;; - *) - # We will do git native transport with just one call later. - continue ;; esac append_fetch_head "$head" "$remote" \ @@ -370,72 +461,17 @@ fetch_main () { done - case "$remote" in - http://* | https://* | ftp://* | rsync://* ) - ;; # we are already done. - *) - ( : subshell because we muck with IFS - IFS=" $LF" - ( - git-fetch-pack --thin $exec $keep $shallow_depth "$remote" $rref || - echo failed "$remote" - ) | - ( - trap ' - if test -n "$keepfile" && test -f "$keepfile" - then - rm -f "$keepfile" - fi - ' 0 - - keepfile= - while read sha1 remote_name - do - case "$sha1" in - failed) - echo >&2 "Fetch failure: $remote" - exit 1 ;; - # special line coming from index-pack with the pack name - pack) - continue ;; - keep) - keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" - continue ;; - esac - found= - single_force= - for ref in $refs - do - case "$ref" in - +$remote_name:*) - single_force=t - not_for_merge= - found="$ref" - break ;; - .+$remote_name:*) - single_force=t - not_for_merge=t - found="$ref" - break ;; - .$remote_name:*) - not_for_merge=t - found="$ref" - break ;; - $remote_name:*) - not_for_merge= - found="$ref" - break ;; - esac - done - local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') - append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" \ - "$not_for_merge" || exit - done - ) - ) || exit ;; - esac +} +fetch_main () { + case "$remote" in + http://* | https://* | ftp://* | rsync://* ) + fetch_dumb "$@" + ;; + *) + fetch_native "$@" + ;; + esac } fetch_main "$reflist" || exit From d4289fff870a85b1b7e55ead2a5baf98847fc72a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Jan 2007 00:23:24 -0800 Subject: [PATCH 02/68] git-fetch--tool: start rewriting parts of git-fetch in C. Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-fetch--tool.c | 214 ++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git-fetch.sh | 134 ++------------------------ git.c | 1 + 5 files changed, 227 insertions(+), 124 deletions(-) create mode 100644 builtin-fetch--tool.c diff --git a/Makefile b/Makefile index 40bdcff696..181ad94279 100644 --- a/Makefile +++ b/Makefile @@ -282,6 +282,7 @@ BUILTIN_OBJS = \ builtin-diff-index.o \ builtin-diff-stages.o \ builtin-diff-tree.o \ + builtin-fetch--tool.o \ builtin-fmt-merge-msg.o \ builtin-for-each-ref.o \ builtin-fsck.o \ diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c new file mode 100644 index 0000000000..3075ba04af --- /dev/null +++ b/builtin-fetch--tool.c @@ -0,0 +1,214 @@ +#include "cache.h" +#include "refs.h" +#include "commit.h" + +static void show_new(char *type, unsigned char *sha1_new) +{ + fprintf(stderr, " %s: %s\n", type, + find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); +} + +static int update_ref(const char *action, + const char *refname, + unsigned char *sha1, + unsigned char *oldval) +{ + int len; + char msg[1024]; + char *rla = getenv("GIT_REFLOG_ACTION"); + static struct ref_lock *lock; + + if (!rla) + rla = "(reflog update)"; + len = snprintf(msg, sizeof(msg), "%s: %s", rla, action); + if (sizeof(msg) <= len) + die("insanely long action"); + lock = lock_any_ref_for_update(refname, oldval); + if (!lock) + return 1; + if (write_ref_sha1(lock, sha1, msg) < 0) + return 1; + return 0; +} + +static int update_local_ref(const char *name, + const char *new_head, + const char *note, + int verbose, int force) +{ + char type[20]; + unsigned char sha1_old[20], sha1_new[20]; + char oldh[41], newh[41]; + struct commit *current, *updated; + + if (get_sha1_hex(new_head, sha1_new)) + die("malformed object name %s", new_head); + if (sha1_object_info(sha1_new, type, NULL)) + die("object %s not found", new_head); + + if (!*name) { + /* Not storing */ + if (verbose) { + fprintf(stderr, "* fetched %s\n", note); + show_new(type, sha1_new); + } + return 0; + } + + if (get_sha1(name, sha1_old)) { + char *msg; + just_store: + /* new ref */ + if (!strncmp(name, "refs/tags/", 10)) + msg = "storing tag"; + else + msg = "storing head"; + fprintf(stderr, "* %s: storing %s\n", + name, note); + show_new(type, sha1_new); + return update_ref(msg, name, sha1_new, NULL); + } + + if (!hashcmp(sha1_old, sha1_new)) { + if (verbose) { + fprintf(stderr, "* %s: same as %s\n", name, note); + show_new(type, sha1_new); + } + return 0; + } + + if (!strncmp(name, "refs/tags/", 10)) { + fprintf(stderr, "* %s: updating with %s\n", name, note); + show_new(type, sha1_new); + return update_ref("updating tag", name, sha1_new, NULL); + } + + current = lookup_commit_reference(sha1_old); + updated = lookup_commit_reference(sha1_new); + if (!current || !updated) + goto just_store; + + strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); + + if (in_merge_bases(current, &updated, 1)) { + fprintf(stderr, "* %s: fast forward to %s\n", + name, note); + fprintf(stderr, " old..new: %s..%s\n", oldh, newh); + return update_ref("fast forward", name, sha1_new, sha1_old); + } + if (!force) { + fprintf(stderr, + "* %s: not updating to non-fast forward %s\n", + name, note); + fprintf(stderr, + " old...new: %s...%s\n", oldh, newh); + return 1; + } + fprintf(stderr, + "* %s: forcing update to non-fast forward %s\n", + name, note); + fprintf(stderr, " old...new: %s...%s\n", oldh, newh); + return update_ref("forced-update", name, sha1_new, sha1_old); +} + +static int append_fetch_head(FILE *fp, + const char *head, const char *remote, + const char *remote_name, const char *remote_nick, + const char *local_name, int not_for_merge, + int verbose, int force) +{ + struct commit *commit; + int remote_len, i, note_len; + unsigned char sha1[20]; + char note[1024]; + const char *what, *kind; + + if (get_sha1(head, sha1)) + return error("Not a valid object name: %s", head); + commit = lookup_commit_reference(sha1); + if (!commit) + not_for_merge = 1; + + if (!strcmp(remote_name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!strncmp(remote_name, "refs/heads/", 11)) { + kind = "branch"; + what = remote_name + 11; + } + else if (!strncmp(remote_name, "refs/tags/", 10)) { + kind = "tag"; + what = remote_name + 10; + } + else if (!strncmp(remote_name, "refs/remotes/", 13)) { + kind = "remote branch"; + what = remote_name + 13; + } + else { + kind = ""; + what = remote_name; + } + + remote_len = strlen(remote); + for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--) + ; + remote_len = i + 1; + if (4 < i && !strncmp(".git", remote + i - 3, 4)) + remote_len = i - 3; + note_len = sprintf(note, "%s\t%s\t", + sha1_to_hex(commit ? commit->object.sha1 : sha1), + not_for_merge ? "not-for-merge" : ""); + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", remote_len, remote); + fprintf(fp, "%s\n", note); + return update_local_ref(local_name, head, note, verbose, force); +} + +int cmd_fetch__tool(int argc, const char **argv, const char *prefix) +{ + int verbose = 0; + int force = 0; + + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp("-v", arg)) + verbose = 1; + else if (!strcmp("-f", arg)) + force = 1; + else + break; + argc--; + argv++; + } + + if (argc <= 1) + return error("Missing subcommand"); + + if (!strcmp("append-fetch-head", argv[1])) { + int result; + FILE *fp; + + if (argc != 8) + return error("append-fetch-head takes 6 args"); + fp = fopen(git_path("FETCH_HEAD"), "a"); + result = append_fetch_head(fp, argv[2], argv[3], + argv[4], argv[5], + argv[6], !!argv[7][0], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("update-local-ref", argv[1])) { + if (argc != 5) + return error("update-local-ref takes 3 args"); + return update_local_ref(argv[2], argv[3], argv[4], + verbose, force); + } + return error("Unknown subcommand: %s", argv[1]); +} diff --git a/builtin.h b/builtin.h index 5108fd2d74..3cad4028d2 100644 --- a/builtin.h +++ b/builtin.h @@ -31,6 +31,7 @@ extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); diff --git a/git-fetch.sh b/git-fetch.sh index 3e01265160..2aa34b3992 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -107,133 +107,19 @@ ls_remote_result=$(git ls-remote $exec "$remote") || die "Cannot get the repository state from $remote" append_fetch_head () { - head_="$1" - remote_="$2" - remote_name_="$3" - remote_nick_="$4" - local_name_="$5" - case "$6" in - t) not_for_merge_='not-for-merge' ;; - '') not_for_merge_= ;; - esac - - # remote-nick is the URL given on the command line (or a shorthand) - # remote-name is the $GIT_DIR relative refs/ path we computed - # for this refspec. - - # the $note_ variable will be fed to git-fmt-merge-msg for further - # processing. - case "$remote_name_" in - HEAD) - note_= ;; - refs/heads/*) - note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')" - note_="branch '$note_' of " ;; - refs/tags/*) - note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')" - note_="tag '$note_' of " ;; - refs/remotes/*) - note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')" - note_="remote branch '$note_' of " ;; - *) - note_="$remote_name of " ;; - esac - remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') && - remote_="$remote_1_" - note_="$note_$remote_" - - # 2.6.11-tree tag would not be happy to be fed to resolve. - if git-cat-file commit "$head_" >/dev/null 2>&1 - then - headc_=$(git-rev-parse --verify "$head_^0") || exit - echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD" - else - echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD" - fi - - update_local_ref "$local_name_" "$head_" "$note_" + flags= + test -n "$verbose" && flags="$flags -v" + test -n "$force" && flags="$flags -f" + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ + git-fetch--tool append-fetch-head $flags "$@" } update_local_ref () { - # If we are storing the head locally make sure that it is - # a fast forward (aka "reverse push"). - - label_=$(git-cat-file -t $2) - newshort_=$(git-rev-parse --short $2) - if test -z "$1" ; then - [ "$verbose" ] && echo >&2 "* fetched $3" - [ "$verbose" ] && echo >&2 " $label_: $newshort_" - return 0 - fi - oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null) - - case "$1" in - refs/tags/*) - # Tags need not be pointing at commits so there - # is no way to guarantee "fast-forward" anyway. - if test -n "$oldshort_" - then - if now_=$(git show-ref --hash "$1") && test "$now_" = "$2" - then - [ "$verbose" ] && echo >&2 "* $1: same as $3" - [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||: - else - echo >&2 "* $1: updating with $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: updating tag" "$1" "$2" - fi - else - echo >&2 "* $1: storing $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: storing tag" "$1" "$2" - fi - ;; - - refs/heads/* | refs/remotes/*) - # $1 is the ref being updated. - # $2 is the new value for the ref. - local=$(git-rev-parse --verify "$1^0" 2>/dev/null) - if test "$local" - then - # Require fast-forward. - mb=$(git-merge-base "$local" "$2") && - case "$2,$mb" in - $local,*) - if test -n "$verbose" - then - echo >&2 "* $1: same as $3" - echo >&2 " $label_: $newshort_" - fi - ;; - *,$local) - echo >&2 "* $1: fast forward to $3" - echo >&2 " old..new: $oldshort_..$newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: fast-forward" "$1" "$2" "$local" - ;; - *) - false - ;; - esac || { - case ",$force,$single_force," in - *,t,*) - echo >&2 "* $1: forcing update to non-fast forward $3" - echo >&2 " old...new: $oldshort_...$newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: forced-update" "$1" "$2" "$local" - ;; - *) - echo >&2 "* $1: not updating to non-fast forward $3" - echo >&2 " old...new: $oldshort_...$newshort_" - exit 1 - ;; - esac - } - else - echo >&2 "* $1: storing $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: storing head" "$1" "$2" - fi - ;; - esac + flags= + test -n "$verbose" && flags="$flags -v" + test -n "$force" && flags="$flags -f" + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ + git-fetch--tool update-local-ref $flags "$@" } # updating the current HEAD with git-fetch in a bare diff --git a/git.c b/git.c index 45265f14d0..a167b1e42e 100644 --- a/git.c +++ b/git.c @@ -242,6 +242,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-stages", cmd_diff_stages, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, From fbe2687eba70385dab7e3d1f5cdcdfdc11dfe0ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Jan 2007 01:53:29 -0800 Subject: [PATCH 03/68] git-fetch: move more code into C. This adds "native-store" subcommand to git-fetch--tool to move a huge loop implemented in shell into C. This shaves about 70% of the runtime to fetch and update 1000 tracking branches with a single fetch. Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 125 ++++++++++++++++++++++++++++++++++++++++++ git-fetch.sh | 57 ++----------------- 2 files changed, 131 insertions(+), 51 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 3075ba04af..24343ac9b0 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -170,6 +170,119 @@ static int append_fetch_head(FILE *fp, return update_local_ref(local_name, head, note, verbose, force); } +static char *keep; +static void remove_keep(void) +{ + if (keep && *keep) + unlink(keep); +} + +static void remove_keep_on_signal(int signo) +{ + remove_keep(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static char *find_local_name(const char *remote_name, const char *refs, + int *force_p, int *not_for_merge_p) +{ + const char *ref = refs; + int len = strlen(remote_name); + + while (ref) { + const char *next; + int single_force, not_for_merge; + + while (*ref == '\n') + ref++; + if (!*ref) + break; + next = strchr(ref, '\n'); + + single_force = not_for_merge = 0; + if (*ref == '+') { + single_force = 1; + ref++; + } + if (*ref == '.') { + not_for_merge = 1; + ref++; + if (*ref == '+') { + single_force = 1; + ref++; + } + } + if (!strncmp(remote_name, ref, len) && ref[len] == ':') { + const char *local_part = ref + len + 1; + char *ret; + int retlen; + + if (!next) + retlen = strlen(local_part); + else + retlen = next - local_part; + ret = xmalloc(retlen + 1); + memcpy(ret, local_part, retlen); + ret[retlen] = 0; + *force_p = single_force; + *not_for_merge_p = not_for_merge; + return ret; + } + ref = next; + } + return NULL; +} + +static int fetch_native_store(FILE *fp, + const char *remote, + const char *remote_nick, + const char *refs, + int verbose, int force) +{ + char buffer[1024]; + int err = 0; + + signal(SIGINT, remove_keep_on_signal); + atexit(remove_keep); + + while (fgets(buffer, sizeof(buffer), stdin)) { + int len; + char *cp; + char *local_name; + int single_force, not_for_merge; + + for (cp = buffer; *cp && !isspace(*cp); cp++) + ; + if (*cp) + *cp++ = 0; + len = strlen(cp); + if (len && cp[len-1] == '\n') + cp[--len] = 0; + if (!strcmp(buffer, "failed")) + die("Fetch failure: %s", remote); + if (!strcmp(buffer, "pack")) + continue; + if (!strcmp(buffer, "keep")) { + char *od = get_object_directory(); + int len = strlen(od) + strlen(cp) + 50; + keep = xmalloc(len); + sprintf(keep, "%s/pack/pack-%s.keep", od, cp); + continue; + } + + local_name = find_local_name(cp, refs, + &single_force, ¬_for_merge); + if (!local_name) + continue; + err |= append_fetch_head(fp, + buffer, remote, cp, remote_nick, + local_name, not_for_merge, + verbose, force || single_force); + } + return err; +} + int cmd_fetch__tool(int argc, const char **argv, const char *prefix) { int verbose = 0; @@ -210,5 +323,17 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) return update_local_ref(argv[2], argv[3], argv[4], verbose, force); } + if (!strcmp("native-store", argv[1])) { + int result; + FILE *fp; + + if (argc != 5) + return error("fetch-native-store takes 3 args"); + fp = fopen(git_path("FETCH_HEAD"), "a"); + result = fetch_native_store(fp, argv[2], argv[3], argv[4], + verbose, force); + fclose(fp); + return result; + } return error("Unknown subcommand: %s", argv[1]); } diff --git a/git-fetch.sh b/git-fetch.sh index 2aa34b3992..b74dd9a309 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -192,57 +192,12 @@ fetch_native () { echo failed "$remote" ) | ( - trap ' - if test -n "$keepfile" && test -f "$keepfile" - then - rm -f "$keepfile" - fi - ' 0 - - keepfile= - while read sha1 remote_name - do - case "$sha1" in - failed) - echo >&2 "Fetch failure: $remote" - exit 1 ;; - # special line coming from index-pack with the pack name - pack) - continue ;; - keep) - keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" - continue ;; - esac - found= - single_force= - for ref in $refs - do - case "$ref" in - +$remote_name:*) - single_force=t - not_for_merge= - found="$ref" - break ;; - .+$remote_name:*) - single_force=t - not_for_merge=t - found="$ref" - break ;; - .$remote_name:*) - not_for_merge=t - found="$ref" - break ;; - $remote_name:*) - not_for_merge= - found="$ref" - break ;; - esac - done - local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') - append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" \ - "$not_for_merge" || exit - done + flags= + test -n "$verbose" && flags="$flags -v" + test -n "$force" && flags="$flags -f" + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ + git-fetch--tool native-store \ + $flags "$remote" "$remote_nick" "$refs" ) ) || exit From d1e0ef6cc89e5ef2f914c37719b9c2327e534834 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Jan 2007 02:31:36 -0800 Subject: [PATCH 04/68] git-fetch: rewrite another shell loop in C Move another shell loop that canonicalizes the list of refs for underlying git-fetch-pack and fetch-native-store into C. This seems to shave the runtime for the same 1000 branch repository from 30 seconds down to 15 seconds (it used to be 2 and half minutes with the original version). Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 46 +++++++++++++++++++++++++++++++++++++++++++ git-fetch.sh | 30 ++-------------------------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 24343ac9b0..705a6649a9 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -283,6 +283,46 @@ static int fetch_native_store(FILE *fp, return err; } +static int parse_reflist(const char *reflist) +{ + const char *ref; + + printf("refs='"); + for (ref = reflist; ref; ) { + const char *next; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + printf("\n%.*s", (int)(next - ref), ref); + ref = next; + } + printf("'\n"); + + printf("rref='"); + for (ref = reflist; ref; ) { + const char *next, *colon; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + if (*ref == '.') + ref++; + if (*ref == '+') + ref++; + colon = strchr(ref, ':'); + putchar('\n'); + printf("%.*s", (int)((colon ? colon : next) - ref), ref); + ref = next; + } + printf("'\n"); + return 0; +} + int cmd_fetch__tool(int argc, const char **argv, const char *prefix) { int verbose = 0; @@ -335,5 +375,11 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) fclose(fp); return result; } + if (!strcmp("parse-reflist", argv[1])) { + if (argc != 3) + return error("parse-reflist takes 1 arg"); + return parse_reflist(argv[2]); + } + return error("Unknown subcommand: %s", argv[1]); } diff --git a/git-fetch.sh b/git-fetch.sh index b74dd9a309..3bed4091a3 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -155,35 +155,9 @@ then fi fetch_native () { - reflist="$1" - refs= - rref= - for ref in $reflist - do - refs="$refs$LF$ref" - - # These are relative path from $GIT_DIR, typically starting at refs/ - # but may be HEAD - if expr "z$ref" : 'z\.' >/dev/null - then - not_for_merge=t - ref=$(expr "z$ref" : 'z\.\(.*\)') - else - not_for_merge= - fi - if expr "z$ref" : 'z+' >/dev/null - then - single_force=t - ref=$(expr "z$ref" : 'z+\(.*\)') - else - single_force= - fi - remote_name=$(expr "z$ref" : 'z\([^:]*\):') - local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)') - - rref="$rref$LF$remote_name" - done + eval=$(git-fetch--tool parse-reflist "$1") + eval "$eval" ( : subshell because we muck with IFS IFS=" $LF" From 86551586da8cba6c06ac04783a656843a4e47f35 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 16 Jan 2007 13:43:28 -0800 Subject: [PATCH 05/68] git-fetch: rewrite expand_ref_wildcard in C This does not seem to make measurable improvement when dealing with 1000 unpacked refs, but we would need something like it if we were to do a full rewrite in C somedaoy. Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 90 +++++++++++++++++++++++++++++++++++++++++++ git-parse-remote.sh | 46 +--------------------- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 705a6649a9..3090ffea20 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -323,6 +323,91 @@ static int parse_reflist(const char *reflist) return 0; } +static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, + const char **refs) +{ + int i, matchlen, replacelen; + int found_one = 0; + const char *remote = *refs++; + numrefs--; + + if (numrefs == 0) { + fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n", + remote); + printf("empty\n"); + } + + for (i = 0; i < numrefs; i++) { + const char *ref = refs[i]; + const char *lref = ref; + const char *colon; + const char *tail; + const char *ls; + const char *next; + + if (*lref == '+') + lref++; + colon = strchr(lref, ':'); + tail = lref + strlen(lref); + if (!(colon && + 2 < colon - lref && + colon[-1] == '*' && + colon[-2] == '/' && + 2 < tail - (colon + 1) && + tail[-1] == '*' && + tail[-2] == '/')) { + /* not a glob */ + if (!found_one++) + printf("explicit\n"); + printf("%s\n", ref); + continue; + } + + /* glob */ + if (!found_one++) + printf("glob\n"); + + /* lref to colon-2 is remote hierarchy name; + * colon+1 to tail-2 is local. + */ + matchlen = (colon-1) - lref; + replacelen = (tail-1) - (colon+1); + for (ls = ls_remote_result; ls; ls = next) { + const char *eol; + unsigned char sha1[20]; + int namelen; + + while (*ls && isspace(*ls)) + ls++; + next = strchr(ls, '\n'); + eol = !next ? (ls + strlen(ls)) : next; + if (!memcmp("^{}", eol-3, 3)) + continue; + if (get_sha1_hex(ls, sha1)) + continue; + ls += 40; + while (ls < eol && isspace(*ls)) + ls++; + /* ls to next (or eol) is the name. + * is it identical to lref to colon-2? + */ + if ((eol - ls) <= matchlen || + strncmp(ls, lref, matchlen)) + continue; + + /* Yes, it is a match */ + namelen = eol - ls; + if (lref != ref) + putchar('+'); + printf("%.*s:%.*s%.*s\n", + namelen, ls, + replacelen, colon + 1, + namelen - matchlen, ls + matchlen); + } + } + return 0; +} + int cmd_fetch__tool(int argc, const char **argv, const char *prefix) { int verbose = 0; @@ -380,6 +465,11 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) return error("parse-reflist takes 1 arg"); return parse_reflist(argv[2]); } + if (!strcmp("expand-refs-wildcard", argv[1])) { + if (argc < 4) + return error("expand-refs-wildcard takes at least 2 args"); + return expand_refs_wildcard(argv[2], argc - 3, argv + 3); + } return error("Unknown subcommand: %s", argv[1]); } diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5208ee6ce0..9b19a21667 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -81,51 +81,7 @@ get_remote_default_refs_for_push () { # is to help prevent randomly "globbed" ref from being chosen as # a merge candidate expand_refs_wildcard () { - remote="$1" - shift - first_one=yes - if test "$#" = 0 - then - echo empty - echo >&2 "Nothing specified for fetching with remote.$remote.fetch" - fi - for ref - do - lref=${ref#'+'} - # a non glob pattern is given back as-is. - expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { - if test -n "$first_one" - then - echo "explicit" - first_one= - fi - echo "$ref" - continue - } - - # glob - if test -n "$first_one" - then - echo "glob" - first_one= - fi - from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` - to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` - local_force= - test "z$lref" = "z$ref" || local_force='+' - echo "$ls_remote_result" | - sed -e '/\^{}$/d' | - ( - IFS=' ' - while read sha1 name - do - # ignore the ones that do not start with $from - mapped=${name#"$from"} - test "z$name" = "z$mapped" && continue - echo "${local_force}${name}:${to}${mapped}" - done - ) - done + git fetch--tool expand-refs-wildcard "$ls_remote_result" "$@" } # Subroutine to canonicalize remote:local notation. From 46ce8b6d2a88b67a839fb53bfa0b8849215352b5 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Tue, 13 Feb 2007 01:21:39 +0000 Subject: [PATCH 06/68] Allow fetch--tool to read from stdin If the reflist is "-" then read the reflist data from stdin instead, this will allow the passing of more than 128K of reflist data - which won't fit in the environment passed by execve. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 3090ffea20..48de08d858 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -2,6 +2,21 @@ #include "refs.h" #include "commit.h" +#define CHUNK_SIZE (1048576) + +static char *get_stdin(void) +{ + char *data = xmalloc(CHUNK_SIZE); + int offset = 0, read = 0; + read = xread(0, data, CHUNK_SIZE); + while (read == CHUNK_SIZE) { + offset += CHUNK_SIZE; + data = xrealloc(data, offset + CHUNK_SIZE); + read = xread(0, data + offset, CHUNK_SIZE); + } + return data; +} + static void show_new(char *type, unsigned char *sha1_new) { fprintf(stderr, " %s: %s\n", type, @@ -461,14 +476,22 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) return result; } if (!strcmp("parse-reflist", argv[1])) { + const char *reflist; if (argc != 3) return error("parse-reflist takes 1 arg"); - return parse_reflist(argv[2]); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return parse_reflist(reflist); } if (!strcmp("expand-refs-wildcard", argv[1])) { + const char *reflist; if (argc < 4) return error("expand-refs-wildcard takes at least 2 args"); - return expand_refs_wildcard(argv[2], argc - 3, argv + 3); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return expand_refs_wildcard(reflist, argc - 3, argv + 3); } return error("Unknown subcommand: %s", argv[1]); From 95339912b97279c29bd842fe036c70fca33d0d66 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Tue, 13 Feb 2007 01:21:40 +0000 Subject: [PATCH 07/68] Use stdin reflist passing in parse-remote Use the new stdin reflist passing mechanism for the call to fetch--tool expand-refs-wildcard, allowing passing of more than ~128K of reflist data. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- git-parse-remote.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 9b19a21667..c46131f6d6 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -81,7 +81,8 @@ get_remote_default_refs_for_push () { # is to help prevent randomly "globbed" ref from being chosen as # a merge candidate expand_refs_wildcard () { - git fetch--tool expand-refs-wildcard "$ls_remote_result" "$@" + echo "$ls_remote_result" | + git fetch--tool expand-refs-wildcard "-" "$@" } # Subroutine to canonicalize remote:local notation. From 617669da4f80ecf05db7ef1976b56260e2124cc2 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Tue, 13 Feb 2007 01:21:41 +0000 Subject: [PATCH 08/68] Use stdin reflist passing in git-fetch.sh Use the new stdin reflist passing mechanism for the call to fetch--tool parse-reflist, allowing passing of more than ~128K of reflist data. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- git-fetch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-fetch.sh b/git-fetch.sh index 3bed4091a3..80f63c85f0 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -156,7 +156,7 @@ fi fetch_native () { - eval=$(git-fetch--tool parse-reflist "$1") + eval=$(echo "$1" | git-fetch--tool parse-reflist "-") eval "$eval" ( : subshell because we muck with IFS From fee7c2c71d9e35b2f54aa3631072bd7f73bb7b4c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Feb 2007 04:35:31 -0800 Subject: [PATCH 09/68] git-fetch--tool takes flags before the subcommand. Signed-off-by: Junio C Hamano --- git-fetch.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-fetch.sh b/git-fetch.sh index 80f63c85f0..f875e0f99e 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -111,7 +111,7 @@ append_fetch_head () { test -n "$verbose" && flags="$flags -v" test -n "$force" && flags="$flags -f" GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ - git-fetch--tool append-fetch-head $flags "$@" + git-fetch--tool $flags append-fetch-head "$@" } update_local_ref () { @@ -119,7 +119,7 @@ update_local_ref () { test -n "$verbose" && flags="$flags -v" test -n "$force" && flags="$flags -f" GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ - git-fetch--tool update-local-ref $flags "$@" + git-fetch--tool $flags update-local-ref "$@" } # updating the current HEAD with git-fetch in a bare @@ -170,8 +170,8 @@ fetch_native () { test -n "$verbose" && flags="$flags -v" test -n "$force" && flags="$flags -f" GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ - git-fetch--tool native-store \ - $flags "$remote" "$remote_nick" "$refs" + git-fetch--tool $flags native-store \ + "$remote" "$remote_nick" "$refs" ) ) || exit From dcf01c6e6b9f63d6f6239a6c6ff9f6373e4c5ff8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 27 Feb 2007 02:39:51 -0800 Subject: [PATCH 10/68] builtin-fetch--tool: adjust to updated sha1_object_info(). Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 48de08d858..e9d16e6315 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -17,9 +17,9 @@ static char *get_stdin(void) return data; } -static void show_new(char *type, unsigned char *sha1_new) +static void show_new(enum object_type type, unsigned char *sha1_new) { - fprintf(stderr, " %s: %s\n", type, + fprintf(stderr, " %s: %s\n", typename(type), find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); } @@ -51,14 +51,16 @@ static int update_local_ref(const char *name, const char *note, int verbose, int force) { - char type[20]; unsigned char sha1_old[20], sha1_new[20]; char oldh[41], newh[41]; struct commit *current, *updated; + enum object_type type; if (get_sha1_hex(new_head, sha1_new)) die("malformed object name %s", new_head); - if (sha1_object_info(sha1_new, type, NULL)) + + type = sha1_object_info(sha1_new, NULL); + if (type < 0) die("object %s not found", new_head); if (!*name) { From dec56c8cf1da06fe9ff4c9d3a26f74fb1ddd2fc6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 26 Feb 2007 11:37:43 -0800 Subject: [PATCH 11/68] fetch--tool: fix uninitialized buffer when reading from stdin The original code allocates too much space and forgets to NUL terminate the string. Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index e9d16e6315..5301c3cb78 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -2,17 +2,24 @@ #include "refs.h" #include "commit.h" -#define CHUNK_SIZE (1048576) +#define CHUNK_SIZE 1024 static char *get_stdin(void) { + int offset = 0; char *data = xmalloc(CHUNK_SIZE); - int offset = 0, read = 0; - read = xread(0, data, CHUNK_SIZE); - while (read == CHUNK_SIZE) { - offset += CHUNK_SIZE; + + while (1) { + int cnt = xread(0, data + offset, CHUNK_SIZE); + if (cnt < 0) + die("error reading standard input: %s", + strerror(errno)); + if (cnt == 0) { + data[offset] = 0; + break; + } + offset += cnt; data = xrealloc(data, offset + CHUNK_SIZE); - read = xread(0, data + offset, CHUNK_SIZE); } return data; } From c7d68c80002090bddc1eb740d83818aa0a08bbbe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 27 Feb 2007 23:51:48 -0800 Subject: [PATCH 12/68] builtin-fetch--tool: make sure not to overstep ls-remote-result buffer. Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 5301c3cb78..eeee0a5ebf 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -407,6 +407,8 @@ static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, eol = !next ? (ls + strlen(ls)) : next; if (!memcmp("^{}", eol-3, 3)) continue; + if (eol - ls < 40) + continue; if (get_sha1_hex(ls, sha1)) continue; ls += 40; From e6eebbb3ae2f2831fe1319d5acdb6477b8abeadb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 28 Feb 2007 17:01:00 -0800 Subject: [PATCH 13/68] git-fetch: retire update-local-ref which is not used anymore. Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 6 ------ git-fetch.sh | 8 -------- 2 files changed, 14 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index eeee0a5ebf..5261bf57fd 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -468,12 +468,6 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix) fclose(fp); return result; } - if (!strcmp("update-local-ref", argv[1])) { - if (argc != 5) - return error("update-local-ref takes 3 args"); - return update_local_ref(argv[2], argv[3], argv[4], - verbose, force); - } if (!strcmp("native-store", argv[1])) { int result; FILE *fp; diff --git a/git-fetch.sh b/git-fetch.sh index f438ac1ef2..4a8d8d6ef7 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -114,14 +114,6 @@ append_fetch_head () { git-fetch--tool $flags append-fetch-head "$@" } -update_local_ref () { - flags= - test -n "$verbose" && flags="$flags -v" - test -n "$force" && flags="$flags -f" - GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ - git-fetch--tool $flags update-local-ref "$@" -} - # updating the current HEAD with git-fetch in a bare # repository is always fine. if test -z "$update_head_ok" && test $(is_bare_repository) = false From 855b34680ea334f05385b3ad58232c3ccdca51d5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 28 Feb 2007 17:02:18 -0800 Subject: [PATCH 14/68] builtin-fetch--tool: fix reflog notes. Also the verbose output had unnecessary SHA1 and not-for-merge markers leaked because append_fetch_head() cheated Signed-off-by: Junio C Hamano --- builtin-fetch--tool.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 5261bf57fd..e9d6764550 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -181,16 +181,18 @@ static int append_fetch_head(FILE *fp, remote_len = i + 1; if (4 < i && !strncmp(".git", remote + i - 3, 4)) remote_len = i - 3; - note_len = sprintf(note, "%s\t%s\t", - sha1_to_hex(commit ? commit->object.sha1 : sha1), - not_for_merge ? "not-for-merge" : ""); + + note_len = 0; if (*what) { if (*kind) note_len += sprintf(note + note_len, "%s ", kind); note_len += sprintf(note + note_len, "'%s' of ", what); } note_len += sprintf(note + note_len, "%.*s", remote_len, remote); - fprintf(fp, "%s\n", note); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : sha1), + not_for_merge ? "not-for-merge" : "", + note); return update_local_ref(local_name, head, note, verbose, force); } From f98ef68faf81f10f89d9168c21acae71dd50c69e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 4 Mar 2007 15:36:08 -0800 Subject: [PATCH 15/68] .gitignore: add git-fetch--tool Signed-off-by: Junio C Hamano --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index eb8a1f8606..847f40a54e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ git-diff-tree git-describe git-fast-import git-fetch +git-fetch--tool git-fetch-pack git-findtags git-fmt-merge-msg From 86ab4906a7c86719cae33b28dac1a4157d665867 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Mar 2007 13:10:06 -0800 Subject: [PATCH 16/68] revision walker: Fix --boundary when limited This cleans up the boundary processing in the commit walker. It - rips out the boundary logic from the commit walker. Placing "negative" commits in the revs->commits list was Ok if all we cared about "boundary" was the UNINTERESTING limiting case, but conceptually it was wrong. - makes get_revision_1() function to walk the commits and return the results as if there is no funny postprocessing flags such as --reverse, --skip nor --max-count. - makes get_revision() function the postprocessing phase: If reverse is given, wait for get_revision_1() to give everything that it would normally give, and then reverse it before consuming. If skip is given, skip that many before going further. If max is given, stop when we gave out that many. Now that we are about to return one positive commit, mark the parents of that commit to be potential boundaries before returning, iff we are doing the boundary processing. Return the commit. - After get_revision() finishes giving out all the positive commits, if we are doing the boundary processing, we look at the parents that we marked as potential boundaries earlier, see if they are really boundaries, and give them out. It loses more code than it adds, even when the new gc_boundary() function, which is purely for early optimization, is counted. Note that this patch is purely for eyeballing and discussion only. It breaks git-bundle's verify logic because the logic does not use BOUNDARY_SHOW flag for its internal computation anymore. After we correct it not to attempt to affect the boundary processing by setting the BOUNDARY_SHOW flag, we can remove BOUNDARY_SHOW from revision.h and use that bit assignment for the new CHILD_SHOWN flag. Signed-off-by: Junio C Hamano --- revision.c | 206 ++++++++++++++++++++++++----------------------------- revision.h | 8 ++- 2 files changed, 99 insertions(+), 115 deletions(-) diff --git a/revision.c b/revision.c index f5b8ae4f03..5d137ea14a 100644 --- a/revision.c +++ b/revision.c @@ -437,36 +437,6 @@ static void limit_list(struct rev_info *revs) continue; p = &commit_list_insert(commit, p)->next; } - if (revs->boundary) { - /* mark the ones that are on the result list first */ - for (list = newlist; list; list = list->next) { - struct commit *commit = list->item; - commit->object.flags |= TMP_MARK; - } - for (list = newlist; list; list = list->next) { - struct commit *commit = list->item; - struct object *obj = &commit->object; - struct commit_list *parent; - if (obj->flags & UNINTERESTING) - continue; - for (parent = commit->parents; - parent; - parent = parent->next) { - struct commit *pcommit = parent->item; - if (!(pcommit->object.flags & UNINTERESTING)) - continue; - pcommit->object.flags |= BOUNDARY; - if (pcommit->object.flags & TMP_MARK) - continue; - pcommit->object.flags |= TMP_MARK; - p = &commit_list_insert(pcommit, p)->next; - } - } - for (list = newlist; list; list = list->next) { - struct commit *commit = list->item; - commit->object.flags &= ~TMP_MARK; - } - } revs->commits = newlist; } @@ -1193,17 +1163,6 @@ static void rewrite_parents(struct rev_info *revs, struct commit *commit) } } -static void mark_boundary_to_show(struct commit *commit) -{ - struct commit_list *p = commit->parents; - while (p) { - commit = p->item; - p = p->next; - if (commit->object.flags & BOUNDARY) - commit->object.flags |= BOUNDARY_SHOW; - } -} - static int commit_match(struct commit *commit, struct rev_info *opt) { if (!opt->grep_filter) @@ -1235,15 +1194,9 @@ static struct commit *get_revision_1(struct rev_info *revs) */ if (!revs->limited) { if (revs->max_age != -1 && - (commit->date < revs->max_age)) { - if (revs->boundary) - commit->object.flags |= - BOUNDARY_SHOW | BOUNDARY; - else - continue; - } else - add_parents_to_list(revs, commit, - &revs->commits); + (commit->date < revs->max_age)) + continue; + add_parents_to_list(revs, commit, &revs->commits); } if (commit->object.flags & SHOWN) continue; @@ -1252,18 +1205,6 @@ static struct commit *get_revision_1(struct rev_info *revs) revs->ignore_packed)) continue; - /* We want to show boundary commits only when their - * children are shown. When path-limiter is in effect, - * rewrite_parents() drops some commits from getting shown, - * and there is no point showing boundary parents that - * are not shown. After rewrite_parents() rewrites the - * parents of a commit that is shown, we mark the boundary - * parents with BOUNDARY_SHOW. - */ - if (commit->object.flags & BOUNDARY_SHOW) { - commit->object.flags |= SHOWN; - return commit; - } if (commit->object.flags & UNINTERESTING) continue; if (revs->min_age != -1 && (commit->date > revs->min_age)) @@ -1286,80 +1227,119 @@ static struct commit *get_revision_1(struct rev_info *revs) if (revs->parents) rewrite_parents(revs, commit); } - if (revs->boundary) - mark_boundary_to_show(commit); commit->object.flags |= SHOWN; return commit; } while (revs->commits); return NULL; } +static void gc_boundary(struct object_array *array) +{ + unsigned nr = array->nr; + unsigned alloc = array->alloc; + struct object_array_entry *objects = array->objects; + + if (alloc <= nr) { + unsigned i, j; + for (i = j = 0; i < nr; i++) { + if (objects[i].item->flags & SHOWN) + continue; + if (i != j) + objects[j] = objects[i]; + j++; + } + for (i = j; j < nr; j++) + objects[i].item = NULL; + array->nr = j; + } +} + struct commit *get_revision(struct rev_info *revs) { struct commit *c = NULL; + struct commit_list *l; - if (revs->reverse) { - struct commit_list *list; - - /* - * rev_info.reverse is used to note the fact that we - * want to output the list of revisions in reverse - * order. To accomplish this goal, reverse can have - * different values: - * - * 0 do nothing - * 1 reverse the list - * 2 internal use: we have already obtained and - * reversed the list, now we only need to yield - * its items. - */ - - if (revs->reverse == 1) { - revs->reverse = 0; - list = NULL; - while ((c = get_revision(revs))) - commit_list_insert(c, &list); - revs->commits = list; - revs->reverse = 2; + if (revs->boundary == 2) { + unsigned i; + struct object_array *array = &revs->boundary_commits; + struct object_array_entry *objects = array->objects; + for (i = 0; i < array->nr; i++) { + c = (struct commit *)(objects[i].item); + if (!c) + continue; + if (!(c->object.flags & CHILD_SHOWN)) + continue; + if (!(c->object.flags & SHOWN)) + break; } - - if (!revs->commits) + if (array->nr <= i) return NULL; - c = revs->commits->item; - list = revs->commits->next; - free(revs->commits); - revs->commits = list; + + c->object.flags |= SHOWN | BOUNDARY; return c; } - if (0 < revs->skip_count) { - while ((c = get_revision_1(revs)) != NULL) { - if (revs->skip_count-- <= 0) - break; - } + if (revs->reverse) { + l = NULL; + while ((c = get_revision_1(revs))) + commit_list_insert(c, &l); + revs->commits = l; + revs->reverse = 0; } - /* Check the max_count ... */ + /* + * Now pick up what they want to give us + */ + c = get_revision_1(revs); + while (0 < revs->skip_count) { + revs->skip_count--; + c = get_revision_1(revs); + if (!c) + break; + } + + /* + * Check the max_count. + */ switch (revs->max_count) { case -1: break; case 0: - if (revs->boundary) { - struct commit_list *list = revs->commits; - while (list) { - list->item->object.flags |= - BOUNDARY_SHOW | BOUNDARY; - list = list->next; - } - /* all remaining commits are boundary commits */ - revs->max_count = -1; - revs->limited = 1; - } else - return NULL; + c = NULL; + break; default: revs->max_count--; } - if (c) + + if (!revs->boundary) return c; - return get_revision_1(revs); + + if (!c) { + /* + * get_revision_1() runs out the commits, and + * we are done computing the boundaries. + * switch to boundary commits output mode. + */ + revs->boundary = 2; + return get_revision(revs); + } + + /* + * boundary commits are the commits that are parents of the + * ones we got from get_revision_1() but they themselves are + * not returned from get_revision_1(). Before returning + * 'c', we need to mark its parents that they could be boundaries. + */ + + for (l = c->parents; l; l = l->next) { + struct object *p; + p = &(l->item->object); + if (p->flags & CHILD_SHOWN) + continue; + p->flags |= CHILD_SHOWN; + gc_boundary(&revs->boundary_commits); + add_object_array(p, NULL, &revs->boundary_commits); + } + + return c; } diff --git a/revision.h b/revision.h index 5fec1846f3..6579a446ea 100644 --- a/revision.h +++ b/revision.h @@ -10,6 +10,7 @@ #define BOUNDARY_SHOW (1u<<6) #define ADDED (1u<<7) /* Parents already parsed and added? */ #define SYMMETRIC_LEFT (1u<<8) +#define CHILD_SHOWN (1u<<9) struct rev_info; struct log_info; @@ -21,6 +22,9 @@ struct rev_info { struct commit_list *commits; struct object_array pending; + /* Parents of shown commits */ + struct object_array boundary_commits; + /* Basic information */ const char *prefix; void *prune_data; @@ -40,10 +44,10 @@ struct rev_info { edge_hint:1, limited:1, unpacked:1, /* see also ignore_packed below */ - boundary:1, + boundary:2, left_right:1, parents:1, - reverse:2; + reverse:1; /* Diff flags */ unsigned int diff:1, From 2b064697a5b0610254f52523ce5b70c81118da80 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Mar 2007 16:10:28 -0800 Subject: [PATCH 17/68] revision traversal: retire BOUNDARY_SHOW This removes the flag internally used by revision traversal to decide which commits are indeed boundaries and renames it to CHILD_SHOWN. builtin-bundle uses the symbol for its verification, but I think the logic it uses it is wrong. The flag is still useful but it is local to the git-bundle, so it is renamed to PREREQ_MARK. Signed-off-by: Junio C Hamano --- builtin-bundle.c | 6 ++++-- revision.c | 9 ++++++++- revision.h | 3 +-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 279b8f8e58..76bd204de8 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -160,6 +160,8 @@ static int fork_with_pipe(const char **argv, int *in, int *out) return pid; } +#define PREREQ_MARK (1u<<16) + static int verify_bundle(struct bundle_header *header) { /* @@ -179,7 +181,7 @@ static int verify_bundle(struct bundle_header *header) struct ref_list_entry *e = p->list + i; struct object *o = parse_object(e->sha1); if (o) { - o->flags |= BOUNDARY_SHOW; + o->flags |= PREREQ_MARK; add_pending_object(&revs, o, e->name); continue; } @@ -202,7 +204,7 @@ static int verify_bundle(struct bundle_header *header) i = req_nr; while (i && (commit = get_revision(&revs))) - if (commit->object.flags & BOUNDARY_SHOW) + if (commit->object.flags & PREREQ_MARK) i--; for (i = 0; i < req_nr; i++) diff --git a/revision.c b/revision.c index 5d137ea14a..2d27ccf70b 100644 --- a/revision.c +++ b/revision.c @@ -1285,17 +1285,21 @@ struct commit *get_revision(struct rev_info *revs) commit_list_insert(c, &l); revs->commits = l; revs->reverse = 0; + c = NULL; } /* * Now pick up what they want to give us */ - c = get_revision_1(revs); + if (!(c = get_revision_1(revs))) + return NULL; while (0 < revs->skip_count) { revs->skip_count--; c = get_revision_1(revs); if (!c) break; + /* Although we grabbed it, it is not shown. */ + c->object.flags &= ~SHOWN; } /* @@ -1305,6 +1309,9 @@ struct commit *get_revision(struct rev_info *revs) case -1: break; case 0: + /* Although we grabbed it, it is not shown. */ + if (c) + c->object.flags &= ~SHOWN; c = NULL; break; default: diff --git a/revision.h b/revision.h index 6579a446ea..1885f8d233 100644 --- a/revision.h +++ b/revision.h @@ -7,10 +7,9 @@ #define SHOWN (1u<<3) #define TMP_MARK (1u<<4) /* for isolated cases; clean after use */ #define BOUNDARY (1u<<5) -#define BOUNDARY_SHOW (1u<<6) +#define CHILD_SHOWN (1u<<6) #define ADDED (1u<<7) /* Parents already parsed and added? */ #define SYMMETRIC_LEFT (1u<<8) -#define CHILD_SHOWN (1u<<9) struct rev_info; struct log_info; From c2dea5a11cc7106d0beec9931e527467b7189ab3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Mar 2007 16:16:32 -0800 Subject: [PATCH 18/68] git-bundle: various fixups verify_bundle() returned with an error early only when all prerequisite commits were missing. It should error out much earlier when some are missing. When the rev-list is limited in ways other than revision range (e.g. --max-count or --max-age), create_bundle() listed all positive refs given from the command line as if they are available, but resulting pack may not have some of them. Add a logic to make sure all of them are included, and error out otherwise. Signed-off-by: Junio C Hamano --- builtin-bundle.c | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 76bd204de8..d4dd57d159 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -189,7 +189,7 @@ static int verify_bundle(struct bundle_header *header) error(message); error("%s %s", sha1_to_hex(e->sha1), e->name); } - if (revs.pending.nr == 0) + if (revs.pending.nr != p->nr) return ret; req_nr = revs.pending.nr; setup_revisions(2, argv, &revs, NULL); @@ -279,6 +279,7 @@ static int create_bundle(struct bundle_header *header, const char *path, int pid, in, out, i, status; char buffer[1024]; struct rev_info revs; + struct object_array tips; bundle_fd = (!strcmp(path, "-") ? 1 : open(path, O_CREAT | O_WRONLY, 0666)); @@ -316,19 +317,23 @@ static int create_bundle(struct bundle_header *header, const char *path, argc = setup_revisions(argc, argv, &revs, NULL); if (argc > 1) return error("unrecognized argument: %s'", argv[1]); + + memset(&tips, 0, sizeof(tips)); for (i = 0; i < revs.pending.nr; i++) { struct object_array_entry *e = revs.pending.objects + i; - if (!(e->item->flags & UNINTERESTING)) { - unsigned char sha1[20]; - char *ref; - if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) - continue; - write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); - write_or_die(bundle_fd, " ", 1); - write_or_die(bundle_fd, ref, strlen(ref)); - write_or_die(bundle_fd, "\n", 1); - free(ref); - } + unsigned char sha1[20]; + char *ref; + + if (e->item->flags & UNINTERESTING) + continue; + if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) + continue; + write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); + write_or_die(bundle_fd, " ", 1); + write_or_die(bundle_fd, ref, strlen(ref)); + write_or_die(bundle_fd, "\n", 1); + add_object_array(e->item, e->name, &tips); + free(ref); } /* end header */ @@ -356,7 +361,22 @@ static int create_bundle(struct bundle_header *header, const char *path, return -1; if (!WIFEXITED(status) || WEXITSTATUS(status)) return error ("pack-objects died"); - return 0; + + /* + * Make sure the refs we wrote out is correct; --max-count and + * other limiting options could have prevented all the tips + * from getting output. + */ + status = 0; + for (i = 0; i < tips.nr; i++) { + if (!(tips.objects[i].item->flags & SHOWN)) { + status = 1; + error("%s: not included in the resulting pack", + tips.objects[i].name); + } + } + + return status; } static int unbundle(struct bundle_header *header, int bundle_fd, From c33d8593851ef1b3b59fc9b8022019cb955e8d59 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Mar 2007 18:23:57 -0800 Subject: [PATCH 19/68] revision traversal: SHOWN means shown This moves the code to set SHOWN on the commit from get_revision_1() back to get_revision(), so that the bit means what it originally meant: this commit has been given back to the caller. Also it fixes the --reverse breakage Dscho pointed out. Signed-off-by: Junio C Hamano --- revision.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/revision.c b/revision.c index 2d27ccf70b..35a171105a 100644 --- a/revision.c +++ b/revision.c @@ -1227,7 +1227,6 @@ static struct commit *get_revision_1(struct rev_info *revs) if (revs->parents) rewrite_parents(revs, commit); } - commit->object.flags |= SHOWN; return commit; } while (revs->commits); return NULL; @@ -1280,11 +1279,22 @@ struct commit *get_revision(struct rev_info *revs) } if (revs->reverse) { + int limit = -1; + + if (0 <= revs->max_count) { + limit = revs->max_count; + if (0 < revs->skip_count) + limit += revs->skip_count; + } l = NULL; - while ((c = get_revision_1(revs))) + while ((c = get_revision_1(revs))) { commit_list_insert(c, &l); + if ((0 < limit) && !--limit) + break; + } revs->commits = l; revs->reverse = 0; + revs->max_count = -1; c = NULL; } @@ -1298,8 +1308,6 @@ struct commit *get_revision(struct rev_info *revs) c = get_revision_1(revs); if (!c) break; - /* Although we grabbed it, it is not shown. */ - c->object.flags &= ~SHOWN; } /* @@ -1310,16 +1318,18 @@ struct commit *get_revision(struct rev_info *revs) break; case 0: /* Although we grabbed it, it is not shown. */ - if (c) - c->object.flags &= ~SHOWN; c = NULL; break; default: revs->max_count--; } - if (!revs->boundary) + if (c) + c->object.flags |= SHOWN; + + if (!revs->boundary) { return c; + } if (!c) { /* @@ -1341,7 +1351,7 @@ struct commit *get_revision(struct rev_info *revs) for (l = c->parents; l; l = l->next) { struct object *p; p = &(l->item->object); - if (p->flags & CHILD_SHOWN) + if (p->flags & (CHILD_SHOWN | SHOWN)) continue; p->flags |= CHILD_SHOWN; gc_boundary(&revs->boundary_commits); From 80e25ceece17fd575735c40dc923086154402cbe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Mar 2007 16:17:27 -0800 Subject: [PATCH 20/68] git-bundle: make verify a bit more chatty. Signed-off-by: Junio C Hamano --- builtin-bundle.c | 55 ++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index d4dd57d159..d0c361763c 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -160,9 +160,28 @@ static int fork_with_pipe(const char **argv, int *in, int *out) return pid; } +static int list_refs(struct ref_list *r, int argc, const char **argv) +{ + int i; + + for (i = 0; i < r->nr; i++) { + if (argc > 1) { + int j; + for (j = 1; j < argc; j++) + if (!strcmp(r->list[i].name, argv[j])) + break; + if (j == argc) + continue; + } + printf("%s %s\n", sha1_to_hex(r->list[i].sha1), + r->list[i].name); + } + return 0; +} + #define PREREQ_MARK (1u<<16) -static int verify_bundle(struct bundle_header *header) +static int verify_bundle(struct bundle_header *header, int verbose) { /* * Do fast check, then if any prereqs are missing then go line by line @@ -218,27 +237,24 @@ static int verify_bundle(struct bundle_header *header) for (i = 0; i < refs.nr; i++) clear_commit_marks((struct commit *)refs.objects[i].item, -1); + if (verbose) { + struct ref_list *r; + + r = &header->references; + printf("The bundle contains %d ref%s\n", + r->nr, (1 < r->nr) ? "s" : ""); + list_refs(r, 0, NULL); + r = &header->prerequisites; + printf("The bundle requires these %d ref%s\n", + r->nr, (1 < r->nr) ? "s" : ""); + list_refs(r, 0, NULL); + } return ret; } static int list_heads(struct bundle_header *header, int argc, const char **argv) { - int i; - struct ref_list *r = &header->references; - - for (i = 0; i < r->nr; i++) { - if (argc > 1) { - int j; - for (j = 1; j < argc; j++) - if (!strcmp(r->list[i].name, argv[j])) - break; - if (j == argc) - continue; - } - printf("%s %s\n", sha1_to_hex(r->list[i].sha1), - r->list[i].name); - } - return 0; + return list_refs(&header->references, argc, argv); } static void show_commit(struct commit *commit) @@ -385,7 +401,7 @@ static int unbundle(struct bundle_header *header, int bundle_fd, const char *argv_index_pack[] = {"index-pack", "--stdin", NULL}; int pid, status, dev_null; - if (verify_bundle(header)) + if (verify_bundle(header, 0)) return -1; dev_null = open("/dev/null", O_WRONLY); pid = fork_with_pipe(argv_index_pack, &bundle_fd, &dev_null); @@ -429,7 +445,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) if (!strcmp(cmd, "verify")) { close(bundle_fd); - if (verify_bundle(&header)) + if (verify_bundle(&header, 1)) return 1; fprintf(stderr, "%s is okay\n", bundle_file); return 0; @@ -449,4 +465,3 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) } else usage(bundle_usage); } - From 892ae6bf13d3811f36e6fe65c4ff85841e1c4f14 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 6 Mar 2007 03:00:18 -0800 Subject: [PATCH 21/68] revision --boundary: fix stupid typo Signed-off-by: Junio C Hamano --- revision.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revision.c b/revision.c index 35a171105a..f48d7f788a 100644 --- a/revision.c +++ b/revision.c @@ -1247,7 +1247,7 @@ static void gc_boundary(struct object_array *array) objects[j] = objects[i]; j++; } - for (i = j; j < nr; j++) + for (i = j; i < nr; i++) objects[i].item = NULL; array->nr = j; } From 8839ac9442c9ded41bfa369a142fd2e659a44377 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 6 Mar 2007 03:20:55 -0800 Subject: [PATCH 22/68] revision --boundary: fix uncounted case. When the list is truly limited and get_revision_1() returned NULL, the code incorrectly returned it without switching to boundary emiting mode. Silly. Signed-off-by: Junio C Hamano --- revision.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/revision.c b/revision.c index f48d7f788a..3c2eb125e6 100644 --- a/revision.c +++ b/revision.c @@ -1301,13 +1301,14 @@ struct commit *get_revision(struct rev_info *revs) /* * Now pick up what they want to give us */ - if (!(c = get_revision_1(revs))) - return NULL; - while (0 < revs->skip_count) { - revs->skip_count--; - c = get_revision_1(revs); - if (!c) - break; + c = get_revision_1(revs); + if (c) { + while (0 < revs->skip_count) { + revs->skip_count--; + c = get_revision_1(revs); + if (!c) + break; + } } /* @@ -1317,7 +1318,6 @@ struct commit *get_revision(struct rev_info *revs) case -1: break; case 0: - /* Although we grabbed it, it is not shown. */ c = NULL; break; default: From 0c3b4aac8ecdd039bac663a7d1b6bd373b7e250d Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 7 Mar 2007 00:44:37 +0100 Subject: [PATCH 23/68] git-gui: Support of "make -s" in: do not output anything of the build itself Signed-off-by: Alex Riesen Signed-off-by: Shawn O. Pearce --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index e486e8f984..a219b5d476 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,11 @@ ifndef V QUIET_BUILT_IN = @echo ' ' BUILTIN $@; endif +ifeq ($(findstring $(MAKEFLAGS),s),s) +QUIET_GEN = +QUIET_BUILT_IN = +endif + DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) From 8315588b59bea45c4b82451cc3ac337fa5c68526 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 6 Mar 2007 22:57:07 +0100 Subject: [PATCH 24/68] bundle: fix wrong check of read_header()'s return value & add tests If read_header() fails, it returns <0, not 0. Further, an open(/dev/null) was not checked for errors. Also, this adds two tests to make sure that the bundle file looks correct, by checking if it has the header has the expected form, and that the pack contains the right amount of objects. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 4 +++- t/t5510-fetch.sh | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index d0c361763c..3b3bc2582d 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -404,6 +404,8 @@ static int unbundle(struct bundle_header *header, int bundle_fd, if (verify_bundle(header, 0)) return -1; dev_null = open("/dev/null", O_WRONLY); + if (dev_null < 0) + return error("Could not open /dev/null"); pid = fork_with_pipe(argv_index_pack, &bundle_fd, &dev_null); if (pid < 0) return error("Could not spawn index-pack"); @@ -440,7 +442,7 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) memset(&header, 0, sizeof(header)); if (strcmp(cmd, "create") && - !(bundle_fd = read_header(bundle_file, &header))) + (bundle_fd = read_header(bundle_file, &header)) < 0) return 1; if (!strcmp(cmd, "verify")) { diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index fa76662dce..ad589dd0df 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -90,6 +90,13 @@ test_expect_success 'create bundle 1' ' git bundle create bundle1 master^..master ' +test_expect_success 'header of bundle looks right' ' + head -n 1 "$D"/bundle1 | grep "^#" && + head -n 2 "$D"/bundle1 | grep "^-[0-9a-f]\{40\} " && + head -n 3 "$D"/bundle1 | grep "^[0-9a-f]\{40\} " && + head -n 4 "$D"/bundle1 | grep "^$" +' + test_expect_success 'create bundle 2' ' cd "$D" && git bundle create bundle2 master~2..master @@ -101,6 +108,20 @@ test_expect_failure 'unbundle 1' ' git fetch "$D/bundle1" master:master ' +test_expect_success 'bundle 1 has only 3 files ' ' + cd "$D" && + ( + while read x && test -n "$x" + do + :; + done + cat + ) bundle.pack && + git index-pack bundle.pack && + verify=$(git verify-pack -v bundle.pack) && + test 4 = $(echo "$verify" | wc -l) +' + test_expect_success 'unbundle 2' ' cd "$D/bundle" && git fetch ../bundle2 master:master && From 18449ab0e9754ae04ae2f232449241ac361f3db4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 8 Mar 2007 00:43:05 +0100 Subject: [PATCH 25/68] git-bundle: avoid packing objects which are in the prerequisites When saying something like "--since=1.day.ago" or "--max-count=5", git-bundle finds the boundary commits which are recorded as prerequisites. However, it failed to tell pack-objects _not_ to pack the objects which are in these. Fix that. And add a test for that. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 15 ++++++++++++--- t/t5510-fetch.sh | 11 +++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 3b3bc2582d..70d4479193 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -305,6 +305,10 @@ static int create_bundle(struct bundle_header *header, const char *path, /* write signature */ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); + /* init revs to list objects for pack-objects later */ + save_commit_buffer = 0; + init_revisions(&revs, NULL); + /* write prerequisites */ memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *)); argv_boundary[0] = "rev-list"; @@ -316,8 +320,15 @@ static int create_bundle(struct bundle_header *header, const char *path, if (pid < 0) return -1; while ((i = read_string(out, buffer, sizeof(buffer))) > 0) - if (buffer[0] == '-') + if (buffer[0] == '-') { + unsigned char sha1[20]; write_or_die(bundle_fd, buffer, i); + if (!get_sha1_hex(buffer + 1, sha1)) { + struct object *object = parse_object(sha1); + object->flags |= UNINTERESTING; + add_pending_object(&revs, object, buffer); + } + } while ((i = waitpid(pid, &status, 0)) < 0) if (errno != EINTR) return error("rev-list died"); @@ -325,8 +336,6 @@ static int create_bundle(struct bundle_header *header, const char *path, return error("rev-list died %d", WEXITSTATUS(status)); /* write references */ - save_commit_buffer = 0; - init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; revs.blob_objects = 1; diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ad589dd0df..ee3f397a9b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -128,4 +128,15 @@ test_expect_success 'unbundle 2' ' test "tip" = "$(git log -1 --pretty=oneline master | cut -b42-)" ' +test_expect_success 'bundle does not prerequisite objects' ' + cd "$D" && + touch file2 && + git add file2 && + git commit -m add.file2 file2 && + git bundle create bundle3 -1 HEAD && + sed "1,4d" < bundle3 > bundle.pack && + git index-pack bundle.pack && + test 4 = $(git verify-pack -v bundle.pack | wc -l) +' + test_done From 9e64d109f905afb225f48409c4e0e068b2203332 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 8 Mar 2007 01:27:44 +0100 Subject: [PATCH 26/68] git-bundle: Make thin packs Thin packs are way smaller, but they rely on the receiving end to have the base objects. However, Git's pack protocol also uses thin packs by default. So make the packs contained in bundles thin, since bundles are just another transport. The patch looks a bit bigger than intended, mainly because --thin _implies_ that pack-objects should run its own rev-list. Therefore, this patch removes all the stuff we used to roll rev-list ourselves. This commit also changes behaviour slightly: since we now know early enough if a specified ref is _not_ contained in the pack, we can avoid putting that ref into the pack. So, we don't die() here, but warn() instead, and skip that ref. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 85 +++++++++++++++--------------------------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 70d4479193..6163358191 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -257,45 +257,15 @@ static int list_heads(struct bundle_header *header, int argc, const char **argv) return list_refs(&header->references, argc, argv); } -static void show_commit(struct commit *commit) -{ - write_or_die(1, sha1_to_hex(commit->object.sha1), 40); - write_or_die(1, "\n", 1); - if (commit->parents) { - free_commit_list(commit->parents); - commit->parents = NULL; - } -} - -static void show_object(struct object_array_entry *p) -{ - /* An object with name "foo\n0000000..." can be used to - * confuse downstream git-pack-objects very badly. - */ - const char *ep = strchr(p->name, '\n'); - int len = ep ? ep - p->name : strlen(p->name); - write_or_die(1, sha1_to_hex(p->item->sha1), 40); - write_or_die(1, " ", 1); - if (len) - write_or_die(1, p->name, len); - write_or_die(1, "\n", 1); -} - -static void show_edge(struct commit *commit) -{ - ; /* nothing to do */ -} - static int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv) { int bundle_fd = -1; const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); - const char **argv_pack = xmalloc(4 * sizeof(const char *)); + const char **argv_pack = xmalloc(5 * sizeof(const char *)); int pid, in, out, i, status; char buffer[1024]; struct rev_info revs; - struct object_array tips; bundle_fd = (!strcmp(path, "-") ? 1 : open(path, O_CREAT | O_WRONLY, 0666)); @@ -319,16 +289,20 @@ static int create_bundle(struct bundle_header *header, const char *path, pid = fork_with_pipe(argv_boundary, NULL, &out); if (pid < 0) return -1; - while ((i = read_string(out, buffer, sizeof(buffer))) > 0) + while ((i = read_string(out, buffer, sizeof(buffer))) > 0) { + unsigned char sha1[20]; if (buffer[0] == '-') { - unsigned char sha1[20]; write_or_die(bundle_fd, buffer, i); if (!get_sha1_hex(buffer + 1, sha1)) { struct object *object = parse_object(sha1); object->flags |= UNINTERESTING; add_pending_object(&revs, object, buffer); } + } else if (!get_sha1_hex(buffer, sha1)) { + struct object *object = parse_object(sha1); + object->flags |= SHOWN; } + } while ((i = waitpid(pid, &status, 0)) < 0) if (errno != EINTR) return error("rev-list died"); @@ -336,14 +310,10 @@ static int create_bundle(struct bundle_header *header, const char *path, return error("rev-list died %d", WEXITSTATUS(status)); /* write references */ - revs.tag_objects = 1; - revs.tree_objects = 1; - revs.blob_objects = 1; argc = setup_revisions(argc, argv, &revs, NULL); if (argc > 1) return error("unrecognized argument: %s'", argv[1]); - memset(&tips, 0, sizeof(tips)); for (i = 0; i < revs.pending.nr; i++) { struct object_array_entry *e = revs.pending.objects + i; unsigned char sha1[20]; @@ -353,11 +323,20 @@ static int create_bundle(struct bundle_header *header, const char *path, continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) continue; + /* + * Make sure the refs we wrote out is correct; --max-count and + * other limiting options could have prevented all the tips + * from getting output. + */ + if (!(e->item->flags & SHOWN)) { + warn("ref '%s' is excluded by the rev-list options", + e->name); + continue; + } write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); write_or_die(bundle_fd, " ", 1); write_or_die(bundle_fd, ref, strlen(ref)); write_or_die(bundle_fd, "\n", 1); - add_object_array(e->item, e->name, &tips); free(ref); } @@ -368,39 +347,27 @@ static int create_bundle(struct bundle_header *header, const char *path, argv_pack[0] = "pack-objects"; argv_pack[1] = "--all-progress"; argv_pack[2] = "--stdout"; - argv_pack[3] = NULL; + argv_pack[3] = "--thin"; + argv_pack[4] = NULL; in = -1; out = bundle_fd; pid = fork_with_pipe(argv_pack, &in, &out); if (pid < 0) return error("Could not spawn pack-objects"); - close(1); - dup2(in, 1); + for (i = 0; i < revs.pending.nr; i++) { + struct object *object = revs.pending.objects[i].item; + if (object->flags & UNINTERESTING) + write(in, "^", 1); + write(in, sha1_to_hex(object->sha1), 40); + write(in, "\n", 1); + } close(in); - prepare_revision_walk(&revs); - mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); - close(1); while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) return -1; if (!WIFEXITED(status) || WEXITSTATUS(status)) return error ("pack-objects died"); - /* - * Make sure the refs we wrote out is correct; --max-count and - * other limiting options could have prevented all the tips - * from getting output. - */ - status = 0; - for (i = 0; i < tips.nr; i++) { - if (!(tips.objects[i].item->flags & SHOWN)) { - status = 1; - error("%s: not included in the resulting pack", - tips.objects[i].name); - } - } - return status; } From 263703fff38d252907d1c7ae9977038715e2e22f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Mar 2007 03:48:27 +0100 Subject: [PATCH 27/68] git-bundle: handle thin packs in subcommand "unbundle" The patch to make the packs in a bundle thin forgot the receiving side. D'oh. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 6163358191..33b533f821 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -374,7 +374,8 @@ static int create_bundle(struct bundle_header *header, const char *path, static int unbundle(struct bundle_header *header, int bundle_fd, int argc, const char **argv) { - const char *argv_index_pack[] = {"index-pack", "--stdin", NULL}; + const char *argv_index_pack[] = {"index-pack", + "--fix-thin", "--stdin", NULL}; int pid, status, dev_null; if (verify_bundle(header, 0)) From d58c6184e345b9c8c8bfe8cc3eb1bbfe2f5ee4f9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Mar 2007 03:48:46 +0100 Subject: [PATCH 28/68] git-bundle: die if a given ref is not included in bundle The earlier patch tried to be nice by just warning, but it seems more likely that the user wants to adjust the parameters. Also, it prevents a bundle containing _all_ revisions in the case when the user only gave one ref, but also rev-list options which excluded the ref. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 33b533f821..ca3de60e44 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -328,11 +328,9 @@ static int create_bundle(struct bundle_header *header, const char *path, * other limiting options could have prevented all the tips * from getting output. */ - if (!(e->item->flags & SHOWN)) { - warn("ref '%s' is excluded by the rev-list options", + if (!(e->item->flags & SHOWN)) + die("ref '%s' is excluded by the rev-list options", e->name); - continue; - } write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); write_or_die(bundle_fd, " ", 1); write_or_die(bundle_fd, ref, strlen(ref)); From 2e578f9a4f08ade4e8da52614c566e5dc1c8ca00 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Mar 2007 03:50:06 +0100 Subject: [PATCH 29/68] git-bundle: prevent overwriting existing bundles Not only does it prevent accidentally losing older bundles, but it also fixes a subtle bug: when writing into an existing bundle, git-pack-objects would not truncate the bundle. Therefore, fetching from the bundle would trigger an error in unpack-objects: "fatal: pack has junk at the end". Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index ca3de60e44..55f6d0abcf 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -268,9 +268,9 @@ static int create_bundle(struct bundle_header *header, const char *path, struct rev_info revs; bundle_fd = (!strcmp(path, "-") ? 1 : - open(path, O_CREAT | O_WRONLY, 0666)); + open(path, O_CREAT | O_EXCL | O_WRONLY, 0666)); if (bundle_fd < 0) - return error("Could not write to '%s'", path); + return error("Could not create '%s': %s", path, strerror(errno)); /* write signature */ write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); From 896bdfa2581f96d3df9a312510c39ce7d80790cd Mon Sep 17 00:00:00 2001 From: James Bowes Date: Tue, 27 Feb 2007 22:31:10 -0500 Subject: [PATCH 30/68] add: Support specifying an excludes file with a configuration variable This adds the 'core.excludesfile' configuration variable. This variable can hold a path to a file containing patterns of file names to exclude from git-add, like $GIT_DIR/info/exclude. Patterns in the excludes file are used in addition to those in info/exclude. Signed-off-by: James Bowes Signed-off-by: Junio C Hamano --- builtin-add.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/builtin-add.c b/builtin-add.c index 87e16aa220..9fcf514dbc 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -12,6 +12,8 @@ static const char builtin_add_usage[] = "git-add [-n] [-v] [-f] [--interactive | -i] [--] ..."; +static const char *excludes_file; + static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { char *seen; @@ -67,6 +69,8 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) path = git_path("info/exclude"); if (!access(path, R_OK)) add_excludes_from_file(dir, path); + if (!access(excludes_file, R_OK)) + add_excludes_from_file(dir, excludes_file); /* * Calculate common prefix for the pathspec, and @@ -88,6 +92,18 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) prune_directory(dir, pathspec, baselen); } +static int git_add_config(const char *var, const char *value) +{ + if (!strcmp(var, "core.excludesfile")) { + if (!value) + die("core.excludesfile without value"); + excludes_file = xstrdup(value); + return 0; + } + + return git_default_config(var, value); +} + static struct lock_file lock_file; static const char ignore_warning[] = @@ -115,7 +131,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) exit(1); } - git_config(git_default_config); + git_config(git_add_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); From 0746d19a823aa12239df9d32db6fa6795d8c8539 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 8 Mar 2007 10:58:35 +0100 Subject: [PATCH 31/68] git-branch, git-checkout: autosetup for remote branch tracking In order to track and build on top of a branch 'topic' you track from your upstream repository, you often would end up doing this sequence: git checkout -b mytopic origin/topic git config --add branch.mytopic.remote origin git config --add branch.mytopic.merge refs/heads/topic This would first fork your own 'mytopic' branch from the 'topic' branch you track from the 'origin' repository; then it would set up two configuration variables so that 'git pull' without parameters does the right thing while you are on your own 'mytopic' branch. This commit adds a --track option to git-branch, so that "git branch --track mytopic origin/topic" performs the latter two actions when creating your 'mytopic' branch. If the configuration variable branch.autosetupmerge is set to true, you do not have to pass the --track option explicitly; further patches in this series allow setting the variable with a "git remote add" option. The configuration variable is off by default, and there is a --no-track option to countermand it even if the variable is set. Signed-off-by: Paolo Bonzini Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 9 ++- Documentation/git-checkout.txt | 15 +++- builtin-branch.c | 140 +++++++++++++++++++++++++++++++-- cache.h | 1 + git-checkout.sh | 17 ++-- t/t3200-branch.sh | 65 ++++++++++++--- trace.c | 18 +++-- 7 files changed, 229 insertions(+), 36 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 3ea3b80635..603f87f3b5 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]] -'git-branch' [-l] [-f] [] +'git-branch' [--track | --no-track] [-l] [-f] [] 'git-branch' (-m | -M) [] 'git-branch' (-d | -D) [-r] ... @@ -26,6 +26,13 @@ It will start out with a head equal to the one given as . If no is given, the branch will be created with a head equal to that of the currently checked out branch. +When a local branch is started off a remote branch, git can setup the +branch so that gitlink:git-pull[1] will appropriately merge from that +remote branch. If this behavior is desired, it is possible to make it +the default using the global `branch.autosetupmerge` configuration +flag. Otherwise, it can be chosen per-branch using the `--track` +and `--no-track` options. + With a '-m' or '-M' option, will be renamed to . If had a corresponding reflog, it is renamed to match , and a reflog entry is created to remember the branch diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 1ae77be450..f5b2d5017b 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch SYNOPSIS -------- [verse] -'git-checkout' [-q] [-f] [-b [-l]] [-m] [] +'git-checkout' [-q] [-f] [-b [--track | --no-track] [-l]] [-m] [] 'git-checkout' [] ... DESCRIPTION @@ -18,7 +18,8 @@ When are not given, this command switches branches by updating the index and working tree to reflect the specified branch, , and updating HEAD to be or, if specified, . Using -b will cause to -be created. +be created; in this case you can use the --track or --no-track +options, which will be passed to `git branch`. When are given, this command does *not* switch branches. It updates the named paths in the working tree from @@ -45,6 +46,16 @@ OPTIONS by gitlink:git-check-ref-format[1]. Some of these checks may restrict the characters allowed in a branch name. +--track:: + When -b is given and a branch is created off a remote branch, + setup so that git-pull will automatically retrieve data from + the remote branch. + +--no-track:: + When -b is given and a branch is created off a remote branch, + force that git-pull will automatically retrieve data from + the remote branch independent of the configuration settings. + -l:: Create the new branch's ref log. This activates recording of all changes to made the branch ref, enabling use of date diff --git a/builtin-branch.c b/builtin-branch.c index 06d8a8ce04..e161e00978 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -12,7 +12,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = - "git-branch [-r] (-d | -D) | [-l] [-f] [] | (-m | -M) [] | [--color | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]]"; + "git-branch [-r] (-d | -D) | [--track | --no-track] [-l] [-f] [] | (-m | -M) [] | [--color | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]]"; #define REF_UNKNOWN_TYPE 0x00 #define REF_LOCAL_BRANCH 0x01 @@ -22,6 +22,8 @@ static const char builtin_branch_usage[] = static const char *head; static unsigned char head_sha1[20]; +static int branch_track_remotes; + static int branch_use_color; static char branch_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ @@ -64,6 +66,9 @@ int git_branch_config(const char *var, const char *value) color_parse(value, var, branch_colors[slot]); return 0; } + if (!strcmp(var, "branch.autosetupmerge")) + branch_track_remotes = git_config_bool(var, value); + return git_default_config(var, value); } @@ -309,14 +314,109 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev) free_ref_list(&ref_list); } +static char *config_repo; +static char *config_remote; +static const char *start_ref; +static int start_len; +static int base_len; + +static int get_remote_branch_name(const char *value) +{ + const char *colon; + const char *end; + + if (*value == '+') + value++; + + colon = strchr(value, ':'); + if (!colon) + return 0; + + end = value + strlen(value); + + /* Try an exact match first. */ + if (!strcmp(colon + 1, start_ref)) { + /* Truncate the value before the colon. */ + nfasprintf(&config_repo, "%.*s", colon - value, value); + return 1; + } + + /* Try with a wildcard match now. */ + if (end - value > 2 && end[-2] == '/' && end[-1] == '*' && + colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' && + (end - 2) - (colon + 1) == base_len && + !strncmp(colon + 1, start_ref, base_len)) { + /* Replace the star with the remote branch name. */ + nfasprintf(&config_repo, "%.*s%s", + (colon - 2) - value, value, + start_ref + base_len); + return 1; + } + + return 0; +} + +static int get_remote_config(const char *key, const char *value) +{ + const char *var; + if (prefixcmp(key, "remote.")) + return 0; + + var = strrchr(key, '.'); + if (var == key + 6) + return 0; + + if (!strcmp(var, ".fetch") && get_remote_branch_name(value)) + nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7); + + return 0; +} + +static void set_branch_defaults(const char *name, const char *real_ref) +{ + char key[1024]; + const char *slash = strrchr(real_ref, '/'); + + if (!slash) + return; + + start_ref = real_ref; + start_len = strlen(real_ref); + base_len = slash - real_ref; + git_config(get_remote_config); + + if (config_repo && config_remote) { + if (sizeof(key) <= + snprintf(key, sizeof(key), "branch.%s.remote", name)) + die("what a long branch name you have!"); + git_config_set(key, config_remote); + + /* + * We do not have to check if we have enough space for + * the 'merge' key, since it's shorter than the + * previous 'remote' key, which we already checked. + */ + snprintf(key, sizeof(key), "branch.%s.merge", name); + git_config_set(key, config_repo); + + printf("Branch %s set up to track remote branch %s.\n", + name, real_ref); + } + + if (config_repo) + free(config_repo); + if (config_remote) + free(config_remote); +} + static void create_branch(const char *name, const char *start_name, unsigned char *start_sha1, - int force, int reflog) + int force, int reflog, int track) { struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char ref[PATH_MAX], msg[PATH_MAX + 20]; + char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; int forcing = 0; snprintf(ref, sizeof ref, "refs/heads/%s", name); @@ -331,10 +431,14 @@ static void create_branch(const char *name, const char *start_name, forcing = 1; } - if (start_sha1) + if (start_sha1) { /* detached HEAD */ hashcpy(sha1, start_sha1); - else if (get_sha1(start_name, sha1)) + real_ref = NULL; + } + else if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1) + die("Ambiguous object name: '%s'.", start_name); + else if (real_ref == NULL) die("Not a valid object name: '%s'.", start_name); if ((commit = lookup_commit_reference(sha1)) == NULL) @@ -355,8 +459,17 @@ static void create_branch(const char *name, const char *start_name, snprintf(msg, sizeof msg, "branch: Created from %s", start_name); + /* When branching off a remote branch, set up so that git-pull + automatically merges from there. So far, this is only done for + remotes registered via .git/config. */ + if (real_ref && track) + set_branch_defaults(name, real_ref); + if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); + + if (real_ref) + free(real_ref); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -398,11 +511,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int delete = 0, force_delete = 0, force_create = 0; int rename = 0, force_rename = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0; + int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; int i; git_config(git_branch_config); + track = branch_track_remotes; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -413,6 +527,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) i++; break; } + if (!strcmp(arg, "--track")) { + track = 1; + continue; + } + if (!strcmp(arg, "--no-track")) { + track = 0; + continue; + } if (!strcmp(arg, "-d")) { delete = 1; continue; @@ -499,9 +621,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) else if (rename && (i == argc - 2)) rename_branch(argv[i], argv[i + 1], force_rename); else if (i == argc - 1) - create_branch(argv[i], head, head_sha1, force_create, reflog); + create_branch(argv[i], head, head_sha1, force_create, reflog, + track); else if (i == argc - 2) - create_branch(argv[i], argv[i+1], NULL, force_create, reflog); + create_branch(argv[i], argv[i+1], NULL, force_create, reflog, + track); else usage(builtin_branch_usage); diff --git a/cache.h b/cache.h index f172d02a65..faed2a8994 100644 --- a/cache.h +++ b/cache.h @@ -480,6 +480,7 @@ extern struct tag *alloc_tag_node(void); extern void alloc_report(void); /* trace.c */ +extern int nfasprintf(char **str, const char *fmt, ...); extern int nfvasprintf(char **str, const char *fmt, va_list va); extern void trace_printf(const char *format, ...); extern void trace_argv_printf(const char **argv, int count, const char *format, ...); diff --git a/git-checkout.sh b/git-checkout.sh index 14835a4aa9..6caa9fdcc6 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -12,6 +12,7 @@ new= new_name= force= branch= +track= newbranch= newbranch_log= merge= @@ -33,7 +34,10 @@ while [ "$#" != "0" ]; do die "git checkout: we do not like '$newbranch' as a branch name." ;; "-l") - newbranch_log=1 + newbranch_log=-l + ;; + "--track"|"--no-track") + track="$arg" ;; "-f") force=1 @@ -85,6 +89,11 @@ while [ "$#" != "0" ]; do esac done +case "$new_branch,$track" in +,--*) + die "git checkout: --track and --no-track require -b" +esac + case "$force$merge" in 11) die "git checkout: -f and -m are incompatible" @@ -235,11 +244,7 @@ fi # if [ "$?" -eq 0 ]; then if [ "$newbranch" ]; then - if [ "$newbranch_log" ]; then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch") - touch "$GIT_DIR/logs/refs/heads/$newbranch" - fi - git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit + git-branch $track $newbranch_log "$newbranch" "$new_name" || exit branch="$newbranch" fi if test -n "$branch" diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5565c27033..75c000a968 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -47,17 +47,6 @@ test_expect_success \ test ! -f .git/refs/heads/d/e/f && test ! -f .git/logs/refs/heads/d/e/f' -cat >expect < 1117150200 +0000 checkout: Created from master -EOF -test_expect_success \ - 'git checkout -b g/h/i -l should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git-checkout -b g/h/i -l master && - test -f .git/refs/heads/g/h/i && - test -f .git/logs/refs/heads/g/h/i && - diff expect .git/logs/refs/heads/g/h/i' - test_expect_success \ 'git branch j/k should work after branch j has been deleted' \ 'git-branch j && @@ -117,4 +106,58 @@ test_expect_failure \ ln -s real-u .git/logs/refs/heads/u && git-branch -m u v' +test_expect_success 'test tracking setup via --track' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my1 local/master && + test $(git-config branch.my1.remote) = local && + test $(git-config branch.my1.merge) = refs/heads/master' + +test_expect_success 'test tracking setup (non-wildcard, matching)' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/master:refs/remotes/local/master && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my4 local/master && + test $(git-config branch.my4.remote) = local && + test $(git-config branch.my4.merge) = refs/heads/master' + +test_expect_success 'test tracking setup (non-wildcard, not matching)' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/s:refs/remotes/local/s && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my5 local/master && + ! test $(git-config branch.my5.remote) = local && + ! test $(git-config branch.my5.merge) = refs/heads/master' + +test_expect_success 'test tracking setup via config' \ + 'git-config branch.autosetupmerge true && + git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch my3 local/master && + test $(git-config branch.my3.remote) = local && + test $(git-config branch.my3.merge) = refs/heads/master' + +test_expect_success 'test overriding tracking setup via --no-track' \ + 'git-config branch.autosetupmerge true && + git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --no-track my2 local/master && + ! test $(git-config branch.my2.remote) = local && + ! test $(git-config branch.my2.merge) = refs/heads/master' + +# Keep this test last, as it changes the current branch +cat >expect < 1117150200 +0000 branch: Created from master +EOF +test_expect_success \ + 'git checkout -b g/h/i -l should create a branch and a log' \ + 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git-checkout -b g/h/i -l master && + test -f .git/refs/heads/g/h/i && + test -f .git/logs/refs/heads/g/h/i && + diff expect .git/logs/refs/heads/g/h/i' + test_done diff --git a/trace.c b/trace.c index 27fef868c4..7961a27a2e 100644 --- a/trace.c +++ b/trace.c @@ -26,14 +26,14 @@ #include "quote.h" /* Stolen from "imap-send.c". */ -static int git_vasprintf(char **strp, const char *fmt, va_list ap) +int nfvasprintf(char **strp, const char *fmt, va_list ap) { int len; char tmp[1024]; if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || !(*strp = xmalloc(len + 1))) - return -1; + die("Fatal: Out of memory\n"); if (len >= (int)sizeof(tmp)) vsprintf(*strp, fmt, ap); else @@ -41,13 +41,15 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap) return len; } -/* Stolen from "imap-send.c". */ -int nfvasprintf(char **str, const char *fmt, va_list va) +int nfasprintf(char **str, const char *fmt, ...) { - int ret = git_vasprintf(str, fmt, va); - if (ret < 0) - die("Fatal: Out of memory\n"); - return ret; + int rc; + va_list args; + + va_start(args, fmt); + rc = nfvasprintf(str, fmt, args); + va_end(args); + return rc; } /* Get a trace file descriptor from GIT_TRACE env variable. */ From 45994a1e33398e55426268fcd5d4ab3143497e29 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 8 Mar 2007 13:59:54 -0800 Subject: [PATCH 32/68] Fix broken create_branch() in builtin-branch. Signed-off-by: Junio C Hamano --- builtin-branch.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index e161e00978..42b1ff129e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -410,7 +410,6 @@ static void set_branch_defaults(const char *name, const char *real_ref) } static void create_branch(const char *name, const char *start_name, - unsigned char *start_sha1, int force, int reflog, int track) { struct ref_lock *lock; @@ -431,16 +430,23 @@ static void create_branch(const char *name, const char *start_name, forcing = 1; } - if (start_sha1) { - /* detached HEAD */ - hashcpy(sha1, start_sha1); - real_ref = NULL; - } - else if (dwim_ref(start_name, strlen(start_name), sha1, &real_ref) > 1) - die("Ambiguous object name: '%s'.", start_name); - else if (real_ref == NULL) + real_ref = NULL; + if (get_sha1(start_name, sha1)) die("Not a valid object name: '%s'.", start_name); + switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { + case 0: + /* Not branching from any existing branch */ + real_ref = NULL; + break; + case 1: + /* Unique completion -- good */ + break; + default: + die("Ambiguous object name: '%s'.", start_name); + break; + } + if ((commit = lookup_commit_reference(sha1)) == NULL) die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); @@ -620,12 +626,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) rename_branch(head, argv[i], force_rename); else if (rename && (i == argc - 2)) rename_branch(argv[i], argv[i + 1], force_rename); - else if (i == argc - 1) - create_branch(argv[i], head, head_sha1, force_create, reflog, - track); - else if (i == argc - 2) - create_branch(argv[i], argv[i+1], NULL, force_create, reflog, - track); + else if (i == argc - 1 || i == argc - 2) + create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head, + force_create, reflog, track); else usage(builtin_branch_usage); From b867092fec81c59a35337d24d2d976ab2acc2488 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 9 Mar 2007 03:52:12 -0800 Subject: [PATCH 33/68] prepare_packed_git(): sort packs by age and localness. When accessing objects, we first look for them in packs that are linked together in the reverse order of discovery. Since younger packs tend to contain more recent objects, which are more likely to be accessed often, and local packs tend to contain objects more relevant to our specific projects, sort the list of packs before starting to access them. In addition, favoring local packs over the ones borrowed from alternates can be a win when alternates are mounted on network file systems. Signed-off-by: Junio C Hamano --- cache.h | 1 + sha1_file.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/cache.h b/cache.h index f172d02a65..75da344fdc 100644 --- a/cache.h +++ b/cache.h @@ -372,6 +372,7 @@ extern struct packed_git { struct packed_git *next; struct pack_window *windows; uint32_t *index_base; + time_t mtime; off_t index_size; off_t pack_size; int pack_fd; diff --git a/sha1_file.c b/sha1_file.c index 7faa8bcd50..5691448d73 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -739,6 +739,7 @@ struct packed_git *add_packed_git(char *path, int path_len, int local) p->windows = NULL; p->pack_fd = -1; p->pack_local = local; + p->mtime = st.st_mtime; if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1)) hashcpy(p->sha1, sha1); return p; @@ -823,6 +824,60 @@ static void prepare_packed_git_one(char *objdir, int local) closedir(dir); } +static int sort_pack(const void *a_, const void *b_) +{ + struct packed_git *a = *((struct packed_git **)a_); + struct packed_git *b = *((struct packed_git **)b_); + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack_local - b->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->mtime < b->mtime) + return 1; + else if (a->mtime == b->mtime) + return 0; + return -1; +} + +static void rearrange_packed_git(void) +{ + struct packed_git **ary, *p; + int i, n; + + for (n = 0, p = packed_git; p; p = p->next) + n++; + if (n < 2) + return; + + /* prepare an array of packed_git for easier sorting */ + ary = xcalloc(n, sizeof(struct packed_git *)); + for (n = 0, p = packed_git; p; p = p->next) + ary[n++] = p; + + qsort(ary, n, sizeof(struct packed_git *), sort_pack); + + /* link them back again */ + for (i = 0; i < n - 1; i++) + ary[i]->next = ary[i + 1]; + ary[n - 1]->next = NULL; + packed_git = ary[0]; + + free(ary); +} + static int prepare_packed_git_run_once = 0; void prepare_packed_git(void) { @@ -837,6 +892,7 @@ void prepare_packed_git(void) prepare_packed_git_one(alt->base, 0); alt->name[-1] = '/'; } + rearrange_packed_git(); prepare_packed_git_run_once = 1; } From f1000898d43a30f6a0d3bbde7b4927e97913d010 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:00 -0500 Subject: [PATCH 34/68] Start defining a more sophisticated run_command There are a number of places where we do some variation of fork()+exec() but we also need to setup redirection in the process, much like what run_command does for us already with its option flags. It would be nice to reuse more of the run_command logic, especially as that non-fork API helps us to port to odd platforms like Win32. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- run-command.c | 25 ++++++++++++++++++------- run-command.h | 9 +++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/run-command.c b/run-command.c index 94ace50a25..1fb7fa5a8d 100644 --- a/run-command.c +++ b/run-command.c @@ -2,26 +2,26 @@ #include "run-command.h" #include "exec_cmd.h" -int run_command_v_opt(const char **argv, int flags) +int run_command(struct child_process *cmd) { pid_t pid = fork(); if (pid < 0) return -ERR_RUN_COMMAND_FORK; if (!pid) { - if (flags & RUN_COMMAND_NO_STDIN) { + if (cmd->no_stdin) { int fd = open("/dev/null", O_RDWR); dup2(fd, 0); close(fd); } - if (flags & RUN_COMMAND_STDOUT_TO_STDERR) + if (cmd->stdout_to_stderr) dup2(2, 1); - if (flags & RUN_GIT_CMD) { - execv_git_cmd(argv); + if (cmd->git_cmd) { + execv_git_cmd(cmd->argv); } else { - execvp(argv[0], (char *const*) argv); + execvp(cmd->argv[0], (char *const*) cmd->argv); } - die("exec %s failed.", argv[0]); + die("exec %s failed.", cmd->argv[0]); } for (;;) { int status, code; @@ -46,3 +46,14 @@ int run_command_v_opt(const char **argv, int flags) return 0; } } + +int run_command_v_opt(const char **argv, int opt) +{ + struct child_process cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = argv; + cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0; + cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0; + cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0; + return run_command(&cmd); +} diff --git a/run-command.h b/run-command.h index 2646d38ac7..f9db2a7f8c 100644 --- a/run-command.h +++ b/run-command.h @@ -10,6 +10,15 @@ enum { ERR_RUN_COMMAND_WAITPID_NOEXIT, }; +struct child_process { + const char **argv; + unsigned no_stdin:1; + unsigned git_cmd:1; /* if this is to be git sub-command */ + unsigned stdout_to_stderr:1; +}; + +int run_command(struct child_process *); + #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 From ebcb5d16ca911d5e21bb8071c185fb47a0c1fbb3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:05 -0500 Subject: [PATCH 35/68] Split run_command into two halves (start/finish) If the calling process wants to send data to stdin of a child process it will need to arrange for a pipe and get the child process running, feed data to it, then wait for the child process to finish. So we split the run function into two halves, allowing callers to first start the child then later finish it. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- run-command.c | 26 +++++++++++++++++++------- run-command.h | 3 +++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/run-command.c b/run-command.c index 1fb7fa5a8d..a866a06694 100644 --- a/run-command.c +++ b/run-command.c @@ -2,13 +2,12 @@ #include "run-command.h" #include "exec_cmd.h" -int run_command(struct child_process *cmd) +int start_command(struct child_process *cmd) { - pid_t pid = fork(); - - if (pid < 0) + cmd->pid = fork(); + if (cmd->pid < 0) return -ERR_RUN_COMMAND_FORK; - if (!pid) { + if (!cmd->pid) { if (cmd->no_stdin) { int fd = open("/dev/null", O_RDWR); dup2(fd, 0); @@ -23,9 +22,14 @@ int run_command(struct child_process *cmd) } die("exec %s failed.", cmd->argv[0]); } + return 0; +} + +int finish_command(struct child_process *cmd) +{ for (;;) { int status, code; - pid_t waiting = waitpid(pid, &status, 0); + pid_t waiting = waitpid(cmd->pid, &status, 0); if (waiting < 0) { if (errno == EINTR) @@ -33,7 +37,7 @@ int run_command(struct child_process *cmd) error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (waiting != pid) + if (waiting != cmd->pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; @@ -47,6 +51,14 @@ int run_command(struct child_process *cmd) } } +int run_command(struct child_process *cmd) +{ + int code = start_command(cmd); + if (code) + return code; + return finish_command(cmd); +} + int run_command_v_opt(const char **argv, int opt) { struct child_process cmd; diff --git a/run-command.h b/run-command.h index f9db2a7f8c..24cdb4eb19 100644 --- a/run-command.h +++ b/run-command.h @@ -12,11 +12,14 @@ enum { struct child_process { const char **argv; + pid_t pid; unsigned no_stdin:1; unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned stdout_to_stderr:1; }; +int start_command(struct child_process *); +int finish_command(struct child_process *); int run_command(struct child_process *); #define RUN_COMMAND_NO_STDIN 1 From 4919bf0354e2a1cfb948c320d45d51319ada30eb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:08 -0500 Subject: [PATCH 36/68] Teach run_command how to setup a stdin pipe Sometimes callers trying to use run_command to execute a child process will want to setup a pipe or file descriptor to redirect into the child's stdin. This idea is completely stolen from builtin-bundle's fork_with_pipe, written by Johannes Schindelin. All credit (and blame) should lie with Dscho. ;-) Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- run-command.c | 35 ++++++++++++++++++++++++++++++++++- run-command.h | 3 +++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/run-command.c b/run-command.c index a866a06694..03ff7bcac2 100644 --- a/run-command.c +++ b/run-command.c @@ -4,15 +4,39 @@ int start_command(struct child_process *cmd) { + int need_in = !cmd->no_stdin && cmd->in < 0; + int fdin[2]; + + if (need_in) { + if (pipe(fdin) < 0) + return -ERR_RUN_COMMAND_PIPE; + cmd->in = fdin[1]; + cmd->close_in = 1; + } + cmd->pid = fork(); - if (cmd->pid < 0) + if (cmd->pid < 0) { + if (need_in) { + close(fdin[0]); + close(fdin[1]); + } return -ERR_RUN_COMMAND_FORK; + } + if (!cmd->pid) { if (cmd->no_stdin) { int fd = open("/dev/null", O_RDWR); dup2(fd, 0); close(fd); + } else if (need_in) { + dup2(fdin[0], 0); + close(fdin[0]); + close(fdin[1]); + } else if (cmd->in) { + dup2(cmd->in, 0); + close(cmd->in); } + if (cmd->stdout_to_stderr) dup2(2, 1); if (cmd->git_cmd) { @@ -22,11 +46,20 @@ int start_command(struct child_process *cmd) } die("exec %s failed.", cmd->argv[0]); } + + if (need_in) + close(fdin[0]); + else if (cmd->in) + close(cmd->in); + return 0; } int finish_command(struct child_process *cmd) { + if (cmd->close_in) + close(cmd->in); + for (;;) { int status, code; pid_t waiting = waitpid(cmd->pid, &status, 0); diff --git a/run-command.h b/run-command.h index 24cdb4eb19..ff090679a6 100644 --- a/run-command.h +++ b/run-command.h @@ -4,6 +4,7 @@ enum { ERR_RUN_COMMAND_FORK = 10000, ERR_RUN_COMMAND_EXEC, + ERR_RUN_COMMAND_PIPE, ERR_RUN_COMMAND_WAITPID, ERR_RUN_COMMAND_WAITPID_WRONG_PID, ERR_RUN_COMMAND_WAITPID_SIGNAL, @@ -13,6 +14,8 @@ enum { struct child_process { const char **argv; pid_t pid; + int in; + unsigned close_in:1; unsigned no_stdin:1; unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned stdout_to_stderr:1; From 6c319a22e47e6b49d799f5893aeac92e723a5e6f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:11 -0500 Subject: [PATCH 37/68] Refactor run_command error handling in receive-pack I'm pulling the error handling used to decode the result of run_command up into a new function so that I can reuse it. No changes, just a simple code movement. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- receive-pack.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/receive-pack.c b/receive-pack.c index 675c88f492..e147076056 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -71,6 +71,29 @@ static const char update_hook[] = "hooks/update"; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; +static int hook_status(int code, const char *hook_name) +{ + switch (code) { + case 0: + return 0; + case -ERR_RUN_COMMAND_FORK: + return error("hook fork failed"); + case -ERR_RUN_COMMAND_EXEC: + return error("hook execute failed"); + case -ERR_RUN_COMMAND_WAITPID: + return error("waitpid failed"); + case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: + return error("waitpid is confused"); + case -ERR_RUN_COMMAND_WAITPID_SIGNAL: + return error("%s died of signal", hook_name); + case -ERR_RUN_COMMAND_WAITPID_NOEXIT: + return error("%s died strangely", hook_name); + default: + error("%s exited with error code %d", hook_name, -code); + return -code; + } +} + static int run_hook(const char *hook_name, struct command *first_cmd, int single) @@ -108,25 +131,7 @@ static int run_hook(const char *hook_name, free((char*)argv[argc]); free(argv); - switch (code) { - case 0: - return 0; - case -ERR_RUN_COMMAND_FORK: - return error("hook fork failed"); - case -ERR_RUN_COMMAND_EXEC: - return error("hook execute failed"); - case -ERR_RUN_COMMAND_WAITPID: - return error("waitpid failed"); - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - return error("waitpid is confused"); - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - return error("%s died of signal", hook_name); - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - return error("%s died strangely", hook_name); - default: - error("%s exited with error code %d", hook_name, -code); - return -code; - } + return hook_status(code, hook_name); } static const char *update(struct command *cmd) From 1d9e8b56fe3a0360bf61ce633827af8fa9a7013c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:13 -0500 Subject: [PATCH 38/68] Split back out update_hook handling in receive-pack Since we have decided to change the calling conventions for the pre-receive and post-receive hooks to take the ref data on stdin rather than on the command line we cannot use the same logic to invoke the update hook anymore. So we take a small step backwards towards what we used to have, and create a specialized function for executing just the update hook. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- receive-pack.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/receive-pack.c b/receive-pack.c index e147076056..4d7170f6ad 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -67,7 +67,6 @@ struct command { static struct command *commands; -static const char update_hook[] = "hooks/update"; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; @@ -134,6 +133,29 @@ static int run_hook(const char *hook_name, return hook_status(code, hook_name); } +static int run_update_hook(struct command *cmd) +{ + static const char update_hook[] = "hooks/update"; + struct child_process proc; + const char *argv[5]; + + if (access(update_hook, X_OK) < 0) + return 0; + + argv[0] = update_hook; + argv[1] = cmd->ref_name; + argv[2] = sha1_to_hex(cmd->old_sha1); + argv[3] = sha1_to_hex(cmd->new_sha1); + argv[4] = NULL; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + + return hook_status(run_command(&proc), update_hook); +} + static const char *update(struct command *cmd) { const char *name = cmd->ref_name; @@ -170,7 +192,7 @@ static const char *update(struct command *cmd) return "non-fast forward"; } } - if (run_hook(update_hook, cmd, 1)) { + if (run_update_hook(cmd)) { error("hook declined to update %s", name); return "hook declined"; } From f43cd49fb82b0eee10b88833b58edd711fe8298d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 10 Mar 2007 03:28:16 -0500 Subject: [PATCH 39/68] Change {pre,post}-receive hooks to use stdin Sergey Vlasov, Andy Parkins and Alex Riesen all pointed out that it is possible for a single invocation of receive-pack to be given more refs than the OS might allow us to pass as command line parameters to a single hook invocation. We don't want to break these up into multiple invocations (like xargs might do) as that makes it impossible for the pre-receive hook to verify multiple related ref updates occur at the same time, and it makes it harder for post-receive to send out a single batch notification. Instead we pass the reference data on a pipe connected to the hook's stdin, supplying one ref per line to the hook. This way a single hook invocation can obtain an infinite amount of ref data, without bumping into any operating system limits. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/git-receive-pack.txt | 32 ++++++++--------- receive-pack.c | 58 +++++++++++++++--------------- t/t5401-update-hooks.sh | 30 ++++++++-------- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index 3cf55111cc..6914aa59c3 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -40,13 +40,13 @@ OPTIONS pre-receive Hook ---------------- Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists -and is executable, it will be invoked once, with three parameters -per ref to be updated: +and is executable, it will be invoked once with no parameters. The +standard input of the hook will be one line per ref to be updated: - $GIT_DIR/hooks/pre-receive (refname sha1-old sha1-new)+ + sha1-old SP sha1-new SP refname LF -The refname parameter is relative to $GIT_DIR; e.g. for the master -head this is "refs/heads/master". The two sha1 arguments after +The refname value is relative to $GIT_DIR; e.g. for the master +head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after the update. Refs to be created will have sha1-old equal to 0{40}, while refs to be deleted will have sha1-new equal to 0{40}, otherwise @@ -86,13 +86,14 @@ post-receive Hook ----------------- After all refs were updated (or attempted to be updated), if any ref update was successful, and if $GIT_DIR/hooks/post-receive -file exists and is executable, it will be invoke once with three -parameters for each successfully updated ref: +file exists and is executable, it will be invoke once with no +parameters. The standard input of the hook will be one line +for each successfully updated ref: - $GIT_DIR/hooks/post-receive (refname sha1-old sha1-new)+ + sha1-old SP sha1-new SP refname LF -The refname parameter is relative to $GIT_DIR; e.g. for the master -head this is "refs/heads/master". The two sha1 arguments after +The refname value is relative to $GIT_DIR; e.g. for the master +head this is "refs/heads/master". The two sha1 values before each refname are the object names for the refname before and after the update. Refs that were created will have sha1-old equal to 0{40}, while refs that were deleted will have sha1-new equal to @@ -105,18 +106,17 @@ ref listing the commits pushed to the repository: #!/bin/sh # mail out commit update information. - while test $# -gt 0 + while read oval nval ref do - if expr "$2" : '0*$' >/dev/null + if expr "$oval" : '0*$' >/dev/null then echo "Created a new ref, with the following commits:" - git-rev-list --pretty "$2" + git-rev-list --pretty "$nval" else echo "New commits:" - git-rev-list --pretty "$3" "^$2" + git-rev-list --pretty "$nval" "^$oval" fi | - mail -s "Changes to ref $1" commit-list@mydomain - shift; shift; shift; # discard this ref's args + mail -s "Changes to ref $ref" commit-list@mydomain done exit 0 diff --git a/receive-pack.c b/receive-pack.c index 4d7170f6ad..7cf58782e3 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -79,6 +79,8 @@ static int hook_status(int code, const char *hook_name) return error("hook fork failed"); case -ERR_RUN_COMMAND_EXEC: return error("hook execute failed"); + case -ERR_RUN_COMMAND_PIPE: + return error("hook pipe failed"); case -ERR_RUN_COMMAND_WAITPID: return error("waitpid failed"); case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: @@ -93,44 +95,44 @@ static int hook_status(int code, const char *hook_name) } } -static int run_hook(const char *hook_name, - struct command *first_cmd, - int single) +static int run_hook(const char *hook_name) { + static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; - int argc, code; - const char **argv; + struct child_process proc; + const char *argv[2]; + int have_input = 0, code; - for (argc = 0, cmd = first_cmd; cmd; cmd = cmd->next) { + for (cmd = commands; !have_input && cmd; cmd = cmd->next) { if (!cmd->error_string) - argc += 3; - if (single) - break; + have_input = 1; } - if (!argc || access(hook_name, X_OK) < 0) + if (!have_input || access(hook_name, X_OK) < 0) return 0; - argv = xmalloc(sizeof(*argv) * (2 + argc)); argv[0] = hook_name; - for (argc = 1, cmd = first_cmd; cmd; cmd = cmd->next) { + argv[1] = NULL; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + + code = start_command(&proc); + if (code) + return hook_status(code, hook_name); + for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) { - argv[argc++] = xstrdup(cmd->ref_name); - argv[argc++] = xstrdup(sha1_to_hex(cmd->old_sha1)); - argv[argc++] = xstrdup(sha1_to_hex(cmd->new_sha1)); + size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", + sha1_to_hex(cmd->old_sha1), + sha1_to_hex(cmd->new_sha1), + cmd->ref_name); + if (write_in_full(proc.in, buf, n) != n) + break; } - if (single) - break; } - argv[argc] = NULL; - - code = run_command_v_opt(argv, - RUN_COMMAND_NO_STDIN | RUN_COMMAND_STDOUT_TO_STDERR); - while (--argc > 0) - free((char*)argv[argc]); - free(argv); - - return hook_status(code, hook_name); + return hook_status(finish_command(&proc), hook_name); } static int run_update_hook(struct command *cmd) @@ -265,7 +267,7 @@ static void execute_commands(const char *unpacker_error) return; } - if (run_hook(pre_receive_hook, commands, 0)) { + if (run_hook(pre_receive_hook)) { while (cmd) { cmd->error_string = "pre-receive hook declined"; cmd = cmd->next; @@ -520,7 +522,7 @@ int main(int argc, char **argv) unlink(pack_lockfile); if (report_status) report(unpack_status); - run_hook(post_receive_hook, commands, 0); + run_hook(post_receive_hook); run_update_post_hook(commands); } return 0; diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index cf6306ce9f..f1c7ff0c0a 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -25,8 +25,8 @@ test_expect_success setup ' cat >victim/.git/hooks/pre-receive <<'EOF' #!/bin/sh -echo "$@" >>$GIT_DIR/pre-receive.args -read x; printf "$x" >$GIT_DIR/pre-receive.stdin +printf "$@" >>$GIT_DIR/pre-receive.args +cat - >$GIT_DIR/pre-receive.stdin echo STDOUT pre-receive echo STDERR pre-receive >&2 EOF @@ -44,8 +44,8 @@ chmod u+x victim/.git/hooks/update cat >victim/.git/hooks/post-receive <<'EOF' #!/bin/sh -echo "$@" >>$GIT_DIR/post-receive.args -read x; printf "$x" >$GIT_DIR/post-receive.stdin +printf "$@" >>$GIT_DIR/post-receive.args +cat - >$GIT_DIR/post-receive.stdin echo STDOUT post-receive echo STDERR post-receive >&2 EOF @@ -80,11 +80,10 @@ test_expect_success 'hooks ran' ' test -f victim/.git/post-update.stdin ' -test_expect_success 'pre-receive hook arguments' ' - echo \ - refs/heads/master $commit0 $commit1 \ - refs/heads/tofail $commit1 $commit0 \ - | git diff - victim/.git/pre-receive.args +test_expect_success 'pre-receive hook input' ' + (echo $commit0 $commit1 refs/heads/master; + echo $commit1 $commit0 refs/heads/tofail + ) | git diff - victim/.git/pre-receive.stdin ' test_expect_success 'update hook arguments' ' @@ -93,9 +92,9 @@ test_expect_success 'update hook arguments' ' ) | git diff - victim/.git/update.args ' -test_expect_success 'post-receive hook arguments' ' - echo refs/heads/master $commit0 $commit1 | - git diff - victim/.git/post-receive.args +test_expect_success 'post-receive hook input' ' + echo $commit0 $commit1 refs/heads/master | + git diff - victim/.git/post-receive.stdin ' test_expect_success 'post-update hook arguments' ' @@ -104,12 +103,15 @@ test_expect_success 'post-update hook arguments' ' ' test_expect_success 'all hook stdin is /dev/null' ' - ! test -s victim/.git/pre-receive.stdin && ! test -s victim/.git/update.stdin && - ! test -s victim/.git/post-receive.stdin && ! test -s victim/.git/post-update.stdin ' +test_expect_success 'all *-receive hook args are empty' ' + ! test -s victim/.git/pre-receive.args && + ! test -s victim/.git/post-receive.args +' + test_expect_failure 'send-pack produced no output' ' test -s send.out ' From be242d576c4457bec1cb748b144eb287e98ac0b5 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Sun, 11 Mar 2007 12:28:56 -0400 Subject: [PATCH 40/68] git-merge: warn when -m provided on a fast forward Warn the user that the "-m" option is ignored in the case of a fast forward. That may save some confusion in the case where the user doesn't know about fast forwards yet and may not realize that the behavior here is intentional. Signed-off-by: "J. Bruce Fields" Signed-off-by: Junio C Hamano --- git-merge.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-merge.sh b/git-merge.sh index 4afcd95316..6ce62c860a 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -294,7 +294,12 @@ f,*) git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && - finish "$new_head" "Fast forward" || exit + msg="Fast forward" + if test -n "$have_message" + then + msg="$msg (no commit created; -m option ignored)" + fi + finish "$new_head" "$msg" || exit dropsave exit 0 ;; From fc095242b16d57bad1bfe089f919b9d6e6deda1b Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 11 Mar 2007 19:19:43 +0200 Subject: [PATCH 41/68] git-send-email: Document configuration options Wishing to implement an email aliases file, I found that they were already implmented. Document them for the next user. Signed-off-by: Avi Kivity Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 35b0104e4a..367646efab 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -91,6 +91,19 @@ The --cc option must be repeated for each user you want on the cc list. The --to option must be repeated for each user you want on the to list. +CONFIGURATION +------------- +sendemail.aliasesfile:: + To avoid typing long email addresses, point this to one or more + email aliases files. You must also supply 'sendemail.aliasfiletype'. + +sendemail.aliasfiletype:: + Format of the file(s) specified in sendemail.aliasesfile. Must be + one of 'mutt', 'mailrc', 'pine', or 'gnus'. + +sendemail.smtpserver:: + Default smtp server to use. + Author ------ Written by Ryan Anderson From 4a62d3f5b237fb9fab7df778ecf62906ff892065 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 11 Mar 2007 19:19:44 +0200 Subject: [PATCH 42/68] git-send-email: configurable bcc and chain-reply-to Chain-reply-to is a personal perference, and is unlikely to change from patchset to patchset. Similarly, bcc is likely to have the same values every invocation is one likes to bcc oneself. So, allow both to be set via configuration variables. Signed-off-by: Avi Kivity Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 10 +++++++++- git-send-email.perl | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 367646efab..9b3aabb6fe 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -40,7 +40,8 @@ The --cc option must be repeated for each user you want on the cc list. the first will be sent as replies to the first email sent. When using this, it is recommended that the first file given be an overview of the entire patch series. - Default is --chain-reply-to + Default is the value of the 'sendemail.chainreplyto' configuration + value; if that is unspecified, default to --chain-reply-to. --compose:: Use $EDITOR to edit an introductory message for the @@ -101,6 +102,13 @@ sendemail.aliasfiletype:: Format of the file(s) specified in sendemail.aliasesfile. Must be one of 'mutt', 'mailrc', 'pine', or 'gnus'. +sendemail.bcc:: + Email address (or alias) to always bcc. + +sendemail.chainreplyto:: + Boolean value specifying the default to the '--chain_reply_to' + parameter. + sendemail.smtpserver:: Default smtp server to use. diff --git a/git-send-email.perl b/git-send-email.perl index a71a192e4d..6989c0260f 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -149,6 +149,16 @@ if ($@) { $term = new FakeTerm "$@: going non-interactive"; } +my $def_chain = $repo->config_boolean('sendemail.chainreplyto'); +if ($def_chain and $def_chain eq 'false') { + $chain_reply_to = 0; +} + +@bcclist = $repo->config('sendemail.bcc'); +if (!@bcclist or !$bcclist[0]) { + @bcclist = (); +} + # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: From 34572ed2c809c2e0b00dc660bdb2dd201c5ff85f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Mar 2007 17:30:15 +0100 Subject: [PATCH 43/68] git-bundle: only die if pack would be empty, warn if ref is skipped A use case for git-bundle expected to be quite common is this: $ git bundle create daily.bundle --since=10.days.ago --all The expected outcome is _not_ to error out if only a couple of the refs were not changed during the last 10 days. This patch complains loudly about refs which are skipped due to the pack not containing the corresponding objects, but dies only if no objects would be in the pack _at all_. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-bundle.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin-bundle.c b/builtin-bundle.c index 55f6d0abcf..786808081b 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -263,7 +263,7 @@ static int create_bundle(struct bundle_header *header, const char *path, int bundle_fd = -1; const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); const char **argv_pack = xmalloc(5 * sizeof(const char *)); - int pid, in, out, i, status; + int pid, in, out, i, status, ref_count = 0; char buffer[1024]; struct rev_info revs; @@ -328,15 +328,20 @@ static int create_bundle(struct bundle_header *header, const char *path, * other limiting options could have prevented all the tips * from getting output. */ - if (!(e->item->flags & SHOWN)) - die("ref '%s' is excluded by the rev-list options", + if (!(e->item->flags & SHOWN)) { + warn("ref '%s' is excluded by the rev-list options", e->name); + continue; + } + ref_count++; write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); write_or_die(bundle_fd, " ", 1); write_or_die(bundle_fd, ref, strlen(ref)); write_or_die(bundle_fd, "\n", 1); free(ref); } + if (!ref_count) + die ("Refusing to create empty bundle."); /* end header */ write_or_die(bundle_fd, "\n", 1); From c7bafad10d294cc0b26ab6171efd89dd9b132f70 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 13:03:47 -0400 Subject: [PATCH 44/68] git-gui: Allow committing empty merges Johannes Sixt noticed that git-gui would not let the user commit a merge created by `git merge -s ours` as the ours strategy does not alter the tree (that is HEAD^1^{tree} = HEAD^{tree} after the merge). The same issue arises from amending such a merge commit. We now permit an empty commit (no changed files) if we are doing a merge commit. Core Git does this with its command line based git-commit tool, so it makes sense for the GUI to do the same. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 1981827a8e..0e448007f7 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1171,7 +1171,7 @@ File [short_path $path] cannot be committed by this program. } } } - if {!$files_ready} { + if {!$files_ready && ![string match *merge $curType]} { info_popup {No changes to commit. You must add at least 1 file before you can commit. From 56a7fde16ed060bc4169cc78308f8cf10f1d92a8 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 13:25:58 -0400 Subject: [PATCH 45/68] git-gui: Revert "Don't modify CREDITS-FILE if it hasn't changed." This reverts commit 92446aba47b0e0db28f7b858ea387efcca30ab44. Too many users have complained about the credits generator in git-gui, so I'm backing the entire thing out. Signed-off-by: Shawn O. Pearce --- CREDITS-GEN | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/CREDITS-GEN b/CREDITS-GEN index d1b0f86355..da2c07629e 100755 --- a/CREDITS-GEN +++ b/CREDITS-GEN @@ -20,8 +20,8 @@ tree_search () generate_credits () { tip=$1 && - rm -f "$2" && - git shortlog -n -s $tip | sed 's/: .*$//' >"$2" || exit + rm -f $CF && + git shortlog -n -s $tip | sed 's/: .*$//' >$CF || exit } # Always use the tarball credits file if found, just @@ -36,14 +36,10 @@ generate_credits () # that fact. # -credits_tmp=/var/tmp/gitgui-credits-$$ -trap 'rm -f "$credits_tmp"' 0 - -orig="$credits_tmp" - if test -f credits then - orig=credits + rm -f $CF && + cp credits $CF || exit elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && test -n "$prefix" && head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && @@ -51,21 +47,12 @@ elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && tip=$(tree_search $head $tree) && test -n "$tip" then - generate_credits $tip "$orig" || exit + generate_credits $tip || exit elif tip="$(git rev-parse --verify HEAD 2>/dev/null)" && test -n "$tip" then - generate_credits $tip "$orig" || exit + generate_credits $tip || exit else echo "error: Cannot locate authorship information." >&2 exit 1 fi - -if test -f "$orig" && cmp -s "$orig" "$CF" -then - : noop -else - rm -f "$CF" && - cat "$orig" >"$CF" -fi - From bb616ddd159508e6c6469626d494a69e28da7032 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 13:26:04 -0400 Subject: [PATCH 46/68] git-gui: Revert "git-gui: Display all authors of git-gui." This reverts commit 871f4c97ad7e021d1a0a98c80c5da77fcf70e4af. Too many users have complained about the credits generator in git-gui, so I'm backing the entire thing out. This revert will finish that series. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 - CREDITS-GEN | 58 ------------------------------------------------- Makefile | 23 ++++++++------------ git-gui.sh | 62 ----------------------------------------------------- 4 files changed, 9 insertions(+), 135 deletions(-) delete mode 100755 CREDITS-GEN diff --git a/.gitignore b/.gitignore index 805ca2e1c7..c714d382e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -CREDITS-FILE GIT-VERSION-FILE git-citool git-gui diff --git a/CREDITS-GEN b/CREDITS-GEN deleted file mode 100755 index da2c07629e..0000000000 --- a/CREDITS-GEN +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/sh - -CF=CREDITS-FILE -tip= - -tree_search () -{ - head=$1 - tree=$2 - for p in $(git rev-list --parents --max-count=1 $head 2>/dev/null) - do - test $tree = $(git rev-parse $p^{tree} 2>/dev/null) && - vn=$(git describe --abbrev=4 $p 2>/dev/null) && - case "$vn" in - gitgui-[0-9]*) echo $p; break;; - esac - done -} - -generate_credits () -{ - tip=$1 && - rm -f $CF && - git shortlog -n -s $tip | sed 's/: .*$//' >$CF || exit -} - -# Always use the tarball credits file if found, just -# in case we are somehow contained in a larger git -# repository that doesn't actually track our state. -# (At least one package manager is doing this.) -# -# We may be a subproject, so try looking for the merge -# commit that supplied this directory content if we are -# not at the toplevel. We probably will always be the -# second parent in the commit, but we shouldn't rely on -# that fact. -# - -if test -f credits -then - rm -f $CF && - cp credits $CF || exit -elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && - test -n "$prefix" && - head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && - tree=$(git rev-parse --verify "HEAD:$prefix" 2>/dev/null) && - tip=$(tree_search $head $tree) && - test -n "$tip" -then - generate_credits $tip || exit -elif tip="$(git rev-parse --verify HEAD 2>/dev/null)" && - test -n "$tip" -then - generate_credits $tip || exit -else - echo "error: Cannot locate authorship information." >&2 - exit 1 -fi diff --git a/Makefile b/Makefile index e486e8f984..d74fca2874 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,9 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE +SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool -ALL_PROGRAMS = git-gui $(GITGUI_BUILT_INS) +ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -31,24 +32,20 @@ DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) -git-gui: git-gui.sh GIT-VERSION-FILE CREDITS-FILE +$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)rm -f $@ $@+ && \ - sed -n \ - -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ - -e '1,/^set gitgui_credits /p' \ $@.sh >$@+ && \ - cat CREDITS-FILE >>$@+ && \ - sed -e '1,/^set gitgui_credits /d' $@.sh >>$@+ && \ chmod +x $@+ && \ mv $@+ $@ -CREDITS-FILE: CREDITS-GEN .FORCE-CREDITS-FILE - $(QUIET_GEN)$(SHELL_PATH) ./CREDITS-GEN - $(GITGUI_BUILT_INS): git-gui $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ +# These can record GITGUI_VERSION +$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE + all:: $(ALL_PROGRAMS) install: all @@ -56,14 +53,12 @@ install: all $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) -dist-version: CREDITS-FILE +dist-version: @mkdir -p $(TARDIR) @echo $(GITGUI_VERSION) > $(TARDIR)/version - @cat CREDITS-FILE > $(TARDIR)/credits clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE CREDITS-FILE + rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE -.PHONY: .FORCE-CREDITS-FILE diff --git a/git-gui.sh b/git-gui.sh index 0e448007f7..2888864e49 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -19,9 +19,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} -set gitgui_credits { -Paul Mackerras -} ###################################################################### ## @@ -4492,61 +4489,6 @@ proc do_commit {} { commit_tree } -proc do_credits {} { - global gitgui_credits - - set w .credits_dialog - - toplevel $w - wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - - label $w.header -text {git-gui Contributors} -font font_uibold - pack $w.header -side top -fill x - - frame $w.buttons - button $w.buttons.close -text {Close} \ - -font font_ui \ - -command [list destroy $w] - pack $w.buttons.close -side right - pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - - frame $w.credits - text $w.credits.t \ - -background [$w.header cget -background] \ - -yscrollcommand [list $w.credits.sby set] \ - -width 20 \ - -height 10 \ - -wrap none \ - -borderwidth 1 \ - -relief solid \ - -padx 5 -pady 5 \ - -font font_ui - scrollbar $w.credits.sby -command [list $w.credits.t yview] - pack $w.credits.sby -side right -fill y - pack $w.credits.t -fill both -expand 1 - pack $w.credits -side top -fill both -expand 1 -padx 5 -pady 5 - - label $w.desc \ - -text "All portions are copyrighted by their respective authors -and are distributed under the GNU General Public License." \ - -padx 5 -pady 5 \ - -justify left \ - -anchor w \ - -borderwidth 1 \ - -relief solid \ - -font font_ui - pack $w.desc -side top -fill x -padx 5 -pady 5 - - $w.credits.t insert end "[string trim $gitgui_credits]\n" - $w.credits.t conf -state disabled - $w.credits.t see 1.0 - - bind $w "grab $w; focus $w" - bind $w [list destroy $w] - wm title $w [$w.header cget -text] - tkwait window $w -} - proc do_about {} { global appvers copyright global tcl_patchLevel tk_patchLevel @@ -4563,10 +4505,6 @@ proc do_about {} { button $w.buttons.close -text {Close} \ -font font_ui \ -command [list destroy $w] - button $w.buttons.credits -text {Contributors} \ - -font font_ui \ - -command do_credits - pack $w.buttons.credits -side left pack $w.buttons.close -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 From 756d846fea4ac4b3bf0a0aea9fbf721138cf34cc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 13:24:10 -0400 Subject: [PATCH 47/68] git-gui: Allow 'git gui version' outside of a repository I got a little surprise one day when I tried to run 'git gui version' outside of a Git repository to determine what version of git-gui was installed on that system. Turns out we were doing the repository check long before we got around to command line argument handling. We now look to see if the only argument we have been given is 'version' or '--version', and if so, print out the version and exit immediately; long before we consider looking at the Git version or working directory. This way users can still get to the git-gui version number even if Git's version cannot be read. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 2888864e49..60e79ca1b0 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -299,6 +299,11 @@ proc ask_popup {msg} { ## ## version check +if {{--version} eq $argv || {version} eq $argv} { + puts "git-gui version $appvers" + exit +} + set req_maj 1 set req_min 5 @@ -5054,8 +5059,6 @@ enable_option branch enable_option transport switch -- $subcommand { ---version - -version - browser - blame { disable_option multicommit @@ -5426,11 +5429,6 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} # -- Not a normal commit type invocation? Do that instead! # switch -- $subcommand { ---version - -version { - puts "git-gui version $appvers" - exit -} browser { if {[llength $argv] != 1} { puts stderr "usage: $argv0 browser commit" From 1358e7d670306a9fe8a612f1ebd1f058474d20af Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 12 Mar 2007 11:30:38 -0700 Subject: [PATCH 48/68] Re-fix get_sha1_oneline() What the function wants to return is not if we saw any return from pop_most_recent_commit(), but if we found what was asked for. Signed-off-by: Junio C Hamano --- sha1_name.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 6b8b67b4db..bede0e5b06 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -602,10 +602,10 @@ static int handle_one_ref(const char *path, */ #define ONELINE_SEEN (1u<<20) -int get_sha1_oneline(const char *prefix, unsigned char *sha1) +static int get_sha1_oneline(const char *prefix, unsigned char *sha1) { struct commit_list *list = NULL, *backup = NULL, *l; - struct commit *commit = NULL; + int retval = -1; if (prefix[0] == '!') { if (prefix[1] != '!') @@ -619,22 +619,22 @@ int get_sha1_oneline(const char *prefix, unsigned char *sha1) commit_list_insert(l->item, &backup); while (list) { char *p; + struct commit *commit; commit = pop_most_recent_commit(&list, ONELINE_SEEN); - if (!commit) - break; parse_object(commit->object.sha1); if (!commit->buffer || !(p = strstr(commit->buffer, "\n\n"))) continue; if (!prefixcmp(p + 2, prefix)) { hashcpy(sha1, commit->object.sha1); + retval = 0; break; } } free_commit_list(list); for (l = backup; l; l = l->next) clear_commit_marks(l->item, ONELINE_SEEN); - return commit == NULL; + return retval; } /* From 115f0fe49951b951495bdcd0420a44fb11c2173a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 13:40:31 -0400 Subject: [PATCH 49/68] Don't package the git-gui credits file anymore Since git-gui 0.6.4 the credits file is no longer produced. This file was removed from git-gui due to build issues that a lot of users and Git developers have reported running into. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f674e556ff..eeb502fa54 100644 --- a/Makefile +++ b/Makefile @@ -901,8 +901,7 @@ dist: git.spec git-archive $(TAR) rf $(GIT_TARNAME).tar \ $(GIT_TARNAME)/git.spec \ $(GIT_TARNAME)/version \ - $(GIT_TARNAME)/git-gui/version \ - $(GIT_TARNAME)/git-gui/credits + $(GIT_TARNAME)/git-gui/version @rm -rf $(GIT_TARNAME) gzip -f -9 $(GIT_TARNAME).tar From f022f85f6d50b66ac5f4c49830a040627a0d8194 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Mar 2007 21:39:17 -0500 Subject: [PATCH 50/68] fast-import: grow tree storage more aggressively When building up a tree for a commit, fast-import dynamically allocates memory for the tree entries. When more space is needed, the allocated memory is increased by a constant amount. For very large trees, this means re-allocating and memcpy()ing the memory O(n) times. To compound this problem, releasing the previous tree resource does not free the memory; it is kept in a pool for future trees. This means that each of the O(n) allocations will consume increasing amounts of memory, giving O(n^2) memory consumption. Signed-off-by: Jeff King Signed-off-by: Shawn O. Pearce --- fast-import.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fast-import.c b/fast-import.c index d9492b9884..ac3714596a 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1062,7 +1062,7 @@ static void load_tree(struct tree_entry *root) struct tree_entry *e = new_tree_entry(); if (t->entry_count == t->entry_capacity) - root->tree = t = grow_tree_content(t, 8); + root->tree = t = grow_tree_content(t, t->entry_count); t->entries[t->entry_count++] = e; e->tree = NULL; @@ -1229,7 +1229,7 @@ static int tree_content_set( } if (t->entry_count == t->entry_capacity) - root->tree = t = grow_tree_content(t, 8); + root->tree = t = grow_tree_content(t, t->entry_count); e = new_tree_entry(); e->name = to_atom(p, (unsigned short)n); e->versions[0].mode = 0; From e7411303861f02a28b76a4c43451c427a3439a5c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 12 Mar 2007 14:58:50 -0400 Subject: [PATCH 51/68] New fast-import test case for valid tree sorting The Git tree sorting convention is more complex than just the name, it needs to include the mode too to make sure trees sort as though their name ends with "/". This is a simple test case that verifies fast-import keeps the tree ordering correct after editing the same tree twice in a single input stream. A recent proposed patch series (that has not yet been applied) will cause this test to fail, due to a bug in the way the series handles sorting within the trees. Signed-off-by: Jeff King Signed-off-by: Shawn O. Pearce --- t/t9300-fast-import.sh | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 03f2f8f347..8e958da536 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -501,4 +501,54 @@ test_expect_success \ 'test `git-rev-parse --verify branch^1` \ = `git-rev-parse --verify K^1`' +### +### series L +### + +cat >input < $GIT_COMMITTER_DATE +data < $GIT_COMMITTER_DATE +data <expect <output && + git diff expect output' + test_done From 061e35c581cb75cb228640a4e40f0d4a5986c11c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 15:48:37 -0400 Subject: [PATCH 52/68] Remove unnecessary casts from fast-import Jeff King pointed out that these casts are quite unnecessary, as the compiler should be doing them anyway, and may cause problems in the future if the size of the argument for to_atom were to ever be increased. Signed-off-by: Shawn O. Pearce --- fast-import.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fast-import.c b/fast-import.c index 240ef266ed..55ffae4fa6 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1066,7 +1066,7 @@ static void load_tree(struct tree_entry *root) if (!c) die("Corrupt mode in %s", sha1_to_hex(sha1)); e->versions[0].mode = e->versions[1].mode; - e->name = to_atom(c, (unsigned short)strlen(c)); + e->name = to_atom(c, strlen(c)); c += e->name->str_len + 1; hashcpy(e->versions[0].sha1, (unsigned char*)c); hashcpy(e->versions[1].sha1, (unsigned char*)c); @@ -1227,7 +1227,7 @@ static int tree_content_set( if (t->entry_count == t->entry_capacity) root->tree = t = grow_tree_content(t, t->entry_count); e = new_tree_entry(); - e->name = to_atom(p, (unsigned short)n); + e->name = to_atom(p, n); e->versions[0].mode = 0; hashclr(e->versions[0].sha1); t->entries[t->entry_count++] = e; From 9dc09c766463333ca51311a2db86fd67f4fec19b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 14:37:28 -0400 Subject: [PATCH 53/68] Simplify closing two fds at once in run-command.c I started hacking on a change to add stdout redirection support to the run_command family, but found I was using a lot of close calls on two pipes in an array (such as for pipe). So I'm doing a tiny bit of refactoring first to make the next set of changes clearer. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- run-command.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/run-command.c b/run-command.c index 03ff7bcac2..bef9775e08 100644 --- a/run-command.c +++ b/run-command.c @@ -2,6 +2,12 @@ #include "run-command.h" #include "exec_cmd.h" +static inline void close_pair(int fd[2]) +{ + close(fd[0]); + close(fd[1]); +} + int start_command(struct child_process *cmd) { int need_in = !cmd->no_stdin && cmd->in < 0; @@ -16,10 +22,8 @@ int start_command(struct child_process *cmd) cmd->pid = fork(); if (cmd->pid < 0) { - if (need_in) { - close(fdin[0]); - close(fdin[1]); - } + if (need_in) + close_pair(fdin); return -ERR_RUN_COMMAND_FORK; } @@ -30,8 +34,7 @@ int start_command(struct child_process *cmd) close(fd); } else if (need_in) { dup2(fdin[0], 0); - close(fdin[0]); - close(fdin[1]); + close_pair(fdin); } else if (cmd->in) { dup2(cmd->in, 0); close(cmd->in); From 6016e35bc18e801a7bd855b1581da6c7fd755470 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Mar 2007 18:59:16 -0400 Subject: [PATCH 54/68] Fix t5510-fetch's use of sed POSIX says sed may add a trailing LF if there isn't already one there. We shouldn't rely on it not adding that LF, as some systems (Mac OS X for example) will add it. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t5510-fetch.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ee3f397a9b..426017e1d0 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -134,7 +134,13 @@ test_expect_success 'bundle does not prerequisite objects' ' git add file2 && git commit -m add.file2 file2 && git bundle create bundle3 -1 HEAD && - sed "1,4d" < bundle3 > bundle.pack && + ( + while read x && test -n "$x" + do + :; + done + cat + ) bundle.pack && git index-pack bundle.pack && test 4 = $(git verify-pack -v bundle.pack | wc -l) ' From 87ab799234639c26ea10de74782fa511cb3ca606 Mon Sep 17 00:00:00 2001 From: Don Zickus Date: Mon, 12 Mar 2007 15:52:04 -0400 Subject: [PATCH 55/68] builtin-mailinfo.c infrastrcture changes I am working on a project that required parsing through regular mboxes that didn't necessarily have patches embedded in them. I started by creating my own modified copy of git-am and working from there. Very quickly, I noticed git-mailinfo wasn't able to handle a big chunk of my email. After hacking up numerous solutions and running into more limitations, I decided it was just easier to rewrite a big chunk of it. The following patch has a bunch of fixes and features that I needed in order for me do what I wanted. Note: I'm didn't follow any email rfc papers but I don't think any of the changes I did required much knowledge (besides the boundary stuff). List of major changes/fixes: - can't create empty patch files fix - empty patch files don't fail, this failure will come inside git-am - multipart boundaries are now handled - only output inbody headers if a patch exists otherwise assume those headers are part of the reply and instead output the original headers - decode and filter base64 patches correctly - various other accidental fixes I believe I didn't break any existing functionality or compatibility (other than what I describe above, which is really only the empty patch file). I tested this through various mailing list archives and everything seemed to parse correctly (a couple thousand emails). [jc: squashed in another patch from Don's five patch series to fix the test case, as this patch exposes the bug in the test.] Signed-off-by: Don Zickus Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 549 ++++++++++++++++++++++++--------------------- git-am.sh | 4 + git-applymbox.sh | 4 + git-quiltimport.sh | 4 + t/t5100/patch0005 | 2 +- 5 files changed, 301 insertions(+), 262 deletions(-) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index f54e8752fb..3f5cb8719b 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -11,19 +11,22 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; static int keep_subject; static const char *metainfo_charset; static char line[1000]; -static char date[1000]; static char name[1000]; static char email[1000]; -static char subject[1000]; static enum { TE_DONTCARE, TE_QP, TE_BASE64, } transfer_encoding; -static char charset[256]; +static enum { + TYPE_TEXT, TYPE_OTHER, +} message_type; -static char multipart_boundary[1000]; -static int multipart_boundary_len; +static char charset[256]; static int patch_lines; +static char **p_hdr_data, **s_hdr_data; + +#define MAX_HDR_PARSED 10 +#define MAX_BOUNDARIES 5 static char *sanity_check(char *name, char *email) { @@ -137,15 +140,13 @@ static int handle_from(char *in_line) return 1; } -static int handle_date(char *line) +static int handle_header(char *line, char *data, int ofs) { - strcpy(date, line); - return 0; -} + if (!line || !data) + return 1; + + strcpy(data, line+ofs); -static int handle_subject(char *line) -{ - strcpy(subject, line); return 0; } @@ -177,17 +178,32 @@ static int slurp_attr(const char *line, const char *name, char *attr) return 1; } -static int handle_subcontent_type(char *line) +struct content_type { + char *boundary; + int boundary_len; +}; + +static struct content_type content[MAX_BOUNDARIES]; + +static struct content_type *content_top = content; + +static int handle_content_type(char *line) { - /* We do not want to mess with boundary. Note that we do not - * handle nested multipart. - */ - if (strcasestr(line, "boundary=")) { - fprintf(stderr, "Not handling nested multipart message.\n"); - exit(1); + char boundary[256]; + + if (strcasestr(line, "text/") == NULL) + message_type = TYPE_OTHER; + if (slurp_attr(line, "boundary=", boundary + 2)) { + memcpy(boundary, "--", 2); + if (content_top++ >= &content[MAX_BOUNDARIES]) { + fprintf(stderr, "Too many boundaries to handle\n"); + exit(1); + } + content_top->boundary_len = strlen(boundary); + content_top->boundary = xmalloc(content_top->boundary_len+1); + strcpy(content_top->boundary, boundary); } - slurp_attr(line, "charset=", charset); - if (*charset) { + if (slurp_attr(line, "charset=", charset)) { int i, c; for (i = 0; (c = charset[i]) != 0; i++) charset[i] = tolower(c); @@ -195,17 +211,6 @@ static int handle_subcontent_type(char *line) return 0; } -static int handle_content_type(char *line) -{ - *multipart_boundary = 0; - if (slurp_attr(line, "boundary=", multipart_boundary + 2)) { - memcpy(multipart_boundary, "--", 2); - multipart_boundary_len = strlen(multipart_boundary); - } - slurp_attr(line, "charset=", charset); - return 0; -} - static int handle_content_transfer_encoding(char *line) { if (strcasestr(line, "base64")) @@ -219,7 +224,7 @@ static int handle_content_transfer_encoding(char *line) static int is_multipart_boundary(const char *line) { - return (!memcmp(line, multipart_boundary, multipart_boundary_len)); + return (!memcmp(line, content_top->boundary, content_top->boundary_len)); } static int eatspace(char *line) @@ -230,62 +235,6 @@ static int eatspace(char *line) return len; } -#define SEEN_FROM 01 -#define SEEN_DATE 02 -#define SEEN_SUBJECT 04 -#define SEEN_BOGUS_UNIX_FROM 010 -#define SEEN_PREFIX 020 - -/* First lines of body can have From:, Date:, and Subject: or empty */ -static void handle_inbody_header(int *seen, char *line) -{ - if (*seen & SEEN_PREFIX) - return; - if (isspace(*line)) { - char *cp; - for (cp = line + 1; *cp; cp++) { - if (!isspace(*cp)) - break; - } - if (!*cp) - return; - } - if (!memcmp(">From", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_BOGUS_UNIX_FROM)) { - *seen |= SEEN_BOGUS_UNIX_FROM; - return; - } - } - if (!memcmp("From:", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_FROM) && handle_from(line+6)) { - *seen |= SEEN_FROM; - return; - } - } - if (!memcmp("Date:", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_DATE)) { - handle_date(line+6); - *seen |= SEEN_DATE; - return; - } - } - if (!memcmp("Subject:", line, 8) && isspace(line[8])) { - if (!(*seen & SEEN_SUBJECT)) { - handle_subject(line+9); - *seen |= SEEN_SUBJECT; - return; - } - } - if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { - if (!(*seen & SEEN_SUBJECT)) { - handle_subject(line); - *seen |= SEEN_SUBJECT; - return; - } - } - *seen |= SEEN_PREFIX; -} - static char *cleanup_subject(char *subject) { if (keep_subject) @@ -296,7 +245,7 @@ static char *cleanup_subject(char *subject) switch (*subject) { case 'r': case 'R': if (!memcmp("e:", subject+1, 2)) { - subject +=3; + subject += 3; continue; } break; @@ -341,57 +290,62 @@ static void cleanup_space(char *buf) } static void decode_header(char *it); -typedef int (*header_fn_t)(char *); -struct header_def { - const char *name; - header_fn_t func; - int namelen; +static char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", }; -static void check_header(char *line, struct header_def *header) +static int check_header(char *line, char **hdr_data) { int i; - if (header[0].namelen <= 0) { - for (i = 0; header[i].name; i++) - header[i].namelen = strlen(header[i].name); - } - for (i = 0; header[i].name; i++) { - int len = header[i].namelen; - if (!strncasecmp(line, header[i].name, len) && + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if (!hdr_data[i] && + !strncasecmp(line, header[i], len) && line[len] == ':' && isspace(line[len + 1])) { /* Unwrap inline B and Q encoding, and optionally * normalize the meta information to utf8. */ decode_header(line + len + 2); - header[i].func(line + len + 2); - break; + hdr_data[i] = xmalloc(1000 * sizeof(char)); + if (! handle_header(line, hdr_data[i], len + 2)) { + return 1; + } } } -} -static void check_subheader_line(char *line) -{ - static struct header_def header[] = { - { "Content-Type", handle_subcontent_type }, - { "Content-Transfer-Encoding", - handle_content_transfer_encoding }, - { NULL }, - }; - check_header(line, header); -} -static void check_header_line(char *line) -{ - static struct header_def header[] = { - { "From", handle_from }, - { "Date", handle_date }, - { "Subject", handle_subject }, - { "Content-Type", handle_content_type }, - { "Content-Transfer-Encoding", - handle_content_transfer_encoding }, - { NULL }, - }; - check_header(line, header); + /* Content stuff */ + if (!strncasecmp(line, "Content-Type", 12) && + line[12] == ':' && isspace(line[12 + 1])) { + decode_header(line + 12 + 2); + if (! handle_content_type(line)) { + return 1; + } + } + if (!strncasecmp(line, "Content-Transfer-Encoding", 25) && + line[25] == ':' && isspace(line[25 + 1])) { + decode_header(line + 25 + 2); + if (! handle_content_transfer_encoding(line)) { + return 1; + } + } + + /* for inbody stuff */ + if (!memcmp(">From", line, 5) && isspace(line[5])) + return 1; + if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { + for (i = 0; header[i]; i++) { + if (!memcmp("Subject: ", header[i], 9)) { + if (! handle_header(line, hdr_data[i], 0)) { + return 1; + } + } + } + } + + /* no match */ + return 0; } static int is_rfc2822_header(char *line) @@ -647,39 +601,139 @@ static void decode_transfer_encoding(char *line) } } -static void handle_info(void) +static int handle_filter(char *line); + +static int find_boundary(void) { - char *sub; - - sub = cleanup_subject(subject); - cleanup_space(name); - cleanup_space(date); - cleanup_space(email); - cleanup_space(sub); - - fprintf(fout, "Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", - name, email, sub, date); + while(fgets(line, sizeof(line), fin) != NULL) { + if (is_multipart_boundary(line)) + return 1; + } + return 0; } -/* We are inside message body and have read line[] already. - * Spit out the commit log. - */ -static int handle_commit_msg(int *seen) +static int handle_boundary(void) { +again: + if (!memcmp(line+content_top->boundary_len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + free(content_top->boundary); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (content_top-- < content) { + fprintf(stderr, "Detected mismatched boundaries, " + "can't recover\n"); + exit(1); + } + handle_filter("\n"); + + /* skip to the next boundary */ + if (!find_boundary()) + return 0; + goto again; + } + + /* set some defaults */ + transfer_encoding = TE_DONTCARE; + charset[0] = 0; + message_type = TYPE_TEXT; + + /* slurp in this section's info */ + while (read_one_header_line(line, sizeof(line), fin)) + check_header(line, p_hdr_data); + + /* eat the blank line after section info */ + return (fgets(line, sizeof(line), fin) != NULL); +} + +static int handle_commit_msg(char *line) +{ + static int still_looking = 1; + if (!cmitmsg) return 0; - do { - if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3) || - !memcmp("Index: ", line, 7)) + + if (still_looking) { + char *cp = line; + if (isspace(*line)) { + for (cp = line + 1; *cp; cp++) { + if (!isspace(*cp)) + break; + } + if (!*cp) + return 0; + } + if ((still_looking = check_header(cp, s_hdr_data)) != 0) + return 0; + } + + if (!memcmp("diff -", line, 6) || + !memcmp("---", line, 3) || + !memcmp("Index: ", line, 7)) { + fclose(cmitmsg); + cmitmsg = NULL; + return 1; + } + + fputs(line, cmitmsg); + return 0; +} + +static int handle_patch(char *line) +{ + fputs(line, patchfile); + patch_lines++; + return 0; +} + +static int handle_filter(char *line) +{ + static int filter = 0; + + /* filter tells us which part we left off on + * a non-zero return indicates we hit a filter point + */ + switch (filter) { + case 0: + if (!handle_commit_msg(line)) break; - if ((multipart_boundary[0] && is_multipart_boundary(line))) { - /* We come here when the first part had only - * the commit message without any patch. We - * pretend we have not seen this line yet, and - * go back to the loop. - */ - return 1; + filter++; + case 1: + if (!handle_patch(line)) + break; + filter++; + default: + return 1; + } + + return 0; +} + +static void handle_body(void) +{ + int rc = 0; + static char newline[2000]; + static char *np = newline; + + /* Skip up to the first boundary */ + if (content_top->boundary) { + if (!find_boundary()) + return; + } + + do { + /* process any boundary lines */ + if (content_top->boundary && is_multipart_boundary(line)) { + /* flush any leftover */ + if ((transfer_encoding == TE_BASE64) && + (np != newline)) { + handle_filter(newline); + } + if (!handle_boundary()) + return; } /* Unwrap transfer encoding and optionally @@ -689,105 +743,80 @@ static int handle_commit_msg(int *seen) if (metainfo_charset) convert_to_utf8(line, charset); - handle_inbody_header(seen, line); - if (!(*seen & SEEN_PREFIX)) + switch (transfer_encoding) { + case TE_BASE64: + { + char *op = line; + + /* binary data most likely doesn't have newlines */ + if (message_type != TYPE_TEXT) { + rc = handle_filter(line); + break; + } + + /* this is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + + do { + while (*op != '\n' && *op != 0) + *np++ = *op++; + *np = *op; + if (*np != 0) { + /* should be sitting on a new line */ + *(++np) = 0; + op++; + rc = handle_filter(newline); + np = newline; + } + } while (*op != 0); + /* the partial chunk is saved in newline and + * will be appended by the next iteration of fgets + */ + break; + } + default: + rc = handle_filter(line); + } + if (rc) + /* nothing left to filter */ + break; + } while (fgets(line, sizeof(line), fin)); + + return; +} + +static void handle_info(void) +{ + char *sub; + char *hdr; + int i; + + for (i = 0; header[i]; i++) { + + /* only print inbody headers if we output a patch file */ + if (patch_lines && s_hdr_data[i]) + hdr = s_hdr_data[i]; + else if (p_hdr_data[i]) + hdr = p_hdr_data[i]; + else continue; - fputs(line, cmitmsg); - } while (fgets(line, sizeof(line), fin) != NULL); - fclose(cmitmsg); - cmitmsg = NULL; - return 0; -} - -/* We have done the commit message and have the first - * line of the patch in line[]. - */ -static void handle_patch(void) -{ - do { - if (multipart_boundary[0] && is_multipart_boundary(line)) - break; - /* Only unwrap transfer encoding but otherwise do not - * do anything. We do *NOT* want UTF-8 conversion - * here; we are dealing with the user payload. - */ - decode_transfer_encoding(line); - fputs(line, patchfile); - patch_lines++; - } while (fgets(line, sizeof(line), fin) != NULL); -} - -/* multipart boundary and transfer encoding are set up for us, and we - * are at the end of the sub header. do equivalent of handle_body up - * to the next boundary without closing patchfile --- we will expect - * that the first part to contain commit message and a patch, and - * handle other parts as pure patches. - */ -static int handle_multipart_one_part(int *seen) -{ - int n = 0; - - while (fgets(line, sizeof(line), fin) != NULL) { - again: - n++; - if (is_multipart_boundary(line)) - break; - if (handle_commit_msg(seen)) - goto again; - handle_patch(); - break; - } - if (n == 0) - return -1; - return 0; -} - -static void handle_multipart_body(void) -{ - int seen = 0; - int part_num = 0; - - /* Skip up to the first boundary */ - while (fgets(line, sizeof(line), fin) != NULL) - if (is_multipart_boundary(line)) { - part_num = 1; - break; + if (!memcmp(header[i], "Subject", 7)) { + sub = cleanup_subject(hdr); + cleanup_space(sub); + fprintf(fout, "Subject: %s\n", sub); + } else if (!memcmp(header[i], "From", 4)) { + handle_from(hdr); + fprintf(fout, "Author: %s\n", name); + fprintf(fout, "Email: %s\n", email); + } else { + cleanup_space(hdr); + fprintf(fout, "%s: %s\n", header[i], hdr); } - if (!part_num) - return; - /* We are on boundary line. Start slurping the subhead. */ - while (1) { - int hdr = read_one_header_line(line, sizeof(line), fin); - if (!hdr) { - if (handle_multipart_one_part(&seen) < 0) - return; - /* Reset per part headers */ - transfer_encoding = TE_DONTCARE; - charset[0] = 0; - } - else - check_subheader_line(line); - } - fclose(patchfile); - if (!patch_lines) { - fprintf(stderr, "No patch found\n"); - exit(1); - } -} - -/* Non multipart message */ -static void handle_body(void) -{ - int seen = 0; - - handle_commit_msg(&seen); - handle_patch(); - fclose(patchfile); - if (!patch_lines) { - fprintf(stderr, "No patch found\n"); - exit(1); } + fprintf(fout, "\n"); } int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, @@ -809,18 +838,16 @@ int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, fclose(cmitmsg); return -1; } - while (1) { - int hdr = read_one_header_line(line, sizeof(line), fin); - if (!hdr) { - if (multipart_boundary[0]) - handle_multipart_body(); - else - handle_body(); - handle_info(); - break; - } - check_header_line(line); - } + + p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); + s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); + + /* process the email header */ + while (read_one_header_line(line, sizeof(line), fin)) + check_header(line, p_hdr_data); + + handle_body(); + handle_info(); return 0; } diff --git a/git-am.sh b/git-am.sh index 2c73d116b2..88af8dd256 100755 --- a/git-am.sh +++ b/git-am.sh @@ -290,6 +290,10 @@ do git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ <"$dotest/$msgnum" >"$dotest/info" || stop_here $this + test -s $dotest/patch || { + echo "Patch is empty. Was is split wrong?" + stop_here $this + } git-stripspace < "$dotest/msg" > "$dotest/msg-clean" ;; esac diff --git a/git-applymbox.sh b/git-applymbox.sh index 1f68599ae5..2cbdc7eb3c 100755 --- a/git-applymbox.sh +++ b/git-applymbox.sh @@ -77,6 +77,10 @@ do *) git-mailinfo $keep_subject $utf8 \ .dotest/msg .dotest/patch <$i >.dotest/info || exit 1 + test -s $dotest/patch || { + echo "Patch is empty. Was is split wrong?" + stop_here $this + } git-stripspace < .dotest/msg > .dotest/msg-clean ;; esac diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 671a5ff865..08ac9bb729 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -73,6 +73,10 @@ mkdir $tmp_dir || exit 2 for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do echo $patch_name (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3 + test -s $dotest/patch || { + echo "Patch is empty. Was is split wrong?" + stop_here $this + } # Parse the author information export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info") diff --git a/t/t5100/patch0005 b/t/t5100/patch0005 index 7d24b24af8..e7d6f66608 100644 --- a/t/t5100/patch0005 +++ b/t/t5100/patch0005 @@ -61,7 +61,7 @@ diff --git a/git-cvsimport-script b/git-cvsimport-script push(@old,$fn); -- -David Kågedal +David KÃ¥gedal - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org From f0658cf2106074b2465a5de0e20f459f966a9e18 Mon Sep 17 00:00:00 2001 From: Don Zickus Date: Mon, 12 Mar 2007 15:52:06 -0400 Subject: [PATCH 56/68] restrict the patch filtering I have come across many emails that use long strings of '-'s as separators for ideas. This patch below limits the separator to only 3 '-', with the intent that long string of '-'s will stay in the commit msg and not in the patch file. Signed-off-by: Don Zickus Acked-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 3f5cb8719b..d94578cb4a 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -649,6 +649,39 @@ again: return (fgets(line, sizeof(line), fin) != NULL); } +static inline int patchbreak(const char *line) +{ + /* Beginning of a "diff -" header? */ + if (!memcmp("diff -", line, 6)) + return 1; + + /* CVS "Index: " line? */ + if (!memcmp("Index: ", line, 7)) + return 1; + + /* + * "--- " starts patches without headers + * "---*" is a manual separator + */ + if (!memcmp("---", line, 3)) { + line += 3; + /* space followed by a filename? */ + if (line[0] == ' ' && !isspace(line[1])) + return 1; + /* Just whitespace? */ + for (;;) { + unsigned char c = *line++; + if (c == '\n') + return 1; + if (!isspace(c)) + break; + } + return 0; + } + return 0; +} + + static int handle_commit_msg(char *line) { static int still_looking = 1; @@ -670,9 +703,7 @@ static int handle_commit_msg(char *line) return 0; } - if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3) || - !memcmp("Index: ", line, 7)) { + if (patchbreak(line)) { fclose(cmitmsg); cmitmsg = NULL; return 1; From ae1a7437355a1fbc907b6d6cad9ed79227c29164 Mon Sep 17 00:00:00 2001 From: Don Zickus Date: Mon, 12 Mar 2007 15:52:07 -0400 Subject: [PATCH 57/68] Add a couple more test cases to the suite. They handle cases where there is no attached patch. Signed-off-by: Don Zickus Signed-off-by: Junio C Hamano --- t/t5100-mailinfo.sh | 2 +- t/t5100/info0007 | 5 +++++ t/t5100/info0008 | 5 +++++ t/t5100/msg0007 | 2 ++ t/t5100/msg0008 | 4 ++++ t/t5100/patch0007 | 0 t/t5100/patch0008 | 0 t/t5100/sample.mbox | 18 ++++++++++++++++++ 8 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 t/t5100/info0007 create mode 100644 t/t5100/info0008 create mode 100644 t/t5100/msg0007 create mode 100644 t/t5100/msg0008 create mode 100644 t/t5100/patch0007 create mode 100644 t/t5100/patch0008 diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 4d2b781a18..ca96918da2 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git-mailsplit -o. ../t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 6' + test `cat last` = 8' for mail in `echo 00*` do diff --git a/t/t5100/info0007 b/t/t5100/info0007 new file mode 100644 index 0000000000..49bb0fec85 --- /dev/null +++ b/t/t5100/info0007 @@ -0,0 +1,5 @@ +Author: A U Thor +Email: a.u.thor@example.com +Subject: another patch +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/info0008 b/t/t5100/info0008 new file mode 100644 index 0000000000..e8a2951383 --- /dev/null +++ b/t/t5100/info0008 @@ -0,0 +1,5 @@ +Author: Junio C Hamano +Email: junio@kernel.org +Subject: another patch +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/msg0007 b/t/t5100/msg0007 new file mode 100644 index 0000000000..71b23c0236 --- /dev/null +++ b/t/t5100/msg0007 @@ -0,0 +1,2 @@ +Here is an empty patch from A U Thor. + diff --git a/t/t5100/msg0008 b/t/t5100/msg0008 new file mode 100644 index 0000000000..a80ecb97ef --- /dev/null +++ b/t/t5100/msg0008 @@ -0,0 +1,4 @@ +>Here is an empty patch from A U Thor. + +Hey you forgot the patch! + diff --git a/t/t5100/patch0007 b/t/t5100/patch0007 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/t/t5100/patch0008 b/t/t5100/patch0008 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 86bfc27147..b80c981c16 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -386,3 +386,21 @@ index 9123cdc..918dcf8 100644 -- 1.4.0.g6f2b +From nobody Mon Sep 17 00:00:00 2001 +From: A U Thor +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: [PATCH] another patch + +Here is an empty patch from A U Thor. + +From nobody Mon Sep 17 00:00:00 2001 +From: Junio C Hamano +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: re: [PATCH] another patch + +From: A U Thor +Subject: [PATCH] another patch +>Here is an empty patch from A U Thor. + +Hey you forgot the patch! + From c4b4a5af1604a7546c0b0786e47bcff8a47a6039 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 6 Mar 2007 00:05:16 -0500 Subject: [PATCH 58/68] Add git-mergetool to run an appropriate merge conflict resolution program The git-mergetool program can be used to automatically run an appropriate merge resolution program to resolve merge conflicts. It will automatically run one of kdiff3, tkdiff, meld, xxdiff, or emacs emerge programs. Signed-off-by: "Theodore Ts'o" --- .gitignore | 1 + Documentation/config.txt | 5 + Documentation/git-mergetool.txt | 46 +++++ Makefile | 2 +- git-mergetool.sh | 352 ++++++++++++++++++++++++++++++++ 5 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 Documentation/git-mergetool.txt create mode 100755 git-mergetool.sh diff --git a/.gitignore b/.gitignore index 0eaba0a278..27797d1491 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ git-merge-ours git-merge-recursive git-merge-resolve git-merge-stupid +git-mergetool git-mktag git-mktree git-name-rev diff --git a/Documentation/config.txt b/Documentation/config.txt index 5408dd67d3..aaae9ac305 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -453,6 +453,11 @@ merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. +merge.tool:: + Controls which merge resolution program is used by + gitlink:git-mergetool[l]. Valid values are: "kdiff3", "tkdiff", + "meld", "xxdiff", "emerge" + merge.verbosity:: Controls the amount of output shown by the recursive merge strategy. Level 0 outputs nothing except a final error diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt new file mode 100644 index 0000000000..ae69a0eb83 --- /dev/null +++ b/Documentation/git-mergetool.txt @@ -0,0 +1,46 @@ +git-mergetool(1) +================ + +NAME +---- +git-mergetool - Run merge conflict resolution tools to resolve merge conflicts + +SYNOPSIS +-------- +'git-mergetool' [--tool=] []... + +DESCRIPTION +----------- + +Use 'git mergetool' to run one of several merge utilities to resolve +merge conflicts. It is typically run after gitlink:git-merge[1]. + +If one or more parameters are given, the merge tool program will +be run to resolve differences on each file. If no names are +specified, 'git mergetool' will run the merge tool program on every file +with merge conflicts. + +OPTIONS +------- +-t or --tool=:: + Use the merge resolution program specified by . + Valid merge tools are: + kdiff3, tkdiff, meld, xxdiff, and emerge. + + If a merge resolution program is not specified, 'git mergetool' + will use the configuration variable merge.tool. If the + configuration variable merge.tool is not set, 'git mergetool' + will pick a suitable default. + +Author +------ +Written by Theodore Y Ts'o + +Documentation +-------------- +Documentation by Theodore Y Ts'o. + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Makefile b/Makefile index 1a51f84f53..45fe3663a5 100644 --- a/Makefile +++ b/Makefile @@ -179,7 +179,7 @@ SCRIPT_SH = \ git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh git-gc.sh \ git-ls-remote.sh \ - git-merge-one-file.sh git-parse-remote.sh \ + git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ diff --git a/git-mergetool.sh b/git-mergetool.sh new file mode 100755 index 0000000000..52386a5443 --- /dev/null +++ b/git-mergetool.sh @@ -0,0 +1,352 @@ +#!/bin/sh +# +# This program resolves merge conflicts in git +# +# Copyright (c) 2006 Theodore Y. Ts'o +# +# This file is licensed under the GPL v2, or a later version +# at the discretion of Junio C Hammano. +# + +USAGE='[--tool=tool] [file to merge] ...' +SUBDIRECTORY_OK=Yes +. git-sh-setup +require_work_tree + +# Returns true if the mode reflects a symlink +function is_symlink () { + test "$1" = 120000 +} + +function local_present () { + test -n "$local_mode" +} + +function remote_present () { + test -n "$remote_mode" +} + +function base_present () { + test -n "$base_mode" +} + +cleanup_temp_files () { + if test "$1" = --save-backup ; then + mv -- "$BACKUP" "$path.orig" + rm -f -- "$LOCAL" "$REMOTE" "$BASE" + else + rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP" + fi +} + +function describe_file () { + mode="$1" + branch="$2" + file="$3" + + echo -n " " + if test -z "$mode"; then + echo -n "'$path' was deleted" + elif is_symlink "$mode" ; then + echo -n "'$path' is a symlink containing '" + cat "$file" + echo -n "'" + else + if base_present; then + echo -n "'$path' was created" + else + echo -n "'$path' was modified" + fi + fi + echo " in the $branch branch" +} + + +resolve_symlink_merge () { + while /bin/true; do + echo -n "Use (r)emote or (l)ocal, or (a)bort? " + read ans + case "$ans" in + [lL]*) + git-checkout-index -f --stage=2 -- "$path" + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [rR]*) + git-checkout-index -f --stage=3 -- "$path" + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [qQ]*) + exit 1 + ;; + esac + done +} + +resolve_deleted_merge () { + while /bin/true; do + echo -n "Use (m)odified or (d)eleted file, or (a)bort? " + read ans + case "$ans" in + [mM]*) + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [dD]*) + git-rm -- "$path" + cleanup_temp_files + return + ;; + [qQ]*) + exit 1 + ;; + esac + done +} + +merge_file () { + path="$1" + + if test ! -f "$path" ; then + echo "$path: file not found" + exit 1 + fi + + f=`git-ls-files -u -- "$path"` + if test -z "$f" ; then + echo "$path: file does not need merging" + exit 1 + fi + + BACKUP="$path.BACKUP.$$" + LOCAL="$path.LOCAL.$$" + REMOTE="$path.REMOTE.$$" + BASE="$path.BASE.$$" + + mv -- "$path" "$BACKUP" + cp -- "$BACKUP" "$path" + + base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'` + local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'` + remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'` + + base_present && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null + local_present && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null + remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null + + if test -z "$local_mode" -o -z "$remote_mode"; then + echo "Deleted merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + resolve_deleted_merge + return + fi + + if is_symlink "$local_mode" || is_symlink "$remote_mode"; then + echo "Symlink merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + resolve_symlink_merge + return + fi + + echo "Normal merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + echo -n "Hit return to start merge resolution tool ($merge_tool): " + read ans + + case "$merge_tool" in + kdiff3) + if base_present ; then + (kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \ + -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) + else + (kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \ + -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) + fi + status=$? + if test "$status" -eq 0; then + rm "$BACKUP" + fi + ;; + tkdiff) + if base_present ; then + tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + else + tkdiff -o "$path" -- "$LOCAL" "$REMOTE" + fi + status=$? + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + meld) + touch "$BACKUP" + meld -- "$LOCAL" "$path" "$REMOTE" + if test "$path" -nt "$BACKUP" ; then + status=0; + else + while true; do + echo "$path seems unchanged." + echo -n "Was the merge successful? [y/n] " + read answer < /dev/tty + case "$answer" in + y*|Y*) status=0; break ;; + n*|N*) status=1; break ;; + esac + done + fi + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + xxdiff) + touch "$BACKUP" + if base_present ; then + xxdiff -X --show-merged-pane \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE" + else + xxdiff -X --show-merged-pane \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$path" -- "$LOCAL" "$REMOTE" + fi + if test "$path" -nt "$BACKUP" ; then + status=0; + else + while true; do + echo "$path seems unchanged." + echo -n "Was the merge successful? [y/n] " + read answer < /dev/tty + case "$answer" in + y*|Y*) status=0; break ;; + n*|N*) status=1; break ;; + esac + done + fi + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + emerge) + if base_present ; then + emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path" + else + emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path" + fi + status=$? + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + esac + if test "$status" -ne 0; then + echo "merge of $path failed" 1>&2 + mv -- "$BACKUP" "$path" + exit 1 + fi + git add -- "$path" + cleanup_temp_files +} + +while case $# in 0) break ;; esac +do + case "$1" in + -t|--tool*) + case "$#,$1" in + *,*=*) + merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'` + ;; + 1,*) + usage ;; + *) + merge_tool="$2" + shift ;; + esac + ;; + --) + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift +done + +if test -z "$merge_tool"; then + merge_tool=`git-config merge.tool` + if test $merge_tool = kdiff3 -o $merge_tool = tkdiff -o \ + $merge_tool = xxdiff -o $merge_tool = meld ; then + unset merge_tool + fi +fi + +if test -z "$merge_tool" ; then + if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool="kdiff3"; + elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=tkdiff + elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=xxdiff + elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=meld + elif type emacs >/dev/null 2>&1; then + merge_tool=emerge + else + echo "No available merge resolution programs available." + exit 1 + fi +fi + +case "$merge_tool" in + kdiff3|tkdiff|meld|xxdiff) + if ! type "$merge_tool" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available" + exit 1 + fi + ;; + emerge) + if ! type "emacs" > /dev/null 2>&1; then + echo "Emacs is not available" + exit 1 + fi + ;; + *) + echo "Unknown merge tool: $merge_tool" + exit 1 + ;; +esac + +if test $# -eq 0 ; then + files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` + if test -z "$files" ; then + echo "No files need merging" + exit 0 + fi + echo Merging the files: $files + git ls-files -u | sed -e 's/^[^ ]* //' | sort -u | while read i + do + echo "" + merge_file "$i" < /dev/tty > /dev/tty + done +else + while test $# -gt 0; do + echo "" + merge_file "$1" + shift + done +fi +exit 0 From ad0f8c9ea76dcce82eb3fa1b308e495c21b834d1 Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Tue, 13 Mar 2007 18:24:26 +0100 Subject: [PATCH 59/68] cvsserver: asciidoc formatting changes Format some lists really as lists. Improves both html and man output. Signed-off-by: Frank Lichtenheld Signed-off-by: Junio C Hamano --- Documentation/git-cvsserver.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index e328db3797..1c6f6a7e27 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -121,10 +121,10 @@ so that calling 'cvs' effectively calls git-cvsserver. Clients known to work --------------------- -CVS 1.12.9 on Debian -CVS 1.11.17 on MacOSX (from Fink package) -Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) -TortoiseCVS +- CVS 1.12.9 on Debian +- CVS 1.11.17 on MacOSX (from Fink package) +- Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) +- TortoiseCVS Operations supported -------------------- @@ -148,13 +148,16 @@ Copyright and Authors This program is copyright The Open University UK - 2006. -Authors: Martyn Smith - Martin Langhoff - with ideas and patches from participants of the git-list . +Authors: + +- Martyn Smith +- Martin Langhoff + +with ideas and patches from participants of the git-list . Documentation -------------- -Documentation by Martyn Smith and Martin Langhoff Matthias Urlichs . +Documentation by Martyn Smith , Martin Langhoff , and Matthias Urlichs . GIT --- From dee41f3e5569f9465018535f02d45796d84aebd8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Mar 2007 11:40:36 -0700 Subject: [PATCH 60/68] git-svn: add -l/--local command to "git svn rebase" This avoids fetching new revisions remotely, and is usefuly versus plain "git rebase" because the user does not have to specify which remote head to rebase against. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- Documentation/git-svn.txt | 8 ++++++++ git-svn.perl | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 9b5a3d6196..a0d34e0058 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -104,6 +104,14 @@ accepts. However '--fetch-all' only fetches from the current Like 'git-rebase'; this requires that the working tree be clean and have no uncommitted changes. ++ +-- +-l;; +--local;; + Do not fetch remotely; only run 'git-rebase' against the + last fetched commit from the upstream SVN. +-- ++ 'dcommit':: Commit each diff from a specified head directly to the SVN diff --git a/git-svn.perl b/git-svn.perl index 326e89fe03..e8457893db 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, $_version, $_fetch_all, - $_merge, $_strategy, $_dry_run, + $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_verbose); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, @@ -145,6 +145,7 @@ my %cmd = ( { 'merge|m|M' => \$_merge, 'verbose|v' => \$_verbose, 'strategy|s=s' => \$_strategy, + 'local|l' => \$_local, 'fetch-all|all' => \$_fetch_all, %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, @@ -439,7 +440,9 @@ sub cmd_rebase { command_noisy('status'); exit 1; } - $_fetch_all ? $gs->fetch_all : $gs->fetch; + unless ($_local) { + $_fetch_all ? $gs->fetch_all : $gs->fetch; + } command_noisy(rebase_cmd(), $gs->refname); } From 86952cdabdcc550a20794794db539c41877d17fc Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 01:29:26 -0700 Subject: [PATCH 61/68] Documentation: add git-mergetool to the command list. Signed-off-by: Junio C Hamano --- Documentation/cmd-list.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index f61c77aa7c..b54382b2bf 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -124,6 +124,7 @@ git-merge-index plumbingmanipulators git-merge mainporcelain git-merge-one-file purehelpers git-merge-tree ancillaryinterrogators +git-mergetool ancillarymanipulators git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain From 0497c620cae0531815afc4ada783b488eab3f447 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 8 Mar 2007 02:12:06 -0800 Subject: [PATCH 62/68] shortlog: prompt when reading from terminal by mistake I was trying to see who have been active recently to find GSoC mentor candidates by running: $ git shortlog -s -n --since=4.months | head -n 20 After waiting for about 20 seconds, I started getting worried, thinking that the recent revision traversal updates might have had an unintended side effect. Not so. "git shortlog" acts as a filter when no revs are given, unlike "git log" which defaults to HEAD. It was reading from its standard input. Signed-off-by: Junio C Hamano --- builtin-shortlog.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 2d7726e8b9..29343aefc8 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -304,8 +304,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); - if (rev.pending.nr == 0) + if (rev.pending.nr == 0) { + if (isatty(0)) + fprintf(stderr, "(reading log to summarize from standard input)\n"); read_from_stdin(&list); + } else get_from_rev(&rev, &list); From 6cd7895feeaee3970b76988ed82b2c701a5b28df Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 11 Mar 2007 14:19:07 +0100 Subject: [PATCH 63/68] Do not output "GEN " when generating perl.mak This fixes the same issue as 8bef6204, which became an issue again after 31d0399c. Besides, it is not really helpful to print just "GEN " (_without_ "perl.mak"). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- perl/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perl/Makefile b/perl/Makefile index 5ec0389883..17d004e5a0 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -33,7 +33,7 @@ $(makfile): ../GIT-CFLAGS Makefile echo ' echo $(instdir_SQ)' >> $@ else $(makfile): Makefile.PL ../GIT-CFLAGS - $(QUIET_GEN)'$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' + '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' endif # this is just added comfort for calling make directly in perl dir From 09f2825147d44bd0333f7f47d8ab3837f0848867 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 12 Mar 2007 13:11:29 +0100 Subject: [PATCH 64/68] git-grep: don't use sscanf If you use scanf or sscanf to parse integers, your code probably accepts bogus inputs. For example, builtin-grep (aka git-grep) uses sscanf(scan, "%u", &num) to parse the integer argument to -A, -B, -C. Currently, "-C 1,000" and "-C 4294967297" are both treated just like "-C 1": $ git-grep -h -C 4294967297 juggle out and you may find it easier to switch back and forth if you juggle multiple lines of development simultaneously. Of course, you will pay the price of more disk usage to hold The obvious fix is to use strtoul instead. But using a bare strtoul is too messy, at least when done properly, so I've added a wrapper function. The new function in the patch below belongs elsewhere if it would be useful in replacing any of the four remaining uses of sscanf. One final note: With this change, I get a slightly different diagnostic depending on the context size: $ ./git-grep -h -C 4294967296 juggle fatal: 4294967296: invalid context length argument [Exit 128] $ ./git-grep -h -C 4294967295 juggle grep: 4294967295: invalid context length argument [Exit 1] A common convention that makes it easy to identify the source of a diagnostic is to include the program name before the first ":". Whether that should be "git" or "git-grep" is another question. Using "grep" or "fatal" is misleading. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- builtin-grep.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/builtin-grep.c b/builtin-grep.c index 694da5ba09..4510d35324 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -431,6 +431,19 @@ static const char emsg_missing_context_len[] = static const char emsg_missing_argument[] = "option requires an argument -%s"; +static int strtoul_ui(char const *s, unsigned int *result) +{ + unsigned long ul; + char *p; + + errno = 0; + ul = strtoul(s, &p, 10); + if (errno || *p || p == s || (unsigned int) ul != ul) + return -1; + *result = ul; + return 0; +} + int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; @@ -553,7 +566,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) scan = arg + 1; break; } - if (sscanf(scan, "%u", &num) != 1) + if (strtoul_ui(scan, &num)) die(emsg_invalid_context_len, scan); switch (arg[1]) { case 'A': From c47e6a43d30c8a1fd4b8e9234883f01fe3bac805 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Wed, 14 Mar 2007 14:25:52 +0000 Subject: [PATCH 65/68] update-hook: fix incorrect use of git-describe and sed for finding previous tag Previously git-describe would output lines of the form v1.1.1-gf509d56 The update hook found the dash and stripped it off using sed 's/-g.*//' The remainder was then used as the previous tag name. However, git-describe has changed format. The output is now of the form v1.1.1-23-gf509d56 The above sed fragment doesn't strip the middle "-23", and so the previous tag name used would be "v1.1.1-23". This is incorrect. Since the hook script was written, git-describe now gained support for "--abbrev=0", which it uses as a special flag to tell it not to output anything other than the nearest tag name. This patch fixes the problem, and prevents any future recurrence by using this new flag rather than sed to find the previous tag. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- templates/hooks--update | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/hooks--update b/templates/hooks--update index 5b82b68e93..8f6c4fea24 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -210,7 +210,7 @@ case "$refname_type" in fi # If this tag succeeds another, then show which tag it replaces - prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//') + prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null) if [ -n "$prevtag" ]; then echo " replaces $prevtag" fi From 41f5d73391e3fe5c4cb2829582b26a85930964d1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 09:48:13 -0700 Subject: [PATCH 66/68] git-checkout: fix "eval" used for merge labelling. The symbolic notation of the fork point can contain whitespaces (e.g. "git checkout -m 'HEAD@{9 hours ago}'"). Quote strings properly when using eval to prepare GITHEAD_$new Signed-off-by: Junio C Hamano --- git-checkout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-checkout.sh b/git-checkout.sh index 14835a4aa9..83b2639d6f 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -196,7 +196,7 @@ else work=`git write-tree` && git read-tree --reset -u $new || exit - eval GITHEAD_$new=${new_name:-${branch:-$new}} && + eval GITHEAD_$new='${new_name:-${branch:-$new}}' && eval GITHEAD_$work=local && export GITHEAD_$new GITHEAD_$work && git merge-recursive $old -- $new $work From 9debca9aac99bc25c99727767ee50c8e405e9591 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Wed, 14 Mar 2007 22:08:41 +0100 Subject: [PATCH 67/68] Clarify doc for git-config --unset-all. Previous formulation could make it appear as removing all lines matching a regexp (at least, I was looking for such a flag, and confused this flag for what I was looking for). Signed-off-by: Yann Dirson Signed-off-by: Junio C Hamano --- Documentation/git-config.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 6624484fe1..ccb8b36f9a 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -78,7 +78,7 @@ OPTIONS Remove the line matching the key from config file. --unset-all:: - Remove all matching lines from config file. + Remove all lines matching the key from config file. -l, --list:: List all variables set in config file. From 3d4e1932f24346d26f15e98dd6a74798035e7870 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Mar 2007 15:56:49 -0700 Subject: [PATCH 68/68] GIT 1.5.0.4 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.0.4.txt | 24 ++++++++++++++++++++++++ GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Documentation/RelNotes-1.5.0.4.txt diff --git a/Documentation/RelNotes-1.5.0.4.txt b/Documentation/RelNotes-1.5.0.4.txt new file mode 100644 index 0000000000..b727a8d1e5 --- /dev/null +++ b/Documentation/RelNotes-1.5.0.4.txt @@ -0,0 +1,24 @@ +GIT v1.5.0.4 Release Notes +========================== + +Fixes since v1.5.0.3 +-------------------- + +* Bugfixes + + - git.el does not add duplicate sign-off lines. + + - git-commit shows the full stat of the resulting commit, not + just about the files in the current directory, when run from + a subdirectory. + + - "git-checkout -m '@{8 hours ago}'" had a funny failure from + eval; fixed. + + - git-gui updates. + +* Documentation updates + +* User manual updates + + diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 05f935972c..6188415c5d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.0.3.GIT +DEF_VER=v1.5.0.4.GIT LF=' ' diff --git a/RelNotes b/RelNotes index 6a658bf134..4e8ff2eb7e 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.5.0.3.txt \ No newline at end of file +Documentation/RelNotes-1.5.0.4.txt \ No newline at end of file