You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
3.0 KiB
142 lines
3.0 KiB
#include "cache.h" |
|
#include "refs.h" |
|
#include <ctype.h> |
|
|
|
static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]"; |
|
|
|
#define MAXDEPTH 5 |
|
|
|
static const char *resolve_ref(const char *path, unsigned char *sha1) |
|
{ |
|
int depth = MAXDEPTH, len; |
|
char buffer[256]; |
|
|
|
for (;;) { |
|
struct stat st; |
|
char *buf; |
|
int fd; |
|
|
|
if (--depth < 0) |
|
return NULL; |
|
|
|
/* Special case: non-existing file */ |
|
if (lstat(path, &st) < 0) { |
|
if (errno != ENOENT) |
|
return NULL; |
|
memset(sha1, 0, 20); |
|
return path; |
|
} |
|
|
|
/* Follow "normalized" - ie "refs/.." symlinks by hand */ |
|
if (S_ISLNK(st.st_mode)) { |
|
len = readlink(path, buffer, sizeof(buffer)-1); |
|
if (len >= 5 && !memcmp("refs/", buffer, 5)) { |
|
path = git_path("%.*s", len, buffer); |
|
continue; |
|
} |
|
} |
|
|
|
/* |
|
* Anything else, just open it and try to use it as |
|
* a ref |
|
*/ |
|
fd = open(path, O_RDONLY); |
|
if (fd < 0) |
|
return NULL; |
|
len = read(fd, buffer, sizeof(buffer)-1); |
|
close(fd); |
|
|
|
/* |
|
* Is it a symbolic ref? |
|
*/ |
|
if (len < 4 || memcmp("ref:", buffer, 4)) |
|
break; |
|
buf = buffer + 4; |
|
len -= 4; |
|
while (len && isspace(*buf)) |
|
buf++, len--; |
|
while (len && isspace(buf[len-1])) |
|
buf[--len] = 0; |
|
path = git_path("%.*s", len, buf); |
|
} |
|
if (len < 40 || get_sha1_hex(buffer, sha1)) |
|
return NULL; |
|
return path; |
|
} |
|
|
|
static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1) |
|
{ |
|
char buf[40]; |
|
int fd = open(path, O_RDONLY), nr; |
|
if (fd < 0) |
|
return -1; |
|
nr = read(fd, buf, 40); |
|
close(fd); |
|
if (nr != 40 || get_sha1_hex(buf, currsha1) < 0) |
|
return -1; |
|
return memcmp(oldsha1, currsha1, 20) ? -1 : 0; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
char *hex; |
|
const char *refname, *value, *oldval, *path, *lockpath; |
|
unsigned char sha1[20], oldsha1[20], currsha1[20]; |
|
int fd, written; |
|
|
|
setup_git_directory(); |
|
if (argc < 3 || argc > 4) |
|
usage(git_update_ref_usage); |
|
|
|
refname = argv[1]; |
|
value = argv[2]; |
|
oldval = argv[3]; |
|
if (get_sha1(value, sha1) < 0) |
|
die("%s: not a valid SHA1", value); |
|
memset(oldsha1, 0, 20); |
|
if (oldval && get_sha1(oldval, oldsha1) < 0) |
|
die("%s: not a valid old SHA1", oldval); |
|
|
|
path = resolve_ref(git_path("%s", refname), currsha1); |
|
if (!path) |
|
die("No such ref: %s", refname); |
|
|
|
if (oldval) { |
|
if (memcmp(currsha1, oldsha1, 20)) |
|
die("Ref %s changed to %s", refname, sha1_to_hex(currsha1)); |
|
/* Nothing to do? */ |
|
if (!memcmp(oldsha1, sha1, 20)) |
|
exit(0); |
|
} |
|
path = strdup(path); |
|
lockpath = mkpath("%s.lock", path); |
|
|
|
fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); |
|
if (fd < 0) |
|
die("Unable to create %s", lockpath); |
|
hex = sha1_to_hex(sha1); |
|
hex[40] = '\n'; |
|
written = write(fd, hex, 41); |
|
close(fd); |
|
if (written != 41) { |
|
unlink(lockpath); |
|
die("Unable to write to %s", lockpath); |
|
} |
|
|
|
/* |
|
* Re-read the ref after getting the lock to verify |
|
*/ |
|
if (oldval && re_verify(path, oldsha1, currsha1) < 0) { |
|
unlink(lockpath); |
|
die("Ref lock failed"); |
|
} |
|
|
|
/* |
|
* Finally, replace the old ref with the new one |
|
*/ |
|
if (rename(lockpath, path) < 0) { |
|
unlink(lockpath); |
|
die("Unable to create %s", path); |
|
} |
|
return 0; |
|
}
|
|
|