update-ref.con commit Teach update-ref about a symbolic ref stored in a textfile. (9b143c6)
   1#include "cache.h"
   2#include "refs.h"
   3#include <ctype.h>
   4
   5static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
   6
   7#define MAXDEPTH 5
   8
   9static const char *resolve_ref(const char *path, unsigned char *sha1)
  10{
  11        int depth = MAXDEPTH, len;
  12        char buffer[256];
  13
  14        for (;;) {
  15                struct stat st;
  16                char *buf;
  17                int fd;
  18
  19                if (--depth < 0)
  20                        return NULL;
  21
  22                /* Special case: non-existing file */
  23                if (lstat(path, &st) < 0) {
  24                        if (errno != ENOENT)
  25                                return NULL;
  26                        memset(sha1, 0, 20);
  27                        return path;
  28                }
  29
  30                /* Follow "normalized" - ie "refs/.." symlinks by hand */
  31                if (S_ISLNK(st.st_mode)) {
  32                        len = readlink(path, buffer, sizeof(buffer)-1);
  33                        if (len >= 5 && !memcmp("refs/", buffer, 5)) {
  34                                path = git_path("%.*s", len, buffer);
  35                                continue;
  36                        }
  37                }
  38
  39                /*
  40                 * Anything else, just open it and try to use it as
  41                 * a ref
  42                 */
  43                fd = open(path, O_RDONLY);
  44                if (fd < 0)
  45                        return NULL;
  46                len = read(fd, buffer, sizeof(buffer)-1);
  47                close(fd);
  48
  49                /*
  50                 * Is it a symbolic ref?
  51                 */
  52                if (len < 4 || memcmp("ref:", buffer, 4))
  53                        break;
  54                buf = buffer + 4;
  55                len -= 4;
  56                while (len && isspace(*buf))
  57                        buf++, len--;
  58                while (len && isspace(buf[len-1]))
  59                        buf[--len] = 0;
  60                path = git_path("%.*s", len, buf);
  61        }
  62        if (len < 40 || get_sha1_hex(buffer, sha1))
  63                return NULL;
  64        return path;
  65}
  66
  67static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
  68{
  69        char buf[40];
  70        int fd = open(path, O_RDONLY), nr;
  71        if (fd < 0)
  72                return -1;
  73        nr = read(fd, buf, 40);
  74        close(fd);
  75        if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
  76                return -1;
  77        return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
  78}
  79
  80int main(int argc, char **argv)
  81{
  82        char *hex;
  83        const char *refname, *value, *oldval, *path, *lockpath;
  84        unsigned char sha1[20], oldsha1[20], currsha1[20];
  85        int fd, written;
  86
  87        setup_git_directory();
  88        if (argc < 3 || argc > 4)
  89                usage(git_update_ref_usage);
  90
  91        refname = argv[1];
  92        value = argv[2];
  93        oldval = argv[3];
  94        if (get_sha1(value, sha1) < 0)
  95                die("%s: not a valid SHA1", value);
  96        memset(oldsha1, 0, 20);
  97        if (oldval && get_sha1(oldval, oldsha1) < 0)
  98                die("%s: not a valid old SHA1", oldval);
  99
 100        path = resolve_ref(git_path("%s", refname), currsha1);
 101        if (!path)
 102                die("No such ref: %s", refname);
 103
 104        if (oldval) {
 105                if (memcmp(currsha1, oldsha1, 20))
 106                        die("Ref %s changed to %s", refname, sha1_to_hex(currsha1));
 107                /* Nothing to do? */
 108                if (!memcmp(oldsha1, sha1, 20))
 109                        exit(0);
 110        }
 111        path = strdup(path);
 112        lockpath = mkpath("%s.lock", path);
 113
 114        fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
 115        if (fd < 0)
 116                die("Unable to create %s", lockpath);
 117        hex = sha1_to_hex(sha1);
 118        hex[40] = '\n';
 119        written = write(fd, hex, 41);
 120        close(fd);
 121        if (written != 41) {
 122                unlink(lockpath);
 123                die("Unable to write to %s", lockpath);
 124        }
 125
 126        /*
 127         * Re-read the ref after getting the lock to verify
 128         */
 129        if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
 130                unlink(lockpath);
 131                die("Ref lock failed");
 132        }
 133
 134        /*
 135         * Finally, replace the old ref with the new one
 136         */
 137        if (rename(lockpath, path) < 0) {
 138                unlink(lockpath);
 139                die("Unable to create %s", path);
 140        }
 141        return 0;
 142}