c075aea5da
In git.git repository, "git-name-rev v1.3.0~158" cannot name the rev, while adjacent revs can be named. This was because it gives up traversal from the tips of existing refs as soon as it sees a commit that has older commit timestamp than what is being named. This is usually a good heuristics, but v1.3.0~158 has a slightly older commit timestamp than v1.3.0~157 (i.e. it's child), as these two were made in a separate repostiory (in fact, in a different continent). This adds a hardcoded slop value (1 day) to the cut-off heuristics to work this kind of problem around. The current algorithm essentially runs around from the available tips down to ancient commits and names every single rev available that are newer than cut-off date, so a single day slop would not add that much overhead in repositories with long enough history where the performance of name-rev matters. I think the algorithm could be made a bit smarter by deepening the graph on demand as a new commit is asked to be named (this would require rewriting of name_rev() function not to recurse itself but use a traversal list like revision.c traverser does), but that would be a separate issue. Acked-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Junio C Hamano <junkio@cox.net>
283 lines
6.1 KiB
C
283 lines
6.1 KiB
C
#include "builtin.h"
|
|
#include "cache.h"
|
|
#include "commit.h"
|
|
#include "tag.h"
|
|
#include "refs.h"
|
|
|
|
#define CUTOFF_DATE_SLOP 86400 /* one day */
|
|
|
|
static const char name_rev_usage[] =
|
|
"git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n";
|
|
|
|
typedef struct rev_name {
|
|
const char *tip_name;
|
|
int merge_traversals;
|
|
int generation;
|
|
} rev_name;
|
|
|
|
static long cutoff = LONG_MAX;
|
|
|
|
static void name_rev(struct commit *commit,
|
|
const char *tip_name, int merge_traversals, int generation,
|
|
int deref)
|
|
{
|
|
struct rev_name *name = (struct rev_name *)commit->util;
|
|
struct commit_list *parents;
|
|
int parent_number = 1;
|
|
|
|
if (!commit->object.parsed)
|
|
parse_commit(commit);
|
|
|
|
if (commit->date < cutoff)
|
|
return;
|
|
|
|
if (deref) {
|
|
char *new_name = xmalloc(strlen(tip_name)+3);
|
|
strcpy(new_name, tip_name);
|
|
strcat(new_name, "^0");
|
|
tip_name = new_name;
|
|
|
|
if (generation)
|
|
die("generation: %d, but deref?", generation);
|
|
}
|
|
|
|
if (name == NULL) {
|
|
name = xmalloc(sizeof(rev_name));
|
|
commit->util = name;
|
|
goto copy_data;
|
|
} else if (name->merge_traversals > merge_traversals ||
|
|
(name->merge_traversals == merge_traversals &&
|
|
name->generation > generation)) {
|
|
copy_data:
|
|
name->tip_name = tip_name;
|
|
name->merge_traversals = merge_traversals;
|
|
name->generation = generation;
|
|
} else
|
|
return;
|
|
|
|
for (parents = commit->parents;
|
|
parents;
|
|
parents = parents->next, parent_number++) {
|
|
if (parent_number > 1) {
|
|
int len = strlen(tip_name);
|
|
char *new_name = xmalloc(len +
|
|
1 + decimal_length(generation) + /* ~<n> */
|
|
1 + 2 + /* ^NN */
|
|
1);
|
|
|
|
if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
|
|
len -= 2;
|
|
if (generation > 0)
|
|
sprintf(new_name, "%.*s~%d^%d", len, tip_name,
|
|
generation, parent_number);
|
|
else
|
|
sprintf(new_name, "%.*s^%d", len, tip_name,
|
|
parent_number);
|
|
|
|
name_rev(parents->item, new_name,
|
|
merge_traversals + 1 , 0, 0);
|
|
} else {
|
|
name_rev(parents->item, tip_name, merge_traversals,
|
|
generation + 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct name_ref_data {
|
|
int tags_only;
|
|
const char *ref_filter;
|
|
};
|
|
|
|
static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data)
|
|
{
|
|
struct object *o = parse_object(sha1);
|
|
struct name_ref_data *data = cb_data;
|
|
int deref = 0;
|
|
|
|
if (data->tags_only && prefixcmp(path, "refs/tags/"))
|
|
return 0;
|
|
|
|
if (data->ref_filter && fnmatch(data->ref_filter, path, 0))
|
|
return 0;
|
|
|
|
while (o && o->type == OBJ_TAG) {
|
|
struct tag *t = (struct tag *) o;
|
|
if (!t->tagged)
|
|
break; /* broken repository */
|
|
o = parse_object(t->tagged->sha1);
|
|
deref = 1;
|
|
}
|
|
if (o && o->type == OBJ_COMMIT) {
|
|
struct commit *commit = (struct commit *)o;
|
|
|
|
if (!prefixcmp(path, "refs/heads/"))
|
|
path = path + 11;
|
|
else if (!prefixcmp(path, "refs/"))
|
|
path = path + 5;
|
|
|
|
name_rev(commit, xstrdup(path), 0, 0, deref);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* returns a static buffer */
|
|
static const char* get_rev_name(struct object *o)
|
|
{
|
|
static char buffer[1024];
|
|
struct rev_name *n;
|
|
struct commit *c;
|
|
|
|
if (o->type != OBJ_COMMIT)
|
|
return "undefined";
|
|
c = (struct commit *) o;
|
|
n = c->util;
|
|
if (!n)
|
|
return "undefined";
|
|
|
|
if (!n->generation)
|
|
return n->tip_name;
|
|
else {
|
|
int len = strlen(n->tip_name);
|
|
if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
|
|
len -= 2;
|
|
snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
|
|
n->generation);
|
|
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
int cmd_name_rev(int argc, const char **argv, const char *prefix)
|
|
{
|
|
struct object_array revs = { 0, 0, NULL };
|
|
int as_is = 0, all = 0, transform_stdin = 0;
|
|
struct name_ref_data data = { 0, NULL };
|
|
|
|
git_config(git_default_config);
|
|
|
|
if (argc < 2)
|
|
usage(name_rev_usage);
|
|
|
|
for (--argc, ++argv; argc; --argc, ++argv) {
|
|
unsigned char sha1[20];
|
|
struct object *o;
|
|
struct commit *commit;
|
|
|
|
if (!as_is && (*argv)[0] == '-') {
|
|
if (!strcmp(*argv, "--")) {
|
|
as_is = 1;
|
|
continue;
|
|
} else if (!strcmp(*argv, "--tags")) {
|
|
data.tags_only = 1;
|
|
continue;
|
|
} else if (!prefixcmp(*argv, "--refs=")) {
|
|
data.ref_filter = *argv + 7;
|
|
continue;
|
|
} else if (!strcmp(*argv, "--all")) {
|
|
if (argc > 1)
|
|
die("Specify either a list, or --all, not both!");
|
|
all = 1;
|
|
cutoff = 0;
|
|
continue;
|
|
} else if (!strcmp(*argv, "--stdin")) {
|
|
if (argc > 1)
|
|
die("Specify either a list, or --stdin, not both!");
|
|
transform_stdin = 1;
|
|
cutoff = 0;
|
|
continue;
|
|
}
|
|
usage(name_rev_usage);
|
|
}
|
|
|
|
if (get_sha1(*argv, sha1)) {
|
|
fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
|
|
*argv);
|
|
continue;
|
|
}
|
|
|
|
o = deref_tag(parse_object(sha1), *argv, 0);
|
|
if (!o || o->type != OBJ_COMMIT) {
|
|
fprintf(stderr, "Could not get commit for %s. Skipping.\n",
|
|
*argv);
|
|
continue;
|
|
}
|
|
|
|
commit = (struct commit *)o;
|
|
|
|
if (cutoff > commit->date)
|
|
cutoff = commit->date;
|
|
|
|
add_object_array((struct object *)commit, *argv, &revs);
|
|
}
|
|
|
|
if (cutoff)
|
|
cutoff = cutoff - CUTOFF_DATE_SLOP;
|
|
for_each_ref(name_ref, &data);
|
|
|
|
if (transform_stdin) {
|
|
char buffer[2048];
|
|
char *p, *p_start;
|
|
|
|
while (!feof(stdin)) {
|
|
int forty = 0;
|
|
p = fgets(buffer, sizeof(buffer), stdin);
|
|
if (!p)
|
|
break;
|
|
|
|
for (p_start = p; *p; p++) {
|
|
#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
|
|
if (!ishex(*p))
|
|
forty = 0;
|
|
else if (++forty == 40 &&
|
|
!ishex(*(p+1))) {
|
|
unsigned char sha1[40];
|
|
const char *name = "undefined";
|
|
char c = *(p+1);
|
|
|
|
forty = 0;
|
|
|
|
*(p+1) = 0;
|
|
if (!get_sha1(p - 39, sha1)) {
|
|
struct object *o =
|
|
lookup_object(sha1);
|
|
if (o)
|
|
name = get_rev_name(o);
|
|
}
|
|
*(p+1) = c;
|
|
|
|
if (!strcmp(name, "undefined"))
|
|
continue;
|
|
|
|
fwrite(p_start, p - p_start + 1, 1,
|
|
stdout);
|
|
printf(" (%s)", name);
|
|
p_start = p + 1;
|
|
}
|
|
}
|
|
|
|
/* flush */
|
|
if (p_start != p)
|
|
fwrite(p_start, p - p_start, 1, stdout);
|
|
}
|
|
} else if (all) {
|
|
int i, max;
|
|
|
|
max = get_max_object_index();
|
|
for (i = 0; i < max; i++) {
|
|
struct object * obj = get_indexed_object(i);
|
|
if (!obj)
|
|
continue;
|
|
printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj));
|
|
}
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < revs.nr; i++)
|
|
printf("%s %s\n",
|
|
revs.objects[i].name,
|
|
get_rev_name(revs.objects[i].item));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|