Как стать автором
Обновить

Комментарии 31

Одно из первых наставлений, которое молодой падаван получает вместе с доступом к git-репозиториям, звучит так: «никогда не ешь жёлтый снег делай git push -f».


Я думаю, на этом месте можно заканчивать читать.
1. У вас отвратительный процесс онбординга.
2. У вас отсутствует документированный девелопмент-процесс
3. Вы в штыки воспринимаете опыт, отличающийся от вашего.

У нас в команде примерно каждый пользуется форс-пушем и rebase --interactive по многу раз за день. И тренинги про гит и наше воркфлоу — одни из первых строк в онбординг-плане.
примерно каждый пользуется форс-пушем и rebase --interactive по многу раз за день

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

Заминусовали, видимо, те, кто подумали, что мы в мастер форс-пушим постоянно что ли? Мастер де-факт protected и туда абсолютно никто не может зафорспушить даже если захочется.

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


Не принуждает, это просто невероятно удобно =)

Если вкратце, то исторически сначала команда работала в Геррите, где ревью 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?

После push, естественно, CI на данный PR прогоняется ещё раз.

При rebase коммиты не просто переносятся, изменения из коммитов применяются (replay) к коду в новом месте. При такой формулировке легче понять, что и почему происходит.
По памяти как минимум Sublime Merge умеет делать патч-коммиты. Вроде ещё каким-то клиенты были, но сейчас не вспомню.

Спасибо, хорошее уточнение. А Sublime merge умеет их делать на уровне строк? Потому что например Atlassian SourceTree умеет, но на уровне hunks, т.е. таких законченных кусков изменений.

PHPStorm тоже умеет коммитить отдельные куски изменений.
В TortoiseGit тоже можно делать частичные коммиты, в том числе и на уровне строк, но там каждый раз нужны небольшие танцы с бубном (Restore after commit / Mark / Leave).

PHPStorm только сплошные куски может, вроде как. Не может 3 строки из 5 подряд идущих в диффе и уж точно не может редактировать то, что коммитится без редактирования рабочей копии. По крайней мере года два назад такое было

Ну да, но там это и не особо проблема. Отредактировал, закоммитил, нажал Ctrl+Z, еще раз закоммитил.

Есть git-gui (пакет именно так и называется). Им можно стейжить как целые куски (hunk), так и построчно. Но это если отдельным инструментом. А вообще всё достаточно технично делается в JetBrains (вроде во всех их IDE оно одинаково) — если нужно распилить какой-то коммит — делаем интерактивный ребейс, начиная с предыдущего перед ним коммита (правый клик — interactively rebase from here). Ставим на нём точку edit, запускаем rebase. Потом кликаем в целевой коммит (который надо распилить) и выбираем compare with local. А дальше уже как фантазия пойдёт. Можно кусками, можно строчками, можно хоть отдельными символами. Можно вообще всё переписать (например, если в конечном результате "А" стало "Б", то в промежуточном можно "А" заменить на "Г", а следующим шагом уже "Г" заменить на конечное "Б".). Несомненный плюс такого подхода — каждое изменение можно протестировать отдельно. Мы имеем "предыдущее" состояние и просто накатываем на него "текущее" отдельными кусками. Это не "полукоммит", когда все изменения на самом деле уже в коде, а мы просто выбираем, что в этот коммит пойдёт половина строк, а в следующий — вторая половина, а именно полноценный коммит. Если мы решили взять в него ровно половину изменений — мы можем прогнать тесты и убедиться в работоспособности. И потом просто продолжить ребейс. Вторая половина изменений подтянется сама; если в промежуточном коммите были какие-то нетривиальные изменения (больше, чем просто выбор отдельных чанков или строчек) — надо будет по горячим следам поправить эти конфликты. И всё.

… и это очень удобно! После ребейза некоторые коммиты могут вообще схлопнуться из сотни строк в пару чанков, а то и вообще исчезнуть (хотя последнее часто вводит в ступор IDE (например, от JetBrains), которые пытаются контролировать процесс и вдруг не могут понять, куда внезапно исчез коммит).


Например, в ветке сделали переименование какой-нибудь сущности, вкоммитали. В это время в мастере провели ту же операцию. Последующий ребейс при применении изменений обнаружит, что коммит "уже применён" и просто его отбросит.

Неплохая и информативная статья, спасибо! Но вот к декомпозиции задач нужно относиться более ответственно: видимо, ваш скрам-мастер просто не справляется со своей работой, и software planning проходит не так, как должно. Практически любую задачу можно разбить на «атомарные» подзадачи, при этом «убиваются» несколько «зайцев» сразу: небольшие, разумные коммиты, которые легко ревьюировать (или как правильно по-русски сказать, контролировать?), достигается хороший sprint velocity, легче планировать свое время, всегда можно заняться тем, к чему лежит душа в данный момент, начинающим же разработчикам так вообще «лафа», все разложено по полочкам, просто и понятно (потому им нужна помощь более опытных товарищей в декомпозиции и планировании спринта). Знающий scrum master и опытный software architect — и вам практически никогда не придется пользоваться git push --force.

Может вы не знали, но не везде скрам. А там, где скрам — команда решает как задачи бить, а не скрам-мастер

Декомпозиция сложных задач на атомарные — это не какая-то исключительная особенность скрама, а очень хорошая практика профессиональной разработки программного обеспечения. Вне зависимости, какой методологией вы пользуетесь, небольшие содержательные коммиты, которые легко проверить, это благо, и это отнюдь не мое личное мнение.
И я написал, что скрам-мастер (если это скрам) должен добиться, чтобы 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, например, хранит такие вещи вечно (если явно не чистить).

А что с ним? Я не знал про такой синтаксис, и, честно говоря, сходу не разобрался.

Аналог -f, и чуть более аккуратный по последствиям (-f без указания конкретного ref может обновить все ветки, а +master:master, например, только одну). Для тематики данной статьи разница несущественна, но для безопасности работы вариант через плюс чуть лучше.

К счастью, эту проблему разработчики 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"

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории