From dd3bf0f4a5bab17011e94bfeb808ee64dd2ad040 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Mon, 19 Nov 2007 10:21:44 +0100 Subject: [PATCH 1/3] parse-options: Allow to hide options from the default usage. This is useful for backward-compatibility aliases, or very advanced command line switches introduced for internal git usages and have no real use for a user. parse-options still shows them if the user asks for --help-all. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano --- parse-options.c | 17 +++++++++++++++-- parse-options.h | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/parse-options.c b/parse-options.c index d3e608ac45..e12b428c0a 100644 --- a/parse-options.c +++ b/parse-options.c @@ -216,6 +216,9 @@ is_abbreviated: return error("unknown option `%s'", arg); } +static NORETURN void usage_with_options_internal(const char * const *, + const struct option *, int); + int parse_options(int argc, const char **argv, const struct option *options, const char * const usagestr[], int flags) { @@ -249,6 +252,8 @@ int parse_options(int argc, const char **argv, const struct option *options, break; } + if (!strcmp(arg + 2, "help-all")) + usage_with_options_internal(usagestr, options, 1); if (!strcmp(arg + 2, "help")) usage_with_options(usagestr, options); if (parse_long_opt(&args, arg + 2, options)) @@ -263,8 +268,8 @@ int parse_options(int argc, const char **argv, const struct option *options, #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 -void usage_with_options(const char * const *usagestr, - const struct option *opts) +void usage_with_options_internal(const char * const *usagestr, + const struct option *opts, int full) { fprintf(stderr, "usage: %s\n", *usagestr++); while (*usagestr && **usagestr) @@ -285,6 +290,8 @@ void usage_with_options(const char * const *usagestr, fprintf(stderr, "%s\n", opts->help); continue; } + if (!full && (opts->flags & PARSE_OPT_HIDDEN)) + continue; pos = fprintf(stderr, " "); if (opts->short_name) @@ -335,6 +342,12 @@ void usage_with_options(const char * const *usagestr, exit(129); } +void usage_with_options(const char * const *usagestr, + const struct option *opts) +{ + usage_with_options_internal(usagestr, opts, 0); +} + /*----- some often used options -----*/ #include "cache.h" diff --git a/parse-options.h b/parse-options.h index a8760ac4b2..102ac31fb7 100644 --- a/parse-options.h +++ b/parse-options.h @@ -24,6 +24,7 @@ enum parse_opt_option_flags { PARSE_OPT_OPTARG = 1, PARSE_OPT_NOARG = 2, PARSE_OPT_NONEG = 4, + PARSE_OPT_HIDDEN = 8, }; struct option; @@ -57,6 +58,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs) * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs * PARSE_OPT_NONEG: says that this option cannot be negated + * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in + * the long one. * * `callback`:: * pointer to the callback to use for OPTION_CALLBACK. From 694a577519a762d12b8a53e76b6f1dd3ccf25e7d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 7 Nov 2007 14:58:09 -0800 Subject: [PATCH 2/3] git-branch --contains=commit This teaches git-branch to limit its listing to branches that are descendants to the named commit. When you are using many topic branches, you often would want to see which branch already includes a commit, so that you know which can and cannot be rewound without disrupting other people. One thing that sometimes happens to me is: * Somebody sends a patch that is a good maint material. I apply it to 'maint': $ git checkout maint $ git am -3 -s obvious-fix.patch * Then somebody else sends another patch that is possibly a good maint material, but I'd want to cook it in 'next' to be extra sure. I fork a topic from 'maint' and apply the patch: $ git checkout -b xx/maint-fix-foo $ git am -3 -s ,xx-maint-fix-foo.patch * A minor typo is found in the "obvious-fix.patch". The above happens without pushing the results out, so I can freely recover from it by amending 'maint', as long as I do not forget to rebase the topics that were forked previously. With this patch, I can do this to find out which topic branches already contain the faulty commit: $ git branch --contains=maint^ xx/maint-fix-foo so I can rebase the xx/maint-fix-foo branch before merging it to 'next'. Signed-off-by: Junio C Hamano --- builtin-branch.c | 57 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index 2694c9cf49..c64768beb2 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -184,9 +184,30 @@ struct ref_item { struct ref_list { int index, alloc, maxwidth; struct ref_item *list; + struct commit_list *with_commit; int kinds; }; +static int has_commit(const unsigned char *sha1, struct commit_list *with_commit) +{ + struct commit *commit; + + if (!with_commit) + return 1; + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + while (with_commit) { + struct commit *other; + + other = with_commit->item; + with_commit = with_commit->next; + if (in_merge_bases(other, &commit, 1)) + return 1; + } + return 0; +} + static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct ref_list *ref_list = (struct ref_list*)(cb_data); @@ -206,6 +227,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, refname += 10; } + /* Filter with with_commit if specified */ + if (!has_commit(sha1, ref_list->with_commit)) + return 0; + /* Don't add types the caller doesn't want */ if ((kind & ref_list->kinds) == 0) return 0; @@ -296,19 +321,20 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } } -static void print_ref_list(int kinds, int detached, int verbose, int abbrev) +static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit) { int i; struct ref_list ref_list; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; + ref_list.with_commit = with_commit; for_each_ref(append_ref, &ref_list); qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached) { + if (detached && has_commit(head_sha1, with_commit)) { struct ref_item item; item.name = xstrdup("(no branch)"); item.kind = REF_LOCAL_BRANCH; @@ -505,12 +531,29 @@ static void rename_branch(const char *oldname, const char *newname, int force) die("Branch is renamed, but update of config-file failed"); } +static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset) +{ + unsigned char sha1[20]; + struct commit *commit; + + if (!arg) + return -1; + if (get_sha1(arg, sha1)) + die("malformed object name %s", arg); + commit = lookup_commit_reference(sha1); + if (!commit) + die("no such commit %s", arg); + commit_list_insert(commit, opt->value); + return 0; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; + struct commit_list *with_commit = NULL; struct option options[] = { OPT_GROUP("Generic options"), @@ -519,6 +562,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", REF_REMOTE_BRANCH), + OPT_CALLBACK(0, "contains", &with_commit, "commit", + "print only branches that contain the commit", + opt_parse_with_commit), + { + OPTION_CALLBACK, 0, "with", &with_commit, "commit", + "print only branches that contain the commit", + PARSE_OPT_HIDDEN, opt_parse_with_commit, + }, OPT__ABBREV(&abbrev), OPT_GROUP("Specific git-branch actions:"), @@ -554,7 +605,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) return delete_branches(argc, argv, delete > 1, kinds); else if (argc == 0) - print_ref_list(kinds, detached, verbose, abbrev); + print_ref_list(kinds, detached, verbose, abbrev, with_commit); else if (rename && (argc == 1)) rename_branch(head, argv[0], rename > 1); else if (rename && (argc == 2)) From 3f7dfe77b71ff0ae78923518ab5ca28b0cf4d786 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 18 Nov 2007 22:22:00 -0800 Subject: [PATCH 3/3] git-branch --contains: doc and test Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 4 +++ t/t3201-branch-contains.sh | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100755 t/t3201-branch-contains.sh diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 5ce905de86..16303ef555 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev= | --no-abbrev]] + [--contains ] 'git-branch' [--track | --no-track] [-l] [-f] [] 'git-branch' (-m | -M) [] 'git-branch' (-d | -D) [-r] ... @@ -20,6 +21,9 @@ With no arguments given a list of existing branches will be shown, the current branch will be highlighted with an asterisk. Option `-r` causes the remote-tracking branches to be listed, and option `-a` shows both. +With `--contains `, shows only the branches that +contains the named commit (in other words, the branches whose +tip commits are descendant of the named commit). In its second form, a new branch named will be created. It will start out with a head equal to the one given as . diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh new file mode 100755 index 0000000000..9ef593f0e1 --- /dev/null +++ b/t/t3201-branch-contains.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='branch --contains ' + +. ./test-lib.sh + +test_expect_success setup ' + + >file && + git add file && + test_tick && + git commit -m initial && + git branch side && + + echo 1 >file && + test_tick && + git commit -a -m "second on master" && + + git checkout side && + echo 1 >file && + test_tick && + git commit -a -m "second on side" && + + git merge master + +' + +test_expect_success 'branch --contains=master' ' + + git branch --contains=master >actual && + { + echo " master" && echo "* side" + } >expect && + diff -u expect actual + +' + +test_expect_success 'branch --contains master' ' + + git branch --contains master >actual && + { + echo " master" && echo "* side" + } >expect && + diff -u expect actual + +' + +test_expect_success 'branch --contains=side' ' + + git branch --contains=side >actual && + { + echo "* side" + } >expect && + diff -u expect actual + +' + +test_done