summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/rev-list-options.txt4
-rw-r--r--builtin-blame.c206
-rw-r--r--builtin-rev-list.c10
-rw-r--r--revision.c39
-rw-r--r--revision.h1
5 files changed, 199 insertions, 61 deletions
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 37dd1d61ea..b6f5d87e72 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -45,6 +45,10 @@ endif::git-rev-list[]
Print the parents of the commit.
+--children::
+
+ Print the children of the commit.
+
ifdef::git-rev-list[]
--timestamp::
Print the raw commit timestamp.
diff --git a/builtin-blame.c b/builtin-blame.c
index b451f6c64d..cf41511c79 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -43,6 +43,7 @@ static int max_orig_digits;
static int max_digits;
static int max_score_digits;
static int show_root;
+static int reverse;
static int blank_boundary;
static int incremental;
static int cmd_is_annotate;
@@ -91,7 +92,7 @@ struct origin {
* Given an origin, prepare mmfile_t structure to be used by the
* diff machinery
*/
-static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+static void fill_origin_blob(struct origin *o, mmfile_t *file)
{
if (!o->file.ptr) {
enum object_type type;
@@ -106,7 +107,6 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)
}
else
*file = o->file;
- return file->ptr;
}
/*
@@ -178,7 +178,7 @@ struct blame_entry {
struct scoreboard {
/* the final commit (i.e. where we started digging from) */
struct commit *final;
-
+ struct rev_info *revs;
const char *path;
/*
@@ -1192,18 +1192,48 @@ static void pass_whole_blame(struct scoreboard *sb,
}
}
-#define MAXPARENT 16
+/*
+ * We pass blame from the current commit to its parents. We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+ if (!reverse)
+ return commit->parents;
+ return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+ int cnt;
+ struct commit_list *l = first_scapegoat(revs, commit);
+ for (cnt = 0; l; l = l->next)
+ cnt++;
+ return cnt;
+}
+
+#define MAXSG 16
static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
{
- int i, pass;
+ struct rev_info *revs = sb->revs;
+ int i, pass, num_sg;
struct commit *commit = origin->commit;
- struct commit_list *parent;
- struct origin *parent_origin[MAXPARENT], *porigin;
-
- memset(parent_origin, 0, sizeof(parent_origin));
+ struct commit_list *sg;
+ struct origin *sg_buf[MAXSG];
+ struct origin *porigin, **sg_origin = sg_buf;
+
+ num_sg = num_scapegoats(revs, commit);
+ if (!num_sg)
+ goto finish;
+ else if (num_sg < ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
- /* The first pass looks for unrenamed path to optimize for
+ /*
+ * The first pass looks for unrenamed path to optimize for
* common cases, then we look for renames in the second pass.
*/
for (pass = 0; pass < 2; pass++) {
@@ -1211,13 +1241,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
struct commit *, struct origin *);
find = pass ? find_rename : find_origin;
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct commit *p = parent->item;
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct commit *p = sg->item;
int j, same;
- if (parent_origin[i])
+ if (sg_origin[i])
continue;
if (parse_commit(p))
continue;
@@ -1230,24 +1260,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
goto finish;
}
for (j = same = 0; j < i; j++)
- if (parent_origin[j] &&
- !hashcmp(parent_origin[j]->blob_sha1,
+ if (sg_origin[j] &&
+ !hashcmp(sg_origin[j]->blob_sha1,
porigin->blob_sha1)) {
same = 1;
break;
}
if (!same)
- parent_origin[i] = porigin;
+ sg_origin[i] = porigin;
else
origin_decref(porigin);
}
}
num_commits++;
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (pass_blame_to_parent(sb, origin, porigin))
@@ -1258,10 +1288,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find moves in parents' files.
*/
if (opt & PICKAXE_BLAME_MOVE)
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (find_move_in_parent(sb, origin, porigin))
@@ -1272,23 +1302,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
* Optionally find copies from parents' files.
*/
if (opt & PICKAXE_BLAME_COPY)
- for (i = 0, parent = commit->parents;
- i < MAXPARENT && parent;
- parent = parent->next, i++) {
- struct origin *porigin = parent_origin[i];
- if (find_copy_in_parent(sb, origin, parent->item,
+ for (i = 0, sg = first_scapegoat(revs, commit);
+ i < num_sg && sg;
+ sg = sg->next, i++) {
+ struct origin *porigin = sg_origin[i];
+ if (find_copy_in_parent(sb, origin, sg->item,
porigin, opt))
goto finish;
}
finish:
- for (i = 0; i < MAXPARENT; i++) {
- if (parent_origin[i]) {
- drop_origin_blob(parent_origin[i]);
- origin_decref(parent_origin[i]);
+ for (i = 0; i < num_sg; i++) {
+ if (sg_origin[i]) {
+ drop_origin_blob(sg_origin[i]);
+ origin_decref(sg_origin[i]);
}
}
drop_origin_blob(origin);
+ if (sg_buf != sg_origin)
+ free(sg_origin);
}
/*
@@ -1487,8 +1519,10 @@ static void found_guilty_entry(struct blame_entry *ent)
* is still unknown, pick one blame_entry, and allow its current
* suspect to pass blames to its parents.
*/
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+static void assign_blame(struct scoreboard *sb, int opt)
{
+ struct rev_info *revs = sb->revs;
+
while (1) {
struct blame_entry *ent;
struct commit *commit;
@@ -1509,8 +1543,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
commit = suspect->commit;
if (!commit->object.parsed)
parse_commit(commit);
- if (!(commit->object.flags & UNINTERESTING) &&
- !(revs->max_age != -1 && commit->date < revs->max_age))
+ if (reverse ||
+ (!(commit->object.flags & UNINTERESTING) &&
+ !(revs->max_age != -1 && commit->date < revs->max_age)))
pass_blame(sb, suspect, opt);
else {
commit->object.flags |= UNINTERESTING;
@@ -2006,6 +2041,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
{
struct commit *commit;
@@ -2122,6 +2161,64 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
return commit;
}
+static const char *prepare_final(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one positive commit in the
+ * revs->pending array.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (obj->flags & UNINTERESTING)
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (sb->final)
+ die("More than one commit to dig from %s and %s?",
+ revs->pending.objects[i].name,
+ final_commit_name);
+ sb->final = (struct commit *) obj;
+ final_commit_name = revs->pending.objects[i].name;
+ }
+ return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+ int i;
+ const char *final_commit_name = NULL;
+ struct rev_info *revs = sb->revs;
+
+ /*
+ * There must be one and only one negative commit, and it must be
+ * the boundary.
+ */
+ for (i = 0; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+ if (!(obj->flags & UNINTERESTING))
+ continue;
+ while (obj->type == OBJ_TAG)
+ obj = deref_tag(obj, NULL, 0);
+ if (obj->type != OBJ_COMMIT)
+ die("Non commit %s?", revs->pending.objects[i].name);
+ if (sb->final)
+ die("More than one commit to dig down to %s and %s?",
+ revs->pending.objects[i].name,
+ final_commit_name);
+ sb->final = (struct commit *) obj;
+ final_commit_name = revs->pending.objects[i].name;
+ }
+ if (!final_commit_name)
+ die("No commit to dig down to?");
+ return final_commit_name;
+}
+
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -2154,6 +2251,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
blank_boundary = 1;
else if (!strcmp("--root", arg))
show_root = 1;
+ else if (!strcmp("--reverse", arg)) {
+ argv[unk++] = "--children";
+ reverse = 1;
+ }
else if (!strcmp(arg, "--show-stats"))
show_stats = 1;
else if (!strcmp("-c", arg))
@@ -2327,26 +2428,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
setup_revisions(unk, argv, &revs, NULL);
memset(&sb, 0, sizeof(sb));
- /*
- * There must be one and only one positive commit in the
- * revs->pending array.
- */
- for (i = 0; i < revs.pending.nr; i++) {
- struct object *obj = revs.pending.objects[i].item;
- if (obj->flags & UNINTERESTING)
- continue;
- while (obj->type == OBJ_TAG)
- obj = deref_tag(obj, NULL, 0);
- if (obj->type != OBJ_COMMIT)
- die("Non commit %s?",
- revs.pending.objects[i].name);
- if (sb.final)
- die("More than one commit to dig from %s and %s?",
- revs.pending.objects[i].name,
- final_commit_name);
- sb.final = (struct commit *) obj;
- final_commit_name = revs.pending.objects[i].name;
- }
+ sb.revs = &revs;
+ if (!reverse)
+ final_commit_name = prepare_final(&sb);
+ else if (contents_from)
+ die("--contents and --children do not blend well.");
+ else
+ final_commit_name = prepare_initial(&sb);
if (!sb.final) {
/*
@@ -2425,7 +2513,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
if (!incremental)
setup_pager();
- assign_blame(&sb, &revs, opt);
+ assign_blame(&sb, opt);
if (incremental)
return 0;
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 83a7b1349e..11a7eae551 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -37,6 +37,7 @@ static const char rev_list_usage[] =
" --reverse\n"
" formatting output:\n"
" --parents\n"
+" --children\n"
" --objects | --objects-edge\n"
" --unpacked\n"
" --header | --pretty\n"
@@ -90,6 +91,15 @@ static void show_commit(struct commit *commit)
parents = parents->next;
}
}
+ if (revs.children.name) {
+ struct commit_list *children;
+
+ children = lookup_decoration(&revs.children, &commit->object);
+ while (children) {
+ printf(" %s", sha1_to_hex(children->item->object.sha1));
+ children = children->next;
+ }
+ }
show_decorations(commit);
if (revs.commit_format == CMIT_FMT_ONELINE)
putchar(' ');
diff --git a/revision.c b/revision.c
index fc66755259..5a1a948a41 100644
--- a/revision.c
+++ b/revision.c
@@ -10,6 +10,7 @@
#include "grep.h"
#include "reflog-walk.h"
#include "patch-ids.h"
+#include "decorate.h"
volatile show_early_output_fn_t show_early_output;
@@ -1321,6 +1322,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
revs->no_walk = 0;
continue;
}
+ if (!strcmp(arg, "--children")) {
+ revs->children.name = "children";
+ revs->limited = 1;
+ continue;
+ }
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
@@ -1406,6 +1412,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->reverse && revs->reflog_info)
die("cannot combine --reverse with --walk-reflogs");
+ if (revs->rewrite_parents && revs->children.name)
+ die("cannot combine --parents and --children");
/*
* Limitations on the graph functionality
@@ -1419,6 +1427,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
return left;
}
+static void add_child(struct rev_info *revs, struct commit *parent, struct commit *child)
+{
+ struct commit_list *l = xcalloc(1, sizeof(*l));
+
+ l->item = child;
+ l->next = add_decoration(&revs->children, &parent->object, l);
+}
+
+static void set_children(struct rev_info *revs)
+{
+ struct commit_list *l;
+ for (l = revs->commits; l; l = l->next) {
+ struct commit *commit = l->item;
+ struct commit_list *p;
+
+ for (p = commit->parents; p; p = p->next)
+ add_child(revs, p->item, commit);
+ }
+}
+
int prepare_revision_walk(struct rev_info *revs)
{
int nr = revs->pending.nr;
@@ -1447,6 +1475,8 @@ int prepare_revision_walk(struct rev_info *revs)
return -1;
if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo);
+ if (revs->children.name)
+ set_children(revs);
return 0;
}
@@ -1524,6 +1554,11 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
commit->buffer, strlen(commit->buffer));
}
+static inline int want_ancestry(struct rev_info *revs)
+{
+ return (revs->rewrite_parents || revs->children.name);
+}
+
enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
{
if (commit->object.flags & SHOWN)
@@ -1544,13 +1579,13 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
/* drop merges unless we want parenthood */
- if (!revs->rewrite_parents)
+ if (!want_ancestry(revs))
return commit_ignore;
/* non-merge - always ignore it */
if (!commit->parents || !commit->parents->next)
return commit_ignore;
}
- if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
+ if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
return commit_error;
}
return commit_show;
diff --git a/revision.h b/revision.h
index abce5001f1..dcf08e089a 100644
--- a/revision.h
+++ b/revision.h
@@ -104,6 +104,7 @@ struct rev_info {
struct diff_options pruning;
struct reflog_walk_info *reflog_info;
+ struct decoration children;
};
#define REV_TREE_SAME 0