diff mbox series

[12/19] built-in add -p: coalesce hunks after splitting them

Message ID da950fdfc5c3e15c59ee2f9cf301590b776e8dca.1576224486.git.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series Implement the git add --patch backend in C | expand

Commit Message

Johannes Schindelin via GitGitGadget Dec. 13, 2019, 8:07 a.m. UTC
From: Johannes Schindelin <johannes.schindelin@gmx.de>

This is considered "the right thing to do", according to 933e44d3a0
("add -p": work-around an old laziness that does not coalesce hunks,
2011-04-06).

Note: we cannot simply modify the hunks while merging them; Once we
implement hunk editing, we will call `reassemble_patch()` whenever a
hunk is edited, therefore we must not modify the hunks (because the user
might e.g. hit `K` and change their mind whether to stage the previous
hunk).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 add-patch.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 57 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/add-patch.c b/add-patch.c
index 2d34ddd7f4..c8d84aec68 100644
--- a/add-patch.c
+++ b/add-patch.c
@@ -433,6 +433,55 @@  static void render_diff_header(struct add_p_state *s,
 	}
 }
 
+/* Coalesce hunks again that were split */
+static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff,
+		       size_t *hunk_index, struct hunk *merged)
+{
+	size_t i = *hunk_index;
+	struct hunk *hunk = file_diff->hunk + i;
+	/* `header` corresponds to the merged hunk */
+	struct hunk_header *header = &merged->header, *next;
+
+	if (hunk->use != USE_HUNK)
+		return 0;
+
+	*merged = *hunk;
+	/* We simply skip the colored part (if any) when merging hunks */
+	merged->colored_start = merged->colored_end = 0;
+
+	for (; i + 1 < file_diff->hunk_nr; i++) {
+		hunk++;
+		next = &hunk->header;
+
+		/*
+		 * Stop merging hunks when:
+		 *
+		 * - the hunk is not selected for use, or
+		 * - the hunk does not overlap with the already-merged hunk(s)
+		 */
+		if (hunk->use != USE_HUNK ||
+		    header->new_offset >= next->new_offset ||
+		    header->new_offset + header->new_count < next->new_offset ||
+		    merged->start >= hunk->start ||
+		    merged->end < hunk->start)
+			break;
+
+		merged->end = hunk->end;
+		merged->colored_end = hunk->colored_end;
+
+		header->old_count = next->old_offset + next->old_count
+			- header->old_offset;
+		header->new_count = next->new_offset + next->new_count
+			- header->new_offset;
+	}
+
+	if (i == *hunk_index)
+		return 0;
+
+	*hunk_index = i;
+	return 1;
+}
+
 static void reassemble_patch(struct add_p_state *s,
 			     struct file_diff *file_diff, struct strbuf *out)
 {
@@ -443,12 +492,19 @@  static void reassemble_patch(struct add_p_state *s,
 	render_diff_header(s, file_diff, 0, out);
 
 	for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
+		struct hunk merged = { 0 };
+
 		hunk = file_diff->hunk + i;
 		if (hunk->use != USE_HUNK)
 			delta += hunk->header.old_count
 				- hunk->header.new_count;
-		else
+		else {
+			/* merge overlapping hunks into a temporary hunk */
+			if (merge_hunks(s, file_diff, &i, &merged))
+				hunk = &merged;
+
 			render_hunk(s, hunk, delta, 0, out);
+		}
 	}
 }