Сделайте так, чтобы Git автоматически удалял конечные пробелы перед фиксацией

Я использую Git со своей командой и хотел бы удалить изменения пробелов из моих различий, журналов, слияний и т. Д. Я предполагаю, что самый простой способ сделать это - Git автоматически удалить конечные пробелы (и другие ошибки пробелов) из всех коммитов по мере их применения.

Я попытался добавить в ~/.gitconfig файл следующее, но при фиксации ничего не происходит. Может быть, он предназначен для чего-то другого. Какое решение?

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

Я использую Ruby на случай, если у кого-то есть какие-то конкретные идеи Ruby. Следующим шагом будет автоматическое форматирование кода перед фиксацией, но это серьезная проблема, которая на самом деле не вызывает большой проблемы.

Ответов (16)

Я думал об этом сегодня. Это все, что я сделал для проекта Java:

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

Вы можете обманом заставить Git исправить пробелы за вас, заставив Git обрабатывать ваши изменения как патч. В отличие от решений «pre-commit hook», эти решения добавляют в Git команды исправления пробелов.

Да, это хаки.


Надежные решения

Следующие псевдонимы Git взяты из моего~/.gitconfig .

Под «надежным» я подразумеваю, что эти псевдонимы выполняются без ошибок, выполняя правильные действия, независимо от того, является ли дерево или индекс грязным. Однако они не работают, если интерактив git rebase -i уже выполняется; см. мои~/.gitconfig дополнительные проверки, если вам небезразличен этот угловой случай, где git add -e трюк, описанный в конце, должен сработать.

Если вы хотите запускать их прямо в оболочке, не создавая псевдонима Git, просто скопируйте и вставьте все в двойные кавычки (при условии, что ваша оболочка похожа на Bash).

Исправьте индекс, но не дерево

Следующий fixws псевдоним Git исправляет все ошибки с пробелами в индексе, если они есть, но не затрагивает дерево:

# 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://answacode.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-index псевдоним Git исправляет все ошибки пробелов в индексе и дереве, если таковые имеются:

# 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.whitespaceзначение fix(вам нужно сделать это только один раз):

    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=: средстве для использования в : качестве редактора, и как команда : является тождественным.

Пожалуйста, попробуйте мои перехватчики перед фиксацией . Он может автоматически определять конечный пробел и удалять его .

Он может работать под 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(-)

Для пользователей Sublime Text .

Правильно установите следующие параметры в конфигурации Пользователь-Настройка .

"trim_trailing_white_space_on_save": true

Откройте файл в Vim. Чтобы заменить табуляцию пробелами, введите в командной строке Vim следующее:

:%s#\t#    #gc

Чтобы избавиться от других конечных пробелов

:%s#\s##gc

Это в значительной степени помогло мне. Это утомительно, если вам нужно редактировать много файлов. Но мне это показалось проще, чем хуки перед фиксацией и работа с несколькими текстовыми редакторами.

Чтобы удалить конечные пробелы в конце строк в файле переносимо, используйте ed :

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

Я написал эту ловушку перед фиксацией, которая удаляет только конечные пробелы из строк, которые вы изменили / добавили, поскольку предыдущие предложения, как правило, создают нечитаемые коммиты, если в целевых файлах слишком много конечных пробелов.

#!/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

Использование атрибутов 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 . Похоже, что это надо поместить в сценарий оболочки. Я не мог понять, как ввести команду sed, включая очистку посторонних лишних строк в конце файла, прямо в файл git-config. (Однако вы можете избавиться от конечных пробелов без необходимости в отдельном сценарии sed. Просто установите что- filter.fix-eol-eof то вроде того, sed 's/[ \t]*$//' %f где \t это фактическая вкладка, нажав Tab.)

Это require = true приводит к возникновению ошибки, если что-то пойдет не так, чтобы избежать неприятностей.

Эти настройки ( core.whitespace и apply.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 только с некоторыми изменениями, добавленными в промежуточную область, по-прежнему приводит к «атомарной» ревизии, которая, возможно, никогда не существовала в качестве рабочей копии и может не работать .


Например, Олдман предлагает в другой ответ на pre-commitкрючок , который детектирует и удалить пробелы.
Поскольку этот хук получает имя файла каждого файла, я бы рекомендовал быть осторожным с определенными типами файлов: вы не хотите удалять конечные пробелы в .md файлах (уценки)!

В 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 этом.

Или для глобального использования (с помощью приложения git post-commit hook ко всем текущим и будущим репозиториям ) вы можете вставить его $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- подобрать недавно добавленные шаблоны.

for Петля для файлов использует $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: как заменить новую строку (\ n) с помощью sed?

Это не удаляет пробелы автоматически перед фиксацией, но это довольно легко сделать. Я поместил следующий сценарий Perl в файл с именем git-wsf (исправление пробелов Git) в каталоге в $ PATH, чтобы я мог:

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

Это, вероятно, не решит напрямую вашу проблему, но вы можете установить их с помощью git-config в вашем фактическом пространстве проекта, который редактирует файл ./.git/config, а не файл ~ / .gitconfig . Приятно сохранять единообразие настроек для всех участников проекта.

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

Я лучше оставлю эту задачу вашему любимому редактору.

Просто установите команду для удаления конечных пробелов при сохранении.

Я нашел ловушку предварительной фиксации 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

Вот версия, совместимая с 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