diff options
Diffstat (limited to 'sha1_name.c')
-rw-r--r-- | sha1_name.c | 219 |
1 files changed, 165 insertions, 54 deletions
diff --git a/sha1_name.c b/sha1_name.c index bf92417838..709ff2eee6 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -7,6 +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; @@ -206,7 +208,9 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) if (exists ? !status : status == SHORT_NAME_NOT_FOUND) { - hex[len] = 0; + int cut_at = len + unique_abbrev_extra_length; + cut_at = (cut_at < 40) ? cut_at : 40; + hex[cut_at] = 0; return hex; } len++; @@ -342,7 +346,7 @@ 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) { - static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; + static const char *warn_msg = "refname '%.*s' is ambiguous."; char *real_ref = NULL; int refs_found = 0; int at, reflog_len; @@ -390,7 +394,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return -1; if (warn_ambiguous_refs && refs_found > 1) - fprintf(stderr, warning, len, str); + warning(warn_msg, len, str); if (reflog_len) { int nth, i; @@ -426,14 +430,14 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (read_ref_at(real_ref, at_time, nth, sha1, NULL, &co_time, &co_tz, &co_cnt)) { if (at_time) - fprintf(stderr, - "warning: Log for '%.*s' only goes " - "back to %s.\n", len, str, + warning("Log for '%.*s' only goes " + "back to %s.", len, str, show_date(co_time, co_tz, DATE_RFC2822)); - else - fprintf(stderr, - "warning: Log for '%.*s' only has " - "%d entries.\n", len, str, co_cnt); + else { + free(real_ref); + die("Log for '%.*s' only has %d entries.", + len, str, co_cnt); + } } } @@ -560,6 +564,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) expected_type = OBJ_BLOB; else if (sp[0] == '}') expected_type = OBJ_NONE; + else if (sp[0] == '/') + expected_type = OBJ_COMMIT; else return -1; @@ -574,19 +580,37 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) 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; } @@ -659,6 +683,16 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) return get_short_sha1(name, len, sha1, 0); } +/* + * This interprets names like ':/Initial revision of "git"' by searching + * through history and returning the first commit whose message starts + * the given regular expression. + * + * For future extension, ':/!' is reserved. If you want to match a message + * beginning with a '!', you have to repeat the exclamation mark. + */ +#define ONELINE_SEEN (1u<<20) + static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { @@ -673,65 +707,65 @@ static int handle_one_ref(const char *path, } if (object->type != OBJ_COMMIT) return 0; - insert_by_date((struct commit *)object, list); + commit_list_insert_by_date((struct commit *)object, list); return 0; } -/* - * This interprets names like ':/Initial revision of "git"' by searching - * through history and returning the first commit whose message starts - * with the given string. - * - * For future extension, ':/!' is reserved. If you want to match a message - * beginning with a '!', you have to repeat the exclamation mark. - */ - -#define ONELINE_SEEN (1u<<20) -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 (prefix[1] != '!') die ("Invalid search pattern: %s", prefix); prefix++; } - for_each_ref(handle_one_ref, &list); - for (l = list; l; l = l->next) + + if (regcomp(®ex, prefix, REG_EXTENDED)) + die("Invalid search pattern: %s", prefix); + + 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 (!prefixcmp(p + 2, prefix)) { + + p = strstr(p, "\n\n"); + matches = p && !regexec(®ex, p + 2, 0, NULL, 0); + free(to_free); + + if (matches) { hashcpy(sha1, commit->object.sha1); - retval = 0; + found = 1; break; } } - free(temp_commit_buffer); + regfree(®ex); 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 { @@ -927,14 +961,32 @@ int interpret_branch_name(const char *name, struct strbuf *buf) return len; } +int strbuf_branchname(struct strbuf *sb, const char *name) +{ + int len = strlen(name); + if (interpret_branch_name(name, sb) == len) + return 0; + strbuf_add(sb, name, len); + return len; +} + +int strbuf_check_branch_ref(struct strbuf *sb, const char *name) +{ + strbuf_branchname(sb, name); + if (name[0] == '-') + return CHECK_REF_FORMAT_ERROR; + strbuf_splice(sb, 0, 0, "refs/heads/", 11); + return check_ref_format(sb->buf); +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" */ int get_sha1(const char *name, unsigned char *sha1) { - unsigned unused; - return get_sha1_with_mode(name, sha1, &unused); + struct object_context unused; + return get_sha1_with_context(name, sha1, &unused); } /* Must be called only when object_name:filename doesn't exist. */ @@ -1032,24 +1084,59 @@ static void diagnose_invalid_index_path(int stage, int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix) { + struct object_context oc; + int ret; + ret = get_sha1_with_context_1(name, sha1, &oc, gently, prefix); + *mode = oc.mode; + 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) +{ int ret, bracket_depth; int namelen = strlen(name); const char *cp; - *mode = S_IFINVALID; + memset(oc, 0, sizeof(*oc)); + oc->mode = S_IFINVALID; ret = get_sha1_1(name, namelen, sha1); 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] == '/') - 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]) @@ -1058,7 +1145,18 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, 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)); + oc->path[sizeof(oc->path)-1] = '\0'; + if (!active_cache) read_cache(); pos = cache_name_pos(cp, namelen); @@ -1071,13 +1169,15 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, break; if (ce_stage(ce) == stage) { hashcpy(sha1, ce->sha1); - *mode = ce->ce_mode; + 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++) { @@ -1098,12 +1198,23 @@ int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, } if (!get_sha1_1(name, cp-name, tree_sha1)) { const char *filename = cp+1; - ret = get_tree_entry(tree_sha1, filename, sha1, mode); + 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, tree_sha1, object_name); free(object_name); } + hashcpy(oc->tree, tree_sha1); + strncpy(oc->path, filename, + sizeof(oc->path)); + oc->path[sizeof(oc->path)-1] = '\0'; + + free(new_filename); return ret; } else { if (!gently) |