23

I'm trying to ensure that I don't commit code that uses tab characters for indentation. This is a soft constraint that I'm applying to my own commits (right now we don't have a standard for indentation characters, but I'd like to use spaces, since there's no disagreement on the width of a space, but some people use width-4 tabs versus width-8 tabs).

The easiest way to check such constraints is often to look at the actual output of git diff every time you are about to commit and see if there are any problems. For example, for me, by default, trailing whitespace is highlighted and windows newlines are also visible in the diff, so if I'm about to accidentally commit code with trailing whitespace, I will be alerted to this. Is there a way to make tab characters also show up in git diff?

4 Answers 4

27

Git learned the tab-in-indent whitespace category in 1.7.2 (2010 July 21).
From Documentation/RelNotes/1.7.2.txt:

  • The whitespace rules used in "git apply --whitespace" and "git diff" gained a new member in the family (tab-in-indent) to help projects with policy to indent only with spaces.

It is controlled and used in the same ways as the other whitespace checking options.

The highlighting in git diff is the same as the other whitespace errors.
Checking is available with git diff --check.
Et cetera.

Add tab-in-indent to the value of the core.whitespace configuration variable to enable it (probably either in one or more specific repositories or in your “global” (per-use) configuration).

set-show-tabs() {
    global=
    test "$1" = -g || test "$1" = --global && global=--global
    cws=$(git config $global core.whitespace)
    case "$cws" in
        tab-in-indent,*|*,tab-in-indent|*,tab-in-indent,*) ;;
        *) git config $global core.whitespace "$cws"${cws:+,}tab-in-indent ;;
    esac
}
set-show-tabs           # only in local repository
set-show-tabs --global  # for all your Git activities
# or just edit it manually with "git config [--global] --edit"

Or, you can set it for individual commands (git -c is also from 1.7.2):

git -c core.whitespace=tab-in-indent diff --check

You could use something like this in a pre-commit hook to check for tabs without having it in any of your actual repository configuration files.

7
  • I'm sure it works like @jonderry wanted, but it only picks up tabs for indentation, not tabs embedded in the middle of a line. If you had a policy of "no tabs at all", you may find my answer helpful. Commented Apr 8, 2011 at 21:52
  • For some reason, all of the tab characters are rendered with red color. Is there any way to make them behave like color.diff.new and color.diff.old?
    – rr-
    Commented Jun 26, 2015 at 21:23
  • 1
    @rr-: That is one of the effects of putting tab-in-indent in core.whitespace. If you do not want the tabs to get special highlighting, just remove it from your configuration (it is not enabled by default, so it will be visible in one of your configuration files; e.g. git config --edit or git config --edit --global). If you want to change the highlighting, see color.diff.whitespace (although like the red highlighting, it will be the same color in both “old” and “new” sections). Commented Jun 26, 2015 at 23:42
  • I want leading tabs to be highlighted just like leading spaces in diffs; right now I can give them only one specific color (that's incompatible with old and new) like you said. I wonder why is that? I mean, why are tabs treated specially (and they are, otherwise they would get highlighted like any other text), even differently from normal spaces?
    – rr-
    Commented Jun 27, 2015 at 6:55
  • 2
    @rr-: Using a single color for whitespace error highlighting was probably a design decision to keep things simple. The part about never highlighting tabs (even with -tab-in-indent; and spaces before tabs with -space-before-tab) may be a bug or just an implementation detail. Git 2.5.0 will have --ws-error-highlight=none that will disable whitespace error highlighting altogether, leaving these tabs (and spaces before tab) colored exactly like the rest of “new” and “old” line. Commented Jun 27, 2015 at 9:11
12

To locate lines with tabs:

git grep -n --cached 'LITERAL TAB HERE'

In bash or zsh you can enter a literal tab with Ctrl-V Ctrl-I. That command will show you all files+lines with tabs.

If you want to enforce your policy by preventing a commit with tabs, put this in .git/hooks/pre-commit and mark it executable (chmod +x):

#!/bin/sh
allowtabs=$(git config hooks.allowtabs)
if [ "$allowtabs" != "true" ] &&
   git diff --cached | egrep '^\+.* '>/dev/null
then
   cat<<END;
Error: This commit would contain a tab, which is against this repo's policy.

If you know what you are doing you can force this commit with:

  git commit --no-verify

Or change the repo policy like so:

  git config hooks.allowtabs true
END
  exit 1
fi

There's a literal tab between the * and the ' on the git diff --cached | egrep line. You can get this in Vim with Ctrl-V Ctrl-I, or Emacs with C-q C-i.

What it does is look for a new line in the diff (starting with "+") which contains a tab. You could put the git grep line in the error message if you want to show the offending tabs in the hook.

I've put this hook on github here.

7
  • For some reason, egrep fails to match any lines when I include the beginning-of-line string '^', but it finds the relevant lines if I remove the '^'. Do you know why this might be the case?
    – jonderry
    Commented Apr 7, 2011 at 1:04
  • I figured out why '^' is not working for me. I believe sed is trying to match the color code on the added line which precedes the '+' at the beginning of the line. Is there a simple way to handle this?
    – jonderry
    Commented Apr 7, 2011 at 2:27
  • 1
    Ultimately, to get the full effect, I did the following: git diff --cached | sed -e "s/^\(^[\[32m+\)[^\t]*\(\t\+\)/\\1${red_bg}\\2${norm_bg}/g" where ^[ is a raw escape (C-q ESC in emacs) with norm_bg=$(tput sgr0) and red_bg=$(tput setab 1) as per stackoverflow.com/questions/5575109/…
    – jonderry
    Commented Apr 7, 2011 at 4:50
  • @jonderry Very nice! Colors draw your eye to exactly the right spot. Commented Apr 7, 2011 at 6:14
  • I've added a link to the hook on github which contains the tabs in the right spot: github.com/mrc/git-hook-library/blob/master/pre-commit.no-tabs - by the way I think your issue about "+" may be because you're using color "always" and I have color "auto" - looks like you solved it anyway, nicely done :-) Commented Apr 7, 2011 at 7:55
0

I have made a pre-commit hook that stops you from committing tab-indented code https://github.com/martinjoiner/portable-code-pre-commit-hook it looks like this...

enter image description here

I routinely use it on all my projects now. Feel free to use it yourself.

I work on a team that write code in a mix of Mac, Windows and Linux environments as well as reviewing it in-browser via the Github website. The team are really happy to have a helping hand checking their code so it looks consistent across all those places.

If you find any problems please let me know, I'd like the chance to improve any weaknesses. Thanks.

0

One way of making tabulation characters visible is to replace them with something that's noticeable, for example .        (that's a dot followed by seven spaces). This can be achieved by substituting pager in your configuration to add invocation of sed. Like this:

[core]
    pager = sed 's/\t/.       /g' | less -R

Example

Without custom pager:

         for (int i = 0; i < 3; ++i) {
-        for (int h = 0; h < 4; ++h) {
+                for (int h = 0; h < 4; ++h) {
                         for (int k = 0; k < 4; ++k) {

With custom pager:

 .       for (int i = 0; i < 3; ++i) {
-        for (int h = 0; h < 4; ++h) {
+.       .       for (int h = 0; h < 4; ++h) {
 .       .       .       for (int k = 0; k < 4; ++k) {

Not the answer you're looking for? Browse other questions tagged or ask your own question.