Discussion:
[PATCH v20 00/48] Use ref transactions
Ronnie Sahlberg
2014-06-20 14:42:41 UTC
Permalink
This patch series can also be found at
https://github.com/rsahlberg/git/tree/ref-transactions

This patch series is based on current master and expands on the transaction
API. It converts all ref updates, inside refs.c as well as external, to use the
transaction API for updates. This makes most of the ref updates to become
atomic when there are failures locking or writing to a ref.

This version completes the work to convert all ref updates to use transactions.
Now that all updates are through transactions I will start working on
cleaning up the reading of refs and to create an api for managing reflogs but
all that will go in a different patch series.

Version 20:
- Whitespace and style changes suggested by Jun.


Ronnie Sahlberg (48):
refs.c: remove ref_transaction_rollback
refs.c: ref_transaction_commit should not free the transaction
refs.c: constify the sha arguments for
ref_transaction_create|delete|update
refs.c: allow passing NULL to ref_transaction_free
refs.c: add a strbuf argument to ref_transaction_commit for error
logging
lockfile.c: add a new public function unable_to_lock_message
lockfile.c: make lock_file return a meaningful errno on failurei
refs.c: add an err argument to repack_without_refs
refs.c: make sure log_ref_setup returns a meaningful errno
refs.c: verify_lock should set errno to something meaningful
refs.c: make remove_empty_directories always set errno to something
sane
refs.c: commit_packed_refs to return a meaningful errno on failure
refs.c: make resolve_ref_unsafe set errno to something meaningful on
error
refs.c: log_ref_write should try to return meaningful errno
refs.c: make ref_update_reject_duplicates take a strbuf argument for
errors
refs.c: make update_ref_write update a strbuf on failure
update-ref: use err argument to get error from ref_transaction_commit
refs.c: remove the onerr argument to ref_transaction_commit
refs.c: change ref_transaction_update() to do error checking and
return status
refs.c: change ref_transaction_create to do error checking and return
status
refs.c: update ref_transaction_delete to check for error and return
status
refs.c: make ref_transaction_begin take an err argument
refs.c: add transaction.status and track OPEN/CLOSED/ERROR
tag.c: use ref transactions when doing updates
replace.c: use the ref transaction functions for updates
commit.c: use ref transactions for updates
sequencer.c: use ref transactions for all ref updates
fast-import.c: change update_branch to use ref transactions
branch.c: use ref transaction for all ref updates
refs.c: change update_ref to use a transaction
receive-pack.c: use a reference transaction for updating the refs
fast-import.c: use a ref transaction when dumping tags
walker.c: use ref transaction for ref updates
refs.c: make lock_ref_sha1 static
refs.c: remove the update_ref_lock function
refs.c: remove the update_ref_write function
refs.c: remove lock_ref_sha1
refs.c: make prune_ref use a transaction to delete the ref
refs.c: make delete_ref use a transaction
refs.c: add an err argument to delete_ref_loose
refs.c: pass the ref log message to _create/delete/update instead of
_commit
refs.c: pass NULL as *flags to read_ref_full
refs.c: move the check for valid refname to lock_ref_sha1_basic
refs.c: call lock_ref_sha1_basic directly from commit
refs.c: pass a skip list to name_conflict_fn
refs.c: propagate any errno==ENOTDIR from _commit back to the callers
fetch.c: change s_update_ref to use a ref transaction
refs.c: make write_ref_sha1 static

branch.c | 30 +--
builtin/commit.c | 24 ++-
builtin/fetch.c | 36 ++--
builtin/receive-pack.c | 97 ++++++----
builtin/remote.c | 5 +-
builtin/replace.c | 15 +-
builtin/tag.c | 15 +-
builtin/update-ref.c | 34 ++--
cache.h | 4 +-
fast-import.c | 53 ++++--
lockfile.c | 39 ++--
refs.c | 506 ++++++++++++++++++++++++++++++++-----------------
refs.h | 131 +++++++++----
sequencer.c | 24 ++-
walker.c | 58 +++---
wrapper.c | 14 +-
16 files changed, 710 insertions(+), 375 deletions(-)
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:43 UTC
Permalink
Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 1 +
refs.c | 1 -
refs.h | 5 ++---
3 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 405267f..1fd7a89 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -369,6 +369,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
update_refs_stdin();
ret = ref_transaction_commit(transaction, msg,
UPDATE_REFS_DIE_ON_ERR);
+ ref_transaction_free(transaction);
return ret;
}

diff --git a/refs.c b/refs.c
index 6d841a0..d9cac6d 100644
--- a/refs.c
+++ b/refs.c
@@ -3509,7 +3509,6 @@ cleanup:
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
free(delnames);
- ref_transaction_free(transaction);
return ret;
}

diff --git a/refs.h b/refs.h
index cfd1832..7d946f6 100644
--- a/refs.h
+++ b/refs.h
@@ -219,8 +219,7 @@ enum action_on_err {

/*
* Begin a reference transaction. The reference transaction must
- * eventually be commited using ref_transaction_commit() or freed by
- * calling ref_transaction_free().
+ * be freed by calling ref_transaction_free().
*/
struct ref_transaction *ref_transaction_begin(void);

@@ -268,7 +267,7 @@ void ref_transaction_delete(struct ref_transaction *transaction,
/*
* Commit all of the changes that have been queued in transaction, as
* atomically as possible. Return a nonzero value if there is a
- * problem. The ref_transaction is freed by this function.
+ * problem.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
const char *msg, enum action_on_err onerr);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:48 UTC
Permalink
Making errno when returning from lock_file() meaningful, which should
fix

* an existing almost-bug in lock_ref_sha1_basic where it assumes
errno==ENOENT is meaningful and could waste some work on retries

* an existing bug in repack_without_refs where it prints
strerror(errno) and picks advice based on errno, despite errno
potentially being zero and potentially having been clobbered by
that point

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
lockfile.c | 17 ++++++++++++-----
refs.c | 1 +
refs.h | 1 +
3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/lockfile.c b/lockfile.c
index 464031b..a921d77 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -121,7 +121,7 @@ static char *resolve_symlink(char *p, size_t s)
return p;
}

-
+/* Make sure errno contains a meaningful value on error */
static int lock_file(struct lock_file *lk, const char *path, int flags)
{
/*
@@ -130,8 +130,10 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
*/
static const size_t max_path_len = sizeof(lk->filename) - 5;

- if (strlen(path) >= max_path_len)
+ if (strlen(path) >= max_path_len) {
+ errno = ENAMETOOLONG;
return -1;
+ }
strcpy(lk->filename, path);
if (!(flags & LOCK_NODEREF))
resolve_symlink(lk->filename, max_path_len);
@@ -148,9 +150,13 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
lock_file_list = lk;
lk->on_list = 1;
}
- if (adjust_shared_perm(lk->filename))
- return error("cannot fix permission bits on %s",
- lk->filename);
+ if (adjust_shared_perm(lk->filename)) {
+ int save_errno = errno;
+ error("cannot fix permission bits on %s",
+ lk->filename);
+ errno = save_errno;
+ return -1;
+ }
}
else
lk->filename[0] = 0;
@@ -188,6 +194,7 @@ NORETURN void unable_to_lock_index_die(const char *path, int err)
die("%s", buf.buf);
}

+/* This should return a meaningful errno on failure */
int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
{
int fd = lock_file(lk, path, flags);
diff --git a/refs.c b/refs.c
index db05602..e9d53e4 100644
--- a/refs.c
+++ b/refs.c
@@ -2212,6 +2212,7 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
return 0;
}

+/* This should return a meaningful errno on failure */
int lock_packed_refs(int flags)
{
struct packed_ref_cache *packed_ref_cache;
diff --git a/refs.h b/refs.h
index 09d3564..64f25d9 100644
--- a/refs.h
+++ b/refs.h
@@ -82,6 +82,7 @@ extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct st
/*
* Lock the packed-refs file for writing. Flags is passed to
* hold_lock_file_for_update(). Return 0 on success.
+ * Errno is set to something meaningful on error.
*/
extern int lock_packed_refs(int flags);
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 11:47:41 UTC
Permalink
Post by Ronnie Sahlberg
Making errno when returning from lock_file() meaningful, which should
fix
* an existing almost-bug in lock_ref_sha1_basic where it assumes
errno==ENOENT is meaningful and could waste some work on retries
* an existing bug in repack_without_refs where it prints
strerror(errno) and picks advice based on errno, despite errno
potentially being zero and potentially having been clobbered by
that point
[...]
Typo in subject line:

s/failurei/failure/

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-06-20 14:42:45 UTC
Permalink
Allow ref_transaction_free(NULL) as a no-op. This makes ref_transaction_free
easier to use and more similar to plain 'free'.

In particular, it lets us rollback unconditionally as part of cleanup code
after setting 'transaction = NULL' if a transaction has been committed or
rolled back already.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/refs.c b/refs.c
index 21ed465..1d6dece 100644
--- a/refs.c
+++ b/refs.c
@@ -3338,6 +3338,9 @@ void ref_transaction_free(struct ref_transaction *transaction)
{
int i;

+ if (!transaction)
+ return;
+
for (i = 0; i < transaction->nr; i++)
free(transaction->updates[i]);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:46 UTC
Permalink
Add a strbuf argument to _commit so that we can pass an error string back to
the caller. So that we can do error logging from the caller instead of from
_commit.

Longer term plan is to first convert all callers to use onerr==QUIET_ON_ERR
and craft any log messages from the callers themselves and finally remove the
onerr argument completely.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 2 +-
refs.c | 6 +++++-
refs.h | 5 ++++-
3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 1fd7a89..22617af 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -367,7 +367,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (end_null)
line_termination = '\0';
update_refs_stdin();
- ret = ref_transaction_commit(transaction, msg,
+ ret = ref_transaction_commit(transaction, msg, NULL,
UPDATE_REFS_DIE_ON_ERR);
ref_transaction_free(transaction);
return ret;
diff --git a/refs.c b/refs.c
index 1d6dece..db05602 100644
--- a/refs.c
+++ b/refs.c
@@ -3444,7 +3444,8 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
}

int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, enum action_on_err onerr)
+ const char *msg, struct strbuf *err,
+ enum action_on_err onerr)
{
int ret = 0, delnum = 0, i;
const char **delnames;
@@ -3473,6 +3474,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
update->flags,
&update->type, onerr);
if (!update->lock) {
+ if (err)
+ strbuf_addf(err, "Cannot lock the ref '%s'.",
+ update->refname);
ret = 1;
goto cleanup;
}
diff --git a/refs.h b/refs.h
index 8c7f9c4..09d3564 100644
--- a/refs.h
+++ b/refs.h
@@ -269,9 +269,12 @@ void ref_transaction_delete(struct ref_transaction *transaction,
* Commit all of the changes that have been queued in transaction, as
* atomically as possible. Return a nonzero value if there is a
* problem.
+ * If err is non-NULL we will add an error string to it to explain why
+ * the transaction failed. The string does not end in newline.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, enum action_on_err onerr);
+ const char *msg, struct strbuf *err,
+ enum action_on_err onerr);

/*
* Free an existing transaction and all associated data.
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:47 UTC
Permalink
Introducing a new unable_to_lock_message helper, which has nicer
semantics than unable_to_lock_error and cleans up lockfile.c a little.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
cache.h | 2 ++
lockfile.c | 22 ++++++++++++----------
2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/cache.h b/cache.h
index cbe1935..8b12aa8 100644
--- a/cache.h
+++ b/cache.h
@@ -559,6 +559,8 @@ struct lock_file {
#define LOCK_DIE_ON_ERROR 1
#define LOCK_NODEREF 2
extern int unable_to_lock_error(const char *path, int err);
+extern void unable_to_lock_message(const char *path, int err,
+ struct strbuf *buf);
extern NORETURN void unable_to_lock_index_die(const char *path, int err);
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
diff --git a/lockfile.c b/lockfile.c
index 8fbcb6a..464031b 100644
--- a/lockfile.c
+++ b/lockfile.c
@@ -157,33 +157,35 @@ static int lock_file(struct lock_file *lk, const char *path, int flags)
return lk->fd;
}

-static char *unable_to_lock_message(const char *path, int err)
+void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
- struct strbuf buf = STRBUF_INIT;
-
if (err == EEXIST) {
- strbuf_addf(&buf, "Unable to create '%s.lock': %s.\n\n"
+ strbuf_addf(buf, "Unable to create '%s.lock': %s.\n\n"
"If no other git process is currently running, this probably means a\n"
"git process crashed in this repository earlier. Make sure no other git\n"
"process is running and remove the file manually to continue.",
absolute_path(path), strerror(err));
} else
- strbuf_addf(&buf, "Unable to create '%s.lock': %s",
+ strbuf_addf(buf, "Unable to create '%s.lock': %s",
absolute_path(path), strerror(err));
- return strbuf_detach(&buf, NULL);
}

int unable_to_lock_error(const char *path, int err)
{
- char *msg = unable_to_lock_message(path, err);
- error("%s", msg);
- free(msg);
+ struct strbuf buf = STRBUF_INIT;
+
+ unable_to_lock_message(path, err, &buf);
+ error("%s", buf.buf);
+ strbuf_release(&buf);
return -1;
}

NORETURN void unable_to_lock_index_die(const char *path, int err)
{
- die("%s", unable_to_lock_message(path, err));
+ struct strbuf buf = STRBUF_INIT;
+
+ unable_to_lock_message(path, err, &buf);
+ die("%s", buf.buf);
}

int hold_lock_file_for_update(struct lock_file *lk, const char *path, int flags)
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:53 UTC
Permalink
Making errno when returning from commit_packed_refs() meaningful,
which should fix

* a bug in "git clone" where it prints strerror(errno) based on
errno, despite errno possibly being zero and potentially having
been clobbered by that point
* the same kind of bug in "git pack-refs"

and prepares for repack_without_refs() to get a meaningful
error message when commit_packed_refs() fails without falling into
the same bug.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 10 +++++++++-
refs.h | 1 +
2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index cc69581..7a815be 100644
--- a/refs.c
+++ b/refs.c
@@ -2239,11 +2239,16 @@ int lock_packed_refs(int flags)
return 0;
}

+/*
+ * Commit the packed refs changes.
+ * On error we must make sure that errno contains a meaningful value.
+ */
int commit_packed_refs(void)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(&ref_cache);
int error = 0;
+ int save_errno = 0;

if (!packed_ref_cache->lock)
die("internal error: packed-refs not locked");
@@ -2253,10 +2258,13 @@ int commit_packed_refs(void)
do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
0, write_packed_entry_fn,
&packed_ref_cache->lock->fd);
- if (commit_lock_file(packed_ref_cache->lock))
+ if (commit_lock_file(packed_ref_cache->lock)) {
+ save_errno = errno;
error = -1;
+ }
packed_ref_cache->lock = NULL;
release_packed_ref_cache(packed_ref_cache);
+ errno = save_errno;
return error;
}

diff --git a/refs.h b/refs.h
index 8d6cac7..e588ff8 100644
--- a/refs.h
+++ b/refs.h
@@ -98,6 +98,7 @@ extern void add_packed_ref(const char *refname, const unsigned char *sha1);
* Write the current version of the packed refs cache from memory to
* disk. The packed-refs file must already be locked for writing (see
* lock_packed_refs()). Return zero on success.
+ * Sets errno to something meaningful on error.
*/
extern int commit_packed_refs(void);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:49 UTC
Permalink
Update repack_without_refs to take an err argument and update it if there
is a failure. Pass the err variable from ref_transaction_commit to this
function so that callers can print a meaningful error message if _commit
fails due to this function.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/remote.c | 5 +++--
refs.c | 19 ++++++++++++++-----
refs.h | 3 ++-
3 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/builtin/remote.c b/builtin/remote.c
index c9102e8..401feb3 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -755,7 +755,7 @@ static int remove_branches(struct string_list *branches)
branch_names = xmalloc(branches->nr * sizeof(*branch_names));
for (i = 0; i < branches->nr; i++)
branch_names[i] = branches->items[i].string;
- result |= repack_without_refs(branch_names, branches->nr);
+ result |= repack_without_refs(branch_names, branches->nr, NULL);
free(branch_names);

for (i = 0; i < branches->nr; i++) {
@@ -1333,7 +1333,8 @@ static int prune_remote(const char *remote, int dry_run)
for (i = 0; i < states.stale.nr; i++)
delete_refs[i] = states.stale.items[i].util;
if (!dry_run)
- result |= repack_without_refs(delete_refs, states.stale.nr);
+ result |= repack_without_refs(delete_refs,
+ states.stale.nr, NULL);
free(delete_refs);
}

diff --git a/refs.c b/refs.c
index e9d53e4..67a0217 100644
--- a/refs.c
+++ b/refs.c
@@ -2456,12 +2456,12 @@ static int curate_packed_ref_fn(struct ref_entry *entry, void *cb_data)
return 0;
}

-int repack_without_refs(const char **refnames, int n)
+int repack_without_refs(const char **refnames, int n, struct strbuf *err)
{
struct ref_dir *packed;
struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
struct string_list_item *ref_to_delete;
- int i, removed = 0;
+ int i, ret, removed = 0;

/* Look for a packed ref */
for (i = 0; i < n; i++)
@@ -2473,6 +2473,11 @@ int repack_without_refs(const char **refnames, int n)
return 0; /* no refname exists in packed refs */

if (lock_packed_refs(0)) {
+ if (err) {
+ unable_to_lock_message(git_path("packed-refs"), errno,
+ err);
+ return -1;
+ }
unable_to_lock_error(git_path("packed-refs"), errno);
return error("cannot delete '%s' from packed refs", refnames[i]);
}
@@ -2499,12 +2504,16 @@ int repack_without_refs(const char **refnames, int n)
}

/* Write what remains */
- return commit_packed_refs();
+ ret = commit_packed_refs();
+ if (ret && err)
+ strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
+ strerror(errno));
+ return ret;
}

static int repack_without_ref(const char *refname)
{
- return repack_without_refs(&refname, 1);
+ return repack_without_refs(&refname, 1, NULL);
}

static int delete_ref_loose(struct ref_lock *lock, int flag)
@@ -3508,7 +3517,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
}
}

- ret |= repack_without_refs(delnames, delnum);
+ ret |= repack_without_refs(delnames, delnum, err);
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
clear_loose_ref_cache(&ref_cache);
diff --git a/refs.h b/refs.h
index 64f25d9..65f7637 100644
--- a/refs.h
+++ b/refs.h
@@ -122,7 +122,8 @@ extern void rollback_packed_refs(void);
*/
int pack_refs(unsigned int flags);

-extern int repack_without_refs(const char **refnames, int n);
+extern int repack_without_refs(const char **refnames, int n,
+ struct strbuf *err);

extern int ref_exists(const char *);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:56 UTC
Permalink
Make ref_update_reject_duplicates return any error that occurs through a
new strbuf argument. This means that when a transaction commit fails in
this function we will now be able to pass a helpful error message back to the
caller.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index 61570c9..115f143 100644
--- a/refs.c
+++ b/refs.c
@@ -3488,6 +3488,7 @@ static int ref_update_compare(const void *r1, const void *r2)
}

static int ref_update_reject_duplicates(struct ref_update **updates, int n,
+ struct strbuf *err,
enum action_on_err onerr)
{
int i;
@@ -3495,6 +3496,9 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
const char *str =
"Multiple updates for ref '%s' not allowed.";
+ if (err)
+ strbuf_addf(err, str, updates[i]->refname);
+
switch (onerr) {
case UPDATE_REFS_MSG_ON_ERR:
error(str, updates[i]->refname); break;
@@ -3525,7 +3529,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,

/* Copy, sort, and reject duplicate refs */
qsort(updates, n, sizeof(*updates), ref_update_compare);
- ret = ref_update_reject_duplicates(updates, n, onerr);
+ ret = ref_update_reject_duplicates(updates, n, err, onerr);
if (ret)
goto cleanup;
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:44 UTC
Permalink
ref_transaction_create|delete|update has no need to modify the sha1
arguments passed to it so it should use const unsigned char* instead
of unsigned char*.

Some functions, such as fast_forward_to(), already have its old/new
sha1 arguments as consts. This function will at some point need to
use ref_transaction_update() in which case this change is required.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 7 ++++---
refs.h | 7 ++++---
2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/refs.c b/refs.c
index d9cac6d..21ed465 100644
--- a/refs.c
+++ b/refs.c
@@ -3359,7 +3359,8 @@ static struct ref_update *add_update(struct ref_transaction *transaction,

void ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
- unsigned char *new_sha1, unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
int flags, int have_old)
{
struct ref_update *update = add_update(transaction, refname);
@@ -3373,7 +3374,7 @@ void ref_transaction_update(struct ref_transaction *transaction,

void ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
- unsigned char *new_sha1,
+ const unsigned char *new_sha1,
int flags)
{
struct ref_update *update = add_update(transaction, refname);
@@ -3387,7 +3388,7 @@ void ref_transaction_create(struct ref_transaction *transaction,

void ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
- unsigned char *old_sha1,
+ const unsigned char *old_sha1,
int flags, int have_old)
{
struct ref_update *update = add_update(transaction, refname);
diff --git a/refs.h b/refs.h
index 7d946f6..8c7f9c4 100644
--- a/refs.h
+++ b/refs.h
@@ -240,7 +240,8 @@ struct ref_transaction *ref_transaction_begin(void);
*/
void ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
- unsigned char *new_sha1, unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
int flags, int have_old);

/*
@@ -251,7 +252,7 @@ void ref_transaction_update(struct ref_transaction *transaction,
*/
void ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
- unsigned char *new_sha1,
+ const unsigned char *new_sha1,
int flags);

/*
@@ -261,7 +262,7 @@ void ref_transaction_create(struct ref_transaction *transaction,
*/
void ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
- unsigned char *old_sha1,
+ const unsigned char *old_sha1,
int flags, int have_old);

/*
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:42 UTC
Permalink
We do not yet need both a rollback and a free function for transactions.
Remove ref_transaction_rollback and use ref_transaction_free instead.

At a later stage we may reintroduce a rollback function if we want to start
adding reusable transactions and similar.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 7 +------
refs.h | 16 +++++++---------
2 files changed, 8 insertions(+), 15 deletions(-)

diff --git a/refs.c b/refs.c
index dc45774..6d841a0 100644
--- a/refs.c
+++ b/refs.c
@@ -3334,7 +3334,7 @@ struct ref_transaction *ref_transaction_begin(void)
return xcalloc(1, sizeof(struct ref_transaction));
}

-static void ref_transaction_free(struct ref_transaction *transaction)
+void ref_transaction_free(struct ref_transaction *transaction)
{
int i;

@@ -3345,11 +3345,6 @@ static void ref_transaction_free(struct ref_transaction *transaction)
free(transaction);
}

-void ref_transaction_rollback(struct ref_transaction *transaction)
-{
- ref_transaction_free(transaction);
-}
-
static struct ref_update *add_update(struct ref_transaction *transaction,
const char *refname)
{
diff --git a/refs.h b/refs.h
index 4e3050d..cfd1832 100644
--- a/refs.h
+++ b/refs.h
@@ -219,18 +219,12 @@ enum action_on_err {

/*
* Begin a reference transaction. The reference transaction must
- * eventually be commited using ref_transaction_commit() or rolled
- * back using ref_transaction_rollback().
+ * eventually be commited using ref_transaction_commit() or freed by
+ * calling ref_transaction_free().
*/
struct ref_transaction *ref_transaction_begin(void);

/*
- * Roll back a ref_transaction and free all associated data.
- */
-void ref_transaction_rollback(struct ref_transaction *transaction);
-
-
-/*
* The following functions add a reference check or update to a
* ref_transaction. In all of them, refname is the name of the
* reference to be affected. The functions make internal copies of
@@ -238,7 +232,6 @@ void ref_transaction_rollback(struct ref_transaction *transaction);
* can be REF_NODEREF; it is passed to update_ref_lock().
*/

-
/*
* Add a reference update to transaction. new_sha1 is the value that
* the reference should have after the update, or zeros if it should
@@ -280,6 +273,11 @@ void ref_transaction_delete(struct ref_transaction *transaction,
int ref_transaction_commit(struct ref_transaction *transaction,
const char *msg, enum action_on_err onerr);

+/*
+ * Free an existing transaction and all associated data.
+ */
+void ref_transaction_free(struct ref_transaction *transaction);
+
/** Lock a ref and then write its file */
int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:21 UTC
Permalink
Add an err argument to delete_loose_ref so that we can pass a descriptive
error string back to the caller. Pass the err argument from transaction
commit to this function so that transaction users will have a nice error
string if the transaction failed due to delete_loose_ref.

Add a new function unlink_or_err that we can call from delete_ref_loose. This
function is similar to unlink_or_warn except that we can pass it an err
argument. If err is non-NULL the function will populate err instead of
printing a warning().

Simplify warn_if_unremovable.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 33 ++++++++++++++++++++++++++++-----
wrapper.c | 14 ++++++--------
2 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/refs.c b/refs.c
index 92a06d4..c7d1f3e 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,16 +2544,38 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}

-static int delete_ref_loose(struct ref_lock *lock, int flag)
+static int add_err_if_unremovable(const char *op, const char *file,
+ struct strbuf *e, int rc)
+{
+ int err = errno;
+ if (rc < 0 && errno != ENOENT) {
+ strbuf_addf(e, "unable to %s %s: %s",
+ op, file, strerror(errno));
+ errno = err;
+ return -1;
+ }
+ return 0;
+}
+
+static int unlink_or_err(const char *file, struct strbuf *err)
+{
+ if (err)
+ return add_err_if_unremovable("unlink", file, err,
+ unlink(file));
+ else
+ return unlink_or_warn(file);
+}
+
+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
- int err, i = strlen(lock->lk->filename) - 5; /* .lock */
+ int res, i = strlen(lock->lk->filename) - 5; /* .lock */

lock->lk->filename[i] = 0;
- err = unlink_or_warn(lock->lk->filename);
+ res = unlink_or_err(lock->lk->filename, err);
lock->lk->filename[i] = '.';
- if (err && errno != ENOENT)
+ if (res)
return 1;
}
return 0;
@@ -3603,7 +3625,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];

if (update->lock) {
- ret |= delete_ref_loose(update->lock, update->type);
+ ret |= delete_ref_loose(update->lock, update->type,
+ err);
if (!(update->flags & REF_ISPRUNING))
delnames[delnum++] = update->lock->ref_name;
}
diff --git a/wrapper.c b/wrapper.c
index bc1bfb8..740e193 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -429,14 +429,12 @@ int xmkstemp_mode(char *template, int mode)

static int warn_if_unremovable(const char *op, const char *file, int rc)
{
- if (rc < 0) {
- int err = errno;
- if (ENOENT != err) {
- warning("unable to %s %s: %s",
- op, file, strerror(errno));
- errno = err;
- }
- }
+ int err;
+ if (rc >= 0 || errno == ENOENT)
+ return rc;
+ err = errno;
+ warning("unable to %s %s: %s", op, file, strerror(errno));
+ errno = err;
return rc;
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 14:19:42 UTC
Permalink
Post by Ronnie Sahlberg
Add an err argument to delete_loose_ref so that we can pass a descriptive
error string back to the caller. Pass the err argument from transaction
commit to this function so that transaction users will have a nice error
string if the transaction failed due to delete_loose_ref.
Add a new function unlink_or_err that we can call from delete_ref_loose. This
function is similar to unlink_or_warn except that we can pass it an err
argument. If err is non-NULL the function will populate err instead of
printing a warning().
Simplify warn_if_unremovable.
The change to warn_if_unremovable() is orthogonal to the rest of the
commit and should be a separate commit.
Post by Ronnie Sahlberg
---
refs.c | 33 ++++++++++++++++++++++++++++-----
wrapper.c | 14 ++++++--------
2 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index 92a06d4..c7d1f3e 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,16 +2544,38 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}
-static int delete_ref_loose(struct ref_lock *lock, int flag)
+static int add_err_if_unremovable(const char *op, const char *file,
+ struct strbuf *e, int rc)
This function is only used once. Given also that its purpose is not
that obvious from its signature, it seems to me that the code would be
easier to read if it were inlined.
Post by Ronnie Sahlberg
+{
+ int err = errno;
+ if (rc < 0 && errno != ENOENT) {
+ strbuf_addf(e, "unable to %s %s: %s",
+ op, file, strerror(errno));
+ errno = err;
+ return -1;
+ }
+ return 0;
+}
+
+static int unlink_or_err(const char *file, struct strbuf *err)
The name of this function is misleading; it sounds like it will try to
unlink the file and if not possible call error(). Maybe a name like
"unlink_or_report" would be less prejudicial.

It might also make sense to move this function to wrapper.c and
implement unlink_or_warn() in terms of it rather than vice versa.
Post by Ronnie Sahlberg
+{
+ if (err)
+ return add_err_if_unremovable("unlink", file, err,
+ unlink(file));
+ else
+ return unlink_or_warn(file);
+}
+
+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
- int err, i = strlen(lock->lk->filename) - 5; /* .lock */
+ int res, i = strlen(lock->lk->filename) - 5; /* .lock */
lock->lk->filename[i] = 0;
- err = unlink_or_warn(lock->lk->filename);
+ res = unlink_or_err(lock->lk->filename, err);
lock->lk->filename[i] = '.';
- if (err && errno != ENOENT)
+ if (res)
return 1;
}
return 0;
@@ -3603,7 +3625,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];
if (update->lock) {
- ret |= delete_ref_loose(update->lock, update->type);
+ ret |= delete_ref_loose(update->lock, update->type,
+ err);
if (!(update->flags & REF_ISPRUNING))
delnames[delnum++] = update->lock->ref_name;
}
diff --git a/wrapper.c b/wrapper.c
index bc1bfb8..740e193 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -429,14 +429,12 @@ int xmkstemp_mode(char *template, int mode)
static int warn_if_unremovable(const char *op, const char *file, int rc)
{
- if (rc < 0) {
- int err = errno;
- if (ENOENT != err) {
- warning("unable to %s %s: %s",
- op, file, strerror(errno));
- errno = err;
- }
- }
+ int err;
+ if (rc >= 0 || errno == ENOENT)
+ return rc;
+ err = errno;
+ warning("unable to %s %s: %s", op, file, strerror(errno));
+ errno = err;
return rc;
}
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-16 18:53:33 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Add an err argument to delete_loose_ref so that we can pass a descriptive
error string back to the caller. Pass the err argument from transaction
commit to this function so that transaction users will have a nice error
string if the transaction failed due to delete_loose_ref.
Add a new function unlink_or_err that we can call from delete_ref_loose. This
function is similar to unlink_or_warn except that we can pass it an err
argument. If err is non-NULL the function will populate err instead of
printing a warning().
Simplify warn_if_unremovable.
The change to warn_if_unremovable() is orthogonal to the rest of the
commit and should be a separate commit.
Done.
I split it into three patches.
One patch for the warn_if_removable change
another patch to add unlink_or_msg to wrapper.c
and a final patch to change delete_loose_ref.
Post by Michael Haggerty
Post by Ronnie Sahlberg
---
refs.c | 33 ++++++++++++++++++++++++++++-----
wrapper.c | 14 ++++++--------
2 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/refs.c b/refs.c
index 92a06d4..c7d1f3e 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,16 +2544,38 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}
-static int delete_ref_loose(struct ref_lock *lock, int flag)
+static int add_err_if_unremovable(const char *op, const char *file,
+ struct strbuf *e, int rc)
This function is only used once. Given also that its purpose is not
that obvious from its signature, it seems to me that the code would be
easier to read if it were inlined.
Post by Ronnie Sahlberg
+{
+ int err = errno;
+ if (rc < 0 && errno != ENOENT) {
+ strbuf_addf(e, "unable to %s %s: %s",
+ op, file, strerror(errno));
+ errno = err;
+ return -1;
+ }
+ return 0;
+}
+
+static int unlink_or_err(const char *file, struct strbuf *err)
The name of this function is misleading; it sounds like it will try to
unlink the file and if not possible call error(). Maybe a name like
"unlink_or_report" would be less prejudicial.
It might also make sense to move this function to wrapper.c and
implement unlink_or_warn() in terms of it rather than vice versa.
Post by Ronnie Sahlberg
+{
+ if (err)
+ return add_err_if_unremovable("unlink", file, err,
+ unlink(file));
+ else
+ return unlink_or_warn(file);
+}
+
+static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
/* loose */
- int err, i = strlen(lock->lk->filename) - 5; /* .lock */
+ int res, i = strlen(lock->lk->filename) - 5; /* .lock */
lock->lk->filename[i] = 0;
- err = unlink_or_warn(lock->lk->filename);
+ res = unlink_or_err(lock->lk->filename, err);
lock->lk->filename[i] = '.';
- if (err && errno != ENOENT)
+ if (res)
return 1;
}
return 0;
@@ -3603,7 +3625,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];
if (update->lock) {
- ret |= delete_ref_loose(update->lock, update->type);
+ ret |= delete_ref_loose(update->lock, update->type,
+ err);
if (!(update->flags & REF_ISPRUNING))
delnames[delnum++] = update->lock->ref_name;
}
diff --git a/wrapper.c b/wrapper.c
index bc1bfb8..740e193 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -429,14 +429,12 @@ int xmkstemp_mode(char *template, int mode)
static int warn_if_unremovable(const char *op, const char *file, int rc)
{
- if (rc < 0) {
- int err = errno;
- if (ENOENT != err) {
- warning("unable to %s %s: %s",
- op, file, strerror(errno));
- errno = err;
- }
- }
+ int err;
+ if (rc >= 0 || errno == ENOENT)
+ return rc;
+ err = errno;
+ warning("unable to %s %s: %s", op, file, strerror(errno));
+ errno = err;
return rc;
}
Michael
--
Michael Haggerty
Ronnie Sahlberg
2014-06-20 14:43:29 UTC
Permalink
No external users call write_ref_sha1 any more so lets declare it static.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 10 ++++++++--
refs.h | 3 ---
2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/refs.c b/refs.c
index 74a55b5..5a0b138 100644
--- a/refs.c
+++ b/refs.c
@@ -2665,6 +2665,9 @@ static int rename_tmp_log(const char *newrefname)
return 0;
}

+static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,
+ const char *logmsg);
+
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
unsigned char sha1[20], orig_sha1[20];
@@ -2914,8 +2917,11 @@ static int is_branch(const char *refname)
return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
}

-/* This function must return a meaningful errno */
-int write_ref_sha1(struct ref_lock *lock,
+/*
+ * Writes sha1 into the ref specified by the lock. Makes sure that errno
+ * is sane on error.
+ */
+static int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
static char term = '\n';
diff --git a/refs.h b/refs.h
index 5c0543d..db463d0 100644
--- a/refs.h
+++ b/refs.h
@@ -203,9 +203,6 @@ extern int commit_ref(struct ref_lock *lock);
/** Release any lock taken but not written. **/
extern void unlock_ref(struct ref_lock *lock);

-/** Writes sha1 into the ref specified by the lock. **/
-extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
-
/*
* Setup reflog before using. Set errno to something meaningful on failure.
*/
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:19 UTC
Permalink
Change prune_ref to delete the ref using a ref transaction. To do this we also
need to add a new flag REF_ISPRUNING that will tell the transaction that we
do not want to delete this ref from the packed refs. This flag is private to
refs.c and not exposed to external callers.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 27 ++++++++++++++++++++-------
refs.h | 14 ++++++++++++--
2 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/refs.c b/refs.c
index 699f1f6..3d070d5 100644
--- a/refs.c
+++ b/refs.c
@@ -25,6 +25,11 @@ static unsigned char refname_disposition[256] = {
};

/*
+ * Used as a flag to ref_transaction_delete when a loose ref is being
+ * pruned.
+ */
+#define REF_ISPRUNING 0x0100
+/*
* Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is
* not legal. It is legal if it is something reasonable to have under
@@ -2379,17 +2384,24 @@ static void try_remove_empty_parents(char *name)
/* make sure nobody touched the ref, and unlink */
static void prune_ref(struct ref_to_prune *r)
{
- struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;

if (check_refname_format(r->name + 5, 0))
return;

- lock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);
- if (lock) {
- unlink_or_warn(git_path("%s", r->name));
- unlock_ref(lock);
- try_remove_empty_parents(r->name);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, r->name, r->sha1,
+ REF_ISPRUNING, 1, &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return;
}
+ ref_transaction_free(transaction);
+ try_remove_empty_parents(r->name);
}

static void prune_refs(struct ref_to_prune *r)
@@ -3599,8 +3611,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];

if (update->lock) {
- delnames[delnum++] = update->lock->ref_name;
ret |= delete_ref_loose(update->lock, update->type);
+ if (!(update->flags & REF_ISPRUNING))
+ delnames[delnum++] = update->lock->ref_name;
}
}

diff --git a/refs.h b/refs.h
index 4ac4a7d..3b321c2 100644
--- a/refs.h
+++ b/refs.h
@@ -177,9 +177,19 @@ extern int ref_exists(const char *);
*/
extern int peel_ref(const char *refname, unsigned char *sha1);

-/** Locks any ref (for 'HEAD' type refs). */
+/*
+ * Flags controlling lock_any_ref_for_update(), ref_transaction_update(),
+ * ref_transaction_create(), etc.
+ * REF_NODEREF: act on the ref directly, instead of dereferencing
+ * symbolic references.
+ *
+ * Flags >= 0x100 are reserved for internal use.
+ */
#define REF_NODEREF 0x01
-/* errno is set to something meaningful on failure */
+/*
+ * Locks any ref (for 'HEAD' type refs) and sets errno to something
+ * meaningful on failure.
+ */
extern struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:20 UTC
Permalink
Change delete_ref to use a ref transaction for the deletion. At the same time
since we no longer have any callers of repack_without_ref we can now delete
this function.

Change delete_ref to return 0 on success and 1 on failure instead of the
previous 0 on success either 1 or -1 on failure.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 34 +++++++++++++---------------------
1 file changed, 13 insertions(+), 21 deletions(-)

diff --git a/refs.c b/refs.c
index 3d070d5..92a06d4 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,11 +2544,6 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}

-static int repack_without_ref(const char *refname)
-{
- return repack_without_refs(&refname, 1, NULL);
-}
-
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
@@ -2566,24 +2561,21 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)

int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
{
- struct ref_lock *lock;
- int ret = 0, flag = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;

- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
- if (!lock)
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname, sha1, delopt,
+ sha1 && !is_null_sha1(sha1), &err) ||
+ ref_transaction_commit(transaction, NULL, &err)) {
+ error("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
return 1;
- ret |= delete_ref_loose(lock, flag);
-
- /* removing the loose one could have resurrected an earlier
- * packed one. Also, if it was not loose we need to repack
- * without it.
- */
- ret |= repack_without_ref(lock->ref_name);
-
- unlink_or_warn(git_path("logs/%s", lock->ref_name));
- clear_loose_ref_cache(&ref_cache);
- unlock_ref(lock);
- return ret;
+ }
+ ref_transaction_free(transaction);
+ return 0;
}

/*
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 13:52:20 UTC
Permalink
Post by Ronnie Sahlberg
Change delete_ref to use a ref transaction for the deletion. At the same time
since we no longer have any callers of repack_without_ref we can now delete
this function.
Change delete_ref to return 0 on success and 1 on failure instead of the
previous 0 on success either 1 or -1 on failure.
---
refs.c | 34 +++++++++++++---------------------
1 file changed, 13 insertions(+), 21 deletions(-)
diff --git a/refs.c b/refs.c
index 3d070d5..92a06d4 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,11 +2544,6 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}
-static int repack_without_ref(const char *refname)
-{
- return repack_without_refs(&refname, 1, NULL);
-}
-
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
@@ -2566,24 +2561,21 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
{
- struct ref_lock *lock;
- int ret = 0, flag = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
The old code checked that the old value of refname was sha1, regardless
of whether sha1 was null_sha1. Presumably callers never set sha1 to
null_sha1...
Post by Ronnie Sahlberg
- if (!lock)
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname, sha1, delopt,
+ sha1 && !is_null_sha1(sha1), &err) ||
...But the new code explicitly skips the check if sha1 is null_sha1.
This shouldn't make a practical difference, because presumably callers
never set sha1 to null_sha1. But given that the new policy elsewhere
for "delete" updates is that it is an error for old_sha1 to equal
null_sha1, it seems to me that this extra check shouldn't be here. So I
think this should be changed to

ref_transaction_delete(transaction, refname, sha1, delopt,
!!sha1, &err) ||
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 20:50:51 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Change delete_ref to use a ref transaction for the deletion. At the same time
since we no longer have any callers of repack_without_ref we can now delete
this function.
Change delete_ref to return 0 on success and 1 on failure instead of the
previous 0 on success either 1 or -1 on failure.
---
refs.c | 34 +++++++++++++---------------------
1 file changed, 13 insertions(+), 21 deletions(-)
diff --git a/refs.c b/refs.c
index 3d070d5..92a06d4 100644
--- a/refs.c
+++ b/refs.c
@@ -2544,11 +2544,6 @@ int repack_without_refs(const char **refnames, int n, struct strbuf *err)
return ret;
}
-static int repack_without_ref(const char *refname)
-{
- return repack_without_refs(&refname, 1, NULL);
-}
-
static int delete_ref_loose(struct ref_lock *lock, int flag)
{
if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
@@ -2566,24 +2561,21 @@ static int delete_ref_loose(struct ref_lock *lock, int flag)
int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
{
- struct ref_lock *lock;
- int ret = 0, flag = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
- lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
The old code checked that the old value of refname was sha1, regardless
of whether sha1 was null_sha1. Presumably callers never set sha1 to
null_sha1...
They sometimes do.
Post by Michael Haggerty
Post by Ronnie Sahlberg
- if (!lock)
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_delete(transaction, refname, sha1, delopt,
+ sha1 && !is_null_sha1(sha1), &err) ||
...But the new code explicitly skips the check if sha1 is null_sha1.
This shouldn't make a practical difference, because presumably callers
never set sha1 to null_sha1.
There are actually a few cases where callers do call delete_ref() with
sha1 == null_sha1.
For example fast-import.c:update_branch() will do this is the ref can
not be resolved.
It can also happen in builtin/update-ref.c where we are passing user
supplied data into the call to delete_ref.

So I think the current behaviour should be ok.

There are a few options we could do:
We could change the semantics for ref_transaction_update|delete and
start allowing
have_old==1
old_sha1==null_sha1
and have this behave the same way as
have_old==0
but I think that would be horrible I think.

We could also change all callers to delete_ref() to be careful to only
specify a sha1 IFF it is not null_sha1
but that would just mean we require all callers to do this type of check.
But that would also be fragile since if/when we get new callers to
delete_ref we risk breaking delete_ref if we are not careful.


I think the least bad option is to just have this check in
delete_ref() as now and have the semantics for delete_ref be that if
sha1 is either NULL or null_sha1 then it means we don't care what the
old value is.



regards
ronnie sahlberg
Ronnie Sahlberg
2014-06-20 14:43:23 UTC
Permalink
We call read_ref_full with a pointer to flags from rename_ref but since
we never actually use the returned flags we can just pass NULL here instead.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index 1f17a13..389a55f 100644
--- a/refs.c
+++ b/refs.c
@@ -2688,7 +2688,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
goto rollback;
}

- if (!read_ref_full(newrefname, sha1, 1, &flag) &&
+ if (!read_ref_full(newrefname, sha1, 1, NULL) &&
delete_ref(newrefname, sha1, REF_NODEREF)) {
if (errno==EISDIR) {
if (remove_empty_directories(git_path("%s", newrefname))) {
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:28 UTC
Permalink
Change s_update_ref to use a ref transaction for the ref update.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/fetch.c | 33 +++++++++++++++++++++++----------
1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index faa1233..52f1ebc 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -375,23 +375,36 @@ static int s_update_ref(const char *action,
{
char msg[1024];
char *rla = getenv("GIT_REFLOG_ACTION");
- static struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret, df_conflict = 0;

if (dry_run)
return 0;
if (!rla)
rla = default_rla.buf;
snprintf(msg, sizeof(msg), "%s: %s", rla, action);
- lock = lock_any_ref_for_update(ref->name,
- check_old ? ref->old_sha1 : NULL,
- 0, NULL);
- if (!lock)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
- if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
- return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
- STORE_REF_ERROR_OTHER;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref->name, ref->new_sha1,
+ ref->old_sha1, 0, check_old, msg, &err))
+ goto fail;
+
+ ret = ref_transaction_commit(transaction, &err);
+ if (ret == UPDATE_REFS_NAME_CONFLICT)
+ df_conflict = 1;
+ if (ret)
+ goto fail;
+
+ ref_transaction_free(transaction);
return 0;
+fail:
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return df_conflict ? STORE_REF_ERROR_DF_CONFLICT
+ : STORE_REF_ERROR_OTHER;
}

#define REFCOL_WIDTH 10
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:24 UTC
Permalink
Move the check for check_refname_format from lock_any_ref_for_update
to lock_ref_sha1_basic. At some later stage we will get rid of
lock_any_ref_for_update completely.

If lock_ref_sha1_basic fails the check_refname_format test, set errno to
EINVAL before returning NULL. This to guarantee that we will not return an
error without updating errno.

This leaves lock_any_ref_for_updates as a no-op wrapper which could be removed.
But this wrapper is also called from an external caller and we will soon
make changes to the signature to lock_ref_sha1_basic that we do not want to
expose to that caller.

This changes semantics for lock_ref_sha1_basic slightly. With this change
it is no longer possible to open a ref that has a badly name which breaks
any codepaths that tries to open and repair badly named refs. The normal refs
API should not allow neither creating nor accessing refs with invalid names.
If we need such recovery code we could add it as an option to git fsck and have
git fsck be the only sanctioned way of bypassing the normal API and checks.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/refs.c b/refs.c
index 389a55f..bccf8c3 100644
--- a/refs.c
+++ b/refs.c
@@ -2088,6 +2088,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int missing = 0;
int attempts_remaining = 3;

+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;

@@ -2179,8 +2184,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
{
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- return NULL;
return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 15:02:20 UTC
Permalink
Post by Ronnie Sahlberg
Move the check for check_refname_format from lock_any_ref_for_update
to lock_ref_sha1_basic. At some later stage we will get rid of
lock_any_ref_for_update completely.
If lock_ref_sha1_basic fails the check_refname_format test, set errno to
EINVAL before returning NULL. This to guarantee that we will not return an
error without updating errno.
This leaves lock_any_ref_for_updates as a no-op wrapper which could be removed.
But this wrapper is also called from an external caller and we will soon
make changes to the signature to lock_ref_sha1_basic that we do not want to
expose to that caller.
This changes semantics for lock_ref_sha1_basic slightly. With this change
it is no longer possible to open a ref that has a badly name which breaks
s/badly name/bad name,/
Post by Ronnie Sahlberg
any codepaths that tries to open and repair badly named refs. The normal refs
s/tries/try/
Post by Ronnie Sahlberg
API should not allow neither creating nor accessing refs with invalid names.
s/not allow neither/allow neither/
Post by Ronnie Sahlberg
If we need such recovery code we could add it as an option to git fsck and have
git fsck be the only sanctioned way of bypassing the normal API and checks.
I like the sentiment, but in the real world I'm not sure we can take
such a step based only on good intentions. Which callers would be
affected? Where is this "git fsck" code that would be needed to help
people rescue their repos?

I can also imagine that we will tighten up the check_refname_format
checks in the future; for example, I think it would be a good idea to
prohibit reference names that start with '-' because it is almost
impossible to work with them (their names look like command-line
options). If we ever make a change like that, we will need some amount
of tolerance in git versions around the transition.

So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.

Michael
Post by Ronnie Sahlberg
---
refs.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 389a55f..bccf8c3 100644
--- a/refs.c
+++ b/refs.c
@@ -2088,6 +2088,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int missing = 0;
int attempts_remaining = 3;
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
@@ -2179,8 +2184,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
{
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- return NULL;
return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
}
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-15 16:40:18 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Move the check for check_refname_format from lock_any_ref_for_update
to lock_ref_sha1_basic. At some later stage we will get rid of
lock_any_ref_for_update completely.
If lock_ref_sha1_basic fails the check_refname_format test, set errno to
EINVAL before returning NULL. This to guarantee that we will not return an
error without updating errno.
This leaves lock_any_ref_for_updates as a no-op wrapper which could be removed.
But this wrapper is also called from an external caller and we will soon
make changes to the signature to lock_ref_sha1_basic that we do not want to
expose to that caller.
This changes semantics for lock_ref_sha1_basic slightly. With this change
it is no longer possible to open a ref that has a badly name which breaks
s/badly name/bad name,/
Post by Ronnie Sahlberg
any codepaths that tries to open and repair badly named refs. The normal refs
s/tries/try/
Post by Ronnie Sahlberg
API should not allow neither creating nor accessing refs with invalid names.
s/not allow neither/allow neither/
Post by Ronnie Sahlberg
If we need such recovery code we could add it as an option to git fsck and have
git fsck be the only sanctioned way of bypassing the normal API and checks.
I like the sentiment, but in the real world I'm not sure we can take
such a step based only on good intentions. Which callers would be
affected? Where is this "git fsck" code that would be needed to help
people rescue their repos?
I think the repair things are already busted since a while, so I don't
think this will make things worse,
but I will change it to make it better than this patch and better than
current master, please see below.

<current git>
$ cp .git/refs/heads/master .git/refs/heads/master.....@\*@\\.
$ git branch
fatal: Reference has invalid format: 'refs/heads/master.....@*@\.'
$ git branch -D master.....@\*@\\.
error: branch 'master.....@*@\.' not found.

git fsck does report that there is a problem with a broken ref
fatal: Reference has invalid format: 'refs/heads/master.....@*@\.'

But I don't think there is any way to fix this other than manually
deleting the file from the command line.
(this is because we need to do a resolve_ref_unsafe which will not
work and if we can not do a resolve_ref_unsafe we can not delete the
ref,
there is also the issue where we can not read the ref into ref-cache)


What I suggest doing here is to create a new patch towards the end of
the series that will :
* change the resolve_ref_unsafe(... , int reading, ...) argument to be
a bitmask of flags with
#define RESOLVE_REF_READING 0x01 (== current flag)
#define RESOLVE_REF_ALLOW_BAD_NAME 0x02 (== disable checking the
refname format during resolve)
* then provide the flag for RESOLVE_REF_ALLOW_BAD_NAME for the cases
where we try to resolve a ref in builtin/branch.c where we try to
delete a ref

* then also pass the same flag to lock_ref_sha1_basic when we are
deleting a ref from transaction_commit so we can disable the "check
refname" check there too.

I think that is all that is needed but I will see if there are
additional changes needed once I implement it.



This will mean that the semantics will become :
* you can not create or access a branch with a bad name since both
resolving it and locking it will fail.
* but you can always delete a branch regardless of whether the name is
good or bad.
(this will also mean that you will be able to rename a bad branch name
to a new good name)

which I think would be pretty sane semantics.


I will implement these changes as a separate patch.

Comments?


regards
ronnie sahlberg
Post by Michael Haggerty
I can also imagine that we will tighten up the check_refname_format
checks in the future; for example, I think it would be a good idea to
prohibit reference names that start with '-' because it is almost
impossible to work with them (their names look like command-line
options). If we ever make a change like that, we will need some amount
of tolerance in git versions around the transition.
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
Michael
Post by Ronnie Sahlberg
---
refs.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index 389a55f..bccf8c3 100644
--- a/refs.c
+++ b/refs.c
@@ -2088,6 +2088,11 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
int missing = 0;
int attempts_remaining = 3;
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;
@@ -2179,8 +2184,6 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
{
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
- return NULL;
return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
}
--
Michael Haggerty
Jonathan Nieder
2014-07-15 18:07:09 UTC
Permalink
Post by Ronnie Sahlberg
What I suggest doing here is to create a new patch towards the end of
* change the resolve_ref_unsafe(... , int reading, ...) argument to be
a bitmask of flags with
#define RESOLVE_REF_READING 0x01 (== current flag)
#define RESOLVE_REF_ALLOW_BAD_NAME 0x02 (== disable checking the
refname format during resolve)
* then provide the flag for RESOLVE_REF_ALLOW_BAD_NAME for the cases
where we try to resolve a ref in builtin/branch.c where we try to
delete a ref
* then also pass the same flag to lock_ref_sha1_basic when we are
deleting a ref from transaction_commit so we can disable the "check
refname" check there too.
Yeah, that sounds lovely.

I wasn't able to reproduce a regression or convince myself there is
one so I think it's okay if that happens in a separate series. But
doing it now would be fine, too.

Thanks,
Jonathan
Jonathan Nieder
2014-07-15 18:04:24 UTC
Permalink
Post by Michael Haggerty
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
The recovery code has been broken for a while, so I don't see harm
in this change.

How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.

In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable? The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?

Thanks,
Jonathan
Junio C Hamano
2014-07-15 18:34:11 UTC
Permalink
Post by Jonathan Nieder
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
Good thinking.
Post by Jonathan Nieder
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable? The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
I somehow thought that was what we have always designed for, which
DO_FOR_EACH_INCLUDE_BROKEN was a part of.
Ronnie Sahlberg
2014-07-15 19:35:28 UTC
Permalink
Post by Junio C Hamano
Post by Jonathan Nieder
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
Good thinking.
Post by Jonathan Nieder
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable? The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
I somehow thought that was what we have always designed for, which
DO_FOR_EACH_INCLUDE_BROKEN was a part of.
I think that include broken only handles the case where the ref itself
is bad, not when the refname is bad.
I.e. it affects cases where the sha1 does not exist or the symref
points to nowhere.
Ronnie Sahlberg
2014-07-15 19:34:08 UTC
Permalink
Post by Jonathan Nieder
Post by Michael Haggerty
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
The recovery code has been broken for a while, so I don't see harm
in this change.
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable?
That should be doable. Ill add these repairs as 3-4 new patches at the
end of the current patch series.
Post by Jonathan Nieder
The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
Right now, "git branch" will error and abort right away when it
finds the first bad ref. I haven't checked the exact spot it does this
yet but I suspect it is when building the ref-cache.
I will solve the cases for create and delete for now.


What/how to handle iterables will require more thought.
Right now, since these refs will be filtered out and never end up in
ref-cache, either from loose refs or from packed refs
it does mean that anyone that uses an iterator is guaranteed to only
get refs with valid names passed back to them.
We would need to audit all code that uses iterators and make sure it
can handle the case where the callback is suddenly
invoked with a bad refname.
Post by Jonathan Nieder
Thanks,
Jonathan
Ronnie Sahlberg
2014-07-15 20:58:03 UTC
Permalink
Post by Ronnie Sahlberg
Post by Jonathan Nieder
Post by Michael Haggerty
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
The recovery code has been broken for a while, so I don't see harm
in this change.
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable?
That should be doable. Ill add these repairs as 3-4 new patches at the
end of the current patch series.
Post by Jonathan Nieder
The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
Right now, "git branch" will error and abort right away when it
finds the first bad ref. I haven't checked the exact spot it does this
yet but I suspect it is when building the ref-cache.
I will solve the cases for create and delete for now.
What/how to handle iterables will require more thought.
Right now, since these refs will be filtered out and never end up in
ref-cache, either from loose refs or from packed refs
it does mean that anyone that uses an iterator is guaranteed to only
get refs with valid names passed back to them.
We would need to audit all code that uses iterators and make sure it
can handle the case where the callback is suddenly
invoked with a bad refname.
Post by Jonathan Nieder
Thanks,
Jonathan
The following seems to do the trick to allow deleting a bad ref. We
would need something for the iterator too.
Since this touches the same areas that my ~100 other ref transaction
patches that are queued up do, I
would like to wait applying this patch until once the next few series
are finished and merged.
(to avoid having to do a lot of rebases and fix legio of merge
conflicts that this would introduce in my branches).


Treat this as an approximation on what the fix to repair git branch -D
will look like once the time comes.

regards
ronnie sahlberg



===

commit a2514213999a192c9995a3a5e1479d7d09e2c083
Author: Ronnie Sahlberg <***@google.com>
Date: Tue Jul 15 12:59:36 2014 -0700

refs.c: fix handling of badly named refs

We currently do not handle badly named refs well :
$ cp .git/refs/heads/master .git/refs/heads/master.....@\*@\\.
$ git branch
fatal: Reference has invalid format: 'refs/heads/master.....@*@\.'
$ git branch -D master.....@\*@\\.
error: branch 'master.....@*@\.' not found.

But we can not really recover from a badly named ref with less than
manually deleting the .git/refs/heads/<refname> file.

Change resolve_ref_unsafe to take a flags field instead of a 'reading'
boolean and update all callers that used a non-zero value for reading
to pass the flag RESOLVE_REF_READING instead.
Add another flag RESOLVE_REF_ALLOW_BAD_NAME that will make
resolve_ref_unsafe skip checking the refname for sanity and use this
from branch.c so that we will be able to call resolve_ref_unsafe on such
refs when trying to delete it.
Add checks for refname sanity when updating (not deleting) a ref in
ref_transaction_update and in ref_transaction_create to make the transaction
fail if an attempt is made to create/update a badly named ref.
Since all ref changes will later go through the transaction layer this means
we no longer need to check for and fail for bad refnames in
lock_ref_sha1_basic.

Change lock_ref_sha1_basic to not fail for bad refnames. Just check the
refname, and print an error, and remember that the refname is bad so that
we can skip calling verify_lock().

Signed-off-by: Ronnie Sahlberg <***@google.com>

diff --git a/builtin/blame.c b/builtin/blame.c
index 662e3fe..76340e2 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -2278,7 +2278,7 @@ static struct commit
*fake_working_tree_commit(struct diff_options *opt,
commit->object.type = OBJ_COMMIT;
parent_tail = &commit->parents;

- if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+ if (!resolve_ref_unsafe("HEAD", head_sha1, RESOLVE_REF_READING, NULL))
die("no such ref: HEAD");

parent_tail = append_parent(parent_tail, head_sha1);
diff --git a/builtin/branch.c b/builtin/branch.c
index 652b1d2..5c95656 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -129,7 +129,8 @@ static int branch_merged(int kind, const char *name,
branch->merge[0] &&
branch->merge[0]->dst &&
(reference_name = reference_name_to_free =
- resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL)
+ resolve_refdup(branch->merge[0]->dst, sha1,
+ RESOLVE_REF_READING, NULL)) != NULL)
reference_rev = lookup_commit_reference(sha1);
}
if (!reference_rev)
@@ -233,7 +234,8 @@ static int delete_branches(int argc, const char
**argv, int force, int kinds,
free(name);

name = mkpathdup(fmt, bname.buf);
- target = resolve_ref_unsafe(name, sha1, 0, &flags);
+ target = resolve_ref_unsafe(name, sha1,
+ RESOLVE_REF_ALLOW_BAD_NAME, &flags);
if (!target ||
(!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) {
error(remote_branch
diff --git a/builtin/clone.c b/builtin/clone.c
index b12989d..f7307e6 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -622,7 +622,7 @@ static int checkout(void)
if (option_no_checkout)
return 0;

- head = resolve_refdup("HEAD", sha1, 1, NULL);
+ head = resolve_refdup("HEAD", sha1, RESOLVE_REF_READING, NULL);
if (!head) {
warning(_("remote HEAD refers to nonexistent ref, "
"unable to checkout.\n"));
diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c
index 3906eda..d8ab177 100644
--- a/builtin/fmt-merge-msg.c
+++ b/builtin/fmt-merge-msg.c
@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,

/* get current branch */
current_branch = current_branch_to_free =
- resolve_refdup("HEAD", head_sha1, 1, NULL);
+ resolve_refdup("HEAD", head_sha1, RESOLVE_REF_READING, NULL);
if (!current_branch)
die("No current branch");
if (starts_with(current_branch, "refs/heads/"))
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 4135980..a5833fd 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -649,7 +649,8 @@ static void populate_value(struct refinfo *ref)

if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
unsigned char unused1[20];
- ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL);
+ ref->symref = resolve_refdup(ref->refname, unused1,
+ RESOLVE_REF_READING, NULL);
if (!ref->symref)
ref->symref = "";
}
@@ -707,7 +708,8 @@ static void populate_value(struct refinfo *ref)
const char *head;
unsigned char sha1[20];

- head = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+ head = resolve_ref_unsafe("HEAD", sha1,
+ RESOLVE_REF_READING, NULL);
if (!strcmp(ref->refname, head))
v->s = "*";
else
diff --git a/builtin/log.c b/builtin/log.c
index a7ba211..92db809 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1395,7 +1395,8 @@ int cmd_format_patch(int argc, const char
**argv, const char *prefix)
if (check_head) {
unsigned char sha1[20];
const char *ref;
- ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
+ ref = resolve_ref_unsafe("HEAD", sha1,
+ RESOLVE_REF_READING, NULL);
if (ref && starts_with(ref, "refs/heads/"))
branch_name = xstrdup(ref + strlen("refs/heads/"));
else
diff --git a/builtin/remote.c b/builtin/remote.c
index 401feb3..be8ebac 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -568,7 +568,8 @@ static int read_remote_branches(const char *refname,
strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, xstrdup(refname));
- symref = resolve_ref_unsafe(refname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(refname, orig_sha1,
+ RESOLVE_REF_READING, &flag);
if (flag & REF_ISSYMREF)
item->util = xstrdup(symref);
else
@@ -704,7 +705,7 @@ static int mv(int argc, const char **argv)
int flag = 0;
unsigned char sha1[20];

- read_ref_full(item->string, sha1, 1, &flag);
+ read_ref_full(item->string, sha1, RESOLVE_REF_READING, &flag);
if (!(flag & REF_ISSYMREF))
continue;
if (delete_ref(item->string, NULL, REF_NODEREF))
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index d873172..a9a5eb3 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -727,7 +727,8 @@ int cmd_show_branch(int ac, const char **av, const
char *prefix)
if (ac == 0) {
static const char *fake_av[2];

- fake_av[0] = resolve_refdup("HEAD", sha1, 1, NULL);
+ fake_av[0] = resolve_refdup("HEAD", sha1,
+ RESOLVE_REF_READING, NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
@@ -789,7 +790,8 @@ int cmd_show_branch(int ac, const char **av, const
char *prefix)
}
}

- head_p = resolve_ref_unsafe("HEAD", head_sha1, 1, NULL);
+ head_p = resolve_ref_unsafe("HEAD", head_sha1,
+ RESOLVE_REF_READING, NULL);
if (head_p) {
head_len = strlen(head_p);
memcpy(head, head_p, head_len + 1);
diff --git a/bundle.c b/bundle.c
index 1222952..8aaf5f8 100644
--- a/bundle.c
+++ b/bundle.c
@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header,
const char *path,
continue;
if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
continue;
- if (read_ref_full(e->name, sha1, 1, &flag))
+ if (read_ref_full(e->name, sha1, RESOLVE_REF_READING, &flag))
flag = 0;
display_ref = (flag & REF_ISSYMREF) ? e->name : ref;

diff --git a/cache.h b/cache.h
index 4ca4583..aab246d 100644
--- a/cache.h
+++ b/cache.h
@@ -948,7 +948,7 @@ extern int get_sha1_hex(const char *hex, unsigned
char *sha1);

extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer
result! */
extern int read_ref_full(const char *refname, unsigned char *sha1,
- int reading, int *flags);
+ int flags, int *ref_flag);
extern int read_ref(const char *refname, unsigned char *sha1);

/*
@@ -960,17 +960,17 @@ extern int read_ref(const char *refname,
unsigned char *sha1);
* or the input ref.
*
* If the reference cannot be resolved to an object, the behavior
- * depends on the "reading" argument:
+ * depends on the RESOLVE_REF_READING flag:
*
- * - If reading is set, return NULL.
+ * - If RESOLVE_REF_READING is set, return NULL.
*
- * - If reading is not set, clear sha1 and return the name of the last
- * reference name in the chain, which will either be a non-symbolic
+ * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
+ * the last reference name in the chain, which will either be a non-symbolic
* reference or an undefined reference. If this is a prelude to
* "writing" to the ref, the return value is the name of the ref
* that will actually be created or changed.
*
- * If flag is non-NULL, set the value that it points to the
+ * If ref_flag is non-NULL, set the value that it points to the
* combination of REF_ISPACKED (if the reference was found among the
* packed references) and REF_ISSYMREF (if the initial reference was a
* symbolic reference).
@@ -981,8 +981,10 @@ extern int read_ref(const char *refname, unsigned
char *sha1);
*
* errno is set to something meaningful on error.
*/
-extern const char *resolve_ref_unsafe(const char *ref, unsigned char
*sha1, int reading, int *flag);
-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int
reading, int *flag);
+#define RESOLVE_REF_READING 0x01
+#define RESOLVE_REF_ALLOW_BAD_NAME 0x02
+extern const char *resolve_ref_unsafe(const char *ref, unsigned char
*sha1, int flags, int *ref_flag);
+extern char *resolve_refdup(const char *ref, unsigned char *sha1, int
flags, int *ref_flag);

extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
diff --git a/http-backend.c b/http-backend.c
index d2c0a62..059f790 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -417,7 +417,8 @@ static int show_head_ref(const char *refname,
const unsigned char *sha1,

if (flag & REF_ISSYMREF) {
unsigned char unused[20];
- const char *target = resolve_ref_unsafe(refname, unused, 1, NULL);
+ const char *target = resolve_ref_unsafe(refname, unused,
+ RESOLVE_REF_READING, NULL);
const char *target_nons = strip_namespace(target);

strbuf_addf(buf, "ref: %s\n", target_nons);
diff --git a/reflog-walk.c b/reflog-walk.c
index 9ce8b53..d80a42a 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -48,7 +48,8 @@ static struct complete_reflogs
*read_complete_reflog(const char *ref)
unsigned char sha1[20];
const char *name;
void *name_to_free;
- name = name_to_free = resolve_refdup(ref, sha1, 1, NULL);
+ name = name_to_free = resolve_refdup(ref, sha1,
+ RESOLVE_REF_READING, NULL);
if (name) {
for_each_reflog_ent(name, read_one_reflog, reflogs);
free(name_to_free);
diff --git a/refs.c b/refs.c
index 221d8a7..dd9d61d 100644
--- a/refs.c
+++ b/refs.c
@@ -1194,7 +1194,8 @@ static void read_loose_refs(const char *dirname,
struct ref_dir *dir)
hashclr(sha1);
flag |= REF_ISBROKEN;
}
- } else if (read_ref_full(refname.buf, sha1, 1, &flag)) {
+ } else if (read_ref_full(refname.buf, sha1,
+ RESOLVE_REF_READING, &flag)) {
hashclr(sha1);
flag |= REF_ISBROKEN;
}
@@ -1346,21 +1347,21 @@ static const char
*handle_missing_loose_ref(const char *refname,
}

/* This function needs to return a meaningful errno on failure */
-const char *resolve_ref_unsafe(const char *refname, unsigned char
*sha1, int reading, int *flag)
+const char *resolve_ref_unsafe(const char *refname, unsigned char
*sha1, int flags, int *ref_flag)
{
int depth = MAXDEPTH;
ssize_t len;
char buffer[256];
static char refname_buffer[256];

- if (flag)
- *flag = 0;
+ if (ref_flag)
+ *ref_flag = 0;

- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ if (!(flags & RESOLVE_REF_ALLOW_BAD_NAME) &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
errno = EINVAL;
return NULL;
}
-
for (;;) {
char path[PATH_MAX];
struct stat st;
@@ -1387,7 +1388,8 @@ const char *resolve_ref_unsafe(const char
*refname, unsigned char *sha1, int rea
if (lstat(path, &st) < 0) {
if (errno == ENOENT)
return handle_missing_loose_ref(refname, sha1,
- reading, flag);
+ flags & RESOLVE_REF_READING,
+ ref_flag);
else
return NULL;
}
@@ -1407,8 +1409,8 @@ const char *resolve_ref_unsafe(const char
*refname, unsigned char *sha1, int rea
!check_refname_format(buffer, 0)) {
strcpy(refname_buffer, buffer);
refname = refname_buffer;
- if (flag)
- *flag |= REF_ISSYMREF;
+ if (ref_flag)
+ *ref_flag |= REF_ISSYMREF;
continue;
}
}
@@ -1453,21 +1455,21 @@ const char *resolve_ref_unsafe(const char
*refname, unsigned char *sha1, int rea
*/
if (get_sha1_hex(buffer, sha1) ||
(buffer[40] != '\0' && !isspace(buffer[40]))) {
- if (flag)
- *flag |= REF_ISBROKEN;
+ if (ref_flag)
+ *ref_flag |= REF_ISBROKEN;
errno = EINVAL;
return NULL;
}
return refname;
}
- if (flag)
- *flag |= REF_ISSYMREF;
+ if (ref_flag)
+ *ref_flag |= REF_ISSYMREF;
buf = buffer + 4;
while (isspace(*buf))
buf++;
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
- if (flag)
- *flag |= REF_ISBROKEN;
+ if (ref_flag)
+ *ref_flag |= REF_ISBROKEN;
errno = EINVAL;
return NULL;
}
@@ -1475,9 +1477,9 @@ const char *resolve_ref_unsafe(const char
*refname, unsigned char *sha1, int rea
}
}

-char *resolve_refdup(const char *ref, unsigned char *sha1, int
reading, int *flag)
+char *resolve_refdup(const char *ref, unsigned char *sha1, int flags,
int *ref_flag)
{
- const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
+ const char *ret = resolve_ref_unsafe(ref, sha1, flags, ref_flag);
return ret ? xstrdup(ret) : NULL;
}

@@ -1488,22 +1490,22 @@ struct ref_filter {
void *cb_data;
};

-int read_ref_full(const char *refname, unsigned char *sha1, int
reading, int *flags)
+int read_ref_full(const char *refname, unsigned char *sha1, int
flags, int *ref_flag)
{
- if (resolve_ref_unsafe(refname, sha1, reading, flags))
+ if (resolve_ref_unsafe(refname, sha1, flags, ref_flag))
return 0;
return -1;
}

int read_ref(const char *refname, unsigned char *sha1)
{
- return read_ref_full(refname, sha1, 1, NULL);
+ return read_ref_full(refname, sha1, RESOLVE_REF_READING, NULL);
}

int ref_exists(const char *refname)
{
unsigned char sha1[20];
- return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
+ return !!resolve_ref_unsafe(refname, sha1, RESOLVE_REF_READING, NULL);
}

static int filter_refs(const char *refname, const unsigned char
*sha1, int flags,
@@ -1617,7 +1619,7 @@ int peel_ref(const char *refname, unsigned char *sha1)
return 0;
}

- if (read_ref_full(refname, base, 1, &flag))
+ if (read_ref_full(refname, base, RESOLVE_REF_READING, &flag))
return -1;

/*
@@ -1783,7 +1785,7 @@ static int do_head_ref(const char *submodule,
each_ref_fn fn, void *cb_data)
return 0;
}

- if (!read_ref_full("HEAD", sha1, 1, &flag))
+ if (!read_ref_full("HEAD", sha1, RESOLVE_REF_READING, &flag))
return fn("HEAD", sha1, flag, cb_data);

return 0;
@@ -1863,7 +1865,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
int flag;

strbuf_addf(&buf, "%sHEAD", get_git_namespace());
- if (!read_ref_full(buf.buf, sha1, 1, &flag))
+ if (!read_ref_full(buf.buf, sha1, RESOLVE_REF_READING, &flag))
ret = fn(buf.buf, sha1, flag, cb_data);
strbuf_release(&buf);

@@ -1958,7 +1960,8 @@ int refname_match(const char *abbrev_name, const
char *full_name)
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
- if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ if (read_ref_full(lock->ref_name, lock->old_sha1,
+ mustexist ? RESOLVE_REF_READING : 0, NULL)) {
int save_errno = errno;
error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
@@ -2031,7 +2034,8 @@ int dwim_ref(const char *str, int len, unsigned
char *sha1, char **ref)

this_result = refs_found ? sha1_from_ref : sha1;
mksnpath(fullref, sizeof(fullref), *p, len, str);
- r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
+ r = resolve_ref_unsafe(fullref, this_result,
+ RESOLVE_REF_READING, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
@@ -2060,7 +2064,7 @@ int dwim_log(const char *str, int len, unsigned
char *sha1, char **log)
const char *ref, *it;

mksnpath(path, sizeof(path), *p, len, str);
- ref = resolve_ref_unsafe(path, hash, 1, NULL);
+ ref = resolve_ref_unsafe(path, hash, RESOLVE_REF_READING, NULL);
if (!ref)
continue;
if (reflog_exists(path))
@@ -2092,18 +2096,22 @@ static struct ref_lock
*lock_ref_sha1_basic(const char *refname,
int last_errno = 0;
int type, lflags;
int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
+ int resolve_flags;
int missing = 0;
int attempts_remaining = 3;
-
- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
- errno = EINVAL;
- return NULL;
- }
+ int bad_refname;

lock = xcalloc(1, sizeof(struct ref_lock));
lock->lock_fd = -1;

- refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
+ bad_refname = check_refname_format(refname, REFNAME_ALLOW_ONELEVEL);
+
+ resolve_flags = RESOLVE_REF_ALLOW_BAD_NAME;
+ if (mustexist)
+ resolve_flags |= RESOLVE_REF_READING;
+
+ refname = resolve_ref_unsafe(refname, lock->old_sha1, resolve_flags,
+ &type);
if (!refname && errno == EISDIR) {
/* we are trying to lock foo but we used to
* have foo/bar which now does not exist;
@@ -2116,7 +2124,8 @@ static struct ref_lock
*lock_ref_sha1_basic(const char *refname,
error("there are still refs under '%s'", orig_refname);
goto error_return;
}
- refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
+ refname = resolve_ref_unsafe(orig_refname, lock->old_sha1,
+ resolve_flags, &type);
}
if (type_p)
*type_p = type;
@@ -2180,6 +2189,8 @@ static struct ref_lock
*lock_ref_sha1_basic(const char *refname,
else
unable_to_lock_index_die(ref_file, errno);
}
+ if (bad_refname)
+ return lock;
return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;

error_return:
@@ -2680,7 +2691,8 @@ int rename_ref(const char *oldrefname, const
char *newrefname, const char *logms
if (log && S_ISLNK(loginfo.st_mode))
return error("reflog for %s is a symlink", oldrefname);

- symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
+ symref = resolve_ref_unsafe(oldrefname, orig_sha1,
+ RESOLVE_REF_READING, &flag);
if (flag & REF_ISSYMREF)
return error("refname %s is a symbolic ref, renaming it is not supported",
oldrefname);
@@ -2704,7 +2716,7 @@ int rename_ref(const char *oldrefname, const
char *newrefname, const char *logms
goto rollback;
}

- if (!read_ref_full(newrefname, sha1, 1, NULL) &&
+ if (!read_ref_full(newrefname, sha1, RESOLVE_REF_READING, NULL) &&
delete_ref(newrefname, sha1, REF_NODEREF)) {
if (errno==EISDIR) {
if (remove_empty_directories(git_path("%s", newrefname))) {
@@ -2982,7 +2994,8 @@ static int write_ref_sha1(struct ref_lock *lock,
unsigned char head_sha1[20];
int head_flag;
const char *head_ref;
- head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
+ head_ref = resolve_ref_unsafe("HEAD", head_sha1,
+ RESOLVE_REF_READING, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name))
log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -3468,6 +3481,12 @@ int ref_transaction_update(struct
ref_transaction *transaction,
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");

+ if (!is_null_sha1(new_sha1) &&
+ check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ strbuf_addf(err, "Bad refname: %s", refname);
+ return -1;
+ }
+
update = add_update(transaction, refname);
hashcpy(update->new_sha1, new_sha1);
update->flags = flags;
@@ -3493,6 +3512,11 @@ int ref_transaction_create(struct
ref_transaction *transaction,
if (!new_sha1 || is_null_sha1(new_sha1))
die("BUG: create ref with null new_sha1");

+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ strbuf_addf(err, "Bad refname: %s", refname);
+ return -1;
+ }
+
update = add_update(transaction, refname);

hashcpy(update->new_sha1, new_sha1);
diff --git a/remote.c b/remote.c
index ae04043..de84ac3 100644
--- a/remote.c
+++ b/remote.c
@@ -1121,7 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)
struct strbuf buf = STRBUF_INIT;
unsigned char sha1[20];

- const char *r = resolve_ref_unsafe(peer->name, sha1, 1, NULL);
+ const char *r = resolve_ref_unsafe(peer->name, sha1,
+ RESOLVE_REF_READING, NULL);
if (!r)
return NULL;

@@ -1182,7 +1183,8 @@ static int match_explicit(struct ref *src,
struct ref *dst,
unsigned char sha1[20];
int flag;

- dst_value = resolve_ref_unsafe(matched_src->name, sha1, 1, &flag);
+ dst_value = resolve_ref_unsafe(matched_src->name, sha1,
+ RESOLVE_REF_READING, &flag);
if (!dst_value ||
((flag & REF_ISSYMREF) &&
!starts_with(dst_value, "refs/heads/")))
diff --git a/sequencer.c b/sequencer.c
index 284059b..e1419bf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -365,7 +365,7 @@ static int is_index_unchanged(void)
unsigned char head_sha1[20];
struct commit *head_commit;

- if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+ if (!resolve_ref_unsafe("HEAD", head_sha1, RESOLVE_REF_READING, NULL))
return error(_("Could not resolve HEAD commit\n"));

head_commit = lookup_commit(head_sha1);
diff --git a/transport-helper.c b/transport-helper.c
index 84c616f..270ae28 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -889,7 +889,7 @@ static int push_refs_with_export(struct transport
*transport,
int flag;

/* Follow symbolic refs (mainly for HEAD). */
- name = resolve_ref_unsafe(ref->peer_ref->name, sha1, 1, &flag);
+ name = resolve_ref_unsafe(ref->peer_ref->name, sha1,
RESOLVE_REF_READING, &flag);
if (!name || !(flag & REF_ISSYMREF))
name = ref->peer_ref->name;

diff --git a/transport.c b/transport.c
index 325f03e..f40e950 100644
--- a/transport.c
+++ b/transport.c
@@ -168,7 +168,8 @@ static void set_upstreams(struct transport
*transport, struct ref *refs,
/* Follow symbolic refs (mainly for HEAD). */
localname = ref->peer_ref->name;
remotename = ref->name;
- tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
+ tmp = resolve_ref_unsafe(localname, sha,
+ RESOLVE_REF_READING, &flag);
if (tmp && flag & REF_ISSYMREF &&
starts_with(tmp, "refs/heads/"))
localname = tmp;
@@ -753,7 +754,7 @@ void transport_print_push_status(const char *dest,
struct ref *refs,
unsigned char head_sha1[20];
char *head;

- head = resolve_refdup("HEAD", head_sha1, 1, NULL);
+ head = resolve_refdup("HEAD", head_sha1, RESOLVE_REF_READING, NULL);

if (verbose) {
for (ref = refs; ref; ref = ref->next)
Michael Haggerty
2014-08-20 14:52:50 UTC
Permalink
Post by Ronnie Sahlberg
Post by Ronnie Sahlberg
Post by Jonathan Nieder
Post by Michael Haggerty
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
The recovery code has been broken for a while, so I don't see harm
in this change.
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable?
That should be doable. Ill add these repairs as 3-4 new patches at the
end of the current patch series.
Post by Jonathan Nieder
The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right? Then as long as iteration is not needed to implement
the command "git update-ref -d", it seems to me that it is OK if
iterating over a reference with a broken name causes a die().
Post by Ronnie Sahlberg
Post by Ronnie Sahlberg
Right now, "git branch" will error and abort right away when it
finds the first bad ref. I haven't checked the exact spot it does this
yet but I suspect it is when building the ref-cache.
I will solve the cases for create and delete for now.
What/how to handle iterables will require more thought.
Right now, since these refs will be filtered out and never end up in
ref-cache, either from loose refs or from packed refs
it does mean that anyone that uses an iterator is guaranteed to only
get refs with valid names passed back to them.
We would need to audit all code that uses iterators and make sure it
can handle the case where the callback is suddenly
invoked with a bad refname.
Post by Jonathan Nieder
Thanks,
Jonathan
The following seems to do the trick to allow deleting a bad ref. We
would need something for the iterator too.
Since this touches the same areas that my ~100 other ref transaction
patches that are queued up do, I
would like to wait applying this patch until once the next few series
are finished and merged.
(to avoid having to do a lot of rebases and fix legio of merge
conflicts that this would introduce in my branches).
Treat this as an approximation on what the fix to repair git branch -D
will look like once the time comes.
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-08-20 16:28:28 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Post by Ronnie Sahlberg
Post by Jonathan Nieder
Post by Michael Haggerty
So...I like the idea of enforcing refname checks at the lowest level
possible, but I think that the change you propose is too abrupt. I
think it needs either more careful analysis showing that it won't hurt
anybody, or some kind of tooling or non-strict mode that people can use
to fix their repositories.
The recovery code has been broken for a while, so I don't see harm
in this change.
How to take care of the recovery use case is another question. FWIW I
also would prefer if "git update-ref -d" or "git branch -D" could be
used to delete corrupt refs instead of having to use fsck (since a
fsck run can take a while), but that's a question for a later series.
In an ideal world, the low-level functions would allow *reading* and
*deleting* poorly named refs (even without any special flag) but not
creating them. Is that doable?
That should be doable. Ill add these repairs as 3-4 new patches at the
end of the current patch series.
Post by Jonathan Nieder
The main complication I can see is
iteration: would iteration skip poorly named refs and warn, or would
something more complicated be needed?
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right? Then as long as iteration is not needed to implement
the command "git update-ref -d", it seems to me that it is OK if
iterating over a reference with a broken name causes a die().
Post by Ronnie Sahlberg
Post by Ronnie Sahlberg
Right now, "git branch" will error and abort right away when it
finds the first bad ref. I haven't checked the exact spot it does this
yet but I suspect it is when building the ref-cache.
I will solve the cases for create and delete for now.
What/how to handle iterables will require more thought.
Right now, since these refs will be filtered out and never end up in
ref-cache, either from loose refs or from packed refs
it does mean that anyone that uses an iterator is guaranteed to only
get refs with valid names passed back to them.
We would need to audit all code that uses iterators and make sure it
can handle the case where the callback is suddenly
invoked with a bad refname.
Post by Jonathan Nieder
Thanks,
Jonathan
The following seems to do the trick to allow deleting a bad ref. We
would need something for the iterator too.
Since this touches the same areas that my ~100 other ref transaction
patches that are queued up do, I
would like to wait applying this patch until once the next few series
are finished and merged.
(to avoid having to do a lot of rebases and fix legio of merge
conflicts that this would introduce in my branches).
Treat this as an approximation on what the fix to repair git branch -D
will look like once the time comes.
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.
I only set this flag from builtin/branch.c so it should only be used
when a user runs 'git branch -D' from the command line.
All other places where we delete branches we should still be checking
the rename for badness.

That said, unless the "rules for good refname" changes in the future,
which is unlikely, is should be exceptionally rare that a user ends up
with a bad refname in the first place.
Perhaps my example I gave was bad since if you manually create bad
refs using echo > .git/refs/heads/... then you should probably know
how to fix it too and thus maybe we do not need this patch in the
first place.

Do you want me to delete this patch and resend this part of the series
? Or is the 'only works for branch -D from the commandline' sufficient
?
I have no strong feelings either way so I will just follow what you decide.


regards
ronnie sahlberg
Jonathan Nieder
2014-08-20 17:49:14 UTC
Permalink
Hi,
Post by Ronnie Sahlberg
Post by Michael Haggerty
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.
I only set this flag from builtin/branch.c so it should only be used
when a user runs 'git branch -D' from the command line.
All other places where we delete branches we should still be checking
the rename for badness.
Right, this should be safe for 'git branch -D' and 'git update-ref -d'.

But if we wanted to open it up in the future for 'git push --delete',
too, then it would be a way to break out of the repository on hosts where
people use git-shell instead of relying on filesystem permissions. And
that wouldn't be good.

I think elsewhere git has some checks for "does this pathname fall in
this directory". Could that be reused here, too, to make sure the
resolved path is under the resolved $GIT_DIR/refs directory?

Alternatively, when a ref being deleted doesn't meet the
'check-ref-format' checks, would it make sense to check that it is one
of the refs you can get by iteration?

Jonathan
Ronnie Sahlberg
2014-08-20 17:55:45 UTC
Permalink
Post by Jonathan Nieder
Hi,
Post by Ronnie Sahlberg
Post by Michael Haggerty
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.
I only set this flag from builtin/branch.c so it should only be used
when a user runs 'git branch -D' from the command line.
All other places where we delete branches we should still be checking
the rename for badness.
Right, this should be safe for 'git branch -D' and 'git update-ref -d'.
But if we wanted to open it up in the future for 'git push --delete',
too, then it would be a way to break out of the repository on hosts where
people use git-shell instead of relying on filesystem permissions. And
that wouldn't be good.
I think elsewhere git has some checks for "does this pathname fall in
this directory". Could that be reused here, too, to make sure the
resolved path is under the resolved $GIT_DIR/refs directory?
Alternatively, when a ref being deleted doesn't meet the
'check-ref-format' checks, would it make sense to check that it is one
of the refs you can get by iteration?
Good idea! I will add such a check using the iterator.
That means that "we can git branch -D anything that shows up in the
iterator regardless if the ref is badly named or unresolvable" which
sounds like fairly sane semantics.

Thanks!

Ronnie
Michael Haggerty
2014-08-20 18:34:43 UTC
Permalink
Post by Ronnie Sahlberg
Post by Michael Haggerty
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.
I only set this flag from builtin/branch.c so it should only be used
when a user runs 'git branch -D' from the command line.
All other places where we delete branches we should still be checking
the rename for badness.
That said, unless the "rules for good refname" changes in the future,
which is unlikely, is should be exceptionally rare that a user ends up
with a bad refname in the first place.
Perhaps my example I gave was bad since if you manually create bad
refs using echo > .git/refs/heads/... then you should probably know
how to fix it too and thus maybe we do not need this patch in the
first place.
Do you want me to delete this patch and resend this part of the series
? Or is the 'only works for branch -D from the commandline' sufficient
?
I have no strong feelings either way so I will just follow what you decide.
I think that if you run the refname through normalize_path_copy_len()
and that function returns (1) without an error, (2) without modifying
its argument, and (3) the result does not begin with a
has_dos_drive_prefix() or is_dir_sep(), then we should be safe against
directory traversal attacks. I suggest doing this kind of check even if
not doing the full check_refname_format() check.

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-08-21 19:42:45 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Post by Michael Haggerty
I'm a little worried that abandoning *all* refname checks could open us
up to somehow trying to delete a "reference" with a name like
"../../../../etc/passwd". Either such names have to be prohibited
somehow, or we have to be very sure that they can only come from trusted
sources.
I only set this flag from builtin/branch.c so it should only be used
when a user runs 'git branch -D' from the command line.
All other places where we delete branches we should still be checking
the rename for badness.
That said, unless the "rules for good refname" changes in the future,
which is unlikely, is should be exceptionally rare that a user ends up
with a bad refname in the first place.
Perhaps my example I gave was bad since if you manually create bad
refs using echo > .git/refs/heads/... then you should probably know
how to fix it too and thus maybe we do not need this patch in the
first place.
Do you want me to delete this patch and resend this part of the series
? Or is the 'only works for branch -D from the commandline' sufficient
?
I have no strong feelings either way so I will just follow what you decide.
I think that if you run the refname through normalize_path_copy_len()
and that function returns (1) without an error, (2) without modifying
its argument, and (3) the result does not begin with a
has_dos_drive_prefix() or is_dir_sep(), then we should be safe against
directory traversal attacks. I suggest doing this kind of check even if
not doing the full check_refname_format() check.
Good idea.
Let me add this.
Post by Michael Haggerty
Michael
--
Michael Haggerty
Junio C Hamano
2014-08-20 19:45:43 UTC
Permalink
Post by Michael Haggerty
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right?
Or read from a ref whose name has retroactively made invalid, in
order to create a similar but validly named ref to serve as its
replacement? So at least we need to give the users some way of
reading from them, in addition to deleting them, no?
Michael Haggerty
2014-08-20 20:11:21 UTC
Permalink
Post by Junio C Hamano
Post by Michael Haggerty
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right?
Or read from a ref whose name has retroactively made invalid, in
order to create a similar but validly named ref to serve as its
replacement? So at least we need to give the users some way of
reading from them, in addition to deleting them, no?
The die() error message would presumably include the name of the invalid
reference.

If we change the rules for reference names and a bunch of names become
invalid, then it would admittedly be cumbersome to run git N times to
find the N invalid names. But if we change the rules, then *at that
time* we could always build in iteration over broken reference names.

It's not that I have something against building it iteration over broken
reference names now, as long as it is clearly segregated from "normal"
iteration to prevent illegal names from getting loose in the code.

Michael
--
Michael Haggerty
***@alum.mit.edu
Junio C Hamano
2014-08-20 21:24:23 UTC
Permalink
Post by Michael Haggerty
Post by Junio C Hamano
Post by Michael Haggerty
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right?
Or read from a ref whose name has retroactively made invalid, in
order to create a similar but validly named ref to serve as its
replacement? So at least we need to give the users some way of
reading from them, in addition to deleting them, no?
The die() error message would presumably include the name of the invalid
reference.
If we change the rules for reference names and a bunch of names become
invalid, then it would admittedly be cumbersome to run git N times to
find the N invalid names. But if we change the rules, then *at that
time* we could always build in iteration over broken reference names.
It's not that I have something against building it iteration over broken
reference names now, as long as it is clearly segregated from "normal"
iteration to prevent illegal names from getting loose in the code.
Oh, I don't think it is that important for iterator to show broken
ones. If refs/heads/foo/$brokenname exists but is skipped from any
and all iterations, nobody will get hurt until the end user wonders
where foo/$brokenname went, at which time rev-parse can be used to
grab the value from it before update-ref -d can be used to remove it.

If refs/heads/foo/$brokenname, which is skipped from iterations,
prevents a valid ref refs/heads/foo from getting created, that would
give a bit of surprise to the user who long forgot foo/$brokenname
existed, but the recovery procedure is exactly the same as the case
where he has a branch foo/$koshername and wants to create foo; first
'branch -D' the D/F-conflicting one and then create foo.

So the primary things I care about are when the user has a string
that is the name of an existing misnamed ref, the value of the ref
can be obtained, and the ref can be removed.

Thanks.
Ronnie Sahlberg
2014-08-20 21:47:08 UTC
Permalink
Post by Michael Haggerty
Post by Junio C Hamano
Post by Michael Haggerty
I think we can get away with not including broken refnames when
iterating. After all, the main goal of tolerating them is to let them
be deleted, right?
Or read from a ref whose name has retroactively made invalid, in
order to create a similar but validly named ref to serve as its
replacement? So at least we need to give the users some way of
reading from them, in addition to deleting them, no?
The die() error message would presumably include the name of the invalid
reference.
If we change the rules for reference names and a bunch of names become
invalid, then it would admittedly be cumbersome to run git N times to
find the N invalid names. But if we change the rules, then *at that
time* we could always build in iteration over broken reference names.
It's not that I have something against building it iteration over broken
reference names now, as long as it is clearly segregated from "normal"
iteration to prevent illegal names from getting loose in the code.
We already have iterators that show also bad refs.
For example, git branch uses
DO_FOR_EACH_INCLUDE_BROKEN
which is a flag to include also broken refs that can not be resolved
which allows them to be listed :



$ echo "this is not a valid sha1" >.git/refs/heads/broken
$ git branch
broken
foo
* master
$ git branch -D broken
error: branch 'broken' not found.

(allowing -D to remove it is in a different patch further down my series)


Since we already display broken/unresolvable refs, I think the most
consistent path is to also allow showing the refs broken/illegal-names
too in the list. (when DO_FOR_EACH_INCLUDE_BROKEN is specified)
Of course, an end user could fix this by deleting the file but since
it is easy to add the special casing to 'git branch -D' to handle this
case I think this would be more userfriendly since then the user can
use git branch -D regardless of the reason why the ref is broken.


Ronnie
Michael Haggerty
2014-08-22 12:41:05 UTC
Permalink
Post by Ronnie Sahlberg
[...]
Since we already display broken/unresolvable refs, I think the most
consistent path is to also allow showing the refs broken/illegal-names
too in the list. (when DO_FOR_EACH_INCLUDE_BROKEN is specified)
Of course, an end user could fix this by deleting the file but since
it is easy to add the special casing to 'git branch -D' to handle this
case I think this would be more userfriendly since then the user can
use git branch -D regardless of the reason why the ref is broken.
My concern with this idea is that some code relies on at least some of
the reference name constraints for its proper functioning; for example,

* The ref caching code would likely be confused by ill-formed refnames
like "refs/heads//foo" or "/refs/heads/foo" or "refs/heads/foo/". (I
understand that such references cannot exist as loose refs, but they
could be represented in the packed-refs file.)

* Any code that might try to read or write a loose reference would
likely be confused by "refs/heads//foo" or "refs/heads/./foo" or
"refs/heads/../foo" or "/refs/heads/foo" or "refs/heads/foo/". On
Windows there might also be problems with "refs/heads\foo" or
"d:refs/heads/foo" or "prn:refs/heads/foo" or "//refs/heads/foo".

* The locking code could easily be confused by a reference named
"refs/heads/foo.lock".

So to the extent that we loosen the checks on refnames when they are
read, we would have to re-vet any code that touches them to make sure
that it doesn't break in a horrible (and possibly security-compromising)
way. This is why I would prefer to quarantine broken reference names in
the smallest possible part of the code.

I *think* that the biggest problems would be related to reference names
that do not map straightforwardly to relative filenames, so an
alternative would be to do some minimal checks in any case, but make it
possible to turn off the stricter checks (those that mostly exist to
make reference expression parsing possible) when necessary.

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-06-20 14:43:25 UTC
Permalink
Skip using the lock_any_ref_for_update wrapper and call lock_ref_sha1_basic
directly from the commit function.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/refs.c b/refs.c
index bccf8c3..f3fab37 100644
--- a/refs.c
+++ b/refs.c
@@ -3598,12 +3598,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];

- update->lock = lock_any_ref_for_update(update->refname,
- (update->have_old ?
- update->old_sha1 :
- NULL),
- update->flags,
- &update->type);
+ update->lock = lock_ref_sha1_basic(update->refname,
+ (update->have_old ?
+ update->old_sha1 :
+ NULL),
+ update->flags,
+ &update->type);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 15:07:43 UTC
Permalink
Post by Ronnie Sahlberg
Skip using the lock_any_ref_for_update wrapper and call lock_ref_sha1_basic
directly from the commit function.
This commit is obviously hostage to whether commit 43/48 is kept.
Post by Ronnie Sahlberg
---
refs.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/refs.c b/refs.c
index bccf8c3..f3fab37 100644
--- a/refs.c
+++ b/refs.c
@@ -3598,12 +3598,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];
- update->lock = lock_any_ref_for_update(update->refname,
- (update->have_old ?
- NULL),
- update->flags,
- &update->type);
+ update->lock = lock_ref_sha1_basic(update->refname,
+ (update->have_old ?
+ NULL),
+ update->flags,
+ &update->type);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-06-20 14:43:27 UTC
Permalink
In _commit, ENOTDIR can happen in the call to lock_ref_sha1_basic, either when
we lstat the new refname and it returns ENOTDIR or if the name checking
function reports that the same type of conflict happened. In both cases it
means that we can not create the new ref due to a name conflict.

For these cases, save the errno value and abort and make sure that the caller
can see errno==ENOTDIR.

Also start defining specific return codes for _commit, assign -1 as a generic
error and -2 as the error that refers to a name conflict. Callers can (and
should) use that return value inspecting errno directly.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 22 +++++++++++++++-------
refs.h | 6 ++++++
2 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/refs.c b/refs.c
index c6990d0..74a55b5 100644
--- a/refs.c
+++ b/refs.c
@@ -3582,7 +3582,7 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
- int ret = 0, delnum = 0, i;
+ int ret = 0, delnum = 0, i, df_conflict = 0;
const char **delnames;
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
@@ -3600,9 +3600,10 @@ int ref_transaction_commit(struct ref_transaction *transaction,

/* Copy, sort, and reject duplicate refs */
qsort(updates, n, sizeof(*updates), ref_update_compare);
- ret = ref_update_reject_duplicates(updates, n, err);
- if (ret)
+ if (ref_update_reject_duplicates(updates, n, err)) {
+ ret = -1;
goto cleanup;
+ }

/* Acquire all locks while verifying old values */
for (i = 0; i < n; i++) {
@@ -3616,10 +3617,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,
&update->type,
delnames, delnum);
if (!update->lock) {
+ if (errno == ENOTDIR)
+ df_conflict = 1;
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
update->refname);
- ret = 1;
+ ret = -1;
goto cleanup;
}
}
@@ -3637,6 +3640,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,

if (err)
strbuf_addf(err, str, update->refname);
+ ret = -1;
goto cleanup;
}
}
@@ -3647,14 +3651,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];

if (update->lock) {
- ret |= delete_ref_loose(update->lock, update->type,
- err);
+ if (delete_ref_loose(update->lock, update->type, err))
+ ret = -1;
+
if (!(update->flags & REF_ISPRUNING))
delnames[delnum++] = update->lock->ref_name;
}
}

- ret |= repack_without_refs(delnames, delnum, err);
+ if (repack_without_refs(delnames, delnum, err))
+ ret = -1;
for (i = 0; i < delnum; i++)
unlink_or_warn(git_path("logs/%s", delnames[i]));
clear_loose_ref_cache(&ref_cache);
@@ -3667,6 +3673,8 @@ cleanup:
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
free(delnames);
+ if (df_conflict)
+ ret = -2;
return ret;
}

diff --git a/refs.h b/refs.h
index f24b2c1..5c0543d 100644
--- a/refs.h
+++ b/refs.h
@@ -333,7 +333,13 @@ int ref_transaction_delete(struct ref_transaction *transaction,
* Commit all of the changes that have been queued in transaction, as
* atomically as possible. Return a nonzero value if there is a
* problem.
+ * If the transaction is already in failed state this function will return
+ * an error.
+ * Function returns 0 on success, -1 for generic failures and
+ * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a name
+ * collision (ENOTDIR).
*/
+#define UPDATE_REFS_NAME_CONFLICT -2
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:26 UTC
Permalink
Allow passing a list of refs to skip checking to name_conflict_fn.
There are some conditions where we want to allow a temporary conflict and skip
checking those refs. For example if we have a transaction that
1, guarantees that m is a packed refs and there is no loose ref for m
2, the transaction will delete m from the packed ref
3, the transaction will create conflicting m/m

For this case we want to be able to lock and create m/m since we know that the
conflict is only transient. I.e. the conflict will be automatically resolved
by the transaction when it deletes m.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 41 ++++++++++++++++++++++++++---------------
1 file changed, 26 insertions(+), 15 deletions(-)

diff --git a/refs.c b/refs.c
index f3fab37..c6990d0 100644
--- a/refs.c
+++ b/refs.c
@@ -801,15 +801,18 @@ static int names_conflict(const char *refname1, const char *refname2)

struct name_conflict_cb {
const char *refname;
- const char *oldrefname;
const char *conflicting_refname;
+ const char **skip;
+ int skipnum;
};

static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
{
struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
- if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
- return 0;
+ int i;
+ for (i = 0; i < data->skipnum; i++)
+ if (!strcmp(entry->name, data->skip[i]))
+ return 0;
if (names_conflict(data->refname, entry->name)) {
data->conflicting_refname = entry->name;
return 1;
@@ -822,15 +825,18 @@ static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
* conflicting with the name of an existing reference in dir. If
* oldrefname is non-NULL, ignore potential conflicts with oldrefname
* (e.g., because oldrefname is scheduled for deletion in the same
- * operation).
+ * operation). skip contains a list of refs we want to skip checking for
+ * conflicts with.
*/
-static int is_refname_available(const char *refname, const char *oldrefname,
- struct ref_dir *dir)
+static int is_refname_available(const char *refname,
+ struct ref_dir *dir,
+ const char **skip, int skipnum)
{
struct name_conflict_cb data;
data.refname = refname;
- data.oldrefname = oldrefname;
data.conflicting_refname = NULL;
+ data.skip = skip;
+ data.skipnum = skipnum;

sort_ref_dir(dir);
if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
@@ -2077,7 +2083,8 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
/* This function should make sure errno is meaningful on error */
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
- int flags, int *type_p)
+ int flags, int *type_p,
+ const char **skip, int skipnum)
{
char *ref_file;
const char *orig_refname = refname;
@@ -2126,7 +2133,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
* name is a proper prefix of our refname.
*/
if (missing &&
- !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
+ !is_refname_available(refname, get_packed_refs(&ref_cache),
+ skip, skipnum)) {
last_errno = ENOTDIR;
goto error_return;
}
@@ -2184,7 +2192,7 @@ struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
{
- return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
+ return lock_ref_sha1_basic(refname, old_sha1, flags, type_p, NULL, 0);
}

/*
@@ -2676,10 +2684,12 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
if (!symref)
return error("refname %s not found", oldrefname);

- if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
+ if (!is_refname_available(newrefname, get_packed_refs(&ref_cache),
+ &oldrefname, 1))
return 1;

- if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
+ if (!is_refname_available(newrefname, get_loose_refs(&ref_cache),
+ &oldrefname, 1))
return 1;

if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
@@ -2709,7 +2719,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms

logmoved = log;

- lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL, NULL, 0);
if (!lock) {
error("unable to lock %s for update", newrefname);
goto rollback;
@@ -2724,7 +2734,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
return 0;

rollback:
- lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
+ lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL, NULL, 0);
if (!lock) {
error("unable to lock %s for rollback", oldrefname);
goto rollbacklog;
@@ -3603,7 +3613,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
update->old_sha1 :
NULL),
update->flags,
- &update->type);
+ &update->type,
+ delnames, delnum);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:22 UTC
Permalink
Change the reference transactions so that we pass the reflog message
through to the create/delete/update function instead of the commit message.
This allows for individual messages for each change in a multi ref
transaction.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
branch.c | 4 ++--
builtin/commit.c | 4 ++--
builtin/fetch.c | 3 +--
builtin/receive-pack.c | 5 +++--
builtin/replace.c | 4 ++--
builtin/tag.c | 4 ++--
builtin/update-ref.c | 13 +++++++------
fast-import.c | 8 ++++----
refs.c | 34 +++++++++++++++++++++-------------
refs.h | 8 ++++----
sequencer.c | 4 ++--
walker.c | 5 ++---
12 files changed, 52 insertions(+), 44 deletions(-)

diff --git a/branch.c b/branch.c
index c1eae00..e0439af 100644
--- a/branch.c
+++ b/branch.c
@@ -301,8 +301,8 @@ void create_branch(const char *head,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, sha1,
- null_sha1, 0, !forcing, &err) ||
- ref_transaction_commit(transaction, msg, &err))
+ null_sha1, 0, !forcing, msg, &err) ||
+ ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
}
diff --git a/builtin/commit.c b/builtin/commit.c
index 668fa6a..c499826 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1767,8 +1767,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
ref_transaction_update(transaction, "HEAD", sha1,
current_head ?
current_head->object.sha1 : NULL,
- 0, !!current_head, &err) ||
- ref_transaction_commit(transaction, sb.buf, &err)) {
+ 0, !!current_head, sb.buf, &err) ||
+ ref_transaction_commit(transaction, &err)) {
rollback_index_files();
die("%s", err.buf);
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 55f457c..faa1233 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -673,10 +673,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
}
}
}
-
if (rc & STORE_REF_ERROR_DF_CONFLICT)
error(_("some local refs could not be updated; try running\n"
- " 'git remote prune %s' to remove any old, conflicting "
+ "'git remote prune %s' to remove any old, conflicting "
"branches"), remote_name);

abort:
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index b51f8ae..0ed1ddd 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -585,8 +585,9 @@ static char *update(struct command *cmd, struct shallow_info *si)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, namespaced_name,
- new_sha1, old_sha1, 0, 1, &err) ||
- ref_transaction_commit(transaction, "push", &err)) {
+ new_sha1, old_sha1, 0, 1, "push",
+ &err) ||
+ ref_transaction_commit(transaction, &err)) {
char *str = strbuf_detach(&err, NULL);
ref_transaction_free(transaction);

diff --git a/builtin/replace.c b/builtin/replace.c
index 7528f3d..df060f8 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -170,8 +170,8 @@ static int replace_object_sha1(const char *object_ref,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref, repl, prev,
- 0, !is_null_sha1(prev), &err) ||
- ref_transaction_commit(transaction, NULL, &err))
+ 0, !is_null_sha1(prev), NULL, &err) ||
+ ref_transaction_commit(transaction, &err))
die("%s", err.buf);

ref_transaction_free(transaction);
diff --git a/builtin/tag.c b/builtin/tag.c
index c9bfc9a..74af63e 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -705,8 +705,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, object, prev,
- 0, !is_null_sha1(prev), &err) ||
- ref_transaction_commit(transaction, NULL, &err))
+ 0, !is_null_sha1(prev), NULL, &err) ||
+ ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
if (force && !is_null_sha1(prev) && hashcmp(prev, object))
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index c6ad0be..28b478a 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -16,6 +16,7 @@ static struct ref_transaction *transaction;

static char line_termination = '\n';
static int update_flags;
+static const char *msg;
static struct strbuf err = STRBUF_INIT;

/*
@@ -199,7 +200,7 @@ static const char *parse_cmd_update(struct strbuf *input, const char *next)
die("update %s: extra input: %s", refname, next);

if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
- update_flags, have_old, &err))
+ update_flags, have_old, msg, &err))
die("%s", err.buf);

update_flags = 0;
@@ -227,7 +228,7 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)
die("create %s: extra input: %s", refname, next);

if (ref_transaction_create(transaction, refname, new_sha1,
- update_flags, &err))
+ update_flags, msg, &err))
die("%s", err.buf);

update_flags = 0;
@@ -259,7 +260,7 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)
die("delete %s: extra input: %s", refname, next);

if (ref_transaction_delete(transaction, refname, old_sha1,
- update_flags, have_old, &err))
+ update_flags, have_old, msg, &err))
die("%s", err.buf);

update_flags = 0;
@@ -292,7 +293,7 @@ static const char *parse_cmd_verify(struct strbuf *input, const char *next)
die("verify %s: extra input: %s", refname, next);

if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
- update_flags, have_old, &err))
+ update_flags, have_old, msg, &err))
die("%s", err.buf);

update_flags = 0;
@@ -345,7 +346,7 @@ static void update_refs_stdin(void)

int cmd_update_ref(int argc, const char **argv, const char *prefix)
{
- const char *refname, *oldval, *msg = NULL;
+ const char *refname, *oldval;
unsigned char sha1[20], oldsha1[20];
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
struct option options[] = {
@@ -371,7 +372,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (end_null)
line_termination = '\0';
update_refs_stdin();
- if (ref_transaction_commit(transaction, msg, &err))
+ if (ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
return 0;
diff --git a/fast-import.c b/fast-import.c
index a95e1be..0876db2 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -1708,8 +1708,8 @@ static int update_branch(struct branch *b)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, b->name, b->sha1, old_sha1,
- 0, 1, &err) ||
- ref_transaction_commit(transaction, msg, &err)) {
+ 0, 1, msg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
strbuf_release(&err);
@@ -1748,12 +1748,12 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);

if (ref_transaction_update(transaction, ref_name.buf, t->sha1,
- NULL, 0, 0, &err)) {
+ NULL, 0, 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
}
- if (ref_transaction_commit(transaction, msg, &err))
+ if (ref_transaction_commit(transaction, &err))
failure |= error("%s", err.buf);

cleanup:
diff --git a/refs.c b/refs.c
index c7d1f3e..1f17a13 100644
--- a/refs.c
+++ b/refs.c
@@ -2393,8 +2393,8 @@ static void prune_ref(struct ref_to_prune *r)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_delete(transaction, r->name, r->sha1,
- REF_ISPRUNING, 1, &err) ||
- ref_transaction_commit(transaction, NULL, &err)) {
+ REF_ISPRUNING, 1, NULL, &err) ||
+ ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
strbuf_release(&err);
@@ -2589,8 +2589,8 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_delete(transaction, refname, sha1, delopt,
- sha1 && !is_null_sha1(sha1), &err) ||
- ref_transaction_commit(transaction, NULL, &err)) {
+ sha1 && !is_null_sha1(sha1), NULL, &err) ||
+ ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ref_transaction_free(transaction);
strbuf_release(&err);
@@ -3367,6 +3367,7 @@ struct ref_update {
int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
struct ref_lock *lock;
int type;
+ char *msg;
const char refname[FLEX_ARRAY];
};

@@ -3414,9 +3415,10 @@ void ref_transaction_free(struct ref_transaction *transaction)
if (!transaction)
return;

- for (i = 0; i < transaction->nr; i++)
+ for (i = 0; i < transaction->nr; i++) {
+ free(transaction->updates[i]->msg);
free(transaction->updates[i]);
-
+ }
free(transaction->updates);
free(transaction);
}
@@ -3437,7 +3439,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err)
{
struct ref_update *update;
@@ -3454,13 +3456,15 @@ int ref_transaction_update(struct ref_transaction *transaction,
update->have_old = have_old;
if (have_old)
hashcpy(update->old_sha1, old_sha1);
+ if (msg)
+ update->msg = xstrdup(msg);
return 0;
}

int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
- int flags,
+ int flags, const char *msg,
struct strbuf *err)
{
struct ref_update *update;
@@ -3477,13 +3481,15 @@ int ref_transaction_create(struct ref_transaction *transaction,
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
+ if (msg)
+ update->msg = xstrdup(msg);
return 0;
}

int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err)
{
struct ref_update *update;
@@ -3501,6 +3507,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
assert(!is_null_sha1(old_sha1));
hashcpy(update->old_sha1, old_sha1);
}
+ if (msg)
+ update->msg = xstrdup(msg);
return 0;
}

@@ -3514,8 +3522,8 @@ int update_ref(const char *action, const char *refname,
t = ref_transaction_begin(&err);
if (!t ||
ref_transaction_update(t, refname, sha1, oldval, flags,
- !!oldval, &err) ||
- ref_transaction_commit(t, action, &err)) {
+ !!oldval, action, &err) ||
+ ref_transaction_commit(t, &err)) {
const char *str = "update_ref failed for ref '%s': %s";

ref_transaction_free(t);
@@ -3559,7 +3567,7 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
}

int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, struct strbuf *err)
+ struct strbuf *err)
{
int ret = 0, delnum = 0, i;
const char **delnames;
@@ -3608,7 +3616,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,

if (!is_null_sha1(update->new_sha1)) {
ret = write_ref_sha1(update->lock, update->new_sha1,
- msg);
+ update->msg);
update->lock = NULL; /* freed by write_ref_sha1 */
if (ret) {
const char *str = "Cannot update the ref '%s'.";
diff --git a/refs.h b/refs.h
index 3b321c2..f24b2c1 100644
--- a/refs.h
+++ b/refs.h
@@ -297,7 +297,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err);

/*
@@ -312,7 +312,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
- int flags,
+ int flags, const char *msg,
struct strbuf *err);

/*
@@ -326,7 +326,7 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err);

/*
@@ -335,7 +335,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
* problem.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, struct strbuf *err);
+ struct strbuf *err);

/*
* Free an existing transaction and all associated data.
diff --git a/sequencer.c b/sequencer.c
index fd8acaf..f9906ef 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -285,8 +285,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD", to, from,
- 0, !unborn, &err) ||
- ref_transaction_commit(transaction, sb.buf, &err)) {
+ 0, !unborn, sb.buf, &err) ||
+ ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
strbuf_release(&sb);
diff --git a/walker.c b/walker.c
index 60d9f9e..fd9ef87 100644
--- a/walker.c
+++ b/walker.c
@@ -295,15 +295,14 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_addf(&ref_name, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, ref_name.buf,
&sha1[20 * i], NULL, 0, 0,
+ msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
goto rollback_and_fail;
}
}
if (write_ref) {
- if (ref_transaction_commit(transaction,
- msg ? msg : "fetch (unknown)",
- &err)) {
+ if (ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
goto rollback_and_fail;
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 14:39:47 UTC
Permalink
Post by Ronnie Sahlberg
Change the reference transactions so that we pass the reflog message
through to the create/delete/update function instead of the commit message.
This allows for individual messages for each change in a multi ref
transaction.
---
[...]
diff --git a/refs.h b/refs.h
index 3b321c2..f24b2c1 100644
--- a/refs.h
+++ b/refs.h
Would you please document the msg parameter in the block comment that
precedes these three declarations? Especially important is the fact
that the functions make internal copies of msg, so the caller retains
ownership of its copy. You might also mention what happens if msg is
NULL (which, as far as I can see, is that a reflog entry is created
anyway (except in the case of a delete) but that the entry doesn't
contain an explanation).
Post by Ronnie Sahlberg
@@ -297,7 +297,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err);
/*
@@ -312,7 +312,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
int ref_transaction_create(struct ref_transaction *transaction,
const char *refname,
const unsigned char *new_sha1,
- int flags,
+ int flags, const char *msg,
struct strbuf *err);
It is noteworthy that ref_transaction_delete() accepts a msg parameter,
even though we currently delete a reference's entire reflog when the
reference is deleted. I prefer to think of this as a shortcoming of the
current reference backend, from which future backends hopefully will not
suffer. So I like this design choice.

However, I think it is worth noting this dichotomy in the commit message
and perhaps also in the function documentation.
Post by Ronnie Sahlberg
/*
@@ -326,7 +326,7 @@ int ref_transaction_create(struct ref_transaction *transaction,
int ref_transaction_delete(struct ref_transaction *transaction,
const char *refname,
const unsigned char *old_sha1,
- int flags, int have_old,
+ int flags, int have_old, const char *msg,
struct strbuf *err);
/*
@@ -335,7 +335,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
* problem.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, struct strbuf *err);
+ struct strbuf *err);
/*
* Free an existing transaction and all associated data.
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
http://softwareswirl.blogspot.com/
Ronnie Sahlberg
2014-06-20 14:43:10 UTC
Permalink
Change create_branch to use a ref transaction when creating the new branch.

This also fixes a race condition in the old code where two concurrent
create_branch could race since the lock_any_ref_for_update/write_ref_sha1
did not protect against the ref already existing. I.e. one thread could end up
overwriting a branch even if the forcing flag is false.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
branch.c | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/branch.c b/branch.c
index 660097b..c1eae00 100644
--- a/branch.c
+++ b/branch.c
@@ -226,7 +226,6 @@ void create_branch(const char *head,
int force, int reflog, int clobber_head,
int quiet, enum branch_track track)
{
- struct ref_lock *lock = NULL;
struct commit *commit;
unsigned char sha1[20];
char *real_ref, msg[PATH_MAX + 20];
@@ -285,15 +284,6 @@ void create_branch(const char *head,
die(_("Not a valid branch point: '%s'."), start_name);
hashcpy(sha1, commit->object.sha1);

- if (!dont_change_ref) {
- lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL);
- if (!lock)
- die_errno(_("Failed to lock ref for update"));
- }
-
- if (reflog)
- log_all_ref_updates = 1;
-
if (forcing)
snprintf(msg, sizeof msg, "branch: Reset to %s",
start_name);
@@ -301,13 +291,25 @@ void create_branch(const char *head,
snprintf(msg, sizeof msg, "branch: Created from %s",
start_name);

+ if (reflog)
+ log_all_ref_updates = 1;
+
+ if (!dont_change_ref) {
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, sha1,
+ null_sha1, 0, !forcing, &err) ||
+ ref_transaction_commit(transaction, msg, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ }
+
if (real_ref && track)
setup_tracking(ref.buf + 11, real_ref, track, quiet);

- if (!dont_change_ref)
- if (write_ref_sha1(lock, sha1, msg) < 0)
- die_errno(_("Failed to write ref"));
-
strbuf_release(&ref);
free(real_ref);
}
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:13 UTC
Permalink
Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
fast-import.c | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/fast-import.c b/fast-import.c
index d5206ee..a95e1be 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -1734,15 +1734,32 @@ static void dump_tags(void)
{
static const char *msg = "fast-import";
struct tag *t;
- struct ref_lock *lock;
- char ref_name[PATH_MAX];
+ struct strbuf ref_name = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;

+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ failure |= error("%s", err.buf);
+ goto cleanup;
+ }
for (t = first_tag; t; t = t->next_tag) {
- sprintf(ref_name, "tags/%s", t->name);
- lock = lock_ref_sha1(ref_name, NULL);
- if (!lock || write_ref_sha1(lock, t->sha1, msg) < 0)
- failure |= error("Unable to update %s", ref_name);
+ strbuf_reset(&ref_name);
+ strbuf_addf(&ref_name, "refs/tags/%s", t->name);
+
+ if (ref_transaction_update(transaction, ref_name.buf, t->sha1,
+ NULL, 0, 0, &err)) {
+ failure |= error("%s", err.buf);
+ goto cleanup;
+ }
}
+ if (ref_transaction_commit(transaction, msg, &err))
+ failure |= error("%s", err.buf);
+
+ cleanup:
+ ref_transaction_free(transaction);
+ strbuf_release(&ref_name);
+ strbuf_release(&err);
}

static void dump_marks_helper(FILE *f,
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:18 UTC
Permalink
lock_ref_sha1 was only called from one place in refc.c and only provided
a check that the refname was sane before adding back the initial "refs/"
part of the ref path name, the initial "refs/" that this caller had already
stripped off before calling lock_ref_sha1.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/refs.c b/refs.c
index 7113843..699f1f6 100644
--- a/refs.c
+++ b/refs.c
@@ -2170,15 +2170,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
return NULL;
}

-static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
-{
- char refpath[PATH_MAX];
- if (check_refname_format(refname, 0))
- return NULL;
- strcpy(refpath, mkpath("refs/%s", refname));
- return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
-}
-
struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
@@ -2388,8 +2379,12 @@ static void try_remove_empty_parents(char *name)
/* make sure nobody touched the ref, and unlink */
static void prune_ref(struct ref_to_prune *r)
{
- struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
+ struct ref_lock *lock;
+
+ if (check_refname_format(r->name + 5, 0))
+ return;

+ lock = lock_ref_sha1_basic(r->name, r->sha1, 0, NULL);
if (lock) {
unlink_or_warn(git_path("%s", r->name));
unlock_ref(lock);
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 13:38:50 UTC
Permalink
Post by Ronnie Sahlberg
lock_ref_sha1 was only called from one place in refc.c and only provided
a check that the refname was sane before adding back the initial "refs/"
part of the ref path name, the initial "refs/" that this caller had already
stripped off before calling lock_ref_sha1.
[...]
I'm especially glad to see this ugly function disappear!

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-06-20 14:43:17 UTC
Permalink
Since we only call update_ref_write from a single place and we only call it
with onerr==QUIET_ON_ERR we can just as well get rid of it and just call
write_ref_sha1 directly. This changes the return status for _commit from
1 to -1 on failures when writing to the ref. Eventually we will want
_commit to start returning more detailed error conditions than the current
simple success/failure. For example if the commit failed due to name
conflicts etc.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 35 +++++++++--------------------------
1 file changed, 9 insertions(+), 26 deletions(-)

diff --git a/refs.c b/refs.c
index 8fb0a9e..7113843 100644
--- a/refs.c
+++ b/refs.c
@@ -3333,25 +3333,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
return retval;
}

-static int update_ref_write(const char *action, const char *refname,
- const unsigned char *sha1, struct ref_lock *lock,
- struct strbuf *err, enum action_on_err onerr)
-{
- if (write_ref_sha1(lock, sha1, action) < 0) {
- const char *str = "Cannot update the ref '%s'.";
- if (err)
- strbuf_addf(err, str, refname);
-
- switch (onerr) {
- case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
- case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
- case UPDATE_REFS_QUIET_ON_ERR: break;
- }
- return 1;
- }
- return 0;
-}
-
/**
* Information needed for a single ref update. Set new_sha1 to the
* new value or to zero to delete the ref. To check the old value
@@ -3605,14 +3586,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,
struct ref_update *update = updates[i];

if (!is_null_sha1(update->new_sha1)) {
- ret = update_ref_write(msg,
- update->refname,
- update->new_sha1,
- update->lock, err,
- UPDATE_REFS_QUIET_ON_ERR);
- update->lock = NULL; /* freed by update_ref_write */
- if (ret)
+ ret = write_ref_sha1(update->lock, update->new_sha1,
+ msg);
+ update->lock = NULL; /* freed by write_ref_sha1 */
+ if (ret) {
+ const char *str = "Cannot update the ref '%s'.";
+
+ if (err)
+ strbuf_addf(err, str, update->refname);
goto cleanup;
+ }
}
}
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:07 UTC
Permalink
Change commit.c to use ref transactions for all ref updates.
Make sure we pass a NULL pointer to ref_transaction_update if have_old
is false.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/commit.c | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 5e2221c..668fa6a 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1627,11 +1627,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
const char *index_file, *reflog_msg;
char *nl;
unsigned char sha1[20];
- struct ref_lock *ref_lock;
struct commit_list *parents = NULL, **pptr = &parents;
struct stat statbuf;
struct commit *current_head = NULL;
struct commit_extra_header *extra = NULL;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;

if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_commit_usage, builtin_commit_options);
@@ -1753,16 +1754,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
strbuf_release(&author_ident);
free_commit_extra_headers(extra);

- ref_lock = lock_any_ref_for_update("HEAD",
- !current_head
- ? NULL
- : current_head->object.sha1,
- 0, NULL);
- if (!ref_lock) {
- rollback_index_files();
- die(_("cannot lock HEAD ref"));
- }
-
nl = strchr(sb.buf, '\n');
if (nl)
strbuf_setlen(&sb, nl + 1 - sb.buf);
@@ -1771,10 +1762,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);

- if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", sha1,
+ current_head ?
+ current_head->object.sha1 : NULL,
+ 0, !!current_head, &err) ||
+ ref_transaction_commit(transaction, sb.buf, &err)) {
rollback_index_files();
- die(_("cannot update HEAD ref"));
+ die("%s", err.buf);
}
+ ref_transaction_free(transaction);

unlink(git_path("CHERRY_PICK_HEAD"));
unlink(git_path("REVERT_HEAD"));
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:06 UTC
Permalink
Update replace.c to use ref transactions for updates.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/replace.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/builtin/replace.c b/builtin/replace.c
index 1bb491d..7528f3d 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -153,7 +153,8 @@ static int replace_object_sha1(const char *object_ref,
unsigned char prev[20];
enum object_type obj_type, repl_type;
char ref[PATH_MAX];
- struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;

obj_type = sha1_object_info(object, NULL);
repl_type = sha1_object_info(repl, NULL);
@@ -166,12 +167,14 @@ static int replace_object_sha1(const char *object_ref,

check_ref_valid(object, prev, ref, sizeof(ref), force);

- lock = lock_any_ref_for_update(ref, prev, 0, NULL);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, repl, prev,
+ 0, !is_null_sha1(prev), &err) ||
+ ref_transaction_commit(transaction, NULL, &err))
+ die("%s", err.buf);

+ ref_transaction_free(transaction);
return 0;
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 12:35:04 UTC
Permalink
Post by Ronnie Sahlberg
Update replace.c to use ref transactions for updates.
---
builtin/replace.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/builtin/replace.c b/builtin/replace.c
index 1bb491d..7528f3d 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -153,7 +153,8 @@ static int replace_object_sha1(const char *object_ref,
unsigned char prev[20];
enum object_type obj_type, repl_type;
char ref[PATH_MAX];
- struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
obj_type = sha1_object_info(object, NULL);
repl_type = sha1_object_info(repl, NULL);
@@ -166,12 +167,14 @@ static int replace_object_sha1(const char *object_ref,
check_ref_valid(object, prev, ref, sizeof(ref), force);
- lock = lock_any_ref_for_update(ref, prev, 0, NULL);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, repl, prev,
+ 0, !is_null_sha1(prev), &err) ||
Same problem here. You need

s/!is_null_sha1(prev)/1/
Post by Ronnie Sahlberg
+ ref_transaction_commit(transaction, NULL, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
return 0;
}
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 21:19:59 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Update replace.c to use ref transactions for updates.
---
builtin/replace.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/builtin/replace.c b/builtin/replace.c
index 1bb491d..7528f3d 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -153,7 +153,8 @@ static int replace_object_sha1(const char *object_ref,
unsigned char prev[20];
enum object_type obj_type, repl_type;
char ref[PATH_MAX];
- struct ref_lock *lock;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
obj_type = sha1_object_info(object, NULL);
repl_type = sha1_object_info(repl, NULL);
@@ -166,12 +167,14 @@ static int replace_object_sha1(const char *object_ref,
check_ref_valid(object, prev, ref, sizeof(ref), force);
- lock = lock_any_ref_for_update(ref, prev, 0, NULL);
- if (!lock)
- die("%s: cannot lock the ref", ref);
- if (write_ref_sha1(lock, repl, NULL) < 0)
- die("%s: cannot update the ref", ref);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, repl, prev,
+ 0, !is_null_sha1(prev), &err) ||
Same problem here. You need
s/!is_null_sha1(prev)/1/
I think we need it.
prev can be null_sha1 here if check_ref_valid fails to resolve the ref.
Post by Michael Haggerty
Post by Ronnie Sahlberg
+ ref_transaction_commit(transaction, NULL, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
return 0;
}
--
Michael Haggerty
Ronnie Sahlberg
2014-06-20 14:43:11 UTC
Permalink
Change the update_ref helper function to use a ref transaction internally.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/refs.c b/refs.c
index 8c695ba..4bdccc5 100644
--- a/refs.c
+++ b/refs.c
@@ -3524,11 +3524,31 @@ int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
- struct ref_lock *lock;
- lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
- if (!lock)
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, sha1, oldval, flags,
+ !!oldval, &err) ||
+ ref_transaction_commit(t, action, &err)) {
+ const char *str = "update_ref failed for ref '%s': %s";
+
+ ref_transaction_free(t);
+ switch (onerr) {
+ case UPDATE_REFS_MSG_ON_ERR:
+ error(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_DIE_ON_ERR:
+ die(str, refname, err.buf);
+ break;
+ case UPDATE_REFS_QUIET_ON_ERR:
+ break;
+ }
+ strbuf_release(&err);
return 1;
- return update_ref_write(action, refname, sha1, lock, NULL, onerr);
+ }
+ return 0;
}

static int ref_update_compare(const void *r1, const void *r2)
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 12:54:28 UTC
Permalink
Post by Ronnie Sahlberg
Change the update_ref helper function to use a ref transaction internally.
---
refs.c | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/refs.c b/refs.c
index 8c695ba..4bdccc5 100644
--- a/refs.c
+++ b/refs.c
@@ -3524,11 +3524,31 @@ int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
- struct ref_lock *lock;
- lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
- if (!lock)
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, sha1, oldval, flags,
+ !!oldval, &err) ||
+ ref_transaction_commit(t, action, &err)) {
+ const char *str = "update_ref failed for ref '%s': %s";
+
+ ref_transaction_free(t);
+ switch (onerr) {
+ error(str, refname, err.buf);
+ break;
+ die(str, refname, err.buf);
+ break;
+ break;
+ }
+ strbuf_release(&err);
return 1;
- return update_ref_write(action, refname, sha1, lock, NULL, onerr);
+ }
+ return 0;
}
Should this function be scheduled for the "take strbuf *err argument"
treatment instead of continuing to use an action_on_err parameter?
(Maybe you've changed this later in the patch series?)

I'm not saying this change has to be part of the current patch series,
but let's consider it for the future.

Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 18:49:00 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Change the update_ref helper function to use a ref transaction internally.
---
refs.c | 28 ++++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/refs.c b/refs.c
index 8c695ba..4bdccc5 100644
--- a/refs.c
+++ b/refs.c
@@ -3524,11 +3524,31 @@ int update_ref(const char *action, const char *refname,
const unsigned char *sha1, const unsigned char *oldval,
int flags, enum action_on_err onerr)
{
- struct ref_lock *lock;
- lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
- if (!lock)
+ struct ref_transaction *t;
+ struct strbuf err = STRBUF_INIT;
+
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, sha1, oldval, flags,
+ !!oldval, &err) ||
+ ref_transaction_commit(t, action, &err)) {
+ const char *str = "update_ref failed for ref '%s': %s";
+
+ ref_transaction_free(t);
+ switch (onerr) {
+ error(str, refname, err.buf);
+ break;
+ die(str, refname, err.buf);
+ break;
+ break;
+ }
+ strbuf_release(&err);
return 1;
- return update_ref_write(action, refname, sha1, lock, NULL, onerr);
+ }
+ return 0;
}
Should this function be scheduled for the "take strbuf *err argument"
treatment instead of continuing to use an action_on_err parameter?
(Maybe you've changed this later in the patch series?)
I'm not saying this change has to be part of the current patch series,
but let's consider it for the future.
There is a patch that does that in a later series. At that stage we
get rid of all action_on_err arguments.
Post by Michael Haggerty
Michael
--
Michael Haggerty
Ronnie Sahlberg
2014-06-20 14:43:16 UTC
Permalink
Since we now only call update_ref_lock with onerr==QUIET_ON_ERR we no longer
need this function and can replace it with just calling lock_any_ref_for_update
directly.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 30 ++++++------------------------
1 file changed, 6 insertions(+), 24 deletions(-)

diff --git a/refs.c b/refs.c
index 456acdd..8fb0a9e 100644
--- a/refs.c
+++ b/refs.c
@@ -3333,24 +3333,6 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
return retval;
}

-static struct ref_lock *update_ref_lock(const char *refname,
- const unsigned char *oldval,
- int flags, int *type_p,
- enum action_on_err onerr)
-{
- struct ref_lock *lock;
- lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
- if (!lock) {
- const char *str = "Cannot lock the ref '%s'.";
- switch (onerr) {
- case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
- case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
- case UPDATE_REFS_QUIET_ON_ERR: break;
- }
- }
- return lock;
-}
-
static int update_ref_write(const char *action, const char *refname,
const unsigned char *sha1, struct ref_lock *lock,
struct strbuf *err, enum action_on_err onerr)
@@ -3603,12 +3585,12 @@ int ref_transaction_commit(struct ref_transaction *transaction,
for (i = 0; i < n; i++) {
struct ref_update *update = updates[i];

- update->lock = update_ref_lock(update->refname,
- (update->have_old ?
- update->old_sha1 : NULL),
- update->flags,
- &update->type,
- UPDATE_REFS_QUIET_ON_ERR);
+ update->lock = lock_any_ref_for_update(update->refname,
+ (update->have_old ?
+ update->old_sha1 :
+ NULL),
+ update->flags,
+ &update->type);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:14 UTC
Permalink
Switch to using ref transactions in walker_fetch(). As part of the refactoring
to use ref transactions we also fix a potential memory leak where in the
original code if write_ref_sha1() would fail we would end up returning from
the function without free()ing the msg string.

Note that this function is only called when fetching from a remote HTTP
repository onto the local (most of the time single-user) repository which
likely means that the type of collissions that the previous locking would
protect against and cause the fetch to fail for to be even more rare.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
walker.c | 59 +++++++++++++++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/walker.c b/walker.c
index 1dd86b8..60d9f9e 100644
--- a/walker.c
+++ b/walker.c
@@ -251,39 +251,36 @@ void walker_targets_free(int targets, char **target, const char **write_ref)
int walker_fetch(struct walker *walker, int targets, char **target,
const char **write_ref, const char *write_ref_log_details)
{
- struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
+ struct strbuf ref_name = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction = NULL;
unsigned char *sha1 = xmalloc(targets * 20);
- char *msg;
- int ret;
+ char *msg = NULL;
int i;

save_commit_buffer = 0;

- for (i = 0; i < targets; i++) {
- if (!write_ref || !write_ref[i])
- continue;
-
- lock[i] = lock_ref_sha1(write_ref[i], NULL);
- if (!lock[i]) {
- error("Can't lock ref %s", write_ref[i]);
- goto unlock_and_fail;
+ if (write_ref) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
}
}
-
if (!walker->get_recover)
for_each_ref(mark_complete, NULL);

for (i = 0; i < targets; i++) {
if (interpret_target(walker, target[i], &sha1[20 * i])) {
error("Could not interpret response from server '%s' as something to pull", target[i]);
- goto unlock_and_fail;
+ goto rollback_and_fail;
}
if (process(walker, lookup_unknown_object(&sha1[20 * i])))
- goto unlock_and_fail;
+ goto rollback_and_fail;
}

if (loop(walker))
- goto unlock_and_fail;
+ goto rollback_and_fail;

if (write_ref_log_details) {
msg = xmalloc(strlen(write_ref_log_details) + 12);
@@ -294,19 +291,33 @@ int walker_fetch(struct walker *walker, int targets, char **target,
for (i = 0; i < targets; i++) {
if (!write_ref || !write_ref[i])
continue;
- ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)");
- lock[i] = NULL;
- if (ret)
- goto unlock_and_fail;
+ strbuf_reset(&ref_name);
+ strbuf_addf(&ref_name, "refs/%s", write_ref[i]);
+ if (ref_transaction_update(transaction, ref_name.buf,
+ &sha1[20 * i], NULL, 0, 0,
+ &err)) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
+ }
+ }
+ if (write_ref) {
+ if (ref_transaction_commit(transaction,
+ msg ? msg : "fetch (unknown)",
+ &err)) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
+ }
+ ref_transaction_free(transaction);
}
- free(msg);

+ free(msg);
return 0;

-unlock_and_fail:
- for (i = 0; i < targets; i++)
- if (lock[i])
- unlock_ref(lock[i]);
+rollback_and_fail:
+ ref_transaction_free(transaction);
+ free(msg);
+ strbuf_release(&err);
+ strbuf_release(&ref_name);

return -1;
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 13:33:39 UTC
Permalink
Post by Ronnie Sahlberg
Switch to using ref transactions in walker_fetch(). As part of the refactoring
to use ref transactions we also fix a potential memory leak where in the
original code if write_ref_sha1() would fail we would end up returning from
the function without free()ing the msg string.
Note that this function is only called when fetching from a remote HTTP
repository onto the local (most of the time single-user) repository which
likely means that the type of collissions that the previous locking would
s/collissions/collisions/
Post by Ronnie Sahlberg
protect against and cause the fetch to fail for to be even more rare.
Grammatico: s/to be/are/ ?
Post by Ronnie Sahlberg
---
walker.c | 59 +++++++++++++++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 24 deletions(-)
diff --git a/walker.c b/walker.c
index 1dd86b8..60d9f9e 100644
--- a/walker.c
+++ b/walker.c
@@ -251,39 +251,36 @@ void walker_targets_free(int targets, char **target, const char **write_ref)
int walker_fetch(struct walker *walker, int targets, char **target,
const char **write_ref, const char *write_ref_log_details)
{
- struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
+ struct strbuf ref_name = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction = NULL;
unsigned char *sha1 = xmalloc(targets * 20);
- char *msg;
- int ret;
+ char *msg = NULL;
int i;
save_commit_buffer = 0;
- for (i = 0; i < targets; i++) {
- if (!write_ref || !write_ref[i])
- continue;
-
- lock[i] = lock_ref_sha1(write_ref[i], NULL);
- if (!lock[i]) {
- error("Can't lock ref %s", write_ref[i]);
- goto unlock_and_fail;
+ if (write_ref) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
}
}
-
Is there some reason why the transaction cannot be built up during a
single iteration over targets, thereby also avoiding the need for the
sha1[20*i] stuff? This seems like exactly the kind of situation where
transactions should *save* code. But perhaps I've overlooked a
dependency between the two loops.
Post by Ronnie Sahlberg
if (!walker->get_recover)
for_each_ref(mark_complete, NULL);
for (i = 0; i < targets; i++) {
if (interpret_target(walker, target[i], &sha1[20 * i])) {
error("Could not interpret response from server '%s' as something to pull", target[i]);
- goto unlock_and_fail;
+ goto rollback_and_fail;
}
if (process(walker, lookup_unknown_object(&sha1[20 * i])))
- goto unlock_and_fail;
+ goto rollback_and_fail;
}
if (loop(walker))
- goto unlock_and_fail;
+ goto rollback_and_fail;
if (write_ref_log_details) {
msg = xmalloc(strlen(write_ref_log_details) + 12);
@@ -294,19 +291,33 @@ int walker_fetch(struct walker *walker, int targets, char **target,
for (i = 0; i < targets; i++) {
if (!write_ref || !write_ref[i])
continue;
- ret = write_ref_sha1(lock[i], &sha1[20 * i], msg ? msg : "fetch (unknown)");
- lock[i] = NULL;
- if (ret)
- goto unlock_and_fail;
+ strbuf_reset(&ref_name);
+ strbuf_addf(&ref_name, "refs/%s", write_ref[i]);
+ if (ref_transaction_update(transaction, ref_name.buf,
+ &sha1[20 * i], NULL, 0, 0,
+ &err)) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
+ }
+ }
+ if (write_ref) {
+ if (ref_transaction_commit(transaction,
+ msg ? msg : "fetch (unknown)",
+ &err)) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
+ }
+ ref_transaction_free(transaction);
}
- free(msg);
+ free(msg);
return 0;
- for (i = 0; i < targets; i++)
- if (lock[i])
- unlock_ref(lock[i]);
+ ref_transaction_free(transaction);
+ free(msg);
+ strbuf_release(&err);
+ strbuf_release(&ref_name);
return -1;
}
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 18:05:39 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Switch to using ref transactions in walker_fetch(). As part of the refactoring
to use ref transactions we also fix a potential memory leak where in the
original code if write_ref_sha1() would fail we would end up returning from
the function without free()ing the msg string.
Note that this function is only called when fetching from a remote HTTP
repository onto the local (most of the time single-user) repository which
likely means that the type of collissions that the previous locking would
s/collissions/collisions/
Post by Ronnie Sahlberg
protect against and cause the fetch to fail for to be even more rare.
Grammatico: s/to be/are/ ?
Thanks. Fixed.
Post by Michael Haggerty
Post by Ronnie Sahlberg
---
walker.c | 59 +++++++++++++++++++++++++++++++++++------------------------
1 file changed, 35 insertions(+), 24 deletions(-)
diff --git a/walker.c b/walker.c
index 1dd86b8..60d9f9e 100644
--- a/walker.c
+++ b/walker.c
@@ -251,39 +251,36 @@ void walker_targets_free(int targets, char **target, const char **write_ref)
int walker_fetch(struct walker *walker, int targets, char **target,
const char **write_ref, const char *write_ref_log_details)
{
- struct ref_lock **lock = xcalloc(targets, sizeof(struct ref_lock *));
+ struct strbuf ref_name = STRBUF_INIT;
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction = NULL;
unsigned char *sha1 = xmalloc(targets * 20);
- char *msg;
- int ret;
+ char *msg = NULL;
int i;
save_commit_buffer = 0;
- for (i = 0; i < targets; i++) {
- if (!write_ref || !write_ref[i])
- continue;
-
- lock[i] = lock_ref_sha1(write_ref[i], NULL);
- if (!lock[i]) {
- error("Can't lock ref %s", write_ref[i]);
- goto unlock_and_fail;
+ if (write_ref) {
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ error("%s", err.buf);
+ goto rollback_and_fail;
}
}
-
Is there some reason why the transaction cannot be built up during a
single iteration over targets, thereby also avoiding the need for the
sha1[20*i] stuff? This seems like exactly the kind of situation where
transactions should *save* code. But perhaps I've overlooked a
dependency between the two loops.
I did it this way to keep the changes minimal. But you are right that
with this we can do a larger refactoring and start saving some code.
I can add changes to a later series to do that change but I want to
keep this change as small as possible for now.

regards
ronnie sahlberg
Ronnie Sahlberg
2014-06-20 14:43:12 UTC
Permalink
Wrap all the ref updates inside a transaction.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/receive-pack.c | 96 +++++++++++++++++++++++++++++++++-----------------
1 file changed, 63 insertions(+), 33 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c323081..b51f8ae 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -194,7 +194,7 @@ static void write_head_info(void)

struct command {
struct command *next;
- const char *error_string;
+ char *error_string;
unsigned int skip_update:1,
did_not_exist:1;
int index;
@@ -468,19 +468,18 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
return 0;
}

-static const char *update(struct command *cmd, struct shallow_info *si)
+static char *update(struct command *cmd, struct shallow_info *si)
{
const char *name = cmd->ref_name;
struct strbuf namespaced_name_buf = STRBUF_INIT;
const char *namespaced_name;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
- struct ref_lock *lock;

/* only refs/... are allowed */
if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
rp_error("refusing to create funny ref '%s' remotely", name);
- return "funny refname";
+ return xstrdup("funny refname");
}

strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name);
@@ -498,20 +497,20 @@ static const char *update(struct command *cmd, struct shallow_info *si)
rp_error("refusing to update checked out branch: %s", name);
if (deny_current_branch == DENY_UNCONFIGURED)
refuse_unconfigured_deny();
- return "branch is currently checked out";
+ return xstrdup("branch is currently checked out");
}
}

if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
error("unpack should have generated %s, "
"but I can't find it!", sha1_to_hex(new_sha1));
- return "bad pack";
+ return xstrdup("bad pack");
}

if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
if (deny_deletes && starts_with(name, "refs/heads/")) {
rp_error("denying ref deletion for %s", name);
- return "deletion prohibited";
+ return xstrdup("deletion prohibited");
}

if (!strcmp(namespaced_name, head_name)) {
@@ -526,7 +525,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (deny_delete_current == DENY_UNCONFIGURED)
refuse_unconfigured_deny_delete_current();
rp_error("refusing to delete the current branch: %s", name);
- return "deletion of the current branch prohibited";
+ return xstrdup("deletion of the current branch prohibited");
}
}
}
@@ -544,19 +543,19 @@ static const char *update(struct command *cmd, struct shallow_info *si)
old_object->type != OBJ_COMMIT ||
new_object->type != OBJ_COMMIT) {
error("bad sha1 objects for %s", name);
- return "bad ref";
+ return xstrdup("bad ref");
}
old_commit = (struct commit *)old_object;
new_commit = (struct commit *)new_object;
if (!in_merge_bases(old_commit, new_commit)) {
rp_error("denying non-fast-forward %s"
" (you should pull first)", name);
- return "non-fast-forward";
+ return xstrdup("non-fast-forward");
}
}
if (run_update_hook(cmd)) {
rp_error("hook declined to update %s", name);
- return "hook declined";
+ return xstrdup("hook declined");
}

if (is_null_sha1(new_sha1)) {
@@ -571,24 +570,32 @@ static const char *update(struct command *cmd, struct shallow_info *si)
}
if (delete_ref(namespaced_name, old_sha1, 0)) {
rp_error("failed to delete %s", name);
- return "failed to delete";
+ return xstrdup("failed to delete");
}
return NULL; /* good */
}
else {
+ struct strbuf err = STRBUF_INIT;
+ struct ref_transaction *transaction;
+
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si))
- return "shallow error";
-
- lock = lock_any_ref_for_update(namespaced_name, old_sha1,
- 0, NULL);
- if (!lock) {
- rp_error("failed to lock %s", name);
- return "failed to lock";
- }
- if (write_ref_sha1(lock, new_sha1, "push")) {
- return "failed to write"; /* error() already called */
+ return xstrdup("shallow error");
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, namespaced_name,
+ new_sha1, old_sha1, 0, 1, &err) ||
+ ref_transaction_commit(transaction, "push", &err)) {
+ char *str = strbuf_detach(&err, NULL);
+ ref_transaction_free(transaction);
+
+ rp_error("%s", str);
+ return str;
}
+
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
return NULL; /* good */
}
}
@@ -647,6 +654,9 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
int flag;

+ if (cmd->error_string)
+ die("BUG: check_alised_update called with failed cmd");
+
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
strbuf_release(&buf);
@@ -658,7 +668,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
if (!dst_name) {
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
cmd->skip_update = 1;
- cmd->error_string = "broken symref";
+ cmd->error_string = xstrdup("broken symref");
return;
}

@@ -684,8 +694,9 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
cmd->ref_name, cmd_oldh, cmd_newh,
dst_cmd->ref_name, dst_oldh, dst_newh);

- cmd->error_string = dst_cmd->error_string =
- "inconsistent aliased update";
+ cmd->error_string = xstrdup("inconsistent aliased update");
+ free(dst_cmd->error_string);
+ dst_cmd->error_string = xstrdup("inconsistent aliased update");
}

static void check_aliased_updates(struct command *commands)
@@ -733,7 +744,9 @@ static void set_connectivity_errors(struct command *commands,
if (!check_everything_connected(command_singleton_iterator,
0, &singleton))
continue;
- cmd->error_string = "missing necessary objects";
+ if (cmd->error_string) /* can't happen */
+ continue;
+ cmd->error_string = xstrdup("missing necessary objects");
}
}

@@ -770,9 +783,9 @@ static void reject_updates_to_hidden(struct command *commands)
if (cmd->error_string || !ref_is_hidden(cmd->ref_name))
continue;
if (is_null_sha1(cmd->new_sha1))
- cmd->error_string = "deny deleting a hidden ref";
+ cmd->error_string = xstrdup("deny deleting a hidden ref");
else
- cmd->error_string = "deny updating a hidden ref";
+ cmd->error_string = xstrdup("deny updating a hidden ref");
}
}

@@ -786,8 +799,11 @@ static void execute_commands(struct command *commands,
struct iterate_data data;

if (unpacker_error) {
- for (cmd = commands; cmd; cmd = cmd->next)
- cmd->error_string = "unpacker error";
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (cmd->error_string) /* can't happen */
+ continue;
+ cmd->error_string = xstrdup("unpacker error");
+ }
return;
}

@@ -800,8 +816,9 @@ static void execute_commands(struct command *commands,

if (run_receive_hook(commands, "pre-receive", 0)) {
for (cmd = commands; cmd; cmd = cmd->next) {
- if (!cmd->error_string)
- cmd->error_string = "pre-receive hook declined";
+ if (cmd->error_string)
+ continue;
+ cmd->error_string = xstrdup("pre-receive hook declined");
}
return;
}
@@ -1079,7 +1096,8 @@ static void update_shallow_info(struct command *commands,
if (is_null_sha1(cmd->new_sha1))
continue;
if (ref_status[cmd->index]) {
- cmd->error_string = "shallow update not allowed";
+ free(cmd->error_string);
+ cmd->error_string = xstrdup("shallow update not allowed");
cmd->skip_update = 1;
}
}
@@ -1120,6 +1138,17 @@ static int delete_only(struct command *commands)
return 1;
}

+static void free_commands(struct command *commands)
+{
+ while (commands) {
+ struct command *next = commands->next;
+
+ free(commands->error_string);
+ free(commands);
+ commands = next;
+ }
+}
+
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
int advertise_refs = 0;
@@ -1215,5 +1244,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
packet_flush(1);
sha1_array_clear(&shallow);
sha1_array_clear(&ref);
+ free_commands(commands);
return 0;
}
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 13:20:22 UTC
Permalink
Post by Ronnie Sahlberg
Wrap all the ref updates inside a transaction.
---
builtin/receive-pack.c | 96 +++++++++++++++++++++++++++++++++-----------------
1 file changed, 63 insertions(+), 33 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c323081..b51f8ae 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
[...]
@@ -647,6 +654,9 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
int flag;
+ if (cmd->error_string)
+ die("BUG: check_alised_update called with failed cmd");
s/check_alised_update/check_aliased_update/
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 18:51:25 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Wrap all the ref updates inside a transaction.
---
builtin/receive-pack.c | 96 +++++++++++++++++++++++++++++++++-----------------
1 file changed, 63 insertions(+), 33 deletions(-)
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c323081..b51f8ae 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
[...]
@@ -647,6 +654,9 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
char cmd_oldh[41], cmd_newh[41], dst_oldh[41], dst_newh[41];
int flag;
+ if (cmd->error_string)
+ die("BUG: check_alised_update called with failed cmd");
s/check_alised_update/check_aliased_update/
Done, thanks.
Ronnie Sahlberg
2014-06-20 14:43:04 UTC
Permalink
Track the status of a transaction in a new status field. Check the field for
sanity, i.e. that status must be OPEN when _commit/_create/_delete or
_update is called or else die(BUG:...)

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 40 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index 9cb7908..8c695ba 100644
--- a/refs.c
+++ b/refs.c
@@ -3387,6 +3387,25 @@ struct ref_update {
};

/*
+ * Transaction states.
+ * OPEN: The transaction is in a valid state and can accept new updates.
+ * An OPEN transaction can be committed.
+ * CLOSED: If an open transaction is successfully committed the state will
+ * change to CLOSED. No further changes can be made to a CLOSED
+ * transaction.
+ * CLOSED means that all updates have been successfully committed and
+ * the only thing that remains is to free the completed transaction.
+ * ERROR: The transaction has failed and is no longer committable.
+ * No further changes can be made to a CLOSED transaction and it must
+ * be rolled back using transaction_free.
+ */
+enum ref_transaction_state {
+ REF_TRANSACTION_OPEN = 0,
+ REF_TRANSACTION_CLOSED = 1,
+ REF_TRANSACTION_ERROR = 2,
+};
+
+/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
* as atomically as possible. This structure is opaque to callers.
@@ -3395,6 +3414,8 @@ struct ref_transaction {
struct ref_update **updates;
size_t alloc;
size_t nr;
+ enum ref_transaction_state state;
+ int status;
};

struct ref_transaction *ref_transaction_begin(struct strbuf *err)
@@ -3437,6 +3458,9 @@ int ref_transaction_update(struct ref_transaction *transaction,
{
struct ref_update *update;

+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");

@@ -3457,6 +3481,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
{
struct ref_update *update;

+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: create called for transaction that is not open");
+
if (!new_sha1 || is_null_sha1(new_sha1))
die("BUG: create ref with null new_sha1");

@@ -3477,6 +3504,9 @@ int ref_transaction_delete(struct ref_transaction *transaction,
{
struct ref_update *update;

+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: delete called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");

@@ -3532,8 +3562,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
int n = transaction->nr;
struct ref_update **updates = transaction->updates;

- if (!n)
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
+
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
return 0;
+ }

/* Allocate work space */
delnames = xmalloc(sizeof(*delnames) * n);
@@ -3595,6 +3630,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
clear_loose_ref_cache(&ref_cache);

cleanup:
+ transaction->state = ret ? REF_TRANSACTION_ERROR
+ : REF_TRANSACTION_CLOSED;
+
for (i = 0; i < n; i++)
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 12:00:15 UTC
Permalink
Post by Ronnie Sahlberg
Track the status of a transaction in a new status field. Check the field for
The status field is not set or used anywhere. The field that you use is
"state".
Post by Ronnie Sahlberg
sanity, i.e. that status must be OPEN when _commit/_create/_delete or
_update is called or else die(BUG:...)
---
refs.c | 40 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/refs.c b/refs.c
index 9cb7908..8c695ba 100644
--- a/refs.c
+++ b/refs.c
@@ -3387,6 +3387,25 @@ struct ref_update {
};
/*
+ * Transaction states.
+ * OPEN: The transaction is in a valid state and can accept new updates.
+ * An OPEN transaction can be committed.
+ * CLOSED: If an open transaction is successfully committed the state will
+ * change to CLOSED. No further changes can be made to a CLOSED
+ * transaction.
+ * CLOSED means that all updates have been successfully committed and
+ * the only thing that remains is to free the completed transaction.
+ * ERROR: The transaction has failed and is no longer committable.
+ * No further changes can be made to a CLOSED transaction and it must
+ * be rolled back using transaction_free.
+ */
+enum ref_transaction_state {
+ REF_TRANSACTION_OPEN = 0,
+ REF_TRANSACTION_CLOSED = 1,
+ REF_TRANSACTION_ERROR = 2,
+};
+
+/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
* as atomically as possible. This structure is opaque to callers.
@@ -3395,6 +3414,8 @@ struct ref_transaction {
struct ref_update **updates;
size_t alloc;
size_t nr;
+ enum ref_transaction_state state;
+ int status;
The status field should probably be deleted.
Post by Ronnie Sahlberg
};
struct ref_transaction *ref_transaction_begin(struct strbuf *err)
@@ -3437,6 +3458,9 @@ int ref_transaction_update(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
@@ -3457,6 +3481,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: create called for transaction that is not open");
+
if (!new_sha1 || is_null_sha1(new_sha1))
die("BUG: create ref with null new_sha1");
@@ -3477,6 +3504,9 @@ int ref_transaction_delete(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: delete called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
@@ -3532,8 +3562,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
- if (!n)
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
+
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
return 0;
+ }
/* Allocate work space */
delnames = xmalloc(sizeof(*delnames) * n);
@@ -3595,6 +3630,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
clear_loose_ref_cache(&ref_cache);
+ transaction->state = ret ? REF_TRANSACTION_ERROR
+ : REF_TRANSACTION_CLOSED;
+
for (i = 0; i < n; i++)
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
--
Michael Haggerty
***@alum.mit.edu
http://softwareswirl.blogspot.com/
Ronnie Sahlberg
2014-07-14 17:55:00 UTC
Permalink
I updated the comments.

Status is used in a later series to track certain errno settings. This
used to be done here but was moved to a later series.
I removed the status field for now and will re add it later when we
start using it.

Thanks!
Post by Michael Haggerty
Post by Ronnie Sahlberg
Track the status of a transaction in a new status field. Check the field for
The status field is not set or used anywhere. The field that you use is
"state".
Post by Ronnie Sahlberg
sanity, i.e. that status must be OPEN when _commit/_create/_delete or
_update is called or else die(BUG:...)
---
refs.c | 40 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/refs.c b/refs.c
index 9cb7908..8c695ba 100644
--- a/refs.c
+++ b/refs.c
@@ -3387,6 +3387,25 @@ struct ref_update {
};
/*
+ * Transaction states.
+ * OPEN: The transaction is in a valid state and can accept new updates.
+ * An OPEN transaction can be committed.
+ * CLOSED: If an open transaction is successfully committed the state will
+ * change to CLOSED. No further changes can be made to a CLOSED
+ * transaction.
+ * CLOSED means that all updates have been successfully committed and
+ * the only thing that remains is to free the completed transaction.
+ * ERROR: The transaction has failed and is no longer committable.
+ * No further changes can be made to a CLOSED transaction and it must
+ * be rolled back using transaction_free.
+ */
+enum ref_transaction_state {
+ REF_TRANSACTION_OPEN = 0,
+ REF_TRANSACTION_CLOSED = 1,
+ REF_TRANSACTION_ERROR = 2,
+};
+
+/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
* as atomically as possible. This structure is opaque to callers.
@@ -3395,6 +3414,8 @@ struct ref_transaction {
struct ref_update **updates;
size_t alloc;
size_t nr;
+ enum ref_transaction_state state;
+ int status;
The status field should probably be deleted.
Post by Ronnie Sahlberg
};
struct ref_transaction *ref_transaction_begin(struct strbuf *err)
@@ -3437,6 +3458,9 @@ int ref_transaction_update(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: update called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
@@ -3457,6 +3481,9 @@ int ref_transaction_create(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: create called for transaction that is not open");
+
if (!new_sha1 || is_null_sha1(new_sha1))
die("BUG: create ref with null new_sha1");
@@ -3477,6 +3504,9 @@ int ref_transaction_delete(struct ref_transaction *transaction,
{
struct ref_update *update;
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: delete called for transaction that is not open");
+
if (have_old && !old_sha1)
die("BUG: have_old is true but old_sha1 is NULL");
@@ -3532,8 +3562,13 @@ int ref_transaction_commit(struct ref_transaction *transaction,
int n = transaction->nr;
struct ref_update **updates = transaction->updates;
- if (!n)
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
+
+ if (!n) {
+ transaction->state = REF_TRANSACTION_CLOSED;
return 0;
+ }
/* Allocate work space */
delnames = xmalloc(sizeof(*delnames) * n);
@@ -3595,6 +3630,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
clear_loose_ref_cache(&ref_cache);
+ transaction->state = ret ? REF_TRANSACTION_ERROR
+ : REF_TRANSACTION_CLOSED;
+
for (i = 0; i < n; i++)
if (updates[i]->lock)
unlock_ref(updates[i]->lock);
--
Michael Haggerty
http://softwareswirl.blogspot.com/
Ronnie Sahlberg
2014-06-20 14:43:00 UTC
Permalink
Update ref_transaction_update() do some basic error checking and return
non-zero on error. Update all callers to check ref_transaction_update() for
error. There are currently no conditions in _update that will return error but
there will be in the future. Add an err argument that will be updated on
failure. In future patches we will start doing both locking and checking
for name conflicts in _update instead of _commit at which time this function
will start returning errors for these conditions.

Also check for BUGs during update and die(BUG:...) if we are calling
_update with have_old but the old_sha1 pointer is NULL.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 12 +++++++-----
refs.c | 18 ++++++++++++------
refs.h | 14 +++++++++-----
3 files changed, 28 insertions(+), 16 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 88ab785..3067b11 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -16,6 +16,7 @@ static struct ref_transaction *transaction;

static char line_termination = '\n';
static int update_flags;
+static struct strbuf err = STRBUF_INIT;

/*
* Parse one whitespace- or NUL-terminated, possibly C-quoted argument
@@ -197,8 +198,9 @@ static const char *parse_cmd_update(struct strbuf *input, const char *next)
if (*next != line_termination)
die("update %s: extra input: %s", refname, next);

- ref_transaction_update(transaction, refname, new_sha1, old_sha1,
- update_flags, have_old);
+ if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, have_old, &err))
+ die("%s", err.buf);

update_flags = 0;
free(refname);
@@ -286,8 +288,9 @@ static const char *parse_cmd_verify(struct strbuf *input, const char *next)
if (*next != line_termination)
die("verify %s: extra input: %s", refname, next);

- ref_transaction_update(transaction, refname, new_sha1, old_sha1,
- update_flags, have_old);
+ if (ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+ update_flags, have_old, &err))
+ die("%s", err.buf);

update_flags = 0;
free(refname);
@@ -342,7 +345,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
const char *refname, *oldval, *msg = NULL;
unsigned char sha1[20], oldsha1[20];
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
- struct strbuf err = STRBUF_INIT;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
diff --git a/refs.c b/refs.c
index 4f78bd9..3f05e88 100644
--- a/refs.c
+++ b/refs.c
@@ -3428,19 +3428,25 @@ static struct ref_update *add_update(struct ref_transaction *transaction,
return update;
}

-void ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- const unsigned char *old_sha1,
- int flags, int have_old)
+int ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (have_old && !old_sha1)
+ die("BUG: have_old is true but old_sha1 is NULL");

+ update = add_update(transaction, refname);
hashcpy(update->new_sha1, new_sha1);
update->flags = flags;
update->have_old = have_old;
if (have_old)
hashcpy(update->old_sha1, old_sha1);
+ return 0;
}

void ref_transaction_create(struct ref_transaction *transaction,
diff --git a/refs.h b/refs.h
index 163b45c..c5376ce 100644
--- a/refs.h
+++ b/refs.h
@@ -246,12 +246,16 @@ struct ref_transaction *ref_transaction_begin(void);
* be deleted. If have_old is true, then old_sha1 holds the value
* that the reference should have had before the update, or zeros if
* it must not have existed beforehand.
+ * Function returns 0 on success and non-zero on failure. A failure to update
+ * means that the transaction as a whole has failed and will need to be
+ * rolled back. On failure the err buffer will be updated.
*/
-void ref_transaction_update(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- const unsigned char *old_sha1,
- int flags, int have_old);
+int ref_transaction_update(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err);

/*
* Add a reference creation to transaction. new_sha1 is the value
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:02 UTC
Permalink
Change ref_transaction_delete() to do basic error checking and return
non-zero of error. Update all callers to check the return for
ref_transaction_delete(). There are currently no conditions in _delete that
will return error but there will be in the future. Add an err argument that
will be updated on failure.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 5 +++--
refs.c | 16 +++++++++++-----
refs.h | 12 ++++++++----
3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 41121fa..7c9c248 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -258,8 +258,9 @@ static const char *parse_cmd_delete(struct strbuf *input, const char *next)
if (*next != line_termination)
die("delete %s: extra input: %s", refname, next);

- ref_transaction_delete(transaction, refname, old_sha1,
- update_flags, have_old);
+ if (ref_transaction_delete(transaction, refname, old_sha1,
+ update_flags, have_old, &err))
+ die("%s", err.buf);

update_flags = 0;
free(refname);
diff --git a/refs.c b/refs.c
index c49f1c6..40f04f4 100644
--- a/refs.c
+++ b/refs.c
@@ -3469,19 +3469,25 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 0;
}

-void ref_transaction_delete(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *old_sha1,
- int flags, int have_old)
+int ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;

+ if (have_old && !old_sha1)
+ die("BUG: have_old is true but old_sha1 is NULL");
+
+ update = add_update(transaction, refname);
update->flags = flags;
update->have_old = have_old;
if (have_old) {
assert(!is_null_sha1(old_sha1));
hashcpy(update->old_sha1, old_sha1);
}
+ return 0;
}

int update_ref(const char *action, const char *refname,
diff --git a/refs.h b/refs.h
index 33b4383..eeababd 100644
--- a/refs.h
+++ b/refs.h
@@ -315,11 +315,15 @@ int ref_transaction_create(struct ref_transaction *transaction,
* Add a reference deletion to transaction. If have_old is true, then
* old_sha1 holds the value that the reference should have had before
* the update (which must not be the null SHA-1).
+ * Function returns 0 on success and non-zero on failure. A failure to delete
+ * means that the transaction as a whole has failed and will need to be
+ * rolled back.
*/
-void ref_transaction_delete(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *old_sha1,
- int flags, int have_old);
+int ref_transaction_delete(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *old_sha1,
+ int flags, int have_old,
+ struct strbuf *err);

/*
* Commit all of the changes that have been queued in transaction, as
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:52 UTC
Permalink
Making errno when returning from remove_empty_directories() more
obviously meaningful, which should provide some peace of mind for
people auditing lock_ref_sha1_basic.

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index a48f805..cc69581 100644
--- a/refs.c
+++ b/refs.c
@@ -1960,14 +1960,16 @@ static int remove_empty_directories(const char *file)
* only empty directories), remove them.
*/
struct strbuf path;
- int result;
+ int result, save_errno;

strbuf_init(&path, 20);
strbuf_addstr(&path, file);

result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
+ save_errno = errno;

strbuf_release(&path);
+ errno = save_errno;

return result;
}
@@ -2056,6 +2058,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}

+/* This function should make sure errno is meaningful on error */
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p)
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:57 UTC
Permalink
Change update_ref_write to also update an error strbuf on failure.
This makes the error available to ref_transaction_commit callers if the
transaction failed due to update_ref_sha1/write_ref_sha1 failures.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/refs.c b/refs.c
index 115f143..003b313 100644
--- a/refs.c
+++ b/refs.c
@@ -3353,10 +3353,13 @@ static struct ref_lock *update_ref_lock(const char *refname,

static int update_ref_write(const char *action, const char *refname,
const unsigned char *sha1, struct ref_lock *lock,
- enum action_on_err onerr)
+ struct strbuf *err, enum action_on_err onerr)
{
if (write_ref_sha1(lock, sha1, action) < 0) {
const char *str = "Cannot update the ref '%s'.";
+ if (err)
+ strbuf_addf(err, str, refname);
+
switch (onerr) {
case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
@@ -3477,7 +3480,7 @@ int update_ref(const char *action, const char *refname,
lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
if (!lock)
return 1;
- return update_ref_write(action, refname, sha1, lock, onerr);
+ return update_ref_write(action, refname, sha1, lock, NULL, onerr);
}

static int ref_update_compare(const void *r1, const void *r2)
@@ -3559,7 +3562,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
ret = update_ref_write(msg,
update->refname,
update->new_sha1,
- update->lock, onerr);
+ update->lock, err, onerr);
update->lock = NULL; /* freed by update_ref_write */
if (ret)
goto cleanup;
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:05 UTC
Permalink
Change tag.c to use ref transactions for all ref updates.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/tag.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/builtin/tag.c b/builtin/tag.c
index c6e8a71..c9bfc9a 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -548,7 +548,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
const char *object_ref, *tag;
- struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
int annotate = 0, force = 0, lines = -1;
@@ -556,6 +555,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
@@ -701,11 +702,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);

- lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
- if (!lock)
- die(_("%s: cannot lock the ref"), ref.buf);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref.buf);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, object, prev,
+ 0, !is_null_sha1(prev), &err) ||
+ ref_transaction_commit(transaction, NULL, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
if (force && !is_null_sha1(prev) && hashcmp(prev, object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 12:33:01 UTC
Permalink
Post by Ronnie Sahlberg
Change tag.c to use ref transactions for all ref updates.
---
builtin/tag.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/builtin/tag.c b/builtin/tag.c
index c6e8a71..c9bfc9a 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -548,7 +548,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct strbuf ref = STRBUF_INIT;
unsigned char object[20], prev[20];
const char *object_ref, *tag;
- struct ref_lock *lock;
struct create_tag_options opt;
char *cleanup_arg = NULL;
int annotate = 0, force = 0, lines = -1;
@@ -556,6 +555,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
const char *msgfile = NULL, *keyid = NULL;
struct msg_arg msg = { 0, STRBUF_INIT };
struct commit_list *with_commit = NULL;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
struct option options[] = {
OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'),
{ OPTION_INTEGER, 'n', NULL, &lines, N_("n"),
@@ -701,11 +702,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (annotate)
create_tag(object, tag, &buf, &opt, prev, object);
- lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL);
- if (!lock)
- die(_("%s: cannot lock the ref"), ref.buf);
- if (write_ref_sha1(lock, object, NULL) < 0)
- die(_("%s: cannot update the ref"), ref.buf);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf, object, prev,
+ 0, !is_null_sha1(prev), &err) ||
Similar to the error in sequencer.c a few patches later (explained in
more detail in my comment on that patch), here you only do a check if
!is_null_sha1(prev), whereas the old code always did the check. I think
you want

ref_transaction_update(transaction, ref.buf, object, prev,
0, 1, &err) ||

Please check whether you have made the same mistake in other patches.
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-06-20 14:42:55 UTC
Permalink
Making errno from write_ref_sha1() meaningful, which should fix

* a bug in "git checkout -b" where it prints strerror(errno)
=C2=A0despite errno possibly being zero or clobbered

* a bug in "git fetch"'s s_update_ref, which trusts the result of an
=C2=A0errno =3D=3D ENOTDIR check to detect D/F conflicts

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/refs.c b/refs.c
index 211429d..61570c9 100644
--- a/refs.c
+++ b/refs.c
@@ -2859,8 +2859,19 @@ static int log_ref_write(const char *refname, co=
nst unsigned char *old_sha1,
len +=3D copy_msg(logrec + len - 1, msg) - 1;
written =3D len <=3D maxlen ? write_in_full(logfd, logrec, len) : -1;
free(logrec);
- if (close(logfd) !=3D 0 || written !=3D len)
- return error("Unable to append to %s", log_file);
+ if (written !=3D len) {
+ int save_errno =3D errno;
+ close(logfd);
+ error("Unable to append to %s", log_file);
+ errno =3D save_errno;
+ return -1;
+ }
+ if (close(logfd)) {
+ int save_errno =3D errno;
+ error("Unable to append to %s", log_file);
+ errno =3D save_errno;
+ return -1;
+ }
return 0;
}
=20
@@ -2869,14 +2880,17 @@ static int is_branch(const char *refname)
return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"=
);
}
=20
+/* This function must return a meaningful errno */
int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
static char term =3D '\n';
struct object *o;
=20
- if (!lock)
+ if (!lock) {
+ errno =3D EINVAL;
return -1;
+ }
if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
unlock_ref(lock);
return 0;
@@ -2886,19 +2900,23 @@ int write_ref_sha1(struct ref_lock *lock,
error("Trying to write ref %s with nonexistent object %s",
lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock);
+ errno =3D EINVAL;
return -1;
}
if (o->type !=3D OBJ_COMMIT && is_branch(lock->ref_name)) {
error("Trying to write non-commit object %s to branch %s",
sha1_to_hex(sha1), lock->ref_name);
unlock_ref(lock);
+ errno =3D EINVAL;
return -1;
}
if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) !=3D 40 ||
- write_in_full(lock->lock_fd, &term, 1) !=3D 1
- || close_ref(lock) < 0) {
+ write_in_full(lock->lock_fd, &term, 1) !=3D 1 ||
+ close_ref(lock) < 0) {
+ int save_errno =3D errno;
error("Couldn't write %s", lock->lk->filename);
unlock_ref(lock);
+ errno =3D save_errno;
return -1;
}
clear_loose_ref_cache(&ref_cache);
--=20
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:58 UTC
Permalink
Call ref_transaction_commit with QUIET_ON_ERR and use the strbuf that is
returned to print a log message if/after the transaction fails.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 22617af..aec9004 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -342,6 +342,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
const char *refname, *oldval, *msg = NULL;
unsigned char sha1[20], oldsha1[20];
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
+ struct strbuf err = STRBUF_INIT;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@@ -359,18 +360,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with empty message.");

if (read_stdin) {
- int ret;
transaction = ref_transaction_begin();
-
if (delete || no_deref || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
update_refs_stdin();
- ret = ref_transaction_commit(transaction, msg, NULL,
- UPDATE_REFS_DIE_ON_ERR);
+ if (ref_transaction_commit(transaction, msg, &err,
+ UPDATE_REFS_QUIET_ON_ERR))
+ die("%s", err.buf);
ref_transaction_free(transaction);
- return ret;
+ return 0;
}

if (end_null)
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:54 UTC
Permalink
Making errno when returning from resolve_ref_unsafe() meaningful,
which should fix

* a bug in lock_ref_sha1_basic, where it assumes EISDIR
means it failed due to a directory being in the way

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
cache.h | 2 +-
refs.c | 19 +++++++++++++++----
2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/cache.h b/cache.h
index 8b12aa8..e7ec626 100644
--- a/cache.h
+++ b/cache.h
@@ -979,7 +979,7 @@ extern int read_ref(const char *refname, unsigned char *sha1);
* NULL. If more than MAXDEPTH recursive symbolic lookups are needed,
* give up and return NULL.
*
- * errno is sometimes set on errors, but not always.
+ * errno is set to something meaningful on error.
*/
extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
diff --git a/refs.c b/refs.c
index 7a815be..211429d 100644
--- a/refs.c
+++ b/refs.c
@@ -1334,6 +1334,7 @@ static const char *handle_missing_loose_ref(const char *refname,
}
}

+/* This function needs to return a meaningful errno on failure */
const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
{
int depth = MAXDEPTH;
@@ -1344,8 +1345,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
if (flag)
*flag = 0;

- if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
+ if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+ errno = EINVAL;
return NULL;
+ }

for (;;) {
char path[PATH_MAX];
@@ -1353,8 +1356,10 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
char *buf;
int fd;

- if (--depth < 0)
+ if (--depth < 0) {
+ errno = ELOOP;
return NULL;
+ }

git_snpath(path, sizeof(path), "%s", refname);

@@ -1416,9 +1421,13 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
return NULL;
}
len = read_in_full(fd, buffer, sizeof(buffer)-1);
- close(fd);
- if (len < 0)
+ if (len < 0) {
+ int save_errno = errno;
+ close(fd);
+ errno = save_errno;
return NULL;
+ }
+ close(fd);
while (len && isspace(buffer[len-1]))
len--;
buffer[len] = '\0';
@@ -1435,6 +1444,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
(buffer[40] != '\0' && !isspace(buffer[40]))) {
if (flag)
*flag |= REF_ISBROKEN;
+ errno = EINVAL;
return NULL;
}
return refname;
@@ -1447,6 +1457,7 @@ const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int rea
if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
if (flag)
*flag |= REF_ISBROKEN;
+ errno = EINVAL;
return NULL;
}
refname = strcpy(refname_buffer, buf);
--
2.0.0.420.g181e020.dirty
Karsten Blees
2014-06-26 09:54:40 UTC
Permalink
Post by Ronnie Sahlberg
+ errno = ELOOP;
This fails on MinGW and MSVC < 2010. Perhaps add this to compat/mingw.h?

#ifndef ELOOP
#define ELOOP EMLINK
#endif
Ronnie Sahlberg
2014-06-20 14:43:03 UTC
Permalink
Add an err argument to _begin so that on non-fatal failures in future ref
backends we can report a nice error back to the caller.
While _begin can currently never fail for other reasons than OOM, in which
case we die() anyway, we may add other types of backends in the future.
For example, a hypothetical MySQL backend could fail in _being with
"Can not connect to MySQL server. No route to host".

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 2 +-
refs.c | 2 +-
refs.h | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 7c9c248..c6ad0be 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -365,7 +365,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
die("Refusing to perform update with empty message.");

if (read_stdin) {
- transaction = ref_transaction_begin();
+ transaction = ref_transaction_begin(&err);
if (delete || no_deref || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
diff --git a/refs.c b/refs.c
index 40f04f4..9cb7908 100644
--- a/refs.c
+++ b/refs.c
@@ -3397,7 +3397,7 @@ struct ref_transaction {
size_t nr;
};

-struct ref_transaction *ref_transaction_begin(void)
+struct ref_transaction *ref_transaction_begin(struct strbuf *err)
{
return xcalloc(1, sizeof(struct ref_transaction));
}
diff --git a/refs.h b/refs.h
index eeababd..e729ea9 100644
--- a/refs.h
+++ b/refs.h
@@ -269,7 +269,7 @@ enum action_on_err {
* Begin a reference transaction. The reference transaction must
* be freed by calling ref_transaction_free().
*/
-struct ref_transaction *ref_transaction_begin(void);
+struct ref_transaction *ref_transaction_begin(struct strbuf *err);

/*
* The following functions add a reference check or update to a
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 11:53:04 UTC
Permalink
Post by Ronnie Sahlberg
Add an err argument to _begin so that on non-fatal failures in future ref
backends we can report a nice error back to the caller.
While _begin can currently never fail for other reasons than OOM, in which
case we die() anyway, we may add other types of backends in the future.
For example, a hypothetical MySQL backend could fail in _being with
s/_being/_begin/
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 17:45:37 UTC
Permalink
Thanks. Fixed.
Post by Michael Haggerty
Post by Ronnie Sahlberg
Add an err argument to _begin so that on non-fatal failures in future ref
backends we can report a nice error back to the caller.
While _begin can currently never fail for other reasons than OOM, in which
case we die() anyway, we may add other types of backends in the future.
For example, a hypothetical MySQL backend could fail in _being with
s/_being/_begin/
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
Ronnie Sahlberg
2014-06-20 14:42:51 UTC
Permalink
Making errno when returning from verify_lock() meaningful, which
should almost but not completely fix

* a bug in "git fetch"'s s_update_ref, which trusts the result of an
errno == ENOTDIR check to detect D/F conflicts

ENOTDIR makes sense as a sign that a file was in the way of a
directory we wanted to create. Should "git fetch" also look for
ENOTEMPTY or EEXIST to catch cases where a directory was in the way
of a file to be created?

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 4 ++++
refs.h | 6 +++++-
2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/refs.c b/refs.c
index 9ea519c..a48f805 100644
--- a/refs.c
+++ b/refs.c
@@ -1932,18 +1932,22 @@ int refname_match(const char *abbrev_name, const char *full_name)
return 0;
}

+/* This function should make sure errno is meaningful on error */
static struct ref_lock *verify_lock(struct ref_lock *lock,
const unsigned char *old_sha1, int mustexist)
{
if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
+ int save_errno = errno;
error("Can't verify ref %s", lock->ref_name);
unlock_ref(lock);
+ errno = save_errno;
return NULL;
}
if (hashcmp(lock->old_sha1, old_sha1)) {
error("Ref %s is at %s but expected %s", lock->ref_name,
sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
unlock_ref(lock);
+ errno = EBUSY;
return NULL;
}
return lock;
diff --git a/refs.h b/refs.h
index 82cc5cb..8d6cac7 100644
--- a/refs.h
+++ b/refs.h
@@ -137,11 +137,15 @@ extern int ref_exists(const char *);
*/
extern int peel_ref(const char *refname, unsigned char *sha1);

-/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+/*
+ * Locks a "refs/" ref returning the lock on success and NULL on failure.
+ * On failure errno is set to something meaningful.
+ */
extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);

/** Locks any ref (for 'HEAD' type refs). */
#define REF_NODEREF 0x01
+/* errno is set to something meaningful on failure */
extern struct ref_lock *lock_any_ref_for_update(const char *refname,
const unsigned char *old_sha1,
int flags, int *type_p);
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:15 UTC
Permalink
No external callers reference lock_ref_sha1 any more so lets declare it static.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 2 +-
refs.h | 6 ------
2 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/refs.c b/refs.c
index 4bdccc5..456acdd 100644
--- a/refs.c
+++ b/refs.c
@@ -2170,7 +2170,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
return NULL;
}

-struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
+static struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
{
char refpath[PATH_MAX];
if (check_refname_format(refname, 0))
diff --git a/refs.h b/refs.h
index e729ea9..4ac4a7d 100644
--- a/refs.h
+++ b/refs.h
@@ -177,12 +177,6 @@ extern int ref_exists(const char *);
*/
extern int peel_ref(const char *refname, unsigned char *sha1);

-/*
- * Locks a "refs/" ref returning the lock on success and NULL on failure.
- * On failure errno is set to something meaningful.
- */
-extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
-
/** Locks any ref (for 'HEAD' type refs). */
#define REF_NODEREF 0x01
/* errno is set to something meaningful on failure */
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:42:50 UTC
Permalink
Making errno when returning from log_ref_setup() meaningful,

Signed-off-by: Ronnie Sahlberg <***@google.com>
---
refs.c | 27 +++++++++++++++++++--------
refs.h | 4 +++-
2 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/refs.c b/refs.c
index 67a0217..9ea519c 100644
--- a/refs.c
+++ b/refs.c
@@ -2751,6 +2751,7 @@ static int copy_msg(char *buf, const char *msg)
return cp - buf;
}

+/* This function must set a meaningful errno on failure */
int log_ref_setup(const char *refname, char *logfile, int bufsize)
{
int logfd, oflags = O_APPEND | O_WRONLY;
@@ -2761,9 +2762,12 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)
starts_with(refname, "refs/remotes/") ||
starts_with(refname, "refs/notes/") ||
!strcmp(refname, "HEAD"))) {
- if (safe_create_leading_directories(logfile) < 0)
- return error("unable to create directory for %s",
- logfile);
+ if (safe_create_leading_directories(logfile) < 0) {
+ int save_errno = errno;
+ error("unable to create directory for %s", logfile);
+ errno = save_errno;
+ return -1;
+ }
oflags |= O_CREAT;
}

@@ -2774,15 +2778,22 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize)

if ((oflags & O_CREAT) && errno == EISDIR) {
if (remove_empty_directories(logfile)) {
- return error("There are still logs under '%s'",
- logfile);
+ int save_errno = errno;
+ error("There are still logs under '%s'",
+ logfile);
+ errno = save_errno;
+ return -1;
}
logfd = open(logfile, oflags, 0666);
}

- if (logfd < 0)
- return error("Unable to append to %s: %s",
- logfile, strerror(errno));
+ if (logfd < 0) {
+ int save_errno = errno;
+ error("Unable to append to %s: %s", logfile,
+ strerror(errno));
+ errno = save_errno;
+ return -1;
+ }
}

adjust_shared_perm(logfile);
diff --git a/refs.h b/refs.h
index 65f7637..82cc5cb 100644
--- a/refs.h
+++ b/refs.h
@@ -158,7 +158,9 @@ extern void unlock_ref(struct ref_lock *lock);
/** Writes sha1 into the ref specified by the lock. **/
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);

-/** Setup reflog before using. **/
+/*
+ * Setup reflog before using. Set errno to something meaningful on failure.
+ */
int log_ref_setup(const char *refname, char *logfile, int bufsize);

/** Reads log for the value of ref during at_time. **/
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:09 UTC
Permalink
Change update_branch() to use ref transactions for updates.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
fast-import.c | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/fast-import.c b/fast-import.c
index 6707a66..d5206ee 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -1679,8 +1679,9 @@ found_entry:
static int update_branch(struct branch *b)
{
static const char *msg = "fast-import";
- struct ref_lock *lock;
+ struct ref_transaction *transaction;
unsigned char old_sha1[20];
+ struct strbuf err = STRBUF_INIT;

if (read_ref(b->name, old_sha1))
hashclr(old_sha1);
@@ -1689,29 +1690,32 @@ static int update_branch(struct branch *b)
delete_ref(b->name, old_sha1, 0);
return 0;
}
- lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
- if (!lock)
- return error("Unable to lock %s", b->name);
if (!force_update && !is_null_sha1(old_sha1)) {
struct commit *old_cmit, *new_cmit;

old_cmit = lookup_commit_reference_gently(old_sha1, 0);
new_cmit = lookup_commit_reference_gently(b->sha1, 0);
- if (!old_cmit || !new_cmit) {
- unlock_ref(lock);
+ if (!old_cmit || !new_cmit)
return error("Branch %s is missing commits.", b->name);
- }

if (!in_merge_bases(old_cmit, new_cmit)) {
- unlock_ref(lock);
warning("Not updating %s"
" (new tip %s does not contain %s)",
b->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));
return -1;
}
}
- if (write_ref_sha1(lock, b->sha1, msg) < 0)
- return error("Unable to update %s", b->name);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, b->name, b->sha1, old_sha1,
+ 0, 1, &err) ||
+ ref_transaction_commit(transaction, msg, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ return -1;
+ }
+ ref_transaction_free(transaction);
return 0;
}
--
2.0.0.420.g181e020.dirty
Ronnie Sahlberg
2014-06-20 14:43:01 UTC
Permalink
Do basic error checking in ref_transaction_create() and make it return
non-zero on error. Update all callers to check the result of
ref_transaction_create(). There are currently no conditions in _create that
will return error but there will be in the future. Add an err argument that
will be updated on failure.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 4 +++-
refs.c | 18 +++++++++++------
refs.h | 55 +++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 63 insertions(+), 14 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 3067b11..41121fa 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -226,7 +226,9 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);

- ref_transaction_create(transaction, refname, new_sha1, update_flags);
+ if (ref_transaction_create(transaction, refname, new_sha1,
+ update_flags, &err))
+ die("%s", err.buf);

update_flags = 0;
free(refname);
diff --git a/refs.c b/refs.c
index 3f05e88..c49f1c6 100644
--- a/refs.c
+++ b/refs.c
@@ -3449,18 +3449,24 @@ int ref_transaction_update(struct ref_transaction *transaction,
return 0;
}

-void ref_transaction_create(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- int flags)
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create ref with null new_sha1");
+
+ update = add_update(transaction, refname);

- assert(!is_null_sha1(new_sha1));
hashcpy(update->new_sha1, new_sha1);
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
+ return 0;
}

void ref_transaction_delete(struct ref_transaction *transaction,
diff --git a/refs.h b/refs.h
index c5376ce..33b4383 100644
--- a/refs.h
+++ b/refs.h
@@ -10,6 +10,45 @@ struct ref_lock {
int force_write;
};

+/*
+ * A ref_transaction represents a collection of ref updates
+ * that should succeed or fail together.
+ *
+ * Calling sequence
+ * ----------------
+ * - Allocate and initialize a `struct ref_transaction` by calling
+ * `ref_transaction_begin()`.
+ *
+ * - List intended ref updates by calling functions like
+ * `ref_transaction_update()` and `ref_transaction_create()`.
+ *
+ * - Call `ref_transaction_commit()` to execute the transaction.
+ * If this succeeds, the ref updates will have taken place and
+ * the transaction cannot be rolled back.
+ *
+ * - At any time call `ref_transaction_free()` to discard the
+ * transaction and free associated resources. In particular,
+ * this rolls back the transaction if it has not been
+ * successfully committed.
+ *
+ * Error handling
+ * --------------
+ *
+ * On error, transaction functions append a message about what
+ * went wrong to the 'err' argument. The message mentions what
+ * ref was being updated (if any) when the error occurred so it
+ * can be passed to 'die' or 'error' as-is.
+ *
+ * The message is appended to err without first clearing err.
+ * This allows the caller to prepare preamble text to the generated
+ * error message:
+ *
+ * strbuf_addf(&err, "Error while doing foo-bar: ");
+ * if (ref_transaction_update(..., &err)) {
+ * ret = error("%s", err.buf);
+ * goto cleanup;
+ * }
+ */
struct ref_transaction;

/*
@@ -248,7 +287,7 @@ struct ref_transaction *ref_transaction_begin(void);
* it must not have existed beforehand.
* Function returns 0 on success and non-zero on failure. A failure to update
* means that the transaction as a whole has failed and will need to be
- * rolled back. On failure the err buffer will be updated.
+ * rolled back.
*/
int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
@@ -262,11 +301,15 @@ int ref_transaction_update(struct ref_transaction *transaction,
* that the reference should have after the update; it must not be the
* null SHA-1. It is verified that the reference does not exist
* already.
+ * Function returns 0 on success and non-zero on failure. A failure to create
+ * means that the transaction as a whole has failed and will need to be
+ * rolled back.
*/
-void ref_transaction_create(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- int flags);
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err);

/*
* Add a reference deletion to transaction. If have_old is true, then
@@ -282,8 +325,6 @@ void ref_transaction_delete(struct ref_transaction *transaction,
* Commit all of the changes that have been queued in transaction, as
* atomically as possible. Return a nonzero value if there is a
* problem.
- * If err is non-NULL we will add an error string to it to explain why
- * the transaction failed. The string does not end in newline.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
const char *msg, struct strbuf *err);
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 11:48:52 UTC
Permalink
I'm in my next attempt to get through your patch series. Sorry for the
long hiatus.

Patches 1-19 look OK aside from a minor typo that I just reported.

See below for a comment on this patch.
Post by Ronnie Sahlberg
Do basic error checking in ref_transaction_create() and make it return
non-zero on error. Update all callers to check the result of
ref_transaction_create(). There are currently no conditions in _create that
will return error but there will be in the future. Add an err argument that
will be updated on failure.
---
builtin/update-ref.c | 4 +++-
refs.c | 18 +++++++++++------
refs.h | 55 +++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 63 insertions(+), 14 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 3067b11..41121fa 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -226,7 +226,9 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
- ref_transaction_create(transaction, refname, new_sha1, update_flags);
+ if (ref_transaction_create(transaction, refname, new_sha1,
+ update_flags, &err))
+ die("%s", err.buf);
update_flags = 0;
free(refname);
diff --git a/refs.c b/refs.c
index 3f05e88..c49f1c6 100644
--- a/refs.c
+++ b/refs.c
@@ -3449,18 +3449,24 @@ int ref_transaction_update(struct ref_transaction *transaction,
return 0;
}
-void ref_transaction_create(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- int flags)
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create ref with null new_sha1");
+
+ update = add_update(transaction, refname);
- assert(!is_null_sha1(new_sha1));
hashcpy(update->new_sha1, new_sha1);
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
+ return 0;
}
void ref_transaction_delete(struct ref_transaction *transaction,
diff --git a/refs.h b/refs.h
index c5376ce..33b4383 100644
--- a/refs.h
+++ b/refs.h
@@ -10,6 +10,45 @@ struct ref_lock {
int force_write;
};
+/*
+ * A ref_transaction represents a collection of ref updates
+ * that should succeed or fail together.
+ *
+ * Calling sequence
+ * ----------------
+ * - Allocate and initialize a `struct ref_transaction` by calling
+ * `ref_transaction_begin()`.
+ *
+ * - List intended ref updates by calling functions like
+ * `ref_transaction_update()` and `ref_transaction_create()`.
+ *
+ * - Call `ref_transaction_commit()` to execute the transaction.
+ * If this succeeds, the ref updates will have taken place and
+ * the transaction cannot be rolled back.
+ *
+ * - At any time call `ref_transaction_free()` to discard the
+ * transaction and free associated resources. In particular,
+ * this rolls back the transaction if it has not been
+ * successfully committed.
+ *
+ * Error handling
+ * --------------
+ *
+ * On error, transaction functions append a message about what
+ * went wrong to the 'err' argument. The message mentions what
+ * ref was being updated (if any) when the error occurred so it
+ * can be passed to 'die' or 'error' as-is.
+ *
+ * The message is appended to err without first clearing err.
+ * This allows the caller to prepare preamble text to the generated
+ *
+ * strbuf_addf(&err, "Error while doing foo-bar: ");
+ * if (ref_transaction_update(..., &err)) {
+ * ret = error("%s", err.buf);
+ * goto cleanup;
+ * }
+ */
I don't have a problem with the API, but I think the idiom suggested in
the comment above is a bit silly. Surely one would do the following
instead:

if (ref_transaction_update(..., &err)) {
ret = error("Error while doing foo-bar: %s", err.buf);
goto cleanup;
}

I think it would also be helpful to document whether the error string
that is appended to the strbuf is terminated with a LF.
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
***@alum.mit.edu
Ronnie Sahlberg
2014-07-14 17:44:27 UTC
Permalink
I have changed the comment. Thanks.
Post by Michael Haggerty
I'm in my next attempt to get through your patch series. Sorry for the
long hiatus.
Patches 1-19 look OK aside from a minor typo that I just reported.
See below for a comment on this patch.
Post by Ronnie Sahlberg
Do basic error checking in ref_transaction_create() and make it return
non-zero on error. Update all callers to check the result of
ref_transaction_create(). There are currently no conditions in _create that
will return error but there will be in the future. Add an err argument that
will be updated on failure.
---
builtin/update-ref.c | 4 +++-
refs.c | 18 +++++++++++------
refs.h | 55 +++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 63 insertions(+), 14 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 3067b11..41121fa 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -226,7 +226,9 @@ static const char *parse_cmd_create(struct strbuf *input, const char *next)
if (*next != line_termination)
die("create %s: extra input: %s", refname, next);
- ref_transaction_create(transaction, refname, new_sha1, update_flags);
+ if (ref_transaction_create(transaction, refname, new_sha1,
+ update_flags, &err))
+ die("%s", err.buf);
update_flags = 0;
free(refname);
diff --git a/refs.c b/refs.c
index 3f05e88..c49f1c6 100644
--- a/refs.c
+++ b/refs.c
@@ -3449,18 +3449,24 @@ int ref_transaction_update(struct ref_transaction *transaction,
return 0;
}
-void ref_transaction_create(struct ref_transaction *transaction,
- const char *refname,
- const unsigned char *new_sha1,
- int flags)
+int ref_transaction_create(struct ref_transaction *transaction,
+ const char *refname,
+ const unsigned char *new_sha1,
+ int flags,
+ struct strbuf *err)
{
- struct ref_update *update = add_update(transaction, refname);
+ struct ref_update *update;
+
+ if (!new_sha1 || is_null_sha1(new_sha1))
+ die("BUG: create ref with null new_sha1");
+
+ update = add_update(transaction, refname);
- assert(!is_null_sha1(new_sha1));
hashcpy(update->new_sha1, new_sha1);
hashclr(update->old_sha1);
update->flags = flags;
update->have_old = 1;
+ return 0;
}
void ref_transaction_delete(struct ref_transaction *transaction,
diff --git a/refs.h b/refs.h
index c5376ce..33b4383 100644
--- a/refs.h
+++ b/refs.h
@@ -10,6 +10,45 @@ struct ref_lock {
int force_write;
};
+/*
+ * A ref_transaction represents a collection of ref updates
+ * that should succeed or fail together.
+ *
+ * Calling sequence
+ * ----------------
+ * - Allocate and initialize a `struct ref_transaction` by calling
+ * `ref_transaction_begin()`.
+ *
+ * - List intended ref updates by calling functions like
+ * `ref_transaction_update()` and `ref_transaction_create()`.
+ *
+ * - Call `ref_transaction_commit()` to execute the transaction.
+ * If this succeeds, the ref updates will have taken place and
+ * the transaction cannot be rolled back.
+ *
+ * - At any time call `ref_transaction_free()` to discard the
+ * transaction and free associated resources. In particular,
+ * this rolls back the transaction if it has not been
+ * successfully committed.
+ *
+ * Error handling
+ * --------------
+ *
+ * On error, transaction functions append a message about what
+ * went wrong to the 'err' argument. The message mentions what
+ * ref was being updated (if any) when the error occurred so it
+ * can be passed to 'die' or 'error' as-is.
+ *
+ * The message is appended to err without first clearing err.
+ * This allows the caller to prepare preamble text to the generated
+ *
+ * strbuf_addf(&err, "Error while doing foo-bar: ");
+ * if (ref_transaction_update(..., &err)) {
+ * ret = error("%s", err.buf);
+ * goto cleanup;
+ * }
+ */
I don't have a problem with the API, but I think the idiom suggested in
the comment above is a bit silly. Surely one would do the following
if (ref_transaction_update(..., &err)) {
ret = error("Error while doing foo-bar: %s", err.buf);
goto cleanup;
}
I think it would also be helpful to document whether the error string
that is appended to the strbuf is terminated with a LF.
Post by Ronnie Sahlberg
[...]
Michael
--
Michael Haggerty
Ronnie Sahlberg
2014-06-20 14:43:08 UTC
Permalink
Change to use ref transactions for all updates to refs.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
sequencer.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/sequencer.c b/sequencer.c
index 0a80c58..fd8acaf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -272,23 +272,31 @@ static int error_dirty_index(struct replay_opts *opts)
static int fast_forward_to(const unsigned char *to, const unsigned char *from,
int unborn, struct replay_opts *opts)
{
- struct ref_lock *ref_lock;
+ struct ref_transaction *transaction;
struct strbuf sb = STRBUF_INIT;
- int ret;
+ struct strbuf err = STRBUF_INIT;

read_cache();
if (checkout_fast_forward(from, to, 1))
exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
- 0, NULL);
- if (!ref_lock)
- return error(_("Failed to lock HEAD during fast_forward_to"));

strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
- ret = write_ref_sha1(ref_lock, to, sb.buf);
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", to, from,
+ 0, !unborn, &err) ||
+ ref_transaction_commit(transaction, sb.buf, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&sb);
+ strbuf_release(&err);
+ return -1;
+ }

strbuf_release(&sb);
- return ret;
+ ref_transaction_free(transaction);
+ return 0;
}

static int do_recursive_merge(struct commit *base, struct commit *next,
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 12:23:50 UTC
Permalink
Post by Ronnie Sahlberg
Change to use ref transactions for all updates to refs.
---
sequencer.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 0a80c58..fd8acaf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -272,23 +272,31 @@ static int error_dirty_index(struct replay_opts *opts)
static int fast_forward_to(const unsigned char *to, const unsigned char *from,
int unborn, struct replay_opts *opts)
{
- struct ref_lock *ref_lock;
+ struct ref_transaction *transaction;
struct strbuf sb = STRBUF_INIT;
- int ret;
+ struct strbuf err = STRBUF_INIT;
read_cache();
if (checkout_fast_forward(from, to, 1))
exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
- 0, NULL);
- if (!ref_lock)
- return error(_("Failed to lock HEAD during fast_forward_to"));
I think you've changed the semantics when unborn is set. Please note
that lock_any_ref_for_update() behaves differently if old_sha1 is NULL
(when no check is done) vs. when it is null_sha1 (when it verifies that
the reference didn't previously exist). So when unborn is true, the old
code verifies that the reference previously didn't exist...
Post by Ronnie Sahlberg
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
- ret = write_ref_sha1(ref_lock, to, sb.buf);
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", to, from,
+ 0, !unborn, &err) ||
...whereas when unborn is true, the new code does no check at all. I
think you want

ref_transaction_update(transaction, "HEAD",
to, unborn ? null_sha1 : from,
0, 1, &err) ||
Post by Ronnie Sahlberg
+ ref_transaction_commit(transaction, sb.buf, &err)) {
+ ref_transaction_free(transaction);
+ error("%s", err.buf);
+ strbuf_release(&sb);
+ strbuf_release(&err);
+ return -1;
+ }
strbuf_release(&sb);
- return ret;
+ ref_transaction_free(transaction);
+ return 0;
}
static int do_recursive_merge(struct commit *base, struct commit *next,
Michael
--
Michael Haggerty
***@alum.mit.edu
http://softwareswirl.blogspot.com/
Ronnie Sahlberg
2014-07-14 22:20:51 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
Change to use ref transactions for all updates to refs.
---
sequencer.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 0a80c58..fd8acaf 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -272,23 +272,31 @@ static int error_dirty_index(struct replay_opts *opts)
static int fast_forward_to(const unsigned char *to, const unsigned char *from,
int unborn, struct replay_opts *opts)
{
- struct ref_lock *ref_lock;
+ struct ref_transaction *transaction;
struct strbuf sb = STRBUF_INIT;
- int ret;
+ struct strbuf err = STRBUF_INIT;
read_cache();
if (checkout_fast_forward(from, to, 1))
exit(1); /* the callee should have complained already */
- ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from,
- 0, NULL);
- if (!ref_lock)
- return error(_("Failed to lock HEAD during fast_forward_to"));
I think you've changed the semantics when unborn is set. Please note
that lock_any_ref_for_update() behaves differently if old_sha1 is NULL
(when no check is done) vs. when it is null_sha1 (when it verifies that
the reference didn't previously exist). So when unborn is true, the old
code verifies that the reference previously didn't exist...
Post by Ronnie Sahlberg
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
- ret = write_ref_sha1(ref_lock, to, sb.buf);
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", to, from,
+ 0, !unborn, &err) ||
...whereas when unborn is true, the new code does no check at all. I
think you want
ref_transaction_update(transaction, "HEAD",
to, unborn ? null_sha1 : from,
0, 1, &err) ||
Right. Fixed.

Thanks.
Ronnie Sahlberg
2014-06-20 14:42:59 UTC
Permalink
Since all callers now use QUIET_ON_ERR we no longer need to provide an onerr
argument any more. Remove the onerr argument from the ref_transaction_commit
signature.

Reviewed-by: Jonathan Nieder <***@gmail.com>
Signed-off-by: Ronnie Sahlberg <***@google.com>
---
builtin/update-ref.c | 3 +--
refs.c | 22 +++++++---------------
refs.h | 3 +--
3 files changed, 9 insertions(+), 19 deletions(-)

diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index aec9004..88ab785 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -366,8 +366,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
if (end_null)
line_termination = '\0';
update_refs_stdin();
- if (ref_transaction_commit(transaction, msg, &err,
- UPDATE_REFS_QUIET_ON_ERR))
+ if (ref_transaction_commit(transaction, msg, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
return 0;
diff --git a/refs.c b/refs.c
index 003b313..4f78bd9 100644
--- a/refs.c
+++ b/refs.c
@@ -3491,8 +3491,7 @@ static int ref_update_compare(const void *r1, const void *r2)
}

static int ref_update_reject_duplicates(struct ref_update **updates, int n,
- struct strbuf *err,
- enum action_on_err onerr)
+ struct strbuf *err)
{
int i;
for (i = 1; i < n; i++)
@@ -3502,22 +3501,13 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
if (err)
strbuf_addf(err, str, updates[i]->refname);

- switch (onerr) {
- case UPDATE_REFS_MSG_ON_ERR:
- error(str, updates[i]->refname); break;
- case UPDATE_REFS_DIE_ON_ERR:
- die(str, updates[i]->refname); break;
- case UPDATE_REFS_QUIET_ON_ERR:
- break;
- }
return 1;
}
return 0;
}

int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, struct strbuf *err,
- enum action_on_err onerr)
+ const char *msg, struct strbuf *err)
{
int ret = 0, delnum = 0, i;
const char **delnames;
@@ -3532,7 +3522,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,

/* Copy, sort, and reject duplicate refs */
qsort(updates, n, sizeof(*updates), ref_update_compare);
- ret = ref_update_reject_duplicates(updates, n, err, onerr);
+ ret = ref_update_reject_duplicates(updates, n, err);
if (ret)
goto cleanup;

@@ -3544,7 +3534,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
(update->have_old ?
update->old_sha1 : NULL),
update->flags,
- &update->type, onerr);
+ &update->type,
+ UPDATE_REFS_QUIET_ON_ERR);
if (!update->lock) {
if (err)
strbuf_addf(err, "Cannot lock the ref '%s'.",
@@ -3562,7 +3553,8 @@ int ref_transaction_commit(struct ref_transaction *transaction,
ret = update_ref_write(msg,
update->refname,
update->new_sha1,
- update->lock, err, onerr);
+ update->lock, err,
+ UPDATE_REFS_QUIET_ON_ERR);
update->lock = NULL; /* freed by update_ref_write */
if (ret)
goto cleanup;
diff --git a/refs.h b/refs.h
index e588ff8..163b45c 100644
--- a/refs.h
+++ b/refs.h
@@ -282,8 +282,7 @@ void ref_transaction_delete(struct ref_transaction *transaction,
* the transaction failed. The string does not end in newline.
*/
int ref_transaction_commit(struct ref_transaction *transaction,
- const char *msg, struct strbuf *err,
- enum action_on_err onerr);
+ const char *msg, struct strbuf *err);

/*
* Free an existing transaction and all associated data.
--
2.0.0.420.g181e020.dirty
Michael Haggerty
2014-07-08 16:29:07 UTC
Permalink
Post by Ronnie Sahlberg
This patch series can also be found at
https://github.com/rsahlberg/git/tree/ref-transactions
This patch series is based on current master and expands on the transaction
API. It converts all ref updates, inside refs.c as well as external, to use the
transaction API for updates. This makes most of the ref updates to become
atomic when there are failures locking or writing to a ref.
This version completes the work to convert all ref updates to use transactions.
Now that all updates are through transactions I will start working on
cleaning up the reading of refs and to create an api for managing reflogs but
all that will go in a different patch series.
- Whitespace and style changes suggested by Jun.
I spent most of the day on reviewing this patch series, but now I'm out
of time again. Here is a summary from my point of view:

Patches 01-19 -- ACK mhagger
Patches 20-42 -- I sent various comments, small to large, concerning
these patches
Patch 43 -- Needs more justification if it is to be acceptable
Patch 44 -- Depends on 43
Patches 45-48 -- I didn't quite get to these, but...

Perhaps it would be more appropriate for the rules about reference name
conflicts to be enforced by the backend, since it is the limitations of
the current backend that impose the restrictions. Would that make sense?

On the other hand, removing the restrictions isn't simply a matter of
picking a different backend, because all Git repositories have to be
able to interact with each other.

So, I don't yet have a considered opinion on the matter.


I think it would be good to try to merge the first part of this patch
series to lock in some progress while we continue iterating on the
remainder. I'm satisfied that it is all going in the right direction
and I am thankful to Ronnie for pushing it forward. But handling
48-patch series is very daunting and I would welcome a split.

I'm not sure whether patches 01-19 are necessarily the right split
between merge-now/iterate-more; it is more or less an accident that I
stopped after patch 19 on an earlier review. Maybe Ronnie could propose
a logical subset of the commits as being ready to be merged to next in
the nearish term?

Michael
--
Michael Haggerty
***@alum.mit.edu
Junio C Hamano
2014-07-08 18:48:06 UTC
Permalink
Post by Michael Haggerty
Patches 01-19 -- ACK mhagger
Patches 20-42 -- I sent various comments, small to large, concerning
these patches
Patch 43 -- Needs more justification if it is to be acceptable
Patch 44 -- Depends on 43
Patches 45-48 -- I didn't quite get to these, but...
Perhaps it would be more appropriate for the rules about reference name
conflicts to be enforced by the backend, since it is the limitations of
the current backend that impose the restrictions. Would that make sense?
On the other hand, removing the restrictions isn't simply a matter of
picking a different backend, because all Git repositories have to be
able to interact with each other.
I'd say that "if you have foo/bar you cannot have foo" may have
started as an implementation limitation, but the interoperability
requirement with existing versions of Git and with existing
repositories makes it necessary to enforce it the same way as other
rules such as "you cannot have double-dots in name, e.g. foo..bar"
or "no branches whose name begins with a dash", neither of which
comes from any filesystem issues. That a rule can be loosened with
one new backend does not at all mean it is a good idea to loosen it
"because we can" in the first place.
Post by Michael Haggerty
I think it would be good to try to merge the first part of this patch
series to lock in some progress while we continue iterating on the
remainder. I'm satisfied that it is all going in the right direction
and I am thankful to Ronnie for pushing it forward. But handling
48-patch series is very daunting and I would welcome a split.
I'm not sure whether patches 01-19 are necessarily the right split
between merge-now/iterate-more; it is more or less an accident that I
stopped after patch 19 on an earlier review. Maybe Ronnie could propose
a logical subset of the commits as being ready to be merged to next in
the nearish term?
Yeah, thanks for going through this, and I agree that we would be
better off merging the earlier part first.
Jeff King
2014-07-09 05:02:26 UTC
Permalink
Post by Junio C Hamano
I'd say that "if you have foo/bar you cannot have foo" may have
started as an implementation limitation, but the interoperability
requirement with existing versions of Git and with existing
repositories makes it necessary to enforce it the same way as other
rules such as "you cannot have double-dots in name, e.g. foo..bar"
or "no branches whose name begins with a dash", neither of which
comes from any filesystem issues. That a rule can be loosened with
one new backend does not at all mean it is a good idea to loosen it
"because we can" in the first place.
To me it makes sense to to have it as an option, for two reasons:

1. If you want to pay the compatibility cost (e.g., because you work
with a defined set of users on a small project and can dictate how
they set their git options), you get the extra feature.

2. It provides a migration path if we eventually want to move to a
default backend that allows it.

I admit that I don't care _too_ much, though. My main desire for it is
not to store two live branches that overlap, but to store reflogs for
deleted branches without conflicting with live branches.

And of course all of this is getting rather ahead of ourselves. We do
not have _any_ alternate backends yet, nor even yet the infrastructure
to make them.

-Peff
Ronnie Sahlberg
2014-07-14 16:16:38 UTC
Permalink
Post by Junio C Hamano
Post by Michael Haggerty
Patches 01-19 -- ACK mhagger
Patches 20-42 -- I sent various comments, small to large, concerning
these patches
Patch 43 -- Needs more justification if it is to be acceptable
Patch 44 -- Depends on 43
Patches 45-48 -- I didn't quite get to these, but...
Perhaps it would be more appropriate for the rules about reference name
conflicts to be enforced by the backend, since it is the limitations of
the current backend that impose the restrictions. Would that make sense?
On the other hand, removing the restrictions isn't simply a matter of
picking a different backend, because all Git repositories have to be
able to interact with each other.
I'd say that "if you have foo/bar you cannot have foo" may have
started as an implementation limitation, but the interoperability
requirement with existing versions of Git and with existing
repositories makes it necessary to enforce it the same way as other
rules such as "you cannot have double-dots in name, e.g. foo..bar"
or "no branches whose name begins with a dash", neither of which
comes from any filesystem issues. That a rule can be loosened with
one new backend does not at all mean it is a good idea to loosen it
"because we can" in the first place.
ACK.
Post by Junio C Hamano
Post by Michael Haggerty
I think it would be good to try to merge the first part of this patch
series to lock in some progress while we continue iterating on the
remainder. I'm satisfied that it is all going in the right direction
and I am thankful to Ronnie for pushing it forward. But handling
48-patch series is very daunting and I would welcome a split.
I'm not sure whether patches 01-19 are necessarily the right split
between merge-now/iterate-more; it is more or less an accident that I
stopped after patch 19 on an earlier review. Maybe Ronnie could propose
a logical subset of the commits as being ready to be merged to next in
the nearish term?
Yeah, thanks for going through this, and I agree that we would be
better off merging the earlier part first.
Ok, 01-19 is as good split as any.
If you merge just the 01-19 part of this series I will address
Michael's concerns and repost patches 20-48 as a separate series.

regards
ronnie sahlberg
Ronnie Sahlberg
2014-07-14 15:03:10 UTC
Permalink
Post by Michael Haggerty
Post by Ronnie Sahlberg
This patch series can also be found at
https://github.com/rsahlberg/git/tree/ref-transactions
This patch series is based on current master and expands on the transaction
API. It converts all ref updates, inside refs.c as well as external, to use the
transaction API for updates. This makes most of the ref updates to become
atomic when there are failures locking or writing to a ref.
This version completes the work to convert all ref updates to use transactions.
Now that all updates are through transactions I will start working on
cleaning up the reading of refs and to create an api for managing reflogs but
all that will go in a different patch series.
- Whitespace and style changes suggested by Jun.
I spent most of the day on reviewing this patch series,
Thanks!
Post by Michael Haggerty
but now I'm out
Patches 01-19 -- ACK mhagger
Patches 20-42 -- I sent various comments, small to large, concerning
these patches
Patch 43 -- Needs more justification if it is to be acceptable
Patch 44 -- Depends on 43
Patches 45-48 -- I didn't quite get to these, but...
Perhaps it would be more appropriate for the rules about reference name
conflicts to be enforced by the backend, since it is the limitations of
the current backend that impose the restrictions. Would that make sense?
On the other hand, removing the restrictions isn't simply a matter of
picking a different backend, because all Git repositories have to be
able to interact with each other.
So, I don't yet have a considered opinion on the matter.
I think for compatibility I would prefer to keep the same rules for
name conflicts as for the current files implementation.
But we could have a configuration option to disable these checks, with
the caveat that this might mean that some users will
no longer be able to access pull all the branches anymore.
Post by Michael Haggerty
I think it would be good to try to merge the first part of this patch
series to lock in some progress while we continue iterating on the
remainder. I'm satisfied that it is all going in the right direction
and I am thankful to Ronnie for pushing it forward. But handling
48-patch series is very daunting and I would welcome a split.
Will do.
Post by Michael Haggerty
I'm not sure whether patches 01-19 are necessarily the right split
between merge-now/iterate-more; it is more or less an accident that I
stopped after patch 19 on an earlier review. Maybe Ronnie could propose
a logical subset of the commits as being ready to be merged to next in
the nearish term?
Michael
--
Michael Haggerty
Loading...