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@xxxxxxxxx> --- .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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html