Merge branch 'mg/branch-list'
authorJunio C Hamano <gitster@pobox.com>
Wed, 5 Oct 2011 19:36:23 +0000 (12:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Oct 2011 19:36:23 +0000 (12:36 -0700)
* mg/branch-list:
t3200: clean up checks for file existence
branch: -v does not automatically imply --list
branch: allow pattern arguments
branch: introduce --list option
git-branch: introduce missing long forms for the options
git-tag: introduce long forms for the options
t6040: test branch -vv

Conflicts:
Documentation/git-tag.txt
t/t3200-branch.sh

1  2 
Documentation/git-tag.txt
builtin/branch.c
builtin/tag.c
t/t3200-branch.sh
t/t6040-tracking-info.sh
index fb1c0ac694bdac20e2fef74710df35c1c4b1e774,61e4b9bc08f50bb532eabcf706e9f111faed2fdc..c83cb13de67943813edc99725b87cfe94beba87e
@@@ -12,7 -12,7 +12,7 @@@ SYNOPSI
  'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]
        <tagname> [<commit> | <object>]
  'git tag' -d <tagname>...
 -'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>]
 +'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...]
  'git tag' -v <tagname>...
  
  DESCRIPTION
@@@ -43,12 -43,15 +43,15 @@@ GnuPG key for signing
  OPTIONS
  -------
  -a::
+ --annotate::
        Make an unsigned, annotated tag object
  
  -s::
+ --sign::
        Make a GPG-signed tag, using the default e-mail address's key
  
  -u <key-id>::
+ --local-user=<key-id>::
        Make a GPG-signed tag, using the given key
  
  -f::
        Replace an existing tag with the given name (instead of failing)
  
  -d::
+ --delete::
        Delete existing tags with the given names.
  
  -v::
+ --verify::
        Verify the gpg signature of the given tag names.
  
  -n<num>::
@@@ -69,6 -74,7 +74,7 @@@
        If the tag is not annotated, the commit message is displayed instead.
  
  -l <pattern>::
+ --list <pattern>::
        List tags with names that match the given pattern (or all if no
        pattern is given).  Running "git tag" without arguments also
        lists all tags. The pattern is a shell wildcard (i.e., matched
@@@ -79,6 -85,7 +85,7 @@@
        Only list tags which contain the specified commit.
  
  -m <msg>::
+ --message=<msg>::
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
        concatenated as separate paragraphs.
@@@ -86,6 -93,7 +93,7 @@@
        is given.
  
  -F <file>::
+ --file=<file>::
        Take the tag message from the given file.  Use '-' to
        read the message from the standard input.
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
diff --combined builtin/branch.c
index f49596f826228e22d354194ddbb73ec100d8728e,099c75c2287913834dea2f6d54e3cd8e97032f7b..009b7138ac72c5845225ce1f801be908ede4e3b4
@@@ -71,7 -71,7 +71,7 @@@ static int parse_branch_color_slot(cons
  static int git_branch_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "color.branch")) {
 -              branch_use_color = git_config_colorbool(var, value, -1);
 +              branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
        if (!prefixcmp(var, "color.branch.")) {
@@@ -88,7 -88,7 +88,7 @@@
  
  static const char *branch_get_color(enum color_branch ix)
  {
 -      if (branch_use_color > 0)
 +      if (want_color(branch_use_color))
                return branch_colors[ix];
        return "";
  }
@@@ -260,9 -260,22 +260,22 @@@ static char *resolve_symref(const char 
  
  struct append_ref_cb {
        struct ref_list *ref_list;
+       const char **pattern;
        int ret;
  };
  
+ static int match_patterns(const char **pattern, const char *refname)
+ {
+       if (!*pattern)
+               return 1; /* no pattern always matches */
+       while (*pattern) {
+               if (!fnmatch(*pattern, refname, 0))
+                       return 1;
+               pattern++;
+       }
+       return 0;
+ }
  static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
        if ((kind & ref_list->kinds) == 0)
                return 0;
  
+       if (!match_patterns(cb->pattern, refname))
+               return 0;
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
                commit = lookup_commit_reference_gently(sha1, 1);
@@@ -492,7 -508,7 +508,7 @@@ static void show_detached(struct ref_li
        }
  }
  
- static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
  {
        int i;
        struct append_ref_cb cb;
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
        cb.ref_list = &ref_list;
+       cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
        if (merge_filter != NO_FILTER) {
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
  
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached)
+       if (detached && match_patterns(pattern, "HEAD"))
                show_detached(&ref_list);
  
        for (i = 0; i < ref_list.index; i++) {
@@@ -566,7 -583,11 +583,7 @@@ static void rename_branch(const char *o
                        die(_("Invalid branch name: '%s'"), oldname);
        }
  
 -      if (strbuf_check_branch_ref(&newref, newname))
 -              die(_("Invalid branch name: '%s'"), newname);
 -
 -      if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
 -              die(_("A branch named '%s' already exists."), newref.buf + 11);
 +      validate_new_branchname(newname, &newref, force, 0);
  
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@@ -608,7 -629,7 +625,7 @@@ static int opt_parse_merge_filter(cons
  
  int cmd_branch(int argc, const char **argv, const char *prefix)
  {
-       int delete = 0, rename = 0, force_create = 0;
+       int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0;
        enum branch_track track;
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
                        BRANCH_TRACK_OVERRIDE),
                OPT__COLOR(&branch_use_color, "use colored output"),
-               OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
+               OPT_SET_INT('r', "remotes",     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
                {
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
                OPT__ABBREV(&abbrev),
  
                OPT_GROUP("Specific git-branch actions:"),
-               OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+               OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches",
                        REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
-               OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+               OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1),
                OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
-               OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+               OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1),
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
-               OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN(0, "list", &list, "list branch names"),
+               OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
                OPT__FORCE(&force_create, "force creation (when already exists)"),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
  
        git_config(git_branch_config, NULL);
  
 -      if (branch_use_color == -1)
 -              branch_use_color = git_use_color_default;
 -
        track = git_branch_track;
  
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
  
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
-       if (!!delete + !!rename + !!force_create > 1)
+       if (!delete && !rename && !force_create && argc == 0)
+               list = 1;
+       if (!!delete + !!rename + !!force_create + !!list > 1)
                usage_with_options(builtin_branch_usage, options);
  
        if (abbrev == -1)
  
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
-       else if (argc == 0)
-               return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
+       else if (list)
+               return print_ref_list(kinds, detached, verbose, abbrev,
+                                     with_commit, argv);
        else if (rename && (argc == 1))
                rename_branch(head, argv[0], rename > 1);
        else if (rename && (argc == 2))
diff --combined builtin/tag.c
index 667515e5278d22548fb83507347ab652533a67b2,e311911f5aa4631e47aab804e6ce6a4fbbd00d6c..9d89616863f4b9102706c726c51e36acddef8997
  #include "tag.h"
  #include "run-command.h"
  #include "parse-options.h"
 +#include "diff.h"
 +#include "revision.h"
  
  static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
        "git tag -d <tagname>...",
 -      "git tag -l [-n[<num>]] [<pattern>]",
 +      "git tag -l [-n[<num>]] [<pattern>...]",
        "git tag -v <tagname>...",
        NULL
  };
  static char signingkey[1000];
  
  struct tag_filter {
 -      const char *pattern;
 +      const char **patterns;
        int lines;
        struct commit_list *with_commit;
  };
  
 +static int match_pattern(const char **patterns, const char *ref)
 +{
 +      /* no pattern means match everything */
 +      if (!*patterns)
 +              return 1;
 +      for (; *patterns; patterns++)
 +              if (!fnmatch(*patterns, ref, 0))
 +                      return 1;
 +      return 0;
 +}
 +
 +static int in_commit_list(const struct commit_list *want, struct commit *c)
 +{
 +      for (; want; want = want->next)
 +              if (!hashcmp(want->item->object.sha1, c->object.sha1))
 +                      return 1;
 +      return 0;
 +}
 +
 +static int contains_recurse(struct commit *candidate,
 +                          const struct commit_list *want)
 +{
 +      struct commit_list *p;
 +
 +      /* was it previously marked as containing a want commit? */
 +      if (candidate->object.flags & TMP_MARK)
 +              return 1;
 +      /* or marked as not possibly containing a want commit? */
 +      if (candidate->object.flags & UNINTERESTING)
 +              return 0;
 +      /* or are we it? */
 +      if (in_commit_list(want, candidate))
 +              return 1;
 +
 +      if (parse_commit(candidate) < 0)
 +              return 0;
 +
 +      /* Otherwise recurse and mark ourselves for future traversals. */
 +      for (p = candidate->parents; p; p = p->next) {
 +              if (contains_recurse(p->item, want)) {
 +                      candidate->object.flags |= TMP_MARK;
 +                      return 1;
 +              }
 +      }
 +      candidate->object.flags |= UNINTERESTING;
 +      return 0;
 +}
 +
 +static int contains(struct commit *candidate, const struct commit_list *want)
 +{
 +      return contains_recurse(candidate, want);
 +}
 +
  static int show_reference(const char *refname, const unsigned char *sha1,
                          int flag, void *cb_data)
  {
        struct tag_filter *filter = cb_data;
  
 -      if (!fnmatch(filter->pattern, refname, 0)) {
 +      if (match_pattern(filter->patterns, refname)) {
                int i;
                unsigned long size;
                enum object_type type;
                        commit = lookup_commit_reference_gently(sha1, 1);
                        if (!commit)
                                return 0;
 -                      if (!is_descendant_of(commit, filter->with_commit))
 +                      if (!contains(commit, filter->with_commit))
                                return 0;
                }
  
        return 0;
  }
  
 -static int list_tags(const char *pattern, int lines,
 +static int list_tags(const char **patterns, int lines,
                        struct commit_list *with_commit)
  {
        struct tag_filter filter;
  
 -      if (pattern == NULL)
 -              pattern = "*";
 -
 -      filter.pattern = pattern;
 +      filter.patterns = patterns;
        filter.lines = lines;
        filter.with_commit = with_commit;
  
@@@ -429,21 -377,21 +429,21 @@@ int cmd_tag(int argc, const char **argv
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
-               OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+               OPT_BOOLEAN('l', "list", &list, "list tag names"),
                { OPTION_INTEGER, 'n', NULL, &lines, "n",
                                "print <n> lines of each tag message",
                                PARSE_OPT_OPTARG, NULL, 1 },
-               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
-               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+               OPT_BOOLEAN('d', "delete", &delete, "delete tags"),
+               OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
  
                OPT_GROUP("Tag creation options"),
-               OPT_BOOLEAN('a', NULL, &annotate,
+               OPT_BOOLEAN('a', "annotate", &annotate,
                                        "annotated tag, needs a message"),
-               OPT_CALLBACK('m', NULL, &msg, "message",
+               OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
-               OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
-               OPT_STRING('u', NULL, &keyid, "key-id",
+               OPT_FILENAME('F', "file", &msgfile, "read message from file"),
+               OPT_BOOLEAN('s', "sign", &sign, "annotated and GPG-signed tag"),
+               OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
  
        if (list + delete + verify > 1)
                usage_with_options(git_tag_usage, options);
        if (list)
 -              return list_tags(argv[0], lines == -1 ? 0 : lines,
 +              return list_tags(argv, lines == -1 ? 0 : lines,
                                 with_commit);
        if (lines != -1)
                die(_("-n option is only allowed with -l."));
diff --combined t/t3200-branch.sh
index 7633930bb472e1c9aebf2799f3eaf3bc53c7263c,d5301038781eb8cb334c9a516651fc31ad7532a5..2f5eada0d2801c7bc99dbbbed4d7b0ff839f25b7
@@@ -23,7 -23,7 +23,7 @@@ test_expect_success 
  test_expect_success \
      'git branch --help should not have created a bogus branch' '
       git branch --help </dev/null >/dev/null 2>/dev/null;
-      ! test -f .git/refs/heads/--help
+      test_path_is_missing .git/refs/heads/--help
  '
  
  test_expect_success 'branch -h in broken repository' '
  
  test_expect_success \
      'git branch abc should create a branch' \
-     'git branch abc && test -f .git/refs/heads/abc'
+     'git branch abc && test_path_is_file .git/refs/heads/abc'
  
  test_expect_success \
      'git branch a/b/c should create a branch' \
-     'git branch a/b/c && test -f .git/refs/heads/a/b/c'
+     'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
  
  cat >expect <<EOF
  $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000       branch: Created from master
@@@ -52,15 -52,15 +52,15 @@@ test_expect_success 
      'git branch -l d/e/f should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
       git branch -l d/e/f &&
-        test -f .git/refs/heads/d/e/f &&
-        test -f .git/logs/refs/heads/d/e/f &&
+        test_path_is_file .git/refs/heads/d/e/f &&
+        test_path_is_file .git/logs/refs/heads/d/e/f &&
         test_cmp expect .git/logs/refs/heads/d/e/f'
  
  test_expect_success \
      'git branch -d d/e/f should delete a branch and a log' \
        'git branch -d d/e/f &&
-        test ! -f .git/refs/heads/d/e/f &&
-        test ! -f .git/logs/refs/heads/d/e/f'
+        test_path_is_missing .git/refs/heads/d/e/f &&
+        test_path_is_missing .git/logs/refs/heads/d/e/f'
  
  test_expect_success \
      'git branch j/k should work after branch j has been deleted' \
@@@ -78,13 -78,13 +78,13 @@@ test_expect_success 
      'git branch -m m m/m should work' \
         'git branch -l m &&
          git branch -m m m/m &&
-         test -f .git/logs/refs/heads/m/m'
+       test_path_is_file .git/logs/refs/heads/m/m'
  
  test_expect_success \
      'git branch -m n/n n should work' \
         'git branch -l n/n &&
          git branch -m n/n n
-         test -f .git/logs/refs/heads/n'
+       test_path_is_file .git/logs/refs/heads/n'
  
  test_expect_success 'git branch -m o/o o should fail when o/p exists' '
        git branch o/o &&
@@@ -98,18 -98,38 +98,50 @@@ test_expect_success 'git branch -m q r/
        test_must_fail git branch -m q r/q
  '
  
 +test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
 +      git branch bar &&
 +      git checkout -b foo &&
 +      test_must_fail git branch -M bar foo
 +'
 +
 +test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
 +      git checkout -b baz &&
 +      git branch bam &&
 +      git branch -M baz bam
 +'
 +
+ test_expect_success 'git branch -v -d t should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -d t &&
+       test_path_is_missing .git/refs/heads/t
+ '
+ test_expect_success 'git branch -v -m t s should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -m t s &&
+       test_path_is_missing .git/refs/heads/t &&
+       test_path_is_file .git/refs/heads/s &&
+       git branch -d s
+ '
+ test_expect_success 'git branch -m -d t s should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch -m -d t s &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+ '
+ test_expect_success 'git branch --list -d t should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch --list -d t &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+ '
  mv .git/config .git/config-saved
  
  test_expect_success 'git branch -m q q2 without config should succeed' '
@@@ -124,12 -144,12 +156,12 @@@ git config branch.s/s.dummy Hell
  test_expect_success \
      'git branch -m s/s s should work when s/t is deleted' \
         'git branch -l s/s &&
-         test -f .git/logs/refs/heads/s/s &&
+       test_path_is_file .git/logs/refs/heads/s/s &&
          git branch -l s/t &&
-         test -f .git/logs/refs/heads/s/t &&
+       test_path_is_file .git/logs/refs/heads/s/t &&
          git branch -d s/t &&
          git branch -m s/s s &&
-         test -f .git/logs/refs/heads/s'
+       test_path_is_file .git/logs/refs/heads/s'
  
  test_expect_success 'config information was renamed, too' \
        "test $(git config branch.s.dummy) = Hello &&
@@@ -140,8 -160,8 +172,8 @@@ test_expect_success 'renaming a symref 
        git symbolic-ref refs/heads/master2 refs/heads/master &&
        test_must_fail git branch -m master2 master3 &&
        git symbolic-ref refs/heads/master2 &&
-       test -f .git/refs/heads/master &&
-       ! test -f .git/refs/heads/master3
+       test_path_is_file .git/refs/heads/master &&
+       test_path_is_missing .git/refs/heads/master3
  '
  
  test_expect_success SYMLINKS \
@@@ -250,8 -270,8 +282,8 @@@ test_expect_success 
      'git checkout -b g/h/i -l should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
       git checkout -b g/h/i -l master &&
-        test -f .git/refs/heads/g/h/i &&
-        test -f .git/logs/refs/heads/g/h/i &&
+        test_path_is_file .git/refs/heads/g/h/i &&
+        test_path_is_file .git/logs/refs/heads/g/h/i &&
         test_cmp expect .git/logs/refs/heads/g/h/i'
  
  test_expect_success 'checkout -b makes reflog by default' '
@@@ -554,17 -574,4 +586,17 @@@ test_expect_success 'attempt to delete 
        test_must_fail git branch -d my10
  '
  
 +test_expect_success 'use set-upstream on the current branch' '
 +      git checkout master &&
 +      git --bare init myupstream.git &&
 +      git push myupstream.git master:refs/heads/frotz &&
 +      git remote add origin myupstream.git &&
 +      git fetch &&
 +      git branch --set-upstream master origin/frotz &&
 +
 +      test "z$(git config branch.master.remote)" = "zorigin" &&
 +      test "z$(git config branch.master.merge)" = "zrefs/heads/frotz"
 +
 +'
 +
  test_done
diff --combined t/t6040-tracking-info.sh
index 19de5b16eb530d428f66e5161a3ab48fce34049d,4e57dc8cdc423e6c478371d94a304f5bd82e1f41..19272bc551277903bc1c444f4f0f05d8f2d7d672
@@@ -51,6 -51,22 +51,22 @@@ test_expect_success 'branch -v' 
        test_i18ncmp expect actual
  '
  
+ cat >expect <<\EOF
+ b1 origin/master: ahead 1, behind 1
+ b2 origin/master: ahead 1, behind 1
+ b3 origin/master: behind 1
+ b4 origin/master: ahead 2
+ EOF
+ test_expect_success 'branch -vv' '
+       (
+               cd test &&
+               git branch -vv
+       ) |
+       sed -n -e "$script" >actual &&
+       test_i18ncmp expect actual
+ '
  test_expect_success 'checkout' '
        (
                cd test && git checkout b1
@@@ -110,18 -126,4 +126,18 @@@ test_expect_success '--set-upstream doe
        grep -q "^refs/heads/master$" actual &&
        cmp expect2 actual2
  '
 +
 +test_expect_success '--set-upstream @{-1}' '
 +      git checkout from-master &&
 +      git checkout from-master2 &&
 +      git config branch.from-master2.merge > expect2 &&
 +      git branch --set-upstream @{-1} follower &&
 +      git config branch.from-master.merge > actual &&
 +      git config branch.from-master2.merge > actual2 &&
 +      git branch --set-upstream from-master follower &&
 +      git config branch.from-master.merge > expect &&
 +      test_cmp expect2 actual2 &&
 +      test_cmp expect actual
 +'
 +
  test_done