Merge branch 'jh/checkout-auto-tracking'
authorJunio C Hamano <gitster@pobox.com>
Wed, 29 May 2013 21:23:10 +0000 (14:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 29 May 2013 21:23:10 +0000 (14:23 -0700)
Update "git checkout foo" that DWIMs the intended "upstream" and
turns it into "git checkout -t -b foo remotes/origin/foo" to
correctly take existing remote definitions into account.

The remote "origin" may be what uniquely map its own branch to
remotes/some/where/foo but that some/where may not be "origin".

* jh/checkout-auto-tracking:
glossary: Update and rephrase the definition of a remote-tracking branch
branch.c: Validate tracking branches with refspecs instead of refs/remotes/*
t9114.2: Don't use --track option against "svn-remote"-tracking branches
t7201.24: Add refspec to keep --track working
t3200.39: tracking setup should fail if there is no matching refspec.
checkout: Use remote refspecs when DWIMming tracking branches
t2024: Show failure to use refspec when DWIMming remote branch names
t2024: Add tests verifying current DWIM behavior of 'git checkout <branch>'

1  2 
Documentation/git-checkout.txt
Documentation/glossary-content.txt
branch.c
builtin/checkout.c
index 23a9413525d4f90435c4996af4d4866b326783bb,bf0c99c5397653374200871c2d121273a02fd09d..ca118ac6bfff9f837d06de37f99f457f6d38916b
@@@ -131,9 -131,9 +131,9 @@@ entries; instead, unmerged entries are 
        "--track" in linkgit:git-branch[1] for details.
  +
  If no '-b' option is given, the name of the new branch will be
- derived from the remote-tracking branch.  If "remotes/" or "refs/remotes/"
- is prefixed it is stripped away, and then the part up to the
next slash (which would be the nickname of the remote) is removed.
+ derived from the remote-tracking branch, by looking at the local part of
+ the refspec configured for the corresponding remote, and then stripping
the initial part up to the "*".
  This would tell us to use "hack" as the local branch when branching
  off of "origin/hack" (or "remotes/origin/hack", or even
  "refs/remotes/origin/hack").  If the given name has no slash, or the above
@@@ -180,12 -180,6 +180,12 @@@ branch by running "git rm -rf ." from t
  Afterwards you will be ready to prepare your new files, repopulating the
  working tree, by copying them from elsewhere, extracting a tarball, etc.
  
 +--ignore-skip-worktree-bits::
 +      In sparse checkout mode, `git checkout -- <paths>` would
 +      update only entries matched by <paths> and sparse patterns
 +      in $GIT_DIR/info/sparse-checkout. This option ignores
 +      the sparse patterns and adds back any files in <paths>.
 +
  -m::
  --merge::
        When switching branches,
index 68a18e14975fadb0cba7ba19e45438887a65bd4f,7a79f26231c2ffa0cd0bfb7ffb9274803e32d8be..db2a74df934f3acd93521e42510b6cd00c9eed6f
@@@ -117,6 -117,9 +117,6 @@@ branch --set-upstream-to` that sets wha
  current branch integrates with) obviously do not work, as there is no
  (real) current branch to ask about in this state.
  
 -[[def_dircache]]dircache::
 -      You are *waaaaay* behind. See <<def_index,index>>.
 -
  [[def_directory]]directory::
        The list you get with "ls" :-)
  
        it contains modifications which have not been <<def_commit,committed>> to the current
        <<def_branch,branch>>.
  
 -[[def_ent]]ent::
 -      Favorite synonym to "<<def_tree-ish,tree-ish>>" by some total geeks. See
 -      http://en.wikipedia.org/wiki/Ent_(Middle-earth) for an in-depth
 -      explanation. Avoid this term, not to confuse people.
 -
  [[def_evil_merge]]evil merge::
        An evil merge is a <<def_merge,merge>> that introduces changes that
        do not appear in any <<def_parent,parent>>.
        created. Configured via the `.git/info/grafts` file.
  
  [[def_hash]]hash::
 -      In Git's context, synonym to <<def_object_name,object name>>.
 +      In Git's context, synonym for <<def_object_name,object name>>.
  
  [[def_head]]head::
        A <<def_ref,named reference>> to the <<def_commit,commit>> at the tip of a
@@@ -238,7 -246,7 +238,7 @@@ This commit is referred to as a "merge 
  
  [[def_object]]object::
        The unit of storage in Git. It is uniquely identified by the
 -      <<def_SHA1,SHA1>> of its contents. Consequently, an
 +      <<def_SHA1,SHA-1>> of its contents. Consequently, an
        object can not be changed.
  
  [[def_object_database]]object database::
        Synonym for <<def_object_name,object name>>.
  
  [[def_object_name]]object name::
 -      The unique identifier of an <<def_object,object>>. The <<def_hash,hash>>
 -      of the object's contents using the Secure Hash Algorithm
 -      1 and usually represented by the 40 character hexadecimal encoding of
 -      the <<def_hash,hash>> of the object.
 +      The unique identifier of an <<def_object,object>>.  The
 +      object name is usually represented by a 40 character
 +      hexadecimal string.  Also colloquially called <<def_SHA1,SHA-1>>.
  
  [[def_object_type]]object type::
        One of the identifiers "<<def_commit_object,commit>>",
        <<def_object,object>>.
  
  [[def_octopus]]octopus::
 -      To <<def_merge,merge>> more than two <<def_branch,branches>>. Also denotes an
 -      intelligent predator.
 +      To <<def_merge,merge>> more than two <<def_branch,branches>>.
  
  [[def_origin]]origin::
        The default upstream <<def_repository,repository>>. Most projects have
        pack.
  
  [[def_pathspec]]pathspec::
 -       Pattern used to specify paths.
 +      Pattern used to limit paths in Git commands.
  +
  Pathspecs are used on the command line of "git ls-files", "git
  ls-tree", "git add", "git grep", "git diff", "git checkout",
@@@ -290,8 -300,6 +290,8 @@@ limit the scope of operations to some s
  worktree.  See the documentation of each command for whether
  paths are relative to the current directory or toplevel.  The
  pathspec syntax is as follows:
 ++
 +--
  
  * any path matches itself
  * the pathspec up to the last slash represents a
    of the pathname.  Paths relative to the directory
    prefix will be matched against that pattern using fnmatch(3);
    in particular, '*' and '?' _can_ match directory separators.
 +
 +--
  +
  For example, Documentation/*.jpg will match all .jpg files
  in the Documentation subtree,
  including Documentation/chapter_1/figure_1.jpg.
 -
  +
  A pathspec that begins with a colon `:` has special meaning.  In the
  short form, the leading colon `:` is followed by zero or more "magic
@@@ -322,10 -329,18 +322,10 @@@ and a close parentheses `)`, and the re
  against the path.
  +
  The "magic signature" consists of an ASCII symbol that is not
 -alphanumeric.
 -+
 ---
 -top `/`;;
 -      The magic word `top` (mnemonic: `/`) makes the pattern match
 -      from the root of the working tree, even when you are running
 -      the command from inside a subdirectory.
 ---
 -+
 -Currently only the slash `/` is recognized as the "magic signature",
 -but it is envisioned that we will support more types of magic in later
 -versions of Git.
 +alphanumeric. Currently only the slash `/` is recognized as a
 +"magic signature": it makes the pattern match from the root of
 +the working tree, even when you are running the command from
 +inside a subdirectory.
  +
  A pathspec with only a colon means "there is no pathspec". This form
  should not be combined with other pathspec.
        to the result.
  
  [[def_ref]]ref::
 -      A 40-byte hex representation of a <<def_SHA1,SHA1>> or a name that
 +      A 40-byte hex representation of a <<def_SHA1,SHA-1>> or a name that
        denotes a particular <<def_object,object>>. They may be stored in
        a file under `$GIT_DIR/refs/` directory, or
        in the `$GIT_DIR/packed-refs` file.
  [[def_refspec]]refspec::
        A "refspec" is used by <<def_fetch,fetch>> and
        <<def_push,push>> to describe the mapping between remote
 -      <<def_ref,ref>> and local ref. They are combined with a colon in
 -      the format <src>:<dst>, preceded by an optional plus sign, +.
 -      For example: `git fetch $URL
 -      refs/heads/master:refs/heads/origin` means "grab the master
 -      <<def_branch,branch>> <<def_head,head>> from the $URL and store
 -      it as my origin branch head". And `git push
 -      $URL refs/heads/master:refs/heads/to-upstream` means "publish my
 -      master branch head as to-upstream branch at $URL". See also
 -      linkgit:git-push[1].
 +      <<def_ref,ref>> and local ref.
  
  [[def_remote_tracking_branch]]remote-tracking branch::
-       A regular Git <<def_branch,branch>> that is used to follow changes from
-       another <<def_repository,repository>>. A remote-tracking
-       branch should not contain direct modifications or have local commits
-       made to it. A remote-tracking branch can usually be
-       identified as the right-hand-side <<def_ref,ref>> in a Pull:
-       <<def_refspec,refspec>>.
+       A <<def_ref,ref>> that is used to follow changes from another
+       <<def_repository,repository>>. It typically looks like
+       'refs/remotes/foo/bar' (indicating that it tracks a branch named
+       'bar' in a remote named 'foo'), and matches the right-hand-side of
+       a configured fetch <<def_refspec,refspec>>. A remote-tracking
+       branch should not contain direct modifications or have local
+       commits made to it.
  
  [[def_repository]]repository::
        A collection of <<def_ref,refs>> together with an
        <<def_merge,merge>> left behind.
  
  [[def_revision]]revision::
 -      A particular state of files and directories which was stored in the
 -      <<def_object_database,object database>>. It is referenced by a
 -      <<def_commit_object,commit object>>.
 +      Synonym for <<def_commit,commit>> (the noun).
  
  [[def_rewind]]rewind::
        To throw away part of the development, i.e. to assign the
  [[def_SCM]]SCM::
        Source code management (tool).
  
 -[[def_SHA1]]SHA1::
 -      Synonym for <<def_object_name,object name>>.
 +[[def_SHA1]]SHA-1::
 +      "Secure Hash Algorithm 1"; a cryptographic hash function.
 +      In the context of Git used as a synonym for <<def_object_name,object name>>.
  
  [[def_shallow_repository]]shallow repository::
        A shallow <<def_repository,repository>> has an incomplete
        its history can be later deepened with linkgit:git-fetch[1].
  
  [[def_symref]]symref::
 -      Symbolic reference: instead of containing the <<def_SHA1,SHA1>>
 +      Symbolic reference: instead of containing the <<def_SHA1,SHA-1>>
        id itself, it is of the format 'ref: refs/some/thing' and when
        referenced, it recursively dereferences to this reference.
        '<<def_HEAD,HEAD>>' is a prime example of a symref. Symbolic
diff --combined branch.c
index 97c72bfe7043701132a2710bf03c5ce3ee109ba5,beaf11d97cd4d8cba3c11c335770050a306038c5..c5c6984cb5266c27d3c13aa6e6aacaab56e112c0
+++ b/branch.c
@@@ -57,7 -57,7 +57,7 @@@ void install_branch_config(int flag, co
        if (remote_is_branch
            && !strcmp(local, shortname)
            && !origin) {
 -              warning("Not setting branch %s as its own upstream.",
 +              warning(_("Not setting branch %s as its own upstream."),
                        local);
                return;
        }
  
        if (flag & BRANCH_CONFIG_VERBOSE) {
                if (remote_is_branch && origin)
 -                      printf(rebasing ?
 -                             "Branch %s set up to track remote branch %s from %s by rebasing.\n" :
 -                             "Branch %s set up to track remote branch %s from %s.\n",
 -                             local, shortname, origin);
 +                      printf_ln(rebasing ?
 +                                _("Branch %s set up to track remote branch %s from %s by rebasing.") :
 +                                _("Branch %s set up to track remote branch %s from %s."),
 +                                local, shortname, origin);
                else if (remote_is_branch && !origin)
 -                      printf(rebasing ?
 -                             "Branch %s set up to track local branch %s by rebasing.\n" :
 -                             "Branch %s set up to track local branch %s.\n",
 -                             local, shortname);
 +                      printf_ln(rebasing ?
 +                                _("Branch %s set up to track local branch %s by rebasing.") :
 +                                _("Branch %s set up to track local branch %s."),
 +                                local, shortname);
                else if (!remote_is_branch && origin)
 -                      printf(rebasing ?
 -                             "Branch %s set up to track remote ref %s by rebasing.\n" :
 -                             "Branch %s set up to track remote ref %s.\n",
 -                             local, remote);
 +                      printf_ln(rebasing ?
 +                                _("Branch %s set up to track remote ref %s by rebasing.") :
 +                                _("Branch %s set up to track remote ref %s."),
 +                                local, remote);
                else if (!remote_is_branch && !origin)
 -                      printf(rebasing ?
 -                             "Branch %s set up to track local ref %s by rebasing.\n" :
 -                             "Branch %s set up to track local ref %s.\n",
 -                             local, remote);
 +                      printf_ln(rebasing ?
 +                                _("Branch %s set up to track local ref %s by rebasing.") :
 +                                _("Branch %s set up to track local ref %s."),
 +                                local, remote);
                else
                        die("BUG: impossible combination of %d and %p",
                            remote_is_branch, origin);
@@@ -115,7 -115,7 +115,7 @@@ static int setup_tracking(const char *n
        int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
  
        if (strlen(new_ref) > 1024 - 7 - 7 - 1)
 -              return error("Tracking not set up: name too long: %s",
 +              return error(_("Tracking not set up: name too long: %s"),
                                new_ref);
  
        memset(&tracking, 0, sizeof(tracking));
                }
  
        if (tracking.matches > 1)
 -              return error("Not tracking: ambiguous information for ref %s",
 +              return error(_("Not tracking: ambiguous information for ref %s"),
                                orig_ref);
  
        install_branch_config(config_flags, new_ref, tracking.remote,
@@@ -179,12 -179,12 +179,12 @@@ int validate_new_branchname(const char 
                            int force, int attr_only)
  {
        if (strbuf_check_branch_ref(ref, name))
 -              die("'%s' is not a valid branch name.", name);
 +              die(_("'%s' is not a valid branch name."), name);
  
        if (!ref_exists(ref->buf))
                return 0;
        else if (!force && !attr_only)
 -              die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/"));
 +              die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/"));
  
        if (!attr_only) {
                const char *head;
  
                head = resolve_ref_unsafe("HEAD", sha1, 0, NULL);
                if (!is_bare_repository() && head && !strcmp(head, ref->buf))
 -                      die("Cannot force update the current branch.");
 +                      die(_("Cannot force update the current branch."));
        }
        return 1;
  }
  
+ static int check_tracking_branch(struct remote *remote, void *cb_data)
+ {
+       char *tracking_branch = cb_data;
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.dst = tracking_branch;
+       return !(remote_find_tracking(remote, &query) ||
+                prefixcmp(query.src, "refs/heads/"));
+ }
+ static int validate_remote_tracking_branch(char *ref)
+ {
+       return !for_each_remote(check_tracking_branch, ref);
+ }
  static const char upstream_not_branch[] =
  N_("Cannot setup tracking information; starting point '%s' is not a branch.");
  static const char upstream_missing[] =
@@@ -247,7 -262,7 +262,7 @@@ void create_branch(const char *head
                        }
                        die(_(upstream_missing), start_name);
                }
 -              die("Not a valid object name: '%s'.", start_name);
 +              die(_("Not a valid object name: '%s'."), start_name);
        }
  
        switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
        case 1:
                /* Unique completion -- good, only if it is a real branch */
                if (prefixcmp(real_ref, "refs/heads/") &&
-                   prefixcmp(real_ref, "refs/remotes/")) {
+                   validate_remote_tracking_branch(real_ref)) {
                        if (explicit_tracking)
                                die(_(upstream_not_branch), start_name);
                        else
                }
                break;
        default:
 -              die("Ambiguous object name: '%s'.", start_name);
 +              die(_("Ambiguous object name: '%s'."), start_name);
                break;
        }
  
        if ((commit = lookup_commit_reference(sha1)) == NULL)
 -              die("Not a valid branch point: '%s'.", start_name);
 +              die(_("Not a valid branch point: '%s'."), start_name);
        hashcpy(sha1, commit->object.sha1);
  
        if (!dont_change_ref) {
                lock = lock_any_ref_for_update(ref.buf, NULL, 0);
                if (!lock)
 -                      die_errno("Failed to lock ref for update");
 +                      die_errno(_("Failed to lock ref for update"));
        }
  
        if (reflog)
  
        if (!dont_change_ref)
                if (write_ref_sha1(lock, sha1, msg) < 0)
 -                      die_errno("Failed to write ref");
 +                      die_errno(_("Failed to write ref"));
  
        strbuf_release(&ref);
        free(real_ref);
diff --combined builtin/checkout.c
index 81b4419da51f3211129833a472048d897385bca4,bcb18c8d206774efde9453a64775ebd9379ea342..f5b50e520feb42c50cd1783871eb63d009e0a0e0
@@@ -35,7 -35,6 +35,7 @@@ struct checkout_opts 
        int force_detach;
        int writeout_stage;
        int overwrite_ignore;
 +      int ignore_skipworktree;
  
        const char *new_branch;
        const char *new_branch_force;
@@@ -279,8 -278,6 +279,8 @@@ static int checkout_paths(const struct 
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                ce->ce_flags &= ~CE_MATCHED;
 +              if (!opts->ignore_skipworktree && ce_skip_worktree(ce))
 +                      continue;
                if (opts->source_tree && !(ce->ce_flags & CE_UPDATE))
                        /*
                         * "git checkout tree-ish -- path", but this entry
@@@ -825,38 -822,40 +825,40 @@@ static int git_checkout_config(const ch
  }
  
  struct tracking_name_data {
-       const char *name;
-       char *remote;
+       /* const */ char *src_ref;
+       char *dst_ref;
+       unsigned char *dst_sha1;
        int unique;
  };
  
- static int check_tracking_name(const char *refname, const unsigned char *sha1,
-                              int flags, void *cb_data)
+ static int check_tracking_name(struct remote *remote, void *cb_data)
  {
        struct tracking_name_data *cb = cb_data;
-       const char *slash;
-       if (prefixcmp(refname, "refs/remotes/"))
-               return 0;
-       slash = strchr(refname + 13, '/');
-       if (!slash || strcmp(slash + 1, cb->name))
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.src = cb->src_ref;
+       if (remote_find_tracking(remote, &query) ||
+           get_sha1(query.dst, cb->dst_sha1))
                return 0;
-       if (cb->remote) {
+       if (cb->dst_ref) {
                cb->unique = 0;
                return 0;
        }
-       cb->remote = xstrdup(refname);
+       cb->dst_ref = xstrdup(query.dst);
        return 0;
  }
  
- static const char *unique_tracking_name(const char *name)
+ static const char *unique_tracking_name(const char *name, unsigned char *sha1)
  {
-       struct tracking_name_data cb_data = { NULL, NULL, 1 };
-       cb_data.name = name;
-       for_each_ref(check_tracking_name, &cb_data);
+       struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 };
+       char src_ref[PATH_MAX];
+       snprintf(src_ref, PATH_MAX, "refs/heads/%s", name);
+       cb_data.src_ref = src_ref;
+       cb_data.dst_sha1 = sha1;
+       for_each_remote(check_tracking_name, &cb_data);
        if (cb_data.unique)
-               return cb_data.remote;
-       free(cb_data.remote);
+               return cb_data.dst_ref;
+       free(cb_data.dst_ref);
        return NULL;
  }
  
@@@ -919,8 -918,8 +921,8 @@@ static int parse_branchname_arg(int arg
                if (dwim_new_local_branch_ok &&
                    !check_filename(NULL, arg) &&
                    argc == 1) {
-                       const char *remote = unique_tracking_name(arg);
-                       if (!remote || get_sha1(remote, rev))
+                       const char *remote = unique_tracking_name(arg, rev);
+                       if (!remote)
                                return argcount;
                        *new_branch = arg;
                        arg = remote;
@@@ -1061,8 -1060,6 +1063,8 @@@ int cmd_checkout(int argc, const char *
                OPT_STRING(0, "conflict", &conflict_style, N_("style"),
                           N_("conflict style (merge or diff3)")),
                OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
 +              OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
 +                       N_("do not limit pathspecs to sparse entries only")),
                { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
                  N_("second guess 'git checkout no-such-branch'"),
                  PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },