From a1b475eeb40dc29246d4ce7465474479639769d0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 11:30:15 -0700 Subject: sha1_name.c: indentation fix Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 03ffc2c..5b0c845 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -103,10 +103,10 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne } if (first < num) { const unsigned char *now, *next; - now = nth_packed_object_sha1(p, first); + now = nth_packed_object_sha1(p, first); if (match_sha(len, match, now)) { next = nth_packed_object_sha1(p, first+1); - if (!next|| !match_sha(len, match, next)) { + if (!next|| !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { found_sha1 = now; -- cgit v0.10.2-6-g49f6 From f01cc14c3c70cfd820114505d2ddc153c28f6f89 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 10:19:35 -0700 Subject: sha1_name.c: hide get_sha1_with_context_1() ugliness There is no outside caller that cares about the "only-to-die" ugliness. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 10afd71..9ee470c 100644 --- a/cache.h +++ b/cache.h @@ -817,11 +817,7 @@ static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsig { return get_sha1_with_mode_1(str, sha1, mode, 0, NULL); } -extern int get_sha1_with_context_1(const char *name, unsigned char *sha1, struct object_context *orc, int only_to_die, const char *prefix); -static inline int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc) -{ - return get_sha1_with_context_1(str, sha1, orc, 0, NULL); -} +extern int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc); /* * Try to read a SHA1 in hexadecimal format from the 40 characters diff --git a/sha1_name.c b/sha1_name.c index 5b0c845..10932bf 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -992,16 +992,6 @@ static void diagnose_invalid_index_path(int stage, } -int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, - int only_to_die, const char *prefix) -{ - struct object_context oc; - int ret; - ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix); - *mode = oc.mode; - return ret; -} - static char *resolve_relative_path(const char *rel) { if (prefixcmp(rel, "./") && prefixcmp(rel, "../")) @@ -1019,9 +1009,9 @@ static char *resolve_relative_path(const char *rel) rel); } -int get_sha1_with_context_1(const char *name, unsigned char *sha1, - struct object_context *oc, - int only_to_die, const char *prefix) +static int get_sha1_with_context_1(const char *name, unsigned char *sha1, + struct object_context *oc, + int only_to_die, const char *prefix) { int ret, bracket_depth; int namelen = strlen(name); @@ -1134,3 +1124,18 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, } return ret; } + +int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, + int only_to_die, const char *prefix) +{ + struct object_context oc; + int ret; + ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix); + *mode = oc.mode; + return ret; +} + +int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc) +{ + return get_sha1_with_context_1(str, sha1, orc, 0, NULL); +} -- cgit v0.10.2-6-g49f6 From 8c135ea260a84ef71899c8bd23bb39425288f9fe Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 11:01:25 -0700 Subject: sha1_name.c: get rid of get_sha1_with_mode_1() The only external caller is setup.c that tries to give a nicer error message when an object name is misspelt (e.g. "HEAD:cashe.h"). Retire it and give the caller a dedicated and more intuitive API function maybe_die_on_misspelt_object_name(). Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 9ee470c..beafa5b 100644 --- a/cache.h +++ b/cache.h @@ -812,11 +812,8 @@ struct object_context { }; extern int get_sha1(const char *str, unsigned char *sha1); -extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix); -static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) -{ - return get_sha1_with_mode_1(str, sha1, mode, 0, NULL); -} +extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); +extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc); /* diff --git a/setup.c b/setup.c index 61c22e6..233bfbe 100644 --- a/setup.c +++ b/setup.c @@ -55,18 +55,14 @@ int check_filename(const char *prefix, const char *arg) static void NORETURN die_verify_filename(const char *prefix, const char *arg) { - unsigned char sha1[20]; - unsigned mode; - /* * Saying "'(icase)foo' does not exist in the index" when the * user gave us ":(icase)foo" is just stupid. A magic pathspec * begins with a colon and is followed by a non-alnum; do not - * let get_sha1_with_mode_1(only_to_die=1) to even trigger. + * let maybe_die_on_misspelt_object_name() even trigger. */ if (!(arg[0] == ':' && !isalnum(arg[1]))) - /* try a detailed diagnostic ... */ - get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix); + maybe_die_on_misspelt_object_name(arg, prefix); /* ... or fall back the most general message. */ die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" diff --git a/sha1_name.c b/sha1_name.c index 10932bf..df583c2 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1125,12 +1125,24 @@ static int get_sha1_with_context_1(const char *name, unsigned char *sha1, return ret; } -int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, - int only_to_die, const char *prefix) +/* + * Call this function when you know "name" given by the end user must + * name an object but it doesn't; the function _may_ die with a better + * diagnostic message than "no such object 'name'", e.g. "Path 'doc' does not + * exist in 'HEAD'" when given "HEAD:doc", or it may return in which case + * you have a chance to diagnose the error further. + */ +void maybe_die_on_misspelt_object_name(const char *name, const char *prefix) { struct object_context oc; - int ret; - ret = get_sha1_with_context_1(name, sha1, &oc, only_to_die, prefix); + unsigned char sha1[20]; + get_sha1_with_context_1(name, sha1, &oc, 1, prefix); +} + +int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) +{ + struct object_context oc; + int ret = get_sha1_with_context_1(str, sha1, &oc, 0, NULL); *mode = oc.mode; return ret; } -- cgit v0.10.2-6-g49f6 From 249c8f4a164c8502f8274c505a48b9c686f458d0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 12:56:44 -0700 Subject: sha1_name.c: get rid of get_sha1_with_mode() There are only two callers, and they will benefit from being able to pass disambiguation hints to underlying get_sha1_with_context() API once it happens. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index beafa5b..2aa9fb6 100644 --- a/cache.h +++ b/cache.h @@ -812,7 +812,6 @@ struct object_context { }; extern int get_sha1(const char *str, unsigned char *sha1); -extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc); diff --git a/revision.c b/revision.c index 064e351..86a14c8 100644 --- a/revision.c +++ b/revision.c @@ -1097,7 +1097,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, int cant_be_filename) { - unsigned mode; + struct object_context oc; char *dotdot; struct object *object; unsigned char sha1[20]; @@ -1180,13 +1180,13 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, local_flags = UNINTERESTING; arg++; } - if (get_sha1_with_mode(arg, sha1, &mode)) + if (get_sha1_with_context(arg, sha1, &oc)) return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); object = get_reference(revs, arg, sha1, flags ^ local_flags); add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags); - add_pending_object_with_mode(revs, object, arg, mode); + add_pending_object_with_mode(revs, object, arg, oc.mode); return 0; } @@ -1794,11 +1794,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->def && !revs->pending.nr && !got_rev_arg) { unsigned char sha1[20]; struct object *object; - unsigned mode; - if (get_sha1_with_mode(revs->def, sha1, &mode)) + struct object_context oc; + if (get_sha1_with_context(revs->def, sha1, &oc)) die("bad default revision '%s'", revs->def); object = get_reference(revs, revs->def, sha1, 0); - add_pending_object_with_mode(revs, object, revs->def, mode); + add_pending_object_with_mode(revs, object, revs->def, oc.mode); } /* Did the user ask for any diff output? Run the diff! */ diff --git a/sha1_name.c b/sha1_name.c index df583c2..e63459b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1139,14 +1139,6 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix) get_sha1_with_context_1(name, sha1, &oc, 1, prefix); } -int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) -{ - struct object_context oc; - int ret = get_sha1_with_context_1(str, sha1, &oc, 0, NULL); - *mode = oc.mode; - return ret; -} - int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc) { return get_sha1_with_context_1(str, sha1, orc, 0, NULL); -- cgit v0.10.2-6-g49f6 From 274ac009f4613a14d2114259e40c6b523d9b7160 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 11:41:03 -0700 Subject: sha1_name.c: clarify what "fake" is for in find_short_object_filename() Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index e63459b..9bb657d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -17,6 +17,13 @@ static int find_short_object_filename(int len, const char *name, unsigned char * static struct alternate_object_database *fakeent; if (!fakeent) { + /* + * Create a "fake" alternate object database that + * points to our own object database, to make it + * easier to get a temporary working space in + * alt->name/alt->base while iterating over the + * object databases including our own. + */ const char *objdir = get_object_directory(); int objdir_len = strlen(objdir); int entlen = objdir_len + 43; -- cgit v0.10.2-6-g49f6 From 1b27c2f01af398706acf554794644daaf6bc8835 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 11:50:36 -0700 Subject: sha1_name.c: rename "now" to "current" This variable points at the element we are currently looking at, and does not have anything to do with the current time which the name "now" implies. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 9bb657d..c5c4591 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -93,11 +93,11 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne last = num; while (first < last) { uint32_t mid = (first + last) / 2; - const unsigned char *now; + const unsigned char *current; int cmp; - now = nth_packed_object_sha1(p, mid); - cmp = hashcmp(match, now); + current = nth_packed_object_sha1(p, mid); + cmp = hashcmp(match, current); if (!cmp) { first = mid; break; @@ -109,17 +109,17 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne last = mid; } if (first < num) { - const unsigned char *now, *next; - now = nth_packed_object_sha1(p, first); - if (match_sha(len, match, now)) { + const unsigned char *current, *next; + current = nth_packed_object_sha1(p, first); + if (match_sha(len, match, current)) { next = nth_packed_object_sha1(p, first+1); if (!next|| !match_sha(len, match, next)) { /* unique within this pack */ if (!found) { - found_sha1 = now; + found_sha1 = current; found++; } - else if (hashcmp(found_sha1, now)) { + else if (hashcmp(found_sha1, current)) { found = 2; break; } -- cgit v0.10.2-6-g49f6 From f703e6ea5e3bc7ff39b3ef6d3a21208fb63dfabd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 13:10:38 -0700 Subject: sha1_name.c: refactor find_short_packed_object() Extract the logic to find object(s) that match a given prefix inside a single pack into a separate helper function, and give it a bit more comment. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index c5c4591..7bef45f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -78,6 +78,59 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char * return 1; } +static int unique_in_pack(int len, + const unsigned char *match, + struct packed_git *p, + const unsigned char **found_sha1, + int seen_so_far) +{ + uint32_t num, last, i, first = 0; + const unsigned char *current = NULL; + + open_pack_index(p); + num = p->num_objects; + last = num; + while (first < last) { + uint32_t mid = (first + last) / 2; + const unsigned char *current; + int cmp; + + current = nth_packed_object_sha1(p, mid); + cmp = hashcmp(match, current); + if (!cmp) { + first = mid; + break; + } + if (cmp > 0) { + first = mid+1; + continue; + } + last = mid; + } + + /* + * At this point, "first" is the location of the lowest object + * with an object name that could match "match". See if we have + * 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + current = nth_packed_object_sha1(p, first); + if (!match_sha(len, match, current)) + break; + + /* current matches */ + if (!seen_so_far) { + *found_sha1 = current; + seen_so_far++; + } else if (seen_so_far) { + /* is it the same as the one previously found elsewhere? */ + if (hashcmp(*found_sha1, current)) + return 2; /* definitely not unique */ + } + } + return seen_so_far; +} + static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1) { struct packed_git *p; @@ -85,53 +138,9 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne int found = 0; prepare_packed_git(); - for (p = packed_git; p && found < 2; p = p->next) { - uint32_t num, last; - uint32_t first = 0; - open_pack_index(p); - num = p->num_objects; - last = num; - while (first < last) { - uint32_t mid = (first + last) / 2; - const unsigned char *current; - int cmp; - - current = nth_packed_object_sha1(p, mid); - cmp = hashcmp(match, current); - if (!cmp) { - first = mid; - break; - } - if (cmp > 0) { - first = mid+1; - continue; - } - last = mid; - } - if (first < num) { - const unsigned char *current, *next; - current = nth_packed_object_sha1(p, first); - if (match_sha(len, match, current)) { - next = nth_packed_object_sha1(p, first+1); - if (!next|| !match_sha(len, match, next)) { - /* unique within this pack */ - if (!found) { - found_sha1 = current; - found++; - } - else if (hashcmp(found_sha1, current)) { - found = 2; - break; - } - } - else { - /* not even unique within this pack */ - found = 2; - break; - } - } - } - } + for (p = packed_git; p && found < 2; p = p->next) + found = unique_in_pack(len, match, p, &found_sha1, found); + if (found == 1) hashcpy(sha1, found_sha1); return found; -- cgit v0.10.2-6-g49f6 From 1703f9aa0b87d52278489029d538ef1a4aa37432 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jun 2012 22:35:43 -0700 Subject: sha1_name.c: correct misnamed "canonical" and "res" These are hexadecimal and binary representation of the short object name given to the callchain as its input. Rename them with _pfx suffix to make it clear they are prefixes, and call them hex and bin respectively. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 7bef45f..db8ac83 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -9,7 +9,7 @@ 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) +static int find_short_object_filename(int len, const char *hex_pfx, unsigned char *sha1) { struct alternate_object_database *alt; char hex[40]; @@ -34,18 +34,18 @@ static int find_short_object_filename(int len, const char *name, unsigned char * } fakeent->next = alt_odb_list; - sprintf(hex, "%.2s", name); + sprintf(hex, "%.2s", hex_pfx); for (alt = fakeent; alt && found < 2; alt = alt->next) { struct dirent *de; DIR *dir; - sprintf(alt->name, "%.2s/", name); + sprintf(alt->name, "%.2s/", hex_pfx); dir = opendir(alt->base); if (!dir) continue; while ((de = readdir(dir)) != NULL) { if (strlen(de->d_name) != 38) continue; - if (memcmp(de->d_name, name + 2, len - 2)) + if (memcmp(de->d_name, hex_pfx + 2, len - 2)) continue; if (!found) { memcpy(hex + 2, de->d_name, 38); @@ -79,7 +79,7 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char * } static int unique_in_pack(int len, - const unsigned char *match, + const unsigned char *bin_pfx, struct packed_git *p, const unsigned char **found_sha1, int seen_so_far) @@ -96,7 +96,7 @@ static int unique_in_pack(int len, int cmp; current = nth_packed_object_sha1(p, mid); - cmp = hashcmp(match, current); + cmp = hashcmp(bin_pfx, current); if (!cmp) { first = mid; break; @@ -110,12 +110,12 @@ static int unique_in_pack(int len, /* * At this point, "first" is the location of the lowest object - * with an object name that could match "match". See if we have + * with an object name that could match "bin_pfx". See if we have * 0, 1 or more objects that actually match(es). */ for (i = first; i < num; i++) { current = nth_packed_object_sha1(p, first); - if (!match_sha(len, match, current)) + if (!match_sha(len, bin_pfx, current)) break; /* current matches */ @@ -131,7 +131,7 @@ static int unique_in_pack(int len, return seen_so_far; } -static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1) +static int find_short_packed_object(int len, const unsigned char *bin_pfx, unsigned char *sha1) { struct packed_git *p; const unsigned char *found_sha1 = NULL; @@ -139,7 +139,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne prepare_packed_git(); for (p = packed_git; p && found < 2; p = p->next) - found = unique_in_pack(len, match, p, &found_sha1, found); + found = unique_in_pack(len, bin_pfx, p, &found_sha1, found); if (found == 1) hashcpy(sha1, found_sha1); @@ -149,15 +149,15 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne #define SHORT_NAME_NOT_FOUND (-1) #define SHORT_NAME_AMBIGUOUS (-2) -static int find_unique_short_object(int len, char *canonical, - unsigned char *res, unsigned char *sha1) +static int find_unique_short_object(int len, char *hex_pfx, + unsigned char *bin_pfx, unsigned char *sha1) { int has_unpacked, has_packed; unsigned char unpacked_sha1[20], packed_sha1[20]; prepare_alt_odb(); - has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1); - has_packed = find_short_packed_object(len, res, packed_sha1); + has_unpacked = find_short_object_filename(len, hex_pfx, unpacked_sha1); + has_packed = find_short_packed_object(len, bin_pfx, packed_sha1); if (!has_unpacked && !has_packed) return SHORT_NAME_NOT_FOUND; if (1 < has_unpacked || 1 < has_packed) @@ -177,13 +177,13 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, int quietly) { int i, status; - char canonical[40]; - unsigned char res[20]; + char hex_pfx[40]; + unsigned char bin_pfx[20]; if (len < MINIMUM_ABBREV || len > 40) return -1; - hashclr(res); - memset(canonical, 'x', 40); + hashclr(bin_pfx); + memset(hex_pfx, 'x', 40); for (i = 0; i < len ;i++) { unsigned char c = name[i]; unsigned char val; @@ -197,15 +197,15 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, } else return -1; - canonical[i] = c; + hex_pfx[i] = c; if (!(i & 1)) val <<= 4; - res[i >> 1] |= val; + bin_pfx[i >> 1] |= val; } - status = find_unique_short_object(i, canonical, res, sha1); + status = find_unique_short_object(i, hex_pfx, bin_pfx, sha1); if (!quietly && (status == SHORT_NAME_AMBIGUOUS)) - return error("short SHA1 %.*s is ambiguous.", len, canonical); + return error("short SHA1 %.*s is ambiguous.", len, hex_pfx); return status; } -- cgit v0.10.2-6-g49f6 From a78fafe74a4f8c416c35ca07b3ba28375475ae96 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jun 2012 22:07:36 -0700 Subject: sha1_name.c: restructure disambiguation of short names We try to find zero, one or more matches from loose objects and packed objects independently and then decide if the given short object name is unique across them. Instead, introduce a "struct disambiguate_state" that keeps track of what we have found so far, that can be one of: - We have seen one object that _could_ be what we are looking for; - We have also checked that object for additional constraints (if any), and found that the object satisfies it; - We have also checked that object for additional constraints (if any), and found that the object does not satisfy it; or - We have seen more than one objects that satisfy the constraints. and pass it to the enumeration functions for loose and packed objects. The disambiguation state can optionally take a callback function that takes a candidate object name and reports if the object satisifies additional criteria (e.g. when the caller knows that the short name must refer to a commit, this mechanism can be used to check the type of the given object). Compared to the earlier attempt, this round avoids the optional check if there is only one candidate that matches the short name in the first place. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index db8ac83..2e2dbb8 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -9,11 +9,67 @@ static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *); -static int find_short_object_filename(int len, const char *hex_pfx, unsigned char *sha1) +typedef int (*disambiguate_hint_fn)(const unsigned char *, void *); + +struct disambiguate_state { + disambiguate_hint_fn fn; + void *cb_data; + unsigned char candidate[20]; + unsigned candidate_exists:1; + unsigned candidate_checked:1; + unsigned candidate_ok:1; + unsigned disambiguate_fn_used:1; + unsigned ambiguous:1; +}; + +static void update_candidates(struct disambiguate_state *ds, const unsigned char *current) +{ + if (!ds->candidate_exists) { + /* this is the first candidate */ + hashcpy(ds->candidate, current); + ds->candidate_exists = 1; + return; + } else if (!hashcmp(ds->candidate, current)) { + /* the same as what we already have seen */ + return; + } + + if (!ds->fn) { + /* cannot disambiguate between ds->candidate and current */ + ds->ambiguous = 1; + return; + } + + if (!ds->candidate_checked) { + ds->candidate_ok = ds->fn(ds->candidate, ds->cb_data); + ds->disambiguate_fn_used = 1; + ds->candidate_checked = 1; + } + + if (!ds->candidate_ok) { + /* discard the candidate; we know it does not satisify fn */ + hashcpy(ds->candidate, current); + ds->candidate_checked = 0; + return; + } + + /* if we reach this point, we know ds->candidate satisfies fn */ + if (ds->fn(current, ds->cb_data)) { + /* + * if both current and candidate satisfy fn, we cannot + * disambiguate. + */ + ds->candidate_ok = 0; + ds->ambiguous = 1; + } + + /* otherwise, current can be discarded and candidate is still good */ +} + +static void find_short_object_filename(int len, const char *hex_pfx, struct disambiguate_state *ds) { struct alternate_object_database *alt; char hex[40]; - int found = 0; static struct alternate_object_database *fakeent; if (!fakeent) { @@ -35,32 +91,27 @@ static int find_short_object_filename(int len, const char *hex_pfx, unsigned cha fakeent->next = alt_odb_list; sprintf(hex, "%.2s", hex_pfx); - for (alt = fakeent; alt && found < 2; alt = alt->next) { + for (alt = fakeent; alt && !ds->ambiguous; alt = alt->next) { struct dirent *de; DIR *dir; sprintf(alt->name, "%.2s/", hex_pfx); dir = opendir(alt->base); if (!dir) continue; - while ((de = readdir(dir)) != NULL) { + + while (!ds->ambiguous && (de = readdir(dir)) != NULL) { + unsigned char sha1[20]; + if (strlen(de->d_name) != 38) continue; if (memcmp(de->d_name, hex_pfx + 2, len - 2)) continue; - if (!found) { - memcpy(hex + 2, de->d_name, 38); - found++; - } - else if (memcmp(hex + 2, de->d_name, 38)) { - found = 2; - break; - } + memcpy(hex + 2, de->d_name, 38); + if (!get_sha1_hex(hex, sha1)) + update_candidates(ds, sha1); } closedir(dir); } - if (found == 1) - return get_sha1_hex(hex, sha1) == 0; - return found; } static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b) @@ -78,11 +129,10 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char * return 1; } -static int unique_in_pack(int len, +static void unique_in_pack(int len, const unsigned char *bin_pfx, - struct packed_git *p, - const unsigned char **found_sha1, - int seen_so_far) + struct packed_git *p, + struct disambiguate_state *ds) { uint32_t num, last, i, first = 0; const unsigned char *current = NULL; @@ -113,63 +163,58 @@ static int unique_in_pack(int len, * with an object name that could match "bin_pfx". See if we have * 0, 1 or more objects that actually match(es). */ - for (i = first; i < num; i++) { - current = nth_packed_object_sha1(p, first); + for (i = first; i < num && !ds->ambiguous; i++) { + current = nth_packed_object_sha1(p, i); if (!match_sha(len, bin_pfx, current)) break; - - /* current matches */ - if (!seen_so_far) { - *found_sha1 = current; - seen_so_far++; - } else if (seen_so_far) { - /* is it the same as the one previously found elsewhere? */ - if (hashcmp(*found_sha1, current)) - return 2; /* definitely not unique */ - } + update_candidates(ds, current); } - return seen_so_far; } -static int find_short_packed_object(int len, const unsigned char *bin_pfx, unsigned char *sha1) +static void find_short_packed_object(int len, const unsigned char *bin_pfx, + struct disambiguate_state *ds) { struct packed_git *p; - const unsigned char *found_sha1 = NULL; - int found = 0; prepare_packed_git(); - for (p = packed_git; p && found < 2; p = p->next) - found = unique_in_pack(len, bin_pfx, p, &found_sha1, found); - - if (found == 1) - hashcpy(sha1, found_sha1); - return found; + for (p = packed_git; p && !ds->ambiguous; p = p->next) + unique_in_pack(len, bin_pfx, p, ds); } #define SHORT_NAME_NOT_FOUND (-1) #define SHORT_NAME_AMBIGUOUS (-2) -static int find_unique_short_object(int len, char *hex_pfx, - unsigned char *bin_pfx, unsigned char *sha1) +static int finish_object_disambiguation(struct disambiguate_state *ds, + unsigned char *sha1) { - int has_unpacked, has_packed; - unsigned char unpacked_sha1[20], packed_sha1[20]; + if (ds->ambiguous) + return SHORT_NAME_AMBIGUOUS; - prepare_alt_odb(); - has_unpacked = find_short_object_filename(len, hex_pfx, unpacked_sha1); - has_packed = find_short_packed_object(len, bin_pfx, packed_sha1); - if (!has_unpacked && !has_packed) + if (!ds->candidate_exists) return SHORT_NAME_NOT_FOUND; - if (1 < has_unpacked || 1 < has_packed) - return SHORT_NAME_AMBIGUOUS; - if (has_unpacked != has_packed) { - hashcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1)); - return 0; - } - /* Both have unique ones -- do they match? */ - if (hashcmp(packed_sha1, unpacked_sha1)) - return SHORT_NAME_AMBIGUOUS; - hashcpy(sha1, packed_sha1); + + if (!ds->candidate_checked) + /* + * If this is the only candidate, there is no point + * calling the disambiguation hint callback. + * + * On the other hand, if the current candidate + * replaced an earlier candidate that did _not_ pass + * the disambiguation hint callback, then we do have + * more than one objects that match the short name + * given, so we should make sure this one matches; + * otherwise, if we discovered this one and the one + * that we previously discarded in the reverse order, + * we would end up showing different results in the + * same repository! + */ + ds->candidate_ok = (!ds->disambiguate_fn_used || + ds->fn(ds->candidate, ds->cb_data)); + + if (!ds->candidate_ok) + return SHORT_NAME_NOT_FOUND; + + hashcpy(sha1, ds->candidate); return 0; } @@ -179,6 +224,7 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, int i, status; char hex_pfx[40]; unsigned char bin_pfx[20]; + struct disambiguate_state ds; if (len < MINIMUM_ABBREV || len > 40) return -1; @@ -203,7 +249,13 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, bin_pfx[i >> 1] |= val; } - status = find_unique_short_object(i, hex_pfx, bin_pfx, sha1); + prepare_alt_odb(); + + memset(&ds, 0, sizeof(ds)); + find_short_object_filename(len, hex_pfx, &ds); + find_short_packed_object(len, bin_pfx, &ds); + status = finish_object_disambiguation(&ds, sha1); + if (!quietly && (status == SHORT_NAME_AMBIGUOUS)) return error("short SHA1 %.*s is ambiguous.", len, hex_pfx); return status; -- cgit v0.10.2-6-g49f6 From c005e98612c3ba896415cfa56df67ae8b240cc85 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 11:01:03 -0700 Subject: get_sha1(): fix error status regression In finish_object_disambiguation(), if the candidate hasn't been checked, there are two cases: - It is the first and only object that match the prefix; or - It replaced another object that matched the prefix but that object did not satisfy ds->fn() callback. And the former case we set ds->candidate_ok to true without doing anything else, while for the latter we check the candidate, which may set ds->candidate_ok to false. At this point in the code, ds->candidate_ok can be false only if this last-round check found that the candidate does not pass the check, because the state after update_candidates() returns cannot satisfy !ds->ambiguous && ds->candidate_exists && ds->candidate_checked and !ds->canidate_ok at the same time. Hence, when we execute this "return", we know we have seen more than one object that match the prefix (and none of them satisfied ds->fn), meaning that we should say "the short name is ambiguous", not "there is no object that matches the prefix". Noticed by Jeff King. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 2e2dbb8..c824bdd 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -212,7 +212,7 @@ static int finish_object_disambiguation(struct disambiguate_state *ds, ds->fn(ds->candidate, ds->cb_data)); if (!ds->candidate_ok) - return SHORT_NAME_NOT_FOUND; + return SHORT_NAME_AMBIGUOUS; hashcpy(sha1, ds->candidate); return 0; -- cgit v0.10.2-6-g49f6 From 37c00e5590605c9d3ba76b6c9d7a94ac0356f703 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 11:32:03 -0700 Subject: sha1_name.c: allow get_short_sha1() to take other flags Instead of a separate "int quietly" argument, make it take "unsigned flags" so that we can pass other options to it. The bit assignment of this flag word is exposed in cache.h because the mechanism will be exposed to callers of the higher layer in later commits in this series. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 2aa9fb6..1bafa45 100644 --- a/cache.h +++ b/cache.h @@ -811,6 +811,8 @@ struct object_context { unsigned mode; }; +#define GET_SHA1_QUIETLY 01 + extern int get_sha1(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc); diff --git a/sha1_name.c b/sha1_name.c index c824bdd..793d80c 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -219,12 +219,13 @@ static int finish_object_disambiguation(struct disambiguate_state *ds, } static int get_short_sha1(const char *name, int len, unsigned char *sha1, - int quietly) + unsigned flags) { int i, status; char hex_pfx[40]; unsigned char bin_pfx[20]; struct disambiguate_state ds; + int quietly = !!(flags & GET_SHA1_QUIETLY); if (len < MINIMUM_ABBREV || len > 40) return -1; @@ -272,7 +273,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) return hex; while (len < 40) { unsigned char sha1_ret[20]; - status = get_short_sha1(hex, len, sha1_ret, 1); + status = get_short_sha1(hex, len, sha1_ret, GET_SHA1_QUIETLY); if (exists ? !status : status == SHORT_NAME_NOT_FOUND) { @@ -603,7 +604,7 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1) if (ch == 'g' && cp[-1] == '-') { cp++; len -= cp - name; - return get_short_sha1(cp, len, sha1, 1); + return get_short_sha1(cp, len, sha1, GET_SHA1_QUIETLY); } } } -- cgit v0.10.2-6-g49f6 From aa1dec9ef6ff184792520eb4539af1555c01604c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Jun 2012 23:03:09 -0700 Subject: sha1_name.c: teach get_short_sha1() a commit-only option When the caller knows that the parameter is meant to name a commit, e.g. "56789a" in describe name "v1.2.3-4-g56789a", pass that as a hint so that lower level can use it to disambiguate objects when there is only one commit whose name begins with 56789a even if there are objects of other types whose names share the same prefix. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 1bafa45..2d91dbd 100644 --- a/cache.h +++ b/cache.h @@ -812,6 +812,7 @@ struct object_context { }; #define GET_SHA1_QUIETLY 01 +#define GET_SHA1_COMMIT 02 extern int get_sha1(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); diff --git a/sha1_name.c b/sha1_name.c index 793d80c..174d3df 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -218,6 +218,12 @@ static int finish_object_disambiguation(struct disambiguate_state *ds, return 0; } +static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unused) +{ + int kind = sha1_object_info(sha1, NULL); + return kind == OBJ_COMMIT; +} + static int get_short_sha1(const char *name, int len, unsigned char *sha1, unsigned flags) { @@ -253,6 +259,9 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, prepare_alt_odb(); memset(&ds, 0, sizeof(ds)); + if (flags & GET_SHA1_COMMIT) + ds.fn = disambiguate_commit_only; + find_short_object_filename(len, hex_pfx, &ds); find_short_packed_object(len, bin_pfx, &ds); status = finish_object_disambiguation(&ds, sha1); -- cgit v0.10.2-6-g49f6 From 6269b6b676310d9fa2560da49be618372b39acf4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Jun 2012 13:45:56 -0700 Subject: sha1_name.c: get_describe_name() by definition groks only commits Teach get_describe_name() to pass the disambiguation hint down the callchain to get_short_sha1(). Also add tests to show various syntactic elements that we could take advantage of the object type information to help disambiguration of abbreviated object names. Many of them are marked as broken, and some of them will be fixed in later patches in this series. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 174d3df..caef6e5 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -603,6 +603,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) static int get_describe_name(const char *name, int len, unsigned char *sha1) { const char *cp; + unsigned flags = GET_SHA1_QUIETLY | GET_SHA1_COMMIT; for (cp = name + len - 1; name + 2 <= cp; cp--) { char ch = *cp; @@ -613,7 +614,7 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1) if (ch == 'g' && cp[-1] == '-') { cp++; len -= cp - name; - return get_short_sha1(cp, len, sha1, GET_SHA1_QUIETLY); + return get_short_sha1(cp, len, sha1, flags); } } } diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh new file mode 100755 index 0000000..0d3f05b --- /dev/null +++ b/t/t1512-rev-parse-disambiguation.sh @@ -0,0 +1,255 @@ +#!/bin/sh + +test_description='object name disambiguation + +Create blobs, trees, commits and a tag that all share the same +prefix, and make sure "git rev-parse" can take advantage of +type information to disambiguate short object names that are +not necessarily unique. + +The final history used in the test has five commits, with the bottom +one tagged as v1.0.0. They all have one regular file each. + + +-------------------------------------------+ + | | + | .-------b3wettvi---- ad2uee | + | / / | + | a2onsxbvj---czy8f73t--ioiley5o | + | | + +-------------------------------------------+ + +' + +. ./test-lib.sh + +test_expect_success 'blob and tree' ' + test_tick && + ( + for i in 0 1 2 3 4 5 6 7 8 9 + do + echo $i + done + echo + echo b1rwzyc3 + ) >a0blgqsjc && + + # create one blob 0000000000b36 + git add a0blgqsjc && + + # create one tree 0000000000cdc + git write-tree +' + +test_expect_success 'warn ambiguity when no candidate matches type hint' ' + test_must_fail git rev-parse --verify 000000000^{commit} 2>actual && + grep "short SHA1 000000000 is ambiguous" actual +' + +test_expect_failure 'disambiguate tree-ish' ' + # feed tree-ish in an unambiguous way + git rev-parse --verify 0000000000cdc:a0blgqsjc && + + # ambiguous at the object name level, but there is only one + # such tree-ish (the other is a blob) + git rev-parse --verify 000000000:a0blgqsjc +' + +test_expect_failure 'disambiguate blob' ' + sed -e "s/|$//" >patch <<-EOF && + diff --git a/frotz b/frotz + index 000000000..ffffff 100644 + --- a/frotz + +++ b/frotz + @@ -10,3 +10,4 @@ + 9 + | + b1rwzyc3 + +irwry + EOF + ( + GIT_INDEX_FILE=frotz && + export GIT_INDEX_FILE && + git apply --build-fake-ancestor frotz patch && + git cat-file blob :frotz >actual + ) && + test_cmp a0blgqsjc actual +' + +test_expect_failure 'disambiguate tree' ' + commit=$(echo "d7xm" | git commit-tree 000000000) && + test $(git rev-parse $commit^{tree}) = $(git rev-parse 0000000000cdc) +' + +test_expect_success 'first commit' ' + # create one commit 0000000000e4f + git commit -m a2onsxbvj +' + +test_expect_failure 'disambiguate commit-ish' ' + # feed commit-ish in an unambiguous way + git rev-parse --verify 0000000000e4f^{commit} && + + # ambiguous at the object name level, but there is only one + # such commit (the others are tree and blob) + git rev-parse --verify 000000000^{commit} && + + # likewise + git rev-parse --verify 000000000^0 +' + +test_expect_failure 'disambiguate commit' ' + commit=$(echo "j9xqh" | git commit-tree 0000000000cdc -p 000000000) && + test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f) +' + +test_expect_failure 'log name1..name2 takes only commit-ishes on both ends' ' + git log 000000000..000000000 && + git log ..000000000 && + git log 000000000.. && + git log 000000000...000000000 && + git log ...000000000 && + git log 000000000... +' + +test_expect_failure 'rev-parse name1..name2 takes only commit-ishes on both ends' ' + git rev-parse 000000000..000000000 && + git rev-parse ..000000000 && + git rev-parse 000000000.. +' + +test_expect_failure 'git log takes only commit-ish' ' + git log 000000000 +' + +test_expect_failure 'git reset takes only commit-ish' ' + git reset 000000000 +' + +test_expect_success 'first tag' ' + # create one tag 0000000000f8f + git tag -a -m j7cp83um v1.0.0 +' + +test_expect_failure 'two semi-ambiguous commit-ish' ' + # Once the parser becomes ultra-smart, it could notice that + # 110282 before ^{commit} name many different objects, but + # that only two (HEAD and v1.0.0 tag) can be peeled to commit, + # and that peeling them down to commit yield the same commit + # without ambiguity. + git rev-parse --verify 110282^{commit} && + + # likewise + git log 000000000..000000000 && + git log ..000000000 && + git log 000000000.. && + git log 000000000...000000000 && + git log ...000000000 && + git log 000000000... +' + +test_expect_failure 'three semi-ambiguous tree-ish' ' + # Likewise for tree-ish. HEAD, v1.0.0 and HEAD^{tree} share + # the prefix but peeling them to tree yields the same thing + git rev-parse --verify 000000000^{tree} +' + +test_expect_success 'parse describe name' ' + # feed an unambiguous describe name + git rev-parse --verify v1.0.0-0-g0000000000e4f && + + # ambiguous at the object name level, but there is only one + # such commit (others are blob, tree and tag) + git rev-parse --verify v1.0.0-0-g000000000 +' + +test_expect_success 'more history' ' + # commit 0000000000043 + git mv a0blgqsjc d12cr3h8t && + echo h62xsjeu >>d12cr3h8t && + git add d12cr3h8t && + + test_tick && + git commit -m czy8f73t && + + # commit 00000000008ec + git mv d12cr3h8t j000jmpzn && + echo j08bekfvt >>j000jmpzn && + git add j000jmpzn && + + test_tick && + git commit -m ioiley5o && + + # commit 0000000005b0 + git checkout v1.0.0^0 && + git mv a0blgqsjc f5518nwu && + + for i in h62xsjeu j08bekfvt kg7xflhm + do + echo $i + done >>f5518nwu && + git add f5518nwu && + + test_tick && + git commit -m b3wettvi && + side=$(git rev-parse HEAD) && + + # commit 000000000066 + git checkout master && + + # If you use recursive, merge will fail and you will need to + # clean up a0blgqsjc as well. If you use resolve, merge will + # succeed. + test_might_fail git merge --no-commit -s recursive $side && + git rm -f f5518nwu j000jmpzn && + + test_might_fail git rm -f a0blgqsjc && + ( + git cat-file blob $side:f5518nwu + echo j3l0i9s6 + ) >ab2gs879 && + git add ab2gs879 && + + test_tick && + git commit -m ad2uee + +' + +test_expect_failure 'parse describe name taking advantage of generation' ' + # ambiguous at the object name level, but there is only one + # such commit at generation 0 + git rev-parse --verify v1.0.0-0-g000000000 && + + # likewise for generation 2 and 4 + git rev-parse --verify v1.0.0-2-g000000000 && + git rev-parse --verify v1.0.0-4-g000000000 +' + +# Note: because rev-parse does not even try to disambiguate based on +# the generation number, this test currently succeeds for a wrong +# reason. When it learns to use the generation number, the previous +# test should succeed, and also this test should fail because the +# describe name used in the test with generation number can name two +# commits. Make sure that such a future enhancement does not randomly +# pick one. +test_expect_success 'parse describe name not ignoring ambiguity' ' + # ambiguous at the object name level, and there are two such + # commits at generation 1 + test_must_fail git rev-parse --verify v1.0.0-1-g000000000 +' + +test_expect_success 'ambiguous commit-ish' ' + # Now there are many commits that begin with the + # common prefix, none of these should pick one at + # random. They all should result in ambiguity errors. + test_must_fail git rev-parse --verify 110282^{commit} && + + # likewise + test_must_fail git log 000000000..000000000 && + test_must_fail git log ..000000000 && + test_must_fail git log 000000000.. && + test_must_fail git log 000000000...000000000 && + test_must_fail git log ...000000000 && + test_must_fail git log 000000000... +' + +test_done -- cgit v0.10.2-6-g49f6 From e48ba200be908f02a3fb30adcbb8000b7100cb32 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 09:46:50 -0700 Subject: sha1_name.c: get_sha1_1() takes lookup flags This is to pass the disambiguation hints from the caller down the callchain. Nothing is changed in this step, as everybody just passes 0 in the flag. Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index caef6e5..8feb9b5 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -333,7 +333,7 @@ static inline int upstream_mark(const char *string, int len) return 0; } -static int get_sha1_1(const char *name, int len, unsigned char *sha1); +static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags); static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { @@ -370,7 +370,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) ret = interpret_branch_name(str+at, &buf); if (ret > 0) { /* substitute this branch name and restart */ - return get_sha1_1(buf.buf, buf.len, sha1); + return get_sha1_1(buf.buf, buf.len, sha1, 0); } else if (ret == 0) { return -1; } @@ -440,7 +440,7 @@ static int get_parent(const char *name, int len, unsigned char *result, int idx) { unsigned char sha1[20]; - int ret = get_sha1_1(name, len, sha1); + int ret = get_sha1_1(name, len, sha1, 0); struct commit *commit; struct commit_list *p; @@ -473,7 +473,7 @@ static int get_nth_ancestor(const char *name, int len, struct commit *commit; int ret; - ret = get_sha1_1(name, len, sha1); + ret = get_sha1_1(name, len, sha1, 0); if (ret) return ret; commit = lookup_commit_reference(sha1); @@ -554,7 +554,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) else return -1; - if (get_sha1_1(name, sp - name - 2, outer)) + if (get_sha1_1(name, sp - name - 2, outer, 0)) return -1; o = parse_object(outer); @@ -621,7 +621,7 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1) return -1; } -static int get_sha1_1(const char *name, int len, unsigned char *sha1) +static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags) { int ret, has_suffix; const char *cp; @@ -1098,7 +1098,7 @@ static int get_sha1_with_context_1(const char *name, unsigned char *sha1, memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; - ret = get_sha1_1(name, namelen, sha1); + ret = get_sha1_1(name, namelen, sha1, 0); if (!ret) return ret; /* sha1:path --> object name of path in ent sha1 @@ -1176,7 +1176,7 @@ static int get_sha1_with_context_1(const char *name, unsigned char *sha1, strncpy(object_name, name, cp-name); object_name[cp-name] = '\0'; } - if (!get_sha1_1(name, cp-name, tree_sha1)) { + if (!get_sha1_1(name, cp-name, tree_sha1, 0)) { const char *filename = cp+1; char *new_filename = NULL; -- cgit v0.10.2-6-g49f6 From e2643617d75e53e5a69278f8d7382553d1c14cf0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 10:00:40 -0700 Subject: sha1_name.c: many short names can only be committish We know that the token "$name" that appear in "$name^{commit}", "$name^4", "$name~4" etc. can only name a committish (either a commit or a tag that peels to a commit). Teach get_short_sha1() to take advantage of that knowledge when disambiguating an abbreviated SHA-1 given as an object name. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index 2d91dbd..1508fdb 100644 --- a/cache.h +++ b/cache.h @@ -813,6 +813,7 @@ struct object_context { #define GET_SHA1_QUIETLY 01 #define GET_SHA1_COMMIT 02 +#define GET_SHA1_COMMITTISH 04 extern int get_sha1(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); diff --git a/sha1_name.c b/sha1_name.c index 8feb9b5..215bb01 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -224,6 +224,24 @@ static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unu return kind == OBJ_COMMIT; } +static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data_unused) +{ + struct object *obj; + int kind; + + kind = sha1_object_info(sha1, NULL); + if (kind == OBJ_COMMIT) + return 1; + if (kind != OBJ_TAG) + return 0; + + /* We need to do this the hard way... */ + obj = deref_tag(lookup_object(sha1), NULL, 0); + if (obj && obj->type == OBJ_COMMIT) + return 1; + return 0; +} + static int get_short_sha1(const char *name, int len, unsigned char *sha1, unsigned flags) { @@ -261,6 +279,8 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, memset(&ds, 0, sizeof(ds)); if (flags & GET_SHA1_COMMIT) ds.fn = disambiguate_commit_only; + else if (flags & GET_SHA1_COMMITTISH) + ds.fn = disambiguate_committish_only; find_short_object_filename(len, hex_pfx, &ds); find_short_packed_object(len, bin_pfx, &ds); @@ -440,7 +460,7 @@ static int get_parent(const char *name, int len, unsigned char *result, int idx) { unsigned char sha1[20]; - int ret = get_sha1_1(name, len, sha1, 0); + int ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH); struct commit *commit; struct commit_list *p; @@ -473,7 +493,7 @@ static int get_nth_ancestor(const char *name, int len, struct commit *commit; int ret; - ret = get_sha1_1(name, len, sha1, 0); + ret = get_sha1_1(name, len, sha1, GET_SHA1_COMMITTISH); if (ret) return ret; commit = lookup_commit_reference(sha1); @@ -519,6 +539,7 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) unsigned char outer[20]; const char *sp; unsigned int expected_type = 0; + unsigned lookup_flags = 0; struct object *o; /* @@ -554,7 +575,10 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) else return -1; - if (get_sha1_1(name, sp - name - 2, outer, 0)) + if (expected_type == OBJ_COMMIT) + lookup_flags = GET_SHA1_COMMITTISH; + + if (get_sha1_1(name, sp - name - 2, outer, lookup_flags)) return -1; o = parse_object(outer); @@ -666,7 +690,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned l if (!ret) return 0; - return get_short_sha1(name, len, sha1, 0); + return get_short_sha1(name, len, sha1, lookup_flags); } /* diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 0d3f05b..7652f1c 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -85,7 +85,7 @@ test_expect_success 'first commit' ' git commit -m a2onsxbvj ' -test_expect_failure 'disambiguate commit-ish' ' +test_expect_success 'disambiguate commit-ish' ' # feed commit-ish in an unambiguous way git rev-parse --verify 0000000000e4f^{commit} && -- cgit v0.10.2-6-g49f6 From 33bd598c3902c40c5a10a68aeaa3004484239258 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 10:32:11 -0700 Subject: sha1_name.c: teach lookup context to get_sha1_with_context() The function takes user input string and returns the object name (binary SHA-1) with mode bits and path when the object was looked up in a tree. Additionally give hints to help disambiguation of abbreviated object names when the caller knows what it is looking for. Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 07bd984..c27268f 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -90,7 +90,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) unsigned long size; struct object_context obj_context; - if (get_sha1_with_context(obj_name, sha1, &obj_context)) + if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) die("Not a valid object name %s", obj_name); buf = NULL; diff --git a/cache.h b/cache.h index 1508fdb..e1fa63b 100644 --- a/cache.h +++ b/cache.h @@ -814,10 +814,11 @@ struct object_context { #define GET_SHA1_QUIETLY 01 #define GET_SHA1_COMMIT 02 #define GET_SHA1_COMMITTISH 04 +#define GET_SHA1_ONLY_TO_DIE 04000 extern int get_sha1(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); -extern int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc); +extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); /* * Try to read a SHA1 in hexadecimal format from the 40 characters diff --git a/revision.c b/revision.c index 86a14c8..7444f2e 100644 --- a/revision.c +++ b/revision.c @@ -1180,7 +1180,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, local_flags = UNINTERESTING; arg++; } - if (get_sha1_with_context(arg, sha1, &oc)) + if (get_sha1_with_context(arg, 0, sha1, &oc)) return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); @@ -1795,7 +1795,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s unsigned char sha1[20]; struct object *object; struct object_context oc; - if (get_sha1_with_context(revs->def, sha1, &oc)) + if (get_sha1_with_context(revs->def, 0, sha1, &oc)) die("bad default revision '%s'", revs->def); object = get_reference(revs, revs->def, sha1, 0); add_pending_object_with_mode(revs, object, revs->def, oc.mode); diff --git a/sha1_name.c b/sha1_name.c index 215bb01..c045be8 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -996,7 +996,7 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name) int get_sha1(const char *name, unsigned char *sha1) { struct object_context unused; - return get_sha1_with_context(name, sha1, &unused); + return get_sha1_with_context(name, 0, sha1, &unused); } /* Must be called only when object_name:filename doesn't exist. */ @@ -1112,20 +1112,24 @@ static char *resolve_relative_path(const char *rel) rel); } -static int get_sha1_with_context_1(const char *name, unsigned char *sha1, - struct object_context *oc, - int only_to_die, const char *prefix) +static int get_sha1_with_context_1(const char *name, + unsigned flags, + const char *prefix, + unsigned char *sha1, + struct object_context *oc) { int ret, bracket_depth; int namelen = strlen(name); const char *cp; + int only_to_die = flags & GET_SHA1_ONLY_TO_DIE; memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; - ret = get_sha1_1(name, namelen, sha1, 0); + ret = get_sha1_1(name, namelen, sha1, flags); if (!ret) return ret; - /* sha1:path --> object name of path in ent sha1 + /* + * sha1:path --> object name of path in ent sha1 * :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 @@ -1239,10 +1243,10 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix) { struct object_context oc; unsigned char sha1[20]; - get_sha1_with_context_1(name, sha1, &oc, 1, prefix); + get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc); } -int get_sha1_with_context(const char *str, unsigned char *sha1, struct object_context *orc) +int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc) { - return get_sha1_with_context_1(str, sha1, orc, 0, NULL); + return get_sha1_with_context_1(str, flags, NULL, sha1, orc); } -- cgit v0.10.2-6-g49f6 From cd74e4733db3e2353077bdc7021caa70bed2a483 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 12:04:52 -0700 Subject: sha1_name.c: introduce get_sha1_committish() Many callers know that the user meant to name a committish by syntactical positions where the object name appears. Calling this function allows the machinery to disambiguate shorter-than-unique abbreviated object names between committish and others. Note that this does NOT error out when the named object is not a committish. It is merely to give a hint to the disambiguation machinery. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index e1fa63b..e947b48 100644 --- a/cache.h +++ b/cache.h @@ -817,6 +817,7 @@ struct object_context { #define GET_SHA1_ONLY_TO_DIE 04000 extern int get_sha1(const char *str, unsigned char *sha1); +extern int get_sha1_committish(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); diff --git a/commit.c b/commit.c index 35af498..8b84eff 100644 --- a/commit.c +++ b/commit.c @@ -67,7 +67,7 @@ struct commit *lookup_commit_reference_by_name(const char *name) unsigned char sha1[20]; struct commit *commit; - if (get_sha1(name, sha1)) + if (get_sha1_committish(name, sha1)) return NULL; commit = lookup_commit_reference(sha1); if (!commit || parse_commit(commit)) diff --git a/revision.c b/revision.c index 7444f2e..c3160f2 100644 --- a/revision.c +++ b/revision.c @@ -979,7 +979,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags) flags ^= UNINTERESTING; arg++; } - if (get_sha1(arg, sha1)) + if (get_sha1_committish(arg, sha1)) return 0; while (1) { it = get_reference(revs, arg, sha1, 0); @@ -1120,8 +1120,8 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, next = "HEAD"; if (dotdot == arg) this = "HEAD"; - if (!get_sha1(this, from_sha1) && - !get_sha1(next, sha1)) { + if (!get_sha1_committish(this, from_sha1) && + !get_sha1_committish(next, sha1)) { struct commit *a, *b; struct commit_list *exclude; diff --git a/sha1_name.c b/sha1_name.c index c045be8..9e13d60 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -872,7 +872,7 @@ int get_sha1_mb(const char *name, unsigned char *sha1) struct strbuf sb; strbuf_init(&sb, dots - name); strbuf_add(&sb, name, dots - name); - st = get_sha1(sb.buf, sha1_tmp); + st = get_sha1_committish(sb.buf, sha1_tmp); strbuf_release(&sb); } if (st) @@ -881,7 +881,7 @@ int get_sha1_mb(const char *name, unsigned char *sha1) if (!one) return -1; - if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) + if (get_sha1_committish(dots[3] ? (dots + 3) : "HEAD", sha1_tmp)) return -1; two = lookup_commit_reference_gently(sha1_tmp, 0); if (!two) @@ -999,6 +999,23 @@ int get_sha1(const char *name, unsigned char *sha1) return get_sha1_with_context(name, 0, sha1, &unused); } +/* + * Many callers know that the user meant to name a committish by + * syntactical positions where the object name appears. Calling this + * function allows the machinery to disambiguate shorter-than-unique + * abbreviated object names between committish and others. + * + * Note that this does NOT error out when the named object is not a + * committish. It is merely to give a hint to the disambiguation + * machinery. + */ +int get_sha1_committish(const char *name, unsigned char *sha1) +{ + struct object_context unused; + return get_sha1_with_context(name, GET_SHA1_COMMITTISH, + sha1, &unused); +} + /* Must be called only when object_name:filename doesn't exist. */ static void diagnose_invalid_sha1_path(const char *prefix, const char *filename, diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 7652f1c..6f87d8e 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -102,7 +102,7 @@ test_expect_failure 'disambiguate commit' ' test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f) ' -test_expect_failure 'log name1..name2 takes only commit-ishes on both ends' ' +test_expect_success 'log name1..name2 takes only commit-ishes on both ends' ' git log 000000000..000000000 && git log ..000000000 && git log 000000000.. && -- cgit v0.10.2-6-g49f6 From 8e676e8ba567eccee1510b90ec2e0364dcc2f3b4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 12:33:52 -0700 Subject: revision.c: allow handle_revision_arg() to take other flags The existing "cant_be_filename" that tells the function that the caller knows the arg is not a path (hence it does not have to be checked for absense of the file whose name matches it) is made into a bit in the flag word. Signed-off-by: Junio C Hamano diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 0f2e7b8..48ccadd 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2290,7 +2290,7 @@ static void get_object_list(int ac, const char **av) } die("not a rev '%s'", line); } - if (handle_revision_arg(line, &revs, flags, 1)) + if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME)) die("bad revision '%s'", line); } diff --git a/revision.c b/revision.c index c3160f2..929497f 100644 --- a/revision.c +++ b/revision.c @@ -1093,9 +1093,7 @@ static void prepare_show_merge(struct rev_info *revs) revs->limited = 1; } -int handle_revision_arg(const char *arg_, struct rev_info *revs, - int flags, - int cant_be_filename) +int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt) { struct object_context oc; char *dotdot; @@ -1103,6 +1101,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, unsigned char sha1[20]; int local_flags; const char *arg = arg_; + int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME; dotdot = strstr(arg, ".."); if (dotdot) { @@ -1236,7 +1235,7 @@ static void read_revisions_from_stdin(struct rev_info *revs, } die("options not supported in --stdin mode"); } - if (handle_revision_arg(sb.buf, revs, 0, 1)) + if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME)) die("bad revision '%s'", sb.buf); } if (seen_dashdash) @@ -1684,7 +1683,7 @@ static int handle_revision_pseudo_opt(const char *submodule, */ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt) { - int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0; + int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt; struct cmdline_pathspec prune_data; const char *submodule = NULL; @@ -1708,6 +1707,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s /* Second, deal with arguments and options */ flags = 0; + revarg_opt = seen_dashdash ? REVARG_CANNOT_BE_FILENAME : 0; read_from_stdin = 0; for (left = i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1743,7 +1743,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s continue; } - if (handle_revision_arg(arg, revs, flags, seen_dashdash)) { + + if (handle_revision_arg(arg, revs, flags, revarg_opt)) { int j; if (seen_dashdash || *arg == '^') die("bad revision '%s'", arg); diff --git a/revision.h b/revision.h index b8e9223..8eceaec 100644 --- a/revision.h +++ b/revision.h @@ -190,7 +190,8 @@ extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, s extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]); -extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename); +#define REVARG_CANNOT_BE_FILENAME 01 +extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt); extern int prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); -- cgit v0.10.2-6-g49f6 From d5f6b1d756a29255efedee3cb6e8526aedcfeb00 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 12:43:05 -0700 Subject: revision.c: the "log" family, except for "show", takes committish Add a field to setup_revision_opt structure and allow these callers to tell the setup_revisions command parsing machinery that short SHA1 it encounters are meant to name committish. This step does not go all the way to connect the setup_revisions() to sha1_name.c yet. Signed-off-by: Junio C Hamano diff --git a/builtin/log.c b/builtin/log.c index 7d1f6f8..9363f39 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -363,6 +363,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) rev.simplify_history = 0; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; + opt.revarg_opt = REVARG_COMMITTISH; cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; @@ -543,6 +544,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; + opt.revarg_opt = REVARG_COMMITTISH; cmd_log_init(argc, argv, prefix, &rev, &opt); return cmd_log_walk(&rev); } @@ -1144,6 +1146,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.def = "HEAD"; + s_r_opt.revarg_opt = REVARG_COMMITTISH; if (default_attach) { rev.mime_boundary = default_attach; diff --git a/revision.c b/revision.c index 929497f..ec6f0c8 100644 --- a/revision.c +++ b/revision.c @@ -1102,6 +1102,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi int local_flags; const char *arg = arg_; int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME; + unsigned get_sha1_flags = 0; dotdot = strstr(arg, ".."); if (dotdot) { @@ -1179,7 +1180,11 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi local_flags = UNINTERESTING; arg++; } - if (get_sha1_with_context(arg, 0, sha1, &oc)) + + if (revarg_opt & REVARG_COMMITTISH) + get_sha1_flags = GET_SHA1_COMMITTISH; + + if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc)) return revs->ignore_missing ? 0 : -1; if (!cant_be_filename) verify_non_filename(revs->prefix, arg); @@ -1707,7 +1712,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s /* Second, deal with arguments and options */ flags = 0; - revarg_opt = seen_dashdash ? REVARG_CANNOT_BE_FILENAME : 0; + revarg_opt = opt ? opt->revarg_opt : 0; + if (seen_dashdash) + revarg_opt |= REVARG_CANNOT_BE_FILENAME; read_from_stdin = 0; for (left = i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/revision.h b/revision.h index 8eceaec..402f10d 100644 --- a/revision.h +++ b/revision.h @@ -183,6 +183,7 @@ struct setup_revision_opt { const char *def; void (*tweak)(struct rev_info *, struct setup_revision_opt *); const char *submodule; + unsigned revarg_opt; }; extern void init_revisions(struct rev_info *revs, const char *prefix); @@ -191,6 +192,7 @@ extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ct const struct option *options, const char * const usagestr[]); #define REVARG_CANNOT_BE_FILENAME 01 +#define REVARG_COMMITTISH 02 extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt); extern int prepare_revision_walk(struct rev_info *revs); diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 6f87d8e..da25fad 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -117,7 +117,7 @@ test_expect_failure 'rev-parse name1..name2 takes only commit-ishes on both ends git rev-parse 000000000.. ' -test_expect_failure 'git log takes only commit-ish' ' +test_expect_success 'git log takes only commit-ish' ' git log 000000000 ' -- cgit v0.10.2-6-g49f6 From daba53aeaf6565b4bbdd6783c659480453ec3c5e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 2 Jul 2012 23:35:05 -0700 Subject: sha1_name.c: add support for disambiguating other types This teaches the revision parser that in "$name:$path" (used for a blob object name), "$name" must be a tree-ish. There are many more places where we know what types of objects are called for. This patch adds support for "commit", "treeish", "tree", and "blob", which could be used in the following contexts: - "git apply --build-fake-ancestor" reads the "index" lines from the patch; they must name blob objects (not even "blob-ish"); - "git commit-tree" reads a tree object name (not "tree-ish"), and zero or more commit object names (not "committish"); - "git reset $rev" wants a committish; "git reset $rev -- $path" wants a treeish. They will come in later patches in the series. Signed-off-by: Junio C Hamano diff --git a/cache.h b/cache.h index e947b48..c8d6406 100644 --- a/cache.h +++ b/cache.h @@ -811,13 +811,20 @@ struct object_context { unsigned mode; }; -#define GET_SHA1_QUIETLY 01 -#define GET_SHA1_COMMIT 02 -#define GET_SHA1_COMMITTISH 04 +#define GET_SHA1_QUIETLY 01 +#define GET_SHA1_COMMIT 02 +#define GET_SHA1_COMMITTISH 04 +#define GET_SHA1_TREE 010 +#define GET_SHA1_TREEISH 020 +#define GET_SHA1_BLOB 040 #define GET_SHA1_ONLY_TO_DIE 04000 extern int get_sha1(const char *str, unsigned char *sha1); +extern int get_sha1_commit(const char *str, unsigned char *sha1); extern int get_sha1_committish(const char *str, unsigned char *sha1); +extern int get_sha1_tree(const char *str, unsigned char *sha1); +extern int get_sha1_treeish(const char *str, unsigned char *sha1); +extern int get_sha1_blob(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); diff --git a/sha1_name.c b/sha1_name.c index 9e13d60..18fac92 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -242,6 +242,36 @@ static int disambiguate_committish_only(const unsigned char *sha1, void *cb_data return 0; } +static int disambiguate_tree_only(const unsigned char *sha1, void *cb_data_unused) +{ + int kind = sha1_object_info(sha1, NULL); + return kind == OBJ_TREE; +} + +static int disambiguate_treeish_only(const unsigned char *sha1, void *cb_data_unused) +{ + struct object *obj; + int kind; + + kind = sha1_object_info(sha1, NULL); + if (kind == OBJ_TREE || kind == OBJ_COMMIT) + return 1; + if (kind != OBJ_TAG) + return 0; + + /* We need to do this the hard way... */ + obj = deref_tag(lookup_object(sha1), NULL, 0); + if (obj && (obj->type == OBJ_TREE || obj->type == OBJ_COMMIT)) + return 1; + return 0; +} + +static int disambiguate_blob_only(const unsigned char *sha1, void *cb_data_unused) +{ + int kind = sha1_object_info(sha1, NULL); + return kind == OBJ_BLOB; +} + static int get_short_sha1(const char *name, int len, unsigned char *sha1, unsigned flags) { @@ -281,6 +311,12 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, ds.fn = disambiguate_commit_only; else if (flags & GET_SHA1_COMMITTISH) ds.fn = disambiguate_committish_only; + else if (flags & GET_SHA1_TREE) + ds.fn = disambiguate_tree_only; + else if (flags & GET_SHA1_TREEISH) + ds.fn = disambiguate_treeish_only; + else if (flags & GET_SHA1_BLOB) + ds.fn = disambiguate_blob_only; find_short_object_filename(len, hex_pfx, &ds); find_short_packed_object(len, bin_pfx, &ds); @@ -1016,6 +1052,34 @@ int get_sha1_committish(const char *name, unsigned char *sha1) sha1, &unused); } +int get_sha1_treeish(const char *name, unsigned char *sha1) +{ + struct object_context unused; + return get_sha1_with_context(name, GET_SHA1_TREEISH, + sha1, &unused); +} + +int get_sha1_commit(const char *name, unsigned char *sha1) +{ + struct object_context unused; + return get_sha1_with_context(name, GET_SHA1_COMMIT, + sha1, &unused); +} + +int get_sha1_tree(const char *name, unsigned char *sha1) +{ + struct object_context unused; + return get_sha1_with_context(name, GET_SHA1_TREE, + sha1, &unused); +} + +int get_sha1_blob(const char *name, unsigned char *sha1) +{ + struct object_context unused; + return get_sha1_with_context(name, GET_SHA1_BLOB, + sha1, &unused); +} + /* Must be called only when object_name:filename doesn't exist. */ static void diagnose_invalid_sha1_path(const char *prefix, const char *filename, @@ -1221,7 +1285,7 @@ static int get_sha1_with_context_1(const char *name, strncpy(object_name, name, cp-name); object_name[cp-name] = '\0'; } - if (!get_sha1_1(name, cp-name, tree_sha1, 0)) { + if (!get_sha1_1(name, cp-name, tree_sha1, GET_SHA1_TREEISH)) { const char *filename = cp+1; char *new_filename = NULL; diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index da25fad..fbd3658 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -45,7 +45,7 @@ test_expect_success 'warn ambiguity when no candidate matches type hint' ' grep "short SHA1 000000000 is ambiguous" actual ' -test_expect_failure 'disambiguate tree-ish' ' +test_expect_success 'disambiguate tree-ish' ' # feed tree-ish in an unambiguous way git rev-parse --verify 0000000000cdc:a0blgqsjc && -- cgit v0.10.2-6-g49f6 From da3ac0c14993c5e8e41875cc3ead32be71647a7a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 10:01:22 -0700 Subject: apply: --build-fake-ancestor expects blobs The "index" line read from the patch to reconstruct a partial preimage tree records the object names of blob objects. Signed-off-by: Junio C Hamano diff --git a/builtin/apply.c b/builtin/apply.c index c24dc54..8b2db1d 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -3203,7 +3203,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename) name = patch->old_name ? patch->old_name : patch->new_name; if (0 < patch->is_new) continue; - else if (get_sha1(patch->old_sha1_prefix, sha1)) + else if (get_sha1_blob(patch->old_sha1_prefix, sha1)) /* git diff has no index line for mode/type changes */ if (!patch->lines_added && !patch->lines_deleted) { if (get_current_sha1(patch->old_name, sha1)) diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index fbd3658..84b8ddd 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -54,7 +54,7 @@ test_expect_success 'disambiguate tree-ish' ' git rev-parse --verify 000000000:a0blgqsjc ' -test_expect_failure 'disambiguate blob' ' +test_expect_success 'disambiguate blob' ' sed -e "s/|$//" >patch <<-EOF && diff --git a/frotz b/frotz index 000000000..ffffff 100644 -- cgit v0.10.2-6-g49f6 From 75f5ac04a2984fcf1e4d167047bfb63c5f385d44 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 10:03:38 -0700 Subject: commit-tree: the command wants a tree and commits Signed-off-by: Junio C Hamano diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 164b655..cb982c5 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -48,8 +48,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (argc < 2 || !strcmp(argv[1], "-h")) usage(commit_tree_usage); - if (get_sha1(argv[1], tree_sha1)) - die("Not a valid object name %s", argv[1]); + if (get_sha1_tree(argv[1], tree_sha1)) + die("Not a valid tree object name %s", argv[1]); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -57,7 +57,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; if (argc <= ++i) usage(commit_tree_usage); - if (get_sha1(argv[i], sha1)) + if (get_sha1_commit(argv[i], sha1)) die("Not a valid object name %s", argv[i]); assert_sha1_type(sha1, OBJ_COMMIT); new_parent(lookup_commit(sha1), &parents); @@ -104,7 +104,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) continue; } - if (get_sha1(arg, tree_sha1)) + if (get_sha1_tree(arg, tree_sha1)) die("Not a valid object name %s", arg); if (got_tree) die("Cannot give more than one trees"); diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 84b8ddd..dc56f81 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -75,7 +75,7 @@ test_expect_success 'disambiguate blob' ' test_cmp a0blgqsjc actual ' -test_expect_failure 'disambiguate tree' ' +test_expect_success 'disambiguate tree' ' commit=$(echo "d7xm" | git commit-tree 000000000) && test $(git rev-parse $commit^{tree}) = $(git rev-parse 0000000000cdc) ' @@ -97,7 +97,7 @@ test_expect_success 'disambiguate commit-ish' ' git rev-parse --verify 000000000^0 ' -test_expect_failure 'disambiguate commit' ' +test_expect_success 'disambiguate commit' ' commit=$(echo "j9xqh" | git commit-tree 0000000000cdc -p 000000000) && test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f) ' -- cgit v0.10.2-6-g49f6 From 13243c2c7a758bb6510ba26d0372f384b5f422ce Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 10:04:22 -0700 Subject: reset: the command takes committish This is not strictly correct, in that resetting selected index entries from corresponding paths out of a given tree without moving HEAD is a valid operation, and in such case a tree-ish would suffice. But the existing code already requires a committish in the codepath, so let's be consistent with it for now. Signed-off-by: Junio C Hamano diff --git a/builtin/reset.c b/builtin/reset.c index 8c2c1d5..392fb63 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -276,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) * Otherwise, argv[i] could be either or and * has to be unambiguous. */ - else if (!get_sha1(argv[i], sha1)) { + else if (!get_sha1_committish(argv[i], sha1)) { /* * Ok, argv[i] looks like a rev; it should not * be a filename. @@ -289,9 +289,15 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } } - if (get_sha1(rev, sha1)) + if (get_sha1_committish(rev, sha1)) die(_("Failed to resolve '%s' as a valid ref."), rev); + /* + * NOTE: As "git reset $treeish -- $path" should be usable on + * any tree-ish, this is not strictly correct. We are not + * moving the HEAD to any commit; we are merely resetting the + * entries in the index to that of a treeish. + */ commit = lookup_commit_reference(sha1); if (!commit) die(_("Could not parse object '%s'."), rev); diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index dc56f81..0210998 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -121,7 +121,7 @@ test_expect_success 'git log takes only commit-ish' ' git log 000000000 ' -test_expect_failure 'git reset takes only commit-ish' ' +test_expect_success 'git reset takes only commit-ish' ' git reset 000000000 ' -- cgit v0.10.2-6-g49f6 From c036c4c5e426cc7ee8070038607edf06ef1d661e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 13:45:12 -0700 Subject: rev-parse: A and B in "rev-parse A..B" refer to committish Signed-off-by: Junio C Hamano diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 98d1cbe..16b98b5 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -238,7 +238,7 @@ static int try_difference(const char *arg) next = "HEAD"; if (dotdot == arg) this = "HEAD"; - if (!get_sha1(this, sha1) && !get_sha1(next, end)) { + if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) { show_rev(NORMAL, end, next); show_rev(symmetric ? NORMAL : REVERSED, sha1, this); if (symmetric) { @@ -278,7 +278,7 @@ static int try_parent_shorthands(const char *arg) return 0; *dotdot = 0; - if (get_sha1(arg, sha1)) + if (get_sha1_committish(arg, sha1)) return 0; if (!parents_only) diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 0210998..6de3cb0 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -111,7 +111,7 @@ test_expect_success 'log name1..name2 takes only commit-ishes on both ends' ' git log 000000000... ' -test_expect_failure 'rev-parse name1..name2 takes only commit-ishes on both ends' ' +test_expect_success 'rev-parse name1..name2 takes only commit-ishes on both ends' ' git rev-parse 000000000..000000000 && git rev-parse ..000000000 && git rev-parse 000000000.. -- cgit v0.10.2-6-g49f6 From 957d74062c1f0e92368aeda335e27d2d61f584f6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 3 Jul 2012 14:21:59 -0700 Subject: rev-parse --disambiguate= The new option allows you to feed an ambiguous prefix and enumerate all the objects that share it as a prefix of their object names. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 8023dc0..8d90863 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -101,6 +101,12 @@ OPTIONS The option core.warnAmbiguousRefs is used to select the strict abbreviation mode. +--disambiguate=:: + Show every object whose name begins with the given prefix. + The must be at least 4 hexadecimal digits long to + avoid listing each and every object in the repository by + mistake. + --all:: Show all refs found in `refs/`. diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 16b98b5..d85b8a6 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -195,6 +195,12 @@ static int anti_reference(const char *refname, const unsigned char *sha1, int fl return 0; } +static int show_abbrev(const unsigned char *sha1, void *cb_data) +{ + show_rev(NORMAL, sha1, NULL); + return 0; +} + static void show_datestring(const char *flag, const char *datestr) { static char buffer[100]; @@ -589,6 +595,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for_each_ref(show_reference, NULL); continue; } + if (!prefixcmp(arg, "--disambiguate=")) { + for_each_abbrev(arg + 15, show_abbrev, NULL); + continue; + } if (!strcmp(arg, "--bisect")) { for_each_ref_in("refs/bisect/bad", show_reference, NULL); for_each_ref_in("refs/bisect/good", anti_reference, NULL); diff --git a/cache.h b/cache.h index c8d6406..6338878 100644 --- a/cache.h +++ b/cache.h @@ -828,6 +828,9 @@ extern int get_sha1_blob(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); +typedef int each_abbrev_fn(const unsigned char *sha1, void *); +extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *); + /* * Try to read a SHA1 in hexadecimal format from the 40 characters * starting at hex. Write the 20-byte result to sha1 in binary form. diff --git a/sha1_name.c b/sha1_name.c index 18fac92..8bc20c5 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -20,10 +20,15 @@ struct disambiguate_state { unsigned candidate_ok:1; unsigned disambiguate_fn_used:1; unsigned ambiguous:1; + unsigned always_call_fn:1; }; static void update_candidates(struct disambiguate_state *ds, const unsigned char *current) { + if (ds->always_call_fn) { + ds->ambiguous = ds->fn(current, ds->cb_data) ? 1 : 0; + return; + } if (!ds->candidate_exists) { /* this is the first candidate */ hashcpy(ds->candidate, current); @@ -272,17 +277,12 @@ static int disambiguate_blob_only(const unsigned char *sha1, void *cb_data_unuse return kind == OBJ_BLOB; } -static int get_short_sha1(const char *name, int len, unsigned char *sha1, - unsigned flags) +static int prepare_prefixes(const char *name, int len, + unsigned char *bin_pfx, + char *hex_pfx) { - int i, status; - char hex_pfx[40]; - unsigned char bin_pfx[20]; - struct disambiguate_state ds; - int quietly = !!(flags & GET_SHA1_QUIETLY); + int i; - if (len < MINIMUM_ABBREV || len > 40) - return -1; hashclr(bin_pfx); memset(hex_pfx, 'x', 40); for (i = 0; i < len ;i++) { @@ -303,6 +303,22 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, val <<= 4; bin_pfx[i >> 1] |= val; } + return 0; +} + +static int get_short_sha1(const char *name, int len, unsigned char *sha1, + unsigned flags) +{ + int status; + char hex_pfx[40]; + unsigned char bin_pfx[20]; + struct disambiguate_state ds; + int quietly = !!(flags & GET_SHA1_QUIETLY); + + if (len < MINIMUM_ABBREV || len > 40) + return -1; + if (prepare_prefixes(name, len, bin_pfx, hex_pfx) < 0) + return -1; prepare_alt_odb(); @@ -327,6 +343,31 @@ static int get_short_sha1(const char *name, int len, unsigned char *sha1, return status; } + +int for_each_abbrev(const char *prefix, each_abbrev_fn fn, void *cb_data) +{ + char hex_pfx[40]; + unsigned char bin_pfx[20]; + struct disambiguate_state ds; + int len = strlen(prefix); + + if (len < MINIMUM_ABBREV || len > 40) + return -1; + if (prepare_prefixes(prefix, len, bin_pfx, hex_pfx) < 0) + return -1; + + prepare_alt_odb(); + + memset(&ds, 0, sizeof(ds)); + ds.always_call_fn = 1; + ds.cb_data = cb_data; + ds.fn = fn; + + find_short_object_filename(len, hex_pfx, &ds); + find_short_packed_object(len, bin_pfx, &ds); + return ds.ambiguous; +} + const char *find_unique_abbrev(const unsigned char *sha1, int len) { int status, exists; diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 6de3cb0..3ed7558 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -252,4 +252,13 @@ test_expect_success 'ambiguous commit-ish' ' test_must_fail git log 000000000... ' +test_expect_success 'rev-parse --disambiguate' ' + # The test creates 16 objects that share the prefix and two + # commits created by commit-tree in earlier tests do not share + # the prefix. + git rev-parse --disambiguate=000000000 >actual && + test "$(wc -l Date: Wed, 11 Jul 2012 16:30:28 -0700 Subject: t1512: ignore whitespaces in wc -l output Some implementations of sed (e.g. MacOS X) have whitespaces in the output of "wc -l" that reads from the standard input. Ignore these whitespaces by not quoting the command substitution to be compared with the constant "16". Signed-off-by: Junio C Hamano diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 3ed7558..1eb3514 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -257,7 +257,7 @@ test_expect_success 'rev-parse --disambiguate' ' # commits created by commit-tree in earlier tests do not share # the prefix. git rev-parse --disambiguate=000000000 >actual && - test "$(wc -l Date: Fri, 13 Jul 2012 12:43:43 -0700 Subject: t1512: match the "other" object names The test creates 16 objects that share the same prefix, and two other objects that do not. Tweak the test so that the other two share the same prefix that is different from the one that is shared by the 16. Signed-off-by: Junio C Hamano diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 1eb3514..6b3d797 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -98,7 +98,7 @@ test_expect_success 'disambiguate commit-ish' ' ' test_expect_success 'disambiguate commit' ' - commit=$(echo "j9xqh" | git commit-tree 0000000000cdc -p 000000000) && + commit=$(echo "hoaxj" | git commit-tree 0000000000cdc -p 000000000) && test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f) ' @@ -254,8 +254,8 @@ test_expect_success 'ambiguous commit-ish' ' test_expect_success 'rev-parse --disambiguate' ' # The test creates 16 objects that share the prefix and two - # commits created by commit-tree in earlier tests do not share - # the prefix. + # commits created by commit-tree in earlier tests share a + # different prefix. git rev-parse --disambiguate=000000000 >actual && test $(wc -l