Update 'crlf' attribute semantics.
authorJunio C Hamano <junkio@cox.net>
Fri, 20 Apr 2007 05:37:19 +0000 (22:37 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 20 Apr 2007 05:37:44 +0000 (22:37 -0700)
This updates the semantics of 'crlf' so that .gitattributes file
can say "this is text, even though it may look funny".

Setting the `crlf` attribute on a path is meant to mark the path
as a "text" file.  'core.autocrlf' conversion takes place
without guessing the content type by inspection.

Unsetting the `crlf` attribute on a path is meant to mark the
path as a "binary" file.  The path never goes through line
endings conversion upon checkin/checkout.

Unspecified `crlf` attribute tells git to apply the
`core.autocrlf` conversion when the file content looks like
text.

Setting the `crlf` attribut to string value "input" is similar
to setting the attribute to `true`, but also forces git to act
as if `core.autocrlf` is set to `input` for the path.

Signed-off-by: Junio C Hamano <junkio@cox.net>
convert.c
t/t0020-crlf.sh

index a5f60c7c6b729efbc7b98b1f5ac098cbf1bf9728..da64253a16e51881decd3e31d5763892f4847e5f 100644 (file)
--- a/convert.c
+++ b/convert.c
  * translation when the "auto_crlf" option is set.
  */
 
+#define CRLF_GUESS     (-1)
+#define CRLF_BINARY    0
+#define CRLF_TEXT      1
+#define CRLF_INPUT     2
+
 struct text_stat {
        /* CR, LF and CRLF counts */
        unsigned cr, lf, crlf;
@@ -74,13 +79,13 @@ static int is_binary(unsigned long size, struct text_stat *stats)
        return 0;
 }
 
-static int crlf_to_git(const char *path, char **bufp, unsigned long *sizep, int guess)
+static int crlf_to_git(const char *path, char **bufp, unsigned long *sizep, int action)
 {
        char *buffer, *nbuf;
        unsigned long size, nsize;
        struct text_stat stats;
 
-       if (guess && !auto_crlf)
+       if ((action == CRLF_BINARY) || (action == CRLF_GUESS && !auto_crlf))
                return 0;
 
        size = *sizep;
@@ -94,7 +99,7 @@ static int crlf_to_git(const char *path, char **bufp, unsigned long *sizep, int
        if (!stats.cr)
                return 0;
 
-       if (guess) {
+       if (action == CRLF_GUESS) {
                /*
                 * We're currently not going to even try to convert stuff
                 * that has bare CR characters. Does anybody do that crazy
@@ -119,7 +124,12 @@ static int crlf_to_git(const char *path, char **bufp, unsigned long *sizep, int
        *bufp = nbuf;
        *sizep = nsize;
 
-       if (guess) {
+       if (action == CRLF_GUESS) {
+               /*
+                * If we guessed, we already know we rejected a file with
+                * lone CR, and we can strip a CR without looking at what
+                * follow it.
+                */
                do {
                        unsigned char c = *buffer++;
                        if (c != '\r')
@@ -136,24 +146,15 @@ static int crlf_to_git(const char *path, char **bufp, unsigned long *sizep, int
        return 1;
 }
 
-static int autocrlf_to_git(const char *path, char **bufp, unsigned long *sizep)
-{
-       return crlf_to_git(path, bufp, sizep, 1);
-}
-
-static int forcecrlf_to_git(const char *path, char **bufp, unsigned long *sizep)
-{
-       return crlf_to_git(path, bufp, sizep, 0);
-}
-
-static int crlf_to_working_tree(const char *path, char **bufp, unsigned long *sizep, int guess)
+static int crlf_to_worktree(const char *path, char **bufp, unsigned long *sizep, int action)
 {
        char *buffer, *nbuf;
        unsigned long size, nsize;
        struct text_stat stats;
        unsigned char last;
 
-       if (guess && auto_crlf <= 0)
+       if ((action == CRLF_BINARY) || (action == CRLF_INPUT) ||
+           (action == CRLF_GUESS && auto_crlf <= 0))
                return 0;
 
        size = *sizep;
@@ -171,7 +172,7 @@ static int crlf_to_working_tree(const char *path, char **bufp, unsigned long *si
        if (stats.lf == stats.crlf)
                return 0;
 
-       if (guess) {
+       if (action == CRLF_GUESS) {
                /* If we have any bare CR characters, we're not going to touch it */
                if (stats.cr != stats.crlf)
                        return 0;
@@ -200,16 +201,6 @@ static int crlf_to_working_tree(const char *path, char **bufp, unsigned long *si
        return 1;
 }
 
-static int autocrlf_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
-{
-       return crlf_to_working_tree(path, bufp, sizep, 1);
-}
-
-static int forcecrlf_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
-{
-       return crlf_to_working_tree(path, bufp, sizep, 0);
-}
-
 static void setup_crlf_check(struct git_attr_check *check)
 {
        static struct git_attr *attr_crlf;
@@ -228,38 +219,24 @@ static int git_path_check_crlf(const char *path)
        if (!git_checkattr(path, 1, &attr_crlf_check)) {
                const char *value = attr_crlf_check.value;
                if (ATTR_TRUE(value))
-                       return 1;
+                       return CRLF_TEXT;
                else if (ATTR_FALSE(value))
-                       return 0;
+                       return CRLF_BINARY;
                else if (ATTR_UNSET(value))
                        ;
-               else
-                       die("unknown value %s given to 'crlf' attribute",
-                           (char *)value);
+               else if (!strcmp(value, "input"))
+                       return CRLF_INPUT;
+               /* fallthru */
        }
-       return -1;
+       return CRLF_GUESS;
 }
 
 int convert_to_git(const char *path, char **bufp, unsigned long *sizep)
 {
-       switch (git_path_check_crlf(path)) {
-       case 0:
-               return 0;
-       case 1:
-               return forcecrlf_to_git(path, bufp, sizep);
-       default:
-               return autocrlf_to_git(path, bufp, sizep);
-       }
+       return crlf_to_git(path, bufp, sizep, git_path_check_crlf(path));
 }
 
 int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep)
 {
-       switch (git_path_check_crlf(path)) {
-       case 0:
-               return 0;
-       case 1:
-               return forcecrlf_to_working_tree(path, bufp, sizep);
-       default:
-               return autocrlf_to_working_tree(path, bufp, sizep);
-       }
+       return crlf_to_worktree(path, bufp, sizep, git_path_check_crlf(path));
 }
index cf84f0a1ab5b392930f1d5a0008530399a6a00f6..fe1dfd08a02e7a8c9c26542e934ccd6fc4f16f5c 100755 (executable)
@@ -4,6 +4,10 @@ test_description='CRLF conversion'
 
 . ./test-lib.sh
 
+q_to_nul () {
+       tr Q '\0'
+}
+
 append_cr () {
        sed -e 's/$/Q/' | tr Q '\015'
 }
@@ -20,6 +24,7 @@ test_expect_success setup '
        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 &&
+       for w in Oh here is NULQin text here; do echo $w; done | q_to_nul >three &&
        git add . &&
 
        git commit -m initial &&
@@ -27,6 +32,7 @@ test_expect_success setup '
        one=`git rev-parse HEAD:one` &&
        dir=`git rev-parse HEAD:dir` &&
        two=`git rev-parse HEAD:dir/two` &&
+       three=`git rev-parse HEAD:three` &&
 
        for w in Some extra lines here; do echo $w; done >>one &&
        git diff >patch.file &&
@@ -38,7 +44,7 @@ test_expect_success setup '
 
 test_expect_success 'update with autocrlf=input' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git read-tree --reset -u HEAD &&
        git repo-config core.autocrlf input &&
 
@@ -62,7 +68,7 @@ test_expect_success 'update with autocrlf=input' '
 
 test_expect_success 'update with autocrlf=true' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git read-tree --reset -u HEAD &&
        git repo-config core.autocrlf true &&
 
@@ -86,7 +92,7 @@ test_expect_success 'update with autocrlf=true' '
 
 test_expect_success 'checkout with autocrlf=true' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -110,7 +116,7 @@ test_expect_success 'checkout with autocrlf=true' '
 
 test_expect_success 'checkout with autocrlf=input' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -136,7 +142,7 @@ test_expect_success 'checkout with autocrlf=input' '
 
 test_expect_success 'apply patch (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -149,7 +155,7 @@ test_expect_success 'apply patch (autocrlf=input)' '
 
 test_expect_success 'apply patch --cached (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -162,7 +168,7 @@ test_expect_success 'apply patch --cached (autocrlf=input)' '
 
 test_expect_success 'apply patch --index (autocrlf=input)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf input &&
        git read-tree --reset -u HEAD &&
 
@@ -176,7 +182,7 @@ test_expect_success 'apply patch --index (autocrlf=input)' '
 
 test_expect_success 'apply patch (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -189,7 +195,7 @@ test_expect_success 'apply patch (autocrlf=true)' '
 
 test_expect_success 'apply patch --cached (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -202,7 +208,7 @@ test_expect_success 'apply patch --cached (autocrlf=true)' '
 
 test_expect_success 'apply patch --index (autocrlf=true)' '
 
-       rm -f tmp one dir/two &&
+       rm -f tmp one dir/two three &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -216,8 +222,8 @@ test_expect_success 'apply patch --index (autocrlf=true)' '
 
 test_expect_success '.gitattributes says two is binary' '
 
+       rm -f tmp one dir/two three &&
        echo "two -crlf" >.gitattributes &&
-       rm -f tmp one dir/two &&
        git repo-config core.autocrlf true &&
        git read-tree --reset -u HEAD &&
 
@@ -230,6 +236,52 @@ test_expect_success '.gitattributes says two is binary' '
        fi &&
 
        if remove_cr one >/dev/null
+       then
+               : happy
+       else
+               echo "Huh?"
+               false
+       fi &&
+
+       if remove_cr three >/dev/null
+       then
+               echo "Huh?"
+               false
+       else
+               : happy
+       fi
+'
+
+test_expect_success '.gitattributes says two is input' '
+
+       rm -f tmp one dir/two three &&
+       echo "two crlf=input" >.gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr dir/two >/dev/null
+       then
+               echo "Huh?"
+               false
+       else
+               : happy
+       fi
+'
+
+test_expect_success '.gitattributes says two and three are text' '
+
+       rm -f tmp one dir/two three &&
+       echo "t* crlf" >.gitattributes &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr dir/two >/dev/null
+       then
+               : happy
+       else
+               echo "Huh?"
+               false
+       fi &&
+
+       if remove_cr three >/dev/null
        then
                : happy
        else