{"thread":{"id":"41142","subject":"[PATCH v9F] ls-files: Add eol diagnostics","startedAt":"2016-01-07T15:17:38Z","lastAt":"2016-01-07T20:33:18Z","messageCount":2,"participants":["tboegi@web.de","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"275490","messageId":"1452179858-17791-1-git-send-email-tboegi@web.de","threadId":"41142","inReplyTo":null,"subject":"[PATCH v9F] ls-files: Add eol diagnostics","fromName":"","fromEmail":"tboegi@web.de","sentAt":"2016-01-07T15:17:38Z","receivedAt":"2016-01-07T15:17:38Z","isPatch":true,"sender":{"key":"tboegi@web.de","avatar":"https://avatars.githubusercontent.com/u/7138363?v=4"},"body":"From: Torsten Bögershausen <tboegi@web.de>\n\nWhen working in a cross-platform environment, a user wants to\ncheck if text files are stored normalized in the repository and if\n.gitattributes are set appropriately.\n\nMake it possible to let Git show the line endings in the index and\nin the working tree and the effective text/eol attributes.\n\nThe end of line (\"eolinfo\") are shown like this:\n\"binary\"       binary file\n\"none\"         text file without any EOL\n\"lf\"           text file with LF\n\"crlf\"         text file with CRLF\n\"mixed\"        text file with mixed line endings.\n\nThe effective text/eol attribute is one of these:\n\"\", \"-text\", \"text\", \"text=auto\", \"eol=lf\", \"eol=crlf\"\n\ngit ls-files --eol gives an output like this:\n\ni/none   w/none   attr/text=auto t/t5100/empty\ni/binary w/binary attr/-text     t/test-binary-2.png\ni/lf     w/lf     attr/eol=lf    t/t5100/rfc2047-info-0007\ni/lf     w/crlf   attr/eol=crlf  doit.bat\ni/mixed  w/mixed  attr/          locale/XX.po\n\nAdd test cases in t0027.\n\nHelped-By: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Torsten Bögershausen <tboegi@web.de>\n---\nRe-send using git send-email,\nthanks Junio for the patience\n Documentation/git-ls-files.txt |  23 +++++++++\n builtin/ls-files.c             |  19 +++++++\n convert.c                      |  85 +++++++++++++++++++++++++++++++\n convert.h                      |   3 ++\n t/t0027-auto-crlf.sh           | 112 ++++++++++++++++++++++++++++++++++++-----\n 5 files changed, 230 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\nindex e26f01f..e231266 100644\n--- a/Documentation/git-ls-files.txt\n+++ b/Documentation/git-ls-files.txt\n@@ -12,6 +12,7 @@ SYNOPSIS\n 'git ls-files' [-z] [-t] [-v]\n \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n \t\t(-[c|d|o|i|s|u|k|m])*\n+\t\t[--eol]\n \t\t[-x <pattern>|--exclude=<pattern>]\n \t\t[-X <file>|--exclude-from=<file>]\n \t\t[--exclude-per-directory=<file>]\n@@ -147,6 +148,19 @@ a space) at the start of each line:\n \tpossible for manual inspection; the exact format may change at\n \tany time.\n \n+--eol::\n+\tShow line endings (<eolinfo>) and the text/eol attributes (<texteolattr>) of files.\n+\t<eolinfo> is the file content identification used by Git when\n+\tthe \"text\" attribute is \"auto\" or \"\" and core.autocrlf != false.\n++\n+<eolinfo> is either \"\" (when the the info is not available\"), or one of \"binary\",\n+\"none\", \"lf\", \"crlf\" or \"mixed\".\n++\n+The <texteolattr> can be \"\", \"-text\", \"text\", \"text=auto\", \"eol=lf\", \"eol=crlf\".\n++\n+Both the content in the index (\"i/\") and the content in the working tree (\"w/\")\n+are shown for regular files, followed by the <texteolattr> (\"attr/\").\n+\n \\--::\n \tDo not interpret any more arguments as options.\n \n@@ -161,6 +175,15 @@ which case it outputs:\n \n         [<tag> ]<mode> <object> <stage> <file>\n \n+'git ls-files --eol' will show\n+\ti/<eolinfo> w/<eolinfo> attr/<eolattr> <file>\n+\n+'git ls-files --eol -o' will show\n+\ti/          w/<eolinfo> attr/<eolattr> <file>\n+\n+'git ls-files --eol -s' will show\n+[<tag> ]<mode> <object> <stage> i/<eolinfo> w/<eolinfo> attr/<eolattr> <file>\n+\n 'git ls-files --unmerged' and 'git ls-files --stage' can be used to examine\n detailed information on unmerged paths.\n \ndiff --git a/builtin/ls-files.c b/builtin/ls-files.c\nindex b6a7cb0..73b2ba6 100644\n--- a/builtin/ls-files.c\n+++ b/builtin/ls-files.c\n@@ -27,6 +27,7 @@ static int show_killed;\n static int show_valid_bit;\n static int line_terminator = '\\n';\n static int debug_mode;\n+static int show_eol;\n \n static const char *prefix;\n static int max_prefix_len;\n@@ -47,6 +48,21 @@ static const char *tag_modified = \"\";\n static const char *tag_skip_worktree = \"\";\n static const char *tag_resolve_undo = \"\";\n \n+static void write_eolinfo(const struct cache_entry *ce, const char *path)\n+{\n+\tstruct stat st;\n+\tconst char *i_txt = \"\";\n+\tconst char *w_txt = \"\";\n+\tif (!show_eol)\n+\t\treturn;\n+\tif (ce && S_ISREG(ce->ce_mode))\n+\t\ti_txt = get_cached_convert_stats_ascii(ce->name);\n+\tif (!lstat(path, &st) && S_ISREG(st.st_mode))\n+\t\tw_txt = get_wt_convert_stats_ascii(path);\n+\tprintf(\"i/%-6s w/%-6s attr/%-9s \", i_txt, w_txt,\n+\t\t\t\t get_convert_attr_ascii(path));\n+}\n+\n static void write_name(const char *name)\n {\n \t/*\n@@ -68,6 +84,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)\n \t\treturn;\n \n \tfputs(tag, stdout);\n+\twrite_eolinfo(NULL, ent->name);\n \twrite_name(ent->name);\n }\n \n@@ -170,6 +187,7 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce)\n \t\t       find_unique_abbrev(ce->sha1,abbrev),\n \t\t       ce_stage(ce));\n \t}\n+\twrite_eolinfo(ce, ce->name);\n \twrite_name(ce->name);\n \tif (debug_mode) {\n \t\tconst struct stat_data *sd = &ce->ce_stat_data;\n@@ -433,6 +451,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n \t\tOPT_BIT(0, \"directory\", &dir.flags,\n \t\t\tN_(\"show 'other' directories' names only\"),\n \t\t\tDIR_SHOW_OTHER_DIRECTORIES),\n+\t\tOPT_BOOL(0, \"eol\", &show_eol, N_(\"show line endings of files\")),\n \t\tOPT_NEGBIT(0, \"empty-directory\", &dir.flags,\n \t\t\tN_(\"don't show empty directories\"),\n \t\t\tDIR_HIDE_EMPTY_DIRECTORIES),\ndiff --git a/convert.c b/convert.c\nindex 814e814..3b805fa 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -13,6 +13,11 @@\n  * translation when the \"text\" attribute or \"auto_crlf\" option is set.\n  */\n \n+/* Stat bits: When BIN is set, the txt bits are unset */\n+#define CONVERT_STAT_BITS_TXT_LF   (1)\n+#define CONVERT_STAT_BITS_TXT_CRLF (2)\n+#define CONVERT_STAT_BITS_BIN      (4)\n+\n enum crlf_action {\n \tCRLF_GUESS = -1,\n \tCRLF_BINARY = 0,\n@@ -95,6 +100,62 @@ static int is_binary(unsigned long size, struct text_stat *stats)\n \treturn 0;\n }\n \n+static unsigned int gather_convert_stats(const char *data, unsigned long size)\n+{\n+\tstruct text_stat stats;\n+\tif (!data || !size)\n+\t\treturn 0;\n+\tgather_stats(data, size, &stats);\n+\tif (is_binary(size, &stats) || stats.cr != stats.crlf)\n+\t\treturn CONVERT_STAT_BITS_BIN;\n+\telse if (stats.crlf && stats.crlf == stats.lf)\n+\t\treturn CONVERT_STAT_BITS_TXT_CRLF;\n+\telse if (stats.crlf && stats.lf)\n+\t\treturn CONVERT_STAT_BITS_TXT_CRLF | CONVERT_STAT_BITS_TXT_LF;\n+\telse if (stats.lf)\n+\t\treturn CONVERT_STAT_BITS_TXT_LF;\n+\telse\n+\t\treturn 0;\n+}\n+\n+static const char *gather_convert_stats_ascii(const char *data, unsigned long size)\n+{\n+\tunsigned int convert_stats = gather_convert_stats(data, size);\n+\n+\tif (convert_stats & CONVERT_STAT_BITS_BIN)\n+\t\treturn \"binary\";\n+\tswitch (convert_stats) {\n+\t\tcase CONVERT_STAT_BITS_TXT_LF:\n+\t\t\treturn \"lf\";\n+\t\tcase CONVERT_STAT_BITS_TXT_CRLF:\n+\t\t\treturn \"crlf\";\n+\t\tcase CONVERT_STAT_BITS_TXT_LF | CONVERT_STAT_BITS_TXT_CRLF:\n+\t\t\treturn \"mixed\";\n+\t\tdefault:\n+\t\t\treturn \"none\";\n+\t}\n+}\n+\n+const char *get_cached_convert_stats_ascii(const char *path)\n+{\n+\tconst char *ret;\n+\tunsigned long sz;\n+\tvoid *data = read_blob_data_from_cache(path, &sz);\n+\tret = gather_convert_stats_ascii(data, sz);\n+\tfree(data);\n+\treturn ret;\n+}\n+\n+const char *get_wt_convert_stats_ascii(const char *path)\n+{\n+\tconst char *ret = \"\";\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tif (strbuf_read_file(&sb, path, 0) >= 0)\n+\t\tret = gather_convert_stats_ascii(sb.buf, sb.len);\n+\tstrbuf_release(&sb);\n+\treturn ret;\n+}\n+\n static enum eol output_eol(enum crlf_action crlf_action)\n {\n \tswitch (crlf_action) {\n@@ -777,6 +838,30 @@ int would_convert_to_git_filter_fd(const char *path)\n \treturn apply_filter(path, NULL, 0, -1, NULL, ca.drv->clean);\n }\n \n+const char *get_convert_attr_ascii(const char *path)\n+{\n+\tstruct conv_attrs ca;\n+\tenum crlf_action crlf_action;\n+\n+\tconvert_attrs(&ca, path);\n+\tcrlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);\n+\tswitch (crlf_action) {\n+\t\tcase CRLF_GUESS:\n+\t\t\treturn \"\";\n+\t\tcase CRLF_BINARY:\n+\t\t\treturn \"-text\";\n+\t\tcase CRLF_TEXT:\n+\t\t\treturn \"text\";\n+\t\tcase CRLF_INPUT:\n+\t\t\treturn \"eol=lf\";\n+\t\tcase CRLF_CRLF:\n+\t\t\treturn \"eol=crlf\";\n+\t\tcase CRLF_AUTO:\n+\t\t\treturn \"text=auto\";\n+\t}\n+\treturn \"\";\n+}\n+\n int convert_to_git(const char *path, const char *src, size_t len,\n                    struct strbuf *dst, enum safe_crlf checksafe)\n {\ndiff --git a/convert.h b/convert.h\nindex d9d853c..ccf436b 100644\n--- a/convert.h\n+++ b/convert.h\n@@ -32,6 +32,9 @@ enum eol {\n };\n \n extern enum eol core_eol;\n+extern const char *get_cached_convert_stats_ascii(const char *path);\n+extern const char *get_wt_convert_stats_ascii(const char *path);\n+extern const char *get_convert_attr_ascii(const char *path);\n \n /* returns 1 if *dst was used */\n extern int convert_to_git(const char *path, const char *src, size_t len,\ndiff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh\nindex b343651..082e7cd 100755\n--- a/t/t0027-auto-crlf.sh\n+++ b/t/t0027-auto-crlf.sh\n@@ -56,21 +56,16 @@ create_gitattributes () {\n }\n \n create_NNO_files () {\n-\tlfname=$1\n-\tcrlfname=$2\n-\tlfmixcrlf=$3\n-\tlfmixcr=$4\n-\tcrlfnul=$5\n \tfor crlf in false true input\n \tdo\n \t\tfor attr in \"\" auto text -text lf crlf\n \t\tdo\n \t\t\tpfx=NNO_${crlf}_attr_${attr} &&\n-\t\t\tcp $lfname    ${pfx}_LF.txt &&\n-\t\t\tcp $crlfname  ${pfx}_CRLF.txt &&\n-\t\t\tcp $lfmixcrlf ${pfx}_CRLF_mix_LF.txt &&\n-\t\t\tcp $lfmixcr   ${pfx}_LF_mix_CR.txt &&\n-\t\t\tcp $crlfnul   ${pfx}_CRLF_nul.txt\n+\t\t\tcp CRLF_mix_LF ${pfx}_LF.txt &&\n+\t\t\tcp CRLF_mix_LF ${pfx}_CRLF.txt &&\n+\t\t\tcp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt &&\n+\t\t\tcp CRLF_mix_LF ${pfx}_LF_mix_CR.txt &&\n+\t\t\tcp CRLF_mix_LF ${pfx}_CRLF_nul.txt\n \t\tdone\n \tdone\n }\n@@ -96,7 +91,7 @@ commit_check_warn () {\n \tcrlfnul=$7\n \tpfx=crlf_${crlf}_attr_${attr}\n \tcreate_gitattributes \"$attr\" &&\n-\tfor f in LF CRLF repoMIX LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul\n+\tfor f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul\n \tdo\n \t\tfname=${pfx}_$f.txt &&\n \t\tcp $f $fname &&\n@@ -149,6 +144,36 @@ commit_chk_wrnNNO () {\n \t'\n }\n \n+stats_ascii () {\n+\tcase \"$1\" in\n+\t\tLF)\n+\t\techo lf\n+\t\t;;\n+\t\tCRLF)\n+\t\techo crlf\n+\t\t;;\n+\t\tCRLF_mix_LF)\n+\t\techo mixed\n+\t\t;;\n+\t\tLF_mix_CR)\n+\t\techo binary\n+\t\t;;\n+\t\tCRLF_nul)\n+\t\techo binary\n+\t\t;;\n+\t\tLF_nul)\n+\t\techo binary\n+\t\t;;\n+\t\tCRLF_mix_CR)\n+\t\techo binary\n+\t\t;;\n+\t\t*)\n+\t\techo error_invalid $1\n+\t\t;;\n+\tesac\n+\n+}\n+\n check_files_in_repo () {\n \tcrlf=$1\n \tattr=$2\n@@ -214,6 +239,20 @@ checkout_files () {\n \t\tfi\n \tdone\n \n+\ttest_expect_success \"ls-files --eol $lfname ${pfx}LF.txt\" \"\n+\t\ttest_when_finished 'rm e expect actual' &&\n+\t\tcat >e <<-EOF &&\n+\t\ti/crlf w/$(stats_ascii $crlfname) ${src}CRLF.txt\n+\t\ti/mixed w/$(stats_ascii $lfmixcrlf) ${src}CRLF_mix_LF.txt\n+\t\ti/lf w/$(stats_ascii $lfname) ${src}LF.txt\n+\t\ti/binary w/$(stats_ascii $lfmixcr) ${src}LF_mix_CR.txt\n+\t\ti/binary w/$(stats_ascii $crlfnul) ${src}CRLF_nul.txt\n+\t\ti/binary w/$(stats_ascii $crlfnul) ${src}LF_nul.txt\n+\t\tEOF\n+\t\tsort <e >expect &&\n+\t\tgit ls-files --eol $src* | sed -e 's!attr/[=a-z-]*!!g' -e 's/  */ /g' | sort >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n \ttest_expect_success \"checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF\" \"\n \t\tcompare_ws_file $pfx $lfname    ${src}LF.txt\n \t\"\n@@ -231,7 +270,41 @@ checkout_files () {\n \t\"\n }\n \n-#######\n+# Test control characters\n+# NUL SOH CR EOF==^Z\n+test_expect_success 'ls-files --eol -o Text/Binary' '\n+\ttest_when_finished \"rm e expect actual TeBi_*\" &&\n+\tSTRT=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA &&\n+\tSTR=$STRT$STRT$STRT$STRT &&\n+\tprintf \"${STR}BBB\\001\" >TeBi_127_S &&\n+\tprintf \"${STR}BBBB\\001\">TeBi_128_S &&\n+\tprintf \"${STR}BBB\\032\" >TeBi_127_E &&\n+\tprintf \"\\032${STR}BBB\" >TeBi_E_127 &&\n+\tprintf \"${STR}BBBB\\000\">TeBi_128_N &&\n+\tprintf \"${STR}BBB\\012\">TeBi_128_L &&\n+\tprintf \"${STR}BBB\\015\">TeBi_127_C &&\n+\tprintf \"${STR}BB\\015\\012\" >TeBi_126_CL &&\n+\tprintf \"${STR}BB\\015\\012\\015\" >TeBi_126_CLC &&\n+\tcat >e <<-\\EOF &&\n+\ti/ w/binary TeBi_127_S\n+\ti/ w/none TeBi_128_S\n+\ti/ w/none TeBi_127_E\n+\ti/ w/binary TeBi_E_127\n+\ti/ w/binary TeBi_128_N\n+\ti/ w/lf TeBi_128_L\n+\ti/ w/binary TeBi_127_C\n+\ti/ w/crlf TeBi_126_CL\n+\ti/ w/binary TeBi_126_CLC\n+\tEOF\n+\tsort <e >expect &&\n+\tgit ls-files --eol -o |\n+\tsed -n -e \"/TeBi_/{s!attr/[=a-z-]*!!g\n+\ts!  *! !g\n+\tp\n+\t}\" | sort >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'setup master' '\n \techo >.gitattributes &&\n \tgit checkout -b master &&\n@@ -480,4 +553,19 @@ checkout_files    native  true  \"lf\"      LF    CRLF  CRLF_mix_LF  LF_mix_CR\n checkout_files    native  false \"crlf\"    CRLF  CRLF  CRLF         CRLF_mix_CR  CRLF_nul\n checkout_files    native  true  \"crlf\"    CRLF  CRLF  CRLF         CRLF_mix_CR  CRLF_nul\n \n+# Should be the last test case: remove some files from the worktree\n+# This test assumes that \"rm\" can remove staged files\n+test_expect_success 'ls-files --eol -d' \"\n+\trm crlf_false_attr__CRLF.txt crlf_false_attr__CRLF_mix_LF.txt crlf_false_attr__LF.txt .gitattributes &&\n+\tcat >expect <<-\\EOF &&\n+\ti/crlf w/ crlf_false_attr__CRLF.txt\n+\ti/lf w/ .gitattributes\n+\ti/lf w/ crlf_false_attr__LF.txt\n+\ti/mixed w/ crlf_false_attr__CRLF_mix_LF.txt\n+\tEOF\n+\tgit ls-files --eol -d | sed -e 's!attr/[=a-z-]*!!g' -e 's/  */ /g' | sort >actual &&\n+\ttest_cmp expect actual &&\n+\trm expect actual\n+\"\n+\n test_done\n-- \n2.6.2.403.g6abe3ff.dirty\n"},{"id":"275500","messageId":"xmqqh9ip3zap.fsf@gitster.mtv.corp.google.com","threadId":"41142","inReplyTo":"1452179858-17791-1-git-send-email-tboegi@web.de","subject":"Re: [PATCH v9F] ls-files: Add eol diagnostics","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-01-07T20:33:18Z","receivedAt":"2016-01-07T20:33:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"tboegi@web.de writes:\n\n> diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt\n> index e26f01f..e231266 100644\n> --- a/Documentation/git-ls-files.txt\n> +++ b/Documentation/git-ls-files.txt\n> @@ -12,6 +12,7 @@ SYNOPSIS\n>  'git ls-files' [-z] [-t] [-v]\n>  \t\t(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*\n>  \t\t(-[c|d|o|i|s|u|k|m])*\n> +\t\t[--eol]\n>  \t\t[-x <pattern>|--exclude=<pattern>]\n>  \t\t[-X <file>|--exclude-from=<file>]\n>  \t\t[--exclude-per-directory=<file>]\n> @@ -147,6 +148,19 @@ a space) at the start of each line:\n>  \tpossible for manual inspection; the exact format may change at\n>  \tany time.\n>  \n> +--eol::\n> +\tShow line endings (<eolinfo>) and the text/eol attributes (<texteolattr>) of files.\n\nThis line looks overly long.\n\nI think s/<texteolattr>/<eolattr>/ is needed to match what is shown\nin the example that appears later in the documentation.\n\n> +\t<eolinfo> is the file content identification used by Git when\n> +\tthe \"text\" attribute is \"auto\" or \"\" and core.autocrlf != false.\n> ++\n> +<eolinfo> is either \"\" (when the the info is not available\"), or one of \"binary\",\n> +\"none\", \"lf\", \"crlf\" or \"mixed\".\n> ++\n> +The <texteolattr> can be \"\", \"-text\", \"text\", \"text=auto\", \"eol=lf\", \"eol=crlf\".\n> ++\n> +Both the content in the index (\"i/\") and the content in the working tree (\"w/\")\n> +are shown for regular files, followed by the <texteolattr> (\"attr/\").\n> +\n\nThis makes the reader wonder what should happen for non-regular\nfiles.  Are they omitted from the output?  Are they shown but lack\nthese three fields?  Only i/ and w/ are missing but the attribute\nthat would have applied to them if they were regular files are\nshown?  Something else?\n\n> @@ -161,6 +175,15 @@ which case it outputs:\n>  \n>          [<tag> ]<mode> <object> <stage> <file>\n>  \n> +'git ls-files --eol' will show\n> +\ti/<eolinfo> w/<eolinfo> attr/<eolattr> <file>\n> +\n> +'git ls-files --eol -o' will show\n> +\ti/          w/<eolinfo> attr/<eolattr> <file>\n> +\n> +'git ls-files --eol -s' will show\n> +[<tag> ]<mode> <object> <stage> i/<eolinfo> w/<eolinfo> attr/<eolattr> <file>\n> +\n>  'git ls-files --unmerged' and 'git ls-files --stage' can be used to examine\n>  detailed information on unmerged paths.\n>  \n> diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n> index b6a7cb0..73b2ba6 100644\n> --- a/builtin/ls-files.c\n> +++ b/builtin/ls-files.c\n> @@ -27,6 +27,7 @@ static int show_killed;\n>  static int show_valid_bit;\n>  static int line_terminator = '\\n';\n>  static int debug_mode;\n> +static int show_eol;\n>  \n>  static const char *prefix;\n>  static int max_prefix_len;\n> @@ -47,6 +48,21 @@ static const char *tag_modified = \"\";\n>  static const char *tag_skip_worktree = \"\";\n>  static const char *tag_resolve_undo = \"\";\n>  \n> +static void write_eolinfo(const struct cache_entry *ce, const char *path)\n> +{\n> +\tstruct stat st;\n> +\tconst char *i_txt = \"\";\n> +\tconst char *w_txt = \"\";\n> +\tif (!show_eol)\n> +\t\treturn;\n> +\tif (ce && S_ISREG(ce->ce_mode))\n> +\t\ti_txt = get_cached_convert_stats_ascii(ce->name);\n> +\tif (!lstat(path, &st) && S_ISREG(st.st_mode))\n> +\t\tw_txt = get_wt_convert_stats_ascii(path);\n> +\tprintf(\"i/%-6s w/%-6s attr/%-9s \", i_txt, w_txt,\n> +\t\t\t\t get_convert_attr_ascii(path));\n> +}\n\nThis should interact with \"-z\" output mode in a more sensible way to\nallow easier machine parsing, no?  Aligning them with \"%-<N>s\" forces\nthe parsing scripts to strip excess spaces, and separating the fields\nwith hardcoded SP even more so.\n\n> diff --git a/convert.c b/convert.c\n> index 814e814..3b805fa 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -13,6 +13,11 @@\n>   * translation when the \"text\" attribute or \"auto_crlf\" option is set.\n>   */\n>  \n> +/* Stat bits: When BIN is set, the txt bits are unset */\n> +#define CONVERT_STAT_BITS_TXT_LF   (1)\n> +#define CONVERT_STAT_BITS_TXT_CRLF (2)\n> +#define CONVERT_STAT_BITS_BIN      (4)\n> +\n\nWhat are these parentheses doing aroud the constants?  Drop them.\nWrite the bit patterns in either hex or octal; if these are\nenumerations, they should stay in decimal.\n\n>  enum crlf_action {\n>  \tCRLF_GUESS = -1,\n>  \tCRLF_BINARY = 0,\n> @@ -95,6 +100,62 @@ static int is_binary(unsigned long size, struct text_stat *stats)\n>  \treturn 0;\n>  }\n>  \n> +static unsigned int gather_convert_stats(const char *data, unsigned long size)\n> +{\n> +\tstruct text_stat stats;\n> +\tif (!data || !size)\n> +\t\treturn 0;\n> +\tgather_stats(data, size, &stats);\n> +\tif (is_binary(size, &stats) || stats.cr != stats.crlf)\n> +\t\treturn CONVERT_STAT_BITS_BIN;\n> +\telse if (stats.crlf && stats.crlf == stats.lf)\n> +\t\treturn CONVERT_STAT_BITS_TXT_CRLF;\n> +\telse if (stats.crlf && stats.lf)\n> +\t\treturn CONVERT_STAT_BITS_TXT_CRLF | CONVERT_STAT_BITS_TXT_LF;\n> +\telse if (stats.lf)\n> +\t\treturn CONVERT_STAT_BITS_TXT_LF;\n> +\telse\n> +\t\treturn 0;\n> +}\n\nI'd imagine that this is attempting to match the existing logic of\nwhat Git actually does.  Shouldn't that existing logic share the\ncode with this function to avoid the two implemetations drifting\napart, or would that be too much work?\n\n> +static const char *gather_convert_stats_ascii(const char *data, unsigned long size)\n> +{\n> +\tunsigned int convert_stats = gather_convert_stats(data, size);\n> +\n> +\tif (convert_stats & CONVERT_STAT_BITS_BIN)\n> +\t\treturn \"binary\";\n> +\tswitch (convert_stats) {\n> +\t\tcase CONVERT_STAT_BITS_TXT_LF:\n> +\t\t\treturn \"lf\";\n\nThese case labels and their statements are indented one level too deep.\n\n> +\t\tcase CONVERT_STAT_BITS_TXT_CRLF:\n> +\t\t\treturn \"crlf\";\n> +\t\tcase CONVERT_STAT_BITS_TXT_LF | CONVERT_STAT_BITS_TXT_CRLF:\n> +\t\t\treturn \"mixed\";\n> +\t\tdefault:\n> +\t\t\treturn \"none\";\n> +\t}\n> +}\n\n> @@ -777,6 +838,30 @@ int would_convert_to_git_filter_fd(const char *path)\n>  \treturn apply_filter(path, NULL, 0, -1, NULL, ca.drv->clean);\n>  }\n>  \n> +const char *get_convert_attr_ascii(const char *path)\n> +{\n> +\tstruct conv_attrs ca;\n> +\tenum crlf_action crlf_action;\n> +\n> +\tconvert_attrs(&ca, path);\n> +\tcrlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr);\n> +\tswitch (crlf_action) {\n> +\t\tcase CRLF_GUESS:\n> +\t\t\treturn \"\";\n\nLikewise.\n\n> +stats_ascii () {\n> +\tcase \"$1\" in\n> +\t\tLF)\n> +\t\techo lf\n\nLikewise.\n\n> @@ -214,6 +239,20 @@ checkout_files () {\n>  \t\tfi\n>  \tdone\n>  \n> +\ttest_expect_success \"ls-files --eol $lfname ${pfx}LF.txt\" \"\n\nHmmmm, by opening the executable part of test_expect_success with\ndouble quote, you are interpolating the variables in it before the\ntest_expect_success function even starts to run.  It may happen to\nbe correct in this script to do so, but it adds more mental burden\nto readers (and more importantly, those who will be modifying this\nscript in the future).\n\n> +\t\ttest_when_finished 'rm e expect actual' &&\n> +\t\tcat >e <<-EOF &&\n> +\t\ti/crlf w/$(stats_ascii $crlfname) ${src}CRLF.txt\n> +\t\ti/mixed w/$(stats_ascii $lfmixcrlf) ${src}CRLF_mix_LF.txt\n> +\t\ti/lf w/$(stats_ascii $lfname) ${src}LF.txt\n> +\t\ti/binary w/$(stats_ascii $lfmixcr) ${src}LF_mix_CR.txt\n> +\t\ti/binary w/$(stats_ascii $crlfnul) ${src}CRLF_nul.txt\n> +\t\ti/binary w/$(stats_ascii $crlfnul) ${src}LF_nul.txt\n> +\t\tEOF\n> +\t\tsort <e >expect &&\n\nDo you even use 'e' after this step?  Why not just produce 'expect'\ndirectly by feeding the here-doc directly to sort?\n\n> @@ -231,7 +270,41 @@ checkout_files () {\n>  \t\"\n>  }\n> ...\n> +\tcat >e <<-\\EOF &&\n> +\t...\n> +\tEOF\n> +\tsort <e >expect &&\n\nLikewise.\n\n> +\tcat >expect <<-\\EOF &&\n> +\ti/crlf w/ crlf_false_attr__CRLF.txt\n> +\ti/lf w/ .gitattributes\n> +\ti/lf w/ crlf_false_attr__LF.txt\n> +\ti/mixed w/ crlf_false_attr__CRLF_mix_LF.txt\n> +\tEOF\n\nHere, there is no need to sort?\n\n> +\tgit ls-files --eol -d | sed -e 's!attr/[=a-z-]*!!g' -e 's/  */ /g' | sort >actual &&\n\nWrite each component of the pipe on a separate line, i.e.\n\n\tgit ls-files --eol -d |\n\tsed -e 's!attr/[=a-z-]*!!g' -e 's/  */ /g' |\n\tsort >actual &&\n\nAgain, the only reason you might want to use \"\" around the\nexecutable part of test_expect_success here is to use '' around the\nsed scripts here, but I do not think there is anything funny there\nthat requires single quotes, i.e.\n\n\ttest_expect_success 'title' '\n        \t...\n\t\tgit ls-files --eol -d |\n                sed -e \"s|attr/[=a-z-]*||g\" -e \"s/  */ /g\" |\n                sort >actual &&\n\t\t..\n\t'\n\nwould match our existing convention better.\n\n> +\ttest_cmp expect actual &&\n> +\trm expect actual\n\nWhy the final \"rm\"?\n\n> +\"\n> +\n>  test_done\n"}]}