Как запустить средство форматирования кода поверх моего источника без изменения истории git?

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

Есть команда git filter-branch, которая позволяет вам запускать команды для каждой ревизии репо, начиная с начала времени.

git filter-branch --tree-filter '\
  npx prettier --write "src/main/web/app/**/**.{js, jsx}" || \
  echo "Error: no JS files found or invalid syntax"' \
  -- --all

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

7 ответов

Решение

То, что вы пытаетесь сделать, невозможно. Вы не можете в какой-то момент изменить строку кода, и все же получить git-отчет о том, что самое последнее изменение в этой строке кода произошло до того момента.

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

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

Кстати, в переписывании истории есть пара минусов. Вы упомянули время обработки, поэтому давайте сначала посмотрим на это:

Как вы заметили, простой способ сделать это с filter-branch будет очень много времени. Есть вещи, которые вы можете сделать, чтобы ускорить его (например, предоставить виртуальный диск для рабочего дерева), но это tree-filter и это включает обработку каждой версии каждого файла.

Если бы вы сделали некоторую предварительную обработку, вы могли бы быть несколько более эффективными. Например, вы можете предварительно обработать каждый BLOB в базе данных и создать отображение (где TREE содержит BLOB X, заменить его на BLOB Y), а затем используйте index-filter выполнить замены. Это позволит избежать всех операций извлечения и добавления и избежать повторного форматирования одних и тех же файлов кода. Так что это экономит много ввода / вывода. Но это нетривиальная вещь для настройки, и все же может занять много времени.

(Можно написать более специализированный инструмент, основанный на этом же принципе, но AFAIK никто не написал. Существует прецедент, что более специализированные инструменты могут быть быстрее, чем filter-branch...)

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

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

Таким образом, переписывание истории - довольно радикальное решение. И с другой стороны, также кажется решительным предположить, что форматирование кода невозможно просто потому, что это не было сделано с первого дня. Итак, мой совет:

Сделайте переформатирование в новом коммите. Если вам нужно использовать git blame, и он указывает вам на коммит, где произошло переформатирование, затем выполните команду git blame снова на родительском коммите переформатирования.

Да, это отстой. Какое-то время. Но данный кусок истории имеет тенденцию становиться менее важным с возрастом, поэтому оттуда вы просто позволяете проблеме постепенно уйти в прошлое.

Вы можете игнорировать определенные коммиты, которые выполняют только массовое переформатирование и т. Д .:

Создать файл .git-blame-ignore-revs нравиться:

       # Format commit 1 SHA:
 1234af5.....
 # Format commit 2 SHA:
 2e4ac56.....

Тогда сделай

      git config blame.ignoreRevsFile .git-blame-ignore-revs

, так что вам не нужно использовать --ignore-revs-file вариант каждый раз с git blame.

Проголосуйте за https://github.com/github/feedback/discussions/5033, чтобы включить эту функцию в веб-программу просмотра обвинений на github.

git blame -w -M должен игнорировать пробелы и перемещенные изменения кода, поэтому вам просто нужно переформатировать код и не забыть использовать эти параметры при поиске виноватых!

https://coderwall.com/p/x8xbnq/git-don-t-blame-people-for-changing-whitespaces-or-moving-code

У Mercurial есть (экспериментальная) опция для этого "--skip":

--skip <REV[+]>
    revision to not display (EXPERIMENTAL)

Я думаю, что в git по умолчанию пока нет эквивалента, но есть команда hyper-blame, разработанная извне.

Аналогичный вариант (--ignore-rev <rev> а также --ignore-revs-file <file>доступен в git с версии 2.23: https://git-scm.com/docs/git-blame.

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

git filter-branch --tree-filter "find

-regex '.*.(cpp\|h\|c\|)' -exec <команда форматирования> {} \;" -- --все

< dir >: directory of related, так как выше необходимо запускать из корневого каталога, но вы можете отформатировать только определенный вспомогательный каталог в корневом каталоге git.

< etc >: другие форматы файлов.

< formatter-command >: команда, которую вы можете запустить для одного файла, и она будет форматировать этот файл.

--all в конце означает сделать это для всех веток git (всего 4 тире)

Например, это то, что у меня есть, где мой git содержит каталог src (кроме тестов, инструментов и т. Д.)

git filter-branch --tree-filter "find src -regex '. *. (cpp \ | h \ | cu \ | inl)' -exec clang-format -style = google -i {} \;" -- --все

Выше будет переписывать каждый коммит git, но не изменять аннотацию git. Так как это изменяет историю мерзавцев, каждый должен будет откинуться, как только это будет выдвинуто.

Должен быть способ отформатировать кодовую базу, сохраняя информацию об авторе для каждой строки.

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

Такова идея, но есть несколько серьезных причин, по которым вам не следует это делать:

  1. Перебазирование общей ветки - плохая идея. Тот факт, что вы даже заботитесь о сохранении авторства изменений, вероятно, означает, что есть много людей, активно работающих над кодом. Если вы пойдете и перебазируете основную ветку, то у каждого форка или клона вашего репо будет главная ветка со старой историей, и это неизбежно вызовет путаницу и боль, если вы не будете очень осторожны в управлении процессом и уверенности что все знают о том, что вы делаете, и обновляют свои копии соответствующим образом. Лучшим подходом, вероятно, было бы не перебазировать master, а вместо этого объединять коммиты из master в вашу ветку. Затем пусть все начнут использовать новую ветку вместо master,

  2. Слияние конфликтов. Переформатируя всю кодовую базу, вы, вероятно, собираетесь вносить изменения в большое количество строк почти в каждом файле. Когда вы объединяете последующие коммиты, будь то через rebase или же merge вам, скорее всего, придется решать большое количество конфликтов. Если вы воспользуетесь подходом, который я предложил выше, и объедините коммиты из master в вашу новую ветку вместо перебазирования, тогда будет легче упорядочить эти конфликты, потому что вы можете объединять несколько коммитов за раз, пока вас не поймают вверх.

  3. Неполное решение. Вам нужно будет выяснить, куда в истории вы хотите вставить свою операцию переформатирования. Чем дальше вы идете назад, тем больше вы сохраняете авторство изменений, но тем больше работы вам потребуется для объединения в последующие изменения. Таким образом, вы, вероятно, по-прежнему будете иметь много кода, в котором ваш коммит переформатирования будет последним изменением.

  4. Ограниченная выгода. Вы никогда не потеряете информацию об авторстве в git - просто инструменты обычно показывают, кто внес последние изменения. Но вы все равно можете вернуться к предыдущим коммитам и просмотреть всю историю любого фрагмента кода, включая того, кто его сделал. Таким образом, единственное, что вставляет вашу операцию переформатирования в историю, действительно покупает вас, это удобство просмотра того, кто изменил какой-то фрагмент кода без дополнительного шага возврата к более раннему коммиту.

  5. Это нечестно. Когда вы переписываете историю ветки, вы изменяете фактическую запись того, как код менялся с течением времени, и это может создать реальные проблемы. Давайте представим, что ваше переформатирование не так уж несущественно, как вы предполагаете, и, выполняя переформатирование, вы фактически создаете ошибку. Скажем, например, что вы вводите дополнительный пробел в многострочную строковую константу. Несколько недель спустя, кто-то наконец замечает проблему и ищет причину, и похоже, что изменение было сделано полтора года назад (потому что именно там вы вставили свое переформатирование в историю). Но проблема кажется новой - она ​​не обнаруживается в сборке, поставленной два месяца назад, так что, черт возьми, происходит?

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

Если вы не хотите, чтобы ваше имя отображалось в качестве автора каждой строки в вашем проекте, но вы также не хотите мириться с проблемами, описанными выше, тогда вы можете переосмыслить свой подход. Лучшим решением может быть решение переформатирования как команды: попросите всех в команде согласиться запускать средство форматирования для любого файла, который они изменяют, и сделайте правильное форматирование обязательным требованием во всех обзорах кода в будущем. Со временем ваша команда охватит большую часть кода, и информация об авторстве будет в основном уместной, поскольку каждый файл, который будет переформатирован, должен был быть изменен в любом случае. В конечном итоге вы можете получить небольшое количество файлов, которые никогда не переформатируются, потому что они очень стабильны и не нуждаются в обновлениях, и вы можете переформатировать их (потому что некоторые плохо отформатированные файлы сводят вас с ума) или нет (потому что в любом случае никто не работает в этих файлах).

Каждый коммит имеет информацию об авторе: автор (имя, адрес электронной почты и дата) и коммиттер (имя, адрес электронной почты, дата). git annotate/blame использовать информацию об авторе, когда они показывают файл.

Если вы хотите редактировать только один недавний коммит git commit --amend, С этим git сохраняет имя автора / адрес электронной почты / дату, но изменяет имя автора / адрес электронной почты / дату. Сохраненное имя автора / адрес электронной почты / дата создания git annotate/blame продолжать показывать ту же информацию об авторстве.

Я уверен git filter-branch делает то же самое - сохраняет имя автора / адрес электронной почты / дату, но меняет имя / адрес электронной почты / дату коммиттера.

Другие вопросы по тегам