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