threads / patch / 40767

patch, 5 partsff-refs: builtin cmd to check and fast forward local refs to their upstream

Subject: [PATCH 1/5] ff-refs: builtin cmd to check and fast forward local refs to their upstream

## tl;dr

13 messages between Nov 11, 2015 and Dec 1, 2015. Diffs are folded; open one to read it.

replies: 12people: 5as markdown or json

Michael Rappazzo· Nov 11, 2015, 02:11 UTC · lore

[PATCH 0/5] ff-refs: builtin command to fast-forward local refs

This patch series is built on (based on) 'next' because it relies on worktree.c

`ff-refs` will update local branches which can be fast-forwarded to their upstream tracking branch. Any branch which has diverged from the upstream will be left untouched by this command. Additionally, there are options for '--dry-run' and to '--skip-worktrees'.

There are two primary update mechanisms for fast-forwarding a branch.
  - For a checked out branch, emulate `git-merge --ff-only`
  - For a non-checked out branch, emulate `git update-ref`

When run on a repo with multiple worktrees (created with git-worktree add), git-ff-refs will take that into account when fast-forwarding. That is, it will run in 'merge --ff-only' emulation mode when a branch is checked out in a worktree, rather than in 'update-ref' mode.

The primary benefit of ff-refs will come for those who maintain several local branches which track upstream remote branches that update often. The intended usage pattern is to run `git-fetch` followed by `git-ff-refs`.

Michael Rappazzo (5):
  ff-refs: builtin cmd to check and fast forward local refs to their
    upstream
  ff-refs: update each updatable ref
  ff-refs: add --dry-run and --skip-worktree options
  ff-refs: Add documentation
  ff-refs: Add tests
 .gitignore                    |   1 +
 Documentation/git-ff-refs.txt |  55 +++++++++
 Makefile                      |   1 +
 builtin.h                     |   1 +
 builtin/ff-refs.c             | 272 ++++++++++++++++++++++++++++++++++++++++++
 command-list.txt              |   1 +
 git.c                         |   1 +
 t/t7900-ff-refs.sh            | 164 +++++++++++++++++++++++++
 8 files changed, 496 insertions(+)
 create mode 100644 Documentation/git-ff-refs.txt
 create mode 100644 builtin/ff-refs.c
 create mode 100755 t/t7900-ff-refs.sh
-- 
2.6.2
Michael Rappazzo· Nov 11, 2015, 02:11 UTC · re: Michael Rappazzo · lore

Each local branch with an upstream remote is checked to see if it can be fast-forwarded to its upstream. If fast-forward applies to the branch, then this is reported to the user.

The statuses are
	UP-TO-DATE - The local branch is the same or equal to the upstream
	WOULD-UPDATE - The branch would be fast forwarded
	REMOTE-MISSING - The branch is tracking an upstream that is not present
	NON-FAST-FORWARD - The branch has diverged from the upstream
Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
---
 .gitignore        |   1 +
 Makefile          |   1 +
 builtin.h         |   1 +
 builtin/ff-refs.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 command-list.txt  |   1 +
 git.c             |   1 +
 6 files changed, 226 insertions(+)
 create mode 100644 builtin/ff-refs.c
Show changes to 6 files +226 −0

.gitignore, Makefile, builtin.h, builtin/ff-refs.c, command-list.txt, git.c

diff --git a/.gitignore b/.gitignore
index 1c2f832..e86a490 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@
 /git-difftool--helper
 /git-describe
 /git-fast-export
+/git-ff-refs
 /git-fast-import
 /git-fetch
 /git-fetch-pack
diff --git a/Makefile b/Makefile
index 43ceeb9..8e312ad 100644
--- a/Makefile
+++ b/Makefile
@@ -853,6 +853,7 @@ BUILTIN_OBJS += builtin/diff.o
 BUILTIN_OBJS += builtin/fast-export.o
 BUILTIN_OBJS += builtin/fetch-pack.o
 BUILTIN_OBJS += builtin/fetch.o
+BUILTIN_OBJS += builtin/ff-refs.o
 BUILTIN_OBJS += builtin/fmt-merge-msg.o
 BUILTIN_OBJS += builtin/for-each-ref.o
 BUILTIN_OBJS += builtin/fsck.o
diff --git a/builtin.h b/builtin.h
index 6b95006..5680e33 100644
--- a/builtin.h
+++ b/builtin.h
@@ -63,6 +63,7 @@ extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_ff_refs(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/builtin/ff-refs.c b/builtin/ff-refs.c
new file mode 100644
index 0000000..94a4649
--- /dev/null
+++ b/builtin/ff-refs.c
@@ -0,0 +1,221 @@
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+#include "remote.h"
+#include "run-command.h"
+#include "worktree.h"
+
+struct worktree **worktrees;
+const char *padding = ".....................................................";
+
+static const char * const builtin_ff_refs_usage[] = {
+	N_("git ff-refs [<options>]"),
+	NULL
+};
+
+enum ff_result_type {
+	UP_TO_DATE,
+	UPDATABLE,
+	REMOTE_MISSING,
+	NON_FAST_FORWARD,
+	UNABLE_TO_UPDATE
+};
+
+struct ff_ref_details {
+	struct branch *branch;
+	const char *upstream;
+	const char *shortened_upstream;
+	int names_length;
+	enum ff_result_type result_type;
+
+	struct commit *branch_commit;
+	struct commit *upstream_commit;
+	struct commit *merge_base;
+	struct worktree *wt;
+};
+
+struct ff_ref_data {
+	int max_names_length;
+
+	int detail_counter;
+	int detail_alloc;
+	struct ff_ref_details **detail_list;
+};
+
+static const char *result_type_str(enum ff_result_type result_type)
+{
+	switch (result_type) {
+	case UP_TO_DATE:
+		return _("UP-TO-DATE");
+	case UPDATABLE:
+		return _("WOULD-UPDATE");
+	case REMOTE_MISSING:
+		return _("REMOTE-MISSING");
+	case NON_FAST_FORWARD:
+		return _("NON-FAST-FORWARD");
+	default:
+		return _("UNABLE-TO-UPDATE");
+	}
+}
+
+/**
+ * return the worktree with the given refname checked out, or NULL if that
+ * ref is not checked out in any branch.
+ *
+ * This implementation assumes a small number of worktrees (since it loops
+ * through each worktree for every ref).  If a repository has a large number
+ * of worktrees, then it might be beneficial to implement this as a hashmap
+ * lookup instead.
+ */
+static struct worktree *find_worktree(const char *refname)
+{
+	int i = 0;
+
+	for (i = 0; worktrees[i]; i++) {
+		if (!worktrees[i]->is_detached && !strcmp(worktrees[i]->head_ref, refname)) {
+			return worktrees[i];
+		}
+	}
+	return NULL;
+}
+
+/**
+ * After all of the relevant refs have been collected, process the
+ * interesting ones
+ */
+static void process_refs(struct ff_ref_data *data)
+{
+	int i = 0;
+
+	for (i = 0; data->detail_list[i]; i++) {
+		struct ff_ref_details *details;
+		int padLen;
+
+		details = data->detail_list[i];
+		padLen = 3 + data->max_names_length - details->names_length;
+		if (padLen < 0)
+			padLen = 0;
+
+		printf("     %s -> %s%*.*s",
+			details->branch->name, details->shortened_upstream, padLen, padLen, padding);
+		printf("[%s]\n", result_type_str(details->result_type));
+	}
+}
+
+static void add_to_detail_list(struct ff_ref_data *data,
+		struct ff_ref_details *details)
+{
+	if (!data->detail_alloc) {
+		data->detail_list = xmalloc(sizeof(struct ff_ref_details *));
+		data->detail_alloc = 1;
+	} else
+		ALLOC_GROW(data->detail_list, data->detail_counter + 1, data->detail_alloc);
+
+	if (details && details->names_length > data->max_names_length)
+		data->max_names_length = details->names_length;
+
+	data->detail_list[data->detail_counter++] = details;
+}
+
+/**
+ * Look for refs which have an upstream configured.  Each ref with an upstream
+ * is added to a list to later possibly make changes on.  All of the necessary
+ * read-only data is gleaned here.
+ */
+static int analize_refs(const char *refname,
+			const struct object_id *oid, int flags, void *cb_data) {
+
+	struct branch *branch;
+	const char *upstream;
+	struct ff_ref_data *data = cb_data;
+
+	branch = branch_get(shorten_unambiguous_ref(refname, 0));
+	upstream = branch_get_upstream(branch, NULL);
+	if (upstream) {
+		struct ff_ref_details *details = xmalloc(sizeof(struct ff_ref_details));
+		unsigned char upstream_hash[GIT_SHA1_RAWSZ];
+
+		details->branch = branch;
+		details->upstream = upstream;
+
+		details->shortened_upstream = shorten_unambiguous_ref(upstream, 0);
+		details->branch_commit = NULL;
+		details->upstream_commit = NULL;
+		details->merge_base = NULL;
+		details->result_type = UNABLE_TO_UPDATE;
+		details->names_length = strlen(branch->name) +
+				strlen(details->shortened_upstream);
+		details->wt = find_worktree(details->branch->refname);
+
+		if (!resolve_ref_unsafe(details->upstream, RESOLVE_REF_READING,
+				upstream_hash, NULL))
+			details->result_type = REMOTE_MISSING;
+
+		else if (!hashcmp(oid->hash, upstream_hash))
+			details->result_type = UP_TO_DATE;
+		else {
+			struct commit_list *bases;
+
+			details->branch_commit = lookup_commit_reference(oid->hash);
+			details->upstream_commit = lookup_commit_reference(upstream_hash);
+			bases = get_merge_bases(details->branch_commit,
+					details->upstream_commit);
+			details->merge_base = bases->item;
+
+			if (!hashcmp(upstream_hash, details->merge_base->object.sha1))
+				details->result_type = UP_TO_DATE;
+
+			else if (!in_merge_bases(details->branch_commit, details->upstream_commit))
+				details->result_type = NON_FAST_FORWARD;
+
+			else
+				details->result_type = UPDATABLE;
+		}
+		add_to_detail_list(data, details);
+	}
+	return 0;
+}
+
+/**
+ * Free the memory allocated for all of the data
+ */
+static void free_data(struct ff_ref_data *data)
+{
+	int i = 0;
+
+	for (i = 0; data->detail_list[i]; i++)
+		free(data->detail_list[i]);
+	free(data);
+}
+
+int cmd_ff_refs(int argc, const char **argv, const char *prefix)
+{
+	int ret = 0;
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, prefix, options, builtin_ff_refs_usage, 0);
+	if (argc)
+		usage_with_options(builtin_ff_refs_usage, options);
+	else {
+		struct ff_ref_data *data = NULL;
+
+		worktrees = get_worktrees();
+		data = xmalloc(sizeof(struct ff_ref_data));
+		data->detail_alloc = 0;
+		data->detail_counter = 0;
+		data->max_names_length = 0;
+
+		ret = for_each_ref(&analize_refs, data);
+		add_to_detail_list(data, NULL);
+
+		//for each detail
+		process_refs(data);
+
+		free_worktrees(worktrees);
+		free_data(data);
+	}
+	return ret;
+}
diff --git a/command-list.txt b/command-list.txt
index 2a94137..b766ea8 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -54,6 +54,7 @@ git-fast-export                         ancillarymanipulators
 git-fast-import                         ancillarymanipulators
 git-fetch                               mainporcelain           remote
 git-fetch-pack                          synchingrepositories
+git-ff-refs                             mainporcelain           history
 git-filter-branch                       ancillarymanipulators
 git-fmt-merge-msg                       purehelpers
 git-for-each-ref                        plumbinginterrogators
diff --git a/git.c b/git.c
index 6ed824c..1c75156 100644
--- a/git.c
+++ b/git.c
@@ -404,6 +404,7 @@ static struct cmd_struct commands[] = {
 	{ "fast-export", cmd_fast_export, RUN_SETUP },
 	{ "fetch", cmd_fetch, RUN_SETUP },
 	{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
+	{ "ff-refs", cmd_ff_refs, 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 },
-- 
2.6.2
Michael Rappazzo· Nov 11, 2015, 02:11 UTC · re: Michael Rappazzo · lore

[PATCH 2/5] ff-refs: update each updatable ref

If an updatable ref is checked out in any worktree, emulate merge --ff-only to also update the local work tree (including executing the post-merge hook).

If an updatable ref is not checked out in any worktree, the ref is simply updated.

If a ref update is successful, that ref is reported as 'UPDATED'. If it is not successful, the ref is reported as 'UNABLE-TO-UPDATE'.

Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
---
 builtin/ff-refs.c | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
Show changes to builtin/ff-refs.c +40 −0
diff --git a/builtin/ff-refs.c b/builtin/ff-refs.c
index 94a4649..f14afc5 100644
--- a/builtin/ff-refs.c
+++ b/builtin/ff-refs.c
@@ -59,6 +59,43 @@ static const char *result_type_str(enum ff_result_type result_type)
 }
 
 /**
+ * Do the ref update.
+ *  - If the ref is checked out in any worktree, emulate merge --ff-only to
+ *    also update the local work tree (including executing the post-merge hook).
+ *
+ *  - If the ref is not checked out in any worktree, update it
+ *
+ *  - If any of the ref updates fails, the result_type is set to UNABLE-TO-UPDATE
+ */
+static void do_ref_update(struct ff_ref_data *data, struct ff_ref_details *details)
+{
+	const char *refname = details->branch->refname;
+
+	if (details->wt) {
+		struct strbuf path = STRBUF_INIT;
+
+		strbuf_getcwd(&path);
+		chdir(details->wt->path);
+		set_git_dir(details->wt->git_dir);
+		read_index(&the_index);
+
+		if (checkout_fast_forward(details->branch_commit->object.sha1,
+				details->upstream_commit->object.sha1, 1))
+			details->result_type = NON_FAST_FORWARD;
+		else if (update_ref("ff-refs", refname, details->upstream_commit->object.sha1,
+				details->branch_commit->object.sha1, 0, UPDATE_REFS_QUIET_ON_ERR)) {
+			details->result_type = UNABLE_TO_UPDATE;
+			run_hook_le(NULL, "post-merge", "0", NULL);
+		}
+		discard_index(&the_index);
+		chdir(path.buf);
+		strbuf_release(&path);
+	} else if (update_ref("ff-refs", refname, details->upstream_commit->object.sha1,
+			details->branch_commit->object.sha1, 0, UPDATE_REFS_QUIET_ON_ERR))
+		details->result_type = UNABLE_TO_UPDATE;
+}
+
+/**
  * return the worktree with the given refname checked out, or NULL if that
  * ref is not checked out in any branch.
  *
@@ -98,6 +135,9 @@ static void process_refs(struct ff_ref_data *data)
 
 		printf("     %s -> %s%*.*s",
 			details->branch->name, details->shortened_upstream, padLen, padLen, padding);
+		if (details->result_type == UPDATABLE)
+			do_ref_update(data, details);
+
 		printf("[%s]\n", result_type_str(details->result_type));
 	}
 }
-- 
2.6.2
Michael Rappazzo· Nov 11, 2015, 02:11 UTC · re: Michael Rappazzo · lore

[PATCH 3/5] ff-refs: add --dry-run and --skip-worktree options

'--dry-run' reports the updatable state of each ref without acutally updating any refs. Refs which are fast-forwardable are reported as 'WOULD-UPDATE'.

'--skip-worktrees' does not update refs which are checked out in any worktree. Branches which are fast-forwardable but checked out in a worktree are reported as 'SKIPPED'. With `--dry-run` these are reported 'WOULD-SKIP'.

Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
---
 builtin/ff-refs.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)
Show changes to builtin/ff-refs.c +13 −2
diff --git a/builtin/ff-refs.c b/builtin/ff-refs.c
index f14afc5..ae68cfb 100644
--- a/builtin/ff-refs.c
+++ b/builtin/ff-refs.c
@@ -5,6 +5,8 @@
 #include "run-command.h"
 #include "worktree.h"
 
+int dry_run = 0;
+int no_wt = 0;
 struct worktree **worktrees;
 const char *padding = ".....................................................";
 
@@ -18,6 +20,7 @@ enum ff_result_type {
 	UPDATABLE,
 	REMOTE_MISSING,
 	NON_FAST_FORWARD,
+	SKIPPED,
 	UNABLE_TO_UPDATE
 };
 
@@ -48,11 +51,13 @@ static const char *result_type_str(enum ff_result_type result_type)
 	case UP_TO_DATE:
 		return _("UP-TO-DATE");
 	case UPDATABLE:
-		return _("WOULD-UPDATE");
+		return dry_run ? _("WOULD-UPDATE") : _("UPDATED");
 	case REMOTE_MISSING:
 		return _("REMOTE-MISSING");
 	case NON_FAST_FORWARD:
 		return _("NON-FAST-FORWARD");
+	case SKIPPED:
+		return dry_run ? _("WOULD-SKIP") : _("SKIPPED");
 	default:
 		return _("UNABLE-TO-UPDATE");
 	}
@@ -135,7 +140,7 @@ static void process_refs(struct ff_ref_data *data)
 
 		printf("     %s -> %s%*.*s",
 			details->branch->name, details->shortened_upstream, padLen, padLen, padding);
-		if (details->result_type == UPDATABLE)
+		if (!dry_run && details->result_type == UPDATABLE)
 			do_ref_update(data, details);
 
 		printf("[%s]\n", result_type_str(details->result_type));
@@ -208,6 +213,9 @@ static int analize_refs(const char *refname,
 			else if (!in_merge_bases(details->branch_commit, details->upstream_commit))
 				details->result_type = NON_FAST_FORWARD;
 
+			else if (no_wt && details->wt)
+				details->result_type = SKIPPED;
+
 			else
 				details->result_type = UPDATABLE;
 		}
@@ -233,6 +241,9 @@ int cmd_ff_refs(int argc, const char **argv, const char *prefix)
 	int ret = 0;
 
 	struct option options[] = {
+		OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be fast-forwarded")),
+		OPT_BOOL(0, "skip-worktrees", &no_wt,
+				N_("skip refs checked out in any worktree")),
 		OPT_END()
 	};
 
-- 
2.6.2
Michael Rappazzo· Nov 11, 2015, 02:11 UTC · re: Michael Rappazzo · lore

[PATCH 4/5] ff-refs: Add documentation

Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
---
 Documentation/git-ff-refs.txt | 55 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100644 Documentation/git-ff-refs.txt
Show changes to Documentation/git-ff-refs.txt +55 −0
diff --git a/Documentation/git-ff-refs.txt b/Documentation/git-ff-refs.txt
new file mode 100644
index 0000000..569d1d4
--- /dev/null
+++ b/Documentation/git-ff-refs.txt
@@ -0,0 +1,55 @@
+git-ff-refs(1)
+============
+
+NAME
+----
+git-ff-refs - Fast-forward each local branch to its upstream (if possible)
+
+
+SYNOPSIS
+--------
+[verse]
+'git ff-refs' [<options>]
+
+
+DESCRIPTION
+-----------
+Update local branches to their tracked upstream if and only if the local branch
+can be fast-forwarded to the upstream ref.  Local branches which have diverged
+from the upstream will not be updated.
+
+Each local branch tracking an upstream will be reported with a status based on
+whether or not it it was updated.  The statuses are:
+
+	UP-TO-DATE - The local branch is the same or equal to the upstream
+	UPDATED - The branch was fast forwarded
+	WOULD-UPDATE - With `--dry-run`, the branch is fast forwardable
+	REMOTE-MISSING - The branch is tracking an upstream that is not present
+	NON-FAST-FORWARD - The branch has diverged from the upstream
+	SKIPPED - With `--skip-worktrees` the branch is fast forwardable but checked
+		out in a local worktree
+	WOULD-SKIP - With `--skip-worktrees` and `--dry-run`, the branch is fast
+		forwardable but checked out in a local worktree
+	UNABLE-TO-UPDATE - An error occurred while trying to update the branch
+
+OPTIONS
+-------
+--dry-run::
+	Report the updatable state of each branch without acutally updating anything.
+	In a dry run, fast-forwardable branches are marked 'WOULD-UPDATE' instead of
+	'UPDATED'.
+
+--skip-worktrees::
+	Does not update branches which are checked out in any worktree.  Branches
+	which are fast-forwardable that are checked out in any worktree are marked
+	'SKIPPED'.  If `--dry-run` is also set, those branches are marked 'WOULD-SKIP'.
+
+
+SEE ALSO
+--------
+linkgit:git-update-ref[1]
+linkgit:git-merge[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
-- 
2.6.2
Michael Rappazzo· Nov 11, 2015, 02:11 UTC · re: Michael Rappazzo · lore

[PATCH 5/5] ff-refs: Add tests

Signed-off-by: Michael Rappazzo <rappazzo@gmail.com>
---
 t/t7900-ff-refs.sh | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100755 t/t7900-ff-refs.sh
Show changes to t/t7900-ff-refs.sh +164 −0
diff --git a/t/t7900-ff-refs.sh b/t/t7900-ff-refs.sh
new file mode 100755
index 0000000..3cfbcb8
--- /dev/null
+++ b/t/t7900-ff-refs.sh
@@ -0,0 +1,164 @@
+#!/bin/sh
+
+# SKIPPED
+
+test_description='test ff-refs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit init &&
+	for i in $(test_seq 1 9)
+	do
+		echo "data" >file_$i &&
+		git add file_$i &&
+		git commit -m"Commit $i" &&
+		git branch br_$i
+	done
+'
+
+test_expect_success 'UP-TO-DATE for equal branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git ff-refs >actual &&
+		grep "UP-TO-DATE" actual
+	)
+'
+
+test_expect_success 'UP-TO-DATE for ahead local branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git ff-refs >actual &&
+		echo "data" >file_new &&
+		git add file_new &&
+		git commit -m"Commit new" &&
+		grep "UP-TO-DATE" actual
+	)
+'
+
+test_expect_success 'REMOTE-MISSING by local config change' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git config --replace branch.master.merge refs/heads/nothing &&
+		git ff-refs >actual &&
+		grep "REMOTE-MISSING" actual
+	)
+'
+
+test_expect_success 'NON-FAST-FORWARD for diverged branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		echo "data" >file_new &&
+		git add file_new &&
+		git commit -m"Commit new" &&
+		git ff-refs >actual &&
+		grep "NON-FAST-FORWARD" actual
+	)
+'
+
+test_expect_success 'UPDATED for fast-forwardable branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git ff-refs >actual &&
+		grep "UPDATED" actual
+	)
+'
+
+test_expect_success 'WOULD-UPDATE for dry-run on fast-forwardable branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git ff-refs --dry-run >actual &&
+		grep "WOULD-UPDATE" actual
+	)
+'
+
+test_expect_success 'SKIPPED for skip-worktrees on fast-forwardable branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git ff-refs --skip-worktrees >actual &&
+		grep "SKIPPED" actual
+	)
+'
+
+test_expect_success 'WOULD-SKIP for dry-run, skip-worktrees on fast-forwardable branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git ff-refs --dry-run --skip-worktrees >actual &&
+		grep "WOULD-SKIP" actual
+	)
+'
+
+test_expect_success 'UPDATE for fast-forwardable, not checked-out branch' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git checkout -b other origin/br_3 &&
+		git ff-refs >actual &&
+		grep "master" actual | grep "UPDATED"
+	)
+'
+
+test_expect_success 'UPDATE for fast-forwardable, not checked-out branch using --skip-worktrees' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git checkout -b other origin/br_3 &&
+		git ff-refs --skip-worktrees >actual &&
+		grep "master" actual | grep "UPDATED"
+	)
+'
+
+test_expect_success 'UPDATE multiple' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git checkout -b other origin/br_5 &&
+		git reset --hard origin/br_3 &&
+		git ff-refs >actual &&
+		grep "master" actual | grep "UPDATED" &&
+		grep "other" actual | grep "UPDATED"
+	)
+'
+
+test_expect_success 'UPDATE one, skip worktree on another' '
+	test_when_finished "rm -rf local" &&
+	git clone . local &&
+	(
+		cd local &&
+		git reset --hard origin/br_3 &&
+		git checkout -b other origin/br_5 &&
+		git reset --hard origin/br_3 &&
+		git ff-refs --skip-worktrees >actual &&
+		grep "master" actual | grep "UPDATED" &&
+		grep "other" actual | grep "SKIPPED"
+	)
+'
+
+test_done
-- 
2.6.2
Michael J Gruber· Nov 11, 2015, 10:41 UTC · re: Michael Rappazzo · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

Michael Rappazzo venit, vidit, dixit 11.11.2015 03:11:
Show 20 quoted lines
> This patch series is built on (based on) 'next' because it relies on
> worktree.c
> 
> `ff-refs` will update local branches which can be fast-forwarded to their
> upstream tracking branch.  Any branch which has diverged from the upstream
> will be left untouched by this command.  Additionally, there are options
> for '--dry-run' and to '--skip-worktrees'.
> 
> There are two primary update mechanisms for fast-forwarding a branch.
>   - For a checked out branch, emulate `git-merge --ff-only`
>   - For a non-checked out branch, emulate `git update-ref`
> 
> When run on a repo with multiple worktrees (created with git-worktree add),
> git-ff-refs will take that into account when fast-forwarding.  That is, it
> will run in 'merge --ff-only' emulation mode when a branch is checked out
> in a worktree, rather than in 'update-ref' mode.
> 
> The primary benefit of ff-refs will come for those who maintain several
> local branches which track upstream remote branches that update often.  The
> intended usage pattern is to run `git-fetch` followed by `git-ff-refs`.

I'm sorry, but I don't see why this deserves a new command. If refspec with and without "+" are not enough then maybe "git fetch --all" or "git remote update" should learn a new "--ff-only" option (ignoring all "+") like merge has.

As for updating worktrees: This shouldn't be taken too lightly anyways. But the worktree interface still has some rough edges, and I would hope that it learns a "foreach" subcommand very much like the submodule version. That would allow you to

git worktree foreach git merge --ff-only
with a systematic aproach that opens many other opportunities.
Michael
Mike Rappazzo· Nov 11, 2015, 12:32 UTC · re: Michael J Gruber · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

On Wed, Nov 11, 2015 at 5:41 AM, Michael J Gruber <git@drmicha.warpmail.net> wrote:

Show 26 quoted lines
> Michael Rappazzo venit, vidit, dixit 11.11.2015 03:11:
>> This patch series is built on (based on) 'next' because it relies on
>> worktree.c
>>
>> `ff-refs` will update local branches which can be fast-forwarded to their
>> upstream tracking branch.  Any branch which has diverged from the upstream
>> will be left untouched by this command.  Additionally, there are options
>> for '--dry-run' and to '--skip-worktrees'.
>>
>> There are two primary update mechanisms for fast-forwarding a branch.
>>   - For a checked out branch, emulate `git-merge --ff-only`
>>   - For a non-checked out branch, emulate `git update-ref`
>>
>> When run on a repo with multiple worktrees (created with git-worktree add),
>> git-ff-refs will take that into account when fast-forwarding.  That is, it
>> will run in 'merge --ff-only' emulation mode when a branch is checked out
>> in a worktree, rather than in 'update-ref' mode.
>>
>> The primary benefit of ff-refs will come for those who maintain several
>> local branches which track upstream remote branches that update often.  The
>> intended usage pattern is to run `git-fetch` followed by `git-ff-refs`.
>
> I'm sorry, but I don't see why this deserves a new command. If refspec
> with and without "+" are not enough then maybe "git fetch --all" or "git
> remote update" should learn a new "--ff-only" option (ignoring all "+")
> like merge has.

Maybe I wasn't clear in my description, or maybe I misunderstand something. This command is about updating local refs (branches, really), not the local copy of a remote ref. If, for example I have local branches:

    master -> origin/master
    next -> origin/next
    pu -> origin/pu
    feature1 -> features/feature1
    feature2 -> features/feature2
    feature3 -> features/feature3
    bug1 -> features/bugs/bug1
    bug2 -> features/bugs/bug2

If I don't use multiple worktrees, I probably only have one of those checked out at any one time. If any of the upstream branches are updated, then when I fetch those branches will be behind. If I wanted to make sure that the branches I am not touching are updated, I would have to do it individually (AFAIK). And why not update my local worktree if it is a fast-forward?. This command aims to put that local branch update into a single command.

    > git fetch --all
    fetching origin...
        abc1234..abc1235  next -> origin/next
        abd1234..abd1235  pu -> origin/pu
    fetching features...
        123abcd..123abce  feature1 -> features/feature1
      + 124abcd...124abce feature2 -> features/feature2
        125abcd..125abce  feature3 -> features/feature3
    > git ff-refs
        master -> origin/master.........[UP-TO-DATE]
        next -> origin/next.............[UPDATED]
        pu -> origin/pu.................[UPDATED]
        feature1 -> features/feature1...[UPDATED]
        feature2 -> features/feature2...[NON-FAST-FORWARD]
        feature3 -> features/feature3...[UPDATED]
        bug1 -> features/bugs/bug1......[UP-TO-DATE]
        bug2 -> features/bugs/bug2......[UP-TO-DATE]

For reference, I have been using a scripted version of this command [1]. Assuming that I change your mind on this command, I will add this example to the help doc.

Show 11 quoted lines
>
> As for updating worktrees: This shouldn't be taken too lightly anyways.
> But the worktree interface still has some rough edges, and I would hope
> that it learns a "foreach" subcommand very much like the submodule
> version. That would allow you to
>
> git worktree foreach git merge --ff-only
>
> with a systematic aproach that opens many other opportunities.
>
> Michael

I am aware of the current status of the worktrees command (I worked on the 'list' command). If a user only wants to update unchecked out branches, there is a command line option provided, '--skip-worktrees'.

The foreach command sounds like a good idea, but I don't know that it would help here, as ff-refs is looping through all of the refs already (ala for-each-ref). If you are proposing foreach-worktree as an alternative, that is good for half of the command, but I would still want to update the unchecked out refs.

_Mike
[1] https://github.com/rappazzo/dotfiles/blob/ff-refs/bin/git-ff-refs
Michael J Gruber· Nov 17, 2015, 15:28 UTC · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

Mike Rappazzo venit, vidit, dixit 17.11.2015 14:58:
> (This message is off list)
[cut the message part, though no big secrets there]
Hi Mike,
I don't think there's a point in discussing this off-list.
Your intentions are clearer now, yes, or else I would have asked more.

I still don't like the idea of having a new command just for the purpose of fast-forwarding local branches from specified upstreams.

What's wrong with "git merge --ff-only" once you check them out? We have all the gory messages when you checkout a branch or use the git prompt or "branch -vv". And if you don't - how is forgetting to "ff-refs" better than forgetting to "merge --ff-only"?

In short, I don't see a problem that this is solving, but maybe it's because we use local branches differently, I dunno.

If other people were interested they should or would have come up with comments, I think (as a general rule).

Cheers, Michael

Mike Rappazzo· Nov 17, 2015, 15:36 UTC · re: Michael J Gruber · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

On Tue, Nov 17, 2015 at 10:28 AM, Michael J Gruber <git@drmicha.warpmail.net> wrote:

Show 12 quoted lines
> Mike Rappazzo venit, vidit, dixit 17.11.2015 14:58:
>
> I still don't like the idea of having a new command just for the purpose
> of fast-forwarding local branches from specified upstreams.
>
> What's wrong with "git merge --ff-only" once you check them out? We have
> all the gory messages when you checkout a branch or use the git prompt
> or "branch -vv". And if you don't - how is forgetting to "ff-refs"
> better than forgetting to "merge --ff-only"?
>
> In short, I don't see a problem that this is solving, but maybe it's
> because we use local branches differently, I dunno.
For me I use this command more as a post-fetch:
git fetch --all --prune && git-ff-refs

I imagine that the big difference is in the number of branches that I maintain, and perhaps in the way that I use gitk to visualize them. I would be happy to add another option to git-fetch for --ff-refs as an alternative if that would feel better than a full-on builtin.

Show 6 quoted lines
>
> If other people were interested they should or would have come up with
> comments, I think (as a general rule).
>
> Cheers,
> Michael
Johannes Schindelin· Nov 18, 2015, 09:56 UTC · re: Mike Rappazzo · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

Hi Mike,
On Tue, 17 Nov 2015, Mike Rappazzo wrote:
Show 23 quoted lines
> On Tue, Nov 17, 2015 at 10:28 AM, Michael J Gruber
> <git@drmicha.warpmail.net> wrote:
> > Mike Rappazzo venit, vidit, dixit 17.11.2015 14:58:
> >
> > I still don't like the idea of having a new command just for the purpose
> > of fast-forwarding local branches from specified upstreams.
> >
> > What's wrong with "git merge --ff-only" once you check them out? We have
> > all the gory messages when you checkout a branch or use the git prompt
> > or "branch -vv". And if you don't - how is forgetting to "ff-refs"
> > better than forgetting to "merge --ff-only"?
> >
> > In short, I don't see a problem that this is solving, but maybe it's
> > because we use local branches differently, I dunno.
> 
> For me I use this command more as a post-fetch:
> 
> git fetch --all --prune && git-ff-refs
> 
> I imagine that the big difference is in the number of branches that I
> maintain, and perhaps in the way that I use gitk to visualize them.  I
> would be happy to add another option to git-fetch for --ff-refs as an
> alternative if that would feel better than a full-on builtin.

I would much prefer, say, `git fetch --all --prune --fast-forward-tracking-branches` (with maybe `-T` as short option for `--fast-forward-tracking-branches` and/or a shorter `--ff-tracking`) to a new builtin.

But yeah, I can see how it is convenient when you have to work with N tracking branches where N > 2.

Thanks, Dscho

Jeff King· Nov 24, 2015, 22:39 UTC · re: Johannes Schindelin · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

On Wed, Nov 18, 2015 at 10:56:02AM +0100, Johannes Schindelin wrote:
Show 13 quoted lines
> > For me I use this command more as a post-fetch:
> > 
> > git fetch --all --prune && git-ff-refs
> > 
> > I imagine that the big difference is in the number of branches that I
> > maintain, and perhaps in the way that I use gitk to visualize them.  I
> > would be happy to add another option to git-fetch for --ff-refs as an
> > alternative if that would feel better than a full-on builtin.
> 
> I would much prefer, say, `git fetch --all --prune
> --fast-forward-tracking-branches` (with maybe `-T` as short option for
> `--fast-forward-tracking-branches` and/or a shorter `--ff-tracking`) to a
> new builtin.

FWIW, that makes a lot more sense to me, as it would presumably touch only branches which track whatever we just updated, and not other random refs.

I have to admit that I'm a little wary of something like ff-refs meeting all needs, though. I have custom scripts that match my workflow and tell me when a branch could be updated. I could replace part of them with "ff-refs --dry-run", but that is really not much code. Basically:

  git for-each-ref --format='%(refname) %(upstream)' refs/heads |
  while read ref upstream; do
    git merge-base --is-ancestor $ref $upstream &&
      echo "$ref can fast-forward"
  done
-Peff
Junio C Hamano· Dec 1, 2015, 00:24 UTC · re: Jeff King · lore

Re: [PATCH 0/5] ff-refs: builtin command to fast-forward local refs

Jeff King <peff@peff.net> writes:
Show 15 quoted lines
> On Wed, Nov 18, 2015 at 10:56:02AM +0100, Johannes Schindelin wrote:
>
>> > For me I use this command more as a post-fetch:
>> > 
>> > git fetch --all --prune && git-ff-refs
>> > 
>> > I imagine that the big difference is in the number of branches that I
>> > maintain, and perhaps in the way that I use gitk to visualize them.  I
>> > would be happy to add another option to git-fetch for --ff-refs as an
>> > alternative if that would feel better than a full-on builtin.
>> 
>> I would much prefer, say, `git fetch --all --prune
>> --fast-forward-tracking-branches` (with maybe `-T` as short option for
>> `--fast-forward-tracking-branches` and/or a shorter `--ff-tracking`) to a
>> new builtin.

Hmph, I am not sure it is a good idea to allow "git fetch" affect refs that it was not told to "fetch", but that is why you give a new option from the command line to update refs that are not involved in the fetch based on what was fetched, so it might be OK.

But the above is *NOT* fast-forwarding "tracking" branch. It is doing something else: fast-forwarding the local branch that is based on a remote-tracking branch.

    They have refs/heads/master, and they call it their 'master'
    branch.
    You have refs/remotes/origin/master, and that is the
    remote-tracking branch for their 'master'.
    You may have prepared your 'master' to build on their 'master'
    branch.  That is not a 'tracking branch' for anything.
So --ff-tracking and the other name above need to be rethought.
> FWIW, that makes a lot more sense to me, as it would presumably touch
> only branches which track whatever we just updated, and not other random
> refs.
>

This ff-refs series breaks build for me by introducing calls to chdir() whose return values are not checked -Werror=unused-result, by the way.

Show 10 quoted lines
> I have to admit that I'm a little wary of something like ff-refs meeting
> all needs, though. I have custom scripts that match my workflow and tell
> me when a branch could be updated. I could replace part of them with
> "ff-refs --dry-run", but that is really not much code. Basically:
>
>   git for-each-ref --format='%(refname) %(upstream)' refs/heads |
>   while read ref upstream; do
>     git merge-base --is-ancestor $ref $upstream &&
>       echo "$ref can fast-forward"
>   done
Yup.  I like that one.

← back to recent threads