git pre-commit форматирование кода ловушки с частичной фиксацией?
Есть ли способ получить хук предварительной фиксации, который автоматически форматирует код (например, с astyle
) но не разрушает ли частичный коммит?
Процедура:
# edit a file.txt
git add -p file.txt
# add one chunk, but not another
git commit -m 'a message'
[PRE_COMMIT_HOOK] Formatting source code
git status
# the "another" chunk is still not added
Моя проблема в том, что если вы делаете git add
внутри хука pre-commit, который требуется после того, как скрипт отформатировал исходный код, также добавляет "другой" чанк. Но я этого не хочу.
Есть ли способ добиться этого?
2 ответа
Я бы сделал это, выполнив работу с низкоуровневыми "сантехническими" командами, моей первой попыткой было бы что-то вроде
git ls-files --stage \*.c | while read mode object stage path; do
case $mode in
10*)
formatted=`git show $object | indent | git hash-object -w --stdin`
git update-index --cacheinfo $mode $formatted "$path"
;;
esac
done
Чтобы избежать избыточной обработки, начните с git diff-index --name-only --diff-filter=AM
вывод, как подсказывает @torek.
Есть (вроде) способ сделать это. Я бы не стал, но если вы действительно хотите, продолжайте в том же духе.
Во-первых, вам нужно выделить два элемента, которые у вас есть:
- поэтапные изменения
- незадействованные элементы дерева работ
Более того, вы хотите, чтобы первый набор был доступен для переформатирования.
Это может быть сделано с git stash
, как я показал в ответе на вопрос " Как правильно сделать git stash/pop в хуках pre-commit, чтобы получить чистое рабочее дерево для тестов?" (см. там же предупреждение об ошибке в git stash).
По сути, вы хотите достичь точки в сценарии там, где запускаются тесты:
# Run tests
status=...
Находясь в этом состоянии, вы можете запускать элементы рабочего дерева через средства форматирования и git add
результат в хуке предварительной фиксации (как вы уже обнаружили). Это позволит избежать форматирования "другого" чанка, поскольку его не было в версии рабочего каталога. Затем вы можете позволить коммиту продолжаться (т. Е. Остальная часть сценария здесь не применяется).
Проблема теперь в восстановлении версии рабочего дерева из тайника. Поскольку вы изменили индекс, вы не сможете вернуться к нему, даже после завершения коммита:
# Restore changes
git reset --hard -q && git stash apply --index -q && git stash drop -q
Вместо этого вам нужно найти разницу между спрятанным индексом (stash^1
) и спрятанное дерево работы (stash
), и примените это к новому HEAD
совершить. Есть по крайней мере два способа сделать это без использования команд git. Оба могут привести к конфликтам из-за переформатирования зафиксированной версии:
git diff stash^1 stash | git apply --reject
(и в конце концовgit stash drop
)git stash branch tempbranch; git commit -m for-cherry-pick; git checkout prev-branch; git cherry-pick -n tempbranch; git branch -D tempbranch
Метод 1 более простой, но грязный, поскольку изменения, которые могут привести к конфликтам слияния, сбрасываются в "отклоненные" файлы В методе 2 используется механизм слияния, поэтому при необходимости изменения получают маркеры конфликта. (Если нет конфликтов, -n
предотвращает коммит, так что вы можете сделать свое собственное с реальным сообщением, вместо того, чтобы скопировать пустышку for-cherry-pick
сообщение.)
Конечно, я не проверял ничего из этого. Также есть способы сделать это без использования git stash
такие как проверка версий индекса git add
-ed-файлы в отдельную директорию, форматирование там, а затем добавление обратно отформатированных версий, чтобы ни один из этих процессов не влиял на текущую рабочую директорию. В любом случае, это лучше, если вы действительно решили это сделать. Вот сценарий для этого метода (также не очень проверенный - ему нужно добавить немного надежности, используя -z
а также xargs -0
возможно, для обработки имен файлов, содержащих пробелы, в разделе оформления вывода diff-index):
# make a directory for formatting files
WORKDIR=$(mktemp -d -t reformat) || exit 1
# clean it up when we leave
trap "rm -rf $WORKDIR 0 1 2 3 15"
# for files Added or Modified in the index, copy them to $WORKDIR
git --work-tree=$WORKDIR checkout -- \
$(git diff-index --cached --name-only --no-renames --diff-filter=AM HEAD)
# reformat files in the work-dir
(cd $WORKDIR; ...)
# for each file in the work-dir, re-"add" that version to this tree
# (this assumes the reformatter did not leave extraneous files!)
git --work-tree=$WORKDIR add --ignore-removal .
Вот что я бы порекомендовал вместо этого: вместо того, чтобы форматировать код в этот момент в хуке предварительной фиксации, просто проверьте , отформатирован ли он. Если это так, разрешите коммит. Если нет, откажитесь. Это намного больше в духе ловушки перед фиксацией, и это позволяет использовать скрипт в этом другом ответе. По сути, в точке, где говорится:
status=...
вы просто запускаете что-то, что проверяет, изменит ли форматер что-либо (возможно, позволяя форматировщику делать свое дело и видеть, отличается ли результат от того, что находится в индексе, подлежащем фиксации). Это дает вам ваш статус. Затем вы делаете все остальное, что в сценарии, с git reset --hard -q && git stash apply --index -q && git stash drop -q
восстановить все так, как было, когда был создан тайник.