branch: report invalid tracking branch as gone

Command "git branch -vv" will report tracking branches, but invalid tracking branches are also reported. This is because the function stat_tracking_info() can not distinguish invalid tracking branch from other cases which it would not like to report, such as there is no upstream settings at all, or nothing is changed between one branch and its upstream. Junio suggested missing upstream should be reported [1] like: $ git branch -v -v master e67ac84 initial * topic 3fc0f2a [topicbase: gone] topic $ git status # On branch topic # Your branch is based on 'topicbase', but the upstream is gone. # (use "git branch --unset-upstream" to fixup) ... $ git status -b -s ## topic...topicbase [gone] ... In order to do like that, we need to distinguish these three cases (i.e. no tracking, with configured but no longer valid tracking, and with tracking) in function stat_tracking_info(). So the refactored function stat_tracking_info() has three return values: -1 (with "gone" base), 0 (no base), and 1 (with base). If the caller does not like to report tracking info when nothing changed between the branch and its upstream, simply checks if num_theirs and num_ours are both 0. [1]: http://thread.gmane.org/gmane.comp.version-control.git/231830/focus=232288 Suggested-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Jiang Xin <worldhello.net@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jiang Xin committed Aug 26, 2013 at 15:02 UTC f2e087395b78d5828af400072c1b621e1a373be4
4 files changed +142 -48
builtin/branch.c
+21 -15
@@ -423,19 +423,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
423 char *ref = NULL;
424 struct branch *branch = branch_get(branch_name);
425 struct strbuf fancy = STRBUF_INIT;
426 + int upstream_is_gone = 0;
427
427 - if (!stat_tracking_info(branch, &ours, &theirs)) {
428 - if (branch && branch->merge && branch->merge[0]->dst &&
429 - show_upstream_ref) {
430 - ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0);
431 - if (want_color(branch_use_color))
432 - strbuf_addf(stat, "[%s%s%s] ",
433 - branch_get_color(BRANCH_COLOR_UPSTREAM),
434 - ref, branch_get_color(BRANCH_COLOR_RESET));
435 - else
436 - strbuf_addf(stat, "[%s] ", ref);
437 - }
428 + switch (stat_tracking_info(branch, &ours, &theirs)) {
429 + case 0:
430 + /* no base */
431 return;
432 + case -1:
433 + /* with "gone" base */
434 + upstream_is_gone = 1;
435 + break;
436 + default:
437 + /* with base */
438 + break;
439 }
440
441 if (show_upstream_ref) {
@@ -448,19 +448,25 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
448 strbuf_addstr(&fancy, ref);
449 }
450
451 - if (!ours) {
452 - if (ref)
451 + if (upstream_is_gone) {
452 + if (show_upstream_ref)
453 + strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
454 + } else if (!ours && !theirs) {
455 + if (show_upstream_ref)
456 + strbuf_addf(stat, _("[%s]"), fancy.buf);
457 + } else if (!ours) {
458 + if (show_upstream_ref)
459 strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
460 else
461 strbuf_addf(stat, _("[behind %d]"), theirs);
462
463 } else if (!theirs) {
458 - if (ref)
464 + if (show_upstream_ref)
465 strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
466 else
467 strbuf_addf(stat, _("[ahead %d]"), ours);
468 } else {
463 - if (ref)
469 + if (show_upstream_ref)
470 strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
471 fancy.buf, ours, theirs);
472 else
remote.c
+46 -25
@@ -1695,7 +1695,11 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
1695 }
1696
1697 /*
1698 - * Return true if there is anything to report, otherwise false.
1698 + * Compare a branch with its upstream, and save their differences (number
1699 + * of commits) in *num_ours and *num_theirs.
1700 + *
1701 + * Return 0 if branch has no upstream (no base), -1 if upstream is missing
1702 + * (with "gone" base), otherwise 1 (with base).
1703 */
1704 int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
1705 {
@@ -1706,34 +1710,30 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
1710 const char *rev_argv[10], *base;
1711 int rev_argc;
1712
1709 - /*
1710 - * Nothing to report unless we are marked to build on top of
1711 - * somebody else.
1712 - */
1713 + /* Cannot stat unless we are marked to build on top of somebody else. */
1714 if (!branch ||
1715 !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
1716 return 0;
1717
1717 - /*
1718 - * If what we used to build on no longer exists, there is
1719 - * nothing to report.
1720 - */
1718 + /* Cannot stat if what we used to build on no longer exists */
1719 base = branch->merge[0]->dst;
1720 if (read_ref(base, sha1))
1723 - return 0;
1721 + return -1;
1722 theirs = lookup_commit_reference(sha1);
1723 if (!theirs)
1726 - return 0;
1724 + return -1;
1725
1726 if (read_ref(branch->refname, sha1))
1729 - return 0;
1727 + return -1;
1728 ours = lookup_commit_reference(sha1);
1729 if (!ours)
1732 - return 0;
1730 + return -1;
1731
1732 /* are we the same? */
1735 - if (theirs == ours)
1736 - return 0;
1733 + if (theirs == ours) {
1734 + *num_theirs = *num_ours = 0;
1735 + return 1;
1736 + }
1737
1738 /* Run "rev-list --left-right ours...theirs" internally... */
1739 rev_argc = 0;
@@ -1775,31 +1775,52 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs)
1775 */
1776 int format_tracking_info(struct branch *branch, struct strbuf *sb)
1777 {
1778 - int num_ours, num_theirs;
1778 + int ours, theirs;
1779 const char *base;
1780 + int upstream_is_gone = 0;
1781
1781 - if (!stat_tracking_info(branch, &num_ours, &num_theirs))
1782 + switch (stat_tracking_info(branch, &ours, &theirs)) {
1783 + case 0:
1784 + /* no base */
1785 return 0;
1786 + case -1:
1787 + /* with "gone" base */
1788 + upstream_is_gone = 1;
1789 + break;
1790 + default:
1791 + /* Nothing to report if neither side has changes. */
1792 + if (!ours && !theirs)
1793 + return 0;
1794 + /* with base */
1795 + break;
1796 + }
1797
1798 base = branch->merge[0]->dst;
1799 base = shorten_unambiguous_ref(base, 0);
1786 - if (!num_theirs) {
1800 + if (upstream_is_gone) {
1801 + strbuf_addf(sb,
1802 + _("Your branch is based on '%s', but the upstream is gone.\n"),
1803 + base);
1804 + if (advice_status_hints)
1805 + strbuf_addf(sb,
1806 + _(" (use \"git branch --unset-upstream\" to fixup)\n"));
1807 + } else if (!theirs) {
1808 strbuf_addf(sb,
1809 Q_("Your branch is ahead of '%s' by %d commit.\n",
1810 "Your branch is ahead of '%s' by %d commits.\n",
1790 - num_ours),
1791 - base, num_ours);
1811 + ours),
1812 + base, ours);
1813 if (advice_status_hints)
1814 strbuf_addf(sb,
1815 _(" (use \"git push\" to publish your local commits)\n"));
1795 - } else if (!num_ours) {
1816 + } else if (!ours) {
1817 strbuf_addf(sb,
1818 Q_("Your branch is behind '%s' by %d commit, "
1819 "and can be fast-forwarded.\n",
1820 "Your branch is behind '%s' by %d commits, "
1821 "and can be fast-forwarded.\n",
1801 - num_theirs),
1802 - base, num_theirs);
1822 + theirs),
1823 + base, theirs);
1824 if (advice_status_hints)
1825 strbuf_addf(sb,
1826 _(" (use \"git pull\" to update your local branch)\n"));
@@ -1811,8 +1832,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
1832 "Your branch and '%s' have diverged,\n"
1833 "and have %d and %d different commits each, "
1834 "respectively.\n",
1814 - num_theirs),
1815 - base, num_ours, num_theirs);
1835 + theirs),
1836 + base, ours, theirs);
1837 if (advice_status_hints)
1838 strbuf_addf(sb,
1839 _(" (use \"git pull\" to merge the remote branch into yours)\n"));
t/t6040-tracking-info.sh
+53 -4
@@ -28,10 +28,14 @@ test_expect_success setup '
28 git reset --hard HEAD^ &&
29 git checkout -b b4 origin &&
30 advance e &&
31 - advance f
31 + advance f &&
32 + git checkout -b brokenbase origin &&
33 + git checkout -b b5 --track brokenbase &&
34 + advance g &&
35 + git branch -d brokenbase
36 ) &&
37 git checkout -b follower --track master &&
34 - advance g
38 + advance h
39 '
40
41 script='s/^..\(b.\)[ 0-9a-f]*\[\([^]]*\)\].*/\1 \2/p'
@@ -56,6 +60,7 @@ b1 origin/master: ahead 1, behind 1
60 b2 origin/master: ahead 1, behind 1
61 b3 origin/master: behind 1
62 b4 origin/master: ahead 2
63 +b5 brokenbase: gone
64 EOF
65
66 test_expect_success 'branch -vv' '
@@ -67,7 +72,7 @@ test_expect_success 'branch -vv' '
72 test_i18ncmp expect actual
73 '
74
70 -test_expect_success 'checkout' '
75 +test_expect_success 'checkout (diverged from upstream)' '
76 (
77 cd test && git checkout b1
78 ) >actual &&
@@ -80,7 +85,15 @@ test_expect_success 'checkout with local tracked branch' '
85 test_i18ngrep "is ahead of" actual
86 '
87
83 -test_expect_success 'status' '
88 +test_expect_success 'checkout (upstream is gone)' '
89 + (
90 + cd test &&
91 + git checkout b5
92 + ) >actual &&
93 + test_i18ngrep "is based on .*, but the upstream is gone." actual
94 +'
95 +
96 +test_expect_success 'status (diverged from upstream)' '
97 (
98 cd test &&
99 git checkout b1 >/dev/null &&
@@ -90,6 +103,42 @@ test_expect_success 'status' '
103 test_i18ngrep "have 1 and 1 different" actual
104 '
105
106 +test_expect_success 'status (upstream is gone)' '
107 + (
108 + cd test &&
109 + git checkout b5 >/dev/null &&
110 + # reports nothing to commit
111 + test_must_fail git commit --dry-run
112 + ) >actual &&
113 + test_i18ngrep "is based on .*, but the upstream is gone." actual
114 +'
115 +
116 +cat >expect <<\EOF
117 +## b1...origin/master [ahead 1, behind 1]
118 +EOF
119 +
120 +test_expect_success 'status -s -b (diverged from upstream)' '
121 + (
122 + cd test &&
123 + git checkout b1 >/dev/null &&
124 + git status -s -b | head -1
125 + ) >actual &&
126 + test_i18ncmp expect actual
127 +'
128 +
129 +cat >expect <<\EOF
130 +## b5...brokenbase [gone]
131 +EOF
132 +
133 +test_expect_success 'status -s -b (upstream is gone)' '
134 + (
135 + cd test &&
136 + git checkout b5 >/dev/null &&
137 + git status -s -b | head -1
138 + ) >actual &&
139 + test_i18ncmp expect actual
140 +'
141 +
142 test_expect_success 'fail to track lightweight tags' '
143 git checkout master &&
144 git tag light &&
wt-status.c
+22 -4
@@ -1365,6 +1365,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
1365 const char *base;
1366 const char *branch_name;
1367 int num_ours, num_theirs;
1368 + int upstream_is_gone = 0;
1369
1370 color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
1371
@@ -1382,20 +1383,37 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
1383 branch = branch_get(s->branch + 11);
1384 if (s->is_initial)
1385 color_fprintf(s->fp, header_color, _("Initial commit on "));
1385 - if (!stat_tracking_info(branch, &num_ours, &num_theirs)) {
1386 - color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1386 +
1387 + color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1388 +
1389 + switch (stat_tracking_info(branch, &num_ours, &num_theirs)) {
1390 + case 0:
1391 + /* no base */
1392 fputc(s->null_termination ? '\0' : '\n', s->fp);
1393 return;
1394 + case -1:
1395 + /* with "gone" base */
1396 + upstream_is_gone = 1;
1397 + break;
1398 + default:
1399 + /* Stop reporting if neither side has changes. */
1400 + if (!num_ours && !num_theirs) {
1401 + fputc(s->null_termination ? '\0' : '\n', s->fp);
1402 + return;
1403 + }
1404 + /* with base */
1405 + break;
1406 }
1407
1408 base = branch->merge[0]->dst;
1409 base = shorten_unambiguous_ref(base, 0);
1393 - color_fprintf(s->fp, branch_color_local, "%s", branch_name);
1410 color_fprintf(s->fp, header_color, "...");
1411 color_fprintf(s->fp, branch_color_remote, "%s", base);
1412
1413 color_fprintf(s->fp, header_color, " [");
1398 - if (!num_ours) {
1414 + if (upstream_is_gone) {
1415 + color_fprintf(s->fp, header_color, _("gone"));
1416 + } else if (!num_ours) {
1417 color_fprintf(s->fp, header_color, _("behind "));
1418 color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
1419 } else if (!num_theirs) {