234

I'm using Git with my team and would like to remove white space changes from my diffs, logs, merges, etc. I'm assuming that the easiest way to do this would be for Git to automatically remove trailing white space (and other white space errors) from all commits as they are applied.

I have tried to add the following to the ~/.gitconfig file, but it doesn't do anything when I commit. Maybe it's designed for something different. What's the solution?

[core]
    whitespace = trailing-space,space-before-tab
[apply]
    whitespace = fix

I'm using Ruby in case anyone has any Ruby specific ideas. Automatic code formatting before committing would be the next step, but that's a hard problem and is not really causing a big problem.

4

17 回答 17

119

这些设置 (core.whitespaceapply.whitespace) 不是为了删除尾随空格,而是为了:

  • core.whitespace:检测它们,并引发错误
  • apply.whitespace:并剥离它们,但仅在补丁期间,而不是“总是自动”

我相信这git hook pre-commit会做得更好(包括删除尾随空格)


请注意,在任何给定时间,您都可以选择不运行该pre-commit挂钩:

  • 暂时地:git commit --no-verify .
  • 永久:cd .git/hooks/ ; chmod -x pre-commit

警告:默认情况下,一个pre-commit脚本(比如这个没有“删除尾随”功能,而是一个“警告”功能,例如:

if (/\s$/) {
    bad_line("trailing whitespace", $_);
}

但是,您可以构建一个更好的pre-commithook,尤其是当您考虑到以下情况时:

在 Git 中只在暂存区添加一些更改的提交仍然会导致“原子”修订,它可能永远不会作为工作副本存在并且可能无法正常工作


例如,oldman在另一个答案中提出了一个检测和删除空格的pre-commit钩子。
由于该挂钩获取每个文件的文件名,因此我建议对某些类型的文件要小心:您不想删除.md(降价)文件中的尾随空格!


hakre评论中建议的另一种方法:

您可以在 markdown 的行尾有两个空格,而不是通过\在前面添加 ""将其作为尾随空格\n

然后是内容过滤器驱动程序:

git config --global filter.space-removal-at-eol.clean 'sed -e "s/ \+$//"'
# register in .gitattributes 
*.md filter=space-removal-at-eol
于 2009-02-26T19:19:01.453 回答
49

你可以欺骗 Git 为你修复空白,方法是欺骗 Git 将你的更改视为一个补丁。与“预提交挂钩”解决方案相比,这些解决方案向 Git 添加了空白修复命令。

是的,这些都是黑客。


强大的解决方案

以下 Git 别名取自 我的~/.gitconfig.

“健壮”是指这些别名运行没有错误,做正确的事情,无论树或索引是否脏。git rebase -i但是,如果交互已经在进行中,它们将不起作用;如果您关心这个极端情况,请参阅~/.gitconfiggit add -e的其他检查,最后描述的技巧应该可以工作。

如果您想直接在 shell 中运行它们,而不创建 Git 别名,只需复制并粘贴双引号之间的所有内容(假设您的 shell 类似于 Bash)。

修复索引但不修复树

以下fixwsGit 别名修复了索引中的所有空白错误(如果有),但不涉及树:

# Logic:
#
# The 'git stash save' fails if the tree is clean (instead of
# creating an empty stash :P). So, we only 'stash' and 'pop' if
# the tree is dirty.
#
# The 'git rebase --whitespace=fix HEAD~' throws away the commit
# if it's empty, and adding '--keep-empty' prevents the whitespace
# from being fixed. So, we first check that the index is dirty.
#
# Also:
# - '(! git diff-index --quiet --cached HEAD)' is true (zero) if
#   the index is dirty
# - '(! git diff-files --quiet .)' is true if the tree is dirty
#
# The 'rebase --whitespace=fix' trick is from here:
# https://stackoverflow.com/a/19156679/470844
fixws = !"\
  if (! git diff-files --quiet .) && \
     (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git stash save FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~ && \
    git stash pop && \
    git reset --soft HEAD~ ; \
  elif (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git rebase --whitespace=fix HEAD~ && \
    git reset --soft HEAD~ ; \
  fi"

这个想法是在索引中有空格错误时git fixws先运行。git commit

修复索引和树

以下fixws-global-tree-and-indexGit 别名修复了索引和树中的所有空白错误(如果有):

# The different cases are:
# - dirty tree and dirty index
# - dirty tree and clean index
# - clean tree and dirty index
#
# We have to consider separate cases because the 'git rebase
# --whitespace=fix' is not compatible with empty commits (adding
# '--keep-empty' makes Git not fix the whitespace :P).
fixws-global-tree-and-index = !"\
  if (! git diff-files --quiet .) && \
     (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git add -u :/ && \
    git commit -m FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~2 && \
    git reset HEAD~ && \
    git reset --soft HEAD~ ; \
  elif (! git diff-files --quiet .) ; then \
    git add -u :/ && \
    git commit -m FIXWS_SAVE_TREE && \
    git rebase --whitespace=fix HEAD~ && \
    git reset HEAD~ ; \
  elif (! git diff-index --quiet --cached HEAD) ; then \
    git commit -m FIXWS_SAVE_INDEX && \
    git rebase --whitespace=fix HEAD~ && \
    git reset --soft HEAD~ ; \
  fi"

要修复未版本化文件中的空白,请执行

git add --intent-to-add <unversioned files> && git fixws-global-tree-and-index

简单但不可靠的解决方案

这些版本更容易复制和粘贴,但如果不满足其附带条件,它们就不会做正确的事情。

修复以当前目录为根的子树(但如果它不为空则重置索引)

使用git add -e身份编辑器“编辑”补丁:

(export GIT_EDITOR=: && git -c apply.whitespace=fix add -ue .) && git checkout . && git reset

修复并保留索引(但如果树脏或索引为空则失败)

git commit -m TEMP && git rebase --whitespace=fix HEAD~ && git reset --soft HEAD~

修复树和索引(但如果它不为空则重置索引)

git add -u :/ && git commit -m TEMP && git rebase --whitespace=fix HEAD~ && git reset HEAD~

export GIT_EDITOR=: && git -c apply.whitespace=fix add -ue .套路的解释

在我从这个答案中了解git rebase --whitespace=fix技巧之前,我到处都在使用更复杂的技巧。git add

如果我们手动完成:

  1. 设置apply.whitespacefix(您只需执行一次):

    git config apply.whitespace fix
    

    这告诉 Git 修复补丁中的空白。

  2. 说服 Git 将您的更改视为一个补丁

    git add -up .
    

    点击a+enter选择每个文件的所有更改。您将收到有关 Git 修复空白错误的警告。
    git -c color.ui=auto diff此时表明您的非索引更改正是空白错误)。

  3. 从工作副本中删除空白错误:

    git checkout .
    
  4. 带回您的更改(如果您还没有准备好提交它们):

    git reset
    

作为编辑器使用的GIT_EDITOR=:手段:,作为命令 使用:的就是身份。

于 2013-03-13T23:30:18.590 回答
30

我发现了一个 Git预提交钩子,可以删除尾随空格

#!/bin/sh

if git-rev-parse --verify HEAD >/dev/null 2>&1 ; then
   against=HEAD
else
   # Initial commit: diff against an empty tree object
   against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Find files with trailing whitespace
for FILE in `exec git diff-index --check --cached $against -- | sed '/^[+-]/d' | sed -r 's/:[0-9]+:.*//' | uniq` ; do
   # Fix them!
   sed -i 's/[[:space:]]*$//' "$FILE"
   git add "$FILE"
done
exit
于 2010-08-18T20:44:57.350 回答
20

在 macOS(或者可能是任何 BSD)上,sed 命令参数必须略有不同。尝试这个:

#!/bin/sh

if git-rev-parse --verify HEAD >/dev/null 2>&1 ; then
   against=HEAD
else
   # Initial commit: diff against an empty tree object
   against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Find files with trailing whitespace
for FILE in `exec git diff-index --check --cached $against -- | sed '/^[+-]/d' | sed -E 's/:[0-9]+:.*//' | uniq` ; do
    # Fix them!
    sed -i '' -E 's/[[:space:]]*$//' "$FILE"
    git add "$FILE"
done

将此文件另存为.git/hooks/pre-commit-- 或查找已经存在的文件,然后将底部的块粘贴到其中的某个位置。并记住chmod a+x它。

或者为了全局使用(通过Applying a git post-commit hook to all current and future repos)你可以把它放进去$GIT_PREFIX/git-core/templates/hooks(其中 GIT_PREFIX 是 /usr 或 /usr/local 或 /usr/share 或 /opt/local/share )和git init在您现有的存储库中运行。

根据git help init

git init在现有存储库中运行是安全的。它不会覆盖已经存在的东西。重新运行的主要原因git init是选择新添加的模板。

于 2011-01-04T18:31:48.513 回答
13

我宁愿把这个任务留给你最喜欢的编辑。

只需设置一个命令以在保存时删除尾随空格。

于 2009-02-26T21:34:44.530 回答
12

使用 Git 属性和过滤器设置与 Git 配置

好的,这是解决这个问题的新方法……我的方法是不使用任何钩子,而是使用过滤器和 Git 属性。这允许您在您开发的每台机器上设置一组过滤器,这些过滤器将在提交文件之前去除文件末尾的额外尾随空格和额外空行。

然后设置一个 .gitattributes 文件,说明过滤器应该应用于哪些类型的文件。过滤器有两个阶段,clean一个是在将文件添加到索引时应用,另一个是在将文件smudge添加到工作目录时应用。

告诉你的 Git 寻找一个全局属性文件

首先,告诉您的全局配置使用全局属性文件:

git config --global core.attributesfile ~/.gitattributes_global

创建全局过滤器

现在,创建过滤器:

git config --global filter.fix-eol-eof.clean fixup-eol-eof %f
git config --global filter.fix-eol-eof.smudge cat
git config --global filter.fix-eol-eof.required true

添加sed脚本魔法

最后,将fixup-eol-eof脚本放在路径上的某个位置,并使其可执行。该脚本使用 sed 进行一些即时编辑(删除行尾的空格和空白,以及文件末尾的无关空白行)

fixup-eol-eof 应该如下所示:

#!/bin/bash
sed -e 's/[     ]*$//' -e :a -e '/^\n*$/{$d;N;ba' -e '}' $1

我的要点

告诉 Git 将新创建的过滤器应用于哪些文件类型

最后,在您喜欢的文本编辑器中创建或打开文件~/.gitattributes_global并添加如下行:

pattern attr1 [attr2 [attr3 […]]]

因此,如果我们想解决空白问题,对于我们所有的 C 源文件,我们将添加如下所示的一行:

*.c filter=fix-eol-eof

过滤器的讨论

过滤器有两个阶段。将内容添加到索引或签入时应用的清洁阶段,以及 Git 将内容放入工作目录时的涂抹阶段。

在这里,我们的涂抹只是通过cat命令运行内容,应该保持它们不变,除了如果文件末尾没有换行符,则可能添加尾随换行符。

clean 命令是我从http://sed.sourceforge.net/sed1line.txt的注释中拼凑起来的空白过滤。看来它必须放入一个shell脚本。我不知道如何注入 sed 命令,包括将文件末尾多余的额外行直接注入 git-config 文件。(但是,您可以摆脱尾随空白,而无需单独的 sed 脚本。只需将 设置filter.fix-eol-eof为类似于sed 's/[ \t]*$//' %f实际\t选项卡的位置,按Tab。)

如果出现require = true问题,则会引发错误,以免您遇到麻烦。

于 2015-02-11T04:07:35.450 回答
9

请尝试我的预提交钩子。它可以自动检测尾随空格并将其删除

它可以在Git Bash (Windows)、Mac OS X 和 Linux 下运行!


快照:

$ git commit -am "test"
auto remove trailing whitespace in foobar/main.m!
auto remove trailing whitespace in foobar/AppDelegate.m!
[master 80c11fe] test
1 file changed, 2 insertions(+), 2 deletions(-)
于 2014-03-28T04:44:37.460 回答
9

我写了这个 pre-commit 钩子,它只从你更改/添加的行中删除尾随空格,因为如果目标文件有太多尾随空格,前面的建议往往会创建不可读的提交。

#!/bin/sh

if git rev-parse --verify HEAD >/dev/null 2>&1 ; then
   against=HEAD
else
   # Initial commit: diff against an empty tree object
   against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

IFS='
'

files=$(git diff-index --check --cached $against -- | sed '/^[+-]/d' | perl -pe 's/:[0-9]+:.*//' | uniq)
for file in $files ; do
    diff=$(git diff --cached $file)
    if test "$(git config diff.noprefix)" = "true"; then
        prefix=0
    else
        prefix=1
    fi
    echo "$diff" | patch -R -p$prefix
    diff=$(echo "$diff" | perl -pe 's/[ \t]+$// if m{^\+}')
    out=$(echo "$diff" | patch -p$prefix -f -s -t -o -)
    if [ $? -eq 0 ]; then
        echo "$diff" | patch -p$prefix -f -t -s
    fi
    git add $file
done
于 2013-10-14T07:33:56.440 回答
7

这是一个 Ubuntu 和 Mac OS X 兼容的版本:

#!/bin/sh
#

# A Git hook script to find and fix trailing white space
# in your commits. Bypass it with the --no-verify option
# to git-commit
#

if git-rev-parse --verify HEAD >/dev/null 2>&1 ; then
  against=HEAD
else
  # Initial commit: diff against an empty tree object
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Find files with trailing whitespace
for FILE in `exec git diff-index --check --cached $against -- | sed '/^[+-]/d' | (sed -r 's/:[0-9]+:.*//' > /dev/null 2>&1 || sed -E 's/:[0-9]+:.*//') | uniq` ; do
  # Fix them!
  (sed -i 's/[[:space:]]*$//' "$FILE" > /dev/null 2>&1 || sed -i '' -E 's/[[:space:]]*$//' "$FILE")
  git add "$FILE"
done

# Now we can commit
exit
于 2011-06-07T08:44:15.787 回答
5

我今天正在考虑这个问题。这就是我最终为 Java 项目所做的一切:

egrep -rl ' $' --include *.java *  | xargs sed -i 's/\s\+$//g'
于 2012-09-20T07:54:14.863 回答
4

对于崇高文本用户。

在您的设置用户配置中正确设置以下内容。

"trim_trailing_white_space_on_save": true
于 2014-06-19T13:37:50.457 回答
2

for文件循环使用shell$IFS变量。在给定的脚本中,其中包含一个字符且也在 $IFS 变量中的文件名将被视为for循环中的两个不同文件。

这个脚本修复了它: sed手册中给出的多行模式修饰符默认情况下在我的 Ubuntu 机器上似乎不起作用,所以我寻找不同的实现并发现它带有迭代标签,基本上它只会开始替换如果我理解正确,文件的最后一行。

#!/bin/sh
#

# A Git hook script to find and fix trailing white space
# in your commits. Bypass it with the --no-verify option
# to git-commit
#

if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

SAVEIFS="$IFS"
# only use new-line character as separator, introduces EOL-bug?
IFS='
'
# Find files with trailing white space
for FILE in $(
    git diff-index --check --cached $against -- \
    | sed '/^[+-]/d' \
    | ( sed -r 's/:[0-9]+:.*//' || sed -E 's/:[0-9]+:.*//' ) \
    | uniq \
)
do
# replace whitespace-characters with nothing
# if first execution of sed-command fails, try second one (Mac OS X version)
    (
        sed -i ':a;N;$!ba;s/\n\+$//' "$FILE" > /dev/null 2>&1 \
        || \
        sed -i '' -E ':a;N;$!ba;s/\n\+$//' "$FILE" \
    ) \
    && \
# (re-)add files that have been altered to Git commit-tree
#   when change was a [:space:]-character @EOL|EOF git-history becomes weird...
    git add "$FILE"
done
# restore $IFS
IFS="$SAVEIFS"

# Exit script with the exit-code of git's check for white space characters
exec git diff-index --check --cached $against --

1 sed 替换模式:如何使用 sed 替换换行符 (\n)?

于 2011-10-16T23:44:40.453 回答
2

这不会在提交之前自动删除空格,但它很容易实现。我将以下Perl脚本放在 $PATH 目录中名为 git-wsf (Git white space fix) 的文件中,这样我可以:

git wsf | sh

它仅从Git 报告为差异的文件行中删除所有空白。

#! /bin/sh
git diff --check | perl -x $0
exit

#! /usr/bin/perl

use strict;

my %stuff;
while (<>) {
    if (/trailing whitespace./) {
        my ($file,$line) = split(/:/);
        push @{$stuff{$file}},$line;
    }
}

while (my ($file, $line) = each %stuff) {
    printf "ex %s <<EOT\n", $file;
    for (@$line) {
        printf '%ds/ *$//'."\n", $_;
    }
    print "wq\nEOT\n";
}
于 2017-10-21T03:10:35.633 回答
0

相同结果的 Python 脚本。

import subprocess                                                                 
                                                                              
def get_trailing_lines():                                                         
                                                                              
    result = subprocess.run([                                                     
                            'git',                                            
                            'diff',                                           
                            '--check'                                         
                        ], capture_output=True)                               
                                                                              
    return result.stdout.decode().split('\n')                                     
                                                                              
                                                                              
def modify_line(file_path, l_num):                                                
                                                                              
    f_lines = open(file_path).readlines()                                         
    f_lines[l_num] = f_lines[l_num].rstrip()+'\n'\                                
                     if '\n' in f_lines[l_num] else f_lines[l_num].rstrip()    
                                                                              
    with open(file_path, "w") as w_fp:                                            
        w_fp.writelines(f_lines)                                                  
                                                                              
                                                                              
if __name__ == '__main__':                                                        
                                                                              
    l = get_trailing_lines()                                                      
    for m, d in zip(l[::2], l[1::2]):                                             
        f_path, l_no, *_ = m.split(":")                                           
        modify_line(f_path, int(l_no)-1)                                          
于 2021-11-01T10:01:11.050 回答
-1

This probably won't directly solve your problem, but you might want to set those via git-config in your actual project space, which edits file ./.git/config as opposed to file ~/.gitconfig. It is nice to keep the settings consistent among all project members.

git config core.whitespace "trailing-space,space-before-tab"
git config apply.whitespace "trailing-space,space-before-tab"
于 2009-02-26T19:12:18.333 回答
-1

要可移植地删除文件中行尾的尾随空格,请使用ed

test -s file &&
   printf '%s\n' H ',g/[[:space:]]*$/s///' 'wq' | ed -s file
于 2014-12-02T13:50:00.070 回答
-2

在 Vim 中打开文件。要用空格替换制表符,请在 Vim 命令行中输入以下内容:

:%s#\t#    #gc

摆脱其他尾随空格

:%s#\s##gc

这几乎为我做到了。如果您有很多文件要编辑,这很乏味。但我发现它比预提交钩子和使用多个文本编辑器更容易。

于 2014-07-25T22:20:53.477 回答