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 <rappa...@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

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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to