@matheusbsbs / matheus-bagliano / commits / 85af7929ee
Fork 0

git-blame --reverse

This new option allows "git blame" to read an old version of the file, and up to which commit each line survived (i.e. their children rewrote the line out of the contents). The previous revision machinery update to decorate each commit with its children was leading to this change. When the --reverse option is given, we read the old version and pass blame to the children of the current suspect, instead of the usual order of starting from the latest and passing blame to parents. The standard yardstick of "blame" in git.git history is "rev-list.c" which was refactored heavily in its existence. For example: git blame -C -C -w --reverse 9de48752..master -- rev-list.c begins like this: 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 1) #include "cache... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 2) #include "commi... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 3) #include "tree.... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 4) #include "blob.... 213523f4 rev-list.c (JC Hamano 2006-03-01 5) #include "epoch... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 6) ab57c8dd rev-list.c (JC Hamano 2006-02-24 7) #define SEEN ab57c8dd rev-list.c (JC Hamano 2006-02-24 8) #define INTERES... 213523f4 rev-list.c (JC Hamano 2006-03-01 9) #define COUNTED... 7e21c29b rev-list.c (LTorvalds 2005-07-06 10) #define SHOWN ... 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 11) 6c41b801 builtin-rev-list.c (JC Hamano 2008-04-02 12) static const ch... b1349229 rev-list.c (LTorvalds 2005-07-26 13) "usage: git-... This reveals that the original first four lines survived until now in builtin-rev-list.c , inclusion of "epoch.h" was removed after 213523f4 while the contents was still in rev-list.c. This mode probably needs more tweaking so that the commit that removed the line (i.e. the children of the commits listed in the above sample output) is shown instead to be useful, but then there is a little matter of which child of a fork point to show. For now, you can find the diff that rewrote the fifth line above by doing: $ git log --children 213523f4^.. to find its child, which is 1025fe5 (Merge branch 'lt/rev-list' into next, 2006-03-01), and then look at that child with: $ git show 1025fe5 Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Apr 2, 2008 at 22:17 UTC 85af7929ee125385c2771fa4eaccfa2f29dc63c9
1 file changed +65 -16
builtin-blame.c
+65 -16
@@ -43,6 +43,7 @@ static int max_orig_digits;
43 static int max_digits;
44 static int max_score_digits;
45 static int show_root;
46 +static int reverse;
47 static int blank_boundary;
48 static int incremental;
49 static int cmd_is_annotate;
@@ -177,7 +178,7 @@ struct blame_entry {
178 struct scoreboard {
179 /* the final commit (i.e. where we started digging from) */
180 struct commit *final;
180 -
181 + struct rev_info *revs;
182 const char *path;
183
184 /*
@@ -1196,15 +1197,17 @@ static void pass_whole_blame(struct scoreboard *sb,
1197 * "parent" (and "porigin"), but what we mean is to find scapegoat to
1198 * exonerate ourselves.
1199 */
1199 -static struct commit_list *first_scapegoat(struct commit *commit)
1200 +static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
1201 {
1201 - return commit->parents;
1202 + if (!reverse)
1203 + return commit->parents;
1204 + return lookup_decoration(&revs->children, &commit->object);
1205 }
1206
1204 -static int num_scapegoats(struct commit *commit)
1207 +static int num_scapegoats(struct rev_info *revs, struct commit *commit)
1208 {
1209 int cnt;
1207 - struct commit_list *l = first_scapegoat(commit);
1210 + struct commit_list *l = first_scapegoat(revs, commit);
1211 for (cnt = 0; l; l = l->next)
1212 cnt++;
1213 return cnt;
@@ -1214,13 +1217,14 @@ static int num_scapegoats(struct commit *commit)
1217
1218 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
1219 {
1220 + struct rev_info *revs = sb->revs;
1221 int i, pass, num_sg;
1222 struct commit *commit = origin->commit;
1223 struct commit_list *sg;
1224 struct origin *sg_buf[MAXSG];
1225 struct origin *porigin, **sg_origin = sg_buf;
1226
1223 - num_sg = num_scapegoats(commit);
1227 + num_sg = num_scapegoats(revs, commit);
1228 if (!num_sg)
1229 goto finish;
1230 else if (num_sg < ARRAY_SIZE(sg_buf))
@@ -1237,7 +1241,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
1241 struct commit *, struct origin *);
1242 find = pass ? find_rename : find_origin;
1243
1240 - for (i = 0, sg = first_scapegoat(commit);
1244 + for (i = 0, sg = first_scapegoat(revs, commit);
1245 i < num_sg && sg;
1246 sg = sg->next, i++) {
1247 struct commit *p = sg->item;
@@ -1270,7 +1274,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
1274 }
1275
1276 num_commits++;
1273 - for (i = 0, sg = first_scapegoat(commit);
1277 + for (i = 0, sg = first_scapegoat(revs, commit);
1278 i < num_sg && sg;
1279 sg = sg->next, i++) {
1280 struct origin *porigin = sg_origin[i];
@@ -1284,7 +1288,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
1288 * Optionally find moves in parents' files.
1289 */
1290 if (opt & PICKAXE_BLAME_MOVE)
1287 - for (i = 0, sg = first_scapegoat(commit);
1291 + for (i = 0, sg = first_scapegoat(revs, commit);
1292 i < num_sg && sg;
1293 sg = sg->next, i++) {
1294 struct origin *porigin = sg_origin[i];
@@ -1298,7 +1302,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
1302 * Optionally find copies from parents' files.
1303 */
1304 if (opt & PICKAXE_BLAME_COPY)
1301 - for (i = 0, sg = first_scapegoat(commit);
1305 + for (i = 0, sg = first_scapegoat(revs, commit);
1306 i < num_sg && sg;
1307 sg = sg->next, i++) {
1308 struct origin *porigin = sg_origin[i];
@@ -1515,8 +1519,10 @@ static void found_guilty_entry(struct blame_entry *ent)
1519 * is still unknown, pick one blame_entry, and allow its current
1520 * suspect to pass blames to its parents.
1521 */
1518 -static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
1522 +static void assign_blame(struct scoreboard *sb, int opt)
1523 {
1524 + struct rev_info *revs = sb->revs;
1525 +
1526 while (1) {
1527 struct blame_entry *ent;
1528 struct commit *commit;
@@ -1537,8 +1543,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
1543 commit = suspect->commit;
1544 if (!commit->object.parsed)
1545 parse_commit(commit);
1540 - if (!(commit->object.flags & UNINTERESTING) &&
1541 - !(revs->max_age != -1 && commit->date < revs->max_age))
1546 + if (reverse ||
1547 + (!(commit->object.flags & UNINTERESTING) &&
1548 + !(revs->max_age != -1 && commit->date < revs->max_age)))
1549 pass_blame(sb, suspect, opt);
1550 else {
1551 commit->object.flags |= UNINTERESTING;
@@ -2154,10 +2161,11 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
2161 return commit;
2162 }
2163
2157 -static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)
2164 +static const char *prepare_final(struct scoreboard *sb)
2165 {
2166 int i;
2167 const char *final_commit_name = NULL;
2168 + struct rev_info *revs = sb->revs;
2169
2170 /*
2171 * There must be one and only one positive commit in the
@@ -2181,6 +2189,36 @@ static const char *prepare_final(struct scoreboard *sb, struct rev_info *revs)
2189 return final_commit_name;
2190 }
2191
2192 +static const char *prepare_initial(struct scoreboard *sb)
2193 +{
2194 + int i;
2195 + const char *final_commit_name = NULL;
2196 + struct rev_info *revs = sb->revs;
2197 +
2198 + /*
2199 + * There must be one and only one negative commit, and it must be
2200 + * the boundary.
2201 + */
2202 + for (i = 0; i < revs->pending.nr; i++) {
2203 + struct object *obj = revs->pending.objects[i].item;
2204 + if (!(obj->flags & UNINTERESTING))
2205 + continue;
2206 + while (obj->type == OBJ_TAG)
2207 + obj = deref_tag(obj, NULL, 0);
2208 + if (obj->type != OBJ_COMMIT)
2209 + die("Non commit %s?", revs->pending.objects[i].name);
2210 + if (sb->final)
2211 + die("More than one commit to dig down to %s and %s?",
2212 + revs->pending.objects[i].name,
2213 + final_commit_name);
2214 + sb->final = (struct commit *) obj;
2215 + final_commit_name = revs->pending.objects[i].name;
2216 + }
2217 + if (!final_commit_name)
2218 + die("No commit to dig down to?");
2219 + return final_commit_name;
2220 +}
2221 +
2222 int cmd_blame(int argc, const char **argv, const char *prefix)
2223 {
2224 struct rev_info revs;
@@ -2213,6 +2251,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
2251 blank_boundary = 1;
2252 else if (!strcmp("--root", arg))
2253 show_root = 1;
2254 + else if (!strcmp("--reverse", arg)) {
2255 + argv[unk++] = "--children";
2256 + reverse = 1;
2257 + }
2258 else if (!strcmp(arg, "--show-stats"))
2259 show_stats = 1;
2260 else if (!strcmp("-c", arg))
@@ -2386,7 +2428,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
2428 setup_revisions(unk, argv, &revs, NULL);
2429 memset(&sb, 0, sizeof(sb));
2430
2389 - final_commit_name = prepare_final(&sb, &revs);
2431 + sb.revs = &revs;
2432 + if (!reverse)
2433 + final_commit_name = prepare_final(&sb);
2434 + else if (contents_from)
2435 + die("--contents and --children do not blend well.");
2436 + else
2437 + final_commit_name = prepare_initial(&sb);
2438 +
2439 if (!sb.final) {
2440 /*
2441 * "--not A B -- path" without anything positive;
@@ -2464,7 +2513,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
2513 if (!incremental)
2514 setup_pager();
2515
2467 - assign_blame(&sb, &revs, opt);
2516 + assign_blame(&sb, opt);
2517
2518 if (incremental)
2519 return 0;