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