暗无天日

=============>DarkSun的个人博客

git hook+expand+unexpand化解空格与TAB之争

SPACE与TAB之争由来已久,但是有一个观点是两派都公认赞同的,那就是 不能两者混用.

TAB_VS_SPACE.jpg

为此,Linux很贴心的提供了 expandunexpand 命令来帮助我们进行 TABSPACE 之间的相互转换。

使用expand将TAB转换为SPACE

使用expand将TAB转换为SPACE的方法很简单,直接执行 expand 文件 就会把 文件 中的所有 TAB 都替换空格(默认按8个空格进行对齐)了。

例如假设我们有下面一个测试文件

cat -T /tmp/test
^Ihello  ^Itest^I

其中 cat-T 选项文件内容其中的 TAB 显示为 ^I

我们现在来用 expand 对其进行转换

expand /tmp/test |cat -T
hello   test    

如果觉得使用8空格对齐太空了,那么可以使用 -t 选项指定对齐的空格数。比如下面命令按4个空格进行对齐:

expand -t 4 /tmp/test |cat -T
hello   test    

另一方面,我们在写代码时可能只需要转换行首的空格,而字符串中的 TAB 是不需要进行转换的,那么就可以使用 -i 选项指定 expand 只对行首的空格进行转换:

expand -i -t 4 /tmp/test |cat -T
hello  ^Itest^I

使用unexpand将SPACE转换为TAB

unexpand, 从名字上就知道它是 expand 的反操作,即把SPACE转换为TAB. 他们的使用方法也很类似,但是有一点不同就是 expand 在默认情况下替换所有的 TAB,当只替换行首的 TAB 时需要指定 -i 选项;而 unexpand 在默认情况下只替换行首的 SPACE,若要替换 SPACE 时需要明确指定 -a 选项.

还是例如假设我们有下面一个测试文件

cat -T /tmp/test.space
hello   test    

我们现在来用 unexpand 对其进行转换

unexpand /tmp/test.space |cat -T
^Ihello   test    

你会发现只替换了前面的8个空格为 TAB

同样我们可以使用 -t 选项指定 TAB 对齐的空格数。比如下面命令按4个空格进行对齐:

unexpand -t 4 /tmp/test.space |cat -T
^I^Ihello^Itest^I

结果的前面是两个 TAB,也就是每四个空格转换为了一个 TAB.

若要将所有的空格全部替换,那么需要明确使用 -a 选项进行转换:

unexpand -a -t 4 /tmp/test.space |cat -T
^I^Ihello^Itest^I

使用git hook实现代码的自动转换

在确定好规范使用SPACE或TAB后,我们可以借助git的pre-commit hook来让我们在代码提交前自动进行规范化的转换。

比如,假设我们定义的规范是只使用 SPACE 规范,那么我们可以创建下面的 pre-commit hook

if git rev-parse --verify HEAD >/dev/null 2>&1
then
  against=HEAD
else
  # Initial commit: diff against an empty tree object
  against=$(git hash-object -t tree /dev/null)
fi
# Redirect output to stderr.
exec 1>&2
TMPFILE=$(mktemp)
trap 'rm -f ${TMPFILE}' EXIT
for file in $(git diff --cached --name-only --diff-filter=ACM $against)
do
    # 只对text类文件进行转换
    if file "${file}" |grep 'text' 2>&1 >/dev/null;then
        echo "expanding ${file}"
        expand -i -t 4 "${file}" > "${TMPFILE}"
        cp "${TMPFILE}" "${file}"
        git add "${file}"
    fi
done

将该文件保存为 .git/hooks/pre-commit,然后添加可执行权限后,每次提交就会对文件进行格式化处理了:

lujun9972:a/ (master U:1 ?:3✗) $ cat -T test
^Ihello  ^Itest^I^I
lujun9972:a/ (master U:1 ?:3✗) $ git commit -a -m update
expanding test
[master 88b2f66] update
 1 file changed, 1 deletion(-)
lujun9972:a/ (master ?:3✗) $ cat -T test
    hello  ^Itest^I^I

然后我们再通过 post-commit hook 将转换的文件再转换回TAB来:

TMPFILE=$(mktemp)
against="HEAD^1"
trap 'rm -f ${TMPFILE}' EXIT
for file in $(git diff --cached --name-only --diff-filter=ACM $against)
do
    # 只对text类文件进行转换
    if file "${file}" |grep 'text' 2>&1 >/dev/null;then
        echo "unexpanding ${file}"
        unexpand -t 4 "${file}" > "${TMPFILE}"
        cp "${TMPFILE}" "${file}"
        git add "${file}"
    fi
done

类似的,我们还可以通过 post-merge hook(实现跟post-commit一样) 来将远端仓库中的代码也自动转回 TAB,

如此一来, TAB 还好者们就可以安心的在本地使用 TAB,然后由git帮你自动进行 SPACE 的转换了.