Comments 31
Одно из первых наставлений, которое молодой падаван получает вместе с доступом к git-репозиториям, звучит так: «никогда не ешь жёлтый снег делай git push -f».
Я думаю, на этом месте можно заканчивать читать.
1. У вас отвратительный процесс онбординга.
2. У вас отсутствует документированный девелопмент-процесс
3. Вы в штыки воспринимаете опыт, отличающийся от вашего.
У нас в команде примерно каждый пользуется форс-пушем и rebase --interactive по многу раз за день. И тренинги про гит и наше воркфлоу — одни из первых строк в онбординг-плане.
примерно каждый пользуется форс-пушем и rebase --interactive по многу раз за день
а чем это вызвано? или ваше "наше воркфлоу" к этому принуждает? просто интересно, т.к. всего пару-тройку раз требовалось использовать что одно, что другое...
а чем это вызвано? или ваше «наше воркфлоу» к этому принуждает? просто интересно, т.к. всего пару-тройку раз требовалось использовать что одно, что другое...
Не принуждает, это просто невероятно удобно =)
Если вкратце, то исторически сначала команда работала в Геррите, где ревью per-commit. Соответственно, очень высокая девелоперская культура в команде, перед отправкой на ревью история причесывается. Сейчас уже почти три года в Гитлабе, но культура не ухудшилась.
— Перед отправкой на ревью причесываешь локальную историю
— Когда фиксишь фидбек по ревью, то делаешь fixup конкретных коммитов, так, что история остается чистой, соответственно, rebase + force push в свою ветку
— Вливается PR/MR по стратегии rebase + ff-only. Т.е. история в мастере такая же чистая. Если в конкретном случае конкретный разработчик хочет влить MR сквошем — никто не запрещает, можно выбрать сквош и написать внятное коммит-сообщение.
Побочные эффекты всего этого — нет никаких «мусорных» коммитов, история и локальная и в мастере всегда чистая, git log, git log --oneline, git log --graph — одни из главных помощников в работе, нет ухудшающих читаемость мерж-коммитов.
И в том числе поэтому у меня текут глаза, когда я вижу в публичных проектах на гитхабе коммиты, написанные левой пяткой, а в особо запущенных случаях это не только в PR, но и в мастере оказывается. За кривые коммиты принято бить по рукам, это воспитывает :)
Вы в штыки воспринимаете опыт, отличающийся от вашего
В поддержку автора комментария хочу привести ссылку на статью, где я сам впервые прочитал про подобный процесс: Commit Often, Perfect Later, Publish Once: Git Best Practices.
Там был эпичный абзац про изготовление сосисок, который запал мне в душу:
The process of developing software, similar to the process of making sausage, is a messy messy business; all sorts of stuff happens in the process of developing software. Bugs are inserted into the code, uncovered, patched over. The end result may be a tasty program, but anyone looking at the process of how it was created (through inspection of the commits) may end up with a sour taste in their mouth. If you hide the sausage making, you can create a beautiful looking history where each step looks as delicious as the end-product.
В целом, мне эти ценности близки, поэтому и я сам стараюсь по-возможности придерживаться подхода "часто комитить — причёсывать — публиковать".
Мне этот абзац сначала понравился, а потом я вспомнил про нарциссические расстройства личности) Вот она, сила неправильных аналогий!
Красивая (правильнее будет сказать — облагороженная) история сама по себе ценности не имеет. А вот удобство код-ревью и сохранение важной смысловой информации для тех инженеров, кто будет читать этот код через 5 и даже 10 лет — очень даже пересчитываются в деньги и время.
git log, git log --oneline, git log --graph — одни из главных помощников в работе, нет ухудшающих читаемость мерж-коммитов.
— Когда фиксишь фидбек по ревью, то делаешь fixup конкретных коммитов, так, что история остается чистой, соответственно, rebase + force push в свою ветку
И ещё потом прогон юнит тестов для всех коммитов, на которые повлиял rebase?
По памяти как минимум Sublime Merge умеет делать патч-коммиты. Вроде ещё каким-то клиенты были, но сейчас не вспомню.
Спасибо, хорошее уточнение. А Sublime merge умеет их делать на уровне строк? Потому что например Atlassian SourceTree умеет, но на уровне hunks, т.е. таких законченных кусков изменений.
PHPStorm тоже умеет коммитить отдельные куски изменений.
В TortoiseGit тоже можно делать частичные коммиты, в том числе и на уровне строк, но там каждый раз нужны небольшие танцы с бубном (Restore after commit / Mark / Leave).
PHPStorm только сплошные куски может, вроде как. Не может 3 строки из 5 подряд идущих в диффе и уж точно не может редактировать то, что коммитится без редактирования рабочей копии. По крайней мере года два назад такое было
Есть git-gui (пакет именно так и называется). Им можно стейжить как целые куски (hunk), так и построчно. Но это если отдельным инструментом. А вообще всё достаточно технично делается в JetBrains (вроде во всех их IDE оно одинаково) — если нужно распилить какой-то коммит — делаем интерактивный ребейс, начиная с предыдущего перед ним коммита (правый клик — interactively rebase from here). Ставим на нём точку edit, запускаем rebase. Потом кликаем в целевой коммит (который надо распилить) и выбираем compare with local. А дальше уже как фантазия пойдёт. Можно кусками, можно строчками, можно хоть отдельными символами. Можно вообще всё переписать (например, если в конечном результате "А" стало "Б", то в промежуточном можно "А" заменить на "Г", а следующим шагом уже "Г" заменить на конечное "Б".). Несомненный плюс такого подхода — каждое изменение можно протестировать отдельно. Мы имеем "предыдущее" состояние и просто накатываем на него "текущее" отдельными кусками. Это не "полукоммит", когда все изменения на самом деле уже в коде, а мы просто выбираем, что в этот коммит пойдёт половина строк, а в следующий — вторая половина, а именно полноценный коммит. Если мы решили взять в него ровно половину изменений — мы можем прогнать тесты и убедиться в работоспособности. И потом просто продолжить ребейс. Вторая половина изменений подтянется сама; если в промежуточном коммите были какие-то нетривиальные изменения (больше, чем просто выбор отдельных чанков или строчек) — надо будет по горячим следам поправить эти конфликты. И всё.
… и это очень удобно! После ребейза некоторые коммиты могут вообще схлопнуться из сотни строк в пару чанков, а то и вообще исчезнуть (хотя последнее часто вводит в ступор IDE (например, от JetBrains), которые пытаются контролировать процесс и вдруг не могут понять, куда внезапно исчез коммит).
Например, в ветке сделали переименование какой-нибудь сущности, вкоммитали. В это время в мастере провели ту же операцию. Последующий ребейс при применении изменений обнаружит, что коммит "уже применён" и просто его отбросит.
Может вы не знали, но не везде скрам. А там, где скрам — команда решает как задачи бить, а не скрам-мастер
И я написал, что скрам-мастер (если это скрам) должен добиться, чтобы software planning состоял не из blah-blah-blah и шуточек, а из реальной работы, в том числе, и правильной декомпозиции. Многие программисты просто не умеют правильно это делать, потому (как я выше и написал), им порой нужна помощь.
Практически любую задачу можно разбить на «атомарные» подзадачи, при этом «убиваются» несколько «зайцев» сразу: небольшие, разумные коммиты
Это решение технической проблемы административными средствами.
Проекту год с небольшим, постоянно пишут код от 1.5 до 2.5 чел. Ребейзим постоянно, мерджи 99% только FF, история прекрасна и чиста. Вероятно, всё-таки большое значение имеет размер команды — чем меньше, тем проще так делать.
Лично я пользуюсь как IDEA, так и TortoiseGit — часть действий мне в нём делать привычнее.
Из лайфхаков. Если история ветки богатая на MR с базовой веткой и ребейз сложен из-за конфликтов, можно сделать Soft Reset на базовую ветку и закоммитить текущее состояние как единственный засквошенный коммит.
Хороший лайфхак!
Что до чистоты истории, то в перспективе девятилетней кодбазы, которую мы развиваем в iFunny, чистота не так важна, как понятность. Если фича сложная, то разбить её на несколько атомарных коммитов — гуманно по отношению к инженеру, который будет смотреть в этот код через 3-4 года. Squash-коммит приведёт его к тикету на разработку фичи, а почему было сделано именно такое изменение в коде — не факт что станет ясно.
git commit --amend --no-edit
git rebase -i origin/master
git push -f
Ежедневный набор)
Чесно не могу представить, как можно запрещать такую простую вещь как force push, в мастер при нормальной политике так не влить, а с другими ветками проблем нет.
На крайняк всегда есть reflog, благо git никогда ничего сам не забывает.
Еще очень полезно после работы над большой фичей, если не хочется сквошить все в один коммит, сдлеать ресет на корень фичи и потом из полученного собрать отдельные коммиты, в которых будут локализованы атомарные изменения.
Сейчас еще пробуем жить с Rebase & Push при влитии ПР в основную ветку, на выходе линейная история. А это резко облегчает жизнь.
потому что как ещё можно охарактеризовать реквест из 50 коммитов с почти 2000 изменённых строк? И как его, спрашивается, ревьюить?
По одному коммиту за раз, поняв общую сущность. А как ещё?
Если неоправданное дробление — да, жаловаться на него. Но таки бывает нужно иметь и 50 коммитов в цепочке, хоть и редко.
Ниже вы что-то подобное написали, но IMHO недостаточно явно.
Честно говоря, у меня ушло два дня просто на то, чтобы заставить себя приступить к этому ревью. И это нормальная реакция для инженера
Для красного словца — сойдёт. Если же всерьёз — предупредить, что требуется время; обычно в таких системах есть построчные замечания, но если нет — завести файлик и писать в него (а заодно пожаловаться на отсутствие критичного функционала). Вот это будет "нормальной реакцией для инженера".
Поэтому самым простым будет перед окончательной интеграцией в релиз схлопнуть все коммиты ветки в один при помощи git rebase.
Этот рецепт должен сопровождаться обязательным "только если все коммиты одного типа" (изменение функциональности, правка стиля, авторефакторинг, автоформатирование и т.п.) Если разных, за одно только предложение схлопнуть такие коммиты в один, если изменений больше чем на 20 строк — следует разжаловать в интерны с обязательным изучением всего IT с нуля, лучше на Algol-60 на бумаге. Исключение: если исходная ветка сохраняется и в схлопнутом коммите на неё есть ссылка — но всё равно смысл в таком появляется разве что в супертолстой базе.
Но есть способ облегчить жизнь товарищу.
Я честно не понимаю, при чём тут push -f из заголовка. Всё это можно было сделать и до первой публикации.
Если же речь про переделку после цикла замечаний — да, push -f для временных веток для pull requestʼов это нормально (хотя можно и сделать ветку типа my_request_v2).
Вернёмся наконец к git rebase --interactive.
Это сверхважный инструмент, но опять же к forced push имеет слабое отношение. И имело бы смысл создать статью именно по применению workflow с ним на основании цикла "выдал цепочку на ревью — получил замечания — переработал — goto 10".
Помните, что добросовестный ревьюер возможно клонирует ваш код к себе локально, чтобы иметь возможность смотреть на него через IDE и запускать тесты.
Если ведётся история в стиле reflog (а нормальные реализации ведут её и на сервере), то в течение некоторого времени (стандартно 90 дней) можно найти commit id от предыдущего push и вытащить по нему.
Ну а Gerrit, например, хранит такие вещи вечно (если явно не чистить).
А как же git push +?
А что с ним? Я не знал про такой синтаксис, и, честно говоря, сходу не разобрался.
К счастью, эту проблему разработчики git тоже предусмотрели, введя команду сборки мусора git gc --prune.
Надеюсь этого никого не научит использовать git gc --prune
, ведь даже после переписывания истории её можно восстановить через reflog – пару раз очень сильно выручало!
Про переписывание добавил бы мини-хак: можно добавлять изменения не только в последний коммит через -a
, но и любой старый через git commit -a --fixup=<hash>
. А вместе с этим можно добавить в глобальный .gitconfig
настройку autosquash = true
– теперь после запуска git rebase --interactive
коммиты сами будут в нужном порядке и с нужными «флагами»
Не вижу особых проблем с push -f
. Это такой же инструмент, как и другие тулзы гита, и у него есть своя область применения, причём далеко не сказочно-экстремальные ситуации вроде "случайно впушил в паблик кошелёк с сотней биткойнов".
Ситуация один (капитан-очевидность) — собственный непубличный клон. Клон где-нибудь на хецнере/амазоне/яндекс-диске/роутере, который прописан в remotes как ssh://...
. Кому вообще какое дело, что там лежит и как обновляется? Это как раз для коммитов вида feature_day1..feature_month5, чтоб потом их переформатировать и выкинуть.
Ситуация два: такая же ветка, но в публичном репозитории. Не для ревью, а просто как бэкап.
Ситуация три: mr/pr, созданный исключительно ради автоматизированных тестов CI. Накоммитал, напушил, дождался результатов (что все тесты прошлись) — и всё, на этом мавр сделал своё дело. Можно получившийся результат вообще не мержить, а напилить на коммиты прямо в мастер (если это допустимо).
Все ситуации имеют под собой основную идею: Я ХОЗЯИН этого репозитория/ветки/реквеста, и делаю, что хочу. Вы можете наблюдать за изменениями, но это всё ещё не public domain; код не обсуждается, предложения не принимаются. Дошло дело до ревью — вот с этого момента решения нужно принимать уже совместно с остальными участниками. И формировать какой-то совместный чеклист, типа "Вася собрался переписать последний коммит. Если кто его уже запуллил — по команде делаем git fetch, потом git reset --hard на новый remote/master"
Переписывание истории репозитория кода, или почему иногда можно git push -f