]> git.proxmox.com Git - libgit2.git/commitdiff
refs: conditional ref updates
authorCarlos Martín Nieto <cmn@dwim.me>
Wed, 18 Dec 2013 18:58:16 +0000 (19:58 +0100)
committerCarlos Martín Nieto <cmn@dwim.me>
Wed, 5 Feb 2014 11:07:56 +0000 (12:07 +0100)
Allow updating references if the old value matches the given one.

include/git2/refs.h
include/git2/sys/refdb_backend.h
src/refdb.c
src/refdb.h
src/refdb_fs.c
src/refs.c

index f9aaea82707c0c1b4c574d662a0b7eda78abfa90..296fcb67d9320f930b13693dacc21deb04f45e13 100644 (file)
@@ -143,6 +143,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.
  *
@@ -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
index 5bbd4ba4c021614ac214b0b7372541f98c32cf15..e43f9960b3358acd030d4bdeff399b7ee32fc7f5 100644 (file)
@@ -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,
index 411423d57ad3205c19abeadb19941cd9c47fc5cf..9ff8124337ae53bccdc74b7fb4426614e0ec6321 100644 (file)
@@ -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(
index 91eecb782dc33de4393b2324b0637c13dd76aa0b..86a1f8971c83913144a12b55389e5aa4c0b14cc9 100644 (file)
@@ -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);
index 89c77c14c15155918a5b37242b37706521c557d3..fd6d61e1dd743253713c7699ee3b9ca4dfe4690b 100644 (file)
@@ -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(
index ca5f24ea27f69f6961586684276834e2c4b1b66b..a5492cf91a19606d49e773030f75417ba2bbfbec 100644 (file)
@@ -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,