Merge branch 'fixes'
authorJunio C Hamano <junkio@cox.net>
Fri, 21 Oct 2005 06:19:47 +0000 (23:19 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 21 Oct 2005 06:19:47 +0000 (23:19 -0700)
27 files changed:
Documentation/tutorial.txt
Makefile
apply.c
cache.h
check-ref-format.c [new file with mode: 0644]
clone-pack.c
connect.c
daemon.c
debian/changelog
fetch-pack.c
git-branch.sh
git-checkout.sh
git-clone.sh
git-cvsimport.perl
git-fetch.sh
git-parse-remote.sh
git-tag.sh
peek-remote.c
quote.c
quote.h
receive-pack.c
refs.c
rev-list.c
send-pack.c
sha1_name.c
t/t1200-tutorial.sh [deleted file]
upload-pack.c

index 6ecb089..36f42e0 100644 (file)
@@ -52,9 +52,7 @@ your new project. You will now have a `.git` directory, and you can
 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 @@ which will spit out
 
 ------------
 diff --git a/hello b/hello
-index 557db03..263414f 100644
 --- a/hello
 +++ b/hello
 @@ -1 +1,2 @@
@@ -293,16 +290,13 @@ also wants to get a commit message
 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 @@ other point in the history than the current `HEAD`, you can do so by
 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 @@ and check out the state at that time.
 
 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 @@ There will be asterisk in front of the branch you are currently on.
 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 @@ $ git show-branch master mybranch
  ! [mybranch] Some work.
 --
 +  [master] Merged "mybranch" changes.
++  [master~1] Some fun.
 ++ [mybranch] Some work.
 ------------------------------------------------
 
@@ -892,10 +871,8 @@ Now, let's pretend you are the one who did all the work in
 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 @@ i.e. `<project>.git`. Let's create such a public repository for
 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 @@ Your "public repository" is now ready to accept your changes.
 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 @@ As a real example, this is how I update my public git
 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 @@ not so convenient to transport over the network. Since git objects are
 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 @@ Our programs are always perfect ;-).
 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.
 
index e67d0e7..588e5b0 100644 (file)
--- 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 @@ PROGRAMS = \
        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 (file)
--- 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 @@ static char * find_name(const char *line, char *def, int p_value, int terminate)
        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 int gitdiff_hdrend(const char *line, struct patch *patch)
  */
 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 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
        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 @@ static int parse_git_header(char *line, int len, unsigned int size, struct patch
         * 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 @@ static void show_stats(struct patch *patch)
 {
        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 @@ static void show_stats(struct patch *patch)
        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 @@ static void patch_stats(struct patch *patch)
        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 (file)
--- a/cache.h
+++ b/cache.h
@@ -331,7 +331,7 @@ extern int path_match(const char *path, int nr, char **match);
 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 (file)
index 0000000..a0adb3d
--- /dev/null
@@ -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;
+}
index c102ca8..2ac35f6 100644 (file)
@@ -34,6 +34,12 @@ static void write_one_ref(struct ref *ref)
        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 @@ static int clone_pack(int fd[2], int nr_match, char **match)
        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");
index 825c439..e21d39a 100644 (file)
--- 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 @@ struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **ma
                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);
index cec7e75..bd278b0 100644 (file)
--- 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 @@ static char **ok_paths = NULL;
 /* 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 @@ static int path_ok(const char *dir)
 {
        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 @@ static int path_ok(const char *dir)
 
        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 @@ static int path_ok(const char *dir)
        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 @@ static int upload(char *dir, int dirlen)
         * 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 @@ static int upload(char *dir, int dirlen)
         */
        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 int execute(void)
        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 @@ int main(int argc, char **argv)
                        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;
index bebc191..0df3747 100644 (file)
@@ -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
index 582f967..969e72a 100644 (file)
@@ -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 const char *exec = "git-upload-pack";
 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 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        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 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        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 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
        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 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
                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);
index 074229c..e2db906 100755 (executable)
@@ -13,38 +13,42 @@ If two arguments, create a new branch <branchname> based off of <start-point>.
 }
 
 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 @@ while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
 do
        case "$1" in
        -d | -D)
-               delete_branch "$1" "$2"
+               delete_branch "$@"
                exit
                ;;
        --)
@@ -93,6 +97,9 @@ branchname="$1"
 
 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"
index c382590..2c053a3 100755 (executable)
@@ -17,6 +17,8 @@ while [ "$#" != "0" ]; do
                        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
index 7143131..18e692a 100755 (executable)
@@ -53,7 +53,11 @@ Perhaps git-update-server-info needs to be run there?"
        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"
 }
index f7c3a51..f00f759 100755 (executable)
@@ -487,7 +487,7 @@ unless($pid) {
        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);
index d398866..360fecd 100755 (executable)
@@ -170,7 +170,11 @@ esac
 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.
index 4d8a572..32f1085 100755 (executable)
@@ -94,6 +94,12 @@ canon_refs_list_for_fetch () {
                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
index 400bdb9..11b0492 100755 (executable)
@@ -46,6 +46,8 @@ if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
     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
index 4b1d0d5..ee49bf3 100644 (file)
@@ -11,7 +11,7 @@ static int peek_remote(int fd[2])
 {
        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 (file)
--- a/quote.c
+++ b/quote.c
@@ -39,3 +39,167 @@ char *sq_quote(const char *src)
        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 (file)
--- 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
  * 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
index 06857eb..8f157bc 100644 (file)
@@ -95,6 +95,10 @@ static int update(const char *name,
        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 (file)
--- a/refs.c
+++ b/refs.c
@@ -335,17 +335,54 @@ int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
        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)
index c60aa72..3a32e40 100644 (file)
@@ -624,6 +624,11 @@ int main(int argc, char **argv)
 
        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)
index 55d8ff7..9f9a6e7 100644 (file)
@@ -181,7 +181,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        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 */
index f64755f..d0896f8 100644 (file)
@@ -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 @@ static int get_nth_ancestor(const char *name, int len,
        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 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
                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 (file)
index 35db799..0000000
+++ /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
-
index 83f5a35..80a5d09 100644 (file)
@@ -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 @@ static int get_common_commits(void)
 
        for(;;) {
                len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
 
                if (!len) {
                        packet_write(1, "NAK\n");
@@ -120,6 +127,7 @@ static int get_common_commits(void)
 
        for (;;) {
                len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
                if (!len)
                        continue;
                len = strip(line, len);
@@ -143,6 +151,7 @@ static int receive_needs(void)
        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 send_ref(const char *refname, const unsigned char *sha1)
 
 static int upload_pack(void)
 {
+       reset_timeout();
        head_ref(send_ref);
        for_each_ref(send_ref);
        packet_flush(1);
@@ -185,18 +195,43 @@ static int upload_pack(void)
 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;