Commit 9b148098 by Carlos Martín Nieto

refs: conditional ref updates

Allow updating references if the old value matches the given one.
parent 7369ea80
...@@ -144,6 +144,49 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor ...@@ -144,6 +144,49 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor
GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_signature *signature, const char *log_message); GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_signature *signature, const char *log_message);
/** /**
* Conditionally create new direct reference
*
* A direct reference (also called an object id reference) refers directly
* to a specific object id (a.k.a. OID or SHA) in the repository. The id
* permanently refers to the object (although the reference itself can be
* moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0"
* refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977.
*
* The direct reference will be created in the repository and written to
* the disk. The generated reference object must be freed by the user.
*
* Valid reference names must follow one of two patterns:
*
* 1. Top-level names must contain only capital letters and underscores,
* and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
* 2. Names prefixed with "refs/" can be almost anything. You must avoid
* the characters '~', '^', ':', '\\', '?', '[', and '*', and the
* sequences ".." and "@{" which have special meaning to revparse.
*
* This function will return an error if a reference already exists with the
* given name unless `force` is true, in which case it will be overwritten.
*
* The signature and message for the reflog will be ignored if the
* reference does not belong in the standard set (HEAD, branches and
* remote-tracking branches) and and it does not have a reflog.
*
* It will also return an error if the reference's value at the time
* of updating does not match the one passed.
*
* @param out Pointer to the newly created reference
* @param repo Repository where that reference will live
* @param name The name of the reference
* @param id The object id pointed to by the reference.
* @param force Overwrite existing references
* @param force Overwrite existing references
* @param signature The identity that will used to populate the reflog entry
* @param log_message The one line long message to be appended to the reflog
* @param old_id The old value which the reference should have
* @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
*/
GIT_EXTERN(int) git_reference_create_if(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_signature *signature, const char *log_message, const git_oid *old_id);
/**
* Get the OID pointed to by a direct reference. * Get the OID pointed to by a direct reference.
* *
* Only available if the reference is direct (i.e. an object id reference, * Only available if the reference is direct (i.e. an object id reference,
...@@ -254,16 +297,12 @@ GIT_EXTERN(int) git_reference_symbolic_set_target( ...@@ -254,16 +297,12 @@ GIT_EXTERN(int) git_reference_symbolic_set_target(
const char *log_message); const char *log_message);
/** /**
* Create a new reference with the same name as the given reference but a * Conditionally create a new reference with the same name as the given reference but a
* different OID target. The reference must be a direct reference, otherwise * different OID target. The reference must be a direct reference, otherwise
* this will fail. * this will fail.
* *
* The new reference will be written to disk, overwriting the given reference. * The new reference will be written to disk, overwriting the given reference.
* *
* The signature and message for the reflog will be ignored if the
* reference does not belong in the standard set (HEAD, branches and
* remote-tracking branches) and and it does not have a reflog.
*
* @param out Pointer to the newly created reference * @param out Pointer to the newly created reference
* @param ref The reference * @param ref The reference
* @param id The new target OID for the reference * @param id The new target OID for the reference
......
...@@ -94,7 +94,8 @@ struct git_refdb_backend { ...@@ -94,7 +94,8 @@ struct git_refdb_backend {
*/ */
int (*write)(git_refdb_backend *backend, int (*write)(git_refdb_backend *backend,
const git_reference *ref, int force, const git_reference *ref, int force,
const git_signature *who, const char *message); const git_signature *who, const char *message,
const git_oid *old);
int (*rename)( int (*rename)(
git_reference **out, git_refdb_backend *backend, git_reference **out, git_refdb_backend *backend,
......
...@@ -167,14 +167,14 @@ void git_refdb_iterator_free(git_reference_iterator *iter) ...@@ -167,14 +167,14 @@ void git_refdb_iterator_free(git_reference_iterator *iter)
iter->free(iter); iter->free(iter);
} }
int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message) int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old)
{ {
assert(db && db->backend); assert(db && db->backend);
GIT_REFCOUNT_INC(db); GIT_REFCOUNT_INC(db);
ref->db = db; ref->db = db;
return db->backend->write(db->backend, ref, force, who, message); return db->backend->write(db->backend, ref, force, who, message, old);
} }
int git_refdb_rename( int git_refdb_rename(
......
...@@ -42,7 +42,7 @@ int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter); ...@@ -42,7 +42,7 @@ int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);
int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter); int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);
void git_refdb_iterator_free(git_reference_iterator *iter); void git_refdb_iterator_free(git_reference_iterator *iter);
int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message); int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old);
int git_refdb_delete(git_refdb *refdb, const char *ref_name); int git_refdb_delete(git_refdb *refdb, const char *ref_name);
int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name); int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name);
......
...@@ -690,8 +690,11 @@ static int reference_path_available( ...@@ -690,8 +690,11 @@ static int reference_path_available(
static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_reference *ref) static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_reference *ref)
{ {
int error;
git_buf ref_path = GIT_BUF_INIT; git_buf ref_path = GIT_BUF_INIT;
assert(file && backend && ref);
/* Remove a possibly existing empty directory hierarchy /* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name * which name would collide with the reference name
*/ */
...@@ -701,17 +704,16 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_re ...@@ -701,17 +704,16 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_re
if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)
return -1; return -1;
if (git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
git_buf_free(&ref_path);
return -1;
}
git_buf_free(&ref_path); git_buf_free(&ref_path);
return 0; return error;
} }
static int loose_commit(git_filebuf *file, const git_reference *ref) static int loose_commit(git_filebuf *file, const git_reference *ref)
{ {
assert(file && ref);
if (ref->type == GIT_REF_OID) { if (ref->type == GIT_REF_OID) {
char oid[GIT_OID_HEXSZ + 1]; char oid[GIT_OID_HEXSZ + 1];
git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
...@@ -933,10 +935,12 @@ static int refdb_fs_backend__write( ...@@ -933,10 +935,12 @@ static int refdb_fs_backend__write(
const git_reference *ref, const git_reference *ref,
int force, int force,
const git_signature *who, const git_signature *who,
const char *message) const char *message,
const git_oid *old_id)
{ {
refdb_fs_backend *backend = (refdb_fs_backend *)_backend; refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
git_filebuf file = GIT_FILEBUF_INIT; git_filebuf file = GIT_FILEBUF_INIT;
git_reference *old_ref;
int error; int error;
assert(backend); assert(backend);
...@@ -945,10 +949,28 @@ static int refdb_fs_backend__write( ...@@ -945,10 +949,28 @@ static int refdb_fs_backend__write(
if (error < 0) if (error < 0)
return error; return error;
/* We need to perform the reflog append under the ref's lock */ /* We need to perform the reflog append and old value check under the ref's lock */
if ((error = loose_lock(&file, backend, ref)) < 0) if ((error = loose_lock(&file, backend, ref)) < 0)
return error; return error;
if (old_id) {
if ((error = refdb_fs_backend__lookup(&old_ref, _backend, ref->name)) < 0) {
git_filebuf_cleanup(&file);
return error;
}
if (old_ref->type == GIT_REF_SYMBOLIC) {
giterr_set(GITERR_REFERENCE, "cannot compare id to symbolic reference target");
goto on_error;
}
/* Finally we can compare the ids */
if (git_oid_cmp(old_id, &old_ref->target.oid)) {
giterr_set(GITERR_REFERENCE, "old reference value does not match");
goto on_error;
}
}
if (should_write_reflog(backend->repo, ref->name) && if (should_write_reflog(backend->repo, ref->name) &&
(error = reflog_append(backend, ref, who, message)) < 0) { (error = reflog_append(backend, ref, who, message)) < 0) {
git_filebuf_cleanup(&file); git_filebuf_cleanup(&file);
...@@ -956,6 +978,11 @@ static int refdb_fs_backend__write( ...@@ -956,6 +978,11 @@ static int refdb_fs_backend__write(
} }
return loose_commit(&file, ref); return loose_commit(&file, ref);
on_error:
git_filebuf_cleanup(&file);
git_reference_free(old_ref);
return -1;
} }
static int refdb_fs_backend__delete( static int refdb_fs_backend__delete(
......
...@@ -331,7 +331,8 @@ static int reference__create( ...@@ -331,7 +331,8 @@ static int reference__create(
const char *symbolic, const char *symbolic,
int force, int force,
const git_signature *signature, const git_signature *signature,
const char *log_message) const char *log_message,
const git_oid *old_id)
{ {
char normalized[GIT_REFNAME_MAX]; char normalized[GIT_REFNAME_MAX];
git_refdb *refdb; git_refdb *refdb;
...@@ -380,7 +381,7 @@ static int reference__create( ...@@ -380,7 +381,7 @@ static int reference__create(
GITERR_CHECK_ALLOC(ref); GITERR_CHECK_ALLOC(ref);
if ((error = git_refdb_write(refdb, ref, force, signature, log_message)) < 0) { if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id)) < 0) {
git_reference_free(ref); git_reference_free(ref);
return error; return error;
} }
...@@ -410,15 +411,28 @@ int git_reference_create( ...@@ -410,15 +411,28 @@ int git_reference_create(
git_reference **ref_out, git_reference **ref_out,
git_repository *repo, git_repository *repo,
const char *name, const char *name,
const git_oid *oid, const git_oid *id,
int force, int force,
const git_signature *signature, const git_signature *signature,
const char *log_message) const char *log_message)
{ {
return git_reference_create_if(ref_out, repo, name, id, force, signature, log_message, NULL);
}
int git_reference_create_if(
git_reference **ref_out,
git_repository *repo,
const char *name,
const git_oid *id,
int force,
const git_signature *signature,
const char *log_message,
const git_oid *old_id)
{
int error; int error;
git_signature *who = NULL; git_signature *who = NULL;
assert(oid); assert(id);
if (!signature) { if (!signature) {
if ((error = log_signature(&who, repo)) < 0) if ((error = log_signature(&who, repo)) < 0)
...@@ -428,7 +442,7 @@ int git_reference_create( ...@@ -428,7 +442,7 @@ int git_reference_create(
} }
error = reference__create( error = reference__create(
ref_out, repo, name, oid, NULL, force, signature, log_message); ref_out, repo, name, id, NULL, force, signature, log_message, old_id);
git_signature_free(who); git_signature_free(who);
return error; return error;
...@@ -456,7 +470,7 @@ int git_reference_symbolic_create( ...@@ -456,7 +470,7 @@ int git_reference_symbolic_create(
} }
error = reference__create( error = reference__create(
ref_out, repo, name, NULL, target, force, signature, log_message); ref_out, repo, name, NULL, target, force, signature, log_message, NULL);
git_signature_free(who); git_signature_free(who);
return error; return error;
...@@ -471,22 +485,25 @@ static int ensure_is_an_updatable_direct_reference(git_reference *ref) ...@@ -471,22 +485,25 @@ static int ensure_is_an_updatable_direct_reference(git_reference *ref)
return -1; return -1;
} }
int git_reference_set_target( int git_reference_set_target_if(
git_reference **out, git_reference **out,
git_reference *ref, git_reference *ref,
const git_oid *id, const git_oid *id,
const git_signature *signature, const git_signature *signature,
const char *log_message) const char *log_message,
const git_oid *old_id)
{ {
int error; int error;
git_repository *repo;
assert(out && ref && id); assert(out && ref && id);
repo = ref->db->repo;
if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
return error; return error;
return git_reference_create( return git_reference_create_if(out, repo, ref->name, id, 1, signature, log_message, old_id);
out, ref->db->repo, ref->name, id, 1, signature, log_message);
} }
static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
...@@ -498,6 +515,16 @@ static int ensure_is_an_updatable_symbolic_reference(git_reference *ref) ...@@ -498,6 +515,16 @@ static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
return -1; return -1;
} }
int git_reference_set_target(
git_reference **out,
git_reference *ref,
const git_oid *id,
const git_signature *signature,
const char *log_message)
{
return git_reference_set_target_if(out, ref, id, signature, log_message, NULL);
}
int git_reference_symbolic_set_target( int git_reference_symbolic_set_target(
git_reference **out, git_reference **out,
git_reference *ref, git_reference *ref,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment