Merge branch 'fixes'
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 6ecb089..36f42e0 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -52,9 +52,7 @@
 inspect that with `ls`. For your new empty project, it should show you
 three entries, among other things:
 
- - a symlink called `HEAD`, pointing to `refs/heads/master` (if your
-   platform does not have native symlinks, it is a file containing the
-   line "ref: refs/heads/master")
+ - a symlink called `HEAD`, pointing to `refs/heads/master`
 +
 Don't worry about the fact that the file that the `HEAD` link points to
 doesn't even exist yet -- you haven't created the commit that will
@@ -230,7 +228,6 @@
 
 ------------
 diff --git a/hello b/hello
-index 557db03..263414f 100644
 --- a/hello
 +++ b/hello
 @@ -1 +1,2 @@
@@ -293,16 +290,13 @@
 on its standard input, and it will write out the resulting object name for the
 commit to its standard output.
 
-And this is where we create the `.git/refs/heads/master` file
-which is pointed at by `HEAD`. This file is supposed to contain
-the reference to the top-of-tree of the master branch, and since
-that's exactly what `git-commit-tree` spits out, we can do this
-all with a sequence of simple shell commands:
+And this is where we start using the `.git/HEAD` file. The `HEAD` file is
+supposed to contain the reference to the top-of-tree, and since that's
+exactly what `git-commit-tree` spits out, we can do this all with a simple
+shell pipeline:
 
 ------------------------------------------------
-tree=$(git-write-tree)
-commit=$(echo 'Initial commit' | git-commit-tree $tree)
-git-update-ref HEAD $(commit)
+echo "Initial commit" | git-commit-tree $(git-write-tree) > .git/HEAD
 ------------------------------------------------
 
 which will say:
@@ -698,9 +692,7 @@
 just telling `git checkout` what the base of the checkout would be.
 In other words, if you have an earlier tag or branch, you'd just do
 
-------------
-git checkout -b mybranch earlier-commit
-------------
+	git checkout -b mybranch earlier-commit
 
 and it would create the new branch `mybranch` at the earlier commit,
 and check out the state at that time.
@@ -708,29 +700,17 @@
 
 You can always just jump back to your original `master` branch by doing
 
-------------
-git checkout master
-------------
+	git checkout master
 
 (or any other branch-name, for that matter) and if you forget which
 branch you happen to be on, a simple
 
-------------
-ls -l .git/HEAD
-------------
+	ls -l .git/HEAD
 
-will tell you where it's pointing (Note that on platforms with bad or no
-symlink support, you have to execute
+will tell you where it's pointing. To get the list of branches
+you have, you can say
 
-------------
-cat .git/HEAD
-------------
-
-instead). To get the list of branches you have, you can say
-
-------------
-git branch
-------------
+	git branch
 
 which is nothing more than a simple script around `ls .git/refs/heads`.
 There will be asterisk in front of the branch you are currently on.
@@ -738,9 +718,7 @@
 Sometimes you may wish to create a new branch _without_ actually
 checking it out and switching to it. If so, just use the command
 
-------------
-git branch <branchname> [startingpoint]
-------------
+	git branch <branchname> [startingpoint]
 
 which will simply _create_ the branch, but will not do anything further. 
 You can then later -- once you decide that you want to actually develop
@@ -866,6 +844,7 @@
  ! [mybranch] Some work.
 --
 +  [master] Merged "mybranch" changes.
++  [master~1] Some fun.
 ++ [mybranch] Some work.
 ------------------------------------------------
 
@@ -892,10 +871,8 @@
 to the `master` branch. Let's go back to `mybranch`, and run
 resolve to get the "upstream changes" back to your branch.
 
-------------
-git checkout mybranch
-git resolve HEAD master "Merge upstream changes."
-------------
+	git checkout mybranch
+	git resolve HEAD master "Merge upstream changes."
 
 This outputs something like this (the actual commit object names
 would be different)
@@ -1111,17 +1088,13 @@
 project `my-git`. After logging into the remote machine, create
 an empty directory:
 
-------------
-mkdir my-git.git
-------------
+	mkdir my-git.git
 
 Then, make that directory into a GIT repository by running
 `git init-db`, but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
-------------
-GIT_DIR=my-git.git git-init-db
-------------
+	GIT_DIR=my-git.git git-init-db
 
 Make sure this directory is available for others you want your
 changes to be pulled by via the transport of your choice. Also
@@ -1145,9 +1118,7 @@
 Come back to the machine you have your private repository. From
 there, run this command:
 
-------------
-git push <public-host>:/path/to/my-git.git master
-------------
+	git push <public-host>:/path/to/my-git.git master
 
 This synchronizes your public repository to match the named
 branch head (i.e. `master` in this case) and objects reachable
@@ -1157,9 +1128,7 @@
 repository. Kernel.org mirror network takes care of the
 propagation to other publicly visible machines:
 
-------------
-git push master.kernel.org:/pub/scm/git/git.git/ 
-------------
+	git push master.kernel.org:/pub/scm/git/git.git/ 
 
 
 Packing your repository
@@ -1172,9 +1141,7 @@
 immutable once they are created, there is a way to optimize the
 storage by "packing them together". The command
 
-------------
-git repack
-------------
+	git repack
 
 will do it for you. If you followed the tutorial examples, you
 would have accumulated about 17 objects in `.git/objects/??/`
@@ -1198,9 +1165,7 @@
 Once you have packed objects, you do not need to leave the
 unpacked objects that are contained in the pack file anymore.
 
-------------
-git prune-packed
-------------
+	git prune-packed
 
 would remove them for you.
 
diff --git a/Makefile b/Makefile
index e67d0e7..588e5b0 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@
 
 # DEFINES += -DUSE_STDEV
 
-GIT_VERSION = 0.99.8
+GIT_VERSION = 0.99.8f
 
 CFLAGS = -g -O2 -Wall
 ALL_CFLAGS = $(CFLAGS) $(PLATFORM_DEFINES) $(DEFINES)
@@ -116,7 +116,7 @@
 	git-ssh-upload git-tar-tree git-unpack-file \
 	git-unpack-objects git-update-index git-update-server-info \
 	git-upload-pack git-verify-pack git-write-tree \
-	git-update-ref git-symbolic-ref \
+	git-update-ref git-symbolic-ref git-check-ref-format \
 	$(SIMPLE_PROGRAMS)
 
 # Backward compatibility -- to be removed after 1.0
diff --git a/apply.c b/apply.c
index f886272..c58d9a2 100644
--- a/apply.c
+++ b/apply.c
@@ -15,6 +15,7 @@
 #include <ctype.h>
 #include <fnmatch.h>
 #include "cache.h"
+#include "quote.h"
 
 // We default to the merge behaviour, since that's what most people would
 // expect.
@@ -142,6 +143,35 @@
 	const char *start = line;
 	char *name;
 
+	if (*line == '"') {
+		/* Proposed "new-style" GNU patch/diff format; see
+		 * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+		 */
+		name = unquote_c_style(line, NULL);
+		if (name) {
+			char *cp = name;
+			while (p_value) {
+				cp = strchr(name, '/');
+				if (!cp)
+					break;
+				cp++;
+				p_value--;
+			}
+			if (cp) {
+				/* name can later be freed, so we need
+				 * to memmove, not just return cp
+				 */
+				memmove(name, cp, strlen(cp) + 1);
+				free(def);
+				return name;
+			}
+			else {
+				free(name);
+				name = NULL;
+			}
+		}
+	}
+
 	for (;;) {
 		char c = *line;
 
@@ -231,37 +261,29 @@
  */
 static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
 {
-	int len;
-	const char *name;
-
 	if (!orig_name && !isnull)
 		return find_name(line, NULL, 1, 0);
 
-	name = "/dev/null";
-	len = 9;
 	if (orig_name) {
+		int len;
+		const char *name;
+		char *another;
 		name = orig_name;
 		len = strlen(name);
 		if (isnull)
 			die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
-	}
-
-	if (*name == '/')
-		goto absolute_path;
-
-	for (;;) {
-		char c = *line++;
-		if (c == '\n')
-			break;
-		if (c != '/')
-			continue;
-absolute_path:
-		if (memcmp(line, name, len) || line[len] != '\n')
-			break;
+		another = find_name(line, NULL, 1, 0);
+		if (!another || memcmp(another, name, len))
+			die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+		free(another);
 		return orig_name;
 	}
-	die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
-	return NULL;
+	else {
+		/* expect "/dev/null" */
+		if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+			die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
+		return NULL;
+	}
 }
 
 static int gitdiff_oldname(const char *line, struct patch *patch)
@@ -353,29 +375,124 @@
 	return -1;
 }
 
-static char *git_header_name(char *line)
+static const char *stop_at_slash(const char *line, int llen)
+{
+	int i;
+
+	for (i = 0; i < llen; i++) {
+		int ch = line[i];
+		if (ch == '/')
+			return line + i;
+	}
+	return NULL;
+}
+
+/* This is to extract the same name that appears on "diff --git"
+ * line.  We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file.  In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
 {
 	int len;
-	char *name, *second;
+	const char *name;
+	const char *second = NULL;
 
-	/*
-	 * Find the first '/'
-	 */
-	name = line;
-	for (;;) {
-		char c = *name++;
-		if (c == '\n')
+	line += strlen("diff --git ");
+	llen -= strlen("diff --git ");
+
+	if (*line == '"') {
+		const char *cp;
+		char *first = unquote_c_style(line, &second);
+		if (!first)
 			return NULL;
-		if (c == '/')
-			break;
+
+		/* advance to the first slash */
+		cp = stop_at_slash(first, strlen(first));
+		if (!cp || cp == first) {
+			/* we do not accept absolute paths */
+		free_first_and_fail:
+			free(first);
+			return NULL;
+		}
+		len = strlen(cp+1);
+		memmove(first, cp+1, len+1); /* including NUL */
+
+		/* second points at one past closing dq of name.
+		 * find the second name.
+		 */
+		while ((second < line + llen) && isspace(*second))
+			second++;
+
+		if (line + llen <= second)
+			goto free_first_and_fail;
+		if (*second == '"') {
+			char *sp = unquote_c_style(second, NULL);
+			if (!sp)
+				goto free_first_and_fail;
+			cp = stop_at_slash(sp, strlen(sp));
+			if (!cp || cp == sp) {
+			free_both_and_fail:
+				free(sp);
+				goto free_first_and_fail;
+			}
+			/* They must match, otherwise ignore */
+			if (strcmp(cp+1, first))
+				goto free_both_and_fail;
+			free(sp);
+			return first;
+		}
+
+		/* unquoted second */
+		cp = stop_at_slash(second, line + llen - second);
+		if (!cp || cp == second)
+			goto free_first_and_fail;
+		cp++;
+		if (line + llen - cp != len + 1 ||
+		    memcmp(first, cp, len))
+			goto free_first_and_fail;
+		return first;
 	}
 
-	/*
-	 * We don't accept absolute paths (/dev/null) as possibly valid
-	 */
-	if (name == line+1)
+	/* unquoted first name */
+	name = stop_at_slash(line, llen);
+	if (!name || name == line)
 		return NULL;
 
+	name++;
+
+	/* since the first name is unquoted, a dq if exists must be
+	 * the beginning of the second name.
+	 */
+	for (second = name; second < line + llen; second++) {
+		if (*second == '"') {
+			const char *cp = second;
+			const char *np;
+			char *sp = unquote_c_style(second, NULL);
+
+			if (!sp)
+				return NULL;
+			np = stop_at_slash(sp, strlen(sp));
+			if (!np || np == sp) {
+			free_second_and_fail:
+				free(sp);
+				return NULL;
+			}
+			np++;
+			len = strlen(np);
+			if (len < cp - name &&
+			    !strncmp(np, name, len) &&
+			    isspace(name[len])) {
+				/* Good */
+				memmove(sp, np, len + 1);
+				return sp;
+			}
+			goto free_second_and_fail;
+		}
+	}
+
 	/*
 	 * Accept a name only if it shows up twice, exactly the same
 	 * form.
@@ -423,7 +540,7 @@
 	 * or removing or adding empty files), so we get
 	 * the default name from the header.
 	 */
-	patch->def_name = git_header_name(line + strlen("diff --git "));
+	patch->def_name = git_header_name(line, len);
 
 	line += len;
 	size -= len;
@@ -756,11 +873,18 @@
 {
 	const char *prefix = "";
 	char *name = patch->new_name;
+	char *qname = NULL;
 	int len, max, add, del, total;
 
 	if (!name)
 		name = patch->old_name;
 
+	if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+		qname = xmalloc(len + 1);
+		quote_c_style(name, qname, NULL, 0);
+		name = qname;
+	}
+
 	/*
 	 * "scale" the filename
 	 */
@@ -798,6 +922,8 @@
 	printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
 		len, name, patch->lines_added + patch->lines_deleted,
 		add, pluses, del, minuses);
+	if (qname)
+		free(qname);
 }
 
 static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
@@ -1220,12 +1346,16 @@
 	if (lines > max_change)
 		max_change = lines;
 	if (patch->old_name) {
-		int len = strlen(patch->old_name);
+		int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+		if (!len)
+			len = strlen(patch->old_name);
 		if (len > max_len)
 			max_len = len;
 	}
 	if (patch->new_name) {
-		int len = strlen(patch->new_name);
+		int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+		if (!len)
+			len = strlen(patch->new_name);
 		if (len > max_len)
 			max_len = len;
 	}
diff --git a/cache.h b/cache.h
index ec2a161..201ce99 100644
--- a/cache.h
+++ b/cache.h
@@ -331,7 +331,7 @@
 extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
 		      int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
-extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1);
 extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
diff --git a/check-ref-format.c b/check-ref-format.c
new file mode 100644
index 0000000..a0adb3d
--- /dev/null
+++ b/check-ref-format.c
@@ -0,0 +1,17 @@
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+
+#include <stdio.h>
+
+int main(int ac, char **av)
+{
+	if (ac != 2)
+		usage("git-check-ref-format refname");
+	if (check_ref_format(av[1]))
+		exit(1);
+	return 0;
+}
diff --git a/clone-pack.c b/clone-pack.c
index c102ca8..2ac35f6 100644
--- a/clone-pack.c
+++ b/clone-pack.c
@@ -34,6 +34,12 @@
 	int fd;
 	char *hex;
 
+	if (!strncmp(ref->name, "refs/", 5) &&
+	    check_ref_format(ref->name + 5)) {
+		error("refusing to create funny ref '%s' locally", ref->name);
+		return;
+	}
+
 	if (safe_create_leading_directories(path))
 		die("unable to create leading directory for %s", ref->name);
 	fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);
@@ -112,7 +118,7 @@
 	int status;
 	pid_t pid;
 
-	get_remote_heads(fd[0], &refs, nr_match, match);
+	get_remote_heads(fd[0], &refs, nr_match, match, 1);
 	if (!refs) {
 		packet_flush(fd[1]);
 		die("no matching remote head");
diff --git a/connect.c b/connect.c
index 825c439..e21d39a 100644
--- a/connect.c
+++ b/connect.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "pkt-line.h"
 #include "quote.h"
+#include "refs.h"
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -10,7 +11,8 @@
 /*
  * Read all the refs from the other end
  */
-struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match)
+struct ref **get_remote_heads(int in, struct ref **list,
+			      int nr_match, char **match, int ignore_funny)
 {
 	*list = NULL;
 	for (;;) {
@@ -29,6 +31,11 @@
 		if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
 			die("protocol error: expected sha/ref, got '%s'", buffer);
 		name = buffer + 41;
+
+		if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
+		    check_ref_format(name + 5))
+			continue;
+
 		if (nr_match && !path_match(name, nr_match, match))
 			continue;
 		ref = xcalloc(1, sizeof(*ref) + len - 40);
diff --git a/daemon.c b/daemon.c
index cec7e75..bd278b0 100644
--- a/daemon.c
+++ b/daemon.c
@@ -12,7 +12,9 @@
 static int log_syslog;
 static int verbose;
 
-static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [directory...]";
+static const char daemon_usage[] =
+"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+"           [--timeout=n] [--init-timeout=n] [directory...]";
 
 /* List of acceptable pathname prefixes */
 static char **ok_paths = NULL;
@@ -20,6 +22,9 @@
 /* If this is set, git-daemon-export-ok is not required */
 static int export_all_trees = 0;
 
+/* Timeout, and initial timeout */
+static unsigned int timeout = 0;
+static unsigned int init_timeout = 0;
 
 static void logreport(int priority, const char *err, va_list params)
 {
@@ -79,17 +84,30 @@
 {
 	const char *p = dir;
 	char **pp;
-	int sl = 1, ndot = 0;
+	int sl, ndot;
+
+	/* The pathname here should be an absolute path. */
+	if ( *p++ != '/' )
+		return 0;
+
+	sl = 1;  ndot = 0;
 
 	for (;;) {
 		if ( *p == '.' ) {
 			ndot++;
-		} else if ( *p == '/' || *p == '\0' ) {
+		} else if ( *p == '\0' ) {
+			/* Reject "." and ".." at the end of the path */
 			if ( sl && ndot > 0 && ndot < 3 )
-				return 0; /* . or .. in path */
+				return 0;
+
+			/* Otherwise OK */
+			break;
+		} else if ( *p == '/' ) {
+			/* Refuse "", "." or ".." */
+			if ( sl && ndot < 3 )
+				return 0;
 			sl = 1;
-			if ( *p == '\0' )
-				break; /* End of string and all is good */
+			ndot = 0;
 		} else {
 			sl = ndot = 0;
 		}
@@ -98,7 +116,7 @@
 
 	if ( ok_paths && *ok_paths ) {
 		int ok = 0;
-		int dirlen = strlen(dir); /* read_packet_line can return embedded \0 */
+		int dirlen = strlen(dir);
 
 		for ( pp = ok_paths ; *pp ; pp++ ) {
 			int len = strlen(*pp);
@@ -117,22 +135,16 @@
 	return 1;		/* Path acceptable */
 }
 
-static int upload(char *dir, int dirlen)
+static int set_dir(const char *dir)
 {
-	loginfo("Request for '%s'", dir);
-
 	if (!path_ok(dir)) {
-		logerror("Forbidden directory: %s\n", dir);
+		errno = EACCES;
 		return -1;
 	}
 
-	if (chdir(dir) < 0) {
-		logerror("Cannot chdir('%s'): %s", dir, strerror(errno));
+	if ( chdir(dir) )
 		return -1;
-	}
-
-	chdir(".git");
-
+	
 	/*
 	 * Security on the cheap.
 	 *
@@ -140,10 +152,41 @@
 	 * a "git-daemon-export-ok" flag that says that the other side
 	 * is ok with us doing this.
 	 */
-	if ((!export_all_trees && access("git-daemon-export-ok", F_OK)) ||
-	    access("objects/00", X_OK) ||
-	    access("HEAD", R_OK)) {
-		logerror("Not a valid git-daemon-enabled repository: '%s'", dir);
+	if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+		errno = EACCES;
+		return -1;
+	}
+
+	if (access("objects/", X_OK) || access("HEAD", R_OK)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	/* If all this passed, we're OK */
+	return 0;
+}
+
+static int upload(char *dir)
+{
+	/* Try paths in this order */
+	static const char *paths[] = { "%s", "%s/.git", "%s.git", "%s.git/.git", NULL };
+	const char **pp;
+	/* Enough for the longest path above including final null */
+	int buflen = strlen(dir)+10;
+	char *dirbuf = xmalloc(buflen);
+	/* Timeout as string */
+	char timeout_buf[64];
+
+	loginfo("Request for '%s'", dir);
+
+	for ( pp = paths ; *pp ; pp++ ) {
+		snprintf(dirbuf, buflen, *pp, dir);
+		if ( !set_dir(dirbuf) )
+			break;
+	}
+
+	if ( !*pp ) {
+		logerror("Cannot set directory '%s': %s", dir, strerror(errno));
 		return -1;
 	}
 
@@ -153,8 +196,10 @@
 	 */
 	signal(SIGTERM, SIG_IGN);
 
+	snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+
 	/* git-upload-pack only ever reads stuff, so this is safe */
-	execlp("git-upload-pack", "git-upload-pack", ".", NULL);
+	execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf, ".", NULL);
 	return -1;
 }
 
@@ -163,13 +208,15 @@
 	static char line[1000];
 	int len;
 
+	alarm(init_timeout ? init_timeout : timeout);
 	len = packet_read_line(0, line, sizeof(line));
+	alarm(0);
 
 	if (len && line[len-1] == '\n')
 		line[--len] = 0;
 
 	if (!strncmp("git-upload-pack /", line, 17))
-		return upload(line + 16, len - 16);
+		return upload(line+16);
 
 	logerror("Protocol error: '%s'", line);
 	return -1;
@@ -512,6 +559,12 @@
 			export_all_trees = 1;
 			continue;
 		}
+		if (!strncmp(arg, "--timeout=", 10)) {
+			timeout = atoi(arg+10);
+		}
+		if (!strncmp(arg, "--init-timeout=", 15)) {
+			init_timeout = atoi(arg+15);
+		}
 		if (!strcmp(arg, "--")) {
 			ok_paths = &argv[i+1];
 			break;
diff --git a/debian/changelog b/debian/changelog
index bebc191..0df3747 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,39 @@
+git-core (0.99.8f-0) unstable; urgency=low
+
+  * GIT 0.99.8f
+
+ -- Junio C Hamano <junkio@cox.net>  Wed, 19 Oct 2005 02:29:24 -0700
+
+git-core (0.99.8e-0) unstable; urgency=low
+
+  * GIT 0.99.8e
+
+ -- Junio C Hamano <junkio@cox.net>  Mon, 17 Oct 2005 17:45:08 -0700
+
+git-core (0.99.8d-0) unstable; urgency=low
+
+  * GIT 0.99.8d
+
+ -- Junio C Hamano <junkio@cox.net>  Sat, 15 Oct 2005 17:22:58 -0700
+
+git-core (0.99.8c-0) unstable; urgency=low
+
+  * GIT 0.99.8c
+
+ -- Junio C Hamano <junkio@cox.net>  Sun,  9 Oct 2005 19:19:16 -0700
+
+git-core (0.99.8b-0) unstable; urgency=low
+
+  * GIT 0.99.8b
+
+ -- Junio C Hamano <junkio@cox.net>  Wed,  5 Oct 2005 15:41:24 -0700
+
+git-core (0.99.8a-0) unstable; urgency=low
+
+  * GIT 0.99.8a
+
+ -- Junio C Hamano <junkio@cox.net>  Mon,  3 Oct 2005 16:27:32 -0700
+
 git-core (0.99.8-0) unstable; urgency=low
 
   * GIT 0.99.8
diff --git a/fetch-pack.c b/fetch-pack.c
index 582f967..969e72a 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -1,6 +1,9 @@
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+#include <time.h>
 #include <sys/wait.h>
 
 static int quiet;
@@ -12,6 +15,7 @@
 static int find_common(int fd[2], unsigned char *result_sha1,
 		       struct ref *refs)
 {
+	int fetching;
 	static char line[1000];
 	int count = 0, flushes = 0, retval;
 	FILE *revs;
@@ -20,16 +24,19 @@
 	if (!revs)
 		die("unable to run 'git-rev-list'");
 
-	while (refs) {
+	fetching = 0;
+	for ( ; refs ; refs = refs->next) {
 		unsigned char *remote = refs->old_sha1;
-		if (verbose)
-			fprintf(stderr,
-				"want %s (%s)\n", sha1_to_hex(remote),
-				refs->name);
+		unsigned char *local = refs->new_sha1;
+
+		if (!memcmp(remote, local, 20))
+			continue;
 		packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
-		refs = refs->next;
+		fetching++;
 	}
 	packet_flush(fd[1]);
+	if (!fetching)
+		return 1;
 	flushes = 1;
 	retval = -1;
 	while (fgets(line, sizeof(line), revs) != NULL) {
@@ -74,6 +81,92 @@
 	return retval;
 }
 
+#define COMPLETE	(1U << 0)
+static struct commit_list *complete = NULL;
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+	struct object *o = parse_object(sha1);
+
+	while (o && o->type == tag_type) {
+		o->flags |= COMPLETE;
+		o = parse_object(((struct tag *)o)->tagged->sha1);
+	}
+	if (o->type == commit_type) {
+		struct commit *commit = (struct commit *)o;
+		commit->object.flags |= COMPLETE;
+		insert_by_date(commit, &complete);
+	}
+	return 0;
+}
+
+static void mark_recent_complete_commits(unsigned long cutoff)
+{
+	while (complete && cutoff <= complete->item->date) {
+		if (verbose)
+			fprintf(stderr, "Marking %s as complete\n",
+				sha1_to_hex(complete->item->object.sha1));
+		pop_most_recent_commit(&complete, COMPLETE);
+	}
+}
+
+static int everything_local(struct ref *refs)
+{
+	struct ref *ref;
+	int retval;
+	unsigned long cutoff = 0;
+
+	track_object_refs = 0;
+	save_commit_buffer = 0;
+
+	for (ref = refs; ref; ref = ref->next) {
+		struct object *o;
+
+		o = parse_object(ref->old_sha1);
+		if (!o)
+			continue;
+
+		/* We already have it -- which may mean that we were
+		 * in sync with the other side at some time after
+		 * that (it is OK if we guess wrong here).
+		 */
+		if (o->type == commit_type) {
+			struct commit *commit = (struct commit *)o;
+			if (!cutoff || cutoff < commit->date)
+				cutoff = commit->date;
+		}
+	}
+
+	for_each_ref(mark_complete);
+	if (cutoff)
+		mark_recent_complete_commits(cutoff);
+
+	for (retval = 1; refs ; refs = refs->next) {
+		const unsigned char *remote = refs->old_sha1;
+		unsigned char local[20];
+		struct object *o;
+
+		o = parse_object(remote);
+		if (!o || !(o->flags & COMPLETE)) {
+			retval = 0;
+			if (!verbose)
+				continue;
+			fprintf(stderr,
+				"want %s (%s)\n", sha1_to_hex(remote),
+				refs->name);
+			continue;
+		}
+
+		memcpy(refs->new_sha1, local, 20);
+		if (!verbose)
+			continue;
+		fprintf(stderr,
+			"already have %s (%s)\n", sha1_to_hex(remote),
+			refs->name);
+	}
+	return retval;
+}
+
 static int fetch_pack(int fd[2], int nr_match, char **match)
 {
 	struct ref *ref;
@@ -81,11 +174,15 @@
 	int status;
 	pid_t pid;
 
-	get_remote_heads(fd[0], &ref, nr_match, match);
+	get_remote_heads(fd[0], &ref, nr_match, match, 1);
 	if (!ref) {
 		packet_flush(fd[1]);
 		die("no matching remote head");
 	}
+	if (everything_local(ref)) {
+		packet_flush(fd[1]);
+		goto all_done;
+	}
 	if (find_common(fd, sha1, ref) < 0)
 		fprintf(stderr, "warning: no common commits\n");
 	pid = fork();
@@ -109,6 +206,7 @@
 		int code = WEXITSTATUS(status);
 		if (code)
 			die("git-unpack-objects died with error code %d", code);
+all_done:
 		while (ref) {
 			printf("%s %s\n",
 			       sha1_to_hex(ref->old_sha1), ref->name);
diff --git a/git-branch.sh b/git-branch.sh
index 074229c..e2db906 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -13,38 +13,42 @@
 }
 
 delete_branch () {
-    option="$1" branch_name="$2"
+    option="$1"
+    shift
     headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
     	       sed -e 's|^refs/heads/||')
-    case ",$headref," in
-    ",$branch_name,")
-	die "Cannot delete the branch you are on." ;;
-    ,,)
-	die "What branch are you on anyway?" ;;
-    esac
-    branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
-	branch=$(git-rev-parse --verify "$branch^0") ||
-	    die "Seriously, what branch are you talking about?"
-    case "$option" in
-    -D)
-	;;
-    *)
-	mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
-	case " $mbs " in
-	*' '$branch' '*)
-	    # the merge base of branch and HEAD contains branch --
-	    # which means that the HEAD contains everything in the HEAD.
+    for branch_name
+    do
+	case ",$headref," in
+	",$branch_name,")
+	    die "Cannot delete the branch you are on." ;;
+	,,)
+	    die "What branch are you on anyway?" ;;
+	esac
+	branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
+	    branch=$(git-rev-parse --verify "$branch^0") ||
+		die "Seriously, what branch are you talking about?"
+	case "$option" in
+	-D)
 	    ;;
 	*)
-	    echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
-If you are sure you want to delete it, run 'git branch -D $branch_name'."
-	    exit 1
+	    mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
+	    case " $mbs " in
+	    *' '$branch' '*)
+		# the merge base of branch and HEAD contains branch --
+		# which means that the HEAD contains everything in the HEAD.
+		;;
+	    *)
+		echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
+    If you are sure you want to delete it, run 'git branch -D $branch_name'."
+		exit 1
+		;;
+	    esac
 	    ;;
 	esac
-	;;
-    esac
-    rm -f "$GIT_DIR/refs/heads/$branch_name"
-    echo "Deleted branch $branch_name."
+	rm -f "$GIT_DIR/refs/heads/$branch_name"
+	echo "Deleted branch $branch_name."
+    done
     exit 0
 }
 
@@ -52,7 +56,7 @@
 do
 	case "$1" in
 	-d | -D)
-		delete_branch "$1" "$2"
+		delete_branch "$@"
 		exit
 		;;
 	--)
@@ -93,6 +97,9 @@
 
 rev=$(git-rev-parse --verify "$head") || exit
 
-[ -e "$GIT_DIR/refs/heads/$branchname" ] && die "$branchname already exists"
+[ -e "$GIT_DIR/refs/heads/$branchname" ] &&
+	die "$branchname already exists."
+git-check-ref-format "heads/$branchname" ||
+	die "we do not like '$branchname' as a branch name."
 
 echo $rev > "$GIT_DIR/refs/heads/$branchname"
diff --git a/git-checkout.sh b/git-checkout.sh
index c382590..2c053a3 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -17,6 +17,8 @@
 			die "git checkout: -b needs a branch name"
 		[ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
 			die "git checkout: branch $newbranch already exists"
+		git-check-ref-format "heads/$newbranch" ||
+			die "we do not like '$newbranch' as a branch name."
 		;;
 	"-f")
 		force=1
diff --git a/git-clone.sh b/git-clone.sh
index 7143131..18e692a 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -53,7 +53,11 @@
 	while read sha1 refname
 	do
 		name=`expr "$refname" : 'refs/\(.*\)'` &&
-		git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1
+		case "$name" in
+		*^*)	;;
+		*)
+			git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1
+		esac
 	done <"$clone_tmp/refs"
 	rm -fr "$clone_tmp"
 }
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index f7c3a51..f00f759 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -487,7 +487,7 @@
 	my @opt;
 	@opt = split(/,/,$opt_p) if defined $opt_p;
 	unshift @opt, '-z', $opt_z if defined $opt_z;
-	unless ($opt_p =~ m/--no-cvs-direct/) {
+	unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
 		push @opt, '--cvs-direct';
 	}
 	exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
diff --git a/git-fetch.sh b/git-fetch.sh
index d398866..360fecd 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -170,7 +170,11 @@
 reflist=$(get_remote_refs_for_fetch "$@")
 if test "$tags"
 then
-	taglist=$(git-ls-remote --tags "$remote" | awk '{ print "."$2":"$2 }')
+	taglist=$(git-ls-remote --tags "$remote" |
+		sed -e '
+			/\^/d
+			s/^[^	]*	//
+			s/.*/&:&/')
 	if test "$#" -gt 1
 	then
 		# remote URL plus explicit refspecs; we need to merge them.
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
index 4d8a572..32f1085 100755
--- a/git-parse-remote.sh
+++ b/git-parse-remote.sh
@@ -94,6 +94,12 @@
 		heads/* | tags/* ) local="refs/$local" ;;
 		*) local="refs/heads/$local" ;;
 		esac
+
+		if local_ref_name=$(expr "$local" : 'refs/\(.*\)')
+		then
+		   git-check-ref-format "$local_ref_name" ||
+		   die "* refusing to create funny ref '$local_ref_name' locally"
+		fi
 		echo "${dot_prefix}${force}${remote}:${local}"
 		dot_prefix=.
 	done
diff --git a/git-tag.sh b/git-tag.sh
index 400bdb9..11b0492 100755
--- a/git-tag.sh
+++ b/git-tag.sh
@@ -46,6 +46,8 @@
     die "tag '$name' already exists"
 fi
 shift
+git-check-ref-format "tags/$name" ||
+	die "we do not like '$name' as a tag name."
 
 object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
 type=$(git-cat-file -t $object) || exit 1
diff --git a/peek-remote.c b/peek-remote.c
index 4b1d0d5..ee49bf3 100644
--- a/peek-remote.c
+++ b/peek-remote.c
@@ -11,7 +11,7 @@
 {
 	struct ref *ref;
 
-	get_remote_heads(fd[0], &ref, 0, NULL);
+	get_remote_heads(fd[0], &ref, 0, NULL, 0);
 	packet_flush(fd[1]);
 
 	while (ref) {
diff --git a/quote.c b/quote.c
index 5e6fda3..92e07f0 100644
--- a/quote.c
+++ b/quote.c
@@ -39,3 +39,167 @@
 	return buf;
 }
 
+/*
+ * C-style name quoting.
+ *
+ * Does one of three things:
+ *
+ * (1) if outbuf and outfp are both NULL, inspect the input name and
+ *     counts the number of bytes that are needed to hold c_style
+ *     quoted version of name, counting the double quotes around
+ *     it but not terminating NUL, and returns it.  However, if name
+ *     does not need c_style quoting, it returns 0.
+ *
+ * (2) if outbuf is not NULL, it must point at a buffer large enough
+ *     to hold the c_style quoted version of name, enclosing double
+ *     quotes, and terminating NUL.  Fills outbuf with c_style quoted
+ *     version of name enclosed in double-quote pair.  Return value
+ *     is undefined.
+ *
+ * (3) if outfp is not NULL, outputs c_style quoted version of name,
+ *     but not enclosed in double-quote pair.  Return value is undefined.
+ */
+
+int quote_c_style(const char *name, char *outbuf, FILE *outfp, int no_dq)
+{
+#undef EMIT
+#define EMIT(c) \
+	(outbuf ? (*outbuf++ = (c)) : outfp ? fputc(c, outfp) : (count++))
+
+#define EMITQ() EMIT('\\')
+
+	const char *sp;
+	int ch, count = 0, needquote = 0;
+
+	if (!no_dq)
+		EMIT('"');
+	for (sp = name; (ch = *sp++); ) {
+
+		if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
+		    (ch == 0177)) {
+			needquote = 1;
+			switch (ch) {
+			case '\a': EMITQ(); ch = 'a'; break;
+			case '\b': EMITQ(); ch = 'b'; break;
+			case '\f': EMITQ(); ch = 'f'; break;
+			case '\n': EMITQ(); ch = 'n'; break;
+			case '\r': EMITQ(); ch = 'r'; break;
+			case '\t': EMITQ(); ch = 't'; break;
+			case '\v': EMITQ(); ch = 'v'; break;
+
+			case '\\': /* fallthru */
+			case '"': EMITQ(); break;
+			case ' ':
+				break;
+			default:
+				/* octal */
+				EMITQ();
+				EMIT(((ch >> 6) & 03) + '0');
+				EMIT(((ch >> 3) & 07) + '0');
+				ch = (ch & 07) + '0';
+				break;
+			}
+		}
+		EMIT(ch);
+	}
+	if (!no_dq)
+		EMIT('"');
+	if (outbuf)
+		*outbuf = 0;
+
+	return needquote ? count : 0;
+}
+
+/*
+ * C-style name unquoting.
+ *
+ * Quoted should point at the opening double quote.  Returns
+ * an allocated memory that holds unquoted name, which the caller
+ * should free when done.  Updates endp pointer to point at
+ * one past the ending double quote if given.
+ */
+
+char *unquote_c_style(const char *quoted, const char **endp)
+{
+	const char *sp;
+	char *name = NULL, *outp = NULL;
+	int count = 0, ch, ac;
+
+#undef EMIT
+#define EMIT(c) (outp ? (*outp++ = (c)) : (count++))
+
+	if (*quoted++ != '"')
+		return NULL;
+
+	while (1) {
+		/* first pass counts and allocates, second pass fills */
+		for (sp = quoted; (ch = *sp++) != '"'; ) {
+			if (ch == '\\') {
+				switch (ch = *sp++) {
+				case 'a': ch = '\a'; break;
+				case 'b': ch = '\b'; break;
+				case 'f': ch = '\f'; break;
+				case 'n': ch = '\n'; break;
+				case 'r': ch = '\r'; break;
+				case 't': ch = '\t'; break;
+				case 'v': ch = '\v'; break;
+
+				case '\\': case '"':
+					break; /* verbatim */
+
+				case '0'...'7':
+					/* octal */
+					ac = ((ch - '0') << 6);
+					if ((ch = *sp++) < '0' || '7' < ch)
+						return NULL;
+					ac |= ((ch - '0') << 3);
+					if ((ch = *sp++) < '0' || '7' < ch)
+						return NULL;
+					ac |= (ch - '0');
+					ch = ac;
+					break;
+				default:
+					return NULL; /* malformed */
+				}
+			}
+			EMIT(ch);
+		}
+
+		if (name) {
+			*outp = 0;
+			if (endp)
+				*endp = sp;
+			return name;
+		}
+		outp = name = xmalloc(count + 1);
+	}
+}
+
+void write_name_quoted(const char *prefix, const char *name,
+		       int quote, FILE *out)
+{
+	int needquote;
+
+	if (!quote) {
+	no_quote:
+		if (prefix && prefix[0])
+			fputs(prefix, out);
+		fputs(name, out);
+		return;
+	}
+
+	needquote = 0;
+	if (prefix && prefix[0])
+		needquote = quote_c_style(prefix, NULL, NULL, 0);
+	if (!needquote)
+		needquote = quote_c_style(name, NULL, NULL, 0);
+	if (needquote) {
+		fputc('"', out);
+		if (prefix && prefix[0])
+			quote_c_style(prefix, NULL, out, 1);
+		quote_c_style(name, NULL, out, 1);
+		fputc('"', out);
+	}
+	else
+		goto no_quote;
+}
diff --git a/quote.h b/quote.h
index c8cfb3a..ea227bb 100644
--- a/quote.h
+++ b/quote.h
@@ -1,6 +1,7 @@
 #ifndef QUOTE_H
 #define QUOTE_H
 
+#include <stdio.h>
 
 /* Help to copy the thing properly quoted for the shell safety.
  * any single quote is replaced with '\'', and the whole thing
@@ -21,6 +22,12 @@
  * sq_quote() in a real application.
  */
 
-char *sq_quote(const char *src);
+extern char *sq_quote(const char *src);
 
+extern int quote_c_style(const char *name, char *outbuf, FILE *outfp,
+			 int nodq);
+extern char *unquote_c_style(const char *quoted, const char **endp);
+
+extern void write_name_quoted(const char *prefix, const char *name,
+			      int quote, FILE *out);
 #endif
diff --git a/receive-pack.c b/receive-pack.c
index 06857eb..8f157bc 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -95,6 +95,10 @@
 	char new_hex[60], *old_hex, *lock_name;
 	int newfd, namelen, written;
 
+	if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5))
+		return error("refusing to create funny ref '%s' locally",
+			     name);
+
 	namelen = strlen(name);
 	lock_name = xmalloc(namelen + 10);
 	memcpy(lock_name, name, namelen);
diff --git a/refs.c b/refs.c
index 5a8cbd4..d7f8dfd 100644
--- a/refs.c
+++ b/refs.c
@@ -335,17 +335,54 @@
 	return retval;
 }
 
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ */
+
+static inline int bad_ref_char(int ch)
+{
+	return (((unsigned) ch) <= ' ' ||
+		ch == '~' || ch == '^' || ch == ':');
+}
+
 int check_ref_format(const char *ref)
 {
-	char *middle;
-	if (ref[0] == '.' || ref[0] == '/')
-		return -1;
-	middle = strchr(ref, '/');
-	if (!middle || !middle[1])
-		return -1;
-	if (strchr(middle + 1, '/'))
-		return -1;
-	return 0;
+	int ch, level;
+	const char *cp = ref;
+
+	level = 0;
+	while (1) {
+		while ((ch = *cp++) == '/')
+			; /* tolerate duplicated slashes */
+		if (!ch)
+			return -1; /* should not end with slashes */
+
+		/* we are at the beginning of the path component */
+		if (ch == '.' || bad_ref_char(ch))
+			return -1;
+
+		/* scan the rest of the path component */
+		while ((ch = *cp++) != 0) {
+			if (bad_ref_char(ch))
+				return -1;
+			if (ch == '/')
+				break;
+			if (ch == '.' && *cp == '.')
+				return -1;
+		}
+		level++;
+		if (!ch) {
+			if (level < 2)
+				return -1; /* at least of form "heads/blah" */
+			return 0;
+		}
+	}
 }
 
 int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
diff --git a/rev-list.c b/rev-list.c
index c60aa72..3a32e40 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -624,6 +624,11 @@
 
 	if (!merge_order) {		
 		sort_by_date(&list);
+		if (list && !limited && max_count == 1 &&
+		    !tag_objects && !tree_objects && !blob_objects) {
+			show_commit(list->item);
+			return 0;
+		}
 	        if (limited)
 			list = limit_list(list);
 		if (topo_order)
diff --git a/send-pack.c b/send-pack.c
index 55d8ff7..9f9a6e7 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -181,7 +181,7 @@
 	int new_refs;
 
 	/* No funny business with the matcher */
-	remote_tail = get_remote_heads(in, &remote_refs, 0, NULL);
+	remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
 	get_local_heads();
 
 	/* match them up */
diff --git a/sha1_name.c b/sha1_name.c
index f64755f..d0896f8 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -1,5 +1,8 @@
 #include "cache.h"
+#include "tag.h"
 #include "commit.h"
+#include "tree.h"
+#include "blob.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -248,6 +251,82 @@
 	return 0;
 }
 
+static int peel_onion(const char *name, int len, unsigned char *sha1)
+{
+	unsigned char outer[20];
+	const char *sp;
+	const char *type_string = NULL;
+	struct object *o;
+
+	/*
+	 * "ref^{type}" dereferences ref repeatedly until you cannot
+	 * dereference anymore, or you get an object of given type,
+	 * whichever comes first.  "ref^{}" means just dereference
+	 * tags until you get a non-tag.  "ref^0" is a shorthand for
+	 * "ref^{commit}".  "commit^{tree}" could be used to find the
+	 * top-level tree of the given commit.
+	 */
+	if (len < 4 || name[len-1] != '}')
+		return -1;
+
+	for (sp = name + len - 1; name <= sp; sp--) {
+		int ch = *sp;
+		if (ch == '{' && name < sp && sp[-1] == '^')
+			break;
+	}
+	if (sp <= name)
+		return -1;
+
+	sp++; /* beginning of type name, or closing brace for empty */
+	if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
+		type_string = commit_type;
+	else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
+		type_string = tree_type;
+	else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
+		type_string = blob_type;
+	else if (sp[0] == '}')
+		type_string = NULL;
+	else
+		return -1;
+
+	if (get_sha1_1(name, sp - name - 2, outer))
+		return -1;
+
+	o = parse_object(outer);
+	if (!o)
+		return -1;
+	if (!type_string) {
+		o = deref_tag(o);
+		memcpy(sha1, o->sha1, 20);
+	}
+	else {
+		/* At this point, the syntax look correct, so
+		 * if we do not get the needed object, we should
+		 * barf.
+		 */
+
+		while (1) {
+			if (!o)
+				return -1;
+			if (o->type == type_string) {
+				memcpy(sha1, o->sha1, 20);
+				return 0;
+			}
+			if (o->type == tag_type)
+				o = ((struct tag*) o)->tagged;
+			else if (o->type == commit_type)
+				o = &(((struct commit *) o)->tree->object);
+			else
+				return error("%.*s: expected %s type, but the object dereferences to %s type",
+					     len, name, type_string,
+					     o->type);
+			if (!o->parsed)
+				parse_object(o->sha1);
+		}
+	}
+	return 0;
+}
+
 static int get_sha1_1(const char *name, int len, unsigned char *sha1)
 {
 	int parent, ret;
@@ -289,6 +368,10 @@
 		return get_nth_ancestor(name, len1, sha1, parent);
 	}
 
+	ret = peel_onion(name, len, sha1);
+	if (!ret)
+		return 0;
+
 	ret = get_sha1_basic(name, len, sha1);
 	if (!ret)
 		return 0;
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
deleted file mode 100644
index 35db799..0000000
--- a/t/t1200-tutorial.sh
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Johannes Schindelin
-#
-
-test_description='Test git-rev-parse with different parent options'
-
-. ./test-lib.sh
-
-echo "Hello World" > hello
-echo "Silly example" > example
-
-git-update-index --add hello example
-
-test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
-
-test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
-
-echo "It's a new day for git" >>hello
-cat > diff.expect << EOF
-diff --git a/hello b/hello
-index 557db03..263414f 100644
---- a/hello
-+++ b/hello
-@@ -1 +1,2 @@
- Hello World
-+It's a new day for git
-EOF
-git-diff-files -p > diff.output
-test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
-git diff > diff.output
-test_expect_success 'git diff' 'cmp diff.expect diff.output'
-
-tree=$(git-write-tree 2>/dev/null)
-
-test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
-
-output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
-
-test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
-
-git-diff-index -p HEAD > diff.output
-test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
-
-git diff HEAD > diff.output
-test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
-
-#rm hello
-#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
-
-cat > whatchanged.expect << EOF
-diff-tree VARIABLE (from root)
-Author: VARIABLE
-Date:   VARIABLE
-
-    Initial commit
-
-diff --git a/example b/example
-new file mode 100644
-index 0000000..f24c74a
---- /dev/null
-+++ b/example
-@@ -0,0 +1 @@
-+Silly example
-diff --git a/hello b/hello
-new file mode 100644
-index 0000000..557db03
---- /dev/null
-+++ b/hello
-@@ -0,0 +1 @@
-+Hello World
-EOF
-
-git-whatchanged -p --root | \
-	sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
-		-e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
-> whatchanged.output
-test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
-
-git tag my-first-tag
-test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
-
-# TODO: test git-clone
-
-git checkout -b mybranch
-test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
-
-cat > branch.expect <<EOF
-  master
-* mybranch
-EOF
-
-git branch > branch.output
-test_expect_success 'git branch' 'cmp branch.expect branch.output'
-
-git checkout mybranch
-echo "Work, work, work" >>hello
-git commit -m 'Some work.' hello
-
-git checkout master
-
-echo "Play, play, play" >>hello
-echo "Lots of fun" >>example
-git commit -m 'Some fun.' hello example
-
-test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
-
-cat > hello << EOF
-Hello World
-It's a new day for git
-Play, play, play
-Work, work, work
-EOF
-
-git commit -m 'Merged "mybranch" changes.' hello
-
-cat > show-branch.expect << EOF
-* [master] Merged "mybranch" changes.
- ! [mybranch] Some work.
---
-+  [master] Merged "mybranch" changes.
-++ [mybranch] Some work.
-EOF
-
-git show-branch master mybranch > show-branch.output
-test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
-
-git checkout mybranch
-
-cat > resolve.expect << EOF
-Updating from VARIABLE to VARIABLE.
- example |    1 +
- hello   |    1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
-EOF
-
-git resolve HEAD master "Merge upstream changes." | \
-	sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
-test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
-
-cat > show-branch2.expect << EOF
-! [master] Merged "mybranch" changes.
- * [mybranch] Merged "mybranch" changes.
---
-++ [master] Merged "mybranch" changes.
-EOF
-
-git show-branch master mybranch > show-branch2.output
-test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
-
-# TODO: test git fetch
-
-# TODO: test git push
-
-test_expect_success 'git repack' 'git repack'
-test_expect_success 'git prune-packed' 'git prune-packed'
-test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
-
-test_done
-
diff --git a/upload-pack.c b/upload-pack.c
index 83f5a35..80a5d09 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -2,13 +2,19 @@
 #include "refs.h"
 #include "pkt-line.h"
 
-static const char upload_pack_usage[] = "git-upload-pack <dir>";
+static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
 #define MAX_HAS (16)
 #define MAX_NEEDS (256)
 static int nr_has = 0, nr_needs = 0;
 static unsigned char has_sha1[MAX_HAS][20];
 static unsigned char needs_sha1[MAX_NEEDS][20];
+static unsigned int timeout = 0;
+
+static void reset_timeout(void)
+{
+	alarm(timeout);
+}
 
 static int strip(char *line, int len)
 {
@@ -98,6 +104,7 @@
 
 	for(;;) {
 		len = packet_read_line(0, line, sizeof(line));
+		reset_timeout();
 
 		if (!len) {
 			packet_write(1, "NAK\n");
@@ -120,6 +127,7 @@
 
 	for (;;) {
 		len = packet_read_line(0, line, sizeof(line));
+		reset_timeout();
 		if (!len)
 			continue;
 		len = strip(line, len);
@@ -143,6 +151,7 @@
 	for (;;) {
 		unsigned char dummy[20], *sha1_buf;
 		len = packet_read_line(0, line, sizeof(line));
+		reset_timeout();
 		if (!len)
 			return needs;
 
@@ -171,6 +180,7 @@
 
 static int upload_pack(void)
 {
+	reset_timeout();
 	head_ref(send_ref);
 	for_each_ref(send_ref);
 	packet_flush(1);
@@ -185,18 +195,43 @@
 int main(int argc, char **argv)
 {
 	const char *dir;
-	if (argc != 2)
+	int i;
+	int strict = 0;
+
+	for (i = 1; i < argc; i++) {
+		char *arg = argv[i];
+
+		if (arg[0] != '-')
+			break;
+		if (!strcmp(arg, "--strict")) {
+			strict = 1;
+			continue;
+		}
+		if (!strncmp(arg, "--timeout=", 10)) {
+			timeout = atoi(arg+10);
+			continue;
+		}
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+	}
+	
+	if (i != argc-1)
 		usage(upload_pack_usage);
-	dir = argv[1];
+	dir = argv[i];
 
 	/* chdir to the directory. If that fails, try appending ".git" */
 	if (chdir(dir) < 0) {
-		if (chdir(mkpath("%s.git", dir)) < 0)
+		if (strict || chdir(mkpath("%s.git", dir)) < 0)
 			die("git-upload-pack unable to chdir to %s", dir);
 	}
-	chdir(".git");
+	if (!strict)
+		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);
+
 	putenv("GIT_DIR=.");
 	upload_pack();
 	return 0;