使git在提交前自动删除尾随空格


220

我正在与团队一起使用git,并希望从差异,日志,合并等中删除空格更改。我假设最简单的方法是让git自动删除结尾的空格(以及其他空格错误) )中的所有提交。

我试图将以下内容添加到~/.gitconfig文件中,但是在提交时它什么也没做。也许它是为不同的东西而设计的。有什么解决办法?

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

如果有人对红宝石有任何特定想法,我会使用红宝石。下一步是提交之前自动进行代码格式化,但这是一个难题,并且不会真正引起大问题。


如果core.whitespace指令不能解决您的问题,您还可以更改预提交钩子(.git / hooks / pre-commit)来为您查找并修复它们。请参阅帖子以获取详细说明。
VolkA

2
我对类似的空格错误和部分解决方案感到沮丧,并编写了一个灵活且功能齐全的实用程序,该实用程序可以修复简单地报告那些困扰魔鬼版本控制系统的空格错误: Github上的TotalTotal Fixer(如果这太过自我宣传,我深表歉意)
Dan Lenski 2014年

Answers:


111

这些设置(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-commit钩子,尤其是考虑到以下情况:

在Git中提交但仅对登台区域进行了一些更改的情况下,仍然会导致“原子”修订,该修订可能永远不会作为工作副本存在并且可能无法正常工作


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


1
事实证明,可以apply.whitespace通过诱使git诱使git 修复工作副本中的空白,方法是欺骗git将您的工作副本更改视为补丁。请参阅下面的答案
ntc2

>“您不想删除.md(降价)文件中的尾随空格”-为什么?在markdown文件中尾随空格的目的是什么?我注意到某些.editorconfig文件对此有特定的规则。
Friederbluemle 2015年

5
@friederbluemle取决于markdown的类型,尾随双空格表示<br>github.com/FriendsOfPHP/PHP-CS-Fixer/issues/…–
VonC

在2.5.0中提交时,设置core.whitespacetrailing-spacewith git config不会引发错误git
Karl Richter)

43

您可以通过诱使Git将更改视为补丁来诱使Git为您修复空白。与“预提交挂钩”解决方案相比,这些解决方案向Git添加了空格固定命令。

是的,这些都是骇客。


强大的解决方案

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

“健壮”是指这些别名运行正确,不管树或索引是否脏都正确无误。但是,如果git rebase -i已经在进行交互,则它们将无效。如果您关心这种极端情况,请参阅~/.gitconfig的其他检查,该情况git add -e在末尾描述的技巧应该起作用的地方。

如果要直接在外壳中运行它们而不创建Git别名,则只需在双引号之间复制并粘贴所有内容(假设您的外壳类似于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=:装置使用:作为编辑器,并作为命令 :是标识。


1
我刚刚在Windows中对其进行了测试:在DOS命令提示符下可以正常工作:set VISUAL= && git add -ue . && git checkout .请注意与.'一起使用的' git add因为git1.8.3
VonC

@VonC不会永久取消设置VISUAL,这可能例如导致以后使用git commit来使用错误的编辑器?VISUAL=为了避免这种情况,我将部件包装在上述unix版本的子外壳中,但是我不知道DOS是否具有子外壳。
ntc2

1
谢谢你的破解!仅供参考,如果您已core.editor设置,则导出VISUAL无效,因为config设置优先于per man git-var。要覆盖它,您需要导出GIT_EDITOR=:
Nick Felt

1
另外,fixws如果您已经处于交互式基础中,我会调整我的版本以使其快速失败,因为否则它将在git rebase --whitespace=fix生产线上死机并使您处于怪异状态。我从这个问题中借用,并在if之前添加了一个额外的案例: fixws = !"\ if test -d $(git rev-parse --git-dir)/rebase-merge ; then \ echo 'In rebase - cannot fixws' ; \ elif (! git diff-files --quiet .) && \ (! git diff-index --quiet --cached HEAD) ; then \ ...
Nick Felt

1
仅供参考:我将此更改为一个预先提交的挂钩
Ian Kelling

29

我发现了一个git 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
# 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

3
第二个sed调用(sed -r 's/:[0-9]+:.*//')可以替换为cut -f1 -d:。在基于Linux和BSD的平台上,此方法应相同。
Ihor Kaharlichenko 2011年

2
@IhorKaharlichenko:实际上,使用cut不如第二种安全sed:在(极不可能)文件名包含“:”的情况下,剪切将失败。awk 'NF>2{NF-=2}1'为了安全起见,您可以使用
MestreLion 2012年

1
顺便说一句,如果您在Windows(msysgit)上并使用core.autocrlf=true,则可能要dos2unix -D "$FILE"在sed之后在for循环内添加。否则,它将仅通过发布sed将所有CRLF更改为LF。
jakub.g 2012年

49
git add对我来说,在内部执行提交钩子似乎很邪恶。如果要进行文件的部分登台/提交怎么办?您不希望将整个文件放在背后,对吗?
Stefaan 2013年

19

在Mac OS(或可能的任何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这一点。

或者供全局使用(通过Git commit$GIT_PREFIX/git-core/templates/hooks hooks- 全局设置),您可以将其放入(其中GIT_PREFIX是/ usr或/ usr / local或/ usr / share或/ opt / local / share)并git init在现有存储库中运行。

根据git help init

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


7
这个钩子不是在修改工作文件并用修改后的工作文件覆盖索引吗?如果要'git add -p'来构造索引,那么该提交钩子将使您大吃一惊。
马修·达顿

2
是的,您可能是对的。可能有人必须重写此脚本才能使用 git hash-object -wgit update-index并将修改后的文件直接(重新)插入索引。有人很勇敢。
AlexChaffee 2012年

11

我宁愿将此任务留给您最喜欢的编辑器。

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


2
在vim中,您可以使用以下命令执行此操作:autocmd BufWritePre .cpp, .c,*。h:%/ \ s \ + $ // e
Robert Massaioli,2009年

3
抱歉,我在测试之前对以上评论进行了投票。百分号后有一个缺少的“ s”,如果找到空格,它将在四处移动光标,并且将删除最后一个搜索模式。有关更好的替代方法,请参见vim.wikia.com/wiki/Remove_unwanted_spaces
塞斯·约翰逊

1
在emacs中,它是Mx delete-trailing-whitespace。
莫维斯·莱德福德

2
更好的是,对于emacs,设置一个挂钩以在添加(add-hook 'before-save-hook 'delete-trailing-whitespace).emacs文件之前进行保存之前删除尾随空格。Emacs的空白技巧
Duncan Parkes

1
我使用(add-hook'beave-hook'whitespace-cleanup)还将制表符转换为空格。
尼尔斯·法格堡2013年

10

使用git属性,并使用git config过滤设置

好的,这是解决此问题的新方法。我的方法是不使用任何钩子,而使用过滤器和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文件中。(您CAN甩掉尾随空白,但是,而不需要单独的sed脚本的,只需设置filter.fix-eol-eof喜欢的东西sed 's/[ \t]*$//' %f,其中的\t是一个实际的选项卡,按Tab键)。

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

如果我有关git的语言不准确,请原谅我。我认为我对这些概念掌握得很好,但仍在学习术语。


有趣的方法。+1
VonC'2

谢谢@VonC!我还想借此机会指出,可以在.git文件夹中而不是全局地在每个副本的基础上配置git属性,这可能更有意义。
zbeekman 2015年

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

1
有趣。+1。请参阅我的其他答案以计算空树。
VonC

1
好主意,这正是我想要的。但是,使用时要小心!对我来说,在OSX和git 2.3.5版上,它炸毁了我已上演的所有添加但未提交的更改。不过,我仍然对此有一个可行的解决方案感兴趣。
卡斯珀

9

请尝试使用我的预提交挂钩,它可以自动检测尾随空格并将其删除。谢谢!

它可以在下工作GitBash(windows), Mac OS X and 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(-)

1
有趣。+1。我在自己的答案中
VonC 2014年

@VonC感谢您的确认!对于'.md',我只发现有git commit -no-verify任何建议吗?
年长的人

我宁愿使该钩子能够检测.md文件而不删除空格,而不是要求最终用户在上添加一个--no-verify选项git commit
VonC 2014年

如果失败提交文件/目录下,以a开头+-
罗迪Oldenhuis

6

这是ubuntu + mac os x兼容版本:

#!/bin/sh
#

# A git hook script to find and fix trailing whitespace
# 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

玩得开心


看起来您和我的唯一的区别是您在重写文件之前检查sed是否会真正替换某些东西...我不确定这是怎么回事,因为git不会提交实际上不会更改任何内容的更改。我认为它稍微安全一些,但也稍慢一些,我更希望不要在一行上重复两次正则表达式。无争议!
AlexChaffee 2011年

没有区别是版本先使用ubuntu语法,然后使用osx(如果失败)。
sdepold 2011年

1
我编辑了sdepold的帖子,它现在应该也可以允许文件名中包含空格。
imme

5

今天在想这个。这就是我最后为一个Java项目所做的一切:

egrep -rl ' $' --include *.java *  | xargs sed -i 's/\s\+$//g'

3

对于Sublime Text用户。

在“ 设置用户”配置中正确设置以下内容。

"trim_trailing_white_space_on_save": true


1
是否可以通过文件类型进行设置?我有*.md依赖(降价)文件“”(结尾双空格)用于标记的简单<br />,而且设置似乎适用于所有文件,包括那些我希望删除尾随空格。
VonC 2014年

@VonC这里有关于如何应用配置的层次结构,stackoverflow.com/ questions/16983328/… 希望能有所帮助
Haris Krajina 2014年

2

文件的for循环使用$ IFS shell变量。在给定的脚本中,文件名中也带有$ IFS变量的字符将在for循环中被视为两个不同的文件。这个脚本解决了这个问题:在ubuntu框上,默认情况下,如sed-manual所示的multiline-mode修饰符似乎无法正常工作,因此我寻求了一个不同的实现,并使用了迭代标签,基本上只能在如果我正确理解文件的最后一行。

#!/bin/sh
#

# A git hook script to find and fix trailing whitespace
# 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 seperator, introduces EOL-bug?
IFS='
'
# Find files with trailing whitespace
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( MacOSx-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 whitespace-characters
exec git diff-index --check --cached $against --

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


2

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

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";
}

0

稍晚一些,但是既然这样可以帮助某个人,这里就来了。

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

:%s#\t#    #gc

摆脱其他尾随空格

:%s#\s##gc

这几乎为我做了。如果要编辑很多文件,这很繁琐。但是我发现它比预先提交钩子和与多个编辑器一起工作要容易。


如果变得乏味-如果您有要编辑的内容的备份-那么我经常只使用sed将制表符更改为空格:(sed -i 's|\t| |g' filenames替换位置的空格)。请注意,您可以使用find来获取文件名。如果您还没有想到如何获取该备份,我通常只需提交所有内容,然后通过软重置“撤消”提交即可回到我所在的位置。有时我将所有内容添加到树中,但不提交,有时我使用隐藏/应用(不是弹出!)。如果感到不安,请在干预之前将整棵树同步到一个安全的位置……
Sage 2014年

0

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

test -s file &&
   printf '%s\n' H ',g/[[:space:]]*$/s///' 'wq' | ed -s file

-1

这可能不会直接解决您的问题,但您可能希望通过git-config在实际项目空间中进行设置,该项目将编辑./.git/config而不是〜/ .gitconfig。很高兴让所有项目成员之间的设置保持一致。

git config core.whitespace "trailing-space,space-before-tab"
git config apply.whitespace "trailing-space,space-before-tab"

3
afaik,.git中的设置不会与其他任何人共享;它们特定于您的本地
仓库
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.