Skip to content

Commit 68067ca

Browse files
committed
builtin-branch.c: optimize --merged and --no-merged
"git branch --no-merged $commit" used to compute the merge base between the tip of each and every branch with the named $commit, but this was wasteful when you have many branches. Inside append_ref() we literally ran has_commit() between the tip of the branch and the merge_filter_ref. Instead, we can let the revision machinery traverse the history as if we are running: $ git rev-list --branches --not $commit by queueing the tips of branches we encounter as positive refs (this mimicks the "--branches" option in the above command line) and then appending the merge_filter_ref commit as a negative one, and finally calling prepare_revision_walk() to limit the list.. After the traversal is done, branch tips that are reachable from $commit are painted UNINTERESTING; they are already fully contained in $commit (i.e. --merged). Tips that are not painted UNINTERESTING still have commits that are not reachable from $commit, thus "--no-merged" will show them. With an artificial repository that has "master" and 1000 test-$i branches where they were created by "git branch test-$i master~$i": (with patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.12user 0.02system 0:00.15elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1588minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.15user 0.03system 0:00.18elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+1711minor)pagefaults 0swaps (without patch) $ /usr/bin/time git-branch --no-merged master >/dev/null 0.69user 0.03system 0:00.72elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2229minor)pagefaults 0swaps $ /usr/bin/time git-branch --no-merged test-200 >/dev/null 0.58user 0.03system 0:00.61elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+2248minor)pagefaults 0swaps Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0f31d68 commit 68067ca

File tree

1 file changed

+38
-21
lines changed

1 file changed

+38
-21
lines changed

builtin-branch.c

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "remote.h"
1414
#include "parse-options.h"
1515
#include "branch.h"
16+
#include "diff.h"
17+
#include "revision.h"
1618

1719
static const char * const builtin_branch_usage[] = {
1820
"git branch [options] [-r | -a] [--merged | --no-merged]",
@@ -179,25 +181,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
179181
struct ref_item {
180182
char *name;
181183
unsigned int kind;
182-
unsigned char sha1[20];
184+
struct commit *commit;
183185
};
184186

185187
struct ref_list {
188+
struct rev_info revs;
186189
int index, alloc, maxwidth;
187190
struct ref_item *list;
188191
struct commit_list *with_commit;
189192
int kinds;
190193
};
191194

192-
static int has_commit(const unsigned char *sha1, struct commit_list *with_commit)
195+
static int has_commit(struct commit *commit, struct commit_list *with_commit)
193196
{
194-
struct commit *commit;
195-
196197
if (!with_commit)
197198
return 1;
198-
commit = lookup_commit_reference_gently(sha1, 1);
199-
if (!commit)
200-
return 0;
201199
while (with_commit) {
202200
struct commit *other;
203201

@@ -213,6 +211,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
213211
{
214212
struct ref_list *ref_list = (struct ref_list*)(cb_data);
215213
struct ref_item *newitem;
214+
struct commit *commit;
216215
int kind;
217216
int len;
218217
static struct commit_list branch;
@@ -227,8 +226,12 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
227226
} else
228227
return 0;
229228

229+
commit = lookup_commit_reference_gently(sha1, 1);
230+
if (!commit)
231+
return error("branch '%s' does not point at a commit", refname);
232+
230233
/* Filter with with_commit if specified */
231-
if (!has_commit(sha1, ref_list->with_commit))
234+
if (!has_commit(commit, ref_list->with_commit))
232235
return 0;
233236

234237
/* Don't add types the caller doesn't want */
@@ -239,12 +242,8 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
239242
branch.item = lookup_commit_reference_gently(sha1, 1);
240243
if (!branch.item)
241244
die("Unable to lookup tip of branch %s", refname);
242-
if (merge_filter == SHOW_NOT_MERGED &&
243-
has_commit(merge_filter_ref, &branch))
244-
return 0;
245-
if (merge_filter == SHOW_MERGED &&
246-
!has_commit(merge_filter_ref, &branch))
247-
return 0;
245+
add_pending_object(&ref_list->revs,
246+
(struct object *)branch.item, refname);
248247
}
249248

250249
/* Resize buffer */
@@ -258,7 +257,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
258257
newitem = &(ref_list->list[ref_list->index++]);
259258
newitem->name = xstrdup(refname);
260259
newitem->kind = kind;
261-
hashcpy(newitem->sha1, sha1);
260+
newitem->commit = commit;
262261
len = strlen(newitem->name);
263262
if (len > ref_list->maxwidth)
264263
ref_list->maxwidth = len;
@@ -305,7 +304,13 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
305304
{
306305
char c;
307306
int color;
308-
struct commit *commit;
307+
struct commit *commit = item->commit;
308+
309+
if (merge_filter != NO_FILTER) {
310+
int is_merged = !!(item->commit->object.flags & UNINTERESTING);
311+
if (is_merged != (merge_filter == SHOW_MERGED))
312+
return;
313+
}
309314

310315
switch (item->kind) {
311316
case REF_LOCAL_BRANCH:
@@ -333,7 +338,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
333338
strbuf_init(&subject, 0);
334339
stat[0] = '\0';
335340

336-
commit = lookup_commit(item->sha1);
341+
commit = item->commit;
337342
if (commit && !parse_commit(commit)) {
338343
pretty_print_commit(CMIT_FMT_ONELINE, commit,
339344
&subject, 0, NULL, NULL, 0, 0);
@@ -346,7 +351,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
346351
printf("%c %s%-*s%s %s %s%s\n", c, branch_get_color(color),
347352
maxwidth, item->name,
348353
branch_get_color(COLOR_BRANCH_RESET),
349-
find_unique_abbrev(item->sha1, abbrev),
354+
find_unique_abbrev(item->commit->object.sha1, abbrev),
350355
stat, sub);
351356
strbuf_release(&subject);
352357
} else {
@@ -359,22 +364,34 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
359364
{
360365
int i;
361366
struct ref_list ref_list;
367+
struct commit *head_commit = lookup_commit_reference_gently(head_sha1, 1);
362368

363369
memset(&ref_list, 0, sizeof(ref_list));
364370
ref_list.kinds = kinds;
365371
ref_list.with_commit = with_commit;
372+
if (merge_filter != NO_FILTER)
373+
init_revisions(&ref_list.revs, NULL);
366374
for_each_ref(append_ref, &ref_list);
375+
if (merge_filter != NO_FILTER) {
376+
struct commit *filter;
377+
filter = lookup_commit_reference_gently(merge_filter_ref, 0);
378+
filter->object.flags |= UNINTERESTING;
379+
add_pending_object(&ref_list.revs,
380+
(struct object *) filter, "");
381+
ref_list.revs.limited = 1;
382+
prepare_revision_walk(&ref_list.revs);
383+
}
367384

368385
qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
369386

370387
detached = (detached && (kinds & REF_LOCAL_BRANCH));
371-
if (detached && has_commit(head_sha1, with_commit)) {
388+
if (detached && head_commit && has_commit(head_commit, with_commit)) {
372389
struct ref_item item;
373390
item.name = xstrdup("(no branch)");
374391
item.kind = REF_LOCAL_BRANCH;
375-
hashcpy(item.sha1, head_sha1);
392+
item.commit = head_commit;
376393
if (strlen(item.name) > ref_list.maxwidth)
377-
ref_list.maxwidth = strlen(item.name);
394+
ref_list.maxwidth = strlen(item.name);
378395
print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
379396
free(item.name);
380397
}

0 commit comments

Comments
 (0)