Git tree-filter снова сбрасывает изменения в последовательных коммитах - PullRequest
0 голосов
/ 07 ноября 2018

Мы планируем применять стиль в формате clang в исходном репозитории. Мы ожидаем некоторых трудностей, поэтому мы хотим предоставить цель make для выполнения переформатирования текущей ветви от ее базы слияния с master до ветви HEAD.

В качестве упрощенного примера рассмотрим следующую команду:

git filter-branch -f --tree-filter '
  AFFECTED_FILES=$(git diff-index --diff-filter=AM --name-only $GIT_COMMIT^);
  echo; echo AFFECTED $AFFECTED_FILES;
  for f in $AFFECTED_FILES; do
    echo formatting $f;
    echo foo >> $f;
  done
' HEAD~10..HEAD

Мы запускаем древовидный фильтр для нескольких коммитов (мы просто ограничиваем это до нескольких последних коммитов, это уже демонстрирует проблему). Мы определяем затронутые файлы (мы только хотим коснуться файлов, добавленных или измененных в коммите). Для простоты (ошибку легче обнаружить), мы не используем здесь формат clang, а просто добавляем «foo» к каждому из этих затронутых файлов (замена echo foo >> $f на clang-format -i $f - это все, что необходимо для получения фактического код).

Он правильно применяет изменения, которые мы намереваемся. Однако в каждом, кроме первого коммита, он отбрасывает изменения, которые мы сделали ранее. Просматривая коммиты, предположим, что в файле some.txt вы видите «+ foo» в diff. В дочернем коммите для some.txt вы видите «-foo» в diff, даже если some.txt вообще не был изменен в дочернем коммите, а только someother.txt. Я выполнил это на произвольных тестовых репозиториях, демонстрируя то же поведение.

Я также попробовал следующее (возвращаясь к фактическому формату clang):

git filter-branch -f --tree-filter 'git clang-format --extensions cpp,h' -- HEAD~10..HEAD

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

Чего мне не хватает, чтобы избежать отмены изменений в детских коммитах? Нужно ли каким-либо образом обновлять индекс?

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Спасибо @CBBailey за быстрые и полезные ответы. С этими битами информации я нашел следующее решение:

git filter-branch -f --tree-filter 'echo;
  PREV=$(map $(git rev-parse $GIT_COMMIT^));
  echo PREV $PREV;
  AFFECTED_FILES=$(git diff --name-only $GIT_COMMIT^..$GIT_COMMIT | egrep "\.(h|cpp)$");
  echo AFFECTED $AFFECTED_FILES;
  PREV_AFFECTED_FILES=$(bash -c "comm -23 <(git diff --name-only HEAD~10..$GIT_COMMIT^ | egrep \"\.(h|cpp)$\" | sort -u) <(echo $AFFECTED_FILES | sort -u)");
  echo PREV_AFFECTED $PREV_AFFECTED_FILES;
  for f in $PREV_AFFECTED_FILES; do
    echo "checking out $f";
    git checkout $PREV -- $f;
  done;
  for f in $AFFECTED_FILES; do
    echo formatting $f;
    clang-format -i $f;
  done
' -- HEAD~10..HEAD

В дополнение к файлам, затронутым фиксацией, он также определяет все файлы, которые были затронуты в данном диапазоне фиксации до текущей фиксации (PREV_AFFECTED_FILES). Они фильтруются для файлов, которые также были затронуты текущим коммитом (нам нужно запустить это в bash, так как sh, который используется filter-branch, не поддерживает подстановку процессов с использованием <()). Мы используем функцию map, которая определяется filter-branch (см. Последний абзац в разделе Filters документации по filter-branch ), чтобы определить переписанный коммит предшественника (PREV). Все ранее затронутые файлы затем извлекаются из этого коммита (поэтому нам нужно отфильтровать PREV_AFFECTED_FILES, чтобы он не содержал ни одного из AFFECTED_FILES, в противном случае мы перезаписали бы наши изменения). Файлы, затронутые в текущем коммите, затем форматируются. Использование индекса-фильтра может быть еще быстрее. Однако с учетом ограничений на переформатирование только измененных файлов и извлечение ранее измененных файлов этого достаточно для нашего варианта использования.

Вы можете увидеть окончательную версию в нашей системе сборки ( script , invocation ). Он содержит дополнительные улучшения, например, использование GNU Parallel для ускорения форматирования файлов.

0 голосов
/ 07 ноября 2018

Древовидный фильтр в git filter-branch просматривает состояние файлов при каждом коммите, но изменение этих файлов за один коммит не влияет на состояние файлов в следующем коммите, на которое смотрит древовидный фильтр. Это означает, что если вы внесете некоторые изменения только в один коммит в вызове git filter-branch, то эти изменения не будут распространены на потомков этого коммита. Это означает, что дерево этих дочерних элементов останется неизменным по сравнению с предварительно переписанным коммитом и, следовательно, будет казаться, что отменяет пользовательские изменения, внесенные в их переписанный родительский элемент.

Чтобы достичь того, что вы хотите, вы, вероятно, захотите рассмотреть другой набор AFFECTED_FILES, такой как diff вместо HEAD~10 вместо только родительского коммита, чтобы убедиться, что любой файл, который был ранее переписан, все еще переформатируется. (Обратите внимание, что это не идеально, потому что если файл возвращается к точному состоянию, в котором он находился в HEAD~10, тогда он не будет переформатироваться снова, но это может быть крайний случай, который достаточно редок, чтобы он Не стоит кодировать вокруг - или вы могли бы включить различия в отношении всех родителей и основы операции filter-branch.)

...