aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--builtin-apply.c155
-rw-r--r--cache.h5
-rw-r--r--config.c9
-rw-r--r--convert.c186
-rw-r--r--diff.c17
-rw-r--r--entry.c16
-rw-r--r--environment.c1
-rw-r--r--sha1_file.c23
-rwxr-xr-xt/t0020-crlf.sh217
-rwxr-xr-xt/t4119-apply-config.sh162
11 files changed, 767 insertions, 27 deletions
diff --git a/Makefile b/Makefile
index 35be5e204..fbe05f938 100644
--- a/Makefile
+++ b/Makefile
@@ -266,7 +266,8 @@ LIB_OBJS = \
revision.o pager.o tree-walk.o xdiff-interface.o \
write_or_die.o trace.o list-objects.o grep.o \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
- color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o
+ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
+ convert.o
BUILTIN_OBJS = \
builtin-add.o \
diff --git a/builtin-apply.c b/builtin-apply.c
index d67817834..630d8fd65 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -28,6 +28,7 @@ static int newfd = -1;
static int unidiff_zero;
static int p_value = 1;
+static int p_value_known;
static int check_index;
static int write_index;
static int cached;
@@ -144,6 +145,7 @@ struct patch {
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
+ unsigned int is_toplevel_relative:1;
unsigned int inaccurate_eof:1;
unsigned int is_binary:1;
unsigned int is_copy:1;
@@ -238,7 +240,7 @@ static int name_terminate(const char *name, int namelen, int c, int terminate)
return 1;
}
-static char * find_name(const char *line, char *def, int p_value, int terminate)
+static char *find_name(const char *line, char *def, int p_value, int terminate)
{
int len;
const char *start = line;
@@ -311,11 +313,54 @@ static char * find_name(const char *line, char *def, int p_value, int terminate)
return name;
}
+static int count_slashes(const char *cp)
+{
+ int cnt = 0;
+ char ch;
+
+ while ((ch = *cp++))
+ if (ch == '/')
+ cnt++;
+ return cnt;
+}
+
+/*
+ * Given the string after "--- " or "+++ ", guess the appropriate
+ * p_value for the given patch.
+ */
+static int guess_p_value(const char *nameline)
+{
+ char *name, *cp;
+ int val = -1;
+
+ if (is_dev_null(nameline))
+ return -1;
+ name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
+ if (!name)
+ return -1;
+ cp = strchr(name, '/');
+ if (!cp)
+ val = 0;
+ else if (prefix) {
+ /*
+ * Does it begin with "a/$our-prefix" and such? Then this is
+ * very likely to apply to our directory.
+ */
+ if (!strncmp(name, prefix, prefix_length))
+ val = count_slashes(prefix);
+ else {
+ cp++;
+ if (!strncmp(cp, prefix, prefix_length))
+ val = count_slashes(prefix) + 1;
+ }
+ }
+ free(name);
+ return val;
+}
+
/*
* Get the name etc info from the --/+++ lines of a traditional patch header
*
- * NOTE! This hardcodes "-p1" behaviour in filename detection.
- *
* FIXME! The end-of-filename heuristics are kind of screwy. For existing
* files, we can happily check the index for a match, but for creating a
* new file we should try to match whatever "patch" does. I have no idea.
@@ -326,6 +371,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc
first += 4; /* skip "--- " */
second += 4; /* skip "+++ " */
+ if (!p_value_known) {
+ int p, q;
+ p = guess_p_value(first);
+ q = guess_p_value(second);
+ if (p < 0) p = q;
+ if (0 <= p && p == q) {
+ p_value = p;
+ p_value_known = 1;
+ }
+ }
if (is_dev_null(first)) {
patch->is_new = 1;
patch->is_delete = 0;
@@ -787,6 +842,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
{
unsigned long offset, len;
+ patch->is_toplevel_relative = 0;
patch->is_rename = patch->is_copy = 0;
patch->is_new = patch->is_delete = -1;
patch->old_mode = patch->new_mode = 0;
@@ -831,6 +887,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
die("git diff header lacks filename information (line %d)", linenr);
patch->old_name = patch->new_name = patch->def_name;
}
+ patch->is_toplevel_relative = 1;
*hdrsize = git_hdr_len;
return offset;
}
@@ -1393,28 +1450,39 @@ static void show_stats(struct patch *patch)
free(qname);
}
-static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
+static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p)
{
int fd;
unsigned long got;
+ unsigned long nsize;
+ char *nbuf;
+ unsigned long size = *size_p;
+ char *buf = *buf_p;
switch (st->st_mode & S_IFMT) {
case S_IFLNK:
- return readlink(path, buf, size);
+ return readlink(path, buf, size) != size;
case S_IFREG:
fd = open(path, O_RDONLY);
if (fd < 0)
return error("unable to open %s", path);
got = 0;
for (;;) {
- int ret = xread(fd, (char *) buf + got, size - got);
+ int ret = xread(fd, buf + got, size - got);
if (ret <= 0)
break;
got += ret;
}
close(fd);
- return got;
-
+ nsize = got;
+ nbuf = buf;
+ if (convert_to_git(path, &nbuf, &nsize)) {
+ free(buf);
+ *buf_p = nbuf;
+ *alloc_p = nsize;
+ *size_p = nsize;
+ }
+ return got != size;
default:
return -1;
}
@@ -1910,7 +1978,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
size = st->st_size;
alloc = size + 8192;
buf = xmalloc(alloc);
- if (read_old_data(st, patch->old_name, buf, alloc) != size)
+ if (read_old_data(st, patch->old_name, &buf, &alloc, &size))
return error("read of %s failed", patch->old_name);
}
@@ -2232,7 +2300,7 @@ static void patch_stats(struct patch *patch)
}
}
-static void remove_file(struct patch *patch)
+static void remove_file(struct patch *patch, int rmdir_empty)
{
if (write_index) {
if (remove_file_from_cache(patch->old_name) < 0)
@@ -2240,7 +2308,7 @@ static void remove_file(struct patch *patch)
cache_tree_invalidate_path(active_cache_tree, patch->old_name);
}
if (!cached) {
- if (!unlink(patch->old_name)) {
+ if (!unlink(patch->old_name) && rmdir_empty) {
char *name = xstrdup(patch->old_name);
char *end = strrchr(name, '/');
while (end) {
@@ -2282,12 +2350,22 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
{
int fd;
+ char *nbuf;
+ unsigned long nsize;
if (S_ISLNK(mode))
/* Although buf:size is counted string, it also is NUL
* terminated.
*/
return symlink(buf, path);
+ nsize = size;
+ nbuf = (char *) buf;
+ if (convert_to_working_tree(path, &nbuf, &nsize)) {
+ free((char *) buf);
+ buf = nbuf;
+ size = nsize;
+ }
+
fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
if (fd < 0)
return -1;
@@ -2373,7 +2451,7 @@ static void write_out_one_result(struct patch *patch, int phase)
{
if (patch->is_delete > 0) {
if (phase == 0)
- remove_file(patch);
+ remove_file(patch, 1);
return;
}
if (patch->is_new > 0 || patch->is_copy) {
@@ -2386,7 +2464,7 @@ static void write_out_one_result(struct patch *patch, int phase)
* thing: remove the old, write the new
*/
if (phase == 0)
- remove_file(patch);
+ remove_file(patch, 0);
if (phase == 1)
create_file(patch);
}
@@ -2508,6 +2586,32 @@ static int use_patch(struct patch *p)
return 1;
}
+static void prefix_one(char **name)
+{
+ char *old_name = *name;
+ if (!old_name)
+ return;
+ *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+ free(old_name);
+}
+
+static void prefix_patches(struct patch *p)
+{
+ if (!prefix || p->is_toplevel_relative)
+ return;
+ for ( ; p; p = p->next) {
+ if (p->new_name == p->old_name) {
+ char *prefixed = p->new_name;
+ prefix_one(&prefixed);
+ p->new_name = p->old_name = prefixed;
+ }
+ else {
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
+ }
+ }
+}
+
static int apply_patch(int fd, const char *filename, int inaccurate_eof)
{
unsigned long offset, size;
@@ -2530,11 +2634,14 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
break;
if (apply_in_reverse)
reverse_patches(patch);
+ if (prefix)
+ prefix_patches(patch);
if (use_patch(patch)) {
patch_stats(patch);
*listp = patch;
listp = &patch->next;
- } else {
+ }
+ else {
/* perhaps free it a bit better? */
free(patch);
skipped_patch++;
@@ -2595,9 +2702,16 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
int read_stdin = 1;
int inaccurate_eof = 0;
int errs = 0;
+ int is_not_gitdir = 0;
const char *whitespace_option = NULL;
+ prefix = setup_git_directory_gently(&is_not_gitdir);
+ prefix_length = prefix ? strlen(prefix) : 0;
+ git_config(git_apply_config);
+ if (apply_default_whitespace)
+ parse_whitespace_option(apply_default_whitespace);
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
char *end;
@@ -2617,6 +2731,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
}
if (!prefixcmp(arg, "-p")) {
p_value = atoi(arg + 2);
+ p_value_known = 1;
continue;
}
if (!strcmp(arg, "--no-add")) {
@@ -2648,10 +2763,14 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
continue;
}
if (!strcmp(arg, "--index")) {
+ if (is_not_gitdir)
+ die("--index outside a repository");
check_index = 1;
continue;
}
if (!strcmp(arg, "--cached")) {
+ if (is_not_gitdir)
+ die("--cached outside a repository");
check_index = 1;
cached = 1;
continue;
@@ -2700,14 +2819,6 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
inaccurate_eof = 1;
continue;
}
-
- if (check_index && prefix_length < 0) {
- prefix = setup_git_directory();
- prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_apply_config);
- if (!whitespace_option && apply_default_whitespace)
- parse_whitespace_option(apply_default_whitespace);
- }
if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
diff --git a/cache.h b/cache.h
index 04f8e63ba..8bbc14299 100644
--- a/cache.h
+++ b/cache.h
@@ -211,6 +211,7 @@ extern const char *apply_default_whitespace;
extern int zlib_compression_level;
extern size_t packed_git_window_size;
extern size_t packed_git_limit;
+extern int auto_crlf;
#define GIT_REPO_VERSION 0
extern int repository_format_version;
@@ -478,4 +479,8 @@ extern int nfvasprintf(char **str, const char *fmt, va_list va);
extern void trace_printf(const char *format, ...);
extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
+/* convert.c */
+extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep);
+extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep);
+
#endif /* CACHE_H */
diff --git a/config.c b/config.c
index c938aa0b1..8b6cf1aaa 100644
--- a/config.c
+++ b/config.c
@@ -326,6 +326,15 @@ int git_default_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.autocrlf")) {
+ if (value && !strcasecmp(value, "input")) {
+ auto_crlf = -1;
+ return 0;
+ }
+ auto_crlf = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "user.name")) {
strlcpy(git_default_name, value, sizeof(git_default_name));
return 0;
diff --git a/convert.c b/convert.c
new file mode 100644
index 000000000..898bfe3eb
--- /dev/null
+++ b/convert.c
@@ -0,0 +1,186 @@
+#include "cache.h"
+/*
+ * convert.c - convert a file when checking it out and checking it in.
+ *
+ * This should use the pathname to decide on whether it wants to do some
+ * more interesting conversions (automatic gzip/unzip, general format
+ * conversions etc etc), but by default it just does automatic CRLF<->LF
+ * translation when the "auto_crlf" option is set.
+ */
+
+struct text_stat {
+ /* CR, LF and CRLF counts */
+ unsigned cr, lf, crlf;
+
+ /* These are just approximations! */
+ unsigned printable, nonprintable;
+};
+
+static void gather_stats(const char *buf, unsigned long size, struct text_stat *stats)
+{
+ unsigned long i;
+
+ memset(stats, 0, sizeof(*stats));
+
+ for (i = 0; i < size; i++) {
+ unsigned char c = buf[i];
+ if (c == '\r') {
+ stats->cr++;
+ if (i+1 < size && buf[i+1] == '\n')
+ stats->crlf++;
+ continue;
+ }
+ if (c == '\n') {
+ stats->lf++;
+ continue;
+ }
+ if (c == 127)
+ /* DEL */
+ stats->nonprintable++;
+ else if (c < 32) {
+ switch (c) {
+ /* BS, HT, ESC and FF */
+ case '\b': case '\t': case '\033': case '\014':
+ stats->printable++;
+ break;
+ default:
+ stats->nonprintable++;
+ }
+ }
+ else
+ stats->printable++;
+ }
+}
+
+/*
+ * The same heuristics as diff.c::mmfile_is_binary()
+ */
+static int is_binary(unsigned long size, struct text_stat *stats)
+{
+
+ if ((stats->printable >> 7) < stats->nonprintable)
+ return 1;
+ /*
+ * Other heuristics? Average line length might be relevant,
+ * as might LF vs CR vs CRLF counts..
+ *
+ * NOTE! It might be normal to have a low ratio of CRLF to LF
+ * (somebody starts with a LF-only file and edits it with an editor
+ * that adds CRLF only to lines that are added..). But do we
+ * want to support CR-only? Probably not.
+ */
+ return 0;
+}
+
+int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
+{
+ char *buffer, *nbuf;
+ unsigned long size, nsize;
+ struct text_stat stats;
+
+ /*
+ * FIXME! Other pluggable conversions should go here,
+ * based on filename patterns. Right now we just do the
+ * stupid auto-CRLF one.
+ */
+ if (!auto_crlf)
+ return 0;
+
+ size = *sizep;
+ if (!size)
+ return 0;
+ buffer = *bufp;
+
+ gather_stats(buffer, size, &stats);
+
+ /* No CR? Nothing to convert, regardless. */
+ if (!stats.cr)
+ return 0;
+
+ /*
+ * We're currently not going to even try to convert stuff
+ * that has bare CR characters. Does anybody do that crazy
+ * stuff?
+ */
+ if (stats.cr != stats.crlf)
+ return 0;
+
+ /*
+ * And add some heuristics for binary vs text, of course...
+ */
+ if (is_binary(size, &stats))
+ return 0;
+
+ /*
+ * Ok, allocate a new buffer, fill it in, and return true
+ * to let the caller know that we switched buffers on it.
+ */
+ nsize = size - stats.crlf;
+ nbuf = xmalloc(nsize);
+ *bufp = nbuf;
+ *sizep = nsize;
+ do {
+ unsigned char c = *buffer++;
+ if (c != '\r')
+ *nbuf++ = c;
+ } while (--size);
+
+ return 1;
+}
+
+int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
+{
+ char *buffer, *nbuf;
+ unsigned long size, nsize;
+ struct text_stat stats;
+ unsigned char last;
+
+ /*
+ * FIXME! Other pluggable conversions should go here,
+ * based on filename patterns. Right now we just do the
+ * stupid auto-CRLF one.
+ */
+ if (auto_crlf <= 0)
+ return 0;
+
+ size = *sizep;
+ if (!size)
+ return 0;
+ buffer = *bufp;
+
+ gather_stats(buffer, size, &stats);
+
+ /* No LF? Nothing to convert, regardless. */
+ if (!stats.lf)
+ return 0;
+
+ /* Was it already in CRLF format? */
+ if (stats.lf == stats.crlf)
+ return 0;
+
+ /* If we have any bare CR characters, we're not going to touch it */
+ if (stats.cr != stats.crlf)
+ return 0;
+
+ if (is_binary(size, &stats))
+ return 0;
+
+ /*
+ * Ok, allocate a new buffer, fill it in, and return true
+ * to let the caller know that we switched buffers on it.
+ */
+ nsize = size + stats.lf - stats.crlf;
+ nbuf = xmalloc(nsize);
+ *bufp = nbuf;
+ *sizep = nsize;
+ last = 0;
+ do {
+ unsigned char c = *buffer++;
+ if (c == '\n' && last != '\r')
+ *nbuf++ = '\r';
+ *nbuf++ = c;
+ last = c;
+ } while (--size);
+
+ return 1;
+}
diff --git a/diff.c b/diff.c
index 019ecbc1f..5ecb12225 100644
--- a/diff.c
+++ b/diff.c
@@ -1378,6 +1378,9 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
reuse_worktree_file(s->path, s->sha1, 0)) {
struct stat st;
int fd;
+ char *buf;
+ unsigned long size;
+
if (lstat(s->path, &st) < 0) {
if (errno == ENOENT) {
err_empty:
@@ -1410,7 +1413,19 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
s->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
s->should_munmap = 1;
- /* FIXME! CRLF -> LF conversion goes here, based on "s->path" */
+
+ /*
+ * Convert from working tree format to canonical git format
+ */
+ buf = s->data;
+ size = s->size;
+ if (convert_to_git(s->path, &buf, &size)) {
+ munmap(s->data, s->size);
+ s->should_munmap = 0;
+ s->data = buf;
+ s->size = size;
+ s->should_free = 1;
+ }
}
else {
char type[20];
diff --git a/entry.c b/entry.c
index c2641ddef..472a9ef32 100644
--- a/entry.c
+++ b/entry.c
@@ -78,6 +78,9 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
path, sha1_to_hex(ce->sha1));
}
switch (ntohl(ce->ce_mode) & S_IFMT) {
+ char *buf;
+ unsigned long nsize;
+
case S_IFREG:
if (to_tempfile) {
strcpy(path, ".merge_file_XXXXXX");
@@ -89,7 +92,18 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
return error("git-checkout-index: unable to create file %s (%s)",
path, strerror(errno));
}
- /* FIXME: LF -> CRLF conversion goes here, based on "ce->name" */
+
+ /*
+ * Convert from git internal format to working tree format
+ */
+ buf = new;
+ nsize = size;
+ if (convert_to_working_tree(ce->name, &buf, &nsize)) {
+ free(new);
+ new = buf;
+ size = nsize;
+ }
+
wrote = write_in_full(fd, new, size);
close(fd);
free(new);
diff --git a/environment.c b/environment.c
index 54c22f824..570e32ac3 100644
--- a/environment.c
+++ b/environment.c
@@ -28,6 +28,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
int pager_in_use;
int pager_use_color = 1;
+int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
static const char *git_dir;
static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
diff --git a/sha1_file.c b/sha1_file.c
index 2c870314d..9a1dee051 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -2082,7 +2082,7 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
{
unsigned long size = st->st_size;
void *buf;
- int ret;
+ int ret, re_allocated = 0;
buf = "";
if (size)
@@ -2091,11 +2091,30 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
if (!type)
type = blob_type;
- /* FIXME: CRLF -> LF conversion here for blobs! We'll need the path! */
+
+ /*
+ * Convert blobs to git internal format
+ */
+ if (!strcmp(type, blob_type)) {
+ unsigned long nsize = size;
+ char *nbuf = buf;
+ if (convert_to_git(NULL, &nbuf, &nsize)) {
+ if (size)
+ munmap(buf, size);
+ size = nsize;
+ buf = nbuf;
+ re_allocated = 1;
+ }
+ }
+
if (write_object)
ret = write_sha1_file(buf, size, type, sha1);
else
ret = hash_sha1_file(buf, size, type, sha1);
+ if (re_allocated) {
+ free(buf);
+ return ret;
+ }
if (size)
munmap(buf, size);
return ret;
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
new file mode 100755
index 000000000..723b29ad1
--- /dev/null
+++ b/t/t0020-crlf.sh
@@ -0,0 +1,217 @@
+#!/bin/sh
+
+test_description='CRLF conversion'
+
+. ./test-lib.sh
+
+append_cr () {
+ sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+ tr '\015' Q <"$1" | grep Q >/dev/null &&
+ tr '\015' Q <"$1" | sed -ne 's/Q$//p'
+}
+
+test_expect_success setup '
+
+ git repo-config core.autocrlf false &&
+
+ for w in Hello world how are you; do echo $w; done >one &&
+ mkdir dir &&
+ for w in I am very very fine thank you; do echo $w; done >dir/two &&
+ git add . &&
+
+ git commit -m initial &&
+
+ one=`git rev-parse HEAD:one` &&
+ dir=`git rev-parse HEAD:dir` &&
+ two=`git rev-parse HEAD:dir/two` &&
+
+ for w in Some extra lines here; do echo $w; done >>one &&
+ git diff >patch.file &&
+ patched=`git hash-object --stdin <one` &&
+ git read-tree --reset -u HEAD &&
+
+ echo happy.
+'
+
+test_expect_success 'update with autocrlf=input' '
+
+ rm -f tmp one dir/two &&
+ git read-tree --reset -u HEAD &&
+ git repo-config core.autocrlf input &&
+
+ for f in one dir/two
+ do
+ append_cr <$f >tmp && mv -f tmp $f &&
+ git update-index -- $f || {
+ echo Oops
+ false
+ break
+ }
+ done &&
+
+ differs=`git diff-index --cached HEAD` &&
+ test -z "$differs" || {
+ echo Oops "$differs"
+ false
+ }
+
+'
+
+test_expect_success 'update with autocrlf=true' '
+
+ rm -f tmp one dir/two &&
+ git read-tree --reset -u HEAD &&
+ git repo-config core.autocrlf true &&
+
+ for f in one dir/two
+ do
+ append_cr <$f >tmp && mv -f tmp $f &&
+ git update-index -- $f || {
+ echo "Oops $f"
+ false
+ break
+ }
+ done &&
+
+ differs=`git diff-index --cached HEAD` &&
+ test -z "$differs" || {
+ echo Oops "$differs"
+ false
+ }
+
+'
+
+test_expect_success 'checkout with autocrlf=true' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf true &&
+ git read-tree --reset -u HEAD &&
+
+ for f in one dir/two
+ do
+ remove_cr "$f" >tmp && mv -f tmp $f &&
+ git update-index -- $f || {
+ echo "Eh? $f"
+ false
+ break
+ }
+ done &&
+ test "$one" = `git hash-object --stdin <one` &&
+ test "$two" = `git hash-object --stdin <dir/two` &&
+ differs=`git diff-index --cached HEAD` &&
+ test -z "$differs" || {
+ echo Oops "$differs"
+ false
+ }
+'
+
+test_expect_success 'checkout with autocrlf=input' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf input &&
+ git read-tree --reset -u HEAD &&
+
+ for f in one dir/two
+ do
+ if remove_cr "$f" >/dev/null
+ then
+ echo "Eh? $f"
+ false
+ break
+ else
+ git update-index -- $f
+ fi
+ done &&
+ test "$one" = `git hash-object --stdin <one` &&
+ test "$two" = `git hash-object --stdin <dir/two` &&
+ differs=`git diff-index --cached HEAD` &&
+ test -z "$differs" || {
+ echo Oops "$differs"
+ false
+ }
+'
+
+test_expect_success 'apply patch (autocrlf=input)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf input &&
+ git read-tree --reset -u HEAD &&
+
+ git apply patch.file &&
+ test "$patched" = "`git hash-object --stdin <one`" || {
+ echo "Eh? apply without index"
+ false
+ }
+'
+
+test_expect_success 'apply patch --cached (autocrlf=input)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf input &&
+ git read-tree --reset -u HEAD &&
+
+ git apply --cached patch.file &&
+ test "$patched" = `git rev-parse :one` || {
+ echo "Eh? apply with --cached"
+ false
+ }
+'
+
+test_expect_success 'apply patch --index (autocrlf=input)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf input &&
+ git read-tree --reset -u HEAD &&
+
+ git apply --index patch.file &&
+ test "$patched" = `git rev-parse :one` &&
+ test "$patched" = `git hash-object --stdin <one` || {
+ echo "Eh? apply with --index"
+ false
+ }
+'
+
+test_expect_success 'apply patch (autocrlf=true)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf true &&
+ git read-tree --reset -u HEAD &&
+
+ git apply patch.file &&
+ test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+ echo "Eh? apply without index"
+ false
+ }
+'
+
+test_expect_success 'apply patch --cached (autocrlf=true)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf true &&
+ git read-tree --reset -u HEAD &&
+
+ git apply --cached patch.file &&
+ test "$patched" = `git rev-parse :one` || {
+ echo "Eh? apply without index"
+ false
+ }
+'
+
+test_expect_success 'apply patch --index (autocrlf=true)' '
+
+ rm -f tmp one dir/two &&
+ git repo-config core.autocrlf true &&
+ git read-tree --reset -u HEAD &&
+
+ git apply --index patch.file &&
+ test "$patched" = `git rev-parse :one` &&
+ test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+ echo "Eh? apply with --index"
+ false
+ }
+'
+
+test_done
diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh
new file mode 100755
index 000000000..620a9207b
--- /dev/null
+++ b/t/t4119-apply-config.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='git-apply --whitespace=strip and configuration file.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ mkdir sub &&
+ echo A >sub/file1 &&
+ cp sub/file1 saved &&
+ git add sub/file1 &&
+ echo "B " >sub/file1 &&
+ git diff >patch.file
+'
+
+# Also handcraft GNU diff output; note this has trailing whitespace.
+cat >gpatch.file <<\EOF &&
+--- file1 2007-02-21 01:04:24.000000000 -0800
++++ file1+ 2007-02-21 01:07:44.000000000 -0800
+@@ -1 +1 @@
+-A
++B
+EOF
+
+sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
+sed -e '
+ /^--- /s|file1|a/sub/&|
+ /^+++ /s|file1|b/sub/&|
+' gpatch.file >gpatch-ab-sub.file &&
+
+check_result () {
+ if grep " " "$1"
+ then
+ echo "Eh?"
+ false
+ elif grep B "$1"
+ then
+ echo Happy
+ else
+ echo "Huh?"
+ false
+ fi
+}
+
+test_expect_success 'apply --whitespace=strip' '
+
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ git apply --whitespace=strip patch.file &&
+ check_result sub/file1
+'
+
+test_expect_success 'apply --whitespace=strip from config' '
+
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ git config apply.whitespace strip &&
+ git apply patch.file &&
+ check_result sub/file1
+'
+
+D=`pwd`
+
+test_expect_success 'apply --whitespace=strip in subdir' '
+
+ cd "$D" &&
+ git config --unset-all apply.whitespace
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ cd sub &&
+ git apply --whitespace=strip ../patch.file &&
+ check_result file1
+'
+
+test_expect_success 'apply --whitespace=strip from config in subdir' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ cd sub &&
+ git apply ../patch.file &&
+ check_result file1
+'
+
+test_expect_success 'same in subdir but with traditional patch input' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ cd sub &&
+ git apply ../gpatch.file &&
+ check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 1' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ cd sub &&
+ git apply ../gpatch-sub.file &&
+ check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 2' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ cd sub &&
+ git apply ../gpatch-ab-sub.file &&
+ check_result file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 1' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ git apply -p0 gpatch-sub.file &&
+ check_result sub/file1
+'
+
+test_expect_success 'same but with traditional patch input of depth 2' '
+
+ cd "$D" &&
+ git config apply.whitespace strip &&
+ rm -f sub/file1 &&
+ cp saved sub/file1 &&
+ git update-index --refresh &&
+
+ git apply gpatch-ab-sub.file &&
+ check_result sub/file1
+'
+
+test_done