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

bde73dd3dfeab1085f5e78ff686d1f8c

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

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

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

o    «У этой опции предусмотрено 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 именно в той точке, где данная ветка отходит от главной.

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

o    Опция 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

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

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

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

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

rebase.autosquash true

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

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

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

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

o    Теперь при выполнении 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 автоматически отправить актуальную ветку в одноимённую удалённую ветку.

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

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

o    Кажется, что 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, т.д. В качестве её значения обычно задают:

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

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

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

o    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//"]
path = "~/code//.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

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

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

o    status.submoduleSummary true

o    diff.submodule log

o    submodule.recurse true

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[diff]
	algorithm = histogram

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

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

o    init.defaultBranch main

o    push.autoSetupRemote true

o    merge.tool meld

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

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

o    diff.algorithm histogram

o    branch.sort -committerdate

o    merge.conflictstyle zdiff3

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

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

Вот и всё!

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

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

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

© Habrahabr.ru