diff options
author | Carlos Martín Nieto <cmn@dwim.me> | 2013-12-18 19:58:16 +0100 |
---|---|---|
committer | Carlos Martín Nieto <cmn@dwim.me> | 2014-02-05 12:07:56 +0100 |
commit | 9b148098e6802f9dd797471fc4f20cfc58a846b4 (patch) | |
tree | 2a31b9c1ab22c45a0f3c6f0783f28f7a24aa586a | |
parent | 7369ea8075fa112bbd4c13f06eeedeb2a4e47153 (diff) | |
download | libgit2-9b148098e6802f9dd797471fc4f20cfc58a846b4.tar.gz |
refs: conditional ref updates
Allow updating references if the old value matches the given one.
-rw-r--r-- | include/git2/refs.h | 49 | ||||
-rw-r--r-- | include/git2/sys/refdb_backend.h | 3 | ||||
-rw-r--r-- | src/refdb.c | 4 | ||||
-rw-r--r-- | src/refdb.h | 2 | ||||
-rw-r--r-- | src/refdb_fs.c | 41 | ||||
-rw-r--r-- | src/refs.c | 47 |
6 files changed, 120 insertions, 26 deletions
diff --git a/include/git2/refs.h b/include/git2/refs.h index f9aaea827..296fcb67d 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -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); /** + * 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. * * 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( 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 * this will fail. * * 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 ref The reference * @param id The new target OID for the reference diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 5bbd4ba4c..e43f9960b 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -94,7 +94,8 @@ struct git_refdb_backend { */ int (*write)(git_refdb_backend *backend, 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)( git_reference **out, git_refdb_backend *backend, diff --git a/src/refdb.c b/src/refdb.c index 411423d57..9ff812433 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -167,14 +167,14 @@ void git_refdb_iterator_free(git_reference_iterator *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); GIT_REFCOUNT_INC(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( diff --git a/src/refdb.h b/src/refdb.h index 91eecb782..86a1f8971 100644 --- a/src/refdb.h +++ b/src/refdb.h @@ -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); 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_reflog_read(git_reflog **out, git_refdb *db, const char *name); diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 89c77c14c..fd6d61e1d 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -690,8 +690,11 @@ static int reference_path_available( static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const git_reference *ref) { + int error; git_buf ref_path = GIT_BUF_INIT; + assert(file && backend && ref); + /* Remove a possibly existing empty directory hierarchy * 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 if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) return -1; - if (git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { - git_buf_free(&ref_path); - return -1; - } + error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE); git_buf_free(&ref_path); - return 0; + return error; } static int loose_commit(git_filebuf *file, const git_reference *ref) { + assert(file && ref); + if (ref->type == GIT_REF_OID) { char oid[GIT_OID_HEXSZ + 1]; git_oid_nfmt(oid, sizeof(oid), &ref->target.oid); @@ -933,10 +935,12 @@ static int refdb_fs_backend__write( const git_reference *ref, int force, const git_signature *who, - const char *message) + const char *message, + const git_oid *old_id) { refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_filebuf file = GIT_FILEBUF_INIT; + git_reference *old_ref; int error; assert(backend); @@ -945,10 +949,28 @@ static int refdb_fs_backend__write( if (error < 0) 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) 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) && (error = reflog_append(backend, ref, who, message)) < 0) { git_filebuf_cleanup(&file); @@ -956,6 +978,11 @@ static int refdb_fs_backend__write( } return loose_commit(&file, ref); + +on_error: + git_filebuf_cleanup(&file); + git_reference_free(old_ref); + return -1; } static int refdb_fs_backend__delete( diff --git a/src/refs.c b/src/refs.c index ca5f24ea2..a5492cf91 100644 --- a/src/refs.c +++ b/src/refs.c @@ -331,7 +331,8 @@ static int reference__create( const char *symbolic, int force, const git_signature *signature, - const char *log_message) + const char *log_message, + const git_oid *old_id) { char normalized[GIT_REFNAME_MAX]; git_refdb *refdb; @@ -380,7 +381,7 @@ static int reference__create( 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); return error; } @@ -410,15 +411,28 @@ int git_reference_create( git_reference **ref_out, git_repository *repo, const char *name, - const git_oid *oid, + const git_oid *id, int force, const git_signature *signature, 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; git_signature *who = NULL; - assert(oid); + assert(id); if (!signature) { if ((error = log_signature(&who, repo)) < 0) @@ -428,7 +442,7 @@ int git_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); return error; @@ -456,7 +470,7 @@ int git_reference_symbolic_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); return error; @@ -471,22 +485,25 @@ static int ensure_is_an_updatable_direct_reference(git_reference *ref) return -1; } -int git_reference_set_target( +int git_reference_set_target_if( git_reference **out, git_reference *ref, const git_oid *id, const git_signature *signature, - const char *log_message) + const char *log_message, + const git_oid *old_id) { int error; + git_repository *repo; assert(out && ref && id); + repo = ref->db->repo; + if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0) return error; - return git_reference_create( - out, ref->db->repo, ref->name, id, 1, signature, log_message); + return git_reference_create_if(out, repo, ref->name, id, 1, signature, log_message, old_id); } 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; } +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( git_reference **out, git_reference *ref, |