Pull to refresh

Популярные конфигурационные опции для работы с git

Reading time10 min
Views12K
Original author: Julia Evans

Привет! Я всегда мечтала, чтобы в инструментах для работы с командной строкой заранее сообщалось, насколько популярны те или иные конфигурационные опции, предусмотренные в них, например:

  • «В принципе, никто этим не пользуется»

  • «Этой опцией пользуется 80% аудитории, стоит ознакомиться»

  • «У этой опции предусмотрено 6 возможных значений, но в реальной практике применяется всего 2 из них».

Так что я решила спросить пользователей Mastodon, какие у них любимые опции конфигурации git:

А какие опции git config вы больше всего любите выставлять? В настоящее время у меня в ~/.gitconfig установлены только git config push.autosetupremote true и git config init.defaultBranch main, вот интересуюсь, а что выставляют другие люди.

Как обычно, получила КУЧУ отличных откликов и так узнала множество очень популярных опций конфигурации git, о которых ранее никогда не слышала.

Далее перечислю их по порядку, при этом (очень примерно) попытаюсь начать с наиболее популярных.

Все описанные опции документированы на странице man git-config, а также на этой странице.

pull.ff only ИЛИ pull.rebase true

Оказывается, две эти опции наиболее популярны. Назначение у них схожее: перестраховаться от того, чтобы случайно не выполнить коммит слияния (merge commit) при выполнении git pull именно в той точке, где данная ветка отходит от главной.

  • Опция pull.rebase true эквивалентна выполнению git pull --rebase при каждом git pull

  • Опция pull.ff only эквивалентна выполнению git pull --ff-only при каждом git pull

Совершенно уверена, что использовать обе эти опции одновременно не имеет смысла, поскольку --ff-only перекрывает --rebase.

Лично я не пользуюсь ни одной из них, поскольку предпочитаю сама решать, как разобраться с такой ситуацией в каждом конкретном случае. Теперь же в git по умолчанию принято такое поведение: если ваша ветка отклоняется от вышестоящей – просто выбросить исключение и спросить вас, что делать дальше (функционально это очень напоминает принцип действия git pull --ff-only).

merge.conflictstyle zdiff3

Далее: хорошо бы сделать, чтобы описания конфликтов при слиянии получались максимально удобочитаемыми! Опции merge.conflictstyle zdiff3 и merge.conflictstyle diff3 оказались крайне популярными (их даже описывают как «совершенно незаменимые»).

Суть, по‑видимому, во всеобщем консенсусе по следующему тезису: «diff3 отличная, а zdiff3 (более новая) — даже лучше!».

Итак, давайте подробнее разберёмся с diff3. По умолчанию в git конфликты при слиянии имеют следующий вид:

<<<<<<< HEAD
def parse(input):
    return input.split("\n")
=======
def parse(text):
    return text.split("\n\n")
>>>>>>> somebranch

Предполагается, что это я должна решить, что лучше: input.split("\n") или text.split("\n\n"). Но как? Что если я просто не помню, как правильнее —\n или \n\n? Тут нам и пригодится diff3!

Вот как выглядит тот же самый конфликт при слиянии, но с установленной опцией merge.conflictstyle diff3:

<<<<<<< HEAD
def parse(input):
    return input.split("\n")
||||||| b9447fc
def parse(input):
    return input.split("\n\n")
=======
def parse(text):
    return text.split("\n\n")
>>>>>>> somebranch

Здесь есть дополнительная информация: теперь исходная версия кода оказалась в середине! Соответственно, можем убедиться, что:

  • С одной стороны \n\n изменилось на \n

  • С другой стороны input переименовано в text

Поэтому можно предположить, что корректное разрешение конфликта оформляется так: return text.split("\n"), поскольку так сочетаются изменения с обеих сторон. Я не пользовалась zdiff3, но, по-видимому, многим кажется, что с ней удобнее. Подробнее этот вопрос рассмотрен в статье Better Git Conflicts with zdiff3.

rebase.autosquash true

Возможность автоматического склеивания (Autosquash) также была для меня в новинку. С её помощью становится значительно проще вносить изменения в старые коммиты.

Вот как это устроено:

  • Допустим, у вас есть коммит, который вы хотели бы объединить с другим коммитом, зафиксированным три операции назад, например, с add parsing code

  • Вы фиксируете его операцией git commit --fixup OLD_COMMIT_ID, и в результате получается новый коммит, сопровождаемый сообщением fixup! add parsing code

  • Теперь при выполнении git rebase --autosquash main, эта операция автоматически скомбинирует все коммиты fixup! именно с теми коммитами, с которыми нужно.

rebase.autosquash true означает, что --autosquash  обязательно автоматически передаётся в git rebase.

rebase.autostash true

При этой опции автоматически выполняется git stash, до git rebase и после git stash pop. В принципе, здесь --autostash передаётся git rebase.

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

push.default simple, push.default current

Такие опции push.default приказывают git push автоматически отправить актуальную ветку в одноимённую удалённую ветку.

  • push.default simple действует в Git по умолчанию. Сработает только в том случае, если в вашей ветке уже отслеживается удалённая ветка.

  • push.default current похожа на предыдущую, но она всегда отправляет локальную ветку в одноимённую удалённую.

  • Кажется, что push.autoSetupRemote и push.default simple в сумме делают то же самое, что и push.default current

Представляется, что опция current хороша в качестве исходной, если вы совершенно уверены, что случайно не создадите локальную ветку, которая совпадёт по имени с какой-нибудь удалённой. Очень многие специалисты придерживаются таких соглашений об именовании веток (например, julia/my-change), при которых конфликты такого рода становятся очень маловероятны. Также можно постараться работать в малых группах, где все находятся в контакте друг с другом и поэтому не допускают конфликтов имён в названиях веток.

init.defaultBranch main

Создаёт ветку main, а не master при закладывании нового репозитория.

commit.verbose true

Эта опция добавляет в текстовый редактор полное описание отличий данного коммита, когда вы пишете сообщение о нём. Так впоследствии будет проще вспомнить, что было сделано в рамках данного коммита.

rerere.enabled true

Так активируется rerereповторно использовать сохранённое решение»), позволяющее запоминать, как именно разрешались конфликты при слиянии в процессе git rebase. Кроме того, эта опция автоматически разрешает конфликты за вас, когда есть такая возможность.

help.autocorrect 10

По умолчанию функция автокоррекции в git пытается проверить, нет ли опечаток (например git ocmmit), но исправленную команду сама не выполнит.

Если вы хотите, чтобы предложенный исправленный вариант выполнялся автоматически, то можете установить help.autocorrect в значение 1 (выполнить через 0,1 секунды), 10 (выполнить через 1 секунду), immediate (сразу же выполнить) или prompt (выполнить после приглашения).

core.pager delta

В данном случае «pager» — это информация о размере страниц, которую git использует для отображения вывода git diffgit loggit show, т.д. В качестве её значения обычно задают:

  • delta (интересный инструмент для просмотра разницы, в котором предусмотрена подсветка синтаксиса)

  • less -x5,9 (устанавливает табфокусы, полагаю, это упрощает работу в случае, когда приходится иметь дело со множеством файлов, в которых многократно встречается табуляция?)

  • less -F -X (не уверена, как именно работает эта опция. Видимо, -F отключает информацию о разбивке на страницы, если весь вывод точно умещается на экране. Только мне кажется, что моя версия git и так это делает)

  • cat (вообще отключает разбивку на страницы)

В своё время я пользовалась delta, но потом отключила её, поскольку с ней путалась в цветовой схеме моей командной строки и не знала, как это исправить. Всё равно, на мой взгляд, это отличный инструмент.

Думаю, при работе с delta также просится опция interactive.diffFilter delta --color-only – с ней подсветка синтаксиса в коде включается лишь после выполнения команды git add -p.

diff.algorithm histogram

Алгоритм оценки различий, действующий в Git по умолчанию, часто обрабатывает те функции, которые были изрядно переупорядочены. Например, рассмотрим этот код:

-.header {
+.footer {
     margin: 0;
 }

-.footer {
+.header {
     margin: 0;
+    color: green;
 }

По-моему, здесь сплошная путаница. Но после применения diff.algorithm histogram код приобретает следующий вид, и в такой форме кажется мне гораздо более понятным:

-.header {
-    margin: 0;
-}
-
 .footer {
     margin: 0;
 }

+.header {
+    margin: 0;
+    color: green;
+}

Некоторые предпочитают в данном случае писать patience, но мне кажется, что histogram популярнее. Подробнее об этом рассказано в статье When to Use Each of the Git Diff Algorithms.

core.excludesfile: глобальный файл .gitignore

При помощи опции core.excludeFiles = ~/.gitignore можно заложить глобальный файл gitignore, применимый ко всем репозиториям. Он нужен для таких вещей как .idea или .DS_Store, которые вы точно не захотите коммитить ни в один репозиторий. По умолчанию эта опция принимает значение ~/.config/git/ignore.

includeIf: как отделить конфигурацию git от личных и рабочих файлов

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

[includeIf "gitdir:~/code/<work>/"]
path = "~/code/<work>/.gitconfig"

url."git@github.com:".insteadOf 'https://github.com/'

Часто бывает, что я случайно склонирую HTTP-версию репозитория, а не SSH-версию. В таком случае приходится вручную переходить в ~/.git/config, чтобы отредактировать удалённый URL. Конечно, удобно иметь такой красивый обходной путь: данная команда заменит https://github.com в удалённых репозиториях на git@github.com:.

Вот как это выглядит в ~/.gitconfig :

[url "git@github.com:"]
	insteadOf = "https://github.com/"

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

Некоторые также упомянули установку insteadOf = "gh:", которая располагает к применению команды git remote add gh:jvns/mysite, позволяющей минимумом усилий добавить удалённый репозиторий.  

fsckobjects: помогает избежать повреждения данных

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

transfer.fsckobjects = true
fetch.fsckobjects = true
receive.fsckObjects = true

Материал, касающийся субмодулей

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

  • status.submoduleSummary true

  • diff.submodule log

  •   submodule.recurse true

Не берусь объяснять эти настройки, но отсылаю вас к этому комментарию с Mastodon, написанному пользователем @unlambda.

И многие другие

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

  • blame.ignoreRevsFile .git-blame-ignore-revs позволяет указать файл с коммитами, которые следует игнорировать в период git blame, поэтому обширные переименования не мешают вам работать

  • branch.sort –committerdate обеспечивает сортировку git branch не в алфавитном порядке, а по тем веткам, которые были задействованы в самое последнее время. В таком случае находить ветки становится проще. Опция tag.sort taggerdate аналогично работает с тегами.

  • color.ui false: отключить цвета

  • commit.cleanup scissors: позволяет написать #include в сообщении о коммите, и при этом # не будет считаться знаком комментария и, соответственно, не будет удаляться

  • core.autocrlf false: в Windows, чтобы было удобнее работать с ребятами, предпочитающими Unix

  • core.editor emacs: позволяет использовать emacs (или другой подобный инструмент) для редактирования сообщений о коммитах

  • credential.helper osxkeychain: использовать связку ключей Mac для управления ключами

  • diff.tool difftastic: использовать difftastic (или meld, или nvimdiffs) для вывода различий между коммитами

  • diff.colorMoved default: подсвечивание разными цветами тех строк в различиях между коммитами, которые были «перемещены»

  • diff.colorMovedWS allow-indentation-change: при установленной опции diff.colorMoved также игнорируются изменения отступов

  • diff.context 10: в разницу между коммитами включается дополнительный контекст

  • fetch.prune true и fetch.prunetags автоматически избавляются от  удалённых веток наблюдения

  • gpg.format ssh: позволяет подписывать комментарии SSH-ключами

  • log.date iso: отображает даты в формате 2023-05-25 13:54:51, а не Thu May 25 13:54:51 2023

  • merge.keepbackup false, чтобы избавиться от файлов .orig, которые git создаёт при конфликтах слияния

  • merge.tool meld (или nvim, или nvimdiff), чтобы разрешать конфликты слияния можно было, в том числе, с применением git mergetool 

  • push.followtags true: отправлять новые теги в процессе отправления коммитов

  • rebase.missingCommitsCheck error: не допускает удаления коммитов в процессе перебазирования

  • rebase.updateRefs true: значительно упрощает перебазирования множества вложенных веток, образующих стек. Вот пост об этом.

Как их устанавливать

Обычно я устанавливаю опции конфигурации git при помощи git config --global NAME VALUE, например, git config --global diff.algorithm histogram. Все мои опции я обычно устанавливаю глобально, поскольку это стимулирует меня предусматривать разные варианты поведения git в разных репозиториях.

Если требуется удалить опцию, я вручную редактирую ~/.gitconfig, такие изменения принимают следующий вид:

[diff]
	algorithm = histogram

Какие изменения в конфигурацию я внесла, дописав этот пост

Конфигурация git у меня, можно сказать, минимальная. Вот что у меня уже было выставлено:

  • init.defaultBranch main

  • push.autoSetupRemote true

  • merge.tool meld

  • diff.colorMoved default (кстати, по какой-то причине она у меня не работает, но я никак не найду времени заняться отладкой)

и ещё три опции я добавила по итогам этого поста:

  • diff.algorithm histogram

  • branch.sort -committerdate

  • merge.conflictstyle zdiff3

Пожалуй, я бы добавила ещё rebase.autosquash, если бы на данном жизненном этапе я чаще имела дело с аккуратно составленными пул-реквестами, в которых содержится много коммитов.

Я научилась определённой осторожности при выставлении новых конфигурационных опций. Мне требуется немало времени, чтобы привыкнуть к новому поведению инструмента и, если я сразу изменю много вещей, то просто запутаюсь. В определённой мере я уже и так пользуюсь branch.sort -committerdate (под псевдонимом), и практически согласна, что с diff.algorithm histogram будет проще читать сообщения о разнице коммитов, если при работе я переупорядочиваю функции.

Вот и всё!

Не устаю удивляться, как же бывает полезно просто спросить людей в сообществе об их предпочтениях, а потом перечислить наиболее частые ответы. Схожим образом я пару лет назад составила этот список новых инструментов командной строки. Согласитесь, гораздо эффективнее выбирать из списка на 20-30 вариантов, чем рыться сразу во всём списке конфигурационных опций git, которых там около 600.

Составляя этот список, я немного путалась, так как со временем успели несколько измениться опции, выставляемые в git по умолчанию. Иногда люди просто сами выставляют такие опции, которые 8 лет назад уже казались важными, а сегодня действуют по умолчанию. Кроме того, есть некоторые экспериментальные опции, которыми ранее кто-то пользовался, а сегодня эти опции уже удалены и заменены новыми.

Я максимально постаралась в точности описать, как git работает прямо сейчас, в начале 2024 года, но определённо могла допустить здесь какие‑то ошибки, в особенности потому, что сама всеми этими опциями не пользуюсь.

Tags:
Hubs:
Total votes 28: ↑27 and ↓1+38
Comments15

Articles