git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC] checkout to notice forks (Re: Minor annoyance with git push)

From
Junio C Hamano <gitster@pobox.com>
Date
Feb 17, 2008, 01:08 UTC
Message-ID
<7vfxvs75kp.fsf_-_@gitster.siamese.dyndns.org>
In-Reply-To
<7vwspd5z1d.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
Show 7 quoted lines
> Perhaps making "git-checkout" to notice this and offer (or
> suggest) fast-forwarding at that point may be safer and make
> more sense.  You cannot grow your local branch unless you check
> them out, and your remote tracking will keep growing without the
> auto-ff you are suggesting, so it is not like people will lose
> anchoring point to compare between branches if we do not
> auto-ff.
So I did this.

When you are switching to a branch that is marked to merge from somewhere else, e.g. when you have:

    [branch "next"]
            remote = upstream
            merge = refs/heads/next
    [remote "upstream"]
            url = ...
            fetch = refs/heads/*:refs/remotes/linus/*

and you say "git checkout next", then after we switch the branch we check the upstream (in this case, refs/remotes/linus/next) and our branch, and:

    (1) if they match, nothing happens;
    (2) if you are ahead (i.e. the upstream is a strict ancestor
        of you), one line message tells you so;
    (3) otherwise, you are either behind or you and the upstream
        have forked.  One line message will tell you which and
        then you will see a "log --pretty=oneline --left-right".

We could enhance this with an option that tells the command to check if there is no local change, and automatically fast forward when you are truly behind. But I ripped out that change because I was unsure what the right way should be to allow users to control it (issues include that checkout should not become automatically interactive).

This is hot off the press and I know it tends to be a bit too loud. It is based on Daniel's "git checkout in C" with Dscho's lock_file fix.

---
 builtin-checkout.c |  136 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 136 insertions(+), 0 deletions(-)
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 59a0ef4..9370ba0 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -12,6 +12,7 @@
 #include "branch.h"
 #include "diff.h"
 #include "revision.h"
+#include "remote.h"
 
 static const char * const checkout_usage[] = {
 	"git checkout [options] <branch>",
@@ -290,6 +291,139 @@ static int merge_working_tree(struct checkout_opts *opts,
 	return 0;
 }
 
+/*
+ * We really should allow cb_data... Yuck
+ */
+static const char *branch_name;
+static int branch_name_len;
+static char *found_remote;
+static char *found_merge;
+static int read_branch_config(const char *var, const char *value)
+{
+	const char *name;
+	if (prefixcmp(var, "branch."))
+		return 0; /* not ours */
+	name = var + strlen("branch.");
+	if (strncmp(name, branch_name, branch_name_len) ||
+	    name[branch_name_len] != '.')
+		return 0; /* not ours either */
+	if (!strcmp(name + branch_name_len, ".remote")) {
+		/*
+		 * Yeah, I know Christian's clean-up should
+		 * be used here, but the topic is based on an
+		 * older fork point.
+		 */
+		if (!value)
+			return error("'%s' not string", var);
+		found_remote = xstrdup(value);
+		return 0;
+	}
+	if (!strcmp(name + branch_name_len, ".merge")) {
+		if (!value)
+			return error("'%s' not string", var);
+		found_merge = xstrdup(value);
+		return 0;
+	}
+	return 0; /* not ours */
+}
+
+static int find_build_base(const char *ours, char **base)
+{
+	struct remote *remote;
+	struct refspec spec;
+
+	*base = NULL;
+
+	branch_name = ours + strlen("refs/heads/");
+	branch_name_len = strlen(branch_name);
+	found_remote = NULL;
+	found_merge = NULL;
+	git_config(read_branch_config);
+
+	if (!found_remote || !found_merge) {
+	cleanup:
+		free(found_remote);
+		free(found_merge);
+		return 0;
+	}
+
+	remote = remote_get(found_remote);
+	memset(&spec, 0, sizeof(spec));
+	spec.src = found_merge;
+	if (remote_find_tracking(remote, &spec))
+		goto cleanup;
+	*base = spec.dst;
+	return 1;
+}
+
+static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *opts)
+{
+	/*
+	 * 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;
+	const char *msgfmt;
+	char symmetric[84];
+	int show_log;
+
+	if (!resolve_ref(new->path, sha1, 1, NULL))
+		return;
+	ours = lookup_commit(sha1);
+
+	if (!find_build_base(new->path, &base))
+		return;
+
+	sprintf(symmetric, "%s", sha1_to_hex(sha1));
+
+	/*
+	 * Ok, it is tracking base; is it ahead of us?
+	 */
+	if (!resolve_ref(base, sha1, 1, NULL))
+		return;
+	theirs = lookup_commit(sha1);
+
+	sprintf(symmetric + 40, "...%s", sha1_to_hex(sha1));
+
+	if (!hashcmp(sha1, ours->object.sha1))
+		return; /* we are the same */
+
+	show_log = 1;
+	if (in_merge_bases(theirs, &ours, 1)) {
+		msgfmt = "You are ahead of the tracked branch '%s'\n";
+		show_log = 0;
+	}
+	else if (in_merge_bases(ours, &theirs, 1))
+		msgfmt = "Your branch can be fast-forwarded to the tracked branch '%s'\n";
+	else
+		msgfmt = "Both your branch and the tracked branch '%s' have own changes, you would eventually need to merge\n";
+
+	if (!prefixcmp(base, "refs/remotes/"))
+		base += strlen("refs/remotes/");
+	fprintf(stderr, msgfmt, base);
+
+	if (show_log) {
+		const char *args[32];
+		int ac;
+
+		ac = 0;
+		args[ac++] = "log";
+		args[ac++] = "--pretty=oneline";
+		args[ac++] = "--abbrev-commit";
+		args[ac++] = "--left-right";
+		args[ac++] = "--boundary";
+		args[ac++] = symmetric;
+		args[ac++] = "--";
+		args[ac] = NULL;
+
+		run_command_v_opt(args, RUN_GIT_CMD);
+	}
+}
+
+
 static void update_refs_for_switch(struct checkout_opts *opts,
 				   struct branch_info *old,
 				   struct branch_info *new)
@@ -332,6 +466,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 	}
 	remove_branch_state();
 	strbuf_release(&msg);
+	if (new->path)
+		adjust_to_tracking(new, opts);
 }
 
 static int switch_branches(struct checkout_opts *opts,
Previous: Johannes SchindelinNext: Daniel Barkalow
Message 28 of 71 in “Minor annoyance with git push”
  1. Martin LanghoffFeb 8, 2008
  2. Martin LanghoffFeb 8, 2008
  3. Junio C HamanoFeb 8, 2008
  4. Steffen ProhaskaFeb 9, 2008
  5. Junio C HamanoFeb 10, 2008
  6. Johannes SchindelinFeb 10, 2008
  7. Johannes SchindelinFeb 8, 2008
  8. Martin LanghoffFeb 8, 2008
  9. Mike HommeyFeb 8, 2008
  10. SeanFeb 8, 2008
  11. Steffen ProhaskaFeb 8, 2008
  12. Johannes SchindelinFeb 8, 2008
  13. Martin LanghoffFeb 8, 2008
  14. Johannes SchindelinFeb 8, 2008
  15. Jeff KingFeb 9, 2008
  16. Jeff KingFeb 9, 2008
  17. Johannes SchindelinFeb 9, 2008
  18. Jeff KingFeb 9, 2008
  19. Steffen ProhaskaFeb 9, 2008
  20. Jeff KingFeb 9, 2008
  21. Junio C HamanoFeb 9, 2008
  22. Jeff KingFeb 9, 2008
  23. Martin LanghoffFeb 9, 2008
  24. Johannes SchindelinFeb 9, 2008
  25. Junio C HamanoFeb 10, 2008
  26. Jeff KingFeb 10, 2008
  27. Johannes SchindelinFeb 10, 2008
  28. [RFC] checkout to notice forks (Re: Minor annoyance with git push)Junio C Hamano, Feb 17, 2008
  29. Daniel BarkalowFeb 17, 2008
  30. Junio C HamanoFeb 17, 2008
  31. Daniel BarkalowFeb 17, 2008
  32. Junio C HamanoFeb 17, 2008
  33. Daniel BarkalowFeb 17, 2008
  34. Jeff KingFeb 17, 2008
  35. Santi BéjarFeb 20, 2008
  36. Martin LanghoffFeb 19, 2008
  37. checkout: tone down the "forked status" diagnostic messagesJunio C Hamano, Feb 20, 2008
  38. Jeff KingFeb 21, 2008
  39. checkout: updates to tracking reportJunio C Hamano, Feb 21, 2008
  40. Jay SoffianFeb 21, 2008
  41. Daniel BarkalowFeb 21, 2008
  42. Jay SoffianFeb 21, 2008
  43. Steffen ProhaskaFeb 9, 2008
  44. Johannes SchindelinFeb 9, 2008
  45. Junio C HamanoFeb 10, 2008
  46. Johannes SchindelinFeb 10, 2008
  47. Jeff KingFeb 10, 2008
  48. Johannes SchindelinFeb 10, 2008
  49. Jeff KingFeb 10, 2008
  50. Johannes SchindelinFeb 10, 2008
  51. Jeff KingFeb 10, 2008
  52. Junio C HamanoFeb 20, 2008
  53. Johannes SchindelinFeb 20, 2008
  54. Jay SoffianFeb 20, 2008
  55. Johannes SchindelinFeb 20, 2008
  56. Steven WalterFeb 21, 2008
  57. Johannes SchindelinFeb 22, 2008
  58. Jeff KingFeb 20, 2008
  59. Junio C HamanoFeb 20, 2008
  60. Jeff KingFeb 20, 2008
  61. Jeff KingFeb 20, 2008
  62. Junio C HamanoFeb 20, 2008
  63. Jeff KingFeb 20, 2008
  64. Wincent ColaiutaFeb 10, 2008
  65. Steven WalterFeb 10, 2008
  66. Johannes SchindelinFeb 10, 2008
  67. Johannes SchindelinFeb 10, 2008
  68. Wincent ColaiutaFeb 10, 2008
  69. Jeff KingFeb 10, 2008
  70. Junio C HamanoFeb 10, 2008
  71. Jeff KingFeb 10, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.