Merge branches 'lt/crlf' and 'jc/apply-config'
authorJunio C Hamano <junkio@cox.net>
Fri, 23 Feb 2007 05:34:36 +0000 (21:34 -0800)
committerJunio C Hamano <junkio@cox.net>
Fri, 23 Feb 2007 05:34:36 +0000 (21:34 -0800)
* lt/crlf:
  Teach core.autocrlf to 'git apply'
  t0020: add test for auto-crlf
  Make AutoCRLF ternary variable.
  Lazy man's auto-CRLF

* jc/apply-config:
  t4119: test autocomputing -p<n> for traditional diff input.
  git-apply: guess correct -p<n> value for non-git patches.
  git-apply: notice "diff --git" patch again
  Fix botched "leak fix"
  t4119: add test for traditional patch and different p_value
  apply: fix memory leak in prefix_one()
  git-apply: require -p<n> when working in a subdirectory.
  git-apply: do not lose cwd when run from a subdirectory.
  Teach 'git apply' to look at $HOME/.gitconfig even outside of a repository
  Teach 'git apply' to look at $GIT_DIR/config

Makefile
builtin-apply.c
cache.h
config.c
convert.c [new file with mode: 0644]
diff.c
entry.c
environment.c
sha1_file.c
t/t0020-crlf.sh [new file with mode: 0755]
t/t4119-apply-config.sh [new file with mode: 0755]

index 35be5e2..fbe05f9 100644 (file)
--- 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 \
index d678178..630d8fd 100644 (file)
@@ -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 04f8e63..8bbc142 100644 (file)
--- 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 */
index c938aa0..8b6cf1a 100644 (file)
--- 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 (file)
index 0000000..898bfe3
--- /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 019ecbc..5ecb122 100644 (file)
--- 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 c2641dd..472a9ef 100644 (file)
--- 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);
index 54c22f8..570e32a 100644 (file)
@@ -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;
index 2c87031..9a1dee0 100644 (file)
@@ -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 (executable)
index 0000000..723b29a
--- /dev/null
@@ -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 (executable)
index 0000000..620a920
--- /dev/null
@@ -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