[PATCH] blame: can specify shas of commits to ignore on command line

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add the ability for git-blame to ignore changes that occur in
certain commits.  The new "-I <sha>" argument can be used to specify
a commit that should be ignored.  This is useful if you have a
few commits that you know didn't cause a problem, for example you
switched from "u8" to "uint8_t" and it messed up your history.

   Allow multiple commits to be specified and store an array in the
scoreboard structure so it is accessible.  Add should_ignore_commit
function to check if a commit should be ignored. Call
"should_ignore_commit" from blame_overlap and if the commit should
be ignored then pass all the blame on to the parent of the ignored
commit.  This is done by calling "pass_whole_blame" which has been
relocated to a above blame_overlap, but is unchanged.

Signed-off-by: Dylan Reid <dgreid@xxxxxxxxx>
---
 Documentation/blame-options.txt |    6 ++
 builtin/blame.c                 |  124 +++++++++++++++++++++++++++++----------
 t/t8003-blame.sh                |   42 +++++++++++++
 3 files changed, 141 insertions(+), 31 deletions(-)

diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index d820569..eb9c825 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -52,6 +52,12 @@ of lines before or after the line given by <start>.
 --porcelain::
 	Show in a format designed for machine consumption.
 
+--ignore-commit <sha>::
+	Ignore the specified commit when assigning blame.  This is
+	useful if there are commits in the history that make non
+	functional changes to the lines you are interested in
+	finding blame for.
+
 --incremental::
 	Show the result incrementally in a format designed for
 	machine consumption.
diff --git a/builtin/blame.c b/builtin/blame.c
index fc15863..e4bd095 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -176,6 +176,14 @@ struct blame_entry {
 };
 
 /*
+ * List of any commits to ignore
+ */
+struct ignore_commits {
+	unsigned char (*ignore_shas)[20];
+	unsigned num_ignore_shas;
+};
+
+/*
  * The current state of the blame assignment.
  */
 struct scoreboard {
@@ -198,6 +206,9 @@ struct scoreboard {
 	/* look-up a line in the final buffer */
 	int num_lines;
 	int *lineno;
+
+	/* List of the shas of commits that should be ignored */
+	struct ignore_commits *ignored_commits;
 };
 
 static inline int same_suspect(struct origin *a, struct origin *b)
@@ -653,6 +664,44 @@ static void decref_split(struct blame_entry *split)
 }
 
 /*
+ * The blobs of origin and porigin exactly match, so everything
+ * origin is suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(struct scoreboard *sb,
+			     struct origin *origin, struct origin *porigin)
+{
+   struct blame_entry *e;
+
+   if (!porigin->file.ptr && origin->file.ptr) {
+      /* Steal its file */
+      porigin->file = origin->file;
+      origin->file.ptr = NULL;
+   }
+   for (e = sb->ent; e; e = e->next) {
+      if (!same_suspect(e->suspect, origin))
+	 continue;
+      origin_incref(porigin);
+      origin_decref(e->suspect);
+      e->suspect = porigin;
+   }
+}
+
+/*
+ * Helper to determine if the given commit should be ignored
+ */
+static unsigned should_ignore_commit(const struct scoreboard *sb,
+				     struct commit *commit)
+{
+	unsigned i;
+	unsigned num_shas = sb->ignored_commits->num_ignore_shas;
+	for(i = 0; i < num_shas; i++)
+		if(!hashcmp(commit->object.sha1,
+			    sb->ignored_commits->ignore_shas[i]))
+			return 1;
+	return 0;
+}
+
+/*
  * Helper for blame_chunk().  blame_entry e is known to overlap with
  * the patch hunk; split it and pass blame to the parent.
  */
@@ -660,12 +709,15 @@ static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
 			  int tlno, int plno, int same,
 			  struct origin *parent)
 {
-	struct blame_entry split[3];
-
-	split_overlap(split, e, tlno, plno, same, parent);
-	if (split[1].suspect)
-		split_blame(sb, split, e);
-	decref_split(split);
+	if(should_ignore_commit(sb, e->suspect->commit))
+		pass_whole_blame(sb, e->suspect, parent);
+	else {
+		struct blame_entry split[3];
+		split_overlap(split, e, tlno, plno, same, parent);
+		if (split[1].suspect)
+			split_blame(sb, split, e);
+		decref_split(split);
+	}
 }
 
 /*
@@ -1105,29 +1157,6 @@ static int find_copy_in_parent(struct scoreboard *sb,
 }
 
 /*
- * The blobs of origin and porigin exactly match, so everything
- * origin is suspected for can be blamed on the parent.
- */
-static void pass_whole_blame(struct scoreboard *sb,
-			     struct origin *origin, struct origin *porigin)
-{
-	struct blame_entry *e;
-
-	if (!porigin->file.ptr && origin->file.ptr) {
-		/* Steal its file */
-		porigin->file = origin->file;
-		origin->file.ptr = NULL;
-	}
-	for (e = sb->ent; e; e = e->next) {
-		if (!same_suspect(e->suspect, origin))
-			continue;
-		origin_incref(porigin);
-		origin_decref(e->suspect);
-		e->suspect = porigin;
-	}
-}
-
-/*
  * 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.
@@ -1540,8 +1569,14 @@ static void assign_blame(struct scoreboard *sb, int opt)
 
 		/* Take responsibility for the remaining entries */
 		for (ent = sb->ent; ent; ent = ent->next)
-			if (same_suspect(ent->suspect, suspect))
-				found_guilty_entry(ent);
+			if (same_suspect(ent->suspect, suspect)) {
+				if (should_ignore_commit(sb, commit) &&
+				    ent->suspect->previous)
+					pass_whole_blame(sb, ent->suspect,
+							 ent->suspect->previous);
+				else
+					found_guilty_entry(ent);
+		   }
 		origin_decref(suspect);
 
 		if (DEBUG) /* sanity */
@@ -2204,6 +2239,24 @@ static int blame_bottomtop_callback(const struct option *option, const char *arg
 	return 0;
 }
 
+static int blame_ignorecommit_callback(const struct option *option,
+				       const char *arg, int unset)
+{
+	struct ignore_commits *commits = option->value;
+	if (!arg)
+		return -1;
+	commits->ignore_shas =
+		realloc(commits->ignore_shas,
+			( (commits->num_ignore_shas + 1) *
+			  20 ));
+	if(!get_sha1(arg, commits->ignore_shas[commits->num_ignore_shas]))
+		commits->num_ignore_shas++;
+	else
+		return -1;
+
+	return 0;
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info revs;
@@ -2215,6 +2268,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 	const char *final_commit_name = NULL;
 	enum object_type type;
 
+	static struct ignore_commits ignored_commits;
 	static const char *bottomtop = NULL;
 	static int output_option = 0, opt = 0;
 	static int show_stats = 0;
@@ -2239,6 +2293,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
 		{ OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
 		OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+		OPT_CALLBACK(0, "ignore-commit", &ignored_commits, "sha", "don't blame the specified commit for anything", blame_ignorecommit_callback),
 		OPT_END()
 	};
 
@@ -2252,6 +2307,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 	save_commit_buffer = 0;
 	dashdash_pos = 0;
 
+	memset(&ignored_commits, 0, sizeof(ignored_commits));
+
 	parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH |
 			    PARSE_OPT_KEEP_ARGV0);
 	for (;;) {
@@ -2369,6 +2426,8 @@ parse_done:
 	setup_revisions(argc, argv, &revs, NULL);
 	memset(&sb, 0, sizeof(sb));
 
+	sb.ignored_commits = &ignored_commits;
+
 	sb.revs = &revs;
 	if (!reverse)
 		final_commit_name = prepare_final(&sb);
@@ -2468,6 +2527,9 @@ parse_done:
 		ent = e;
 	}
 
+	if (ignored_commits.ignore_shas)
+		free(ignored_commits.ignore_shas);
+
 	if (show_stats) {
 		printf("num read blob: %d\n", num_read_blob);
 		printf("num get patch: %d\n", num_get_patch);
diff --git a/t/t8003-blame.sh b/t/t8003-blame.sh
index 230143c..f90d37a 100755
--- a/t/t8003-blame.sh
+++ b/t/t8003-blame.sh
@@ -185,4 +185,46 @@ test_expect_success 'indent of line numbers, ten lines' '
 	test $(grep -c "  " actual) = 9
 '
 
+test_expect_success 'blame ignore setup with split' '
+	echo A A A >one
+	echo b b b b b >> one
+	echo c c c c c >> one
+	git add one
+	GIT_AUTHOR_NAME=AddLine git commit -m "add As"
+	INIT_ADD_SHA=`git log --pretty=format:"%H" HEAD^..`
+	echo " " A A A >one
+	echo b b b b b >> one
+	echo c c c c c >> one
+	git add one
+	GIT_AUTHOR_NAME=Indent git commit -m "reindent"
+	IGNORE_SHA=`git log --pretty=format:"%H" HEAD^..`
+'
+
+test_expect_success 'blame ignore commit' '
+	git blame --ignore-commit $IGNORE_SHA one > ignored &&
+	test $(grep -c "AddLine" ignored) = 3
+'
+
+test_expect_success 'blame ignore first commit' '
+	git blame --ignore-commit $IGNORE_SHA --ignore-commit $INIT_ADD_SHA \
+		$INIT_ADD_SHA.. -- one > ignore_1st &&
+	test $(grep -c "AddLine" ignore_1st) = 3
+'
+
+test_expect_success 'blame ignore setup avoid blame_overlap' '
+	echo A A A >one
+	git add one
+	GIT_AUTHOR_NAME=AddLine git commit -m "add As"
+	INIT_ADD_SHA=`git log --pretty=format:"%H" HEAD^..`
+	echo " " A A A >one
+	git add one
+	GIT_AUTHOR_NAME=Indent git commit -m "reindent"
+	IGNORE_SHA=`git log --pretty=format:"%H" HEAD^..`
+'
+
+test_expect_success 'blame ignore commit no blame_overlap call' '
+	git blame --ignore-commit $IGNORE_SHA one > ignored &&
+	test $(grep -c "Indent" ignored) = 0
+'
+
 test_done
-- 
1.7.1.6.gc7a9

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]