Merge branch 'dt/refs-backend-preamble'

In preparation for allowing different "backends" to store the refs
in a way different from the traditional "one ref per file in $GIT_DIR
or in a $GIT_DIR/packed-refs file" filesystem storage, reduce
direct filesystem access to ref-like things like CHERRY_PICK_HEAD
from scripts and programs.

* dt/refs-backend-preamble:
  git-stash: use update-ref --create-reflog instead of creating files
  update-ref and tag: add --create-reflog arg
  refs: add REF_FORCE_CREATE_REFLOG flag
  git-reflog: add exists command
  refs: new public ref function: safe_create_reflog
  refs: break out check for reflog autocreation
  refs.c: add err arguments to reflog functions
This commit is contained in:
Junio C Hamano 2015-08-03 11:01:29 -07:00
commit b6d323f164
13 changed files with 238 additions and 85 deletions

View File

@ -23,6 +23,7 @@ depending on the subcommand:
[--dry-run] [--verbose] [--all | <refs>...] [--dry-run] [--verbose] [--all | <refs>...]
'git reflog delete' [--rewrite] [--updateref] 'git reflog delete' [--rewrite] [--updateref]
[--dry-run] [--verbose] ref@\{specifier\}... [--dry-run] [--verbose] ref@\{specifier\}...
'git reflog exists' <ref>
Reference logs, or "reflogs", record when the tips of branches and Reference logs, or "reflogs", record when the tips of branches and
other references were updated in the local repository. Reflogs are other references were updated in the local repository. Reflogs are
@ -52,6 +53,9 @@ argument must be an _exact_ entry (e.g. "`git reflog delete
master@{2}`"). This subcommand is also typically not used directly by master@{2}`"). This subcommand is also typically not used directly by
end users. end users.
The "exists" subcommand checks whether a ref has a reflog. It exits
with zero status if the reflog exists, and non-zero status if it does
not.
OPTIONS OPTIONS
------- -------

View File

@ -13,7 +13,7 @@ SYNOPSIS
<tagname> [<commit> | <object>] <tagname> [<commit> | <object>]
'git tag' -d <tagname>... 'git tag' -d <tagname>...
'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>] 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
[--column[=<options>] | --no-column] [<pattern>...] [--column[=<options>] | --no-column] [--create-reflog] [<pattern>...]
'git tag' -v <tagname>... 'git tag' -v <tagname>...
DESCRIPTION DESCRIPTION
@ -142,6 +142,9 @@ This option is only applicable when listing tags without annotation lines.
all, 'whitespace' removes just leading/trailing whitespace lines and all, 'whitespace' removes just leading/trailing whitespace lines and
'strip' removes both whitespace and commentary. 'strip' removes both whitespace and commentary.
--create-reflog::
Create a reflog for the tag.
<tagname>:: <tagname>::
The name of the tag to create, delete, or describe. The name of the tag to create, delete, or describe.
The new tag name must pass all checks defined by The new tag name must pass all checks defined by

View File

@ -8,7 +8,7 @@ git-update-ref - Update the object name stored in a ref safely
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>] | --stdin [-z]) 'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] [--create-reflog] <ref> <newvalue> [<oldvalue>] | --stdin [-z])
DESCRIPTION DESCRIPTION
----------- -----------
@ -67,6 +67,9 @@ performs all modifications together. Specify commands of the form:
verify SP <ref> [SP <oldvalue>] LF verify SP <ref> [SP <oldvalue>] LF
option SP <opt> LF option SP <opt> LF
With `--create-reflog`, update-ref will create a reflog for each ref
even if one would not ordinarily be created.
Quote fields containing whitespace as if they were strings in C source Quote fields containing whitespace as if they were strings in C source
code; i.e., surrounded by double-quotes and with backslash escapes. code; i.e., surrounded by double-quotes and with backslash escapes.
Use 40 "0" characters or the empty string to specify a zero value. To Use 40 "0" characters or the empty string to specify a zero value. To

View File

@ -612,22 +612,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
if (opts->new_branch) { if (opts->new_branch) {
if (opts->new_orphan_branch) { if (opts->new_orphan_branch) {
if (opts->new_branch_log && !log_all_ref_updates) { if (opts->new_branch_log && !log_all_ref_updates) {
int temp;
struct strbuf log_file = STRBUF_INIT;
int ret; int ret;
const char *ref_name; char *refname;
struct strbuf err = STRBUF_INIT;
ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch);
temp = log_all_ref_updates; ret = safe_create_reflog(refname, 1, &err);
log_all_ref_updates = 1; free(refname);
ret = log_ref_setup(ref_name, &log_file);
log_all_ref_updates = temp;
strbuf_release(&log_file);
if (ret) { if (ret) {
fprintf(stderr, _("Can not do reflog for '%s'\n"), fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
opts->new_orphan_branch); opts->new_orphan_branch, err.buf);
strbuf_release(&err);
return; return;
} }
strbuf_release(&err);
} }
} }
else else

View File

@ -13,6 +13,8 @@ static const char reflog_expire_usage[] =
"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; "git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>...";
static const char reflog_delete_usage[] = static const char reflog_delete_usage[] =
"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; "git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>...";
static const char reflog_exists_usage[] =
"git reflog exists <ref>";
static unsigned long default_reflog_expire; static unsigned long default_reflog_expire;
static unsigned long default_reflog_expire_unreachable; static unsigned long default_reflog_expire_unreachable;
@ -699,12 +701,38 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix)
return status; return status;
} }
static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
{
int i, start = 0;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "--")) {
i++;
break;
}
else if (arg[0] == '-')
usage(reflog_exists_usage);
else
break;
}
start = i;
if (argc - start != 1)
usage(reflog_exists_usage);
if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL))
die("invalid ref format: %s", argv[start]);
return !reflog_exists(argv[start]);
}
/* /*
* main "reflog" * main "reflog"
*/ */
static const char reflog_usage[] = static const char reflog_usage[] =
"git reflog [ show | expire | delete ]"; "git reflog [ show | expire | delete | exists ]";
int cmd_reflog(int argc, const char **argv, const char *prefix) int cmd_reflog(int argc, const char **argv, const char *prefix)
{ {
@ -724,5 +752,8 @@ int cmd_reflog(int argc, const char **argv, const char *prefix)
if (!strcmp(argv[1], "delete")) if (!strcmp(argv[1], "delete"))
return cmd_reflog_delete(argc - 1, argv + 1, prefix); return cmd_reflog_delete(argc - 1, argv + 1, prefix);
if (!strcmp(argv[1], "exists"))
return cmd_reflog_exists(argc - 1, argv + 1, prefix);
return cmd_log_reflog(argc, argv, prefix); return cmd_log_reflog(argc, argv, prefix);
} }

View File

@ -579,6 +579,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct create_tag_options opt; struct create_tag_options opt;
char *cleanup_arg = NULL; char *cleanup_arg = NULL;
int annotate = 0, force = 0, lines = -1; int annotate = 0, force = 0, lines = -1;
int create_reflog = 0;
int cmdmode = 0; int cmdmode = 0;
const char *msgfile = NULL, *keyid = NULL; const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT }; struct msg_arg msg = { 0, STRBUF_INIT };
@ -605,6 +606,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_STRING('u', "local-user", &keyid, N_("key-id"), OPT_STRING('u', "local-user", &keyid, N_("key-id"),
N_("use another key to sign the tag")), N_("use another key to sign the tag")),
OPT__FORCE(&force, N_("replace the tag if exists")), OPT__FORCE(&force, N_("replace the tag if exists")),
OPT_BOOL(0, "create-reflog", &create_reflog, N_("create_reflog")),
OPT_GROUP(N_("Tag listing options")), OPT_GROUP(N_("Tag listing options")),
OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")),
@ -733,7 +735,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err); transaction = ref_transaction_begin(&err);
if (!transaction || if (!transaction ||
ref_transaction_update(transaction, ref.buf, object, prev, ref_transaction_update(transaction, ref.buf, object, prev,
0, NULL, &err) || create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
NULL, &err) ||
ref_transaction_commit(transaction, &err)) ref_transaction_commit(transaction, &err))
die("%s", err.buf); die("%s", err.buf);
ref_transaction_free(transaction); ref_transaction_free(transaction);

View File

@ -14,6 +14,7 @@ static const char * const git_update_ref_usage[] = {
static char line_termination = '\n'; static char line_termination = '\n';
static int update_flags; static int update_flags;
static unsigned create_reflog_flag;
static const char *msg; static const char *msg;
/* /*
@ -200,7 +201,8 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname, if (ref_transaction_update(transaction, refname,
new_sha1, have_old ? old_sha1 : NULL, new_sha1, have_old ? old_sha1 : NULL,
update_flags, msg, &err)) update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf); die("%s", err.buf);
update_flags = 0; update_flags = 0;
@ -231,7 +233,8 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
die("create %s: extra input: %s", refname, next); die("create %s: extra input: %s", refname, next);
if (ref_transaction_create(transaction, refname, new_sha1, if (ref_transaction_create(transaction, refname, new_sha1,
update_flags, msg, &err)) update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf); die("%s", err.buf);
update_flags = 0; update_flags = 0;
@ -354,6 +357,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
unsigned char sha1[20], oldsha1[20]; unsigned char sha1[20], oldsha1[20];
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
unsigned int flags = 0; unsigned int flags = 0;
int create_reflog = 0;
struct option options[] = { struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@ -361,6 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
N_("update <refname> not the one it points to")), N_("update <refname> not the one it points to")),
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create_reflog")),
OPT_END(), OPT_END(),
}; };
@ -370,6 +375,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (msg && !*msg) if (msg && !*msg)
die("Refusing to perform update with empty message."); die("Refusing to perform update with empty message.");
create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
if (read_stdin) { if (read_stdin) {
struct strbuf err = STRBUF_INIT; struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction; struct ref_transaction *transaction;
@ -431,5 +438,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
flags); flags);
else else
return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
flags, UPDATE_REFS_DIE_ON_ERR); flags | create_reflog_flag,
UPDATE_REFS_DIE_ON_ERR);
} }

View File

@ -183,9 +183,7 @@ store_stash () {
stash_msg="Created via \"git stash store\"." stash_msg="Created via \"git stash store\"."
fi fi
# Make sure the reflog for stash is kept. git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit
: >>"$(git rev-parse --git-path logs/$ref_stash)"
git update-ref -m "$stash_msg" $ref_stash $w_commit
ret=$? ret=$?
test $ret != 0 && test -z $quiet && test $ret != 0 && test -z $quiet &&
die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")" die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")"
@ -262,7 +260,7 @@ save_stash () {
say "$(gettext "No local changes to save")" say "$(gettext "No local changes to save")"
exit 0 exit 0
fi fi
test -f "$(git rev-parse --git-path logs/$ref_stash)" || git reflog exists $ref_stash ||
clear_stash || die "$(gettext "Cannot initialize stash")" clear_stash || die "$(gettext "Cannot initialize stash")"
create_stash "$stash_msg" $untracked create_stash "$stash_msg" $untracked

169
refs.c
View File

@ -62,6 +62,11 @@ static unsigned char refname_disposition[256] = {
*/ */
#define REF_NEEDS_COMMIT 0x20 #define REF_NEEDS_COMMIT 0x20
/*
* 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a
* value to ref_update::flags
*/
/* /*
* Try to read one refname component from the front of refname. * Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is * Return the length of the component found, or -1 if the component is
@ -2975,9 +2980,11 @@ static int rename_ref_available(const char *oldname, const char *newname)
return ret; return ret;
} }
static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1); static int write_ref_to_lockfile(struct ref_lock *lock,
const unsigned char *sha1, struct strbuf *err);
static int commit_ref_update(struct ref_lock *lock, static int commit_ref_update(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg); const unsigned char *sha1, const char *logmsg,
int flags, struct strbuf *err);
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{ {
@ -3038,9 +3045,10 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
} }
hashcpy(lock->old_oid.hash, orig_sha1); hashcpy(lock->old_oid.hash, orig_sha1);
if (write_ref_to_lockfile(lock, orig_sha1) || if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
commit_ref_update(lock, orig_sha1, logmsg)) { commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) {
error("unable to write current sha1 into %s", newrefname); error("unable to write current sha1 into %s: %s", newrefname, err.buf);
strbuf_release(&err);
goto rollback; goto rollback;
} }
@ -3056,9 +3064,11 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
flag = log_all_ref_updates; flag = log_all_ref_updates;
log_all_ref_updates = 0; log_all_ref_updates = 0;
if (write_ref_to_lockfile(lock, orig_sha1) || if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
commit_ref_update(lock, orig_sha1, NULL)) commit_ref_update(lock, orig_sha1, NULL, 0, &err)) {
error("unable to write current sha1 into %s", oldrefname); error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
strbuf_release(&err);
}
log_all_ref_updates = flag; log_all_ref_updates = flag;
rollbacklog: rollbacklog:
@ -3113,8 +3123,23 @@ static int copy_msg(char *buf, const char *msg)
return cp - buf; return cp - buf;
} }
/* This function must set a meaningful errno on failure */ static int should_autocreate_reflog(const char *refname)
int log_ref_setup(const char *refname, struct strbuf *sb_logfile) {
if (!log_all_ref_updates)
return 0;
return starts_with(refname, "refs/heads/") ||
starts_with(refname, "refs/remotes/") ||
starts_with(refname, "refs/notes/") ||
!strcmp(refname, "HEAD");
}
/*
* Create a reflog for a ref. If force_create = 0, the reflog will
* only be created for certain refs (those for which
* should_autocreate_reflog returns non-zero. Otherwise, create it
* regardless of the ref name. Fill in *err and return -1 on failure.
*/
static int log_ref_setup(const char *refname, struct strbuf *sb_logfile, struct strbuf *err, int force_create)
{ {
int logfd, oflags = O_APPEND | O_WRONLY; int logfd, oflags = O_APPEND | O_WRONLY;
char *logfile; char *logfile;
@ -3123,15 +3148,10 @@ int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
logfile = sb_logfile->buf; logfile = sb_logfile->buf;
/* make sure the rest of the function can't change "logfile" */ /* make sure the rest of the function can't change "logfile" */
sb_logfile = NULL; sb_logfile = NULL;
if (log_all_ref_updates && if (force_create || should_autocreate_reflog(refname)) {
(starts_with(refname, "refs/heads/") ||
starts_with(refname, "refs/remotes/") ||
starts_with(refname, "refs/notes/") ||
!strcmp(refname, "HEAD"))) {
if (safe_create_leading_directories(logfile) < 0) { if (safe_create_leading_directories(logfile) < 0) {
int save_errno = errno; strbuf_addf(err, "unable to create directory for %s: "
error("unable to create directory for %s", logfile); "%s", logfile, strerror(errno));
errno = save_errno;
return -1; return -1;
} }
oflags |= O_CREAT; oflags |= O_CREAT;
@ -3144,20 +3164,16 @@ int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
if (errno == EISDIR) { if (errno == EISDIR) {
if (remove_empty_directories(logfile)) { if (remove_empty_directories(logfile)) {
int save_errno = errno; strbuf_addf(err, "There are still logs under "
error("There are still logs under '%s'", "'%s'", logfile);
logfile);
errno = save_errno;
return -1; return -1;
} }
logfd = open(logfile, oflags, 0666); logfd = open(logfile, oflags, 0666);
} }
if (logfd < 0) { if (logfd < 0) {
int save_errno = errno; strbuf_addf(err, "unable to append to %s: %s",
error("Unable to append to %s: %s", logfile, logfile, strerror(errno));
strerror(errno));
errno = save_errno;
return -1; return -1;
} }
} }
@ -3167,6 +3183,17 @@ int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
return 0; return 0;
} }
int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)
{
int ret;
struct strbuf sb = STRBUF_INIT;
ret = log_ref_setup(refname, &sb, err, force_create);
strbuf_release(&sb);
return ret;
}
static int log_ref_write_fd(int fd, const unsigned char *old_sha1, static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
const unsigned char *new_sha1, const unsigned char *new_sha1,
const char *committer, const char *msg) const char *committer, const char *msg)
@ -3195,7 +3222,8 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg, const unsigned char *new_sha1, const char *msg,
struct strbuf *sb_log_file) struct strbuf *sb_log_file, int flags,
struct strbuf *err)
{ {
int logfd, result, oflags = O_APPEND | O_WRONLY; int logfd, result, oflags = O_APPEND | O_WRONLY;
char *log_file; char *log_file;
@ -3203,7 +3231,8 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
if (log_all_ref_updates < 0) if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository(); log_all_ref_updates = !is_bare_repository();
result = log_ref_setup(refname, sb_log_file); result = log_ref_setup(refname, sb_log_file, err, flags & REF_FORCE_CREATE_REFLOG);
if (result) if (result)
return result; return result;
log_file = sb_log_file->buf; log_file = sb_log_file->buf;
@ -3216,26 +3245,26 @@ static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
result = log_ref_write_fd(logfd, old_sha1, new_sha1, result = log_ref_write_fd(logfd, old_sha1, new_sha1,
git_committer_info(0), msg); git_committer_info(0), msg);
if (result) { if (result) {
int save_errno = errno; strbuf_addf(err, "unable to append to %s: %s", log_file,
strerror(errno));
close(logfd); close(logfd);
error("Unable to append to %s", log_file);
errno = save_errno;
return -1; return -1;
} }
if (close(logfd)) { if (close(logfd)) {
int save_errno = errno; strbuf_addf(err, "unable to append to %s: %s", log_file,
error("Unable to append to %s", log_file); strerror(errno));
errno = save_errno;
return -1; return -1;
} }
return 0; return 0;
} }
static int log_ref_write(const char *refname, const unsigned char *old_sha1, static int log_ref_write(const char *refname, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg) const unsigned char *new_sha1, const char *msg,
int flags, struct strbuf *err)
{ {
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb); int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
err);
strbuf_release(&sb); strbuf_release(&sb);
return ret; return ret;
} }
@ -3247,36 +3276,36 @@ int is_branch(const char *refname)
/* /*
* Write sha1 into the open lockfile, then close the lockfile. On * Write sha1 into the open lockfile, then close the lockfile. On
* errors, rollback the lockfile and set errno to reflect the problem. * errors, rollback the lockfile, fill in *err and
* return -1.
*/ */
static int write_ref_to_lockfile(struct ref_lock *lock, static int write_ref_to_lockfile(struct ref_lock *lock,
const unsigned char *sha1) const unsigned char *sha1, struct strbuf *err)
{ {
static char term = '\n'; static char term = '\n';
struct object *o; struct object *o;
o = parse_object(sha1); o = parse_object(sha1);
if (!o) { if (!o) {
error("Trying to write ref %s with nonexistent object %s", strbuf_addf(err,
lock->ref_name, sha1_to_hex(sha1)); "Trying to write ref %s with nonexistent object %s",
lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock); unlock_ref(lock);
errno = EINVAL;
return -1; return -1;
} }
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
error("Trying to write non-commit object %s to branch %s", strbuf_addf(err,
sha1_to_hex(sha1), lock->ref_name); "Trying to write non-commit object %s to branch %s",
sha1_to_hex(sha1), lock->ref_name);
unlock_ref(lock); unlock_ref(lock);
errno = EINVAL;
return -1; return -1;
} }
if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 || if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
write_in_full(lock->lk->fd, &term, 1) != 1 || write_in_full(lock->lk->fd, &term, 1) != 1 ||
close_ref(lock) < 0) { close_ref(lock) < 0) {
int save_errno = errno; strbuf_addf(err,
error("Couldn't write %s", lock->lk->filename.buf); "Couldn't write %s", lock->lk->filename.buf);
unlock_ref(lock); unlock_ref(lock);
errno = save_errno;
return -1; return -1;
} }
return 0; return 0;
@ -3288,12 +3317,17 @@ static int write_ref_to_lockfile(struct ref_lock *lock,
* necessary, using the specified lockmsg (which can be NULL). * necessary, using the specified lockmsg (which can be NULL).
*/ */
static int commit_ref_update(struct ref_lock *lock, static int commit_ref_update(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg) const unsigned char *sha1, const char *logmsg,
int flags, struct strbuf *err)
{ {
clear_loose_ref_cache(&ref_cache); clear_loose_ref_cache(&ref_cache);
if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg) < 0 || if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) && (strcmp(lock->ref_name, lock->orig_ref_name) &&
log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg) < 0)) { log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {
char *old_msg = strbuf_detach(err, NULL);
strbuf_addf(err, "Cannot update the ref '%s': %s",
lock->ref_name, old_msg);
free(old_msg);
unlock_ref(lock); unlock_ref(lock);
return -1; return -1;
} }
@ -3316,14 +3350,21 @@ static int commit_ref_update(struct ref_lock *lock,
head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
head_sha1, &head_flag); head_sha1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) && if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) !strcmp(head_ref, lock->ref_name)) {
log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg); struct strbuf log_err = STRBUF_INIT;
if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
logmsg, 0, &log_err)) {
error("%s", log_err.buf);
strbuf_release(&log_err);
}
}
} }
if (commit_ref(lock)) { if (commit_ref(lock)) {
error("Couldn't set %s", lock->ref_name); error("Couldn't set %s", lock->ref_name);
unlock_ref(lock); unlock_ref(lock);
return -1; return -1;
} }
unlock_ref(lock); unlock_ref(lock);
return 0; return 0;
} }
@ -3336,6 +3377,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
int fd, len, written; int fd, len, written;
char *git_HEAD = git_pathdup("%s", ref_target); char *git_HEAD = git_pathdup("%s", ref_target);
unsigned char old_sha1[20], new_sha1[20]; unsigned char old_sha1[20], new_sha1[20];
struct strbuf err = STRBUF_INIT;
if (logmsg && read_ref(ref_target, old_sha1)) if (logmsg && read_ref(ref_target, old_sha1))
hashclr(old_sha1); hashclr(old_sha1);
@ -3384,8 +3426,11 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
#ifndef NO_SYMLINK_HEAD #ifndef NO_SYMLINK_HEAD
done: done:
#endif #endif
if (logmsg && !read_ref(refs_heads_master, new_sha1)) if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
log_ref_write(ref_target, old_sha1, new_sha1, logmsg); log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
error("%s", err.buf);
strbuf_release(&err);
}
free(git_HEAD); free(git_HEAD);
return 0; return 0;
@ -4021,14 +4066,19 @@ int ref_transaction_commit(struct ref_transaction *transaction,
* value, so we don't need to write it. * value, so we don't need to write it.
*/ */
} else if (write_ref_to_lockfile(update->lock, } else if (write_ref_to_lockfile(update->lock,
update->new_sha1)) { update->new_sha1,
err)) {
char *write_err = strbuf_detach(err, NULL);
/* /*
* The lock was freed upon failure of * The lock was freed upon failure of
* write_ref_to_lockfile(): * write_ref_to_lockfile():
*/ */
update->lock = NULL; update->lock = NULL;
strbuf_addf(err, "cannot update the ref '%s'.", strbuf_addf(err,
update->refname); "cannot update the ref '%s': %s",
update->refname, write_err);
free(write_err);
ret = TRANSACTION_GENERIC_ERROR; ret = TRANSACTION_GENERIC_ERROR;
goto cleanup; goto cleanup;
} else { } else {
@ -4054,11 +4104,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,
if (update->flags & REF_NEEDS_COMMIT) { if (update->flags & REF_NEEDS_COMMIT) {
if (commit_ref_update(update->lock, if (commit_ref_update(update->lock,
update->new_sha1, update->msg)) { update->new_sha1, update->msg,
update->flags, err)) {
/* freed by commit_ref_update(): */ /* freed by commit_ref_update(): */
update->lock = NULL; update->lock = NULL;
strbuf_addf(err, "Cannot update the ref '%s'.",
update->refname);
ret = TRANSACTION_GENERIC_ERROR; ret = TRANSACTION_GENERIC_ERROR;
goto cleanup; goto cleanup;
} else { } else {

5
refs.h
View File

@ -224,11 +224,12 @@ int pack_refs(unsigned int flags);
* Other flags are reserved for internal use. * Other flags are reserved for internal use.
*/ */
#define REF_NODEREF 0x01 #define REF_NODEREF 0x01
#define REF_FORCE_CREATE_REFLOG 0x40
/* /*
* Setup reflog before using. Set errno to something meaningful on failure. * Setup reflog before using. Fill in err and return -1 on failure.
*/ */
int log_ref_setup(const char *refname, struct strbuf *logfile); int safe_create_reflog(const char *refname, int force_create, struct strbuf *err);
/** Reads log for the value of ref during at_time. **/ /** Reads log for the value of ref during at_time. **/
extern int read_ref_at(const char *refname, unsigned int flags, extern int read_ref_at(const char *refname, unsigned int flags,

View File

@ -23,6 +23,7 @@ test_expect_success setup '
m=refs/heads/master m=refs/heads/master
n_dir=refs/heads/gu n_dir=refs/heads/gu
n=$n_dir/fixes n=$n_dir/fixes
outside=foo
test_expect_success \ test_expect_success \
"create $m" \ "create $m" \
@ -74,6 +75,24 @@ test_expect_success "delete $m (by HEAD)" '
' '
rm -f .git/$m rm -f .git/$m
test_expect_success 'update-ref does not create reflogs by default' '
test_when_finished "git update-ref -d $outside" &&
git update-ref $outside $A &&
git rev-parse $A >expect &&
git rev-parse $outside >actual &&
test_cmp expect actual &&
test_must_fail git reflog exists $outside
'
test_expect_success 'update-ref creates reflogs with --create-reflog' '
test_when_finished "git update-ref -d $outside" &&
git update-ref --create-reflog $outside $A &&
git rev-parse $A >expect &&
git rev-parse $outside >actual &&
test_cmp expect actual &&
git reflog exists $outside
'
test_expect_success \ test_expect_success \
"create $m (by HEAD)" \ "create $m (by HEAD)" \
"git update-ref HEAD $A && "git update-ref HEAD $A &&
@ -472,6 +491,25 @@ test_expect_success 'stdin create ref works' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'stdin does not create reflogs by default' '
test_when_finished "git update-ref -d $outside" &&
echo "create $outside $m" >stdin &&
git update-ref --stdin <stdin &&
git rev-parse $m >expect &&
git rev-parse $outside >actual &&
test_cmp expect actual &&
test_must_fail git reflog exists $outside
'
test_expect_success 'stdin creates reflogs with --create-reflog' '
echo "create $outside $m" >stdin &&
git update-ref --create-reflog --stdin <stdin &&
git rev-parse $m >expect &&
git rev-parse $outside >actual &&
test_cmp expect actual &&
git reflog exists $outside
'
test_expect_success 'stdin succeeds with quoted argument' ' test_expect_success 'stdin succeeds with quoted argument' '
git update-ref -d $a && git update-ref -d $a &&
echo "create $a \"$m\"" >stdin && echo "create $a \"$m\"" >stdin &&

View File

@ -166,4 +166,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'reflog exists works' '
git reflog exists refs/heads/master &&
! git reflog exists refs/heads/nonexistent
'
test_done test_done

View File

@ -51,7 +51,19 @@ test_expect_success 'creating a tag using default HEAD should succeed' '
echo foo >foo && echo foo >foo &&
git add foo && git add foo &&
git commit -m Foo && git commit -m Foo &&
git tag mytag git tag mytag &&
test_must_fail git reflog exists refs/tags/mytag
'
test_expect_success 'creating a tag with --create-reflog should create reflog' '
test_when_finished "git tag -d tag_with_reflog" &&
git tag --create-reflog tag_with_reflog &&
git reflog exists refs/tags/tag_with_reflog
'
test_expect_success '--create-reflog does not create reflog on failure' '
test_must_fail git tag --create-reflog mytag &&
test_must_fail git reflog exists refs/tags/mytag
' '
test_expect_success 'listing all tags if one exists should succeed' ' test_expect_success 'listing all tags if one exists should succeed' '