Skip to content

Commit

Permalink
Merge branch 'sb/describe-blob'
Browse files Browse the repository at this point in the history
"git describe" was taught to dig trees deeper to find a
<commit-ish>:<path> that refers to a given blob object.

* sb/describe-blob:
  builtin/describe.c: describe a blob
  builtin/describe.c: factor out describe_commit
  builtin/describe.c: print debug statements earlier
  builtin/describe.c: rename `oid` to avoid variable shadowing
  revision.h: introduce blob/tree walking in order of the commits
  list-objects.c: factor out traverse_trees_and_blobs
  t6120: fix typo in test name
  • Loading branch information
gitster committed Dec 28, 2017
2 parents 0433d53 + 644eb60 commit 556de1a
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 52 deletions.
18 changes: 16 additions & 2 deletions Documentation/git-describe.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ git-describe(1)

NAME
----
git-describe - Describe a commit using the most recent tag reachable from it

git-describe - Give an object a human readable name based on an available ref

SYNOPSIS
--------
[verse]
'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...]
'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>]
'git describe' <blob>

DESCRIPTION
-----------
Expand All @@ -24,6 +24,12 @@ By default (without --all or --tags) `git describe` only shows
annotated tags. For more information about creating annotated tags
see the -a and -s options to linkgit:git-tag[1].

If the given object refers to a blob, it will be described
as `<commit-ish>:<path>`, such that the blob can be found
at `<path>` in the `<commit-ish>`, which itself describes the
first commit in which this blob occurs in a reverse revision walk
from HEAD.

OPTIONS
-------
<commit-ish>...::
Expand Down Expand Up @@ -186,6 +192,14 @@ selected and output. Here fewest commits different is defined as
the number of commits which would be shown by `git log tag..input`
will be the smallest number of commits possible.

BUGS
----

Tree objects as well as tag objects not pointing at commits, cannot be described.
When describing blobs, the lightweight tags pointing at blobs are ignored,
but the blob is still described as <committ-ish>:<path> despite the lightweight
tag being favorable.

GIT
---
Part of the linkgit:git[1] suite
5 changes: 5 additions & 0 deletions Documentation/rev-list-options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,11 @@ ifdef::git-rev-list[]
all object IDs which I need to download if I have the commit
object _bar_ but not _foo_''.

--in-commit-order::
Print tree and blob ids in order of the commits. The tree
and blob ids are printed after they are first referenced
by a commit.

--objects-edge::
Similar to `--objects`, but also print the IDs of excluded
commits prefixed with a ``-'' character. This is used by
Expand Down
121 changes: 94 additions & 27 deletions builtin/describe.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "lockfile.h"
#include "commit.h"
#include "tag.h"
#include "blob.h"
#include "refs.h"
#include "builtin.h"
#include "exec_cmd.h"
Expand All @@ -12,6 +13,8 @@
#include "hashmap.h"
#include "argv-array.h"
#include "run-command.h"
#include "revision.h"
#include "list-objects.h"

#define MAX_TAGS (FLAG_BITS - 1)

Expand Down Expand Up @@ -256,7 +259,7 @@ static unsigned long finish_depth_computation(
return seen_commits;
}

static void display_name(struct commit_name *n)
static void append_name(struct commit_name *n, struct strbuf *dst)
{
if (n->prio == 2 && !n->tag) {
n->tag = lookup_tag(&n->oid);
Expand All @@ -272,19 +275,18 @@ static void display_name(struct commit_name *n)
}

if (n->tag)
printf("%s", n->tag->tag);
strbuf_addstr(dst, n->tag->tag);
else
printf("%s", n->path);
strbuf_addstr(dst, n->path);
}

static void show_suffix(int depth, const struct object_id *oid)
static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst)
{
printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
}

static void describe(const char *arg, int last_one)
static void describe_commit(struct object_id *oid, struct strbuf *dst)
{
struct object_id oid;
struct commit *cmit, *gave_up_on = NULL;
struct commit_list *list;
struct commit_name *n;
Expand All @@ -293,30 +295,25 @@ static void describe(const char *arg, int last_one)
unsigned long seen_commits = 0;
unsigned int unannotated_cnt = 0;

if (get_oid(arg, &oid))
die(_("Not a valid object name %s"), arg);
cmit = lookup_commit_reference(&oid);
if (!cmit)
die(_("%s is not a valid '%s' object"), arg, commit_type);
cmit = lookup_commit_reference(oid);

n = find_commit_name(&cmit->object.oid);
if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
*/
display_name(n);
append_name(n, dst);
if (longformat)
show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid);
append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst);
if (suffix)
printf("%s", suffix);
printf("\n");
strbuf_addstr(dst, suffix);
return;
}

if (!max_candidates)
die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid));
if (debug)
fprintf(stderr, _("searching to describe %s\n"), arg);
fprintf(stderr, _("No exact match on refs or tags, searching to describe\n"));

if (!have_util) {
struct hashmap_iter iter;
Expand Down Expand Up @@ -381,22 +378,21 @@ static void describe(const char *arg, int last_one)
}

if (!match_cnt) {
struct object_id *oid = &cmit->object.oid;
struct object_id *cmit_oid = &cmit->object.oid;
if (always) {
printf("%s", find_unique_abbrev(oid->hash, abbrev));
strbuf_addstr(dst, find_unique_abbrev(cmit_oid->hash, abbrev));
if (suffix)
printf("%s", suffix);
printf("\n");
strbuf_addstr(dst, suffix);
return;
}
if (unannotated_cnt)
die(_("No annotated tags can describe '%s'.\n"
"However, there were unannotated tags: try --tags."),
oid_to_hex(oid));
oid_to_hex(cmit_oid));
else
die(_("No tags can describe '%s'.\n"
"Try --always, or create some tags."),
oid_to_hex(oid));
oid_to_hex(cmit_oid));
}

QSORT(all_matches, match_cnt, compare_pt);
Expand Down Expand Up @@ -434,15 +430,86 @@ static void describe(const char *arg, int last_one)
}
}

display_name(all_matches[0].name);
append_name(all_matches[0].name, dst);
if (abbrev)
show_suffix(all_matches[0].depth, &cmit->object.oid);
append_suffix(all_matches[0].depth, &cmit->object.oid, dst);
if (suffix)
printf("%s", suffix);
printf("\n");
strbuf_addstr(dst, suffix);
}

struct process_commit_data {
struct object_id current_commit;
struct object_id looking_for;
struct strbuf *dst;
struct rev_info *revs;
};

static void process_commit(struct commit *commit, void *data)
{
struct process_commit_data *pcd = data;
pcd->current_commit = commit->object.oid;
}

static void process_object(struct object *obj, const char *path, void *data)
{
struct process_commit_data *pcd = data;

if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
reset_revision_walk();
describe_commit(&pcd->current_commit, pcd->dst);
strbuf_addf(pcd->dst, ":%s", path);
free_commit_list(pcd->revs->commits);
pcd->revs->commits = NULL;
}
}

static void describe_blob(struct object_id oid, struct strbuf *dst)
{
struct rev_info revs;
struct argv_array args = ARGV_ARRAY_INIT;
struct process_commit_data pcd = { null_oid, oid, dst, &revs};

argv_array_pushl(&args, "internal: The first arg is not parsed",
"--objects", "--in-commit-order", "--reverse", "HEAD",
NULL);

init_revisions(&revs, NULL);
if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1)
BUG("setup_revisions could not handle all args?");

if (prepare_revision_walk(&revs))
die("revision walk setup failed");

traverse_commit_list(&revs, process_commit, process_object, &pcd);
reset_revision_walk();
}

static void describe(const char *arg, int last_one)
{
struct object_id oid;
struct commit *cmit;
struct strbuf sb = STRBUF_INIT;

if (debug)
fprintf(stderr, _("describe %s\n"), arg);

if (get_oid(arg, &oid))
die(_("Not a valid object name %s"), arg);
cmit = lookup_commit_reference_gently(&oid, 1);

if (cmit)
describe_commit(&oid, &sb);
else if (lookup_blob(&oid))
describe_blob(oid, &sb);
else
die(_("%s is neither a commit nor blob"), arg);

puts(sb.buf);

if (!last_one)
clear_commit_marks(cmit, -1);

strbuf_release(&sb);
}

int cmd_describe(int argc, const char **argv, const char *prefix)
Expand Down
67 changes: 46 additions & 21 deletions list-objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,27 +214,17 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree)
add_pending_object(revs, &tree->object, "");
}

static void do_traverse(struct rev_info *revs,
show_commit_fn show_commit,
show_object_fn show_object,
void *show_data,
filter_object_fn filter_fn,
void *filter_data)
static void traverse_trees_and_blobs(struct rev_info *revs,
struct strbuf *base,
show_object_fn show_object,
void *show_data,
filter_object_fn filter_fn,
void *filter_data)
{
int i;
struct commit *commit;
struct strbuf base;

strbuf_init(&base, PATH_MAX);
while ((commit = get_revision(revs)) != NULL) {
/*
* an uninteresting boundary commit may not have its tree
* parsed yet, but we are not going to show them anyway
*/
if (commit->tree)
add_pending_tree(revs, commit->tree);
show_commit(commit, show_data);
}
assert(base->len == 0);

for (i = 0; i < revs->pending.nr; i++) {
struct object_array_entry *pending = revs->pending.objects + i;
struct object *obj = pending->item;
Expand All @@ -251,21 +241,56 @@ static void do_traverse(struct rev_info *revs,
path = "";
if (obj->type == OBJ_TREE) {
process_tree(revs, (struct tree *)obj, show_object,
&base, path, show_data,
base, path, show_data,
filter_fn, filter_data);
continue;
}
if (obj->type == OBJ_BLOB) {
process_blob(revs, (struct blob *)obj, show_object,
&base, path, show_data,
base, path, show_data,
filter_fn, filter_data);
continue;
}
die("unknown pending object %s (%s)",
oid_to_hex(&obj->oid), name);
}
object_array_clear(&revs->pending);
strbuf_release(&base);
}

static void do_traverse(struct rev_info *revs,
show_commit_fn show_commit,
show_object_fn show_object,
void *show_data,
filter_object_fn filter_fn,
void *filter_data)
{
struct commit *commit;
struct strbuf csp; /* callee's scratch pad */
strbuf_init(&csp, PATH_MAX);

while ((commit = get_revision(revs)) != NULL) {
/*
* an uninteresting boundary commit may not have its tree
* parsed yet, but we are not going to show them anyway
*/
if (commit->tree)
add_pending_tree(revs, commit->tree);
show_commit(commit, show_data);

if (revs->tree_blobs_in_commit_order)
/*
* NEEDSWORK: Adding the tree and then flushing it here
* needs a reallocation for each commit. Can we pass the
* tree directory without allocation churn?
*/
traverse_trees_and_blobs(revs, &csp,
show_object, show_data,
filter_fn, filter_data);
}
traverse_trees_and_blobs(revs, &csp,
show_object, show_data,
filter_fn, filter_data);
strbuf_release(&csp);
}

void traverse_commit_list(struct rev_info *revs,
Expand Down
2 changes: 2 additions & 0 deletions revision.c
Original file line number Diff line number Diff line change
Expand Up @@ -1855,6 +1855,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->dense = 0;
} else if (!strcmp(arg, "--show-all")) {
revs->show_all = 1;
} else if (!strcmp(arg, "--in-commit-order")) {
revs->tree_blobs_in_commit_order = 1;
} else if (!strcmp(arg, "--remove-empty")) {
revs->remove_empty_trees = 1;
} else if (!strcmp(arg, "--merges")) {
Expand Down
3 changes: 2 additions & 1 deletion revision.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ struct rev_info {
bisect:1,
ancestry_path:1,
first_parent_only:1,
line_level_traverse:1;
line_level_traverse:1,
tree_blobs_in_commit_order:1;

/* Diff flags */
unsigned int diff:1,
Expand Down
Loading

0 comments on commit 556de1a

Please sign in to comment.