From 6d21bf96b59cbcc818fdc83b654d7fc83dd2c9cd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 2 Jul 2008 00:51:18 -0700 Subject: [PATCH 1/5] Refactor "tracking statistics" code used by "git checkout" People seem to like "Your branch is ahead by N commit" report made by "git checkout", but the interface into the statistics function was a bit clunky. This splits the function into three parts: * The core "commit counting" function that takes "struct branch" and returns number of commits to show if we are ahead, behind or forked; * Convenience "stat formating" function that takes "struct branch" and formats the report into a given strbuf, using the above function; * "checkout" specific function that takes "branch_info" (type that is internal to checkout implementation), calls the above function and print the formatted result. in the hope that the former two can be more easily reusable. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 94 +++---------------------------------- remote.c | 113 +++++++++++++++++++++++++++++++++++++++++++++ remote.h | 4 ++ 3 files changed, 123 insertions(+), 88 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 93ea69bfaa..d6641c2c56 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -305,97 +305,15 @@ static int merge_working_tree(struct checkout_opts *opts, return 0; } -static void report_tracking(struct branch_info *new, struct checkout_opts *opts) +static void report_tracking(struct branch_info *new) { - /* - * We have switched to a new branch; is it building on - * top of another branch, and if so does that other branch - * have changes we do not have yet? - */ - char *base; - unsigned char sha1[20]; - struct commit *ours, *theirs; - char symmetric[84]; - struct rev_info revs; - const char *rev_argv[10]; - int rev_argc; - int num_ours, num_theirs; - const char *remote_msg; + struct strbuf sb = STRBUF_INIT; struct branch *branch = branch_get(new->name); - /* - * Nothing to report unless we are marked to build on top of - * somebody else. - */ - if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) + if (!format_tracking_info(branch, &sb)) return; - - /* - * If what we used to build on no longer exists, there is - * nothing to report. - */ - base = branch->merge[0]->dst; - if (!resolve_ref(base, sha1, 1, NULL)) - return; - - theirs = lookup_commit(sha1); - ours = new->commit; - if (!hashcmp(sha1, ours->object.sha1)) - return; /* we are the same */ - - /* Run "rev-list --left-right ours...theirs" internally... */ - rev_argc = 0; - rev_argv[rev_argc++] = NULL; - rev_argv[rev_argc++] = "--left-right"; - rev_argv[rev_argc++] = symmetric; - rev_argv[rev_argc++] = "--"; - rev_argv[rev_argc] = NULL; - - strcpy(symmetric, sha1_to_hex(ours->object.sha1)); - strcpy(symmetric + 40, "..."); - strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1)); - - init_revisions(&revs, NULL); - setup_revisions(rev_argc, rev_argv, &revs, NULL); - prepare_revision_walk(&revs); - - /* ... and count the commits on each side. */ - num_ours = 0; - num_theirs = 0; - while (1) { - struct commit *c = get_revision(&revs); - if (!c) - break; - if (c->object.flags & SYMMETRIC_LEFT) - num_ours++; - else - num_theirs++; - } - - if (!prefixcmp(base, "refs/remotes/")) { - remote_msg = " remote"; - base += strlen("refs/remotes/"); - } else { - remote_msg = ""; - } - - if (!num_theirs) - printf("Your branch is ahead of the tracked%s branch '%s' " - "by %d commit%s.\n", - remote_msg, base, - num_ours, (num_ours == 1) ? "" : "s"); - else if (!num_ours) - printf("Your branch is behind the tracked%s branch '%s' " - "by %d commit%s,\n" - "and can be fast-forwarded.\n", - remote_msg, base, - num_theirs, (num_theirs == 1) ? "" : "s"); - else - printf("Your branch and the tracked%s branch '%s' " - "have diverged,\nand respectively " - "have %d and %d different commit(s) each.\n", - remote_msg, base, - num_ours, num_theirs); + fputs(sb.buf, stdout); + strbuf_release(&sb); } static void update_refs_for_switch(struct checkout_opts *opts, @@ -441,7 +359,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, remove_branch_state(); strbuf_release(&msg); if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) - report_tracking(new, opts); + report_tracking(new); } static int switch_branches(struct checkout_opts *opts, struct branch_info *new) diff --git a/remote.c b/remote.c index ff2c802167..bd5c3be3ec 100644 --- a/remote.c +++ b/remote.c @@ -1,6 +1,9 @@ #include "cache.h" #include "remote.h" #include "refs.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" static struct refspec s_tag_refspec = { 0, @@ -1222,3 +1225,113 @@ int resolve_remote_symref(struct ref *ref, struct ref *list) } return 1; } + +/* + * Return true if there is anything to report, otherwise false. + */ +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) +{ + unsigned char sha1[20]; + struct commit *ours, *theirs; + char symmetric[84]; + struct rev_info revs; + const char *rev_argv[10], *base; + int rev_argc; + + /* + * Nothing to report unless we are marked to build on top of + * somebody else. + */ + if (!branch || + !branch->merge || !branch->merge[0] || !branch->merge[0]->dst) + return 0; + + /* + * If what we used to build on no longer exists, there is + * nothing to report. + */ + base = branch->merge[0]->dst; + if (!resolve_ref(base, sha1, 1, NULL)) + return 0; + theirs = lookup_commit(sha1); + if (!theirs) + return 0; + + if (!resolve_ref(branch->refname, sha1, 1, NULL)) + return 0; + ours = lookup_commit(sha1); + if (!ours) + return 0; + + /* are we the same? */ + if (theirs == ours) + return 0; + + /* Run "rev-list --left-right ours...theirs" internally... */ + rev_argc = 0; + rev_argv[rev_argc++] = NULL; + rev_argv[rev_argc++] = "--left-right"; + rev_argv[rev_argc++] = symmetric; + rev_argv[rev_argc++] = "--"; + rev_argv[rev_argc] = NULL; + + strcpy(symmetric, sha1_to_hex(ours->object.sha1)); + strcpy(symmetric + 40, "..."); + strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1)); + + init_revisions(&revs, NULL); + setup_revisions(rev_argc, rev_argv, &revs, NULL); + prepare_revision_walk(&revs); + + /* ... and count the commits on each side. */ + *num_ours = 0; + *num_theirs = 0; + while (1) { + struct commit *c = get_revision(&revs); + if (!c) + break; + if (c->object.flags & SYMMETRIC_LEFT) + (*num_ours)++; + else + (*num_theirs)++; + } + return 1; +} + +/* + * Return true when there is anything to report, otherwise false. + */ +int format_tracking_info(struct branch *branch, struct strbuf *sb) +{ + int num_ours, num_theirs; + const char *base, *remote_msg; + + if (!stat_tracking_info(branch, &num_ours, &num_theirs)) + return 0; + + base = branch->merge[0]->dst; + if (!prefixcmp(base, "refs/remotes/")) { + remote_msg = " remote"; + base += strlen("refs/remotes/"); + } else { + remote_msg = ""; + } + if (!num_theirs) + strbuf_addf(sb, "Your branch is ahead of the tracked%s branch '%s' " + "by %d commit%s.\n", + remote_msg, base, + num_ours, (num_ours == 1) ? "" : "s"); + else if (!num_ours) + strbuf_addf(sb, "Your branch is behind the tracked%s branch '%s' " + "by %d commit%s,\n" + "and can be fast-forwarded.\n", + remote_msg, base, + num_theirs, (num_theirs == 1) ? "" : "s"); + else + strbuf_addf(sb, "Your branch and the tracked%s branch '%s' " + "have diverged,\nand respectively " + "have %d and %d different commit(s) each.\n", + remote_msg, base, + num_ours, num_theirs); + return 1; +} diff --git a/remote.h b/remote.h index 8eed87ba5a..091b1d041f 100644 --- a/remote.h +++ b/remote.h @@ -129,4 +129,8 @@ enum match_refs_flags { MATCH_REFS_MIRROR = (1 << 1), }; +/* Reporting of tracking info */ +int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs); +int format_tracking_info(struct branch *branch, struct strbuf *sb); + #endif From b6975ab59b07e5416b3f9b6f077d3cb50ef5ac7e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 2 Jul 2008 00:52:16 -0700 Subject: [PATCH 2/5] git-status: show the remote tracking statistics This teaches "git status" to show the same remote tracking statistics "git checkout" gives at the beginning of the output. Now the necessary low-level machinery is properly factored out, we can do this quite cleanly. Signed-off-by: Junio C Hamano --- wt-status.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/wt-status.c b/wt-status.c index 28c9e637e3..eeb106e13e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -9,6 +9,7 @@ #include "diffcore.h" #include "quote.h" #include "run-command.h" +#include "remote.h" int wt_status_relative_paths = 1; int wt_status_use_color = -1; @@ -315,6 +316,25 @@ static void wt_status_print_verbose(struct wt_status *s) run_diff_index(&rev, 1); } +static void wt_status_print_tracking(struct wt_status *s) +{ + struct strbuf sb = STRBUF_INIT; + const char *cp, *ep; + struct branch *branch; + + assert(s->branch && !s->is_initial); + if (prefixcmp(s->branch, "refs/heads/")) + return; + branch = branch_get(s->branch + 11); + if (!format_tracking_info(branch, &sb)) + return; + + for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1) + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), + "# %.*s", (int)(ep - cp), cp); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); +} + void wt_status_print(struct wt_status *s) { unsigned char sha1[20]; @@ -333,6 +353,8 @@ void wt_status_print(struct wt_status *s) } color_fprintf(s->fp, color(WT_STATUS_HEADER), "# "); color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name); + if (!s->is_initial) + wt_status_print_tracking(s); } if (s->is_initial) { From 94fcb730ff665d356689f3d30e20e0fd75c6f62f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 2 Jul 2008 00:52:41 -0700 Subject: [PATCH 3/5] git-branch -v: show the remote tracking statistics This teaches "git branch -v" to insert the remote tracking statistics in brackets, just before the one-liner commit log message for the branch. Signed-off-by: Junio C Hamano --- builtin-branch.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index d279702ba9..e9423d1e4c 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -282,6 +282,23 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } +static void fill_tracking_info(char *stat, const char *branch_name) +{ + int ours, theirs; + struct branch *branch = branch_get(branch_name); + + if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) { + stat[0] = '\0'; + return; + } + if (!ours) + sprintf(stat, "[behind %d] ", theirs); + else if (!theirs) + sprintf(stat, "[ahead %d] ", ours); + else + sprintf(stat, "[ahead %d, behind %d] ", ours, theirs); +} + static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, int abbrev, int current) { @@ -310,6 +327,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, if (verbose) { struct strbuf subject; const char *sub = " **** invalid ref ****"; + char stat[128]; strbuf_init(&subject, 0); @@ -319,10 +337,15 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, &subject, 0, NULL, NULL, 0, 0); sub = subject.buf; } - printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color), + + if (item->kind == REF_LOCAL_BRANCH) + fill_tracking_info(stat, item->name); + + printf("%c %s%-*s%s %s %s%s\n", c, branch_get_color(color), maxwidth, item->name, branch_get_color(COLOR_BRANCH_RESET), - find_unique_abbrev(item->sha1, abbrev), sub); + find_unique_abbrev(item->sha1, abbrev), + stat, sub); strbuf_release(&subject); } else { printf("%c %s%s%s\n", c, branch_get_color(color), item->name, From c0234b2ef6a8eaa27d9d93c4c96b36d9e82ebf9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Jul 2008 12:09:48 -0700 Subject: [PATCH 4/5] stat_tracking_info(): clear object flags used during counting When left-right traversal counts the commits in a diverged history, it leaves the flags in the commits smudged, and we need to clear them before we return. Otherwise the caller cannot inspect other branches with this function again. Signed-off-by: Junio C Hamano --- remote.c | 4 +++ revision.h | 1 + t/t6040-tracking-info.sh | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100755 t/t6040-tracking-info.sh diff --git a/remote.c b/remote.c index bd5c3be3ec..df8bd72ba9 100644 --- a/remote.c +++ b/remote.c @@ -1295,6 +1295,10 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) else (*num_theirs)++; } + + /* clear object flags smudged by the above traversal */ + clear_commit_marks(ours, ALL_REV_FLAGS); + clear_commit_marks(theirs, ALL_REV_FLAGS); return 1; } diff --git a/revision.h b/revision.h index abce5001f1..e8bac6d141 100644 --- a/revision.h +++ b/revision.h @@ -11,6 +11,7 @@ #define ADDED (1u<<7) /* Parents already parsed and added? */ #define SYMMETRIC_LEFT (1u<<8) #define TOPOSORT (1u<<9) /* In the active toposort list.. */ +#define ALL_REV_FLAGS ((1u<<10)-1) struct rev_info; struct log_info; diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh new file mode 100755 index 0000000000..aac212e936 --- /dev/null +++ b/t/t6040-tracking-info.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='remote tracking stats' + +. ./test-lib.sh + +advance () { + echo "$1" >"$1" && + git add "$1" && + test_tick && + git commit -m "$1" +} + +test_expect_success setup ' + for i in a b c; + do + advance $i || break + done && + git clone . test && + ( + cd test && + git checkout -b b1 origin && + git reset --hard HEAD^ && + advance d && + git checkout -b b2 origin && + git reset --hard b1 && + git checkout -b b3 origin && + git reset --hard HEAD^ && + git checkout -b b4 origin && + advance e && + advance f + ) +' + +script='s/^..\(b.\)[ 0-9a-f]*\[\([^]]*\)\].*/\1 \2/p' +cat >expect <<\EOF +b1 ahead 1, behind 1 +b2 ahead 1, behind 1 +b3 behind 1 +b4 ahead 2 +EOF + +test_expect_success 'branch -v' ' + ( + cd test && + git branch -v + ) | + sed -n -e "$script" >actual && + test_cmp expect actual +' + +test_expect_success 'checkout' ' + ( + cd test && git checkout b1 + ) >actual && + grep -e "have 1 and 1 different" actual +' + +test_expect_success 'status' ' + ( + cd test && + git checkout b1 >/dev/null && + # reports nothing to commit + test_must_fail git status + ) >actual && + grep -e "have 1 and 1 different" actual +' + + +test_done From 926ab840cdc867b54fa93b8e1ece2790315c2456 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 6 Jul 2008 02:54:56 -0700 Subject: [PATCH 5/5] branch -r -v: do not spit out garbage The codepath to emit relationship between the branch and what it tracks forgot to initialize a string buffer stat[] to empty when showing a tracking branch. This moves the emptying so that the buffer starts as empty and stays so when no information is added to fix this issue. Signed-off-by: Junio C Hamano --- builtin-branch.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index e9423d1e4c..ff71f3d8a6 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -287,10 +287,8 @@ static void fill_tracking_info(char *stat, const char *branch_name) int ours, theirs; struct branch *branch = branch_get(branch_name); - if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) { - stat[0] = '\0'; + if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) return; - } if (!ours) sprintf(stat, "[behind %d] ", theirs); else if (!theirs) @@ -330,6 +328,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, char stat[128]; strbuf_init(&subject, 0); + stat[0] = '\0'; commit = lookup_commit(item->sha1); if (commit && !parse_commit(commit)) {