Merge branch 'jc/name-branch'
authorJunio C Hamano <gitster@pobox.com>
Mon, 6 Apr 2009 07:43:44 +0000 (00:43 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 6 Apr 2009 07:43:44 +0000 (00:43 -0700)
* jc/name-branch:
Don't permit ref/branch names to end with ".lock"
check_ref_format(): tighten refname rules
strbuf_check_branch_ref(): a helper to check a refname for a branch
Fix branch -m @{-1} newname
check-ref-format --branch: give Porcelain a way to grok branch shorthand
strbuf_branchname(): a wrapper for branch name shorthands
Rename interpret/substitute nth_last_branch functions

Conflicts:
Documentation/git-check-ref-format.txt

1  2 
Documentation/git-check-ref-format.txt
builtin-branch.c
builtin-checkout.c
cache.h
refs.c
index 171b68377d6604167fde4f5e4266432fa9048345,9b707a7030b0128a32e3290f2325754d608624e3..c1ce26884e73f1599602472ba5032988486cc465
@@@ -3,52 -3,70 +3,70 @@@ git-check-ref-format(1
  
  NAME
  ----
 -git-check-ref-format - Make sure ref name is well formed
 +git-check-ref-format - Ensures that a reference name is well formed
  
  SYNOPSIS
  --------
+ [verse]
  'git check-ref-format' <refname>
+ 'git check-ref-format' [--branch] <branchname-shorthand>
  
  DESCRIPTION
  -----------
 -Checks if a given 'refname' is acceptable, and exits non-zero if
 -it is not.
 +Checks if a given 'refname' is acceptable, and exits with a non-zero
 +status if it is not.
  
  A reference is used in git to specify branches and tags.  A
 -branch head is stored under `$GIT_DIR/refs/heads` directory, and
 -a tag is stored under `$GIT_DIR/refs/tags` directory.  git
 -imposes the following rules on how refs are named:
 +branch head is stored under the `$GIT_DIR/refs/heads` directory, and
 +a tag is stored under the `$GIT_DIR/refs/tags` directory.  git
 +imposes the following rules on how references are named:
  
 -. It can include slash `/` for hierarchical (directory)
 +. They can include slash `/` for hierarchical (directory)
    grouping, but no slash-separated component can begin with a
 -  dot `.`;
 +  dot `.`.
  
 -. It cannot have two consecutive dots `..` anywhere;
 +. They cannot have two consecutive dots `..` anywhere.
  
 -. It cannot have ASCII control character (i.e. bytes whose
 +. They cannot have ASCII control characters (i.e. bytes whose
    values are lower than \040, or \177 `DEL`), space, tilde `~`,
    caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
 -  or open bracket `[` anywhere;
 +  or open bracket `[` anywhere.
  
- . They cannot end with a slash `/`.
+ . They cannot end with a slash `/` nor a dot `.`.
+ . They cannot end with the sequence `.lock`.
+ . They cannot contain a sequence `@{`.
  
 -These rules makes it easy for shell script based tools to parse
 -refnames, pathname expansion by the shell when a refname is used
 +These rules make it easy for shell script based tools to parse
 +reference names, pathname expansion by the shell when a reference name is used
  unquoted (by mistake), and also avoids ambiguities in certain
 -refname expressions (see linkgit:git-rev-parse[1]).  Namely:
 +reference name expressions (see linkgit:git-rev-parse[1]):
  
 -. double-dot `..` are often used as in `ref1..ref2`, and in some
 -  context this notation means `{caret}ref1 ref2` (i.e. not in
 -  ref1 and in ref2).
 +. A double-dot `..` is often used as in `ref1..ref2`, and in some
 +  contexts this notation means `{caret}ref1 ref2` (i.e. not in
 +  `ref1` and in `ref2`).
  
 -. tilde `~` and caret `{caret}` are used to introduce postfix
 +. A tilde `~` and caret `{caret}` are used to introduce the postfix
    'nth parent' and 'peel onion' operation.
  
 -. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
 +. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
    value and store it in dstref" in fetch and push operations.
    It may also be used to select a specific object such as with
    'git-cat-file': "git cat-file blob v1.3.3:refs.c".
  
+ . at-open-brace `@{` is used as a notation to access a reflog entry.
+ With the `--branch` option, it expands a branch name shorthand and
+ prints the name of the branch the shorthand refers to.
+ EXAMPLE
+ -------
+ git check-ref-format --branch @{-1}::
+ Print the name of the previous branch.
  
  GIT
  ---
diff --combined builtin-branch.c
index 07a440eebacc1aa4e944874704bedfed7efb00e8,330e0c3f1605b46ab6257f0947051c3e562d9cc6..ca81d725cbac618b7dbb351dc4fb8d3a7f8fef1b
@@@ -121,11 -121,7 +121,7 @@@ static int delete_branches(int argc, co
                        die("Couldn't look up commit object for HEAD");
        }
        for (i = 0; i < argc; i++, strbuf_release(&bname)) {
-               int len = strlen(argv[i]);
-               if (interpret_nth_last_branch(argv[i], &bname) != len)
-                       strbuf_add(&bname, argv[i], len);
+               strbuf_branchname(&bname, argv[i]);
                if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
                        error("Cannot delete the branch '%s' "
                              "which you are currently on.", bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
 -                      printf("Deleted %sbranch %s (%s).\n", remote,
 +                      printf("Deleted %sbranch %s (was %s).\n", remote,
                               bname.buf,
                               find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        strbuf_addf(&buf, "branch.%s", bname.buf);
@@@ -468,22 -464,27 +464,27 @@@ static void rename_branch(const char *o
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        unsigned char sha1[20];
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       int recovery = 0;
  
        if (!oldname)
                die("cannot rename the current branch while not on any.");
  
-       strbuf_addf(&oldref, "refs/heads/%s", oldname);
-       if (check_ref_format(oldref.buf))
-               die("Invalid branch name: %s", oldref.buf);
-       strbuf_addf(&newref, "refs/heads/%s", newname);
+       if (strbuf_check_branch_ref(&oldref, oldname)) {
+               /*
+                * Bad name --- this could be an attempt to rename a
+                * ref that we used to allow to be created by accident.
+                */
+               if (resolve_ref(oldref.buf, sha1, 1, NULL))
+                       recovery = 1;
+               else
+                       die("Invalid branch name: '%s'", oldname);
+       }
  
-       if (check_ref_format(newref.buf))
-               die("Invalid branch name: %s", newref.buf);
+       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.", newname);
+               die("A branch named '%s' already exists.", newref.buf + 11);
  
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
                die("Branch rename failed");
        strbuf_release(&logmsg);
  
+       if (recovery)
+               warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
        /* no need to pass logmsg here as HEAD didn't really move */
        if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
                die("Branch renamed to %s, but HEAD is not updated!", newname);
diff --combined builtin-checkout.c
index fc55bbe14d58df66e71a9e1975bb7c4eecdc1a49,66df0c072cba89e74af26a767a6fa9b65d5449ef..33d1fecb6251b4922666255817addeb9f672fa29
@@@ -353,16 -353,11 +353,11 @@@ struct branch_info 
  static void setup_branch_path(struct branch_info *branch)
  {
        struct strbuf buf = STRBUF_INIT;
-       int ret;
  
-       if ((ret = interpret_nth_last_branch(branch->name, &buf))
-           && ret == strlen(branch->name)) {
+       strbuf_branchname(&buf, branch->name);
+       if (strcmp(buf.buf, branch->name))
                branch->name = xstrdup(buf.buf);
-               strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
-       } else {
-               strbuf_addstr(&buf, "refs/heads/");
-               strbuf_addstr(&buf, branch->name);
-       }
+       strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
        branch->path = strbuf_detach(&buf, NULL);
  }
  
@@@ -558,8 -553,8 +553,8 @@@ static int switch_branches(struct check
  
        if (!old.commit && !opts->force) {
                if (!opts->quiet) {
 -                      fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
 -                      fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
 +                      warning("You appear to be on a branch yet to be born.");
 +                      warning("Forcing checkout of %s.", new->name);
                }
                opts->force = 1;
        }
@@@ -738,12 -733,11 +733,11 @@@ no_reference
  
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
-               strbuf_addstr(&buf, "refs/heads/");
-               strbuf_addstr(&buf, opts.new_branch);
+               if (strbuf_check_branch_ref(&buf, opts.new_branch))
+                       die("git checkout: we do not like '%s' as a branch name.",
+                           opts.new_branch);
                if (!get_sha1(buf.buf, rev))
                        die("git checkout: branch %s already exists", opts.new_branch);
-               if (check_ref_format(buf.buf))
-                       die("git checkout: we do not like '%s' as a branch name.", opts.new_branch);
                strbuf_release(&buf);
        }
  
diff --combined cache.h
index 61664addd76829c09282e0d0d7e7b4f347900bd7,d28fd74880c299fa61a1b47331e4359f46fdf8b8..ab1294d6fbb7f699af160ac06674e54798f3db6c
+++ b/cache.h
@@@ -542,17 -542,8 +542,17 @@@ enum rebase_setup_type 
        AUTOREBASE_ALWAYS,
  };
  
 +enum push_default_type {
 +      PUSH_DEFAULT_UNSPECIFIED = -1,
 +      PUSH_DEFAULT_NOTHING = 0,
 +      PUSH_DEFAULT_MATCHING,
 +      PUSH_DEFAULT_TRACKING,
 +      PUSH_DEFAULT_CURRENT,
 +};
 +
  extern enum branch_track git_branch_track;
  extern enum rebase_setup_type autorebase;
 +extern enum push_default_type push_default;
  
  #define GIT_REPO_VERSION 0
  extern int repository_format_version;
@@@ -623,8 -614,7 +623,8 @@@ enum sharedrepo 
        PERM_EVERYBODY      = 0664,
  };
  int git_config_perm(const char *var, const char *value);
 -int adjust_shared_perm(const char *path);
 +int set_shared_perm(const char *path, int mode);
 +#define adjust_shared_perm(path) set_shared_perm((path), 0)
  int safe_create_leading_directories(char *path);
  int safe_create_leading_directories_const(const char *path);
  char *enter_repo(char *path, int strict);
@@@ -656,6 -646,7 +656,6 @@@ extern int check_sha1_signature(const u
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
  
  extern int has_sha1_pack(const unsigned char *sha1);
 -extern int has_sha1_kept_pack(const unsigned char *sha1);
  extern int has_sha1_file(const unsigned char *sha1);
  extern int has_loose_object_nonlocal(const unsigned char *sha1);
  
@@@ -680,7 -671,7 +680,7 @@@ extern int read_ref(const char *filenam
  extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
- extern int interpret_nth_last_branch(const char *str, struct strbuf *);
+ extern int interpret_branch_name(const char *str, struct strbuf *);
  
  extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
  extern const char *ref_rev_parse_rules[];
diff --combined refs.c
index 26b001453bd1566f2ab3554fffeb63ec0437ceaa,f3fdcbd202ab8ac7970f0e8c8cc9b79846c8ff9b..59c373fc6d315aacfc3b1a0cecce0ceb4b65d72f
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -676,6 -676,7 +676,7 @@@ int for_each_rawref(each_ref_fn fn, voi
   * - it has double dots "..", or
   * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
   * - it ends with a "/".
+  * - it ends with ".lock"
   */
  
  static inline int bad_ref_char(int ch)
  
  int check_ref_format(const char *ref)
  {
-       int ch, level, bad_type;
+       int ch, level, bad_type, last;
        int ret = CHECK_REF_FORMAT_OK;
        const char *cp = ref;
  
                                return CHECK_REF_FORMAT_ERROR;
                }
  
+               last = ch;
                /* scan the rest of the path component */
                while ((ch = *cp++) != 0) {
                        bad_type = bad_ref_char(ch);
-                       if (bad_type) {
+                       if (bad_type)
                                return CHECK_REF_FORMAT_ERROR;
-                       }
                        if (ch == '/')
                                break;
-                       if (ch == '.' && *cp == '.')
+                       if (last == '.' && ch == '.')
+                               return CHECK_REF_FORMAT_ERROR;
+                       if (last == '@' && ch == '{')
                                return CHECK_REF_FORMAT_ERROR;
+                       last = ch;
                }
                level++;
                if (!ch) {
+                       if (ref <= cp - 2 && cp[-2] == '.')
+                               return CHECK_REF_FORMAT_ERROR;
                        if (level < 2)
                                return CHECK_REF_FORMAT_ONELEVEL;
+                       if (has_extension(ref, ".lock"))
+                               return CHECK_REF_FORMAT_ERROR;
                        return ret;
                }
        }
  }
  
 +const char *prettify_ref(const struct ref *ref)
 +{
 +      const char *name = ref->name;
 +      return name + (
 +              !prefixcmp(name, "refs/heads/") ? 11 :
 +              !prefixcmp(name, "refs/tags/") ? 10 :
 +              !prefixcmp(name, "refs/remotes/") ? 13 :
 +              0);
 +}
 +
  const char *ref_rev_parse_rules[] = {
        "%.*s",
        "refs/%.*s",
@@@ -1006,7 -1004,7 +1014,7 @@@ int delete_ref(const char *refname, con
  
        err = unlink(git_path("logs/%s", lock->ref_name));
        if (err && errno != ENOENT)
 -              fprintf(stderr, "warning: unlink(%s) failed: %s",
 +              warning("unlink(%s) failed: %s",
                        git_path("logs/%s", lock->ref_name), strerror(errno));
        invalidate_cached_refs();
        unlock_ref(lock);
@@@ -1448,7 -1446,8 +1456,7 @@@ int read_ref_at(const char *ref, unsign
                                if (get_sha1_hex(rec + 41, sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
 -                                      fprintf(stderr,
 -                                              "warning: Log %s has gap after %s.\n",
 +                                      warning("Log %s has gap after %s.",
                                                logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
                                if (get_sha1_hex(rec + 41, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
 -                                      fprintf(stderr,
 -                                              "warning: Log %s unexpectedly ended on %s.\n",
 +                                      warning("Log %s unexpectedly ended on %s.",
                                                logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }