show-branch.c 8.6 KB
Newer Older
J
Junio C Hamano 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
#include <stdlib.h>
#include "cache.h"
#include "commit.h"
#include "refs.h"

static const char show_branch_usage[] =
"git-show-branch [--all] [--heads] [--tags] [--more=count] [<refs>...]";

#define UNINTERESTING	01

#define REV_SHIFT	 2
#define MAX_REVS	29 /* should not exceed bits_per_int - REV_SHIFT */

static struct commit *interesting(struct commit_list *list)
{
	while (list) {
		struct commit *commit = list->item;
		list = list->next;
		if (commit->object.flags & UNINTERESTING)
			continue;
		return commit;
	}
	return NULL;
}

static struct commit *pop_one_commit(struct commit_list **list_p)
{
	struct commit *commit;
	struct commit_list *list;
	list = *list_p;
	commit = list->item;
	*list_p = list->next;
	free(list);
	return commit;
}

struct commit_name {
	int head_rev;	/* which head's ancestor? */
	int generation;	/* how many parents away from head_rev */
};

/* Name the commit as nth generation ancestor of head_rev;
 * we count only the first-parent relationship for naming purposes.
 */
static void name_commit(struct commit *commit, int head_rev, int nth)
{
	struct commit_name *name;
	if (!commit->object.util)
		commit->object.util = xmalloc(sizeof(struct commit_name));
	name = commit->object.util;
	name->head_rev = head_rev;
	name->generation = nth;
}

/* Parent is the first parent of the commit.  We may name it
 * as (n+1)th generation ancestor of the same head_rev as
 * commit is nth generation ancestore of, if that generation
 * number is better than the name it already has.
 */
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;
	if (!commit_name)
		return;
	if (!parent_name ||
	    commit_name->generation + 1 < parent_name->generation)
		name_commit(parent, commit_name->head_rev,
			    commit_name->generation + 1);
}

static int mark_seen(struct commit *commit, struct commit_list **seen_p)
{
	if (!commit->object.flags) {
		insert_by_date(commit, seen_p);
		return 1;
	}
	return 0;
}

static void join_revs(struct commit_list **list_p,
		      struct commit_list **seen_p,
		      int num_rev, int extra)
{
	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);

	while (*list_p) {
		struct commit_list *parents;
		struct commit *commit = pop_one_commit(list_p);
		int flags = commit->object.flags & all_mask;
		int nth_parent = 0;
		int still_interesting = !!interesting(*list_p);

		if (!still_interesting && extra < 0)
			break;

		mark_seen(commit, seen_p);
		if ((flags & all_revs) == all_revs)
			flags |= UNINTERESTING;
		parents = commit->parents;

		while (parents) {
			struct commit *p = parents->item;
			int this_flag = p->object.flags;
			parents = parents->next;
			nth_parent++;
			if (nth_parent == 1)
				name_parent(commit, p);

			if ((this_flag & flags) == flags)
				continue;
			parse_commit(p);
			if (mark_seen(p, seen_p) && !still_interesting)
				extra--;
			p->object.flags |= flags;
			insert_by_date(p, list_p);
		}
	}
}

static void show_one_commit(struct commit *commit, char **head_name)
{
	char pretty[128], *cp;
	struct commit_name *name = commit->object.util;
	pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0,
			    pretty, sizeof(pretty));
	if (!strncmp(pretty, "[PATCH] ", 8))
		cp = pretty + 8;
	else
		cp = pretty;
	if (name && head_name) {
		printf("[%s", head_name[name->head_rev]);
		if (name->generation)
			printf("~%d", name->generation);
		printf("] ");
	}
	puts(cp);
}

static char *ref_name[MAX_REVS + 1];
static int ref_name_cnt;

144 145 146 147 148 149 150 151 152 153 154 155
static int compare_ref_name(const void *a_, const void *b_)
{
	const char * const*a = a_, * const*b = b_;
	return strcmp(*a, *b);
}

static void sort_ref_range(int bottom, int top)
{
	qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
	      compare_ref_name);
}

J
Junio C Hamano 已提交
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
static int append_ref(const char *refname, const unsigned char *sha1)
{
	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
	if (!commit)
		return 0;
	if (MAX_REVS < ref_name_cnt) {
		fprintf(stderr, "warning: ignoring %s; "
			"cannot handle more than %d refs",
			refname, MAX_REVS);
		return 0;
	}
	ref_name[ref_name_cnt++] = strdup(refname);
	ref_name[ref_name_cnt] = NULL;
	return 0;
}

static int append_head_ref(const char *refname, const unsigned char *sha1)
{
	if (strncmp(refname, "refs/heads/", 11))
		return 0;
176
	return append_ref(refname + 11, sha1);
J
Junio C Hamano 已提交
177 178 179 180 181 182 183 184 185 186 187
}

static int append_tag_ref(const char *refname, const unsigned char *sha1)
{
	if (strncmp(refname, "refs/tags/", 10))
		return 0;
	return append_ref(refname + 5, sha1);
}

static void snarf_refs(int head, int tag)
{
188 189
	if (head) {
		int orig_cnt = ref_name_cnt;
J
Junio C Hamano 已提交
190
		for_each_ref(append_head_ref);
191 192 193 194
		sort_ref_range(orig_cnt, ref_name_cnt);
	}
	if (tag) {
		int orig_cnt = ref_name_cnt;
J
Junio C Hamano 已提交
195
		for_each_ref(append_tag_ref);
196 197
		sort_ref_range(orig_cnt, ref_name_cnt);
	}
J
Junio C Hamano 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
}

static int rev_is_head(char *head_path, int headlen,
		       char *name,
		       unsigned char *head_sha1, unsigned char *sha1)
{
	int namelen;
	if ((!head_path[0]) || memcmp(head_sha1, sha1, 20))
		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;
}

static int show_merge_base(struct commit_list *seen, int num_rev)
{
	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);

	while (seen) {
		struct commit *commit = pop_one_commit(&seen);
		int flags = commit->object.flags & all_mask;
		if (!(flags & UNINTERESTING) &&
		    ((flags & all_revs) == all_revs)) {
			puts(sha1_to_hex(commit->object.sha1));
			return 0;
		}
	}
	return 1;
}

int main(int ac, char **av)
{
	struct commit *rev[MAX_REVS], *commit;
	struct commit_list *list = NULL, *seen = NULL;
	int num_rev, i, extra = 0;
	int all_heads = 0, all_tags = 0;
	char head_path[128];
	int head_path_len;
	unsigned char head_sha1[20];
	int merge_base = 0;
244
	char **label;
J
Junio C Hamano 已提交
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

	while (1 < ac && av[1][0] == '-') {
		char *arg = av[1];
		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, "--more"))
			extra = 1;
		else if (!strncmp(arg, "--more=", 7)) {
			extra = atoi(arg + 7);
			if (extra < 0)
				usage(show_branch_usage);
		}
		else if (!strcmp(arg, "--merge-base"))
			merge_base = 1;
		else
			usage(show_branch_usage);
		ac--; av++;
	}
	ac--; av++;

	if (all_heads + all_tags)
		snarf_refs(all_heads, all_tags);

	while (0 < ac) {
		unsigned char revkey[20];
		if (get_sha1(*av, revkey))
			die("bad sha1 reference %s", *av);
		append_ref(*av, revkey);
		ac--; av++;
	}

	/* If still no revs, then add heads */
	if (!ref_name_cnt)
		snarf_refs(1, 0);

	for (num_rev = 0; ref_name[num_rev]; num_rev++) {
		unsigned char revkey[20];

		if (MAX_REVS <= num_rev)
			die("cannot handle more than %d revs.", MAX_REVS);
		if (get_sha1(ref_name[num_rev], revkey))
			usage(show_branch_usage);
		commit = lookup_commit_reference(revkey);
		if (!commit)
			die("cannot find commit %s (%s)",
			    ref_name[num_rev], revkey);
		parse_commit(commit);
		if (!commit->object.util)
			name_commit(commit, num_rev, 0);
		mark_seen(commit, &seen);

		/* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
		 * and so on.  REV_SHIFT bits from bit 0 are used for
		 * internal bookkeeping.
		 */
		commit->object.flags |= 1u << (num_rev + REV_SHIFT);
		insert_by_date(commit, &list);
		rev[num_rev] = commit;
	}
	join_revs(&list, &seen, num_rev, extra);

	head_path_len = readlink(".git/HEAD", head_path, sizeof(head_path)-1);
	if ((head_path_len < 0) || get_sha1("HEAD", head_sha1))
		head_path[0] = 0;
	else
		head_path[head_path_len] = 0;

	if (merge_base)
		return show_merge_base(seen, num_rev);

319 320
	/* Show list */
	if (1 < num_rev) {
J
Junio C Hamano 已提交
321 322 323 324 325 326 327 328 329 330 331 332
		for (i = 0; i < num_rev; i++) {
			int j;
			int is_head = rev_is_head(head_path,
						  head_path_len,
						  ref_name[i],
						  head_sha1,
						  rev[i]->object.sha1);
			for (j = 0; j < i; j++)
				putchar(' ');
			printf("%c [%s] ", is_head ? '*' : '!', ref_name[i]);
			show_one_commit(rev[i], NULL);
		}
333 334 335 336 337 338
		for (i = 0; i < num_rev; i++)
			putchar('-');
		putchar('\n');
	}

	label = ref_name;
J
Junio C Hamano 已提交
339 340 341
	while (seen) {
		struct commit *commit = pop_one_commit(&seen);
		int this_flag = commit->object.flags;
342 343
		static char *obvious[] = { "" };

J
Junio C Hamano 已提交
344 345
		if ((this_flag & UNINTERESTING) && (--extra < 0))
			break;
346 347 348 349 350 351 352 353 354
		if (1 < num_rev) {
			for (i = 0; i < num_rev; i++)
				putchar((this_flag & (1u << (i + REV_SHIFT)))
					? '+' : ' ');
			putchar(' ');
		}
		show_one_commit(commit, label);
		if (num_rev == 1)
			label = obvious;
J
Junio C Hamano 已提交
355 356 357
	}
	return 0;
}