Subject: [PATCH 07/24] apply: support backup log with --keep-backup

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sun, 9 Dec 2018 11:44:02 +0100

Normally changes from git-apply are not worth logging because they
come from a file and can be recovered from there. This option will
mostly be used by add--interactive.pl where patches are generated
during an interactive add session and are very much worth logging.

The logging is a bit more complicated because an update is done in two
phases. The old version of all patches is removed first then the new
one added. We need to keep track of the old version in order to make a
backup.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 Documentation/git-apply.txt |  3 +++
 apply.c                     | 38 +++++++++++++++++++++++++++++++++++--
 apply.h                     |  1 +
 t/t2080-backup-log.sh       | 25 ++++++++++++++++++++++++
 4 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index b9aa39000f..7b0ae790db 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -250,6 +250,9 @@ When `git apply` is used as a "better GNU patch", the user can pass
 the `--unsafe-paths` option to override this safety check.  This option
 has no effect when `--index` or `--cached` is in use.
 
+--keep-backup::
+	Enable index backup log when `--cached` or `--index` is used.
+
 CONFIGURATION
 -------------
 
diff --git a/apply.c b/apply.c
index 01793d6126..716c9a658f 100644
--- a/apply.c
+++ b/apply.c
@@ -21,6 +21,7 @@
 #include "quote.h"
 #include "rerere.h"
 #include "apply.h"
+#include "backup-log.h"
 
 static void git_apply_config(void)
 {
@@ -226,6 +227,7 @@ struct patch {
 	char old_oid_prefix[GIT_MAX_HEXSZ + 1];
 	char new_oid_prefix[GIT_MAX_HEXSZ + 1];
 	struct patch *next;
+	struct object_id old_oid;
 
 	/* three-way fallback result */
 	struct object_id threeway_stage[3];
@@ -4261,6 +4263,16 @@ static void patch_stats(struct apply_state *state, struct patch *patch)
 static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
 {
 	if (state->update_index && !state->ita_only) {
+		if (state->backup_log) {
+			int pos = index_name_pos(state->repo->index,
+						 patch->old_name,
+						 strlen(patch->old_name));
+			if (pos >= 0)
+				oidcpy(&patch->old_oid,
+				       &state->repo->index->cache[pos]->oid);
+			else
+				oidclr(&patch->old_oid);
+		}
 		if (remove_file_from_index(state->repo->index, patch->old_name) < 0)
 			return error(_("unable to remove %s from index"), patch->old_name);
 	}
@@ -4276,7 +4288,8 @@ static int add_index_file(struct apply_state *state,
 			  const char *path,
 			  unsigned mode,
 			  void *buf,
-			  unsigned long size)
+			  unsigned long size,
+			  const struct object_id *old_oid)
 {
 	struct stat st;
 	struct cache_entry *ce;
@@ -4314,6 +4327,16 @@ static int add_index_file(struct apply_state *state,
 				       "for newly created file %s"), path);
 		}
 	}
+	if (state->backup_log) {
+		struct strbuf *sb = state->repo->index->backup_log;
+
+		if (!sb) {
+			sb = xmalloc(sizeof(*sb));
+			strbuf_init(sb, 0);
+			state->repo->index->backup_log = sb;
+		}
+		bkl_append(sb, ce->name, old_oid, &ce->oid);
+	}
 	if (add_index_entry(state->repo->index, ce, ADD_CACHE_OK_TO_ADD) < 0) {
 		discard_cache_entry(ce);
 		return error(_("unable to add cache entry for %s"), path);
@@ -4484,7 +4507,8 @@ static int create_file(struct apply_state *state, struct patch *patch)
 	if (patch->conflicted_threeway)
 		return add_conflicted_stages_file(state, patch);
 	else if (state->update_index)
-		return add_index_file(state, path, mode, buf, size);
+		return add_index_file(state, path, mode, buf, size,
+				      &patch->old_oid);
 	return 0;
 }
 
@@ -4659,6 +4683,7 @@ static int apply_patch(struct apply_state *state,
 	struct patch *list = NULL, **listp = &list;
 	int skipped_patch = 0;
 	int res = 0;
+	int core_backup_log = 0;
 
 	state->patch_input_file = filename;
 	if (read_patch_file(&buf, fd) < 0)
@@ -4721,6 +4746,13 @@ static int apply_patch(struct apply_state *state,
 		goto end;
 	}
 
+	if (state->backup_log &&
+	    (!state->update_index ||
+	     repo_config_get_bool(state->repo, "core.backupLog",
+				  &core_backup_log) ||
+	     !core_backup_log))
+		state->backup_log = 0;
+
 	if (state->check || state->apply) {
 		int r = check_patch_list(state, list);
 		if (r == -128) {
@@ -4982,6 +5014,8 @@ int apply_parse_options(int argc, const char **argv,
 			N_("mark new files with `git add --intent-to-add`")),
 		OPT_BOOL(0, "cached", &state->cached,
 			N_("apply a patch without touching the working tree")),
+		OPT_BOOL(0, "keep-backup", &state->backup_log,
+			 N_("log index changes if the feature is enabled")),
 		OPT_BOOL_F(0, "unsafe-paths", &state->unsafe_paths,
 			   N_("accept a patch that touches outside the working area"),
 			   PARSE_OPT_NOCOMPLETE),
diff --git a/apply.h b/apply.h
index 5948348133..d4de2ebcb9 100644
--- a/apply.h
+++ b/apply.h
@@ -51,6 +51,7 @@ struct apply_state {
 	int check_index; /* preimage must match the indexed version */
 	int update_index; /* check_index && apply */
 	int ita_only;	  /* add intent-to-add entries to the index */
+	int backup_log;	  /* enable backup log */
 
 	/* These control cosmetic aspect of the output */
 	int diffstat; /* just show a diffstat, and don't actually apply */
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index b19e26a807..8d1c8c5935 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -63,4 +63,29 @@ test_expect_success 'partial commit writes backup log' '
 	test_cmp expected .git/index.bkl
 '
 
+test_expect_success 'apply --cached writes backup log' '
+	rm -f .git/index.bkl &&
+	git reset --hard &&
+	test_tick &&
+	echo to-be-deleted >to-be-deleted &&
+	echo to-be-modified >to-be-modified &&
+	OLD_M=$(git hash-object to-be-modified) &&
+	git add . &&
+	git commit -m first &&
+	rm to-be-deleted &&
+	echo modified >>to-be-modified &&
+	NEW_M=$(git hash-object to-be-modified) &&
+	OLD_A=$ZERO_OID &&
+	echo to-be-added >to-be-added &&
+	NEW_A=$(git hash-object to-be-added) &&
+	git add . &&
+	git commit -m second &&
+	git diff HEAD^ HEAD >patch &&
+	git reset --hard HEAD^ &&
+	git -c core.backupLog=true apply --cached --keep-backup patch &&
+	echo "$OLD_A $NEW_A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $test_tick -0700	to-be-added" >expected &&
+	echo "$OLD_M $NEW_M $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $test_tick -0700	to-be-modified" >>expected &&
+	test_cmp expected .git/index.bkl
+'
+
 test_done
-- 
2.20.0.rc2.486.g9832c05c3d