From 6677c4665af2d73f670bec382bc82d0f2e9513fb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Dec 2005 12:54:00 -0800 Subject: get_sha1_basic(): corner case ambiguity fix When .git/refs/heads/frotz and .git/refs/tags/frotz existed, and the object name stored in .git/refs/heads/frotz were corrupt, we ended up picking tags/frotz without complaining. Worse yet, if the corrupt .git/refs/heads/frotz was more than 40 bytes and began with hexadecimal characters, it silently overwritten the initial part of the returned result. This commit adds a couple of tests to demonstrate these cases, with a fix. Signed-off-by: Junio C Hamano --- sha1_name.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) (limited to 'sha1_name.c') diff --git a/sha1_name.c b/sha1_name.c index faac158b16..bf8f0f0e1f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -203,11 +203,12 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) return NULL; } -static int ambiguous_path(const char *path) +static int ambiguous_path(const char *path, int len) { int slash = 1; + int cnt; - for (;;) { + for (cnt = 0; cnt < len; cnt++) { switch (*path++) { case '\0': break; @@ -224,6 +225,7 @@ static int ambiguous_path(const char *path) } return slash; } + return slash; } static int get_sha1_basic(const char *str, int len, unsigned char *sha1) @@ -242,26 +244,41 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return 0; /* Accept only unambiguous ref paths. */ - if (ambiguous_path(str)) + if (ambiguous_path(str, len)) return -1; for (p = prefix; *p; p++) { char *pathname = git_path("%s/%.*s", *p, len, str); + if (!read_ref(pathname, sha1)) { /* Must be unique; i.e. when heads/foo and * tags/foo are both present, reject "foo". - * Note that read_ref() eventually calls - * get_sha1_hex() which can smudge initial - * part of the buffer even if what is read - * is found to be invalid halfway. */ if (1 < found++) return -1; } + + /* We want to allow .git/description file and + * "description" branch to exist at the same time. + * "git-rev-parse description" should silently skip + * .git/description file as a candidate for + * get_sha1(). However, having garbage file anywhere + * under refs/ is not OK, and we would not have caught + * ambiguous heads and tags with the above test. + */ + else if (**p && !access(pathname, F_OK)) { + /* Garbage exists under .git/refs */ + return error("garbage ref found '%s'", pathname); + } } - if (found == 1) + switch (found) { + case 0: + return -1; + case 1: return 0; - return -1; + default: + return error("ambiguous refname '%.*s'", len, str); + } } static int get_sha1_1(const char *name, int len, unsigned char *sha1); -- cgit v1.3-6-g1900 From 011fbc7f07fdf82cf922d1eb269bb4dac3e0cc40 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 16 Dec 2005 23:19:14 -0800 Subject: Remove misguided branch disambiguation. This removes the misguided attempt to refuse processing a branch name xyzzy and insist it to be given as either heads/xyzzy or tags/xyzzy when a tag xyzzy exists. There was no reason to do so --- the search order was predictable and well defined, so if the user says xyzzy we should have taken the tag xyzzy in such a case without complaining. This incidentally fixes another subtle bug related to this. If such a duplicate branch/tag name happened to be a unique valid prefix of an existing commit object name (say, "beef"), we did not take the tag "beef" but after complaining used the commit object whose name started with beef. Another problem this fixes while introducing some confusion is that there is no longer a reason to forbid a branch name HEAD anymore. In other words, now "git pull . ref1:HEAD" would work as expected, once we revert "We do not like HEAD branch" patch. It creates "HEAD" branch under ${GIT_DIR-.git}/refs/heads (or fast-forwards if already exists) using the tip of ref1 branch from the current repository, and merges it into the current branch. Signed-off-by: Junio C Hamano --- sha1_name.c | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) (limited to 'sha1_name.c') diff --git a/sha1_name.c b/sha1_name.c index bf8f0f0e1f..49e2cc394f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -238,7 +238,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) NULL }; const char **p; - int found = 0; if (len == 40 && !get_sha1_hex(str, sha1)) return 0; @@ -249,36 +248,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) for (p = prefix; *p; p++) { char *pathname = git_path("%s/%.*s", *p, len, str); - - if (!read_ref(pathname, sha1)) { - /* Must be unique; i.e. when heads/foo and - * tags/foo are both present, reject "foo". - */ - if (1 < found++) - return -1; - } - - /* We want to allow .git/description file and - * "description" branch to exist at the same time. - * "git-rev-parse description" should silently skip - * .git/description file as a candidate for - * get_sha1(). However, having garbage file anywhere - * under refs/ is not OK, and we would not have caught - * ambiguous heads and tags with the above test. - */ - else if (**p && !access(pathname, F_OK)) { - /* Garbage exists under .git/refs */ - return error("garbage ref found '%s'", pathname); - } - } - switch (found) { - case 0: - return -1; - case 1: - return 0; - default: - return error("ambiguous refname '%.*s'", len, str); + if (!read_ref(pathname, sha1)) + return 0; } + return -1; } static int get_sha1_1(const char *name, int len, unsigned char *sha1); -- cgit v1.3-6-g1900 From c054d64e8783e5ac2fa68c382f00df9087bca0f9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Dec 2005 00:00:50 -0800 Subject: Revert "get_sha1_basic(): corner case ambiguity fix" This reverts 6677c4665af2d73f670bec382bc82d0f2e9513fb commit. The misguided disambiguation has been reverted, so there is no point testing that misfeature. --- sha1_name.c | 2 +- t/t0000-basic.sh | 48 ------------------------------------------------ 2 files changed, 1 insertion(+), 49 deletions(-) (limited to 'sha1_name.c') diff --git a/sha1_name.c b/sha1_name.c index 49e2cc394f..67b69a54fb 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -223,7 +223,7 @@ static int ambiguous_path(const char *path, int len) slash = 0; continue; } - return slash; + break; } return slash; } diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index ffa723ea8b..bc3e711a52 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -205,52 +205,4 @@ test_expect_success \ 'no diff after checkout and git-update-index --refresh.' \ 'git-diff-files >current && cmp -s current /dev/null' - -# extended sha1 parsing and ambiguity resolution - -GIT_AUTHOR_DATE='1995-01-29T16:00:00 -0800' -GIT_AUTHOR_EMAIL=a.u.thor@example.com -GIT_AUTHOR_NAME='A U Thor' -GIT_COMMITTER_DATE='1995-01-29T16:00:00 -0800' -GIT_COMMITTER_EMAIL=c.o.mmitter@example.com -GIT_COMMITTER_NAME='C O Mmitter' -export GIT_AUTHOR_DATE -export GIT_AUTHOR_EMAIL -export GIT_AUTHOR_NAME -export GIT_COMMITTER_DATE -export GIT_COMMITTER_EMAIL -export GIT_COMMITTER_NAME - -test_expect_success \ - 'initial commit.' \ - 'commit=$(echo Initial commit | git-commit-tree $tree) && - echo "$commit" >.git/refs/heads/master && - git-ls-tree HEAD && - test "$commit" = 51a092e9ef6cbbe66d258acd17599d3f80be6162' - -test_expect_success \ - 'Ambiguous' \ - 'echo "$commit" >.git/refs/heads/nasty && - echo "$commit" >.git/refs/tags/nasty && - if git-rev-parse --verify nasty - then - echo "should have barfed" - false - else - : - fi && - # names directly underneath .git/ should not interfere - echo "$commit" >.git/refs/heads/description && - git-rev-parse --verify description && - # broken object name - echo fffffffffffffffffffffffffffffffffffffffg \ - >.git/refs/heads/nasty && - if git-rev-parse --verify nasty - then - echo "should have barfed" - false - else - : - fi' - test_done -- cgit v1.3-6-g1900 From 47dd0d595d04ee5283dfd8a0b4cbd6e6de8ad57f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 13 Dec 2005 17:21:41 -0800 Subject: diff: --abbrev option When I show transcripts to explain how something works, I often find myself hand-editing the diff-raw output to shorten various object names in the output. This adds --abbrev option to the diff family, which shortens diff-raw output and diff-tree commit id headers. Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 7 ++++++ diff-tree.c | 40 +++++++++++++++++------------ diff.c | 57 +++++++++++++++++++++++++++++++++++++----- diff.h | 9 ++++++- sha1_name.c | 3 +++ 5 files changed, 93 insertions(+), 23 deletions(-) (limited to 'sha1_name.c') diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 6b496ede25..3d1175e864 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -18,6 +18,13 @@ object name of pre- and post-image blob on the "index" line when generating a patch format output. +--abbrev:: + Instead of showing the full 40-byte hexadecimal object + name in diff-raw format output and diff-tree header + lines, show only handful prefix. This is independent of + --full-index option above, which controls the diff-patch + output format. + -B:: Break complete rewrite changes into pairs of delete and create. diff --git a/diff-tree.c b/diff-tree.c index d56d921585..efa2b9476e 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -14,11 +14,6 @@ static enum cmit_fmt commit_format = CMIT_FMT_RAW; static struct diff_options diff_options; -static void call_diff_setup_done(void) -{ - diff_setup_done(&diff_options); -} - static int call_diff_flush(void) { diffcore_std(&diff_options); @@ -43,7 +38,6 @@ static int diff_tree_sha1_top(const unsigned char *old, { int ret; - call_diff_setup_done(); ret = diff_tree_sha1(old, new, base, &diff_options); call_diff_flush(); return ret; @@ -55,7 +49,6 @@ static int diff_root_tree(const unsigned char *new, const char *base) void *tree; struct tree_desc empty, real; - call_diff_setup_done(); tree = read_object_with_reference(new, "tree", &real.size, NULL); if (!tree) die("unable to read root tree (%s)", sha1_to_hex(new)); @@ -69,18 +62,29 @@ static int diff_root_tree(const unsigned char *new, const char *base) return retval; } -static const char *generate_header(const char *commit, const char *parent, const char *msg) +static const char *generate_header(const unsigned char *commit_sha1, + const unsigned char *parent_sha1, + const char *msg) { static char this_header[16384]; int offset; unsigned long len; + int abbrev = diff_options.abbrev; if (!verbose_header) - return commit; + return sha1_to_hex(commit_sha1); len = strlen(msg); - offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent); - offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset); + + offset = sprintf(this_header, "%s%s ", + header_prefix, + diff_unique_abbrev(commit_sha1, abbrev)); + offset += sprintf(this_header + offset, "(from %s)\n", + parent_sha1 ? + diff_unique_abbrev(parent_sha1, abbrev) : "root"); + offset += pretty_print_commit(commit_format, msg, len, + this_header + offset, + sizeof(this_header) - offset); return this_header; } @@ -99,18 +103,18 @@ static int diff_tree_commit(const unsigned char *commit_sha1) /* Root commit? */ if (show_root_diff && !commit->parents) { - header = generate_header(name, "root", commit->buffer); + header = generate_header(sha1, NULL, commit->buffer); diff_root_tree(commit_sha1, ""); } /* More than one parent? */ if (ignore_merges && commit->parents && commit->parents->next) - return 0; + return 0; for (parents = commit->parents; parents; parents = parents->next) { struct commit *parent = parents->item; - header = generate_header(name, - sha1_to_hex(parent->object.sha1), + header = generate_header(sha1, + parent->object.sha1, commit->buffer); diff_tree_sha1_top(parent->object.sha1, commit_sha1, ""); if (!header && verbose_header) { @@ -129,6 +133,7 @@ static int diff_tree_stdin(char *line) int len = strlen(line); unsigned char commit[20], parent[20]; static char this_header[1000]; + int abbrev = diff_options.abbrev; if (!len || line[len-1] != '\n') return -1; @@ -138,7 +143,9 @@ static int diff_tree_stdin(char *line) if (isspace(line[40]) && !get_sha1_hex(line+41, parent)) { line[40] = 0; line[81] = 0; - sprintf(this_header, "%s (from %s)\n", line, line+41); + sprintf(this_header, "%s (from %s)\n", + diff_unique_abbrev(commit, abbrev), + diff_unique_abbrev(parent, abbrev)); header = this_header; return diff_tree_sha1_top(parent, commit, ""); } @@ -239,6 +246,7 @@ int main(int argc, const char **argv) diff_options.recursive = 1; diff_tree_setup_paths(get_pathspec(prefix, argv)); + diff_setup_done(&diff_options); switch (nr_sha1) { case 0: diff --git a/diff.c b/diff.c index 2e0797bf3e..c8159183da 100644 --- a/diff.c +++ b/diff.c @@ -723,11 +723,13 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) if (memcmp(one->sha1, two->sha1, 20)) { char one_sha1[41]; - const char *index_fmt = o->full_index ? "index %s..%s" : "index %.7s..%.7s"; + int abbrev = o->full_index ? 40 : DIFF_DEFAULT_INDEX_ABBREV; memcpy(one_sha1, sha1_to_hex(one->sha1), 41); len += snprintf(msg + len, sizeof(msg) - len, - index_fmt, one_sha1, sha1_to_hex(two->sha1)); + "index %.*s..%.*s", + abbrev, one_sha1, abbrev, + sha1_to_hex(two->sha1)); if (one->mode == two->mode) len += snprintf(msg + len, sizeof(msg) - len, " %06o", one->mode); @@ -791,6 +793,8 @@ int diff_setup_done(struct diff_options *options) } if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) use_size_cache = 1; + if (options->abbrev <= 0 || 40 < options->abbrev) + options->abbrev = 40; /* full */ return 0; } @@ -841,6 +845,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--find-copies-harder")) options->find_copies_harder = 1; + else if (!strcmp(arg, "--abbrev")) + options->abbrev = DIFF_DEFAULT_ABBREV; + else if (!strncmp(arg, "--abbrev=", 9)) + options->abbrev = strtoul(arg + 9, NULL, 10); else return 0; return 1; @@ -947,14 +955,49 @@ void diff_free_filepair(struct diff_filepair *p) free(p); } +/* This is different from find_unique_abbrev() in that + * it needs to deal with 0{40} SHA1. + */ +const char *diff_unique_abbrev(const unsigned char *sha1, int len) +{ + int abblen; + const char *abbrev; + if (len == 40) + return sha1_to_hex(sha1); + + abbrev = find_unique_abbrev(sha1, len); + if (!abbrev) { + if (!memcmp(sha1, null_sha1, 20)) { + char *buf = sha1_to_hex(null_sha1); + if (len < 37) + strcpy(buf + len, "..."); + return buf; + } + else + return sha1_to_hex(sha1); + } + abblen = strlen(abbrev); + if (abblen < 37) { + static char hex[41]; + if (len < abblen && abblen <= len + 2) + sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); + else + sprintf(hex, "%s...", abbrev); + return hex; + } + return sha1_to_hex(sha1); +} + static void diff_flush_raw(struct diff_filepair *p, int line_termination, int inter_name_termination, - int output_format) + struct diff_options *options) { int two_paths; char status[10]; + int abbrev = options->abbrev; const char *path_one, *path_two; + int output_format = options->output_format; path_one = p->one->path; path_two = p->two->path; @@ -985,8 +1028,10 @@ static void diff_flush_raw(struct diff_filepair *p, } if (output_format != DIFF_FORMAT_NAME_STATUS) { printf(":%06o %06o %s ", - p->one->mode, p->two->mode, sha1_to_hex(p->one->sha1)); - printf("%s ", sha1_to_hex(p->two->sha1)); + p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, abbrev)); + printf("%s ", + diff_unique_abbrev(p->two->sha1, abbrev)); } printf("%s%c%s", status, inter_name_termination, path_one); if (two_paths) @@ -1194,7 +1239,7 @@ void diff_flush(struct diff_options *options) case DIFF_FORMAT_NAME_STATUS: diff_flush_raw(p, line_termination, inter_name_termination, - diff_output_format); + options); break; case DIFF_FORMAT_NAME: diff_flush_name(p, diff --git a/diff.h b/diff.h index 32b4780173..c3486ffa86 100644 --- a/diff.h +++ b/diff.h @@ -44,6 +44,7 @@ struct diff_options { int reverse_diff; int rename_limit; int setup; + int abbrev; change_fn_t change; add_remove_fn_t add_remove; @@ -87,6 +88,9 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_ALL 1 +#define DIFF_DEFAULT_INDEX_ABBREV 7 /* hex digits */ +#define DIFF_DEFAULT_ABBREV 7 /* hex digits */ + extern void diffcore_std(struct diff_options *); extern void diffcore_std_no_resolve(struct diff_options *); @@ -98,7 +102,8 @@ extern void diffcore_std_no_resolve(struct diff_options *); " -u synonym for -p.\n" \ " --name-only show only names of changed files.\n" \ " --name-status show names and status of changed files.\n" \ -" --full-index show full object name on index ines.\n" \ +" --full-index show full object name on index lines.\n" \ +" --abbrev abbreviate object names in diff-tree header and diff-raw.\n" \ " -R swap input file pairs.\n" \ " -B detect complete rewrites.\n" \ " -M detect renames.\n" \ @@ -137,4 +142,6 @@ extern void diff_flush(struct diff_options*); #define DIFF_STATUS_FILTER_AON '*' #define DIFF_STATUS_FILTER_BROKEN 'B' +extern const char *diff_unique_abbrev(const unsigned char *, int); + #endif /* DIFF_H */ diff --git a/sha1_name.c b/sha1_name.c index 67b69a54fb..b13ed78cee 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -188,7 +188,10 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) { int status; static char hex[41]; + memcpy(hex, sha1_to_hex(sha1), 40); + if (len == 40) + return hex; while (len < 40) { unsigned char sha1_ret[20]; status = get_short_sha1(hex, len, sha1_ret, 1); -- cgit v1.3-6-g1900