{"thread":{"id":"15905","subject":"builtin conversion between tabs and spaces","startedAt":"2008-10-15T01:44:10Z","lastAt":"2008-10-16T02:00:46Z","messageCount":11,"participants":["Stefan Karpinski","Alex Riesen","Johannes Sixt","Jonathan del Strother","A Large Angry SCM"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"93047","messageId":"d4bc1a2a0810141844x76223e76xf04e07ece834fc61@mail.gmail.com","threadId":"15905","inReplyTo":"d4bc1a2a0810141842q1e50c85au7d813f2e5e37a84c@mail.gmail.com","subject":"builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T01:44:10Z","receivedAt":"2008-10-15T01:44:10Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":"I find myself really wanting to enforce standards in the use of spaces\nversus tabs. I deal with some unruly programmers who refuse to set\ntheir editors to use soft tabs, but I *hate* tabs in the repo. And of\ncourse others feel equally strongly about keeping only tabs in the\nrepo (e.g. the git repo).\n\nThis led me to wonder if it wouldn't make sense to have this\nconversion ability built into git. The following patch implements this\nfunctionality. It still needs work—it's not meant to be final, just to\ngive an idea—but I just wanted to see if people on the git list\nthought this sort of thing would be worthwhile at all.\n\nIf people think it's worth having in git, then how should it be\nconfigured? I feel like a project should be able to define the\nexpected tab size for binary file types. Moreover, the project should\nbe able to define the default cannonicalization with resepect to\nwhitespace for different files types. Then, if they so desire, each\ngit user should be able to override the output format on a\nper-repository basis.\n\nDoes this make any sense? Comments?\n\n---\ndiff --git a/convert.c b/convert.c\nindex 1816e97..280f45b 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -18,7 +18,7 @@\n\n struct text_stat {\n       /* NUL, CR, LF and CRLF counts */\n-       unsigned nul, cr, lf, crlf;\n+       unsigned nul, cr, lf, crlf, tab;\n\n       /* These are just approximations! */\n       unsigned printable, nonprintable;\n@@ -48,7 +48,10 @@\n static void gather_stats(const char *buf, unsigned long size, struct\ntext_stat *\n               else if (c < 32) {\n                       switch (c) {\n                               /* BS, HT, ESC and FF */\n-                       case '\\b': case '\\t': case '\\033': case '\\014':\n+                       case '\\t':\n+                               stats->tab++;\n+                               /* fall through */\n+                       case '\\b': case '\\033': case '\\014':\n                               stats->printable++;\n                               break;\n                       case 0:\n@@ -235,6 +238,105 @@\n static int crlf_to_worktree(const char *path, const char *src, size_t len,\n       return 1;\n }\n\n+static int tabs_to_spaces(const char *path, const char *src, size_t len,\n+\n                  struct strbuf *buf, int untabify)\n+{\n+       char *to_free = NULL;\n+       struct text_stat stats;\n+  static const unsigned tab_size = 4;\n+       char *spaces;\n+\n+       if (!untabify)\n+               return 0;\n+\n+  /* instead of calling twice, should cache these stats across calls */\n+       gather_stats(src, len, &stats);\n+\n+       if (!stats.tab)\n+    return 0;\n+\n+       /* are we \"faking\" in place editing ? */\n+       if (src == buf->buf)\n+               to_free = strbuf_detach(buf, NULL);\n+\n+  /* this growth may be excessive: not all tabs get tab_size spaces */\n+       strbuf_grow(buf, len + tab_size * stats.tab);\n+  spaces = (char *) xmalloc(tab_size);\n+  memset(spaces, ' ', tab_size);\n+       for (;;) {\n+               const char *line = src;\n+               const char *nl = memchr(src, '\\n', len);\n+               char *tab;\n+    if (!nl)\n+      nl = src + len;\n+    while (src < nl && (tab = memchr(src, '\\t', nl - src))) {\n+      strbuf_add(buf, src, tab - src);\n+      strbuf_add(buf, spaces, tab_size - ((tab - line) % tab_size));\n+      src = tab + 1;\n+    }\n+    if (src < nl)\n+      strbuf_add(buf, src, nl - src);\n+               if (nl < src + len)\n+                       strbuf_addch(buf, '\\n');\n+               else\n+                       break;\n+               src = nl + 1;\n+               len -= src - line;\n+       }\n+\n+       free(to_free);\n+       free(spaces);\n+       return 1;\n+}\n+\n+static int spaces_to_tabs(const char *path, const char *src, size_t len,\n+\n                  struct strbuf *buf, int tabify)\n+{\n+  static const unsigned tab_size = 4;\n+\n+       if (!tabify)\n+               return 0;\n+\n+       /* only grow if not in place */\n+       if (strbuf_avail(buf) + buf->len < len)\n+               strbuf_grow(buf, len - buf->len);\n+\n+       for (;;) {\n+               int tabs = 0, spaces = 0;\n+               const char *line = src;\n+               const char *nl = memchr(src, '\\n', len);\n+    if (!nl)\n+      nl = src + len;\n+               for (;; src++) {\n+                       if (*src == ' ') {\n+                               spaces++;\n+                               if (spaces == tab_size) {\n+                                       tabs++;\n+                                       spaces = 0;\n+                               }\n+                       } else if (*src == '\\t') {\n+                               tabs++;\n+                               spaces = 0;\n+                       } else break;\n+               }\n+               if (line < src) {\n+                       memset(buf->buf + buf->len, '\\t', tabs);\n+                       memset(buf->buf + buf->len + tabs, ' ', spaces);\n+                       strbuf_setlen(buf, buf->len + tabs + spaces);\n+               }\n+    if (src < nl)\n+      strbuf_add(buf, src, nl - src);\n+               if (nl < src + len)\n+                       strbuf_addch(buf, '\\n');\n+               else\n+                       break;\n+               src = nl + 1;\n+               len -= src - line;\n+       }\n+\n+       return 1;\n+}\n+\n struct filter_params {\n       const char *src;\n       unsigned long size;\n@@ -370,22 +472,29 @@\n static int read_convert_config(const char *var, const char *value, void *cb)\n       return 0;\n }\n\n-static void setup_convert_check(struct git_attr_check *check)\n+struct convert_checks {\n+  struct git_attr_check crlf, tabs, ident, filter;\n+};\n+\n+static void setup_convert_check(struct convert_checks *checks)\n {\n       static struct git_attr *attr_crlf;\n+       static struct git_attr *attr_tabs;\n       static struct git_attr *attr_ident;\n       static struct git_attr *attr_filter;\n\n       if (!attr_crlf) {\n               attr_crlf = git_attr(\"crlf\", 4);\n+               attr_tabs = git_attr(\"tabs\", 4);\n               attr_ident = git_attr(\"ident\", 5);\n               attr_filter = git_attr(\"filter\", 6);\n               user_convert_tail = &user_convert;\n               git_config(read_convert_config, NULL);\n       }\n-       check[0].attr = attr_crlf;\n-       check[1].attr = attr_ident;\n-       check[2].attr = attr_filter;\n+       checks->crlf.attr = attr_crlf;\n+       checks->tabs.attr = attr_tabs;\n+       checks->ident.attr = attr_ident;\n+       checks->filter.attr = attr_filter;\n }\n\n static int count_ident(const char *cp, unsigned long size)\n@@ -566,20 +675,22 @@\n static int git_path_check_ident(const char *path, struct git_attr_check *check)\n       return !!ATTR_TRUE(value);\n }\n\n+#define CHECK_ARRAY_SIZE (sizeof(struct convert_checks)/sizeof(struct\ngit_attr_check))\n+\n int convert_to_git(const char *path, const char *src, size_t len,\n                   struct strbuf *dst, enum safe_crlf checksafe)\n {\n-       struct git_attr_check check[3];\n+       struct convert_checks checks;\n       int crlf = CRLF_GUESS;\n       int ident = 0, ret = 0;\n       const char *filter = NULL;\n\n-       setup_convert_check(check);\n-       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {\n+       setup_convert_check(&checks);\n+       if (!git_checkattr(path, CHECK_ARRAY_SIZE, (struct git_attr_check *)\n&checks)) {\n               struct convert_driver *drv;\n-               crlf = git_path_check_crlf(path, check + 0);\n-               ident = git_path_check_ident(path, check + 1);\n-               drv = git_path_check_convert(path, check + 2);\n+               crlf = git_path_check_crlf(path, &(checks.crlf));\n+               ident = git_path_check_ident(path, &(checks.ident));\n+               drv = git_path_check_convert(path, &(checks.filter));\n               if (drv && drv->clean)\n                       filter = drv->clean;\n       }\n@@ -589,6 +700,11 @@\n int convert_to_git(const char *path, const char *src, size_t len,\n               src = dst->buf;\n               len = dst->len;\n       }\n+       ret |= tabs_to_spaces(path, src, len, dst, 1); // get real variable\n+       if (ret) {\n+               src = dst->buf;\n+               len = dst->len;\n+       }\n       ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);\n       if (ret) {\n               src = dst->buf;\n@@ -599,17 +715,17 @@\n int convert_to_git(const char *path, const char *src, size_t len,\n\n int convert_to_working_tree(const char *path, const char *src, size_t\nlen, struct strbuf *dst)\n {\n-       struct git_attr_check check[3];\n+       struct convert_checks checks;\n       int crlf = CRLF_GUESS;\n       int ident = 0, ret = 0;\n       const char *filter = NULL;\n\n-       setup_convert_check(check);\n-       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {\n+       setup_convert_check(&checks);\n+       if (!git_checkattr(path, CHECK_ARRAY_SIZE, (struct git_attr_check *)\n&checks)) {\n               struct convert_driver *drv;\n-               crlf = git_path_check_crlf(path, check + 0);\n-               ident = git_path_check_ident(path, check + 1);\n-               drv = git_path_check_convert(path, check + 2);\n+               crlf = git_path_check_crlf(path, &(checks.crlf));\n+               ident = git_path_check_ident(path, &(checks.ident));\n+               drv = git_path_check_convert(path, &(checks.filter));\n               if (drv && drv->smudge)\n                       filter = drv->smudge;\n       }\n@@ -624,5 +740,10 @@\n int convert_to_working_tree(const char *path, const char *src, size_t\nlen, struc\n               src = dst->buf;\n               len = dst->len;\n       }\n+       ret |= tabs_to_spaces(path, src, len, dst, 1); // get real variable\n+       if (ret) {\n+               src = dst->buf;\n+               len = dst->len;\n+       }\n       return ret | apply_filter(path, src, len, dst, filter);\n }\n"},{"id":"93048","messageId":"d4bc1a2a0810141847n1c3b9fb8u25d8f6fbb3fd10a@mail.gmail.com","threadId":"15905","inReplyTo":"d4bc1a2a0810141844x76223e76xf04e07ece834fc61@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T01:47:00Z","receivedAt":"2008-10-15T01:47:00Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":"Appologies for gmail mangling. I will use git send-email for real patches.\n\nOn Tue, Oct 14, 2008 at 6:44 PM, Stefan Karpinski\n<stefan.karpinski@gmail.com> wrote:\n> I find myself really wanting to enforce standards in the use of spaces\n> versus tabs. I deal with some unruly programmers who refuse to set\n> their editors to use soft tabs, but I *hate* tabs in the repo. And of\n> course others feel equally strongly about keeping only tabs in the\n> repo (e.g. the git repo).\n>\n> This led me to wonder if it wouldn't make sense to have this\n> conversion ability built into git. The following patch implements this\n> functionality. It still needs work—it's not meant to be final, just to\n> give an idea—but I just wanted to see if people on the git list\n> thought this sort of thing would be worthwhile at all.\n>\n> If people think it's worth having in git, then how should it be\n> configured? I feel like a project should be able to define the\n> expected tab size for binary file types. Moreover, the project should\n> be able to define the default cannonicalization with resepect to\n> whitespace for different files types. Then, if they so desire, each\n> git user should be able to override the output format on a\n> per-repository basis.\n>\n> Does this make any sense? Comments?\n>\n> ---\n> diff --git a/convert.c b/convert.c\n> index 1816e97..280f45b 100644\n> --- a/convert.c\n> +++ b/convert.c\n> @@ -18,7 +18,7 @@\n>\n>  struct text_stat {\n>       /* NUL, CR, LF and CRLF counts */\n> -       unsigned nul, cr, lf, crlf;\n> +       unsigned nul, cr, lf, crlf, tab;\n>\n>       /* These are just approximations! */\n>       unsigned printable, nonprintable;\n> @@ -48,7 +48,10 @@\n>  static void gather_stats(const char *buf, unsigned long size, struct\n> text_stat *\n>               else if (c < 32) {\n>                       switch (c) {\n>                               /* BS, HT, ESC and FF */\n> -                       case '\\b': case '\\t': case '\\033': case '\\014':\n> +                       case '\\t':\n> +                               stats->tab++;\n> +                               /* fall through */\n> +                       case '\\b': case '\\033': case '\\014':\n>                               stats->printable++;\n>                               break;\n>                       case 0:\n> @@ -235,6 +238,105 @@\n>  static int crlf_to_worktree(const char *path, const char *src, size_t len,\n>       return 1;\n>  }\n>\n> +static int tabs_to_spaces(const char *path, const char *src, size_t len,\n> +\n>                  struct strbuf *buf, int untabify)\n> +{\n> +       char *to_free = NULL;\n> +       struct text_stat stats;\n> +  static const unsigned tab_size = 4;\n> +       char *spaces;\n> +\n> +       if (!untabify)\n> +               return 0;\n> +\n> +  /* instead of calling twice, should cache these stats across calls */\n> +       gather_stats(src, len, &stats);\n> +\n> +       if (!stats.tab)\n> +    return 0;\n> +\n> +       /* are we \"faking\" in place editing ? */\n> +       if (src == buf->buf)\n> +               to_free = strbuf_detach(buf, NULL);\n> +\n> +  /* this growth may be excessive: not all tabs get tab_size spaces */\n> +       strbuf_grow(buf, len + tab_size * stats.tab);\n> +  spaces = (char *) xmalloc(tab_size);\n> +  memset(spaces, ' ', tab_size);\n> +       for (;;) {\n> +               const char *line = src;\n> +               const char *nl = memchr(src, '\\n', len);\n> +               char *tab;\n> +    if (!nl)\n> +      nl = src + len;\n> +    while (src < nl && (tab = memchr(src, '\\t', nl - src))) {\n> +      strbuf_add(buf, src, tab - src);\n> +      strbuf_add(buf, spaces, tab_size - ((tab - line) % tab_size));\n> +      src = tab + 1;\n> +    }\n> +    if (src < nl)\n> +      strbuf_add(buf, src, nl - src);\n> +               if (nl < src + len)\n> +                       strbuf_addch(buf, '\\n');\n> +               else\n> +                       break;\n> +               src = nl + 1;\n> +               len -= src - line;\n> +       }\n> +\n> +       free(to_free);\n> +       free(spaces);\n> +       return 1;\n> +}\n> +\n> +static int spaces_to_tabs(const char *path, const char *src, size_t len,\n> +\n>                  struct strbuf *buf, int tabify)\n> +{\n> +  static const unsigned tab_size = 4;\n> +\n> +       if (!tabify)\n> +               return 0;\n> +\n> +       /* only grow if not in place */\n> +       if (strbuf_avail(buf) + buf->len < len)\n> +               strbuf_grow(buf, len - buf->len);\n> +\n> +       for (;;) {\n> +               int tabs = 0, spaces = 0;\n> +               const char *line = src;\n> +               const char *nl = memchr(src, '\\n', len);\n> +    if (!nl)\n> +      nl = src + len;\n> +               for (;; src++) {\n> +                       if (*src == ' ') {\n> +                               spaces++;\n> +                               if (spaces == tab_size) {\n> +                                       tabs++;\n> +                                       spaces = 0;\n> +                               }\n> +                       } else if (*src == '\\t') {\n> +                               tabs++;\n> +                               spaces = 0;\n> +                       } else break;\n> +               }\n> +               if (line < src) {\n> +                       memset(buf->buf + buf->len, '\\t', tabs);\n> +                       memset(buf->buf + buf->len + tabs, ' ', spaces);\n> +                       strbuf_setlen(buf, buf->len + tabs + spaces);\n> +               }\n> +    if (src < nl)\n> +      strbuf_add(buf, src, nl - src);\n> +               if (nl < src + len)\n> +                       strbuf_addch(buf, '\\n');\n> +               else\n> +                       break;\n> +               src = nl + 1;\n> +               len -= src - line;\n> +       }\n> +\n> +       return 1;\n> +}\n> +\n>  struct filter_params {\n>       const char *src;\n>       unsigned long size;\n> @@ -370,22 +472,29 @@\n>  static int read_convert_config(const char *var, const char *value, void *cb)\n>       return 0;\n>  }\n>\n> -static void setup_convert_check(struct git_attr_check *check)\n> +struct convert_checks {\n> +  struct git_attr_check crlf, tabs, ident, filter;\n> +};\n> +\n> +static void setup_convert_check(struct convert_checks *checks)\n>  {\n>       static struct git_attr *attr_crlf;\n> +       static struct git_attr *attr_tabs;\n>       static struct git_attr *attr_ident;\n>       static struct git_attr *attr_filter;\n>\n>       if (!attr_crlf) {\n>               attr_crlf = git_attr(\"crlf\", 4);\n> +               attr_tabs = git_attr(\"tabs\", 4);\n>               attr_ident = git_attr(\"ident\", 5);\n>               attr_filter = git_attr(\"filter\", 6);\n>               user_convert_tail = &user_convert;\n>               git_config(read_convert_config, NULL);\n>       }\n> -       check[0].attr = attr_crlf;\n> -       check[1].attr = attr_ident;\n> -       check[2].attr = attr_filter;\n> +       checks->crlf.attr = attr_crlf;\n> +       checks->tabs.attr = attr_tabs;\n> +       checks->ident.attr = attr_ident;\n> +       checks->filter.attr = attr_filter;\n>  }\n>\n>  static int count_ident(const char *cp, unsigned long size)\n> @@ -566,20 +675,22 @@\n>  static int git_path_check_ident(const char *path, struct git_attr_check *check)\n>       return !!ATTR_TRUE(value);\n>  }\n>\n> +#define CHECK_ARRAY_SIZE (sizeof(struct convert_checks)/sizeof(struct\n> git_attr_check))\n> +\n>  int convert_to_git(const char *path, const char *src, size_t len,\n>                   struct strbuf *dst, enum safe_crlf checksafe)\n>  {\n> -       struct git_attr_check check[3];\n> +       struct convert_checks checks;\n>       int crlf = CRLF_GUESS;\n>       int ident = 0, ret = 0;\n>       const char *filter = NULL;\n>\n> -       setup_convert_check(check);\n> -       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {\n> +       setup_convert_check(&checks);\n> +       if (!git_checkattr(path, CHECK_ARRAY_SIZE, (struct git_attr_check *)\n> &checks)) {\n>               struct convert_driver *drv;\n> -               crlf = git_path_check_crlf(path, check + 0);\n> -               ident = git_path_check_ident(path, check + 1);\n> -               drv = git_path_check_convert(path, check + 2);\n> +               crlf = git_path_check_crlf(path, &(checks.crlf));\n> +               ident = git_path_check_ident(path, &(checks.ident));\n> +               drv = git_path_check_convert(path, &(checks.filter));\n>               if (drv && drv->clean)\n>                       filter = drv->clean;\n>       }\n> @@ -589,6 +700,11 @@\n>  int convert_to_git(const char *path, const char *src, size_t len,\n>               src = dst->buf;\n>               len = dst->len;\n>       }\n> +       ret |= tabs_to_spaces(path, src, len, dst, 1); // get real variable\n> +       if (ret) {\n> +               src = dst->buf;\n> +               len = dst->len;\n> +       }\n>       ret |= crlf_to_git(path, src, len, dst, crlf, checksafe);\n>       if (ret) {\n>               src = dst->buf;\n> @@ -599,17 +715,17 @@\n>  int convert_to_git(const char *path, const char *src, size_t len,\n>\n>  int convert_to_working_tree(const char *path, const char *src, size_t\n> len, struct strbuf *dst)\n>  {\n> -       struct git_attr_check check[3];\n> +       struct convert_checks checks;\n>       int crlf = CRLF_GUESS;\n>       int ident = 0, ret = 0;\n>       const char *filter = NULL;\n>\n> -       setup_convert_check(check);\n> -       if (!git_checkattr(path, ARRAY_SIZE(check), check)) {\n> +       setup_convert_check(&checks);\n> +       if (!git_checkattr(path, CHECK_ARRAY_SIZE, (struct git_attr_check *)\n> &checks)) {\n>               struct convert_driver *drv;\n> -               crlf = git_path_check_crlf(path, check + 0);\n> -               ident = git_path_check_ident(path, check + 1);\n> -               drv = git_path_check_convert(path, check + 2);\n> +               crlf = git_path_check_crlf(path, &(checks.crlf));\n> +               ident = git_path_check_ident(path, &(checks.ident));\n> +               drv = git_path_check_convert(path, &(checks.filter));\n>               if (drv && drv->smudge)\n>                       filter = drv->smudge;\n>       }\n> @@ -624,5 +740,10 @@\n>  int convert_to_working_tree(const char *path, const char *src, size_t\n> len, struc\n>               src = dst->buf;\n>               len = dst->len;\n>       }\n> +       ret |= tabs_to_spaces(path, src, len, dst, 1); // get real variable\n> +       if (ret) {\n> +               src = dst->buf;\n> +               len = dst->len;\n> +       }\n>       return ret | apply_filter(path, src, len, dst, filter);\n>  }\n>\n"},{"id":"93055","messageId":"20081015062539.GB3775@blimp.localdomain","threadId":"15905","inReplyTo":"d4bc1a2a0810141844x76223e76xf04e07ece834fc61@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2008-10-15T06:25:40Z","receivedAt":"2008-10-15T06:25:40Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Stefan Karpinski, Wed, Oct 15, 2008 03:44:10 +0200:\n> I find myself really wanting to enforce standards in the use of spaces\n> versus tabs. I deal with some unruly programmers who refuse to set\n> their editors to use soft tabs, but I *hate* tabs in the repo. And of\n> course others feel equally strongly about keeping only tabs in the\n> repo (e.g. the git repo).\n> \n> This led me to wonder if it wouldn't make sense to have this\n> conversion ability built into git. The following patch implements this\n> functionality. It still needs work—it's not meant to be final, just to\n> give an idea—but I just wanted to see if people on the git list\n> thought this sort of thing would be worthwhile at all.\n\nIs your conversion two-way? IOW, is it possible to convert the\nconverted file and get the original? (Because all the existing\nconversions are reversible)\n"},{"id":"93056","messageId":"48F58D1C.2040208@viscovery.net","threadId":"15905","inReplyTo":"d4bc1a2a0810141844x76223e76xf04e07ece834fc61@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-10-15T06:26:36Z","receivedAt":"2008-10-15T06:26:36Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Stefan Karpinski schrieb:\n> This led me to wonder if it wouldn't make sense to have this\n> conversion ability built into git.\n\nThis wouldn't help your case a lot. It is still at the discretion of each\nindividual repository owner to enable the conversion. (You didn't mean to\nmake this conversion mandatory, did you?)\n\nBTW, you don't need to change git code to achieve this. It's sufficient to\ninstall a suitable \"clean\" filter:\n\necho \"*.c filter=c-code\" > .git/info/attributes\ngit config filter.c-code.clean tabs2spaces\n\nwhere tabs2spaces is your utility that does the conversion.\n\n-- Hannes\n"},{"id":"93144","messageId":"d4bc1a2a0810151352s6c963e32jc4f492a7c84841dc@mail.gmail.com","threadId":"15905","inReplyTo":"20081015062539.GB3775@blimp.localdomain","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T20:52:15Z","receivedAt":"2008-10-15T20:52:15Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":"> Is your conversion two-way? IOW, is it possible to convert the\n> converted file and get the original? (Because all the existing\n> conversions are reversible)\n\nYes and no. The CRLF conversion isn't always invertable—it is so long\nas your use of CRLF/LF is consistent. The tab/space conversion is\nsimilar: if you consistently use spaces, then tabs_to_spaces will\nalways give you back your original version; if you consistently use\ntabs, then spaces_to_tabs will give you back your original version. If\nyou use some crazy mix of the two, you cannot reconstruct your\noriginal without remembering where there were tabs versus spaces,\nwhich information either filter destroys. But that's the same as the\nCRLF conversion. You could enable a warning when worktree file has an\ninconsistent mixture of tabs and spaces, like there is for\ninconsistent CRLF files.\n\nOn Tue, Oct 14, 2008 at 11:25 PM, Alex Riesen <raa.lkml@gmail.com> wrote:\n> Stefan Karpinski, Wed, Oct 15, 2008 03:44:10 +0200:\n>> I find myself really wanting to enforce standards in the use of spaces\n>> versus tabs. I deal with some unruly programmers who refuse to set\n>> their editors to use soft tabs, but I *hate* tabs in the repo. And of\n>> course others feel equally strongly about keeping only tabs in the\n>> repo (e.g. the git repo).\n>>\n>> This led me to wonder if it wouldn't make sense to have this\n>> conversion ability built into git. The following patch implements this\n>> functionality. It still needs work—it's not meant to be final, just to\n>> give an idea—but I just wanted to see if people on the git list\n>> thought this sort of thing would be worthwhile at all.\n>\n> Is your conversion two-way? IOW, is it possible to convert the\n> converted file and get the original? (Because all the existing\n> conversions are reversible)\n>\n"},{"id":"93145","messageId":"d4bc1a2a0810151355q283d7a4y7bf35a3423cf1d46@mail.gmail.com","threadId":"15905","inReplyTo":"48F58D1C.2040208@viscovery.net","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T20:55:13Z","receivedAt":"2008-10-15T20:55:13Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":">> This led me to wonder if it wouldn't make sense to have this\n>> conversion ability built into git.\n>\n> This wouldn't help your case a lot. It is still at the discretion of each\n> individual repository owner to enable the conversion. (You didn't mean to\n> make this conversion mandatory, did you?)\n\nThis would all be completely discretionary, of course. I just thought\nthat the issue was complicated enough and the problem of mixed spaces\nand tabs in source code ubiquitous enough that it might be worth\n\"solving\" correctly in git itself.\n\n> BTW, you don't need to change git code to achieve this. It's sufficient to\n> install a suitable \"clean\" filter:\n>\n> echo \"*.c filter=c-code\" > .git/info/attributes\n> git config filter.c-code.clean tabs2spaces\n>\n> where tabs2spaces is your utility that does the conversion.\n\nThat was the first thing I did—in ruby first, then in C. The ruby\nversion is *way* too slow to use on any number of files—and the\ntypical use case is that most of your files are source code and so\nwill have this applied to them. On the other hand, the ruby script (or\nperl or whatever) is portable, so I can send it to my Windows-based\ndevelopers and have them use it too.\n\nBut the speed issue is pretty crucial, so I rewrote the thing as an\nexternal C program, which is obviously much faster. But it's still\nnoticably slower than native git checkout though. Worse still, now the\nfilter script isn't portable, so I have to cross-compile it and give\nevery developer the version that works on their platform and make sure\nthat they have their path setup correctly, etc.\n\nHaving the conversion built into git would\n  1) have native git checkout speed (I've tried it and it's not\nnoticable slower than normal git checkouts);\n  2) be naturally portable to anywhere git works.\n\nNow, if I thought this was a thing that would only benefit me, I would\nnever bring it up. However, it seems like something that a lot of\nprojects might want to use to ensure standard spacing in their source\nfiles. Moreover, since developers feel strongly about using tabs or\nspaces, it would be beneficial for individuals too. I think there's\nenough complex issues that solving it once-and-for-all might be worth\na shot.\n"},{"id":"93146","messageId":"57518fd10810151402p4ea3283anf4b3d175c4e82425@mail.gmail.com","threadId":"15905","inReplyTo":"d4bc1a2a0810151352s6c963e32jc4f492a7c84841dc@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Jonathan del Strother","fromEmail":"maillist@steelskies.com","sentAt":"2008-10-15T21:02:32Z","receivedAt":"2008-10-15T21:02:32Z","isPatch":false,"sender":{"key":"jon.delstrother@bestbefore.tv","avatar":"https://gravatar.com/avatar/754e21ab701c00e2d21fc261187254c34b2a1c0b959d9ee5be1a295990be3081?d=mp&s=160"},"body":"On Wed, Oct 15, 2008 at 9:52 PM, Stefan Karpinski\n<stefan.karpinski@gmail.com> wrote:\n> if you consistently use spaces, then tabs_to_spaces will\n> always give you back your original version; if you consistently use\n> tabs, then spaces_to_tabs will give you back your original version. If\n> you use some crazy mix of the two, you cannot reconstruct your\n> original without remembering where there were tabs versus spaces,\n\nJust IMO, a crazy mix of tabs and spaces is the only _sane_ thing to\ndo.  Using tabs for the initial indentation, plus spaces for alignment\nof function arguments / comments / whatever, is the only way of\ngetting a layout that will both look right regardless of the tab size,\nand allow a viewer to alter the indentation size.\n"},{"id":"93149","messageId":"d4bc1a2a0810151418r3bf21ddaj498017e8e178f579@mail.gmail.com","threadId":"15905","inReplyTo":"57518fd10810151402p4ea3283anf4b3d175c4e82425@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T21:18:22Z","receivedAt":"2008-10-15T21:18:22Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":">> if you consistently use spaces, then tabs_to_spaces will\n>> always give you back your original version; if you consistently use\n>> tabs, then spaces_to_tabs will give you back your original version. If\n>> you use some crazy mix of the two, you cannot reconstruct your\n>> original without remembering where there were tabs versus spaces,\n>\n> Just IMO, a crazy mix of tabs and spaces is the only _sane_ thing to\n> do.  Using tabs for the initial indentation, plus spaces for alignment\n> of function arguments / comments / whatever, is the only way of\n> getting a layout that will both look right regardless of the tab size,\n> and allow a viewer to alter the indentation size.\n\nThat's not what I would call a \"crazy\" mix of tabs and spaces, but\nrather a *sane* mix of tabs and spaces. That can consistently be\nreproduced, and is in fact what the spaces_to_tabs function included\nabove produces. The sane consistent formats as I see it are:\n\n  1) use spaces for everything\n  2) use tabs for indentation, spaces for everything else\n  3) use tabs for indentation and alignment\n\nIf you know the tab size, you can reproduce any of these from the\nothers, except that #3 is a little tricky since there's places where\nthe tab/space issue can be ambiguous. I actually think that keeping\nthe repo version with tab-based indentation is a very sane thing to\ndo. However, I'd still like to be able to edit the files using soft\ntabs, largely because any program that doesn't know what my tab size\nshould be applies its own interpretation and makes the code look\nterrible (think terminal output for diff, cat, less, etc.)\n\nOn the other hand, a *crazy* mix of tabs and spaces is where some\nindentation is done with spaces while other indentation is done with\ntabs. Even crazier is a single line where the indentation is a mixture\nof tabs and spaces. I think that just about everyone can agree that\nthis is not only crazy, but evil and is the kind of thing one really\nwants to avoid in a code base. Unfortunately, when developers disagree\non their standard settings, it's very, very hard to avoid precisely\nthis kind of mess. My idea is to enable git to prevent this sort of\ninsanity if configured to do so.\n"},{"id":"93156","messageId":"d4bc1a2a0810151602j56550c3di2f59f92039fa8243@mail.gmail.com","threadId":"15905","inReplyTo":"d4bc1a2a0810151418r3bf21ddaj498017e8e178f579@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-15T23:02:28Z","receivedAt":"2008-10-15T23:02:28Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":"Any further comments? I'm more than willing to implement this, but I\nwon't bother if there's no chance of getting it accepted as a patch.\nDoes no one else feel like at least having the option to enforce\nwhitespace consistency in git is a good thing? If not, I guess I'll\njust muddle along without this feature instead of implementing it.\n\nOn Wed, Oct 15, 2008 at 2:18 PM, Stefan Karpinski\n<stefan.karpinski@gmail.com> wrote:\n> That's not what I would call a \"crazy\" mix of tabs and spaces, but\n> rather a *sane* mix of tabs and spaces. That can consistently be\n> reproduced, and is in fact what the spaces_to_tabs function included\n> above produces. The sane consistent formats as I see it are:\n>\n>  1) use spaces for everything\n>  2) use tabs for indentation, spaces for everything else\n>  3) use tabs for indentation and alignment\n>\n> If you know the tab size, you can reproduce any of these from the\n> others, except that #3 is a little tricky since there's places where\n> the tab/space issue can be ambiguous. I actually think that keeping\n> the repo version with tab-based indentation is a very sane thing to\n> do. However, I'd still like to be able to edit the files using soft\n> tabs, largely because any program that doesn't know what my tab size\n> should be applies its own interpretation and makes the code look\n> terrible (think terminal output for diff, cat, less, etc.)\n>\n> On the other hand, a *crazy* mix of tabs and spaces is where some\n> indentation is done with spaces while other indentation is done with\n> tabs. Even crazier is a single line where the indentation is a mixture\n> of tabs and spaces. I think that just about everyone can agree that\n> this is not only crazy, but evil and is the kind of thing one really\n> wants to avoid in a code base. Unfortunately, when developers disagree\n> on their standard settings, it's very, very hard to avoid precisely\n> this kind of mess. My idea is to enable git to prevent this sort of\n> insanity if configured to do so.\n"},{"id":"93159","messageId":"48F67E09.90202@gmail.com","threadId":"15905","inReplyTo":"d4bc1a2a0810151602j56550c3di2f59f92039fa8243@mail.gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"A Large Angry SCM","fromEmail":"gitzilla@gmail.com","sentAt":"2008-10-15T23:34:33Z","receivedAt":"2008-10-15T23:34:33Z","isPatch":false,"sender":{"key":"gitzilla@gmail.com","avatar":"https://gravatar.com/avatar/354625c442439908ff3dd99757dee330e29e9df7847472384faf7a00add247fb?d=mp&s=160"},"body":"Stefan Karpinski wrote:\n> Any further comments? I'm more than willing to implement this, but I\n> won't bother if there's no chance of getting it accepted as a patch.\n> Does no one else feel like at least having the option to enforce\n> whitespace consistency in git is a good thing? If not, I guess I'll\n> just muddle along without this feature instead of implementing it.\n\nI'm against including this in except as a sample smudge/clean script. \nGit is a content tracker; not the enforcement mechanism for individual \nproject policies.\n"},{"id":"93162","messageId":"d4bc1a2a0810151900v3db6ca1t8d295684c5b4cd98@mail.gmail.com","threadId":"15905","inReplyTo":"48F67E09.90202@gmail.com","subject":"Re: builtin conversion between tabs and spaces","fromName":"Stefan Karpinski","fromEmail":"stefan.karpinski@gmail.com","sentAt":"2008-10-16T02:00:46Z","receivedAt":"2008-10-16T02:00:46Z","isPatch":false,"sender":{"key":"stefan.karpinski@gmail.com","avatar":"https://gravatar.com/avatar/780cfb8dd7d7dc749d7276a4ca2ec24e7f0482cfce509717c5ddd165fd2cc9d9?d=mp&s=160"},"body":">> Any further comments? I'm more than willing to implement this, but I\n>> won't bother if there's no chance of getting it accepted as a patch.\n>> Does no one else feel like at least having the option to enforce\n>> whitespace consistency in git is a good thing? If not, I guess I'll\n>> just muddle along without this feature instead of implementing it.\n>\n> I'm against including this in except as a sample smudge/clean script. Git is\n> a content tracker; not the enforcement mechanism for individual project\n> policies.\n\nAlright. Fair enough.\n"}]}