Merge branch 'js/refer-upstream'
authorJunio C Hamano <gitster@pobox.com>
Sat, 23 Jan 2010 00:08:13 +0000 (16:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 23 Jan 2010 00:08:13 +0000 (16:08 -0800)
* js/refer-upstream:
Teach @{upstream} syntax to strbuf_branchanme()
t1506: more test for @{upstream} syntax
Introduce <branch>@{upstream} notation

1  2 
Documentation/git-rev-parse.txt
sha1_name.c
index d375f1af1025698f73900d7eacdf8dc314a87278,923b56a1606877a5a38c315fdfd6bdf0da1eb966..818eb48d679a66fed046917f8211e3b490a80f31
@@@ -15,16 -15,16 +15,16 @@@ DESCRIPTIO
  
  Many git porcelainish commands take mixture of flags
  (i.e. parameters that begin with a dash '-') and parameters
 -meant for the underlying 'git-rev-list' command they use internally
 +meant for the underlying 'git rev-list' command they use internally
  and flags and parameters for the other commands they use
 -downstream of 'git-rev-list'.  This command is used to
 +downstream of 'git rev-list'.  This command is used to
  distinguish between them.
  
  
  OPTIONS
  -------
  --parseopt::
 -      Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below).
 +      Use 'git rev-parse' in option parsing mode (see PARSEOPT section below).
  
  --keep-dashdash::
        Only meaningful in `--parseopt` mode. Tells the option parser to echo
        that take options themself.
  
  --sq-quote::
 -      Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE
 +      Use 'git rev-parse' in shell quoting mode (see SQ-QUOTE
        section below). In contrast to the `--sq` option below, this
        mode does only quoting. Nothing else is done to command input.
  
  --revs-only::
        Do not output flags and parameters not meant for
 -      'git-rev-list' command.
 +      'git rev-list' command.
  
  --no-revs::
        Do not output flags and parameters meant for
 -      'git-rev-list' command.
 +      'git rev-list' command.
  
  --flags::
        Do not output non-flag parameters.
@@@ -74,7 -74,7 +74,7 @@@
        properly quoted for consumption by shell.  Useful when
        you expect your parameter to contain whitespaces and
        newlines (e.g. when using pickaxe `-S` with
 -      'git-diff-\*'). In contrast to the `--sq-quote` option,
 +      'git diff-\*'). In contrast to the `--sq-quote` option,
        the command input is still interpreted as usual.
  
  --not::
  --remotes::
        Show tag refs found in `$GIT_DIR/refs/remotes`.
  
 +--show-toplevel::
 +      Show the absolute path of the top-level directory.
 +
  --show-prefix::
        When the command is invoked from a subdirectory, show the
        path of the current directory relative to the top-level
  --since=datestring::
  --after=datestring::
        Parse the date string, and output the corresponding
 -      --max-age= parameter for 'git-rev-list'.
 +      --max-age= parameter for 'git rev-list'.
  
  --until=datestring::
  --before=datestring::
        Parse the date string, and output the corresponding
 -      --min-age= parameter for 'git-rev-list'.
 +      --min-age= parameter for 'git rev-list'.
  
  <args>...::
        Flags and parameters to be parsed.
@@@ -174,7 -171,7 +174,7 @@@ blobs contained in a commit
    name the same commit object if there are no other object in
    your repository whose object name starts with dae86e.
  
 -* An output from 'git-describe'; i.e. a closest tag, optionally
 +* An output from 'git describe'; i.e. a closest tag, optionally
    followed by a dash and a number of commits, followed by a dash, a
    `g`, and an abbreviated object name.
  
  +
  HEAD names the commit your changes in the working tree is based on.
  FETCH_HEAD records the branch you fetched from a remote repository
 -with your last 'git-fetch' invocation.
 +with your last 'git fetch' invocation.
  ORIG_HEAD is created by commands that moves your HEAD in a drastic
  way, to record the position of the HEAD before their operation, so that
  you can change the tip of the branch back to the state before you ran
  them easily.
  MERGE_HEAD records the commit(s) you are merging into your branch
 -when you run 'git-merge'.
 +when you run 'git merge'.
  
  * A ref followed by the suffix '@' with a date specification
    enclosed in a brace
  * The special construct '@\{-<n>\}' means the <n>th branch checked out
    before the current one.
  
+ * The suffix '@{upstream}' to a ref (short form 'ref@{u}') refers to
+   the branch the ref is set to build on top of.  Missing ref defaults
+   to the current branch.
  * A suffix '{caret}' to a revision parameter means the first parent of
    that commit object.  '{caret}<n>' means the <n>th parent (i.e.
    'rev{caret}'
@@@ -311,7 -312,7 +315,7 @@@ G   H   I   
  SPECIFYING RANGES
  -----------------
  
 -History traversing commands such as 'git-log' operate on a set
 +History traversing commands such as 'git log' operate on a set
  of commits, not just a single commit.  To these commands,
  specifying a single revision with the notation described in the
  previous section means the set of commits reachable from that
@@@ -352,7 -353,7 +356,7 @@@ Here are a handful of examples
  PARSEOPT
  --------
  
 -In `--parseopt` mode, 'git-rev-parse' helps massaging options to bring to shell
 +In `--parseopt` mode, 'git rev-parse' helps massaging options to bring to shell
  scripts the same facilities C builtins have. It works as an option normalizer
  (e.g. splits single switches aggregate values), a bit like `getopt(1)` does.
  
@@@ -364,7 -365,7 +368,7 @@@ usage on the standard error stream, an
  Input Format
  ~~~~~~~~~~~~
  
 -'git-rev-parse --parseopt' input format is fully text based. It has two parts,
 +'git rev-parse --parseopt' input format is fully text based. It has two parts,
  separated by a line that contains only `--`. The lines before the separator
  (should be more than one) are used for the usage.
  The lines after the separator describe the options.
@@@ -423,13 -424,13 +427,13 @@@ eval `echo "$OPTS_SPEC" | git rev-pars
  SQ-QUOTE
  --------
  
 -In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a
 +In `--sq-quote` mode, 'git rev-parse' echoes on the standard output a
  single line suitable for `sh(1)` `eval`. This line is made by
  normalizing the arguments following `--sq-quote`. Nothing other than
  quoting the arguments is done.
  
  If you want command input to still be interpreted as usual by
 -'git-rev-parse' before the output is shell quoted, see the `--sq`
 +'git rev-parse' before the output is shell quoted, see the `--sq`
  option.
  
  Example
diff --combined sha1_name.c
index 1739e9e61240c33a1e2bf110bbeeee111c380716,2376c6d8f453793872718082654745acd4144519..9215ad1d050c427b67124f4c5ca76e60e8e022f0
@@@ -5,6 -5,7 +5,7 @@@
  #include "blob.h"
  #include "tree-walk.h"
  #include "refs.h"
+ #include "remote.h"
  
  static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
  {
@@@ -240,7 -241,8 +241,8 @@@ static int ambiguous_path(const char *p
  
  /*
   * *string and *len will only be substituted, and *string returned (for
-  * later free()ing) if the string passed in is of the form @{-<n>}.
+  * later free()ing) if the string passed in is a magic short-hand form
+  * to name a branch.
   */
  static char *substitute_branch_name(const char **string, int *len)
  {
@@@ -323,6 -325,20 +325,20 @@@ int dwim_log(const char *str, int len, 
        return logs_found;
  }
  
+ static inline int upstream_mark(const char *string, int len)
+ {
+       const char *suffix[] = { "@{upstream}", "@{u}" };
+       int i;
+       for (i = 0; i < ARRAY_SIZE(suffix); i++) {
+               int suffix_len = strlen(suffix[i]);
+               if (suffix_len <= len
+                   && !memcmp(string, suffix[i], suffix_len))
+                       return suffix_len;
+       }
+       return 0;
+ }
  static int get_sha1_1(const char *name, int len, unsigned char *sha1);
  
  static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        if (len && str[len-1] == '}') {
                for (at = len-2; at >= 0; at--) {
                        if (str[at] == '@' && str[at+1] == '{') {
-                               reflog_len = (len-1) - (at+2);
-                               len = at;
+                               if (!upstream_mark(str + at, len - at)) {
+                                       reflog_len = (len-1) - (at+2);
+                                       len = at;
+                               }
                                break;
                        }
                }
@@@ -740,17 -758,10 +758,10 @@@ static int grab_nth_branch_switch(unsig
  }
  
  /*
-  * This reads "@{-N}" syntax, finds the name of the Nth previous
-  * branch we were on, and places the name of the branch in the given
-  * buf and returns the number of characters parsed if successful.
-  *
-  * If the input is not of the accepted format, it returns a negative
-  * number to signal an error.
-  *
-  * If the input was ok but there are not N branch switches in the
-  * reflog, it returns 0.
+  * Parse @{-N} syntax, return the number of characters parsed
+  * if successful; otherwise signal an error with negative value.
   */
int interpret_branch_name(const char *name, struct strbuf *buf)
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
  {
        long nth;
        int i, retval;
@@@ -794,48 -805,60 +805,102 @@@ release_return
        return retval;
  }
  
 +int get_sha1_mb(const char *name, unsigned char *sha1)
 +{
 +      struct commit *one, *two;
 +      struct commit_list *mbs;
 +      unsigned char sha1_tmp[20];
 +      const char *dots;
 +      int st;
 +
 +      dots = strstr(name, "...");
 +      if (!dots)
 +              return get_sha1(name, sha1);
 +      if (dots == name)
 +              st = get_sha1("HEAD", sha1_tmp);
 +      else {
 +              struct strbuf sb;
 +              strbuf_init(&sb, dots - name);
 +              strbuf_add(&sb, name, dots - name);
 +              st = get_sha1(sb.buf, sha1_tmp);
 +              strbuf_release(&sb);
 +      }
 +      if (st)
 +              return st;
 +      one = lookup_commit_reference_gently(sha1_tmp, 0);
 +      if (!one)
 +              return -1;
 +
 +      if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
 +              return -1;
 +      two = lookup_commit_reference_gently(sha1_tmp, 0);
 +      if (!two)
 +              return -1;
 +      mbs = get_merge_bases(one, two, 1);
 +      if (!mbs || mbs->next)
 +              st = -1;
 +      else {
 +              st = 0;
 +              hashcpy(sha1, mbs->item->object.sha1);
 +      }
 +      free_commit_list(mbs);
 +      return st;
 +}
 +
+ /*
+  * This reads short-hand syntax that not only evaluates to a commit
+  * object name, but also can act as if the end user spelled the name
+  * of the branch from the command line.
+  *
+  * - "@{-N}" finds the name of the Nth previous branch we were on, and
+  *   places the name of the branch in the given buf and returns the
+  *   number of characters parsed if successful.
+  *
+  * - "<branch>@{upstream}" finds the name of the other ref that
+  *   <branch> is configured to merge with (missing <branch> defaults
+  *   to the current branch), and places the name of the branch in the
+  *   given buf and returns the number of characters parsed if
+  *   successful.
+  *
+  * If the input is not of the accepted format, it returns a negative
+  * number to signal an error.
+  *
+  * If the input was ok but there are not N branch switches in the
+  * reflog, it returns 0.
+  */
+ int interpret_branch_name(const char *name, struct strbuf *buf)
+ {
+       char *cp;
+       struct branch *upstream;
+       int namelen = strlen(name);
+       int len = interpret_nth_prior_checkout(name, buf);
+       int tmp_len;
+       if (!len)
+               return len; /* syntax Ok, not enough switches */
+       if (0 < len)
+               return len; /* consumed from the front */
+       cp = strchr(name, '@');
+       if (!cp)
+               return -1;
+       tmp_len = upstream_mark(cp, namelen - (cp - name));
+       if (!tmp_len)
+               return -1;
+       len = cp + tmp_len - name;
+       cp = xstrndup(name, cp - name);
+       upstream = branch_get(*cp ? cp : NULL);
+       if (!upstream
+           || !upstream->merge
+           || !upstream->merge[0]->dst)
+               return error("No upstream branch found for '%s'", cp);
+       free(cp);
+       cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
+       strbuf_reset(buf);
+       strbuf_addstr(buf, cp);
+       free(cp);
+       return len;
+ }
  /*
   * This is like "get_sha1_basic()", except it allows "sha1 expressions",
   * notably "xyz^" for "parent of xyz"
@@@ -846,96 -869,7 +911,96 @@@ int get_sha1(const char *name, unsigne
        return get_sha1_with_mode(name, sha1, &unused);
  }
  
 -int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
 +/* Must be called only when object_name:filename doesn't exist. */
 +static void diagnose_invalid_sha1_path(const char *prefix,
 +                                     const char *filename,
 +                                     const unsigned char *tree_sha1,
 +                                     const char *object_name)
 +{
 +      struct stat st;
 +      unsigned char sha1[20];
 +      unsigned mode;
 +
 +      if (!prefix)
 +              prefix = "";
 +
 +      if (!lstat(filename, &st))
 +              die("Path '%s' exists on disk, but not in '%s'.",
 +                  filename, object_name);
 +      if (errno == ENOENT || errno == ENOTDIR) {
 +              char *fullname = xmalloc(strlen(filename)
 +                                           + strlen(prefix) + 1);
 +              strcpy(fullname, prefix);
 +              strcat(fullname, filename);
 +
 +              if (!get_tree_entry(tree_sha1, fullname,
 +                                  sha1, &mode)) {
 +                      die("Path '%s' exists, but not '%s'.\n"
 +                          "Did you mean '%s:%s'?",
 +                          fullname,
 +                          filename,
 +                          object_name,
 +                          fullname);
 +              }
 +              die("Path '%s' does not exist in '%s'",
 +                  filename, object_name);
 +      }
 +}
 +
 +/* Must be called only when :stage:filename doesn't exist. */
 +static void diagnose_invalid_index_path(int stage,
 +                                      const char *prefix,
 +                                      const char *filename)
 +{
 +      struct stat st;
 +      struct cache_entry *ce;
 +      int pos;
 +      unsigned namelen = strlen(filename);
 +      unsigned fullnamelen;
 +      char *fullname;
 +
 +      if (!prefix)
 +              prefix = "";
 +
 +      /* Wrong stage number? */
 +      pos = cache_name_pos(filename, namelen);
 +      if (pos < 0)
 +              pos = -pos - 1;
 +      ce = active_cache[pos];
 +      if (ce_namelen(ce) == namelen &&
 +          !memcmp(ce->name, filename, namelen))
 +              die("Path '%s' is in the index, but not at stage %d.\n"
 +                  "Did you mean ':%d:%s'?",
 +                  filename, stage,
 +                  ce_stage(ce), filename);
 +
 +      /* Confusion between relative and absolute filenames? */
 +      fullnamelen = namelen + strlen(prefix);
 +      fullname = xmalloc(fullnamelen + 1);
 +      strcpy(fullname, prefix);
 +      strcat(fullname, filename);
 +      pos = cache_name_pos(fullname, fullnamelen);
 +      if (pos < 0)
 +              pos = -pos - 1;
 +      ce = active_cache[pos];
 +      if (ce_namelen(ce) == fullnamelen &&
 +          !memcmp(ce->name, fullname, fullnamelen))
 +              die("Path '%s' is in the index, but not '%s'.\n"
 +                  "Did you mean ':%d:%s'?",
 +                  fullname, filename,
 +                  ce_stage(ce), fullname);
 +
 +      if (!lstat(filename, &st))
 +              die("Path '%s' exists on disk, but not in the index.", filename);
 +      if (errno == ENOENT || errno == ENOTDIR)
 +              die("Path '%s' does not exist (neither on disk nor in the index).",
 +                  filename);
 +
 +      free(fullname);
 +}
 +
 +
 +int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
  {
        int ret, bracket_depth;
        int namelen = strlen(name);
                        }
                        pos++;
                }
 +              if (!gently)
 +                      diagnose_invalid_index_path(stage, prefix, cp);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
        }
        if (*cp == ':') {
                unsigned char tree_sha1[20];
 -              if (!get_sha1_1(name, cp-name, tree_sha1))
 -                      return get_tree_entry(tree_sha1, cp+1, sha1,
 -                                            mode);
 +              char *object_name = NULL;
 +              if (!gently) {
 +                      object_name = xmalloc(cp-name+1);
 +                      strncpy(object_name, name, cp-name);
 +                      object_name[cp-name] = '\0';
 +              }
 +              if (!get_sha1_1(name, cp-name, tree_sha1)) {
 +                      const char *filename = cp+1;
 +                      ret = get_tree_entry(tree_sha1, filename, sha1, mode);
 +                      if (!gently) {
 +                              diagnose_invalid_sha1_path(prefix, filename,
 +                                                         tree_sha1, object_name);
 +                              free(object_name);
 +                      }
 +                      return ret;
 +              } else {
 +                      if (!gently)
 +                              die("Invalid object name '%s'.", object_name);
 +              }
        }
        return ret;
  }