Skip to content

Commit 3daafe9

Browse files
committed
Merge branch 'jc/name-rev-exact-ref'
Corrects the longstanding sloppiness in the implementation of name-rev that conflated "we take commit-ish" and "differences between tags and commits do not matter". * jc/name-rev-exact-ref: describe: fix --contains when a tag is given as input name-rev: differentiate between tags and commits they point at describe: use argv-array name-rev: allow converting the exact object name at the tip of a ref name-ref: factor out name shortening logic from name_ref()
2 parents 07b83b5 + adfc185 commit 3daafe9

File tree

3 files changed

+142
-32
lines changed

3 files changed

+142
-32
lines changed

builtin/describe.c

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "parse-options.h"
88
#include "diff.h"
99
#include "hash.h"
10+
#include "argv-array.h"
1011

1112
#define SEEN (1u<<0)
1213
#define MAX_TAGS (FLAG_BITS - 1)
@@ -442,24 +443,24 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
442443
die(_("--long is incompatible with --abbrev=0"));
443444

444445
if (contains) {
445-
const char **args = xmalloc((7 + argc) * sizeof(char *));
446-
int i = 0;
447-
args[i++] = "name-rev";
448-
args[i++] = "--name-only";
449-
args[i++] = "--no-undefined";
446+
struct argv_array args;
447+
448+
argv_array_init(&args);
449+
argv_array_pushl(&args, "name-rev",
450+
"--peel-tag", "--name-only", "--no-undefined",
451+
NULL);
450452
if (always)
451-
args[i++] = "--always";
453+
argv_array_push(&args, "--always");
452454
if (!all) {
453-
args[i++] = "--tags";
454-
if (pattern) {
455-
char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
456-
sprintf(s, "--refs=refs/tags/%s", pattern);
457-
args[i++] = s;
458-
}
455+
argv_array_push(&args, "--tags");
456+
if (pattern)
457+
argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
458+
}
459+
while (*argv) {
460+
argv_array_push(&args, *argv);
461+
argv++;
459462
}
460-
memcpy(args + i, argv, argc * sizeof(char *));
461-
args[i + argc] = NULL;
462-
return cmd_name_rev(i + argc, args, prefix);
463+
return cmd_name_rev(args.argc, args.argv, prefix);
463464
}
464465

465466
init_hash(&names);

builtin/name-rev.c

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "tag.h"
55
#include "refs.h"
66
#include "parse-options.h"
7+
#include "sha1-lookup.h"
78

89
#define CUTOFF_DATE_SLOP 86400 /* one day */
910

@@ -96,12 +97,51 @@ static int subpath_matches(const char *path, const char *filter)
9697
return -1;
9798
}
9899

100+
static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
101+
{
102+
if (shorten_unambiguous)
103+
refname = shorten_unambiguous_ref(refname, 0);
104+
else if (!prefixcmp(refname, "refs/heads/"))
105+
refname = refname + 11;
106+
else if (!prefixcmp(refname, "refs/"))
107+
refname = refname + 5;
108+
return refname;
109+
}
110+
99111
struct name_ref_data {
100112
int tags_only;
101113
int name_only;
102114
const char *ref_filter;
103115
};
104116

117+
static struct tip_table {
118+
struct tip_table_entry {
119+
unsigned char sha1[20];
120+
const char *refname;
121+
} *table;
122+
int nr;
123+
int alloc;
124+
int sorted;
125+
} tip_table;
126+
127+
static void add_to_tip_table(const unsigned char *sha1, const char *refname,
128+
int shorten_unambiguous)
129+
{
130+
refname = name_ref_abbrev(refname, shorten_unambiguous);
131+
132+
ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
133+
hashcpy(tip_table.table[tip_table.nr].sha1, sha1);
134+
tip_table.table[tip_table.nr].refname = xstrdup(refname);
135+
tip_table.nr++;
136+
tip_table.sorted = 0;
137+
}
138+
139+
static int tipcmp(const void *a_, const void *b_)
140+
{
141+
const struct tip_table_entry *a = a_, *b = b_;
142+
return hashcmp(a->sha1, b->sha1);
143+
}
144+
105145
static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
106146
{
107147
struct object *o = parse_object(sha1);
@@ -124,6 +164,8 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
124164
}
125165
}
126166

167+
add_to_tip_table(sha1, path, can_abbreviate_output);
168+
127169
while (o && o->type == OBJ_TAG) {
128170
struct tag *t = (struct tag *) o;
129171
if (!t->tagged)
@@ -134,18 +176,38 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
134176
if (o && o->type == OBJ_COMMIT) {
135177
struct commit *commit = (struct commit *)o;
136178

137-
if (can_abbreviate_output)
138-
path = shorten_unambiguous_ref(path, 0);
139-
else if (!prefixcmp(path, "refs/heads/"))
140-
path = path + 11;
141-
else if (!prefixcmp(path, "refs/"))
142-
path = path + 5;
143-
179+
path = name_ref_abbrev(path, can_abbreviate_output);
144180
name_rev(commit, xstrdup(path), 0, 0, deref);
145181
}
146182
return 0;
147183
}
148184

185+
static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
186+
{
187+
struct tip_table_entry *table = table_;
188+
return table[ix].sha1;
189+
}
190+
191+
static const char *get_exact_ref_match(const struct object *o)
192+
{
193+
int found;
194+
195+
if (!tip_table.table || !tip_table.nr)
196+
return NULL;
197+
198+
if (!tip_table.sorted) {
199+
qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table),
200+
tipcmp);
201+
tip_table.sorted = 1;
202+
}
203+
204+
found = sha1_pos(o->sha1, tip_table.table, tip_table.nr,
205+
nth_tip_table_ent);
206+
if (0 <= found)
207+
return tip_table.table[found].refname;
208+
return NULL;
209+
}
210+
149211
/* returns a static buffer */
150212
static const char *get_rev_name(const struct object *o)
151213
{
@@ -154,7 +216,7 @@ static const char *get_rev_name(const struct object *o)
154216
struct commit *c;
155217

156218
if (o->type != OBJ_COMMIT)
157-
return NULL;
219+
return get_exact_ref_match(o);
158220
c = (struct commit *) o;
159221
n = c->util;
160222
if (!n)
@@ -245,7 +307,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
245307
int cmd_name_rev(int argc, const char **argv, const char *prefix)
246308
{
247309
struct object_array revs = OBJECT_ARRAY_INIT;
248-
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0;
310+
int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
249311
struct name_ref_data data = { 0, 0, NULL };
250312
struct option opts[] = {
251313
OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
@@ -258,6 +320,12 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
258320
OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")),
259321
OPT_BOOLEAN(0, "always", &always,
260322
N_("show abbreviated commit object as fallback")),
323+
{
324+
/* A Hidden OPT_BOOL */
325+
OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL,
326+
N_("dereference tags in the input (internal use)"),
327+
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1,
328+
},
261329
OPT_END(),
262330
};
263331

@@ -272,7 +340,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
272340

273341
for (; argc; argc--, argv++) {
274342
unsigned char sha1[20];
275-
struct object *o;
343+
struct object *object;
276344
struct commit *commit;
277345

278346
if (get_sha1(*argv, sha1)) {
@@ -281,17 +349,34 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
281349
continue;
282350
}
283351

284-
o = deref_tag(parse_object(sha1), *argv, 0);
285-
if (!o || o->type != OBJ_COMMIT) {
286-
fprintf(stderr, "Could not get commit for %s. Skipping.\n",
352+
commit = NULL;
353+
object = parse_object(sha1);
354+
if (object) {
355+
struct object *peeled = deref_tag(object, *argv, 0);
356+
if (peeled && peeled->type == OBJ_COMMIT)
357+
commit = (struct commit *)peeled;
358+
}
359+
360+
if (!object) {
361+
fprintf(stderr, "Could not get object for %s. Skipping.\n",
287362
*argv);
288363
continue;
289364
}
290365

291-
commit = (struct commit *)o;
292-
if (cutoff > commit->date)
293-
cutoff = commit->date;
294-
add_object_array((struct object *)commit, *argv, &revs);
366+
if (commit) {
367+
if (cutoff > commit->date)
368+
cutoff = commit->date;
369+
}
370+
371+
if (peel_tag) {
372+
if (!commit) {
373+
fprintf(stderr, "Could not get commit for %s. Skipping.\n",
374+
*argv);
375+
continue;
376+
}
377+
object = (struct object *)commit;
378+
}
379+
add_object_array(object, *argv, &revs);
295380
}
296381

297382
if (cutoff)

t/t6120-describe.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,28 @@ check_describe "test2-lightweight-*" --tags --match="test2-*"
174174

175175
check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^
176176

177+
test_expect_success 'name-rev with exact tags' '
178+
echo A >expect &&
179+
tag_object=$(git rev-parse refs/tags/A) &&
180+
git name-rev --tags --name-only $tag_object >actual &&
181+
test_cmp expect actual &&
182+
183+
echo "A^0" >expect &&
184+
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
185+
git name-rev --tags --name-only $tagged_commit >actual &&
186+
test_cmp expect actual
187+
'
188+
189+
test_expect_success 'describe --contains with the exact tags' '
190+
echo "A^0" >expect &&
191+
tag_object=$(git rev-parse refs/tags/A) &&
192+
git describe --contains $tag_object >actual &&
193+
test_cmp expect actual &&
194+
195+
echo "A^0" >expect &&
196+
tagged_commit=$(git rev-parse "refs/tags/A^0") &&
197+
git describe --contains $tagged_commit >actual &&
198+
test_cmp expect actual
199+
'
200+
177201
test_done

0 commit comments

Comments
 (0)