aboutsummaryrefslogtreecommitdiff
path: root/update-ref.c
blob: 6919cead4beb2a8aae42c9fbac01e1093477b26d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#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;
}