Subject: [PATCH 18/24] refs: keep backup of deleted reflog

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

As noted in git-backup-log.txt a long time ago, this is mostly meant
for recovering a branch immediately after an accidental deletion.

References from the deleted reflog will not be included in
reachability tests and they will be deleted at the next gc. At that
point, this deleted reflog becomes useless.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 refs/files-backend.c  | 32 ++++++++++++++++++++++++++++++++
 t/t2080-backup-log.sh | 12 ++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/refs/files-backend.c b/refs/files-backend.c
index dd8abe9185..9afb274075 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -11,6 +11,7 @@
 #include "../dir.h"
 #include "../chdir-notify.h"
 #include "worktree.h"
+#include "backup-log.h"
 
 /*
  * This backend uses the following flags in `ref_update::flags` for
@@ -1873,6 +1874,35 @@ static int files_reflog_exists(struct ref_store *ref_store,
 	return ret;
 }
 
+static void backup_reflog(struct files_ref_store *refs,
+			  const char *reflog_path,
+			  const char *refname)
+{
+	int core_backup_log = 0;
+	struct stat st;
+	struct strbuf line = STRBUF_INIT;
+	struct strbuf path = STRBUF_INIT;
+	struct object_id old, new;
+
+	repo_config_get_bool(the_repository, "core.backuplog",
+			     &core_backup_log);
+	if (!core_backup_log)
+		return;
+	if (ref_type(refname) != REF_TYPE_NORMAL)
+		return;
+	if (lstat(reflog_path, &st) ||
+	    index_path(NULL, &old, reflog_path, &st, HASH_WRITE_OBJECT))
+		return;
+
+	strbuf_addf(&path, "logs/%s", refname);
+	oidclr(&new);
+	bkl_append(&line, path.buf, &old, &new);
+	strbuf_release(&path);
+	mkdir_in_gitdir(git_path("common"));
+	bkl_write(git_path("common/gitdir.bkl"), &line);
+	strbuf_release(&line);
+}
+
 static int files_delete_reflog(struct ref_store *ref_store,
 			       const char *refname)
 {
@@ -1882,6 +1912,7 @@ static int files_delete_reflog(struct ref_store *ref_store,
 	int ret;
 
 	files_reflog_path(refs, &sb, refname);
+	backup_reflog(refs, sb.buf, refname);
 	ret = remove_path(sb.buf);
 	strbuf_release(&sb);
 	return ret;
@@ -2797,6 +2828,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
 		    !(update->flags & REF_IS_PRUNING)) {
 			strbuf_reset(&sb);
 			files_reflog_path(refs, &sb, update->refname);
+			backup_reflog(refs, sb.buf, update->refname);
 			if (!unlink_or_warn(sb.buf))
 				try_remove_empty_parents(refs, update->refname,
 							 REMOVE_EMPTY_PARENTS_REFLOG);
diff --git a/t/t2080-backup-log.sh b/t/t2080-backup-log.sh
index dbd19db757..710df1ec8b 100755
--- a/t/t2080-backup-log.sh
+++ b/t/t2080-backup-log.sh
@@ -178,4 +178,16 @@ test_expect_success 'config --edit makes a backup' '
 	grep $NEW .git/common/gitdir.bkl
 '
 
+test_expect_success 'deleted reflog is kept' '
+	git checkout -b foo &&
+	git commit -am everything-else &&
+	test_commit two &&
+	test_commit three &&
+	git checkout -f master &&
+	OLD=$(git hash-object .git/logs/refs/heads/foo) &&
+	git -c core.backupLog=true branch -D foo &&
+	grep logs/refs/heads/foo .git/common/gitdir.bkl &&
+	grep ^$OLD .git/common/gitdir.bkl
+'
+
 test_done
-- 
2.20.0.rc2.486.g9832c05c3d