update-ref: -d flag and ref creation safety.
authorJunio C Hamano <junkio@cox.net>
Wed, 27 Sep 2006 08:58:57 +0000 (01:58 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 27 Sep 2006 09:01:42 +0000 (02:01 -0700)
This adds -d flag to update-ref to allow safe deletion of ref.
Before deleting it, the command checks if the given <oldvalue>
still matches the value the caller thought the ref contained.

Similarly, it also accepts 0{40} or an empty string as <oldvalue>
to allow safe creation of a new ref.

Signed-off-by: Junio C Hamano <junkio@cox.net>
Documentation/git-update-ref.txt
builtin-update-ref.c
cache.h
refs.c

index e062030e91ae2b2a25c2ef7bafc4c026015e1f5e..71bcb7954f0c53a995ba56de6c99d9aebc6aa52a 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
 -----------
@@ -20,7 +20,9 @@ possibly dereferencing the symbolic refs, after verifying that
 the current value of the <ref> matches <oldvalue>.
 E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
 updates the master branch head to <newvalue> only if its current
-value is <oldvalue>.
+value is <oldvalue>.  You can specify 40 "0" or an empty string
+as <oldvalue> to make sure that the ref you are creating does
+not exist.
 
 It also allows a "ref" file to be a symbolic pointer to another
 ref file by starting with the four-byte header sequence of
@@ -49,6 +51,10 @@ for reading but not for writing (so we'll never write through a
 ref symlink to some other tree, if you have copied a whole
 archive by creating a symlink tree).
 
+With `-d` flag, it deletes the named <ref> after verifying it
+still contains <oldvalue>.
+
+
 Logging Updates
 ---------------
 If config parameter "core.logAllRefUpdates" is true or the file
index ab528337aac66fb9fa93e86739a897ebffc364d5..b34e5987dd256e0b7d9fae46fe89f66dd18ad91f 100644 (file)
@@ -3,15 +3,16 @@
 #include "builtin.h"
 
 static const char git_update_ref_usage[] =
-"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
        const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
        struct ref_lock *lock;
        unsigned char sha1[20], oldsha1[20];
-       int i;
+       int i, delete;
 
+       delete = 0;
        setup_ident();
        git_config(git_default_config);
 
@@ -26,6 +27,10 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                                die("Refusing to perform update with \\n in message.");
                        continue;
                }
+               if (!strcmp("-d", argv[i])) {
+                       delete = 1;
+                       continue;
+               }
                if (!refname) {
                        refname = argv[i];
                        continue;
@@ -44,8 +49,15 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 
        if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
+
+       if (delete) {
+               if (oldval)
+                       usage(git_update_ref_usage);
+               return delete_ref(refname, sha1);
+       }
+
        hashclr(oldsha1);
-       if (oldval && get_sha1(oldval, oldsha1))
+       if (oldval && *oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
        lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
diff --git a/cache.h b/cache.h
index 6def1551624fffd90044b8154ee78683143e343e..6e004505be5091146302c75b12a4538c915092da 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -179,6 +179,7 @@ struct lock_file {
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
 extern int commit_lock_file(struct lock_file *);
 extern void rollback_lock_file(struct lock_file *);
+extern int delete_ref(const char *, unsigned char *sha1);
 
 /* Environment bits from configuration mechanism */
 extern int use_legacy_headers;
diff --git a/refs.c b/refs.c
index 9a1bc0db59078fe5dc9c7919e119abfc6f579fc3..3d4cdd1eb9bda5fe6260d8e7ddba99a4278c3252 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -378,6 +378,32 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
        return read_ref(mkpath("refs/%s", ref), sha1);
 }
 
+int delete_ref(const char *refname, unsigned char *sha1)
+{
+       struct ref_lock *lock;
+       int err, i, ret = 0;
+
+       lock = lock_any_ref_for_update(refname, sha1);
+       if (!lock)
+               return 1;
+       i = strlen(lock->lk->filename) - 5; /* .lock */
+       lock->lk->filename[i] = 0;
+       err = unlink(lock->lk->filename);
+       if (err) {
+               ret = 1;
+               error("unlink(%s) failed: %s",
+                     lock->lk->filename, strerror(errno));
+       }
+       lock->lk->filename[i] = '.';
+
+       err = unlink(lock->log_file);
+       if (err && errno != ENOENT)
+               fprintf(stderr, "warning: unlink(%s) failed: %s",
+                       lock->log_file, strerror(errno));
+
+       return ret;
+}
+
 /*
  * Make sure "ref" is something reasonable to have under ".git/refs/";
  * We do not like it if: