From: Junio C Hamano Date: Fri, 21 Oct 2005 06:19:47 +0000 (-0700) Subject: Merge branch 'fixes' X-Git-Tag: v0.99.8g~2 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/f6804930cae3ccf48b200264bdb7a25b4382d454?hp=a935c3972749095e7ea6c341e539a94de705ecfd Merge branch 'fixes' --- diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 6ecb089c5c..36f42e051c 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -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 [startingpoint] ------------- + git branch [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. `.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 :/path/to/my-git.git master ------------- + git push :/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. diff --git a/Makefile b/Makefile index e67d0e7822..588e5b0015 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 @@ 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 f8862722fd..c58d9a2491 100644 --- a/apply.c +++ b/apply.c @@ -15,6 +15,7 @@ #include #include #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 ec2a1610b2..201ce99089 100644 --- 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 index 0000000000..a0adb3dcb3 --- /dev/null +++ b/check-ref-format.c @@ -0,0 +1,17 @@ +/* + * GIT - The information manager from hell + */ + +#include "cache.h" +#include "refs.h" + +#include + +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 c102ca885b..2ac35f6825 100644 --- a/clone-pack.c +++ b/clone-pack.c @@ -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"); diff --git a/connect.c b/connect.c index 825c439acc..e21d39a357 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 #include #include @@ -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); diff --git a/daemon.c b/daemon.c index cec7e75d5e..bd278b01c1 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 @@ 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; diff --git a/debian/changelog b/debian/changelog index bebc1919ba..0df374767d 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 Wed, 19 Oct 2005 02:29:24 -0700 + +git-core (0.99.8e-0) unstable; urgency=low + + * GIT 0.99.8e + + -- Junio C Hamano Mon, 17 Oct 2005 17:45:08 -0700 + +git-core (0.99.8d-0) unstable; urgency=low + + * GIT 0.99.8d + + -- Junio C Hamano Sat, 15 Oct 2005 17:22:58 -0700 + +git-core (0.99.8c-0) unstable; urgency=low + + * GIT 0.99.8c + + -- Junio C Hamano Sun, 9 Oct 2005 19:19:16 -0700 + +git-core (0.99.8b-0) unstable; urgency=low + + * GIT 0.99.8b + + -- Junio C Hamano Wed, 5 Oct 2005 15:41:24 -0700 + +git-core (0.99.8a-0) unstable; urgency=low + + * GIT 0.99.8a + + -- Junio C Hamano 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 582f967a7a..969e72a781 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 #include 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); diff --git a/git-branch.sh b/git-branch.sh index 074229c206..e2db9063d4 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -13,38 +13,42 @@ If two arguments, create a new branch based off of . } 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" diff --git a/git-checkout.sh b/git-checkout.sh index c3825904b6..2c053a33c3 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -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 diff --git a/git-clone.sh b/git-clone.sh index 71431319c0..18e692a67b 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -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" } diff --git a/git-cvsimport.perl b/git-cvsimport.perl index f7c3a51593..f00f7596b7 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 d3988660ff..360fecdd4e 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -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. diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 4d8a572a99..32f10855b6 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -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 diff --git a/git-tag.sh b/git-tag.sh index 400bdb9843..11b0492ba8 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -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 diff --git a/peek-remote.c b/peek-remote.c index 4b1d0d5ba8..ee49bf3b7b 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -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 5e6fda311c..92e07f070f 100644 --- 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 c8cfb3a124..ea227bb7d4 100644 --- a/quote.h +++ b/quote.h @@ -1,6 +1,7 @@ #ifndef QUOTE_H #define QUOTE_H +#include /* 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 06857eb77f..8f157bc3f0 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -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 5a8cbd4ef3..d7f8dfddf4 100644 --- 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) diff --git a/rev-list.c b/rev-list.c index c60aa72957..3a32e405a3 100644 --- a/rev-list.c +++ b/rev-list.c @@ -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) diff --git a/send-pack.c b/send-pack.c index 55d8ff7e10..9f9a6e70b8 100644 --- a/send-pack.c +++ b/send-pack.c @@ -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 */ diff --git a/sha1_name.c b/sha1_name.c index f64755fbce..d0896f8183 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 @@ 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 index 35db799edf..0000000000 --- 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 < 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 83f5a35d26..80a5d0925a 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 "; +static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] "; #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;