diff --git a/Makefile b/Makefile
index e7a26449bf..a833a85618 100644
--- a/Makefile
+++ b/Makefile
@@ -40,10 +40,10 @@ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
 	git-unpack-file git-export git-diff-cache git-convert-cache \
 	git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
 	git-diff-helper git-tar-tree git-local-pull git-write-blob \
-	git-get-tar-commit-id git-apply git-stripspace \
-	git-cvs2git git-diff-stages git-rev-parse git-patch-id \
-	git-pack-objects git-unpack-objects git-verify-pack \
-	git-receive-pack git-send-pack git-prune-packed
+	git-get-tar-commit-id git-apply git-stripspace git-cvs2git \
+	git-diff-stages git-rev-parse git-patch-id git-pack-objects \
+	git-unpack-objects git-verify-pack git-receive-pack git-send-pack \
+	git-prune-packed git-fetch-pack git-upload-pack
 
 all: $(PROG)
 
@@ -139,6 +139,7 @@ git-verify-pack: verify-pack.c
 git-receive-pack: receive-pack.c
 git-send-pack: send-pack.c
 git-prune-packed: prune-packed.c
+git-fetch-pack: fetch-pack.c
 
 git-http-pull: LIBS += -lcurl
 git-rev-list: LIBS += -lssl
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644
index 0000000000..e9035a1dc1
--- /dev/null
+++ b/fetch-pack.c
@@ -0,0 +1,125 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+static const char fetch_pack_usage[] = "git-fetch-pack [host:]directory [heads]* < mycommitlist";
+static const char *exec = "git-upload-pack";
+
+static int get_ack(int fd, unsigned char *result_sha1)
+{
+	static char line[1000];
+	int len = packet_read_line(fd, line, sizeof(line));
+
+	if (!len)
+		die("git-fetch-pack: expected ACK/NAK, got EOF");
+	if (line[len-1] == '\n')
+		line[--len] = 0;
+	if (!strcmp(line, "NAK"))
+		return 0;
+	if (!strncmp(line, "ACK ", 3)) {
+		if (!get_sha1_hex(line+4, result_sha1))
+			return 1;
+	}
+	die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static int find_common(int fd[2], unsigned char *result_sha1)
+{
+	static char line[1000];
+	int count = 0, flushes = 0;
+
+	while (fgets(line, sizeof(line), stdin) != NULL) {
+		unsigned char sha1[20];
+		if (get_sha1_hex(line, sha1))
+			die("git-fetch-pack: expected object name, got crud");
+		packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+		if (!(31 & ++count)) {
+			packet_flush(fd[1]);
+			flushes++;
+
+			/*
+			 * We keep one window "ahead" of the other side, and
+			 * will wait for an ACK only on the next one
+			 */
+			if (count == 32)
+				continue;
+			if (get_ack(fd[0], result_sha1))
+				return 0;
+			flushes--;
+		}
+	}
+	flushes++;
+	packet_flush(fd[1]);
+	while (flushes) {
+		flushes--;
+		if (get_ack(fd[0], result_sha1))
+			return 0;
+	}
+	return -1;
+}
+
+static int get_remote_heads(int fd, int nr_match, char **match)
+{
+	for (;;) {
+		static char line[1000];
+		unsigned char sha1[20];
+		char *refname;
+		int len;
+
+		len = packet_read_line(fd, line, sizeof(line));
+		if (!len)
+			break;
+		if (line[len-1] == '\n')
+			line[--len] = 0;
+		if (len < 42 || get_sha1_hex(line, sha1))
+			die("git-fetch-pack: protocol error - expected ref descriptor, got '%sä'", line);
+		refname = line+41;
+		if (nr_match && !path_match(refname, nr_match, match))
+			continue;
+		printf("%s %s\n", sha1_to_hex(sha1), refname);
+	}
+	return 0;
+}
+
+static int fetch_pack(int fd[2], int nr_match, char **match)
+{
+	unsigned char sha1[20];
+
+	get_remote_heads(fd[0], nr_match, match);
+	if (find_common(fd, sha1) < 0)
+		die("git-fetch-pack: no common commits");
+	printf("common commit: %s\n", sha1_to_hex(sha1));
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int i, ret, nr_heads;
+	char *dest = NULL, **heads;
+	int fd[2];
+	pid_t pid;
+
+	nr_heads = 0;
+	heads = NULL;
+	for (i = 1; i < argc; i++) {
+		char *arg = argv[i];
+
+		if (*arg == '-') {
+			/* Arguments go here */
+			usage(fetch_pack_usage);
+		}
+		dest = arg;
+		heads = argv + i + 1;
+		nr_heads = argc - i - 1;
+		break;
+	}
+	if (!dest)
+		usage(fetch_pack_usage);
+	pid = git_connect(fd, dest, exec);
+	if (pid < 0)
+		return 1;
+	ret = fetch_pack(fd, nr_heads, heads);
+	close(fd[0]);
+	close(fd[1]);
+	finish_connect(pid);
+	return ret;
+}
diff --git a/upload-pack.c b/upload-pack.c
new file mode 100644
index 0000000000..18b8e1a6ca
--- /dev/null
+++ b/upload-pack.c
@@ -0,0 +1,86 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char upload_pack_usage[] = "git-upload-pack <dir>";
+
+static int got_sha1(char *hex, unsigned char *sha1)
+{
+	if (get_sha1_hex(hex, sha1))
+		die("git-upload-pack: expected SHA1 object, got '%s'", hex);
+	return has_sha1_file(sha1);
+}
+
+static int get_common_commits(void)
+{
+	static char line[1000];
+	unsigned char sha1[20];
+	int len;
+
+	for(;;) {
+		len = packet_read_line(0, line, sizeof(line));
+
+		if (!len) {
+			packet_write(1, "NAK\n");
+			continue;
+		}
+		if (line[len-1] == '\n')
+			line[--len] = 0;
+		if (!strncmp(line, "have ", 5)) {
+			if (got_sha1(line+5, sha1)) {
+				packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
+				break;
+			}
+			continue;
+		}
+		if (!strcmp(line, "done")) {
+			packet_write(1, "NAK\n");
+			return -1;
+		}
+		die("git-upload-pack: expected SHA1 list, got '%s'", line);
+	}
+
+	for (;;) {
+		len = packet_read_line(0, line, sizeof(line));
+		if (!len)
+			break;
+		if (!strncmp(line, "have ", 5)) {
+			got_sha1(line+5, sha1);
+			continue;
+		}
+		if (!strcmp(line, "done"))
+			break;
+		die("git-upload-pack: expected SHA1 list, got '%s'", line);
+	}
+	return 0;
+}
+
+static int send_ref(const char *refname, const unsigned char *sha1)
+{
+	packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+	return 0;
+}
+
+static int upload_pack(void)
+{
+	for_each_ref(send_ref);
+	packet_flush(1);
+	get_common_commits();
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	const char *dir;
+	if (argc != 2)
+		usage(upload_pack_usage);
+	dir = argv[1];
+	if (chdir(dir))
+		die("git-upload-pack unable to chdir to %s", dir);
+	chdir(".git");
+	if (access("objects", X_OK) || access("refs", X_OK))
+		die("git-upload-pack: %s doesn't seem to be a git archive", dir);
+	setenv("GIT_DIR", ".", 1);
+	upload_pack();
+	return 0;
+}