git/send-pack.c
Linus Torvalds 2a9c3fe838 git-send-pack: verify that sender is a proper superset of receiver
This should make sure that if you have multiple people pushing to the
same tree, they cannot overwrite each others work, but have to merge
before being able to update the common reference tree.
2005-07-19 07:03:47 -04:00

292 lines
5.9 KiB
C

#include "cache.h"
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
static const char send_pack_usage[] =
"git-send-pack [--exec=git-receive-pack] [host:]directory [heads]*";
static const char *exec = "git-receive-pack";
static int send_all = 0;
static int force_update = 0;
static int is_zero_sha1(const unsigned char *sha1)
{
int i;
for (i = 0; i < 20; i++) {
if (*sha1++)
return 0;
}
return 1;
}
static void exec_pack_objects(void)
{
static char *args[] = {
"git-pack-objects",
"--stdout",
NULL
};
execvp("git-pack-objects", args);
die("git-pack-objects exec failed (%s)", strerror(errno));
}
static void exec_rev_list(struct ref *refs)
{
static char *args[1000];
int i = 0;
args[i++] = "git-rev-list"; /* 0 */
args[i++] = "--objects"; /* 1 */
while (refs) {
char *buf = malloc(100);
if (i > 900)
die("git-rev-list environment overflow");
if (!is_zero_sha1(refs->old_sha1)) {
args[i++] = buf;
snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
buf += 50;
}
if (!is_zero_sha1(refs->new_sha1)) {
args[i++] = buf;
snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
}
refs = refs->next;
}
args[i] = NULL;
execvp("git-rev-list", args);
die("git-rev-list exec failed (%s)", strerror(errno));
}
static void rev_list(int fd, struct ref *refs)
{
int pipe_fd[2];
pid_t pack_objects_pid;
if (pipe(pipe_fd) < 0)
die("rev-list setup: pipe failed");
pack_objects_pid = fork();
if (!pack_objects_pid) {
dup2(pipe_fd[0], 0);
dup2(fd, 1);
close(pipe_fd[0]);
close(pipe_fd[1]);
close(fd);
exec_pack_objects();
die("pack-objects setup failed");
}
if (pack_objects_pid < 0)
die("pack-objects fork failed");
dup2(pipe_fd[1], 1);
close(pipe_fd[0]);
close(pipe_fd[1]);
close(fd);
exec_rev_list(refs);
}
static int pack_objects(int fd, struct ref *refs)
{
pid_t rev_list_pid;
rev_list_pid = fork();
if (!rev_list_pid) {
rev_list(fd, refs);
die("rev-list setup failed");
}
if (rev_list_pid < 0)
die("rev-list fork failed");
/*
* We don't wait for the rev-list pipeline in the parent:
* we end up waiting for the other end instead
*/
return 0;
}
static int read_ref(const char *ref, unsigned char *sha1)
{
int fd, ret;
char buffer[60];
fd = open(git_path("%s", ref), O_RDONLY);
if (fd < 0)
return -1;
ret = -1;
if (read(fd, buffer, sizeof(buffer)) >= 40)
ret = get_sha1_hex(buffer, sha1);
close(fd);
return ret;
}
static int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
{
struct commit *new, *old;
struct commit_list *list;
if (force_update)
return 1;
old = lookup_commit_reference(old_sha1);
if (!old)
return 0;
new = lookup_commit_reference(new_sha1);
if (!new)
return 0;
if (parse_commit(new) < 0)
return 0;
list = NULL;
commit_list_insert(new, &list);
while ((new = pop_most_recent_commit(&list, 1)) != NULL) {
if (new == old)
return 1;
}
return 0;
}
static int local_ref_nr_match;
static char **local_ref_match;
static struct ref *local_ref_list;
static struct ref **local_last_ref;
static int try_to_match(const char *refname, const unsigned char *sha1)
{
struct ref *ref;
int len;
if (!path_match(refname, local_ref_nr_match, local_ref_match)) {
if (!send_all)
return 0;
/* If we have it listed already, skip it */
for (ref = local_ref_list ; ref ; ref = ref->next) {
if (!strcmp(ref->name, refname))
return 0;
}
}
len = strlen(refname)+1;
ref = xmalloc(sizeof(*ref) + len);
memset(ref->old_sha1, 0, 20);
memcpy(ref->new_sha1, sha1, 20);
memcpy(ref->name, refname, len);
ref->next = NULL;
*local_last_ref = ref;
local_last_ref = &ref->next;
return 0;
}
static int send_pack(int in, int out, int nr_match, char **match)
{
struct ref *ref_list, **last_ref;
struct ref *ref;
int new_refs;
/* First we get all heads, whether matching or not.. */
last_ref = get_remote_heads(in, &ref_list, 0, NULL);
/*
* Go through the refs, see if we want to update
* any of them..
*/
for (ref = ref_list; ref; ref = ref->next) {
unsigned char new_sha1[20];
char *name = ref->name;
if (nr_match && !path_match(name, nr_match, match))
continue;
if (read_ref(name, new_sha1) < 0)
continue;
if (!memcmp(ref->old_sha1, new_sha1, 20)) {
fprintf(stderr, "'%s' unchanged\n", name);
continue;
}
if (!ref_newer(new_sha1, ref->old_sha1)) {
error("remote '%s' isn't a strict parent of local", name);
continue;
}
/* Ok, mark it for update */
memcpy(ref->new_sha1, new_sha1, 20);
}
/*
* See if we have any refs that the other end didn't have
*/
if (nr_match) {
local_ref_nr_match = nr_match;
local_ref_match = match;
local_ref_list = ref_list;
local_last_ref = last_ref;
for_each_ref(try_to_match);
}
/*
* Finally, tell the other end!
*/
new_refs = 0;
for (ref = ref_list; ref; ref = ref->next) {
char old_hex[60], *new_hex;
if (is_zero_sha1(ref->new_sha1))
continue;
new_refs++;
strcpy(old_hex, sha1_to_hex(ref->old_sha1));
new_hex = sha1_to_hex(ref->new_sha1);
packet_write(out, "%s %s %s", old_hex, new_hex, ref->name);
fprintf(stderr, "'%s': updating from %s to %s\n", ref->name, old_hex, new_hex);
}
packet_flush(out);
if (new_refs)
pack_objects(out, ref_list);
close(out);
return 0;
}
int main(int argc, char **argv)
{
int i, nr_heads = 0;
char *dest = NULL;
char **heads = NULL;
int fd[2], ret;
pid_t pid;
argv++;
for (i = 1; i < argc; i++, argv++) {
char *arg = *argv;
if (*arg == '-') {
if (!strncmp(arg, "--exec=", 7)) {
exec = arg + 7;
continue;
}
if (!strcmp(arg, "--all")) {
send_all = 1;
continue;
}
if (!strcmp(arg, "--force")) {
force_update = 1;
continue;
}
usage(send_pack_usage);
}
if (!dest) {
dest = arg;
continue;
}
heads = argv;
nr_heads = argc - i;
break;
}
if (!dest)
usage(send_pack_usage);
pid = git_connect(fd, dest, exec);
if (pid < 0)
return 1;
ret = send_pack(fd[0], fd[1], nr_heads, heads);
close(fd[0]);
close(fd[1]);
finish_connect(pid);
return ret;
}