Merge branch 'nd/oneline-sha1-name-from-specific-ref'
authorJunio C Hamano <gitster@pobox.com>
Tue, 21 Dec 2010 22:30:19 +0000 (14:30 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 21 Dec 2010 22:30:19 +0000 (14:30 -0800)
* nd/oneline-sha1-name-from-specific-ref:
get_sha1: handle special case $commit^{/}
get_sha1: support $commit^{/regex} syntax
get_sha1_oneline: make callers prepare the commit list to traverse
get_sha1_oneline: fix lifespan rule of temp_commit_buffer variable

1  2 
Documentation/revisions.txt
sha1_name.c
index 8b519d74487ce1089462df8556e76d1829fb82d4,174fa8e90d7a5b90706b69119d107feb880ec75d..9e92734bc1b308c47e5f5726c704a3ddffa5beb9
@@@ -106,6 -106,12 +106,12 @@@ the `$GIT_DIR/refs` directory or from t
    and dereference the tag recursively until a non-tag object is
    found.
  
+ * A suffix '{caret}' to a revision parameter followed by a brace
+   pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):
+   this is the same as `:/fix nasty bug` syntax below except that
+   it returns the youngest matching commit which is reachable from
+   the ref before '{caret}'.
  * A colon, followed by a slash, followed by a text (e.g. `:/fix nasty bug`): this names
    a commit whose commit message matches the specified regular expression.
    This name returns the youngest matching commit which is
    ':path' (with an empty part before the colon, e.g. `:README`)
    is a special case of the syntax described next: content
    recorded in the index at the given path.
 +  A path starting with './' or '../' is relative to current working directory.
 +  The given path will be converted to be relative to working tree's root directory.
 +  This is most useful to address a blob or tree from a commit or tree that has
 +  the same tree structure with the working tree.
  
  * A colon, optionally followed by a stage number (0 to 3) and a
    colon, followed by a path (e.g. `:0:README`); this names a blob object in the
diff --combined sha1_name.c
index 8f49279642bf1aa8ce74fee351772e3fb28a73c4,c5c59ced7f0cbd9ced026d44df6439da65082fb8..ceb9cdd860da78bf3fe0a1819846ded0e87fda8c
@@@ -7,6 -7,8 +7,8 @@@
  #include "refs.h"
  #include "remote.h"
  
+ static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
  static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
  {
        struct alternate_object_database *alt;
@@@ -562,6 -564,8 +564,8 @@@ static int peel_onion(const char *name
                expected_type = OBJ_BLOB;
        else if (sp[0] == '}')
                expected_type = OBJ_NONE;
+       else if (sp[0] == '/')
+               expected_type = OBJ_COMMIT;
        else
                return -1;
  
                if (!o || (!o->parsed && !parse_object(o->sha1)))
                        return -1;
                hashcpy(sha1, o->sha1);
+               return 0;
        }
-       else {
+       /*
+        * At this point, the syntax look correct, so
+        * if we do not get the needed object, we should
+        * barf.
+        */
+       o = peel_to_type(name, len, o, expected_type);
+       if (!o)
+               return -1;
+       hashcpy(sha1, o->sha1);
+       if (sp[0] == '/') {
+               /* "$commit^{/foo}" */
+               char *prefix;
+               int ret;
+               struct commit_list *list = NULL;
                /*
-                * At this point, the syntax look correct, so
-                * if we do not get the needed object, we should
-                * barf.
+                * $commit^{/}. Some regex implementation may reject.
+                * We don't need regex anyway. '' pattern always matches.
                 */
-               o = peel_to_type(name, len, o, expected_type);
-               if (o) {
-                       hashcpy(sha1, o->sha1);
+               if (sp[1] == '}')
                        return 0;
-               }
-               return -1;
+               prefix = xstrndup(sp + 1, name + len - 1 - (sp + 1));
+               commit_list_insert((struct commit *)o, &list);
+               ret = get_sha1_oneline(prefix, sha1, list);
+               free(prefix);
+               return ret;
        }
        return 0;
  }
@@@ -686,15 -708,14 +708,14 @@@ static int handle_one_ref(const char *p
        if (object->type != OBJ_COMMIT)
                return 0;
        insert_by_date((struct commit *)object, list);
-       object->flags |= ONELINE_SEEN;
        return 0;
  }
  
- static int get_sha1_oneline(const char *prefix, unsigned char *sha1)
+ static int get_sha1_oneline(const char *prefix, unsigned char *sha1,
+                           struct commit_list *list)
  {
-       struct commit_list *list = NULL, *backup = NULL, *l;
-       int retval = -1;
-       char *temp_commit_buffer = NULL;
+       struct commit_list *backup = NULL, *l;
+       int found = 0;
        regex_t regex;
  
        if (prefix[0] == '!') {
        if (regcomp(&regex, prefix, REG_EXTENDED))
                die("Invalid search pattern: %s", prefix);
  
-       for_each_ref(handle_one_ref, &list);
-       for (l = list; l; l = l->next)
+       for (l = list; l; l = l->next) {
+               l->item->object.flags |= ONELINE_SEEN;
                commit_list_insert(l->item, &backup);
+       }
        while (list) {
-               char *p;
+               char *p, *to_free = NULL;
                struct commit *commit;
                enum object_type type;
                unsigned long size;
+               int matches;
  
                commit = pop_most_recent_commit(&list, ONELINE_SEEN);
                if (!parse_object(commit->object.sha1))
                        continue;
-               free(temp_commit_buffer);
                if (commit->buffer)
                        p = commit->buffer;
                else {
                        p = read_sha1_file(commit->object.sha1, &type, &size);
                        if (!p)
                                continue;
-                       temp_commit_buffer = p;
+                       to_free = p;
                }
-               if (!(p = strstr(p, "\n\n")))
-                       continue;
-               if (!regexec(&regex, p + 2, 0, NULL, 0)) {
+               p = strstr(p, "\n\n");
+               matches = p && !regexec(&regex, p + 2, 0, NULL, 0);
+               free(to_free);
+               if (matches) {
                        hashcpy(sha1, commit->object.sha1);
-                       retval = 0;
+                       found = 1;
                        break;
                }
        }
        regfree(&regex);
-       free(temp_commit_buffer);
        free_commit_list(list);
        for (l = backup; l; l = l->next)
                clear_commit_marks(l->item, ONELINE_SEEN);
-       return retval;
+       free_commit_list(backup);
+       return found ? 0 : -1;
  }
  
  struct grab_nth_branch_switch_cbdata {
@@@ -1066,23 -1091,6 +1091,23 @@@ int get_sha1_with_mode_1(const char *na
        return ret;
  }
  
 +static char *resolve_relative_path(const char *rel)
 +{
 +      if (prefixcmp(rel, "./") && prefixcmp(rel, "../"))
 +              return NULL;
 +
 +      if (!startup_info)
 +              die("BUG: startup_info struct is not initialized.");
 +
 +      if (!is_inside_work_tree())
 +              die("relative path syntax can't be used outside working tree.");
 +
 +      /* die() inside prefix_path() if resolved path is outside worktree */
 +      return prefix_path(startup_info->prefix,
 +                         startup_info->prefix ? strlen(startup_info->prefix) : 0,
 +                         rel);
 +}
 +
  int get_sha1_with_context_1(const char *name, unsigned char *sha1,
                            struct object_context *oc,
                            int gently, const char *prefix)
        if (!ret)
                return ret;
        /* sha1:path --> object name of path in ent sha1
 -       * :path -> object name of path in index
 +       * :path -> object name of absolute path in index
 +       * :./path -> object name of path relative to cwd in index
         * :[0-3]:path -> object name of path in index at stage
         * :/foo -> recent commit matching foo
         */
        if (name[0] == ':') {
                int stage = 0;
                struct cache_entry *ce;
 +              char *new_path = NULL;
                int pos;
-               if (namelen > 2 && name[1] == '/')
-                       /* don't need mode for commit */
-                       return get_sha1_oneline(name + 2, sha1);
+               if (namelen > 2 && name[1] == '/') {
+                       struct commit_list *list = NULL;
+                       for_each_ref(handle_one_ref, &list);
+                       return get_sha1_oneline(name + 2, sha1, list);
+               }
                if (namelen < 3 ||
                    name[2] != ':' ||
                    name[1] < '0' || '3' < name[1])
                        stage = name[1] - '0';
                        cp = name + 3;
                }
 -              namelen = namelen - (cp - name);
 +              new_path = resolve_relative_path(cp);
 +              if (!new_path) {
 +                      namelen = namelen - (cp - name);
 +              } else {
 +                      cp = new_path;
 +                      namelen = strlen(cp);
 +              }
  
                strncpy(oc->path, cp,
                        sizeof(oc->path));
                        if (ce_stage(ce) == stage) {
                                hashcpy(sha1, ce->sha1);
                                oc->mode = ce->ce_mode;
 +                              free(new_path);
                                return 0;
                        }
                        pos++;
                }
                if (!gently)
                        diagnose_invalid_index_path(stage, prefix, cp);
 +              free(new_path);
                return -1;
        }
        for (cp = name, bracket_depth = 0; *cp; cp++) {
                }
                if (!get_sha1_1(name, cp-name, tree_sha1)) {
                        const char *filename = cp+1;
 +                      char *new_filename = NULL;
 +
 +                      new_filename = resolve_relative_path(filename);
 +                      if (new_filename)
 +                              filename = new_filename;
                        ret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);
                        if (!gently) {
                                diagnose_invalid_sha1_path(prefix, filename,
                                sizeof(oc->path));
                        oc->path[sizeof(oc->path)-1] = '\0';
  
 +                      free(new_filename);
                        return ret;
                } else {
                        if (!gently)