Commit ea6215df authored by Duy Nguyen's avatar Duy Nguyen

backup-log: add "update" subcommand

This defines backup log file format and adds basic support for writing
new entries to backup log files. The format is the same as reflog
except that "message" field becomes "path".

Similar to reflog, updating is done by appending to the end of the file
instead of creating a branch new file and do an atomic rename. If the
backup log file gets large, regenerating whole file could take longer,
and we should keep backup log overhead to minimum since it will be
called by a bunch of commands later.
parent 5b19f320
......@@ -21,6 +21,7 @@
......@@ -839,6 +839,7 @@ LIB_OBJS += archive-tar.o
LIB_OBJS += archive-zip.o
LIB_OBJS += argv-array.o
LIB_OBJS += attr.o
LIB_OBJS += backup-log.o
LIB_OBJS += base85.o
LIB_OBJS += bisect.o
LIB_OBJS += blame.o
......@@ -1040,6 +1041,7 @@ BUILTIN_OBJS += builtin/am.o
BUILTIN_OBJS += builtin/annotate.o
BUILTIN_OBJS += builtin/apply.o
BUILTIN_OBJS += builtin/archive.o
BUILTIN_OBJS += builtin/backup-log.o
BUILTIN_OBJS += builtin/bisect--helper.o
BUILTIN_OBJS += builtin/blame.o
BUILTIN_OBJS += builtin/branch.o
#include "cache.h"
#include "backup-log.h"
#include "lockfile.h"
#include "strbuf.h"
void bkl_append(struct strbuf *output, const char *path,
const struct object_id *from,
const struct object_id *to)
if (oideq(from, to))
* Do paths with \n in them really exist? At least it's not
* often seen to justify the support. Just drop them otherwise
* we break the line-based format.
if (strchr(path, '\n'))
strbuf_addf(output, "%s %s %s\t%s\n", oid_to_hex(from),
oid_to_hex(to), git_committer_info(0), path);
static int bkl_write_unlocked(const char *path, struct strbuf *new_log)
int fd = open(path, O_CREAT | O_WRONLY | O_APPEND, 0666);
if (fd == -1)
return error_errno(_("unable to open %s"), path);
if (write_in_full(fd, new_log->buf, new_log->len) < 0) {
return error_errno(_("unable to update %s"), path);
return 0;
int bkl_write(const char *path, struct strbuf *new_log)
struct lock_file lk;
int ret;
ret = hold_lock_file_for_update(&lk, path, LOCK_REPORT_ON_ERROR);
if (ret == -1)
return -1;
ret = bkl_write_unlocked(path, new_log);
* We do not write the the .lock file and append to the real one
* instead to reduce update cost. So we can't commit even in
* successful case.
return ret;
#ifndef __BACKUP_LOG_H__
#define __BACKUP_LOG_H__
struct object_id;
struct strbuf;
void bkl_append(struct strbuf *output, const char *path,
const struct object_id *from,
const struct object_id *to);
int bkl_write(const char *path, struct strbuf *new_log);
......@@ -132,6 +132,7 @@ extern int cmd_am(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
extern int cmd_backup_log(int argc, const char **argv, const char *prefix);
extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
extern int cmd_blame(int argc, const char **argv, const char *prefix);
extern int cmd_branch(int argc, const char **argv, const char *prefix);
#include "cache.h"
#include "builtin.h"
#include "backup-log.h"
#include "parse-options.h"
static char const * const backup_log_usage[] = {
N_("git backup-log [--path=<path> | --id=<id>] update <path> <old-hash> <new-hash>"),
static int update(int argc, const char **argv,
const char *prefix, const char *log_path)
struct strbuf sb = STRBUF_INIT;
struct object_id oid_from, oid_to;
const char *path;
int ret;
if (argc != 4)
usage_with_options(backup_log_usage, NULL);
path = argv[1];
if (get_oid(argv[2], &oid_from))
die(_("not a valid object name: %s"), argv[2]);
if (get_oid(argv[3], &oid_to))
die(_("not a valid object name: %s"), argv[3]);
bkl_append(&sb, path, &oid_from, &oid_to);
ret = bkl_write(log_path, &sb);
return ret;
static char *log_id_to_path(const char *id)
if (!strcmp(id, "index"))
return git_pathdup("index.bkl");
else if (!strcmp(id, "worktree"))
return git_pathdup("worktree.bkl");
else if (!strcmp(id, "gitdir"))
return git_pathdup("common/gitdir.bkl");
die(_("backup log id '%s' is not recognized"), id);
int cmd_backup_log(int argc, const char **argv, const char *prefix)
const char *log_id = NULL;
const char *log_path = NULL;
char *to_free = NULL;
struct option common_opts[] = {
OPT_STRING(0, "id", &log_id, N_("id"), N_("backup log file id")),
OPT_FILENAME(0, "path", &log_path, N_("backup log file path")),
argc = parse_options(argc, argv, prefix, common_opts,
if (!argc)
die(_("expected a subcommand"));
if (log_id) {
if (log_path)
die(_("--id and --path are incompatible"));
log_path = to_free = log_id_to_path(log_id);
if (!log_path)
die(_("either --id or --path is required"));
if (!strcmp(argv[0], "update"))
return update(argc, argv, prefix, log_path);
die(_("unknown subcommand: %s"), argv[0]);
return 0;
......@@ -51,6 +51,7 @@ git-annotate ancillaryinterrogators
git-apply plumbingmanipulators complete
git-archimport foreignscminterface
git-archive mainporcelain
git-backup-log plumbinginterrogators
git-bisect mainporcelain info
git-blame ancillaryinterrogators complete
git-branch mainporcelain history
......@@ -470,6 +470,7 @@ static struct cmd_struct commands[] = {
{ "annotate", cmd_annotate, RUN_SETUP | NO_PARSEOPT },
{ "apply", cmd_apply, RUN_SETUP_GENTLY },
{ "archive", cmd_archive, RUN_SETUP_GENTLY },
{ "backup-log", cmd_backup_log, RUN_SETUP_GENTLY },
{ "bisect--helper", cmd_bisect__helper, RUN_SETUP },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG },
......@@ -781,7 +781,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,
need_newline = 1;
for (; opts->type != OPTION_END; opts++) {
for (; opts && opts->type != OPTION_END; opts++) {
size_t pos;
int pad;
. ./
test_expect_success 'setup' '
test_commit initial &&
mkdir sub
test_expect_success 'backup-log add new item' '
ID=$(git rev-parse HEAD:./initial.t) &&
test_tick &&
git -C sub backup-log --id=index update foo $ZERO_OID $ID &&
echo "$ZERO_OID $ID $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $test_tick -0700 foo" >expected &&
test_cmp expected .git/index.bkl
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment