summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/core-tutorial.txt2
-rw-r--r--Documentation/cvs-migration.txt2
-rw-r--r--Documentation/git-apply.txt13
-rw-r--r--Documentation/git-grep.txt9
-rw-r--r--Documentation/git-unpack-objects.txt8
-rw-r--r--Documentation/tutorial-2.txt2
-rw-r--r--Makefile4
-rw-r--r--builtin-add.c1
-rw-r--r--builtin-apply.c131
-rw-r--r--builtin-grep.c15
-rw-r--r--builtin-unpack-objects.c61
-rw-r--r--builtin.h3
-rw-r--r--cache.h2
-rw-r--r--connect.c30
-rw-r--r--contrib/vim/README8
-rw-r--r--contrib/vim/syntax/gitcommit.vim18
-rw-r--r--describe.c4
-rw-r--r--diff.c8
-rw-r--r--fetch-pack.c4
-rwxr-xr-xgit-clone.sh2
-rw-r--r--git-compat-util.h7
-rwxr-xr-xgit-fetch.sh4
-rwxr-xr-xgit-ls-remote.sh2
-rw-r--r--git.c4
-rwxr-xr-xgitweb/gitweb.perl299
-rw-r--r--http-fetch.c39
-rw-r--r--merge-tree.c4
-rw-r--r--path.c10
-rw-r--r--peek-remote.c4
-rw-r--r--quote.c29
-rw-r--r--quote.h6
-rw-r--r--rsh.c31
-rw-r--r--send-pack.c109
-rwxr-xr-xt/t1400-update-ref.sh86
-rwxr-xr-xt/t3403-rebase-skip.sh4
-rwxr-xr-xt/t4103-apply-binary.sh4
-rwxr-xr-xt/t4104-apply-boundary.sh115
-rw-r--r--trace.c2
38 files changed, 783 insertions, 303 deletions
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index 1185897f70..47505aa20a 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -1620,7 +1620,7 @@ suggested in the previous section may be new to you. You do not
have to worry. git supports "shared public repository" style of
cooperation you are probably more familiar with as well.
-See link:cvs-migration.txt[git for CVS users] for the details.
+See link:cvs-migration.html[git for CVS users] for the details.
Bundling your work together
---------------------------
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
index d2b0bd38de..6812683a16 100644
--- a/Documentation/cvs-migration.txt
+++ b/Documentation/cvs-migration.txt
@@ -172,7 +172,7 @@ Advanced Shared Repository Management
Git allows you to specify scripts called "hooks" to be run at certain
points. You can use these, for example, to send all commits to the shared
-repository to a mailing list. See link:hooks.txt[Hooks used by git].
+repository to a mailing list. See link:hooks.html[Hooks used by git].
You can enforce finer grained permissions using update hooks. See
link:howto/update-hook-example.txt[Controlling access to branches using
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index c76cfffdc6..0a6f7b3219 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -110,15 +110,10 @@ OPTIONS
deletion part but not addition part.
--allow-binary-replacement, --binary::
- When applying a patch, which is a git-enhanced patch
- that was prepared to record the pre- and post-image object
- name in full, and the path being patched exactly matches
- the object the patch applies to (i.e. "index" line's
- pre-image object name is what is in the working tree),
- and the post-image object is available in the object
- database, use the post-image object as the patch
- result. This allows binary files to be patched in a
- very limited way.
+ Historically we did not allow binary patch applied
+ without an explicit permission from the user, and this
+ flag was the way to do so. Currently we always allow binary
+ patch application, so this is a no-op.
--exclude=<path-pattern>::
Don't apply changes to files matching the given path pattern. This can
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 7545dd9a3e..d8af4d961b 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git-grep' [--cached]
[-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
- [-v | --invert-match] [--full-name]
+ [-v | --invert-match] [-h|-H] [--full-name]
[-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
[-n] [-l | --files-with-matches] [-L | --files-without-match]
[-c | --count]
@@ -47,6 +47,13 @@ OPTIONS
-v | --invert-match::
Select non-matching lines.
+-h | -H::
+ By default, the command shows the filename for each
+ match. `-h` option is used to suppress this output.
+ `-H` is there for completeness and does not do anything
+ except it overrides `-h` given earlier on the command
+ line.
+
--full-name::
When run from a subdirectory, the command usually
outputs paths relative to the current directory. This
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
index c20b38b08a..ff6184b0f7 100644
--- a/Documentation/git-unpack-objects.txt
+++ b/Documentation/git-unpack-objects.txt
@@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive
SYNOPSIS
--------
-'git-unpack-objects' [-n] [-q] <pack-file
+'git-unpack-objects' [-n] [-q] [-r] <pack-file
DESCRIPTION
@@ -34,6 +34,12 @@ OPTIONS
The command usually shows percentage progress. This
flag suppresses it.
+-r::
+ When unpacking a corrupt packfile, the command dies at
+ the first corruption. This flag tells it to keep going
+ and make the best effort to recover as many objects as
+ possible.
+
Author
------
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
index 2f4fe1217a..42b6e7d7d2 100644
--- a/Documentation/tutorial-2.txt
+++ b/Documentation/tutorial-2.txt
@@ -368,7 +368,7 @@ in the index file is identical to the one in the working directory.
In addition to being the staging area for new commits, the index file
is also populated from the object database when checking out a
branch, and is used to hold the trees involved in a merge operation.
-See the link:core-tutorial.txt[core tutorial] and the relevant man
+See the link:core-tutorial.html[core tutorial] and the relevant man
pages for details.
What next?
diff --git a/Makefile b/Makefile
index c724b481a4..b98745045c 100644
--- a/Makefile
+++ b/Makefile
@@ -126,6 +126,8 @@ GITWEB_CONFIG = gitweb_config.perl
GITWEB_HOME_LINK_STR = projects
GITWEB_SITENAME =
GITWEB_PROJECTROOT = /pub/git
+GITWEB_EXPORT_OK =
+GITWEB_STRICT_EXPORT =
GITWEB_BASE_URL =
GITWEB_LIST =
GITWEB_HOMETEXT = indextext.html
@@ -633,6 +635,8 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
-e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \
-e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \
-e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \
+ -e 's|++GITWEB_EXPORT_OK++|$(GITWEB_EXPORT_OK)|g' \
+ -e 's|++GITWEB_STRICT_EXPORT++|$(GITWEB_STRICT_EXPORT)|g' \
-e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \
-e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \
-e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \
diff --git a/builtin-add.c b/builtin-add.c
index 0cb9c81200..febb75ed99 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -70,7 +70,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
base = "";
if (baselen) {
char *common = xmalloc(baselen + 1);
- common = xmalloc(baselen + 1);
memcpy(common, *pathspec, baselen);
common[baselen] = 0;
path = base = common;
diff --git a/builtin-apply.c b/builtin-apply.c
index 872c8005a2..25e90d8d29 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -27,8 +27,8 @@ static const char *prefix;
static int prefix_length = -1;
static int newfd = -1;
+static int unidiff_zero;
static int p_value = 1;
-static int allow_binary_replacement;
static int check_index;
static int write_index;
static int cached;
@@ -855,11 +855,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
}
/*
- * Parse a unified diff. Note that this really needs
- * to parse each fragment separately, since the only
- * way to know the difference between a "---" that is
- * part of a patch, and a "---" that starts the next
- * patch is to look at the line counts..
+ * Parse a unified diff. Note that this really needs to parse each
+ * fragment separately, since the only way to know the difference
+ * between a "---" that is part of a patch, and a "---" that starts
+ * the next patch is to look at the line counts..
*/
static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
{
@@ -876,31 +875,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
leading = 0;
trailing = 0;
- if (patch->is_new < 0) {
- patch->is_new = !oldlines;
- if (!oldlines)
- patch->old_name = NULL;
- }
- if (patch->is_delete < 0) {
- patch->is_delete = !newlines;
- if (!newlines)
- patch->new_name = NULL;
- }
-
- if (patch->is_new && oldlines)
- return error("new file depends on old contents");
- if (patch->is_delete != !newlines) {
- if (newlines)
- return error("deleted file still has contents");
- fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
- }
-
/* Parse the thing.. */
line += len;
size -= len;
linenr++;
added = deleted = 0;
- for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+ for (offset = len;
+ 0 < size;
+ offset += len, size -= len, line += len, linenr++) {
if (!oldlines && !newlines)
break;
len = linelen(line, size);
@@ -973,12 +955,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
patch->lines_added += added;
patch->lines_deleted += deleted;
+
+ if (0 < patch->is_new && oldlines)
+ return error("new file depends on old contents");
+ if (0 < patch->is_delete && newlines)
+ return error("deleted file still has contents");
return offset;
}
static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
+ unsigned long oldlines = 0, newlines = 0, context = 0;
struct fragment **fragp = &patch->fragments;
while (size > 4 && !memcmp(line, "@@ -", 4)) {
@@ -989,9 +977,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
die("corrupt patch at line %d", linenr);
-
fragment->patch = line;
fragment->size = len;
+ oldlines += fragment->oldlines;
+ newlines += fragment->newlines;
+ context += fragment->leading + fragment->trailing;
*fragp = fragment;
fragp = &fragment->next;
@@ -1000,6 +990,46 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
line += len;
size -= len;
}
+
+ /*
+ * If something was removed (i.e. we have old-lines) it cannot
+ * be creation, and if something was added it cannot be
+ * deletion. However, the reverse is not true; --unified=0
+ * patches that only add are not necessarily creation even
+ * though they do not have any old lines, and ones that only
+ * delete are not necessarily deletion.
+ *
+ * Unfortunately, a real creation/deletion patch do _not_ have
+ * any context line by definition, so we cannot safely tell it
+ * apart with --unified=0 insanity. At least if the patch has
+ * more than one hunk it is not creation or deletion.
+ */
+ if (patch->is_new < 0 &&
+ (oldlines || (patch->fragments && patch->fragments->next)))
+ patch->is_new = 0;
+ if (patch->is_delete < 0 &&
+ (newlines || (patch->fragments && patch->fragments->next)))
+ patch->is_delete = 0;
+ if (!unidiff_zero || context) {
+ /* If the user says the patch is not generated with
+ * --unified=0, or if we have seen context lines,
+ * then not having oldlines means the patch is creation,
+ * and not having newlines means the patch is deletion.
+ */
+ if (patch->is_new < 0 && !oldlines)
+ patch->is_new = 1;
+ if (patch->is_delete < 0 && !newlines)
+ patch->is_delete = 1;
+ }
+
+ if (0 < patch->is_new && oldlines)
+ die("new file %s depends on old contents", patch->new_name);
+ if (0 < patch->is_delete && newlines)
+ die("deleted file %s still has contents", patch->old_name);
+ if (!patch->is_delete && !newlines && context)
+ fprintf(stderr, "** warning: file %s becomes empty but "
+ "is not deleted\n", patch->new_name);
+
return offset;
}
@@ -1228,14 +1258,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
}
}
- /* Empty patch cannot be applied if:
- * - it is a binary patch and we do not do binary_replace, or
- * - text patch without metadata change
+ /* Empty patch cannot be applied if it is a text patch
+ * without metadata change. A binary patch appears
+ * empty to us here.
*/
if ((apply || check) &&
- (patch->is_binary
- ? !allow_binary_replacement
- : !metadata_changes(patch)))
+ (!patch->is_binary && !metadata_changes(patch)))
die("patch with only garbage at line %d", linenr);
}
@@ -1559,9 +1587,19 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
/*
* If we don't have any leading/trailing data in the patch,
* we want it to match at the beginning/end of the file.
+ *
+ * But that would break if the patch is generated with
+ * --unified=0; sane people wouldn't do that to cause us
+ * trouble, but we try to please not so sane ones as well.
*/
- match_beginning = !leading && (frag->oldpos == 1);
- match_end = !trailing;
+ if (unidiff_zero) {
+ match_beginning = (!leading && !frag->oldpos);
+ match_end = 0;
+ }
+ else {
+ match_beginning = !leading && (frag->oldpos == 1);
+ match_end = !trailing;
+ }
lines = 0;
pos = frag->newpos;
@@ -1676,11 +1714,6 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch)
unsigned char hdr[50];
int hdrlen;
- if (!allow_binary_replacement)
- return error("cannot apply binary patch to '%s' "
- "without --allow-binary-replacement",
- name);
-
/* For safety, we require patch index line to contain
* full 40-byte textual SHA1 for old and new, at least for now.
*/
@@ -1812,7 +1845,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
patch->result = desc.buffer;
patch->resultsize = desc.size;
- if (patch->is_delete && patch->resultsize)
+ if (0 < patch->is_delete && patch->resultsize)
return error("removal patch leaves file contents");
return 0;
@@ -1884,7 +1917,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
old_name, st_mode, patch->old_mode);
}
- if (new_name && prev_patch && prev_patch->is_delete &&
+ if (new_name && prev_patch && 0 < prev_patch->is_delete &&
!strcmp(prev_patch->old_name, new_name))
/* A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
@@ -1897,7 +1930,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
else
ok_if_exists = 0;
- if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+ if (new_name &&
+ ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
if (check_index &&
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
!ok_if_exists)
@@ -1914,7 +1948,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
return error("%s: %s", new_name, strerror(errno));
}
if (!patch->new_mode) {
- if (patch->is_new)
+ if (0 < patch->is_new)
patch->new_mode = S_IFREG | 0644;
else
patch->new_mode = patch->old_mode;
@@ -1965,7 +1999,7 @@ static void show_index_list(struct patch *list)
const char *name;
name = patch->old_name ? patch->old_name : patch->new_name;
- if (patch->is_new)
+ if (0 < patch->is_new)
sha1_ptr = null_sha1;
else if (get_sha1(patch->old_sha1_prefix, sha1))
die("sha1 information is lacking or useless (%s).",
@@ -2497,8 +2531,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
}
if (!strcmp(arg, "--allow-binary-replacement") ||
!strcmp(arg, "--binary")) {
- allow_binary_replacement = 1;
- continue;
+ continue; /* now no-op */
}
if (!strcmp(arg, "--numstat")) {
apply = 0;
@@ -2552,6 +2585,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
apply_in_reverse = 1;
continue;
}
+ if (!strcmp(arg, "--unidiff-zero")) {
+ unidiff_zero = 1;
+ continue;
+ }
if (!strcmp(arg, "--reject")) {
apply = apply_with_reject = apply_verbosely = 1;
continue;
diff --git a/builtin-grep.c b/builtin-grep.c
index 6430f6d79e..ed87a5550c 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -138,6 +138,7 @@ struct grep_opt {
unsigned binary:2;
unsigned extended:1;
unsigned relative:1;
+ unsigned pathname:1;
int regflags;
unsigned pre_context;
unsigned post_context;
@@ -316,7 +317,8 @@ static int word_char(char ch)
static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
const char *name, unsigned lno, char sign)
{
- printf("%s%c", name, sign);
+ if (opt->pathname)
+ printf("%s%c", name, sign);
if (opt->linenum)
printf("%d%c", lno, sign);
printf("%.*s\n", (int)(eol-bol), bol);
@@ -691,6 +693,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
push_arg("-F");
if (opt->linenum)
push_arg("-n");
+ if (!opt->pathname)
+ push_arg("-h");
if (opt->regflags & REG_EXTENDED)
push_arg("-E");
if (opt->regflags & REG_ICASE)
@@ -911,6 +915,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0;
opt.relative = 1;
+ opt.pathname = 1;
opt.pattern_tail = &opt.pattern_list;
opt.regflags = REG_NEWLINE;
@@ -970,10 +975,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
opt.linenum = 1;
continue;
}
+ if (!strcmp("-h", arg)) {
+ opt.pathname = 0;
+ continue;
+ }
if (!strcmp("-H", arg)) {
- /* We always show the pathname, so this
- * is a noop.
- */
+ opt.pathname = 1;
continue;
}
if (!strcmp("-l", arg) ||
diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c
index 0c180b53a3..4f96bcae32 100644
--- a/builtin-unpack-objects.c
+++ b/builtin-unpack-objects.c
@@ -10,8 +10,8 @@
#include <sys/time.h>
-static int dry_run, quiet;
-static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file";
+static int dry_run, quiet, recover, has_errors;
+static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
/* We always read in 4kB chunks. */
static unsigned char buffer[4096];
@@ -71,8 +71,15 @@ static void *get_data(unsigned long size)
use(len - stream.avail_in);
if (stream.total_out == size && ret == Z_STREAM_END)
break;
- if (ret != Z_OK)
- die("inflate returned %d\n", ret);
+ if (ret != Z_OK) {
+ error("inflate returned %d\n", ret);
+ free(buf);
+ buf = NULL;
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ break;
+ }
stream.next_in = fill(1);
stream.avail_in = len;
}
@@ -110,9 +117,9 @@ static void write_object(void *buf, unsigned long size, const char *type)
added_object(sha1, type, buf, size);
}
-static int resolve_delta(const char *type,
- void *base, unsigned long base_size,
- void *delta, unsigned long delta_size)
+static void resolve_delta(const char *type,
+ void *base, unsigned long base_size,
+ void *delta, unsigned long delta_size)
{
void *result;
unsigned long result_size;
@@ -125,7 +132,6 @@ static int resolve_delta(const char *type,
free(delta);
write_object(result, result_size, type);
free(result);
- return 0;
}
static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
@@ -145,7 +151,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
}
}
-static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type kind, unsigned long size)
{
void *buf = get_data(size);
const char *type;
@@ -157,39 +163,42 @@ static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
case OBJ_TAG: type = tag_type; break;
default: die("bad type %d", kind);
}
- if (!dry_run)
+ if (!dry_run && buf)
write_object(buf, size, type);
free(buf);
- return 0;
}
-static int unpack_delta_entry(unsigned long delta_size)
+static void unpack_delta_entry(unsigned long delta_size)
{
void *delta_data, *base;
unsigned long base_size;
char type[20];
unsigned char base_sha1[20];
- int result;
hashcpy(base_sha1, fill(20));
use(20);
delta_data = get_data(delta_size);
- if (dry_run) {
+ if (dry_run || !delta_data) {
free(delta_data);
- return 0;
+ return;
}
if (!has_sha1_file(base_sha1)) {
add_delta_to_list(base_sha1, delta_data, delta_size);
- return 0;
+ return;
}
base = read_sha1_file(base_sha1, type, &base_size);
- if (!base)
- die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
- result = resolve_delta(type, base, base_size, delta_data, delta_size);
+ if (!base) {
+ error("failed to read delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+ if (!recover)
+ exit(1);
+ has_errors = 1;
+ return;
+ }
+ resolve_delta(type, base, base_size, delta_data, delta_size);
free(base);
- return result;
}
static void unpack_one(unsigned nr, unsigned total)
@@ -236,7 +245,11 @@ static void unpack_one(unsigned nr, unsigned total)
unpack_delta_entry(size);
return;
default:
- die("bad object type %d", type);
+ error("bad object type %d", type);
+ has_errors = 1;
+ if (recover)
+ return;
+ exit(1);
}
}
@@ -280,6 +293,10 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
quiet = 1;
continue;
}
+ if (!strcmp(arg, "-r")) {
+ recover = 1;
+ continue;
+ }
usage(unpack_usage);
}
@@ -306,5 +323,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
/* All done */
if (!quiet)
fprintf(stderr, "\n");
- return 0;
+ return has_errors;
}
diff --git a/builtin.h b/builtin.h
index 6bfd2e79f5..37a8c875a8 100644
--- a/builtin.h
+++ b/builtin.h
@@ -1,8 +1,7 @@
#ifndef BUILTIN_H
#define BUILTIN_H
-#include <stdio.h>
-#include <limits.h>
+#include "git-compat-util.h"
extern const char git_version_string[];
extern const char git_usage_string[];
diff --git a/cache.h b/cache.h
index a53204f6d6..8d099979d9 100644
--- a/cache.h
+++ b/cache.h
@@ -359,7 +359,7 @@ struct ref {
#define REF_HEADS (1u << 1)
#define REF_TAGS (1u << 2)
-extern int git_connect(int fd[2], char *url, const char *prog);
+extern pid_t git_connect(int fd[2], char *url, const char *prog);
extern int finish_connect(pid_t pid);
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,
diff --git a/connect.c b/connect.c
index 1c6429bd51..c55a20a4aa 100644
--- a/connect.c
+++ b/connect.c
@@ -599,12 +599,19 @@ static void git_proxy_connect(int fd[2], char *host)
close(pipefd[1][0]);
}
+#define MAX_CMD_LEN 1024
+
/*
- * Yeah, yeah, fixme. Need to pass in the heads etc.
+ * This returns 0 if the transport protocol does not need fork(2),
+ * or a process id if it does. Once done, finish the connection
+ * with finish_connect() with the value returned from this function
+ * (it is safe to call finish_connect() with 0 to support the former
+ * case).
+ *
+ * Does not return a negative value on error; it just dies.
*/
-int git_connect(int fd[2], char *url, const char *prog)
+pid_t git_connect(int fd[2], char *url, const char *prog)
{
- char command[1024];
char *host, *path = url;
char *end;
int c;
@@ -697,8 +704,18 @@ int git_connect(int fd[2], char *url, const char *prog)
if (pid < 0)
die("unable to fork");
if (!pid) {
- snprintf(command, sizeof(command), "%s %s", prog,
- sq_quote(path));
+ char command[MAX_CMD_LEN];
+ char *posn = command;
+ int size = MAX_CMD_LEN;
+ int of = 0;
+
+ of |= add_to_string(&posn, &size, prog, 0);
+ of |= add_to_string(&posn, &size, " ", 0);
+ of |= add_to_string(&posn, &size, path, 1);
+
+ if (of)
+ die("command line too long");
+
dup2(pipefd[1][0], 0);
dup2(pipefd[0][1], 1);
close(pipefd[0][0]);
@@ -737,6 +754,9 @@ int git_connect(int fd[2], char *url, const char *prog)
int finish_connect(pid_t pid)
{
+ if (pid == 0)
+ return 0;
+
while (waitpid(pid, NULL, 0) < 0) {
if (errno != EINTR)
return -1;
diff --git a/contrib/vim/README b/contrib/vim/README
new file mode 100644
index 0000000000..9e7881fea9
--- /dev/null
+++ b/contrib/vim/README
@@ -0,0 +1,8 @@
+To syntax highlight git's commit messages, you need to:
+ 1. Copy syntax/gitcommit.vim to vim's syntax directory:
+ $ mkdir -p $HOME/.vim/syntax
+ $ cp syntax/gitcommit.vim $HOME/.vim/syntax
+ 2. Auto-detect the editing of git commit files:
+ $ cat >>$HOME/.vimrc <<'EOF'
+ autocmd BufNewFile,BufRead COMMIT_EDITMSG set filetype=gitcommit
+ EOF
diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim
new file mode 100644
index 0000000000..a9de09fa2f
--- /dev/null
+++ b/contrib/vim/syntax/gitcommit.vim
@@ -0,0 +1,18 @@
+syn region gitLine start=/^#/ end=/$/
+syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile
+syn region gitHead contained start=/^# (.*)/ end=/^#$/
+syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
+syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
+
+syn match gitCommitFile contained /^#\t.*/hs=s+2
+syn match gitChangedFile contained /^#\t.*/hs=s+2
+syn match gitUntrackedFile contained /^#\t.*/hs=s+2
+
+hi def link gitLine Comment
+hi def link gitCommit Comment
+hi def link gitChanged Comment
+hi def link gitHead Comment
+hi def link gitUntracked Comment
+hi def link gitCommitFile Type
+hi def link gitChangedFile Constant
+hi def link gitUntrackedFile Constant
diff --git a/describe.c b/describe.c
index 5dd8b2e397..ab192f83ae 100644
--- a/describe.c
+++ b/describe.c
@@ -161,7 +161,9 @@ int main(int argc, char **argv)
usage(describe_usage);
}
- if (i == argc)
+ setup_git_directory();
+
+ if (argc <= i)
describe("HEAD", 1);
else
while (i < argc) {
diff --git a/diff.c b/diff.c
index 9dcbda3117..6638865709 100644
--- a/diff.c
+++ b/diff.c
@@ -1588,6 +1588,12 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
if (hashcmp(one->sha1, two->sha1)) {
int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+ if (o->binary) {
+ mmfile_t mf;
+ if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
+ (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
+ abbrev = 40;
+ }
len += snprintf(msg + len, sizeof(msg) - len,
"index %.*s..%.*s",
abbrev, sha1_to_hex(one->sha1),
@@ -1818,7 +1824,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->full_index = 1;
else if (!strcmp(arg, "--binary")) {
options->output_format |= DIFF_FORMAT_PATCH;
- options->full_index = options->binary = 1;
+ options->binary = 1;
}
else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) {
options->text = 1;
diff --git a/fetch-pack.c b/fetch-pack.c
index 1b2d6ee20d..e8708aa802 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -525,7 +525,7 @@ int main(int argc, char **argv)
ret = fetch_pack(fd, nr_heads, heads);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
+ ret |= finish_connect(pid);
if (!ret && nr_heads) {
/* If the heads to pull were given, we should have
@@ -540,5 +540,5 @@ int main(int argc, char **argv)
}
}
- return ret;
+ return !!ret;
}
diff --git a/git-clone.sh b/git-clone.sh
index 7060bdab01..e1b3bf382f 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -298,7 +298,7 @@ yes,yes)
fi
git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
;;
- https://*|http://*)
+ https://*|http://*|ftp://*)
if test -z "@@NO_CURL@@"
then
clone_dumb_http "$repo" "$D"
diff --git a/git-compat-util.h b/git-compat-util.h
index 552b8ec23a..0272d043d0 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -26,6 +26,13 @@
#include <sys/types.h>
#include <dirent.h>
+/* On most systems <limits.h> would have given us this, but
+ * not on some systems (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#else
diff --git a/git-fetch.sh b/git-fetch.sh
index c2eebee798..09a5d6ceab 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -286,7 +286,7 @@ fetch_main () {
# There are transports that can fetch only one head at a time...
case "$remote" in
- http://* | https://*)
+ http://* | https://* | ftp://*)
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
@@ -350,7 +350,7 @@ fetch_main () {
done
case "$remote" in
- http://* | https://* | rsync://* )
+ http://* | https://* | ftp://* | rsync://* )
;; # we are already done.
*)
( : subshell because we muck with IFS
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 2fdcaf7886..2c0b52122f 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -49,7 +49,7 @@ trap "rm -fr $tmp-*" 0 1 2 3 15
tmpdir=$tmp-d
case "$peek_repo" in
-http://* | https://* )
+http://* | https://* | ftp://* )
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
diff --git a/git.c b/git.c
index bcf3fc8444..70cafb0262 100644
--- a/git.c
+++ b/git.c
@@ -225,7 +225,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
- { "count-objects", cmd_count_objects },
+ { "count-objects", cmd_count_objects, RUN_SETUP },
{ "diff", cmd_diff, RUN_SETUP },
{ "diff-files", cmd_diff_files, RUN_SETUP },
{ "diff-index", cmd_diff_index, RUN_SETUP },
@@ -296,7 +296,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
int main(int argc, const char **argv, char **envp)
{
- const char *cmd = argv[0];
+ const char *cmd = argv[0] ? argv[0] : "git-help";
char *slash = strrchr(cmd, '/');
const char *exec_path = NULL;
int done_alias = 0;
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index d89f709d13..497129aefc 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -54,6 +54,13 @@ our $favicon = "++GITWEB_FAVICON++";
# source of projects list
our $projects_list = "++GITWEB_LIST++";
+# show repository only if this file exists
+# (only effective if this variable evaluates to true)
+our $export_ok = "++GITWEB_EXPORT_OK++";
+
+# only allow viewing of repositories also shown on the overview page
+our $strict_export = "++GITWEB_STRICT_EXPORT++";
+
# list of git base URLs used for URL to where fetch project from,
# i.e. full URL is "$git_base_url/$project"
our @git_base_url_list = ("++GITWEB_BASE_URL++");
@@ -90,6 +97,11 @@ our %feature = (
'override' => 0,
# => [content-encoding, suffix, program]
'default' => ['x-gzip', 'gz', 'gzip']},
+
+ 'pickaxe' => {
+ 'sub' => \&feature_pickaxe,
+ 'override' => 0,
+ 'default' => [1]},
);
sub gitweb_check_feature {
@@ -143,6 +155,24 @@ sub feature_snapshot {
return ($ctype, $suffix, $command);
}
+# To enable system wide have in $GITWEB_CONFIG
+# $feature{'pickaxe'}{'default'} = [1];
+# To have project specific config enable override in $GITWEB_CONFIG
+# $feature{'pickaxe'}{'override'} = 1;
+# and in project config gitweb.pickaxe = 0|1;
+
+sub feature_pickaxe {
+ my ($val) = git_get_project_config('pickaxe', '--bool');
+
+ if ($val eq 'true') {
+ return (1);
+ } elsif ($val eq 'false') {
+ return (0);
+ }
+
+ return ($_[0]);
+}
+
# rename detection options for git-diff and git-diff-tree
# - default is '-M', with the cost proportional to
# (number of removed files) * (number of new files).
@@ -173,23 +203,16 @@ if (defined $action) {
}
}
-our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
+our $project = $cgi->param('p');
if (defined $project) {
- $project =~ s|^/||;
- $project =~ s|/$||;
- $project = undef unless $project;
-}
-if (defined $project) {
- if (!validate_input($project)) {
- die_error(undef, "Invalid project parameter");
- }
- if (!(-d "$projectroot/$project")) {
- die_error(undef, "No such directory");
- }
- if (!(-e "$projectroot/$project/HEAD")) {
+ if (!validate_input($project) ||
+ !(-d "$projectroot/$project") ||
+ !(-e "$projectroot/$project/HEAD") ||
+ ($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
+ ($strict_export && !project_in_list($project))) {
+ undef $project;
die_error(undef, "No such project");
}
- $git_dir = "$projectroot/$project";
}
our $file_name = $cgi->param('f');
@@ -249,6 +272,32 @@ if (defined $searchtext) {
$searchtext = quotemeta $searchtext;
}
+# now read PATH_INFO and use it as alternative to parameters
+our $path_info = $ENV{"PATH_INFO"};
+$path_info =~ s|^/||;
+$path_info =~ s|/$||;
+if (validate_input($path_info) && !defined $project) {
+ $project = $path_info;
+ while ($project && !-e "$projectroot/$project/HEAD") {
+ $project =~ s,/*[^/]*$,,;
+ }
+ if (defined $project) {
+ $project = undef unless $project;
+ }
+ if ($path_info =~ m,^$project/([^/]+)/(.+)$,) {
+ # we got "project.git/branch/filename"
+ $action ||= "blob_plain";
+ $hash_base ||= $1;
+ $file_name ||= $2;
+ } elsif ($path_info =~ m,^$project/([^/]+)$,) {
+ # we got "project.git/branch"
+ $action ||= "shortlog";
+ $hash ||= $1;
+ }
+}
+
+$git_dir = "$projectroot/$project";
+
# dispatch
my %actions = (
"blame" => \&git_blame2,
@@ -273,6 +322,7 @@ my %actions = (
# those below don't need $project
"opml" => \&git_opml,
"project_list" => \&git_project_list,
+ "project_index" => \&git_project_index,
);
if (defined $project) {
@@ -302,11 +352,12 @@ sub href(%) {
hash_base => "hb",
hash_parent_base => "hpb",
page => "pg",
+ order => "o",
searchtext => "s",
);
my %mapping = @mapping;
- $params{"project"} ||= $project;
+ $params{'project'} = $project unless exists $params{'project'};
my @result = ();
for (my $i = 0; $i < @mapping; $i += 2) {
@@ -380,6 +431,12 @@ sub untabify {
return $line;
}
+sub project_in_list {
+ my $project = shift;
+ my @list = git_get_projects_list();
+ return @list && scalar(grep { $_->{'path'} eq $project } @list);
+}
+
## ----------------------------------------------------------------------
## HTML aware string manipulation
@@ -653,19 +710,6 @@ sub git_get_hash_by_path {
## ......................................................................
## git utility functions, directly accessing git repository
-# assumes that PATH is not symref
-sub git_get_hash_by_ref {
- my $path = shift;
-
- open my $fd, "$projectroot/$path" or return undef;
- my $head = <$fd>;
- close $fd;
- chomp $head;
- if ($head =~ m/^[0-9a-fA-F]{40}$/) {
- return $head;
- }
-}
-
sub git_get_project_description {
my $path = shift;
@@ -692,16 +736,27 @@ sub git_get_projects_list {
if (-d $projects_list) {
# search in directory
my $dir = $projects_list;
- opendir my ($dh), $dir or return undef;
- while (my $dir = readdir($dh)) {
- if (-e "$projectroot/$dir/HEAD") {
- my $pr = {
- path => $dir,
- };
- push @list, $pr
- }
- }
- closedir($dh);
+ my $pfxlen = length("$dir");
+
+ File::Find::find({
+ follow_fast => 1, # follow symbolic links
+ dangling_symlinks => 0, # ignore dangling symlinks, silently
+ wanted => sub {
+ # skip project-list toplevel, if we get it.
+ return if (m!^[/.]$!);
+ # only directories can be git repositories
+ return unless (-d $_);
+
+ my $subdir = substr($File::Find::name, $pfxlen + 1);
+ # we check related file in $projectroot
+ if (-e "$projectroot/$subdir/HEAD" && (!$export_ok ||
+ -e "$projectroot/$subdir/$export_ok")) {
+ push @list, { path => $subdir };
+ $File::Find::prune = 1;
+ }
+ },
+ }, "$dir");
+
} elsif (-f $projects_list) {
# read from file(url-encoded):
# 'git%2Fgit.git Linus+Torvalds'
@@ -716,7 +771,8 @@ sub git_get_projects_list {
if (!defined $path) {
next;
}
- if (-e "$projectroot/$path/HEAD") {
+ if (-e "$projectroot/$path/HEAD" && (!$export_ok ||
+ -e "$projectroot/$path/$export_ok")) {
my $pr = {
path => $path,
owner => decode("utf8", $owner, Encode::FB_DEFAULT),
@@ -1065,17 +1121,27 @@ sub git_get_refs_list {
my @reflist;
my @refs;
- my $pfxlen = length("$projectroot/$project/$ref_dir");
- File::Find::find(sub {
- return if (/^\./);
- if (-f $_) {
- push @refs, substr($File::Find::name, $pfxlen + 1);
+ open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+ or return;
+ while (my $line = <$fd>) {
+ chomp $line;
+ if ($line =~ m/^([0-9a-fA-F]{40})\t$ref_dir\/?([^\^]+)$/) {
+ push @refs, { hash => $1, name => $2 };
+ } elsif ($line =~ m/^[0-9a-fA-F]{40}\t$ref_dir\/?(.*)\^\{\}$/ &&
+ $1 eq $refs[-1]{'name'}) {
+ # most likely a tag is followed by its peeled
+ # (deref) one, and when that happens we know the
+ # previous one was of type 'tag'.
+ $refs[-1]{'type'} = "tag";
}
- }, "$projectroot/$project/$ref_dir");
+ }
+ close $fd;
+
+ foreach my $ref (@refs) {
+ my $ref_file = $ref->{'name'};
+ my $ref_id = $ref->{'hash'};
- foreach my $ref_file (@refs) {
- my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file");
- my $type = git_get_type($ref_id) || next;
+ my $type = $ref->{'type'} || git_get_type($ref_id) || next;
my %ref_item = parse_ref($ref_file, $ref_id, $type);
push @reflist, \%ref_item;
@@ -1223,6 +1289,13 @@ EOF
printf('<link rel="alternate" title="%s log" '.
'href="%s" type="application/rss+xml"/>'."\n",
esc_param($project), href(action=>"rss"));
+ } else {
+ printf('<link rel="alternate" title="%s projects list" '.
+ 'href="%s" type="text/plain; charset=utf-8"/>'."\n",
+ $site_name, href(project=>undef, action=>"project_index"));
+ printf('<link rel="alternate" title="%s projects logs" '.
+ 'href="%s" type="text/x-opml"/>'."\n",
+ $site_name, href(project=>undef, action=>"opml"));
}
if (defined $favicon) {
print qq(<link rel="shortcut icon" href="$favicon" type="image/png"/>\n);
@@ -1273,9 +1346,13 @@ sub git_footer_html {
if (defined $descr) {
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
}
- print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n";
+ print $cgi->a({-href => href(action=>"rss"),
+ -class => "rss_logo"}, "RSS") . "\n";
} else {
- print $cgi->a({-href => href(action=>"opml"), -class => "rss_logo"}, "OPML") . "\n";
+ print $cgi->a({-href => href(project=>undef, action=>"opml"),
+ -class => "rss_logo"}, "OPML") . " ";
+ print $cgi->a({-href => href(project=>undef, action=>"project_index"),
+ -class => "rss_logo"}, "TXT") . "\n";
}
print "</div>\n" .
"</body>\n" .
@@ -1910,12 +1987,15 @@ sub git_shortlog_body {
sub git_history_body {
# Warning: assumes constant type (blob or tree) during history
- my ($fd, $refs, $hash_base, $ftype, $extra) = @_;
+ my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+
+ $from = 0 unless defined $from;
+ $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
print "<table class=\"history\" cellspacing=\"0\">\n";
my $alternate = 0;
- while (my $line = <$fd>) {
- if ($line !~ m/^([0-9a-fA-F]{40})/) {
+ for (my $i = $from; $i <= $to; $i++) {
+ if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
next;
}
@@ -2119,7 +2199,7 @@ sub git_project_list {
print "<th>Project</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
+ $cgi->a({-href => href(project=>undef, order=>'project'),
-class => "header"}, "Project") .
"</th>\n";
}
@@ -2128,7 +2208,7 @@ sub git_project_list {
print "<th>Description</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
+ $cgi->a({-href => href(project=>undef, order=>'descr'),
-class => "header"}, "Description") .
"</th>\n";
}
@@ -2137,7 +2217,7 @@ sub git_project_list {
print "<th>Owner</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
+ $cgi->a({-href => href(project=>undef, order=>'owner'),
-class => "header"}, "Owner") .
"</th>\n";
}
@@ -2146,7 +2226,7 @@ sub git_project_list {
print "<th>Last Change</th>\n";
} else {
print "<th>" .
- $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
+ $cgi->a({-href => href(project=>undef, order=>'age'),
-class => "header"}, "Last Change") .
"</th>\n";
}
@@ -2177,6 +2257,30 @@ sub git_project_list {
git_footer_html();
}
+sub git_project_index {
+ my @projects = git_get_projects_list();
+
+ print $cgi->header(
+ -type => 'text/plain',
+ -charset => 'utf-8',
+ -content_disposition => qq(inline; filename="index.aux"));
+
+ foreach my $pr (@projects) {
+ if (!exists $pr->{'owner'}) {
+ $pr->{'owner'} = get_file_owner("$projectroot/$project");
+ }
+
+ my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
+ # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' '
+ $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
+ $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg;
+ $path =~ s/ /\+/g;
+ $owner =~ s/ /\+/g;
+
+ print "$path $owner\n";
+ }
+}
+
sub git_summary {
my $descr = git_get_project_description($project) || "none";
my $head = git_get_head_hash($project);
@@ -2457,11 +2561,7 @@ sub git_heads {
}
sub git_blob_plain {
- # blobs defined by non-textual hash id's can be cached
my $expires;
- if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
- $expires = "+1d";
- }
if (!defined $hash) {
if (defined $file_name) {
@@ -2471,7 +2571,11 @@ sub git_blob_plain {
} else {
die_error(undef, "No file name defined");
}
+ } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ # blobs defined by non-textual hash id's can be cached
+ $expires = "+1d";
}
+
my $type = shift;
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(undef, "Couldn't cat $file_name, $hash");
@@ -2499,11 +2603,7 @@ sub git_blob_plain {
}
sub git_blob {
- # blobs defined by non-textual hash id's can be cached
my $expires;
- if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
- $expires = "+1d";
- }
if (!defined $hash) {
if (defined $file_name) {
@@ -2513,7 +2613,11 @@ sub git_blob {
} else {
die_error(undef, "No file name defined");
}
+ } elsif ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ # blobs defined by non-textual hash id's can be cached
+ $expires = "+1d";
}
+
my ($have_blame) = gitweb_check_feature('blame');
open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
or die_error(undef, "Couldn't cat $file_name, $hash");
@@ -3091,29 +3195,70 @@ sub git_history {
if (!defined $hash_base) {
$hash_base = git_get_head_hash($project);
}
+ if (!defined $page) {
+ $page = 0;
+ }
my $ftype;
my %co = parse_commit($hash_base);
if (!%co) {
die_error(undef, "Unknown commit object");
}
+
my $refs = git_get_references();
- git_header_html();
- git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base);
- git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
+
if (!defined $hash && defined $file_name) {
$hash = git_get_hash_by_path($hash_base, $file_name);
}
if (defined $hash) {
$ftype = git_get_type($hash);
}
- git_print_page_path($file_name, $ftype, $hash_base);
open my $fd, "-|",
- git_cmd(), "rev-list", "--full-history", $hash_base, "--", $file_name;
+ git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name
+ or die_error(undef, "Open git-rev-list-failed");
+ my @revlist = map { chomp; $_ } <$fd>;
+ close $fd
+ or die_error(undef, "Reading git-rev-list failed");
- git_history_body($fd, $refs, $hash_base, $ftype);
+ my $paging_nav = '';
+ if ($page > 0) {
+ $paging_nav .=
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name)},
+ "first");
+ $paging_nav .= " &sdot; " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page-1),
+ -accesskey => "p", -title => "Alt-p"}, "prev");
+ } else {
+ $paging_nav .= "first";
+ $paging_nav .= " &sdot; prev";
+ }
+ if ($#revlist >= (100 * ($page+1)-1)) {
+ $paging_nav .= " &sdot; " .
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ } else {
+ $paging_nav .= " &sdot; next";
+ }
+ my $next_link = '';
+ if ($#revlist >= (100 * ($page+1)-1)) {
+ $next_link =
+ $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
+ file_name=>$file_name, page=>$page+1),
+ -title => "Alt-n"}, "next");
+ }
+
+ git_header_html();
+ git_print_page_nav('history','', $hash_base,$co{'tree'},$hash_base, $paging_nav);
+ git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
+ git_print_page_path($file_name, $ftype, $hash_base);
+
+ git_history_body(\@revlist, ($page * 100), $#revlist,
+ $refs, $hash_base, $ftype, $next_link);
- close $fd;
git_footer_html();
}
@@ -3128,8 +3273,7 @@ sub git_search {
if (!%co) {
die_error(undef, "Unknown commit object");
}
- # pickaxe may take all resources of your box and run for several minutes
- # with every query - so decide by yourself how public you make this feature :)
+
my $commit_search = 1;
my $author_search = 0;
my $committer_search = 0;
@@ -3141,6 +3285,13 @@ sub git_search {
} elsif ($searchtext =~ s/^pickaxe\\://i) {
$commit_search = 0;
$pickaxe_search = 1;
+
+ # pickaxe may take all resources of your box and run for several minutes
+ # with every query - so decide by yourself how public you make this feature
+ my ($have_pickaxe) = gitweb_check_feature('pickaxe');
+ if (!$have_pickaxe) {
+ die_error('403 Permission denied', "Permission denied");
+ }
}
git_header_html();
git_print_page_nav('','', $hash,$co{'tree'},$hash);
diff --git a/http-fetch.c b/http-fetch.c
index fac17607b4..a113bb8c4b 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -559,9 +559,36 @@ static void process_alternates_response(void *callback_data)
char *target = NULL;
char *path;
if (data[i] == '/') {
- serverlen = strchr(base + 8, '/') - base;
- okay = 1;
+ /* This counts
+ * http://git.host/pub/scm/linux.git/
+ * -----------here^
+ * so memcpy(dst, base, serverlen) will
+ * copy up to "...git.host".
+ */
+ const char *colon_ss = strstr(base,"://");
+ if (colon_ss) {
+ serverlen = (strchr(colon_ss + 3, '/')
+ - base);
+ okay = 1;
+ }
} else if (!memcmp(data + i, "../", 3)) {
+ /* Relative URL; chop the corresponding
+ * number of subpath from base (and ../
+ * from data), and concatenate the result.
+ *
+ * The code first drops ../ from data, and
+ * then drops one ../ from data and one path
+ * from base. IOW, one extra ../ is dropped
+ * from data than path is dropped from base.
+ *
+ * This is not wrong. The alternate in
+ * http://git.host/pub/scm/linux.git/
+ * to borrow from
+ * http://git.host/pub/scm/linus.git/
+ * is ../../linus.git/objects/. You need
+ * two ../../ to borrow from your direct
+ * neighbour.
+ */
i += 3;
serverlen = strlen(base);
while (i + 2 < posn &&
@@ -583,11 +610,13 @@ static void process_alternates_response(void *callback_data)
okay = 1;
}
}
- /* skip 'objects' at end */
+ /* skip "objects\n" at end */
if (okay) {
target = xmalloc(serverlen + posn - i - 6);
- strlcpy(target, base, serverlen);
- strlcpy(target + serverlen, data + i, posn - i - 6);
+ memcpy(target, base, serverlen);
+ memcpy(target + serverlen, data + i,
+ posn - i - 7);
+ target[serverlen + posn - i - 7] = 0;
if (get_verbosely)
fprintf(stderr,
"Also look at %s\n", target);
diff --git a/merge-tree.c b/merge-tree.c
index 60df758c41..692ede0e3d 100644
--- a/merge-tree.c
+++ b/merge-tree.c
@@ -337,9 +337,11 @@ int main(int argc, char **argv)
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
- if (argc < 4)
+ if (argc != 4)
usage(merge_tree_usage);
+ setup_git_directory();
+
buf1 = get_tree_descriptor(t+0, argv[1]);
buf2 = get_tree_descriptor(t+1, argv[2]);
buf3 = get_tree_descriptor(t+2, argv[3]);
diff --git a/path.c b/path.c
index db8905f3c3..bb89fb02dc 100644
--- a/path.c
+++ b/path.c
@@ -13,9 +13,15 @@
#include "cache.h"
#include <pwd.h>
-static char pathname[PATH_MAX];
static char bad_path[] = "/bad-path/";
+static char *get_pathname(void)
+{
+ static char pathname_array[4][PATH_MAX];
+ static int index;
+ return pathname_array[3 & ++index];
+}
+
static char *cleanup_path(char *path)
{
/* Clean it up */
@@ -31,6 +37,7 @@ char *mkpath(const char *fmt, ...)
{
va_list args;
unsigned len;
+ char *pathname = get_pathname();
va_start(args, fmt);
len = vsnprintf(pathname, PATH_MAX, fmt, args);
@@ -43,6 +50,7 @@ char *mkpath(const char *fmt, ...)
char *git_path(const char *fmt, ...)
{
const char *git_dir = get_git_dir();
+ char *pathname = get_pathname();
va_list args;
unsigned len;
diff --git a/peek-remote.c b/peek-remote.c
index 87f1543fec..353da002b4 100644
--- a/peek-remote.c
+++ b/peek-remote.c
@@ -66,6 +66,6 @@ int main(int argc, char **argv)
ret = peek_remote(fd, flags);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
- return ret;
+ ret |= finish_connect(pid);
+ return !!ret;
}
diff --git a/quote.c b/quote.c
index a38786c177..e3a4d4aef3 100644
--- a/quote.c
+++ b/quote.c
@@ -106,6 +106,35 @@ char *sq_quote_argv(const char** argv, int count)
return buf;
}
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
+{
+ char *p = *ptrp;
+ int size = *sizep;
+ int oc;
+ int err = 0;
+
+ if (quote)
+ oc = sq_quote_buf(p, size, str);
+ else {
+ oc = strlen(str);
+ memcpy(p, str, (size <= oc) ? size - 1 : oc);
+ }
+
+ if (size <= oc) {
+ err = 1;
+ oc = size - 1;
+ }
+
+ *ptrp += oc;
+ **ptrp = '\0';
+ *sizep -= oc;
+ return err;
+}
+
char *sq_dequote(char *arg)
{
char *dst = arg;
diff --git a/quote.h b/quote.h
index a6c4611c25..1a29e791dd 100644
--- a/quote.h
+++ b/quote.h
@@ -33,6 +33,12 @@ extern void sq_quote_print(FILE *stream, const char *src);
extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
extern char *sq_quote_argv(const char** argv, int count);
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+extern int add_to_string(char **ptrp, int *sizep, const char *str, int quote);
+
/* This unwraps what sq_quote() produces in place, but returns
* NULL if the input does not look like what sq_quote would have
* produced.
diff --git a/rsh.c b/rsh.c
index 07166addd9..f34409e1bc 100644
--- a/rsh.c
+++ b/rsh.c
@@ -8,36 +8,7 @@
#define COMMAND_SIZE 4096
-/*
- * Append a string to a string buffer, with or without shell quoting.
- * Return true if the buffer overflowed.
- */
-static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
-{
- char *p = *ptrp;
- int size = *sizep;
- int oc;
- int err = 0;
-
- if ( quote ) {
- oc = sq_quote_buf(p, size, str);
- } else {
- oc = strlen(str);
- memcpy(p, str, (oc >= size) ? size-1 : oc);
- }
-
- if ( oc >= size ) {
- err = 1;
- oc = size-1;
- }
-
- *ptrp += oc;
- **ptrp = '\0';
- *sizep -= oc;
- return err;
-}
-
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
char *url, int rmt_argc, char **rmt_argv)
{
char *host;
diff --git a/send-pack.c b/send-pack.c
index ac4501d341..5bb123a376 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -38,9 +38,8 @@ static void exec_pack_objects(void)
static void exec_rev_list(struct ref *refs)
{
- struct ref *ref;
- static const char *args[1000];
- int i = 0, j;
+ static const char *args[4];
+ int i = 0;
args[i++] = "rev-list"; /* 0 */
if (use_thin_pack) /* 1 */
@@ -48,43 +47,16 @@ static void exec_rev_list(struct ref *refs)
else
args[i++] = "--objects";
- /* First send the ones we care about most */
- for (ref = refs; ref; ref = ref->next) {
- if (900 < i)
- die("git-rev-list environment overflow");
- if (!is_zero_sha1(ref->new_sha1)) {
- char *buf = xmalloc(100);
- args[i++] = buf;
- snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1));
- buf += 50;
- if (!is_zero_sha1(ref->old_sha1) &&
- has_sha1_file(ref->old_sha1)) {
- args[i++] = buf;
- snprintf(buf, 50, "^%s",
- sha1_to_hex(ref->old_sha1));
- }
- }
- }
+ args[i++] = "--stdin";
- /* Then a handful of the remainder
- * NEEDSWORK: we would be better off if used the newer ones first.
- */
- for (ref = refs, j = i + 16;
- i < 900 && i < j && ref;
- ref = ref->next) {
- if (is_zero_sha1(ref->new_sha1) &&
- !is_zero_sha1(ref->old_sha1) &&
- has_sha1_file(ref->old_sha1)) {
- char *buf = xmalloc(42);
- args[i++] = buf;
- snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1));
- }
- }
args[i] = NULL;
execv_git_cmd(args);
die("git-rev-list exec failed (%s)", strerror(errno));
}
+/*
+ * Run "rev-list --stdin | pack-objects" pipe.
+ */
static void rev_list(int fd, struct ref *refs)
{
int pipe_fd[2];
@@ -94,6 +66,9 @@ static void rev_list(int fd, struct ref *refs)
die("rev-list setup: pipe failed");
pack_objects_pid = fork();
if (!pack_objects_pid) {
+ /* The child becomes pack-objects; reads from pipe
+ * and writes to the original fd
+ */
dup2(pipe_fd[0], 0);
dup2(fd, 1);
close(pipe_fd[0]);
@@ -104,6 +79,8 @@ static void rev_list(int fd, struct ref *refs)
}
if (pack_objects_pid < 0)
die("pack-objects fork failed");
+
+ /* We become rev-list --stdin; output goes to pipe. */
dup2(pipe_fd[1], 1);
close(pipe_fd[0]);
close(pipe_fd[1]);
@@ -111,13 +88,71 @@ static void rev_list(int fd, struct ref *refs)
exec_rev_list(refs);
}
+/*
+ * Create "rev-list --stdin | pack-objects" pipe and feed
+ * the refs into the pipeline.
+ */
+static void rev_list_generate(int fd, struct ref *refs)
+{
+ int pipe_fd[2];
+ pid_t rev_list_generate_pid;
+
+ if (pipe(pipe_fd) < 0)
+ die("rev-list-generate setup: pipe failed");
+ rev_list_generate_pid = fork();
+ if (!rev_list_generate_pid) {
+ /* The child becomes the "rev-list | pack-objects"
+ * pipeline. It takes input from us, and its output
+ * goes to fd.
+ */
+ dup2(pipe_fd[0], 0);
+ dup2(fd, 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ close(fd);
+ rev_list(fd, refs);
+ die("rev-list setup failed");
+ }
+ if (rev_list_generate_pid < 0)
+ die("rev-list-generate fork failed");
+
+ /* We feed the rev parameters to them. We do not write into
+ * fd nor read from the pipe.
+ */
+ close(pipe_fd[0]);
+ close(fd);
+ while (refs) {
+ char buf[42];
+
+ if (!is_null_sha1(refs->old_sha1) &&
+ has_sha1_file(refs->old_sha1)) {
+ memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
+ buf[0] = '^';
+ buf[41] = '\n';
+ write(pipe_fd[1], buf, 42);
+ }
+ if (!is_null_sha1(refs->new_sha1)) {
+ memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
+ buf[40] = '\n';
+ write(pipe_fd[1], buf, 41);
+ }
+ refs = refs->next;
+ }
+ close(pipe_fd[1]);
+ // waitpid(rev_list_generate_pid);
+ exit(0);
+}
+
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
static void pack_objects(int fd, struct ref *refs)
{
pid_t rev_list_pid;
rev_list_pid = fork();
if (!rev_list_pid) {
- rev_list(fd, refs);
+ rev_list_generate(fd, refs);
die("rev-list setup failed");
}
if (rev_list_pid < 0)
@@ -408,6 +443,6 @@ int main(int argc, char **argv)
ret = send_pack(fd[0], fd[1], nr_heads, heads);
close(fd[0]);
close(fd[1]);
- finish_connect(pid);
- return ret;
+ ret |= finish_connect(pid);
+ return !!ret;
}
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index ddc80bbeae..b3b920edb1 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -19,51 +19,51 @@ n=$n_dir/fixes
test_expect_success \
"create $m" \
- 'git-update-ref $m $A &&
- test $A = $(cat .git/$m)'
+ "git-update-ref $m $A &&
+ test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m" \
- 'git-update-ref $m $B $A &&
- test $B = $(cat .git/$m)'
+ "git-update-ref $m $B $A &&
+ test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_success \
"fail to create $n" \
- 'touch .git/$n_dir
+ "touch .git/$n_dir
git-update-ref $n $A >out 2>err
- test $? = 1 &&
+ test "'$? = 1 &&
test "" = "$(cat out)" &&
grep "error: unable to resolve reference" err &&
- grep $n err'
+ grep '"$n err"
rm -f .git/$n_dir out err
test_expect_success \
"create $m (by HEAD)" \
- 'git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ "git-update-ref HEAD $A &&
+ test $A"' = $(cat .git/'"$m"')'
test_expect_success \
"create $m (by HEAD)" \
- 'git-update-ref HEAD $B $A &&
- test $B = $(cat .git/$m)'
+ "git-update-ref HEAD $B $A &&
+ test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
test_expect_failure \
'(not) create HEAD with old sha1' \
- 'git-update-ref HEAD $A $B'
+ "git-update-ref HEAD $A $B"
test_expect_failure \
"(not) prior created .git/$m" \
- 'test -f .git/$m'
+ "test -f .git/$m"
rm -f .git/$m
test_expect_success \
"create HEAD" \
- 'git-update-ref HEAD $A'
+ "git-update-ref HEAD $A"
test_expect_failure \
'(not) change HEAD with wrong SHA1' \
- 'git-update-ref HEAD $B $Z'
+ "git-update-ref HEAD $B $Z"
test_expect_failure \
"(not) changed .git/$m" \
- 'test $B = $(cat .git/$m)'
+ "test $B"' = $(cat .git/'"$m"')'
rm -f .git/$m
mkdir -p .git/logs/refs/heads
@@ -71,18 +71,18 @@ touch .git/logs/refs/heads/master
test_expect_success \
"create $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git-update-ref HEAD $A -m "Initial Creation" &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD '"$A"' -m "Initial Creation" &&
+ test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:31" \
- git-update-ref HEAD $B $A -m "Switch" &&
- test $B = $(cat .git/$m)'
+ git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by touch)" \
'GIT_COMMITTER_DATE="2005-05-26 23:41" \
- git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD'" $A &&
+ test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation
@@ -91,7 +91,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
EOF
test_expect_success \
"verifying $m's log" \
- 'diff expect .git/logs/$m'
+ "diff expect .git/logs/$m"
rm -rf .git/$m .git/logs expect
test_expect_success \
@@ -102,18 +102,18 @@ test_expect_success \
test_expect_success \
"create $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:32" \
- git-update-ref HEAD $A -m "Initial Creation" &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD'" $A "'-m "Initial Creation" &&
+ test '"$A"' = $(cat .git/'"$m"')'
test_expect_success \
"update $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:33" \
- git-update-ref HEAD $B $A -m "Switch" &&
- test $B = $(cat .git/$m)'
+ git-update-ref HEAD'" $B $A "'-m "Switch" &&
+ test '"$B"' = $(cat .git/'"$m"')'
test_expect_success \
"set $m (logged by config)" \
'GIT_COMMITTER_DATE="2005-05-26 23:43" \
- git-update-ref HEAD $A &&
- test $A = $(cat .git/$m)'
+ git-update-ref HEAD '"$A &&
+ test $A"' = $(cat .git/'"$m"')'
cat >expect <<EOF
$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
@@ -140,50 +140,50 @@ test_expect_success \
'Query "master@{May 25 2005}" (before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
- test $C = $(cat o) &&
- test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
"Query master@{2005-05-25} (before history)" \
'rm -f o e
git-rev-parse --verify master@{2005-05-25} >o 2>e &&
- test $C = $(cat o) &&
- echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
- test $C = $(cat o) &&
- test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+ test '"$C"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
test_expect_success \
'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
'rm -f o e
git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
- test $A = $(cat o) &&
+ test '"$A"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
- test $B = $(cat o) &&
- test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+ test '"$B"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
- test $Z = $(cat o) &&
+ test '"$Z"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
- test $E = $(cat o) &&
+ test '"$E"' = $(cat o) &&
test "" = "$(cat e)"'
test_expect_success \
'Query "master@{2005-05-28}" (past end of history)' \
'rm -f o e
git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
- test $D = $(cat o) &&
- test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+ test '"$D"' = $(cat o) &&
+ test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
rm -f .git/$m .git/logs/$m expect
@@ -221,7 +221,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c
EOF
test_expect_success \
'git-commit logged updates' \
- 'diff expect .git/logs/$m'
+ "diff expect .git/logs/$m"
unset h_TEST h_OTHER h_FIXED h_MERGED
test_expect_success \
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index 8ab63c5276..bb25315361 100755
--- a/t/t3403-rebase-skip.sh
+++ b/t/t3403-rebase-skip.sh
@@ -37,7 +37,9 @@ test_expect_success setup '
git branch skip-merge skip-reference
'
-test_expect_failure 'rebase with git am -3 (default)' 'git rebase master'
+test_expect_failure 'rebase with git am -3 (default)' '
+ git rebase master
+'
test_expect_success 'rebase --skip with am -3' '
git reset --hard HEAD &&
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
index ff052699a2..e2b1124c78 100755
--- a/t/t4103-apply-binary.sh
+++ b/t/t4103-apply-binary.sh
@@ -94,11 +94,11 @@ test_expect_failure 'apply binary diff (copy) -- should fail.' \
'do_reset
git-apply --index C.diff'
-test_expect_failure 'apply binary diff without replacement -- should fail.' \
+test_expect_success 'apply binary diff without replacement.' \
'do_reset
git-apply BF.diff'
-test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \
+test_expect_success 'apply binary diff without replacement (copy).' \
'do_reset
git-apply CF.diff'
diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh
new file mode 100755
index 0000000000..2ff800c23f
--- /dev/null
+++ b/t/t4104-apply-boundary.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply boundary tests
+
+'
+. ./test-lib.sh
+
+L="c d e f g h i j k l m n o p q r s t u v w x"
+
+test_expect_success setup '
+ for i in b '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >original &&
+ git update-index --add victim &&
+
+ : add to the head
+ for i in a b '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >add-a-expect &&
+ git diff victim >add-a-patch.with &&
+ git diff --unified=0 >add-a-patch.without &&
+
+ : modify at the head
+ for i in a '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >mod-a-expect &&
+ git diff victim >mod-a-patch.with &&
+ git diff --unified=0 >mod-a-patch.without &&
+
+ : remove from the head
+ for i in '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >del-a-expect &&
+ git diff victim >del-a-patch.with
+ git diff --unified=0 >del-a-patch.without &&
+
+ : add to the tail
+ for i in b '"$L"' y z
+ do
+ echo $i
+ done >victim &&
+ cat victim >add-z-expect &&
+ git diff victim >add-z-patch.with &&
+ git diff --unified=0 >add-z-patch.without &&
+
+ : modify at the tail
+ for i in a '"$L"' y
+ do
+ echo $i
+ done >victim &&
+ cat victim >mod-z-expect &&
+ git diff victim >mod-z-patch.with &&
+ git diff --unified=0 >mod-z-patch.without &&
+
+ : remove from the tail
+ for i in b '"$L"'
+ do
+ echo $i
+ done >victim &&
+ cat victim >del-z-expect &&
+ git diff victim >del-z-patch.with
+ git diff --unified=0 >del-z-patch.without &&
+
+ : done
+'
+
+for with in with without
+do
+ case "$with" in
+ with) u= ;;
+ without) u='--unidiff-zero ' ;;
+ esac
+ for kind in add-a add-z mod-a mod-z del-a del-z
+ do
+ test_expect_success "apply $kind-patch $with context" '
+ cat original >victim &&
+ git update-index victim &&
+ git apply --index '"$u$kind-patch.$with"' || {
+ cat '"$kind-patch.$with"'
+ (exit 1)
+ } &&
+ diff -u '"$kind"'-expect victim
+ '
+ done
+done
+
+for kind in add-a add-z mod-a mod-z del-a del-z
+do
+ rm -f $kind-ng.without
+ sed -e "s/^diff --git /diff /" \
+ -e '/^index /d' \
+ <$kind-patch.without >$kind-ng.without
+ test_expect_success "apply non-git $kind-patch without context" '
+ cat original >victim &&
+ git update-index victim &&
+ git apply --unidiff-zero --index '"$kind-ng.without"' || {
+ cat '"$kind-ng.without"'
+ (exit 1)
+ } &&
+ diff -u '"$kind"'-expect victim
+ '
+done
+
+test_done
diff --git a/trace.c b/trace.c
index ce01c34749..f9efc918b8 100644
--- a/trace.c
+++ b/trace.c
@@ -55,7 +55,7 @@ static int get_trace_fd(int *need_close)
{
char *trace = getenv("GIT_TRACE");
- if (!trace || !strcmp(trace, "0") || !strcasecmp(trace," false"))
+ if (!trace || !strcmp(trace, "0") || !strcasecmp(trace, "false"))
return 0;
if (!strcmp(trace, "1") || !strcasecmp(trace, "true"))
return STDERR_FILENO;