Популярные конфигурационные опции для работы с git
Привет! Я всегда мечтала, чтобы в инструментах для работы с командной строкой заранее сообщалось, насколько популярны те или иные конфигурационные опции, предусмотренные в них, например:
«В принципе, никто этим не пользуется»
«Этой опцией пользуется 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 diff
, git log
, git 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, чтобы было удобнее работать с ребятами, предпочитающими Unixcore.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 2023merge.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 года, но определённо могла допустить здесь какие‑то ошибки, в особенности потому, что сама всеми этими опциями не пользуюсь.