receive-pack.c 6.3 KB
Newer Older
1
#include "cache.h"
2
#include "pkt-line.h"
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
#include <sys/wait.h>

static const char receive_pack_usage[] = "git-receive-pack [--unpack=executable] <git-dir> [heads]";

static const char *unpacker = "git-unpack-objects";

static int path_match(const char *path, int nr, char **match)
{
	int i;
	int pathlen = strlen(path);

	for (i = 0; i < nr; i++) {
		char *s = match[i];
		int len = strlen(s);

		if (!len || len > pathlen)
			continue;
		if (memcmp(path + pathlen - len, s, len))
			continue;
		if (pathlen > len && path[pathlen - len - 1] != '/')
			continue;
		*s = 0;
		return 1;
	}
	return 0;
}

static void show_ref(const char *path, unsigned char *sha1)
{
32
	packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
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
}

static int read_ref(const char *path, unsigned char *sha1)
{
	int ret = -1;
	int fd = open(path, O_RDONLY);

	if (fd >= 0) {
		char buffer[60];
		if (read(fd, buffer, sizeof(buffer)) >= 40)
			ret = get_sha1_hex(buffer, sha1);
		close(fd);
	}
	return ret;
}

static void write_head_info(const char *base, int nr, char **match)
{
	DIR *dir = opendir(base);

	if (dir) {
		struct dirent *de;
		int baselen = strlen(base);
		char *path = xmalloc(baselen + 257);
		memcpy(path, base, baselen);

		while ((de = readdir(dir)) != NULL) {
			char sha1[20];
			struct stat st;
			int namelen;

			if (de->d_name[0] == '.')
				continue;
			namelen = strlen(de->d_name);
			if (namelen > 255)
				continue;
			memcpy(path + baselen, de->d_name, namelen+1);
			if (lstat(path, &st) < 0)
				continue;
			if (S_ISDIR(st.st_mode)) {
				path[baselen + namelen] = '/';
				path[baselen + namelen + 1] = 0;
				write_head_info(path, nr, match);
				continue;
			}
			if (read_ref(path, sha1) < 0)
				continue;
80 81
			if (!has_sha1_file(sha1))
				continue;
82 83 84 85 86 87 88 89 90
			if (nr && !path_match(path, nr, match))
				continue;
			show_ref(path, sha1);
		}
		free(path);
		closedir(dir);
	}
}

91 92 93 94 95
struct command {
	struct command *next;
	unsigned char old_sha1[20];
	unsigned char new_sha1[20];
	char ref_name[0];
96 97
};

98
struct command *commands = NULL;
99

100 101 102 103 104 105 106 107 108
static int is_all_zeroes(const char *hex)
{
	int i;
	for (i = 0; i < 40; i++)
		if (*hex++ != '0')
			return 0;
	return 1;
}

109 110 111 112 113
static int verify_old_ref(const char *name, char *hex_contents)
{
	int fd, ret;
	char buffer[60];

114 115
	if (is_all_zeroes(hex_contents))
		return 0;
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 143 144 145
	fd = open(name, O_RDONLY);
	if (fd < 0)
		return -1;
	ret = read(fd, buffer, 40);
	close(fd);
	if (ret != 40)
		return -1;
	if (memcmp(buffer, hex_contents, 40))
		return -1;
	return 0;
}

static void update(const char *name, unsigned char *old_sha1, unsigned char *new_sha1)
{
	char new_hex[60], *old_hex, *lock_name;
	int newfd, namelen, written;

	namelen = strlen(name);
	lock_name = xmalloc(namelen + 10);
	memcpy(lock_name, name, namelen);
	memcpy(lock_name + namelen, ".lock", 6);

	strcpy(new_hex, sha1_to_hex(new_sha1));
	old_hex = sha1_to_hex(old_sha1);
	if (!has_sha1_file(new_sha1))
		die("unpack should have generated %s, but I can't find it!", new_hex);

	newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0644);
	if (newfd < 0)
		die("unable to create %s (%s)", lock_name, strerror(errno));
146 147 148 149

	/* Write the ref with an ending '\n' */
	new_hex[40] = '\n';
	new_hex[41] = 0;
150
	written = write(newfd, new_hex, 41);
151 152 153
	/* Remove the '\n' again */
	new_hex[40] = 0;

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
	close(newfd);
	if (written != 41) {
		unlink(lock_name);
		die("unable to write %s", lock_name);
	}
	if (verify_old_ref(name, old_hex) < 0) {
		unlink(lock_name);
		die("%s changed during push", name);
	}
	if (rename(lock_name, name) < 0) {
		unlink(lock_name);
		die("unable to replace %s", name);
	}
	fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
}


171 172 173 174 175 176
/*
 * This gets called after(if) we've successfully
 * unpacked the data payload.
 */
static void execute_commands(void)
{
177 178 179
	struct command *cmd = commands;

	while (cmd) {
180
		update(cmd->ref_name, cmd->old_sha1, cmd->new_sha1);
181
		cmd = cmd->next;
182 183 184 185 186
	}
}

static void read_head_info(void)
{
187
	struct command **p = &commands;
188 189
	for (;;) {
		static char line[1000];
190 191 192 193 194
		unsigned char old_sha1[20], new_sha1[20];
		struct command *cmd;
		int len;

		len = packet_read_line(0, line, sizeof(line));
195 196
		if (!len)
			break;
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
		if (line[len-1] == '\n')
			line[--len] = 0;
		if (len < 83 ||
		    line[40] != ' ' ||
		    line[81] != ' ' ||
		    get_sha1_hex(line, old_sha1) ||
		    get_sha1_hex(line + 41, new_sha1))
			die("protocol error: expected old/new/ref, got '%s'", line);
		cmd = xmalloc(sizeof(struct command) + len - 80);
		memcpy(cmd->old_sha1, old_sha1, 20);
		memcpy(cmd->new_sha1, new_sha1, 20);
		memcpy(cmd->ref_name, line + 82, len - 81);
		cmd->next = NULL;
		*p = cmd;
		p = &cmd->next;
212 213 214 215 216 217 218 219 220 221
	}
}

static void unpack(void)
{
	pid_t pid = fork();

	if (pid < 0)
		die("unpack fork failed");
	if (!pid) {
222
		execlp(unpacker, unpacker, NULL);
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
		die("unpack execute failed");
	}

	for (;;) {
		int status, code;
		int retval = waitpid(pid, &status, 0);

		if (retval < 0) {
			if (errno == EINTR)
				continue;
			die("waitpid failed (%s)", strerror(retval));
		}
		if (retval != pid)
			die("waitpid is confused");
		if (WIFSIGNALED(status))
			die("%s died of signal %d", unpacker, WTERMSIG(status));
		if (!WIFEXITED(status))
			die("%s died out of really strange complications", unpacker);
		code = WEXITSTATUS(status);
		if (code)
			die("%s exited with error code %d", unpacker, code);
		return;
	}
}

int main(int argc, char **argv)
{
	int i, nr_heads = 0;
	const char *dir = NULL;
	char **heads = NULL;

	argv++;
	for (i = 1; i < argc; i++) {
		const char *arg = *argv++;

		if (*arg == '-') {
			if (!strncmp(arg, "--unpack=", 9)) {
				unpacker = arg+9;
				continue;
			}
			/* Do flag handling here */
			usage(receive_pack_usage);
		}
		dir = arg;
		heads = argv;
		nr_heads = argc - i - 1;
		break;
	}
	if (!dir)
		usage(receive_pack_usage);

	/* chdir to the directory. If that fails, try appending ".git" */
	if (chdir(dir) < 0) {
		static char path[PATH_MAX];
		snprintf(path, sizeof(path), "%s.git", dir);
		if (chdir(path) < 0)
			die("unable to cd to %s", dir);
	}

	/* If we have a ".git" directory, chdir to it */
	chdir(".git");
284
	setenv("GIT_DIR", ".", 1);
285 286 287 288 289 290

	if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0)
		die("%s doesn't appear to be a git directory", dir);
	write_head_info("refs/", nr_heads, heads);

	/* EOF */
291
	packet_flush(1);
292 293

	read_head_info();
294 295 296 297
	if (commands) {
		unpack();
		execute_commands();
	}
298 299
	return 0;
}