Дарья Меленцова
Разработчица в команде инфраструктуры Яндекса, действующий автор курса «DevOps для эксплуатации и разработки».
Прошлая статья «Работаем с Git: первые шаги в GitHub» была посвящена установке, настройке Git и классическим операциям из набора для новичков GitHub. А теперь перейдём к практике и рассмотрим «горячие» сценарии, которые делают трудовые будни куда веселее. Или не очень.
Что будет в этой статье
поговорим о моделях ветвления, подходах к созданию веток и работе с ними;
помёржим две ветки разными способами (
rebase
иmerge);
столкнёмся с конфликтами при мёрже и решим их;
научимся забирать определённый коммит из другой ветки (
cherry-pick
);поговорим, как схлопывать коммиты, чтобы история коммитов выглядела более красиво (
squash
);разберёмся, как откатывать изменения в случае необходимости (
reset
,revert
).
Начнём разминку с моделей ветвления — чётких сценариев, по которым должны работать команды. Как и техника безопасности, они прописаны болью IT-специалистов, поэтому стоит внимательно разобрать этот вопрос.
Модели ветвления
В реальной жизни Git должен хранить код с историей изменений, а также позволять любому количеству пользователей работать с этим кодом: изменять, клонировать и препарировать другими способами.
Чтобы пользователи могли продуктивно работать вместе и желательно ничего не ломать друг другу в коде, были созданы правила для работы в Git: как создавать ветки, как их вливать, в какой очерёдности и так далее.
Эти соглашения получили название «Модели ветвления». Рассмотрим тройку самых распространённых: Trunk-Based Development, Feature/Issue Branch Workflow и Gitflow.
На самом деле моделей намного больше, и вы можете настроить работу с Git под свой проект.
Trunk-Based Development
Trunk-Based Development — довольно удобная модель: разработчики трудятся над одной веткой (trunk
, main
или master
) и не создают отдельные, а клонируют мастер-ветку к себе на компьютер, вносят изменения и потом вливают («мёржат») их обратно в master
.
Обычно в течение одного рабочего дня происходит много мёржей в master
— это и плюс, и минус одновременно. Кстати, о плюсах и минусах.
За что любят Trunk-Based Development:
Быстрая обратная связь от коллег. Например, вы запушили в
master
, а потом пришёл тестировщик и сказал, что всё сломалось. Изменения были недавно, поэтому легко понять причину «выключения»master
и откатиться.«Своя ветка». Если все разработчики работают над одной веткой, в идеале они относятся к ней как к своему общему коду и следят, чтобы она была в рабочем состоянии перед мёржем.
Разбивка на модули. Пуш всегда происходит в
master
, и большие задачи разбиваются на много задач. Такое деление кода на модули делает работу более комфортной.Нет сложных мёржей. Бывают ситуации, когда вы работаете над веткой две недели, пытаетесь мёржить в
master
и получаете миллион конфликтов. В случае с Trunk-Based Development если и будут конфликты, то небольшие и легко решаемые.
За что ругают Trunk-Based Development:
Раз — и готово. Можно всё сломать одним коммитом.
Сложный
revert
. В день делают много коммитов, и если проблему заметили только на двадцатом, от HEAD будет не самый простойrevert
.Регулярный пулл. В процессе работы нужно постоянно подтягивать к себе изменения из
master
, чтобы избежать конфликтов мёржа.
Feature/Issue Branch Workflow
Feature/Issue Branch Workflow — логичное развитие модели Trunk-Based Development.
Обычно под каждую фичу или задачу создаётся своя ветка, в которой ведётся разработка, и по окончании ветка вливается в master
.
За что любят Feature/Issue Branch Workflow:
Независимость. Работаете в своей ветке и ни от кого не ждёте сюрпризов, ничего не надо пуллить из
master
.Всегда рабочий
master
. Изменения, которые пуллятся вmaster
, обычно проходят тесты, и мастер всегда горит «зелёным».
За что ругают Feature/Issue Branch Workflow:
Впереди паровоза. Если коллега решил вмёржить что-то в
master
до того, как делаете мёрж вы, нужно забрать себеmaster
и решить все возникающие конфликты, проблемы со сломанными тестами и только потом вливать ветку вmaster
.Переполнение. Количество веток может быстро расти, что требует большего код-ревью и тестирования для стабильности и целостности мастер-ветки.
Gitflow
Gitflow — чуть ли не самая популярная модель ветвления.
Основная идея Gitflow в том, что в проекте используют две основные ветки: master
и develop
. В ветке master хранится стабильная версия программного продукта, которая готова для выпуска в production (прод). В ветке develop
хранится актуальная версия кода с последними изменениями, которые ещё не были выпущены в прод.
Как работает Gitflow: в начале спринта создают ветку develop
, и все трудятся в ней. От ветки develop
разработчики отводят другие ветки под конкретные задачи — по сути Feature/Issue Branch Workflow, только от develop
, а не master
. Разработчики кодят в своих ветках, а по завершении работы вливают изменения в develop
. В конце спринта от ветки develop
создаётся release-ветка, на которой прогоняются уже более серьёзные тесты, приближенные к продовой среде.
Hotfix-ветки вливаются в
release
, при этом новые feature-ветки в релиз уже не попадают.
Когда всё починили, влили фичи и готовы выпускать релиз, ветка release
вливается в master
и обратно в develop
, чтобы дальнейшее исправление багов и работа велись от актуальной версии кода.
За что любят Gitflow:
Большие команды. Модель хорошо подходит для работы больших, распределённых команд.
«Выбор джуна». Gitflow эффективна при работе с junior-разработчиками, которым свойственно большое количество итераций до отправки кода в релиз.
За что ругают Gitflow:
Скорость. Модель медленная, поэтому получение MVP, коммуникация сотрудников и организация процессов будут происходить неэффективно.
Теперь вы знаете, что грамотные команды работают в GitHub по правилам, а не желанию «сделать мёрж в мастер перед выходными». Самое время посмотреть, как модели ведут себя в реальных условиях и помогают (или нет) разбираться с проблемами разработки.
Merge и rebase
Представим, что мы отвели свою ветку от main
. Вносим в неё изменения, коммитим и уже хотим сделать мёрж в main
, как… другие разработчики вносят свои изменения раньше нас.
Забрать последнее main-состояние в свою ветку можно с помощью команды git merge
или git rebase
:
git merge
— помёржит изменения из другой ветки, создав отдельный merge-коммит.git rebase
— заново наложит наши коммиты поверх той ветки, которую подтягиваем в свою.
Разберём различие между merge и rebase на примере.
У нас есть ветки main и merge и вот такой лог.
$ git log --all --graph --oneline:
Видно, что ветка merge
отведена от main
, дальше разработка разделилась, появились новые коммиты как в main
, так и в merge
.
Мы хотим забрать изменения из main
в ветку merge
.
Merge
Переключаемся на ветку merge:
$ git checkout merge
Скачиваем изменения с удалённого сервера в ветку main
(чтобы под рукой была локальная и актуальная версия ветки):
$ git fetch origin main ✔ 4 ⚙ 6421 11:08:28
From github.com:ifireice/git
* branch main -> FETCH_HEAD
Мёржим изменения из main
в текущую ветку.
$ git merge main
Merge branch 'main' into merge
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
Merge made by the 'recursive' strategy.
README.md | 9 ---------
1 file changed, 9 deletions(-)
Смотрим лог $ git log --all --graph --oneline
:
Видим по истории, что две ветки смёржены в merge
через дополнительный merge-коммит, который их объединил.
Rebase
Подтягиваем актуальное состояние main
:
$ git fetch origin main ✔ 4 ⚙ 6421 11:08:28
From github.com:ifireice/git
* branch main -> FETCH_HEAD
Делаем rebase
:
$ git rebase main
Successfully rebased and updated refs/heads/merge.
Смотрим лог $ git log --all --graph --oneline
:
Видим, что наши изменения были применены поверх ветки main
заново — будто мы отвели ветку merge
от main
только что.
Отдельного merge-коммита нет.
Стоит помнить, что git rebase
переписывает историю коммитов и придётся делать git push force
. Поэтому не нужно использовать rebase
на ветках, с которыми работают несколько разработчиков.
Подробнее про то, чем может быть опасен rebase
, расскажем ниже.
Разрешение конфликтов
При мёрже может быть две ситуации.
Если наши изменения касаются разных частей проекта, то ничего страшного.
Но может быть так, что мы и другие разработчики затрагивали одну и ту же часть проекта и наши изменения будут конфликтовать. В таком случае GitHub сообщит о конфликте:
Рассмотрим более подробно, что такое конфликт.
Классический пример конфликта
Разработчик A, выполняя задание из первой части статьи, поправил опечатку:
В это же самое время разработчик B в отдельной ветке внёс другое изменение и удалил строку с опечаткой:
При попытке влить эти изменения в main
возникнет конфликт, который Git не может разрешить сам, — непонятно, что должно быть в результате. Этим должен заняться человек.
С помощью команды $ git log --all --graph
мы можем посмотреть дерево коммитов:
Ищем свои коммиты
Чтобы поправить этот конфликт, нам нужно помёржить ветку main
в нашу ветку feature-b
, вручную поправить конфликт и обновить пулл-реквест.
Итак, мы склонировали локально репозиторий, и наша активная ветка — feature-b
.
Выполним $ git checkout feature-b
.
Если вы задаётесь вопросами, сделайте паузу и прочтите первую часть статьи. Иначе вопросов станет только больше, а ответов — нет.
Мёржим изменения из ветки main
с помощью $ git merge main
:
$ git merge main
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
Git говорит, что просто так помёржить не получится и нужно вручную разрешить конфликт. Посмотрим, в чём у нас проблема:
$ git status
On branch feature-b
Your branch is up to date with 'origin/feature-b'.`
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)`
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: [README.md](http://readme.md/)`
no changes added to commit (use "git add" and/or "git commit -a")`
Видно, что в обеих ветках есть изменение в файле README.md
. Откроем его любым текстовым редактором и найдём специальную метку о конфликте:
Так конфликт выглядит в редакторе VS Code со специальной подсветкой, но можно пользоваться любым редактором.
Сейчас мы должны привести файл к итоговой версии и сохранить. Пусть у нас итоговый текст выглядит так:
Добавим файл в индекс и закоммитим изменения:
Git автоматически предложит сообщение о коммите, его можно не менять. Сохраняем и выходим:
$ git commit
[feature-b 151a787] Merge branch 'main' into feature-b
Посмотрим на результат с помощью команды $ git log --all --graph
:
Обратите внимание, что появился специальный merge-коммит, который сливает две ветки. В нём как раз и содержится разрешение конфликта, которое мы сделали вручную.
Теперь осталось обновить наш pull-реквест. Для этого надо отправить на сервер изменения нашей ветки. Выполним: $ git push
.
И… конфликтов больше нет, можем помёржить pull-реквест.
Squash — уборка коммитов
Squash коммитов помогает упростить и почистить историю изменений ветки и уменьшить общее количество коммитов в репозитории. Это делает историю изменений более читабельной и помогает лучше понять, какие изменения были внесены в проект на определённом этапе разработки.
Представим, что мы работали над одной задачей несколько дней и каждый день коммитили небольшой кусочек изменений. В итоге видим в истории несколько коммитов для одной задачи:
Хотим объединить коммиты 1, 2 и 3 в один-единственный.
Для этого выполним $ git rebase -i HEAD~3
:
git rebase -i
— это интерактивная команда Git, которая позволяет изменять порядок и применять изменения коммитов ветки в интерактивном режиме.
Помните, что использование команды
git rebase -i
может привести к потере данных. Нужно точно понимать, что вы хотите сделать. Поэтому всегда создавайте резервную копию текущей ветки перед использованием этой команды.
HEAD
— ссылка на последний коммит.~n
— от последнего коммита взять n коммитов.HEAD~3
— потому что работать будем с тремя последними коммитами.
Открывается редактор. Видим, что захватили три последних коммита, которые и склеим:
git rebase -i
— мощный инструмент (посмотрите, сколько опций), но сейчас нас интересует только squash.
Склеиваем коммиты:
Отменяем два последних коммита при помощи squash
и s
.
Оба коммита будут склеены с тем, который помечен pick
.
Сохраняем изменения и выходим.
git rebase -i
откроет следующее окно:
Можно отредактировать сообщение, сохранить изменения и выйти из редактора.
Смотрим на $ git log --graph
:
Теперь у нас вместо трёх коммитов один. Как мы и хотели.
В двух словах разберём, как действовал squash
:
Выполнили команду
$ git rebase -i <BASE>
, где<BASE>
— это точка начала ветки, с которой начинаем переписывать историю коммитов.Открылся текстовый файл, содержащий список всех коммитов в ветке.
Поработали с коммитами: изменили их порядок, объединили несколько коммитов в один или удалили ненужные коммиты.
После необходимых изменений сохранили файл и закрыли его.
Git выполнил перезапись истории коммитов в соответствии с изменениями.
Ещё одно применение squash — схлопывание коммитов при мёрже
squash
полезен, когда вы мёржите ветку в main
. В отдельной ветке вы можете вести разработку как угодно, но для сохранения более понятной и чистой истории основной ветки при мёрже ветки можно схлопнуть все коммиты в один.
Как обычно, представим, что разработку вели в ветке feature-c
и сделали два коммита:
Теперь хотим мёржить ветку feature-c
в main
. Для этого переключимся на ветку main
и выполним $ git merge --squash target_branch_name
:
$ git merge --squash origin/feature-c
Updating bb0b109..e7989c7
Fast-forward
Squash commit -- not updating HEAD
README.md | 7 +++++++
1 file changed, 7 insertions(+)
Сохраним изменения с помощью $ git commit
:
И смотрим историю $ git log --graph
:
Видим, что помёржили нашу ветку feature-c
с main
благодаря --squash
.
Сжатие коммитов в GitHub
GitHub позволяет вам сжимать коммиты при мёрже пулл-реквеста (если вдруг забыли сделать это перед созданием PR).
Здесь достаточно посмотреть на скриншот:
Сначала выполнится squash
, а потом merge
.
И в истории коммитов вместо двух будет всего один.
Cherry-pick — выборочный мёрж
git cherry-pick
— команда Git, которая переносит коммит(ы) из одной ветки в другую.
Она берёт изменения, которые были сделаны в указанном коммите, и накладывает на текущую ветку.
Предположим, в нашей команде процесс разработки построен так: существует ветка stable
со стабильной версией продукта, которая сейчас работает в проде, и ветка main
, где находится самая свежая версия продукта.
Внезапно в продакшен-версии продукта нашли баг. Разработчик исправил баг в main
, но баг оказался настолько критичным, что и на проде его тоже нужно чинить.
Если мы просто выкатим ветку main в прод, туда попадёт ещё и много новых изменений, которые сделали другие разработчики в main
.
К слову, срочно и в спешке выкатывать новую версию продукта — не самая лучшая идея (можно выкатить ещё несколько новых багов).
Поэтому нужно взять коммит с исправлением и помёржить в ветку stable
только его (и потом уже выкатить ветку stable
в продакшен).
Посмотрим на $ git log --all --graph
:
Если мы просто помёржим main
в stable
, то и коммит с фичей, и коммит с лекарством попадут в прод. А нам нужен только коммит с лекарством.
Чтобы сделать «мёрж по выбору», у Git есть команда cherry-pick (можно по-русски, вас сразу поймут).
Склонируем репозиторий.
В
$ git log
найдём идентификатор коммита, который надо помёржить. В нашем случае это4215d16f17f52e5279f84df6b89dd3d7b423cac4
.Переключимся в ветку
stable
:$ git checkout stable
.Черри-пикнем наш коммит:
$ git cherry-pick 4215d16f17f52e5279f84df6b89dd3d7b423cac4
Auto-merging README.md
[stable 2e07d5a] fix bug
Date: Wed Mar 29 09:33:51 2023 +0300
1 file changed, 2 deletions(-)
Что именно сделает Git: возьмёт изменения, которые были сделаны в коммите 4215d16f17f52e5279f84df6b89dd3d7b423cac4
, и наложит их на самый верхний коммит в ветке stable
. Поэтому если мы посмотрим $ git log --all --graph
, то наш коммит будет выглядеть как новый, независимый коммит (а не как коммит-мёрж).
Чтобы изменения оказались на сервере, не забудьте сделать $ git push
Revert и reset
Предположим, что в основную ветку попали изменения, которые принесли баг, и нужно откатить изменения с багом.
git revert и git reset — это две команды для отмены изменений в Git.
Однако их действия и последствия различаются.
Если кратко, то различие между git revert
и git reset
в том, что git reset
переносит вас на определённую точку в истории коммитов, a git revert создаёт новый коммит с отменой изменений.
Revert
$ git revert
используется для добавления нового коммита, который отменяет изменения, сделанные в другом коммите. В отличие от git reset
, не изменяет историю коммитов.
Прописываем git log --oneline
:
Сделаем revert двух коммитов — 4215d16
и 3ce8c50
:
$ git revert 4215d16 3ce8c50.
Git попросит ввести коммит-месседжи для каждого коммита:
$ git revert 4215d16 3ce8c50
[revert 98a0bfc] Revert "fix bug"
1 file changed, 2 insertions(+)
[revert 7b330be] Revert "update ReadMe.md"
1 file changed, 1 deletion(-)
Смотрим $ git log --oneline
:
Видим, что у нас появилось два новых коммита, которые откатывают изменения заданных коммитов.
Чтобы не засорять историю и при необходимости быстро посмотреть, что изменилось, revert
можно сделать в один коммит с помощью ключа -n
. Нужно не забыть потом закоммитить изменения:
$ git revert -n 4215d16 3ce8c50
$ git commit
Revert "update ReadMe.md"
This reverts commit 3ce8c505f9651d548454c8856fdfee86e92a123f.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch revert
# You are currently reverting commit 3ce8c50.
#
# Changes to be committed:
# modified: README.md
#
$ git log --oneline
8b6752c (HEAD -> revert) Revert "update ReadMe.md"
4215d16 (main) fix bug
3ce8c50 update ReadMe.md
80dcb32 Merge pull request #13 from ifireice/feature-b
338d0e9 (origin/feature-b) Merge branch 'main' into feature-b
1e2c3a0 fix typo
2db25fc drop line
9e8b1e5 Merge pull request #1 from ifireiceya/fix-misprint
d2fa945 Поправили опечатку
0f75a77 Init
cdb80a7 Initial commit
При revert
также работает запись HEAD~<число коммитов>
.
Если нужно удалить несколько коммитов, то ещё можно использовать вот такую запись:
$ git revert -n HEAD~5..HEAD~2
(первый коммит..последний коммит).
Reset
$ git reset
используется для отмены изменений, применённых в коммите, и возвращения к предыдущему состоянию. По сути, перемещает HEAD
на заданный коммит.
У команды есть различные опции, которые влияют на её поведение:
--soft
— изменения не удаляются, а только помещаются в рабочий каталог. C помощью этой опции вы можете отменить коммит и оставить изменения в рабочем каталоге.--mixed
, в отличие от --soft, удаляет коммит и возвращает изменения в индекс. То есть нужно будет выполнить команду git add перед следующим коммитом. Используется по умолчанию, если не передать опцию revert.--hard
— крайний вариант. Он удаляет не только коммит, но и все изменения, внесённые в историю коммитов до него. Осторожно, восстановить данные после применения этой команды нельзя.
Что может быть не так с reset
Отведём отдельную ветку reset и поработаем в ней с помощью $ git checkout -b reset
. Внесём какие-то изменения и запушим. Получим вот такой $ git log --oneline
:
Мы хотим отменить два последних коммита: c314848
и 3391dc8
.
Для этого выполняем $ git reset dddcea7
.
Смотрим ещё раз $ git log --oneline
:
Коммиты пропали, вроде всё ок.
Попробуем запушить изменения.
$ git push
To github.com:ifireice/git.git
! [rejected] reset -> reset (non-fast-forward)
error: failed to push some refs to 'github.com:ifireice/git.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Git просто так не даёт запушить и намекает на проблемы. Что может быть не так?
Допустим, наш коллега работает с той же веткой, что и мы.
Для примера склонируем наш репозиторий в другую папку и переключимся на ветку reset
.
Коллега внёс в неё изменения и пушит их:
$ git commit -am "added changes from a colleague"
[main 50bd1e1] added changes from a colleague
1 file changed, 1 insertion(+)
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 365 bytes | 365.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:ifireice/git.git
dddcea7..50bd1e1 main -> main
Посмотрим, что у нас в истории на удалённом сервере:
Видим в истории все три коммита (и наши, и коллеги).
Теперь мы пушим наши изменения с force
:
$ git push -f
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:ifireice/git.git
+ f27c52e...dddcea7 reset -> reset (forced update)
Смотрим историю:
Пропали все ненужные нам коммиты + пропал коммит коллеги.
А у коллеги — всё хорошо:
$ git status
On branch reset
Your branch is up to date with 'origin/reset'.
nothing to commit, working tree clean
Он делает ещё один коммит и пушит изменения без force
, так как для Git всё в порядке:
$ git commit -am "delete typo on ReadMe"
[reset 455e520] delete typo on ReadMe
1 file changed, 3 deletions(-)
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 314 bytes | 314.00 KiB/s, done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:ifireice/git.git
f27c52e..455e520 reset -> reset
Смотрим историю:
Те коммиты, которые мы откатывали, вернулись + появились коммиты коллеги.
Как от такого защититься? В GitHub есть возможность установить «правила защиты» на ветку. Заходим в репозиторий → Settings → Branches → Add branch protection rules:
Вводим имя ветки reset
(но чаще так «защищают» основную ветку) в поле Branch name pattern, пролистываем вниз и выбираем настройку Allow force pushes. Указываем пользователей, которым разрешено делать force push
(если не выберете пользователя, после сохранения проставится значение в Everyone):
Создаём правило и проверяем, что теперь пушить с force
нельзя:
$ git push -f
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
remote: error: GH006: Protected branch update failed for refs/heads/reset.
remote: error: Cannot force-push to this branch
To github.com:ifireice/git.git
! [remote rejected] reset -> reset (protected branch hook declined)
error: failed to push some refs to 'github.com:ifireice/git.git
На этом будний день с Git подошёл к концу. Надеемся, он был продуктивным для вас!
Итоги
Спасибо всем, кто дочитал статью и проникся регулярной работой, которую проводят специалисты с Git. Вот краткий список достижений:
познакомились с самыми популярными моделями ветвления;
разобрали и решили конфликты при мёрже;
освоили «черри-пикинг» — забрали коммит из другой ветки;
благодаря squash попробовали схлопнуть коммиты, чтобы история коммитов выглядела более красиво;
научились откатывать изменения в случае необходимости, используя reset и revert.
Комментарии (14)
joffer
14.04.2023 15:43-5при всём уважении к git цена ошибки в нём в большой команде настолько дорога, что, как правило, гит используется в самом примитивном своём виде - add ., push, pull, checkout, merge.
Про rebase - за последние 5 лет работал в 3х компаниях и ни разу не видел применения этой команды, rebase - мифическая команда, про которую спрашивают на собеседованиях и никогда не применяют. Возможность сделать из последних веток кашу - высочайшая
Про "более красивой истории в логах" - ни разу не видел примерения squash, более того - узнал, что оно делает из этой статьи. Имхо, возня со сквошем не стоит свеч - куда ценнее сохранить атомарные коммиты и пояснения к ним, чем случайно слить лишнее и потом разбираться, что в комментариях к чему относится
Пример с reset, к сожалению, не понял - так что там происходит-то? Почему мы там сделали push -f, почему не помогло и как надо правильно в таких случаях делать?
CaptainFlint
14.04.2023 15:43+2Пример с reset, к сожалению, не понял — так что там происходит-то? Почему мы там сделали push -f, почему не помогло и как надо правильно в таких случаях делать?
Пусть у нас была цепочка a->b->c->d. Мы сделали reset на коммит B и запушили с форсом, теперь у нас и на сервере стала история: a->b. Но другой сотрудник раньше успел склонировать себе репозиторий со всей историей, и, разумеется, у него на компе ничего не удалится, а так и останется a->b->c->d. Вот поверх них этот сотрудник и продолжал свою разработку. И когда он вызвал пуш из своего локального репозитория с историей a->b->c->d->e->f, сервер просто увидел, что к имеющимся у него a->b добавляются четыре коммита: c, d, e, f. Он их и принял, ибо почему бы ему их не принять. И так удалённые нами коммиты c и d вернулись обратно.
Вот если бы мы после резета добавили ещё один коммит и вышло бы a->b->x, тогда ветки разъехались, и у коллеги запушить уже так просто не получится. Сервер обнаружит конфликт, который надо будет как-то решать (мёрджить или ребейзить).
Как правильно делать — универсального ответа нет. Вообще, конечно, форс-пуши считаются злом. Если в них возникает необходимость, да ещё и не в личную ветку, а в ту, где работает несколько человек, то это какая-то исключительная ситуация, которую надо разруливать индивидуально. Например, уведомить всех, кто работает с веткой, что у нас тут форс-
мажорпуш, и всем обязательно надо сделать fetch и адаптировать свои локальные репы.joffer
14.04.2023 15:43-1понятно, спасибо.
по суди, и, в частности, практика и опыт работы показывают, что если работаешь с веткой и делаешь reset или revert, то лучше сразу по выполнении (а ещё лучше до) предупредить всю команду, чтобы сразу после операции отмены коммиты в ветке пошли дальше как надо
CaptainFlint
14.04.2023 15:43+1revert не требует никаких предупреждений, так как создаёт самый обычный коммит, просто содержащий изменения, обратные тем, что были в каком-то другом коммите. Он как раз удобен в тех случаях, когда надо откатить изменения в кодовой базе, но резет и ребейз с форс-пушем невозможны по тем или иным причинам. То же самое можно проделать и руками: вывести дифф "плохого" коммита в реверснутом виде, переналожить этот "откатывающий" патч поверх текущей кодовой базы, добавить, закоммитить. revert просто автоматизирует эти шаги.
Andrey_Solomatin
14.04.2023 15:43+1Гит это система построенная на пайплайнах, то есть чтобы получить результат нужно сделать несколько действий. Пока вы не планируете сделать push --force не надо никого предупреждать. push --force это опасная команда для общей работы, всё остальное вы делаете со своей локальной копией.
slonopotamus
14.04.2023 15:43+3цена ошибки в нём в большой команде настолько дорога
Эмм... Чо? Благодаря наличию reflog, в гите гораздо сложнее потерять изменения по сравнению с другими vcs.
rebase - мифическая команда, про которую спрашивают на собеседованиях и никогда не применяют
Используем по несколько раз в день.
Звучит так что вы просто не разобрались как использовать инструмент.
ни разу не видел примерения squash
Вам видос записать?
joffer
14.04.2023 15:43-3Очень много раз вливали и мерджили не то и не туда, рядовая ситуация, когда полдня теряется на разгребание конфликтов и генерацию таких веток фич, которые бы слились в гармоничный пред-релиз. Особенно в коммит-неделю, когда у 3 - 5 команд окончания спринтов и наработки вливаются на предрелиз перед blue-green, где-то в суматохе путаются ветки и потом понеслось разгребание, восстановление, перетаскивание HEAD "туда, где работало", "все спультесь" в чат "деплой" и т д. Даже спорить не буду, что это может указывать на плохое знания гита - просто это вот то, как оно было и по-другому нигде не видел;
2) про rebase - возможно, я же ведь не спорю или говорю, что рэбейз плох - просто по каким-то причинам где бы я не работал, везде был гит, но нигде не делали ребейза;
3) про сквош - зачем же видео? в статье вроде объясняется, в чём суть. Просто опять же не видел применения этой команды в процессе разработки + есть некоторые сомнения в необходимости этой выполнения этой команды, имхо атомарный коммит + комментарий лучше
Andrey_Solomatin
14.04.2023 15:43Очень много раз вливали и мерджили не то и не туда
Я использую Trunk-Based Development, там практически нет понятия замерджить не туда. Все разработчики мерджат в одну ветку 99% времени.
Есть процедура для хотфиксов, там можно накосячить, он там обычно кто-то из опытных присутствует.
Ну и мердж-конфликты ты решаешь между мастером и своими изменениями. Уже и не помню, когда я решал конфликты между чужим кодом и чужим кодом.
В общем процесс у вас сложный. Возможно обоснованно, возможно нет.
Dolios
14.04.2023 15:43+2Просто у вас бардак, судя по описанию. Как можно вмержить что-то "не туда"?
Rebase и squash использую постоянно, ни туда ни разу не мержил.Ситуация. Код ревью джуна проходит со 2-3 раза. Вы, по итогу, будете лить в мастер 2-3 отдельных коммита без сквоша?
LedIndicator
14.04.2023 15:43+2rebase — мифическая команда, про которую спрашивают на собеседованиях и никогда не применяют.
Да ладно.
В случае вот этого Feature/Issue Branch Workflow накатить себе последние изменения в мастере перед пушем фича-ветки при помощи rebase — вообще святое. Я, правда, это в Intellij делаю, не руками, но какая разница.Про "более красивой истории в логах" — ни разу не видел примерения squash
Это по желанию. Проще, когда работаешь в фича-ветке, амендить уже существующий коммит. Но если забылся, увлёкся и наплодил миллион коммитов, то почему бы и нет.
А сохранять промежуточные коммиты, когда ты забыл тест написать, потом pmd поправил, потом чекстайл починил, смысла нет никакого.
Andrey_Solomatin
14.04.2023 15:43+3Если все используют один репозиторий для синхронизаци например на GitHub или GitLab, то там делается защита для общих веток, и проблем с переписыванием истории нет.
То есть в своих ветках можно делать что угодно, а в общих будет порядок.
В интрефейсе GitHub есть и rebase и squash. Не надо возиться с этим самому, красивую историю можно получить забесплатно.
Andrey_Solomatin
14.04.2023 15:43Gitflow — чуть ли не самая популярная модель ветвления.
Я тоже как-то участвовал в проекте где эту модель выбрали для разработки. Просто за красивое имя. На практики никто даже не пытался и сделали по нормальному.
Про популярность это мнение расходится вот с этими ребятами.
https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
А то, что в статье описано очень похоже на GitHubFlow, его мы как раз и использовали.
CaptainFlint
Для разрешения конфликтов очень полезна опция
merge.conflictstyle=diff3
. Если её задать, то в конфликтных блоках вдобавок в финальным версиям из сливаемых веток будет также присутствовать версия из их общего предка. Это позволит напрямую увидеть, что конкретно изменялось в первой ветке, а что — во второй. Одних лишь финальных версий часто бывает недостаточно, чтобы понять, а как же правильно смёрджить.