-#include <stdlib.h>
-#include <fnmatch.h>
#include "cache.h"
#include "commit.h"
#include "refs.h"
#include "builtin.h"
static const char show_branch_usage[] =
-"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>";
+static const char show_branch_usage_reflog[] =
+"--reflog is incompatible with --all, --remotes, --independent or --merge-base";
-static int default_num = 0;
-static int default_alloc = 0;
-static const char **default_arg = NULL;
+static int default_num;
+static int default_alloc;
+static const char **default_arg;
#define UNINTERESTING 01
#define REV_SHIFT 2
-#define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */
+#define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
+
+#define DEFAULT_REFLOG 4
static struct commit *interesting(struct commit_list *list)
{
static void name_commit(struct commit *commit, const char *head_name, int nth)
{
struct commit_name *name;
- if (!commit->object.util)
- commit->object.util = xmalloc(sizeof(struct commit_name));
- name = commit->object.util;
+ if (!commit->util)
+ commit->util = xmalloc(sizeof(struct commit_name));
+ name = commit->util;
name->head_name = head_name;
name->generation = nth;
}
*/
static void name_parent(struct commit *commit, struct commit *parent)
{
- struct commit_name *commit_name = commit->object.util;
- struct commit_name *parent_name = parent->object.util;
+ struct commit_name *commit_name = commit->util;
+ struct commit_name *parent_name = parent->util;
if (!commit_name)
return;
if (!parent_name ||
int i = 0;
while (c) {
struct commit *p;
- if (!c->object.util)
+ if (!c->util)
break;
if (!c->parents)
break;
p = c->parents->item;
- if (!p->object.util) {
+ if (!p->util) {
name_parent(c, p);
i++;
}
+ else
+ break;
c = p;
}
return i;
/* First give names to the given heads */
for (cl = list; cl; cl = cl->next) {
c = cl->item;
- if (c->object.util)
+ if (c->util)
continue;
for (i = 0; i < num_rev; i++) {
if (rev[i] == c) {
struct commit_name *n;
int nth;
c = cl->item;
- if (!c->object.util)
+ if (!c->util)
continue;
- n = c->object.util;
+ n = c->util;
parents = c->parents;
nth = 0;
while (parents) {
char newname[1000], *en;
parents = parents->next;
nth++;
- if (p->object.util)
+ if (p->util)
continue;
en = newname;
switch (n->generation) {
en += sprintf(en, "^");
else
en += sprintf(en, "^%d", nth);
- name_commit(p, strdup(newname), 0);
+ name_commit(p, xstrdup(newname), 0);
i++;
name_first_parent_chain(p);
}
static int mark_seen(struct commit *commit, struct commit_list **seen_p)
{
if (!commit->object.flags) {
- insert_by_date(commit, seen_p);
+ commit_list_insert(commit, seen_p);
return 1;
}
return 0;
* Postprocess to complete well-poisoning.
*
* At this point we have all the commits we have seen in
- * seen_p list (which happens to be sorted chronologically but
- * it does not really matter). Mark anything that can be
- * reached from uninteresting commits not interesting.
+ * seen_p list. Mark anything that can be reached from
+ * uninteresting commits not interesting.
*/
for (;;) {
int changed = 0;
static void show_one_commit(struct commit *commit, int no_name)
{
char pretty[256], *cp;
- struct commit_name *name = commit->object.util;
+ struct commit_name *name = commit->util;
if (commit->object.parsed)
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- pretty, sizeof(pretty), 0);
+ pretty, sizeof(pretty), 0, NULL, NULL, 0);
else
strcpy(pretty, "(unavailable)");
- if (!strncmp(pretty, "[PATCH] ", 8))
+ if (!prefixcmp(pretty, "[PATCH] "))
cp = pretty + 8;
else
cp = pretty;
compare_ref_name);
}
-static int append_ref(const char *refname, const unsigned char *sha1)
+static int append_ref(const char *refname, const unsigned char *sha1,
+ int allow_dups)
{
struct commit *commit = lookup_commit_reference_gently(sha1, 1);
int i;
if (!commit)
return 0;
- /* Avoid adding the same thing twice */
- for (i = 0; i < ref_name_cnt; i++)
- if (!strcmp(refname, ref_name[i]))
- return 0;
+ if (!allow_dups) {
+ /* Avoid adding the same thing twice */
+ for (i = 0; i < ref_name_cnt; i++)
+ if (!strcmp(refname, ref_name[i]))
+ return 0;
+ }
if (MAX_REVS <= ref_name_cnt) {
fprintf(stderr, "warning: ignoring %s; "
"cannot handle more than %d refs\n",
refname, MAX_REVS);
return 0;
}
- ref_name[ref_name_cnt++] = strdup(refname);
+ ref_name[ref_name_cnt++] = xstrdup(refname);
ref_name[ref_name_cnt] = NULL;
return 0;
}
-static int append_head_ref(const char *refname, const unsigned char *sha1)
+static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
unsigned char tmp[20];
int ofs = 11;
- if (strncmp(refname, "refs/heads/", ofs))
+ if (prefixcmp(refname, "refs/heads/"))
+ return 0;
+ /* If both heads/foo and tags/foo exists, get_sha1 would
+ * get confused.
+ */
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+ ofs = 5;
+ return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+ unsigned char tmp[20];
+ int ofs = 13;
+ if (prefixcmp(refname, "refs/remotes/"))
return 0;
/* If both heads/foo and tags/foo exists, get_sha1 would
* get confused.
*/
- if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+ if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
ofs = 5;
- return append_ref(refname + ofs, sha1);
+ return append_ref(refname + ofs, sha1, 0);
}
-static int append_tag_ref(const char *refname, const unsigned char *sha1)
+static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
- if (strncmp(refname, "refs/tags/", 10))
+ if (prefixcmp(refname, "refs/tags/"))
return 0;
- return append_ref(refname + 5, sha1);
+ return append_ref(refname + 5, sha1, 0);
}
static const char *match_ref_pattern = NULL;
return cnt;
}
-static int append_matching_ref(const char *refname, const unsigned char *sha1)
+static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
{
/* we want to allow pattern hold/<asterisk> to show all
* branches under refs/heads/hold/, and v0.99.9? to show
return 0;
if (fnmatch(match_ref_pattern, tail, 0))
return 0;
- if (!strncmp("refs/heads/", refname, 11))
- return append_head_ref(refname, sha1);
- if (!strncmp("refs/tags/", refname, 10))
- return append_tag_ref(refname, sha1);
- return append_ref(refname, sha1);
+ if (!prefixcmp(refname, "refs/heads/"))
+ return append_head_ref(refname, sha1, flag, cb_data);
+ if (!prefixcmp(refname, "refs/tags/"))
+ return append_tag_ref(refname, sha1, flag, cb_data);
+ return append_ref(refname, sha1, 0);
}
-static void snarf_refs(int head, int tag)
+static void snarf_refs(int head, int remotes)
{
if (head) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_head_ref);
+ for_each_ref(append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
- if (tag) {
+ if (remotes) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_tag_ref);
+ for_each_ref(append_remote_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
-static int rev_is_head(char *head_path, int headlen, char *name,
+static int rev_is_head(char *head, int headlen, char *name,
unsigned char *head_sha1, unsigned char *sha1)
{
- int namelen;
- if ((!head_path[0]) ||
- (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
+ if ((!head[0]) ||
+ (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
return 0;
- namelen = strlen(name);
- if ((headlen < namelen) ||
- memcmp(head_path + headlen - namelen, name, namelen))
- return 0;
- if (headlen == namelen ||
- head_path[headlen - namelen - 1] == '/')
- return 1;
- return 0;
+ if (!prefixcmp(head, "refs/heads/"))
+ head += 11;
+ if (!prefixcmp(name, "refs/heads/"))
+ name += 11;
+ else if (!prefixcmp(name, "heads/"))
+ name += 6;
+ return !strcmp(head, name);
}
static int show_merge_base(struct commit_list *seen, int num_rev)
{
unsigned char revkey[20];
if (!get_sha1(av, revkey)) {
- append_ref(av, revkey);
+ append_ref(av, revkey, 0);
return;
}
if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
int saved_matches = ref_name_cnt;
match_ref_pattern = av;
match_ref_slash = count_slash(av);
- for_each_ref(append_matching_ref);
+ for_each_ref(append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
error("no matching refs with %s", av);
default_alloc = default_alloc * 3 / 2 + 20;
default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
}
- default_arg[default_num++] = strdup(value);
+ default_arg[default_num++] = xstrdup(value);
default_arg[default_num] = NULL;
return 0;
}
return 0;
}
-int cmd_show_branch(int ac, const char **av, char **envp)
+static void parse_reflog_param(const char *arg, int *cnt, const char **base)
+{
+ char *ep;
+ *cnt = strtoul(arg, &ep, 10);
+ if (*ep == ',')
+ *base = ep + 1;
+ else if (*ep)
+ die("unrecognized reflog param '%s'", arg + 9);
+ else
+ *base = NULL;
+ if (*cnt <= 0)
+ *cnt = DEFAULT_REFLOG;
+}
+
+int cmd_show_branch(int ac, const char **av, const char *prefix)
{
struct commit *rev[MAX_REVS], *commit;
+ char *reflog_msg[MAX_REVS];
struct commit_list *list = NULL, *seen = NULL;
unsigned int rev_mask[MAX_REVS];
int num_rev, i, extra = 0;
- int all_heads = 0, all_tags = 0;
+ int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
int lifo = 1;
- char head_path[128];
- const char *head_path_p;
- int head_path_len;
+ char head[128];
+ const char *head_p;
+ int head_len;
unsigned char head_sha1[20];
int merge_base = 0;
int independent = 0;
int head_at = -1;
int topics = 0;
int dense = 1;
+ int reflog = 0;
+ const char *reflog_base = NULL;
- setup_git_directory();
git_config(git_show_branch_config);
/* If nothing is specified, try the default first */
ac--; av++;
break;
}
- else if (!strcmp(arg, "--all"))
- all_heads = all_tags = 1;
- else if (!strcmp(arg, "--heads"))
- all_heads = 1;
- else if (!strcmp(arg, "--tags"))
- all_tags = 1;
+ else if (!strcmp(arg, "--all") || !strcmp(arg, "-a"))
+ all_heads = all_remotes = 1;
+ else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r"))
+ all_remotes = 1;
else if (!strcmp(arg, "--more"))
extra = 1;
else if (!strcmp(arg, "--list"))
with_current_branch = 1;
else if (!strcmp(arg, "--sha1-name"))
sha1_name = 1;
- else if (!strncmp(arg, "--more=", 7))
+ else if (!prefixcmp(arg, "--more="))
extra = atoi(arg + 7);
else if (!strcmp(arg, "--merge-base"))
merge_base = 1;
dense = 0;
else if (!strcmp(arg, "--date-order"))
lifo = 0;
+ else if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) {
+ reflog = DEFAULT_REFLOG;
+ }
+ else if (!prefixcmp(arg, "--reflog="))
+ parse_reflog_param(arg + 9, &reflog, &reflog_base);
+ else if (!prefixcmp(arg, "-g="))
+ parse_reflog_param(arg + 3, &reflog, &reflog_base);
else
usage(show_branch_usage);
ac--; av++;
}
ac--; av++;
- /* Only one of these is allowed */
- if (1 < independent + merge_base + (extra != 0))
- usage(show_branch_usage);
+ if (extra || reflog) {
+ /* "listing" mode is incompatible with
+ * independent nor merge-base modes.
+ */
+ if (independent || merge_base)
+ usage(show_branch_usage);
+ if (reflog && ((0 < extra) || all_heads || all_remotes))
+ /*
+ * Asking for --more in reflog mode does not
+ * make sense. --list is Ok.
+ *
+ * Also --all and --remotes do not make sense either.
+ */
+ usage(show_branch_usage_reflog);
+ }
/* If nothing is specified, show all branches by default */
- if (ac + all_heads + all_tags == 0)
+ if (ac + all_heads + all_remotes == 0)
all_heads = 1;
- if (all_heads + all_tags)
- snarf_refs(all_heads, all_tags);
- while (0 < ac) {
- append_one_rev(*av);
- ac--; av++;
+ if (reflog) {
+ unsigned char sha1[20];
+ char nth_desc[256];
+ char *ref;
+ int base = 0;
+
+ if (ac == 0) {
+ static const char *fake_av[2];
+ const char *refname;
+
+ refname = resolve_ref("HEAD", sha1, 1, NULL);
+ fake_av[0] = xstrdup(refname);
+ fake_av[1] = NULL;
+ av = fake_av;
+ ac = 1;
+ }
+ if (ac != 1)
+ die("--reflog option needs one branch name");
+
+ if (MAX_REVS < reflog)
+ die("Only %d entries can be shown at one time.",
+ MAX_REVS);
+ if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+ die("No such ref %s", *av);
+
+ /* Has the base been specified? */
+ if (reflog_base) {
+ char *ep;
+ base = strtoul(reflog_base, &ep, 10);
+ if (*ep) {
+ /* Ah, that is a date spec... */
+ unsigned long at;
+ at = approxidate(reflog_base);
+ read_ref_at(ref, at, -1, sha1, NULL,
+ NULL, NULL, &base);
+ }
+ }
+
+ for (i = 0; i < reflog; i++) {
+ char *logmsg, *m;
+ const char *msg;
+ unsigned long timestamp;
+ int tz;
+
+ if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+ ×tamp, &tz, NULL)) {
+ reflog = i;
+ break;
+ }
+ msg = strchr(logmsg, '\t');
+ if (!msg)
+ msg = "(none)";
+ else
+ msg++;
+ m = xmalloc(strlen(msg) + 200);
+ sprintf(m, "(%s) %s",
+ show_date(timestamp, tz, 1),
+ msg);
+ reflog_msg[i] = m;
+ free(logmsg);
+ sprintf(nth_desc, "%s@{%d}", *av, base+i);
+ append_ref(nth_desc, sha1, 1);
+ }
+ }
+ else if (all_heads + all_remotes)
+ snarf_refs(all_heads, all_remotes);
+ else {
+ while (0 < ac) {
+ append_one_rev(*av);
+ ac--; av++;
+ }
}
- head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
- if (head_path_p) {
- head_path_len = strlen(head_path_p);
- memcpy(head_path, head_path_p, head_path_len + 1);
+ head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
+ if (head_p) {
+ head_len = strlen(head_p);
+ memcpy(head, head_p, head_len + 1);
}
else {
- head_path_len = 0;
- head_path[0] = 0;
+ head_len = 0;
+ head[0] = 0;
}
- if (with_current_branch && head_path_p) {
+ if (with_current_branch && head_p) {
int has_head = 0;
for (i = 0; !has_head && i < ref_name_cnt; i++) {
/* We are only interested in adding the branch
* HEAD points at.
*/
- if (rev_is_head(head_path,
- head_path_len,
+ if (rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1, NULL))
has_head++;
}
if (!has_head) {
- int pfxlen = strlen(git_path("refs/heads/"));
- append_one_rev(head_path + pfxlen);
+ int pfxlen = strlen("refs/heads/");
+ append_one_rev(head + pfxlen);
}
}
if (0 <= extra)
join_revs(&list, &seen, num_rev, extra);
+ sort_by_date(&seen);
+
if (merge_base)
return show_merge_base(seen, num_rev);
if (1 < num_rev || extra < 0) {
for (i = 0; i < num_rev; i++) {
int j;
- int is_head = rev_is_head(head_path,
- head_path_len,
+ int is_head = rev_is_head(head,
+ head_len,
ref_name[i],
head_sha1,
rev[i]->object.sha1);
printf("%c [%s] ",
is_head ? '*' : '!', ref_name[i]);
}
- /* header lines never need name */
- show_one_commit(rev[i], 1);
+
+ if (!reflog) {
+ /* header lines never need name */
+ show_one_commit(rev[i], 1);
+ }
+ else
+ puts(reflog_msg[i]);
+
if (is_head)
head_at = i;
}