mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
Merge branch 'jc/sha1-name-more'
Teaches the object name parser things like a "git describe" output is always a commit object, "A" in "git log A" must be a committish, and "A" and "B" in "git log A...B" both must be committish, etc., to prolong the lifetime of abbreviated object names. * jc/sha1-name-more: (27 commits) t1512: match the "other" object names t1512: ignore whitespaces in wc -l output rev-parse --disambiguate=<prefix> rev-parse: A and B in "rev-parse A..B" refer to committish reset: the command takes committish commit-tree: the command wants a tree and commits apply: --build-fake-ancestor expects blobs sha1_name.c: add support for disambiguating other types revision.c: the "log" family, except for "show", takes committish revision.c: allow handle_revision_arg() to take other flags sha1_name.c: introduce get_sha1_committish() sha1_name.c: teach lookup context to get_sha1_with_context() sha1_name.c: many short names can only be committish sha1_name.c: get_sha1_1() takes lookup flags sha1_name.c: get_describe_name() by definition groks only commits sha1_name.c: teach get_short_sha1() a commit-only option sha1_name.c: allow get_short_sha1() to take other flags get_sha1(): fix error status regression sha1_name.c: restructure disambiguation of short names sha1_name.c: correct misnamed "canonical" and "res" ...
This commit is contained in:
commit
0958a24d73
@ -101,6 +101,12 @@ OPTIONS
|
||||
The option core.warnAmbiguousRefs is used to select the strict
|
||||
abbreviation mode.
|
||||
|
||||
--disambiguate=<prefix>::
|
||||
Show every object whose name begins with the given prefix.
|
||||
The <prefix> 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/`.
|
||||
|
||||
|
@ -3589,7 +3589,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))
|
||||
|
@ -91,7 +91,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;
|
||||
|
@ -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");
|
||||
|
@ -367,6 +367,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;
|
||||
@ -557,6 +558,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);
|
||||
}
|
||||
@ -1132,6 +1134,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;
|
||||
|
@ -2373,7 +2373,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);
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
* Otherwise, argv[i] could be either <rev> or <paths> 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);
|
||||
|
@ -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];
|
||||
@ -238,7 +244,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 +284,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)
|
||||
@ -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);
|
||||
|
28
cache.h
28
cache.h
@ -790,17 +790,25 @@ struct object_context {
|
||||
unsigned mode;
|
||||
};
|
||||
|
||||
#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_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_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_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);
|
||||
|
||||
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
|
||||
|
2
commit.c
2
commit.c
@ -68,7 +68,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))
|
||||
|
38
revision.c
38
revision.c
@ -1000,7 +1000,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);
|
||||
@ -1114,16 +1114,16 @@ 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)
|
||||
{
|
||||
unsigned mode;
|
||||
struct object_context oc;
|
||||
char *dotdot;
|
||||
struct object *object;
|
||||
unsigned char sha1[20];
|
||||
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) {
|
||||
@ -1141,8 +1141,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;
|
||||
|
||||
@ -1201,13 +1201,17 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs,
|
||||
local_flags = UNINTERESTING;
|
||||
arg++;
|
||||
}
|
||||
if (get_sha1_with_mode(arg, sha1, &mode))
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1257,7 +1261,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)
|
||||
@ -1708,7 +1712,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;
|
||||
|
||||
@ -1736,6 +1740,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 = 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];
|
||||
@ -1771,7 +1778,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);
|
||||
@ -1822,11 +1830,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, 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, mode);
|
||||
add_pending_object_with_mode(revs, object, revs->def, oc.mode);
|
||||
}
|
||||
|
||||
/* Did the user ask for any diff output? Run the diff! */
|
||||
|
@ -184,6 +184,7 @@ struct setup_revision_opt {
|
||||
void (*tweak)(struct rev_info *, struct setup_revision_opt *);
|
||||
const char *submodule;
|
||||
int assume_dashdash;
|
||||
unsigned revarg_opt;
|
||||
};
|
||||
|
||||
extern void init_revisions(struct rev_info *revs, const char *prefix);
|
||||
@ -191,7 +192,9 @@ 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
|
||||
#define REVARG_COMMITTISH 02
|
||||
extern int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt);
|
||||
|
||||
extern void reset_revision_walk(void);
|
||||
extern int prepare_revision_walk(struct rev_info *revs);
|
||||
|
8
setup.c
8
setup.c
@ -77,9 +77,6 @@ static void NORETURN die_verify_filename(const char *prefix,
|
||||
const char *arg,
|
||||
int diagnose_misspelt_rev)
|
||||
{
|
||||
unsigned char sha1[20];
|
||||
unsigned mode;
|
||||
|
||||
if (!diagnose_misspelt_rev)
|
||||
die("%s: no such path in the working tree.\n"
|
||||
"Use '-- <path>...' to specify paths that do not exist locally.",
|
||||
@ -88,11 +85,10 @@ static void NORETURN die_verify_filename(const char *prefix,
|
||||
* 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"
|
||||
|
496
sha1_name.c
496
sha1_name.c
@ -9,14 +9,82 @@
|
||||
|
||||
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)
|
||||
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;
|
||||
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);
|
||||
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) {
|
||||
/*
|
||||
* 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;
|
||||
@ -27,33 +95,28 @@ static int find_short_object_filename(int len, const char *name, unsigned char *
|
||||
}
|
||||
fakeent->next = alt_odb_list;
|
||||
|
||||
sprintf(hex, "%.2s", name);
|
||||
for (alt = fakeent; alt && found < 2; alt = alt->next) {
|
||||
sprintf(hex, "%.2s", hex_pfx);
|
||||
for (alt = fakeent; alt && !ds->ambiguous; 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) {
|
||||
|
||||
while (!ds->ambiguous && (de = readdir(dir)) != NULL) {
|
||||
unsigned char sha1[20];
|
||||
|
||||
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);
|
||||
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)
|
||||
@ -71,103 +134,157 @@ static int match_sha(unsigned len, const unsigned char *a, const unsigned char *
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
|
||||
static void unique_in_pack(int len,
|
||||
const unsigned char *bin_pfx,
|
||||
struct packed_git *p,
|
||||
struct disambiguate_state *ds)
|
||||
{
|
||||
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(bin_pfx, 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 "bin_pfx". See if we have
|
||||
* 0, 1 or more objects that actually match(es).
|
||||
*/
|
||||
for (i = first; i < num && !ds->ambiguous; i++) {
|
||||
current = nth_packed_object_sha1(p, i);
|
||||
if (!match_sha(len, bin_pfx, current))
|
||||
break;
|
||||
update_candidates(ds, current);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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 *now;
|
||||
int cmp;
|
||||
|
||||
now = nth_packed_object_sha1(p, mid);
|
||||
cmp = hashcmp(match, now);
|
||||
if (!cmp) {
|
||||
first = mid;
|
||||
break;
|
||||
}
|
||||
if (cmp > 0) {
|
||||
first = mid+1;
|
||||
continue;
|
||||
}
|
||||
last = mid;
|
||||
}
|
||||
if (first < num) {
|
||||
const unsigned char *now, *next;
|
||||
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)) {
|
||||
/* unique within this pack */
|
||||
if (!found) {
|
||||
found_sha1 = now;
|
||||
found++;
|
||||
}
|
||||
else if (hashcmp(found_sha1, now)) {
|
||||
found = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* not even unique within this pack */
|
||||
found = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 *canonical,
|
||||
unsigned char *res, 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, canonical, unpacked_sha1);
|
||||
has_packed = find_short_packed_object(len, res, packed_sha1);
|
||||
if (!has_unpacked && !has_packed)
|
||||
if (!ds->candidate_exists)
|
||||
return SHORT_NAME_NOT_FOUND;
|
||||
if (1 < has_unpacked || 1 < has_packed)
|
||||
|
||||
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_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);
|
||||
|
||||
hashcpy(sha1, ds->candidate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_short_sha1(const char *name, int len, unsigned char *sha1,
|
||||
int quietly)
|
||||
static int disambiguate_commit_only(const unsigned char *sha1, void *cb_data_unused)
|
||||
{
|
||||
int i, status;
|
||||
char canonical[40];
|
||||
unsigned char res[20];
|
||||
int kind = sha1_object_info(sha1, NULL);
|
||||
return kind == OBJ_COMMIT;
|
||||
}
|
||||
|
||||
if (len < MINIMUM_ABBREV || len > 40)
|
||||
return -1;
|
||||
hashclr(res);
|
||||
memset(canonical, 'x', 40);
|
||||
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 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 prepare_prefixes(const char *name, int len,
|
||||
unsigned char *bin_pfx,
|
||||
char *hex_pfx)
|
||||
{
|
||||
int i;
|
||||
|
||||
hashclr(bin_pfx);
|
||||
memset(hex_pfx, 'x', 40);
|
||||
for (i = 0; i < len ;i++) {
|
||||
unsigned char c = name[i];
|
||||
unsigned char val;
|
||||
@ -181,18 +298,76 @@ 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;
|
||||
}
|
||||
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();
|
||||
|
||||
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;
|
||||
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);
|
||||
status = finish_object_disambiguation(&ds, sha1);
|
||||
|
||||
status = find_unique_short_object(i, canonical, res, 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;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
@ -204,7 +379,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) {
|
||||
@ -255,7 +430,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)
|
||||
{
|
||||
@ -292,7 +467,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;
|
||||
}
|
||||
@ -362,7 +537,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, GET_SHA1_COMMITTISH);
|
||||
struct commit *commit;
|
||||
struct commit_list *p;
|
||||
|
||||
@ -395,7 +570,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, GET_SHA1_COMMITTISH);
|
||||
if (ret)
|
||||
return ret;
|
||||
commit = lookup_commit_reference(sha1);
|
||||
@ -441,6 +616,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;
|
||||
|
||||
/*
|
||||
@ -476,7 +652,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))
|
||||
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);
|
||||
@ -525,6 +704,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;
|
||||
@ -535,14 +715,14 @@ 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, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
@ -587,7 +767,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
return get_short_sha1(name, len, sha1, 0);
|
||||
return get_short_sha1(name, len, sha1, lookup_flags);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -769,7 +949,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)
|
||||
@ -778,7 +958,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)
|
||||
@ -905,7 +1085,52 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
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. */
|
||||
@ -1004,16 +1229,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, "../"))
|
||||
@ -1031,20 +1246,24 @@ 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 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);
|
||||
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
|
||||
@ -1119,7 +1338,7 @@ 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, GET_SHA1_TREEISH)) {
|
||||
const char *filename = cp+1;
|
||||
char *new_filename = NULL;
|
||||
|
||||
@ -1146,3 +1365,22 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1,
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
unsigned char sha1[20];
|
||||
get_sha1_with_context_1(name, GET_SHA1_ONLY_TO_DIE, prefix, sha1, &oc);
|
||||
}
|
||||
|
||||
int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)
|
||||
{
|
||||
return get_sha1_with_context_1(str, flags, NULL, sha1, orc);
|
||||
}
|
||||
|
264
t/t1512-rev-parse-disambiguation.sh
Executable file
264
t/t1512-rev-parse-disambiguation.sh
Executable file
@ -0,0 +1,264 @@
|
||||
#!/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_success '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_success '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_success '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_success '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_success 'disambiguate commit' '
|
||||
commit=$(echo "hoaxj" | git commit-tree 0000000000cdc -p 000000000) &&
|
||||
test $(git rev-parse $commit^) = $(git rev-parse 0000000000e4f)
|
||||
'
|
||||
|
||||
test_expect_success '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_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..
|
||||
'
|
||||
|
||||
test_expect_success 'git log takes only commit-ish' '
|
||||
git log 000000000
|
||||
'
|
||||
|
||||
test_expect_success '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_expect_success 'rev-parse --disambiguate' '
|
||||
# The test creates 16 objects that share the prefix and two
|
||||
# commits created by commit-tree in earlier tests share a
|
||||
# different prefix.
|
||||
git rev-parse --disambiguate=000000000 >actual &&
|
||||
test $(wc -l <actual) = 16 &&
|
||||
test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user