Про простой workflow в git

Что: be0132b6d3f1508ca0b33b9499bb6b9535615d3f

Когда: 2020-05-23 14:46:24+03:00

Темы: git

Про простой workflow в git

>Какой workflow нужно использовать, если я хочу просто отслеживать свежие
>изменения в remote репозитории >и чтобы при этом держать свои локальные патчи
>(отдавать их наверх не собираюсь).

Одному человеку вот написал моё видение workflow для этого случая:

------------------------ >8 ------------------------

>Вот склонировал я репозиторий, периодически делаю "git pull" - всё хорошо. А
>как комиттить свои локальные патчи? В интернете пишут - в master ничего не
>коммитить (WTF, это моя копия master). А тогда как?

Workflow в git-е тьму всяких используют и единого или рекомендуемого
нет. Но это и так очевидно. Очень сильно разнятся советы по
использованию git-а в зависимости от человека который его будет
использовать: опытный или нет например. В одной компании где я работал,
git cherry-pick или git rebase -i -- были табу, запрещены к
использованию в одном отделе. Потому что в нём работали не очень опытные
с git-ом разработчики, а git штука мощная и позволяет коверкать и изменять
историю множеством способов и эти разработчики её коверкали так, что фиг
потом разберёшься что как и откуда шло/росло и как посмотреть разницу и
историю между рядом правок. В умелых же руках все эти rebase,
cherry-pick -- к месту творят хорошие вещи. Я не пользовался Mercurial,
но знакомые с ним и с git, говорят, что в Mercurial сложнее выстрелить
себе в ногу, но он и менее гибок/мощен -- git же наоборот.

>git-rebase - Reapply commits on top of another base tip
>
>Не, я точно не хочу такую хрень. Где-то в гите перемудрили. По-твоему это
>понятно звучит? [...]

Дока в нём не везде и не всегда понятна -- это точно. Но в ней
(git-rebase) вот например есть такая картинка:

       Assume the following history exists and the current branch is "topic":

                     A---B---C topic
                    /
               D---E---F---G master

       From this point, the result of either of the following commands:

           git rebase master
           git rebase master topic

       would be:

                             A'--B'--C' topic
                            /
               D---E---F---G master

которая, по моему, очень понятно объясняет суть rebase.

>А нельзя просто мержить из мастера в свою ветку, без rebase и прочих
>модификаций истории?

1) Ваш вариант: вы постоянно находитесь на ветке master. Сделали в ней
свой коммит. А дальше, если сделаете git pull, то, так как верхушка
вашей ветки не совпадает с верхушкой origin/master ветки, потому что
появился ваш коммит, то git не может просто переставить указатель вашего
master на коммит на который указывает origin/master (это называется
fast forward). Git вынужден будет сделать "искусственный" (чисто
технически это обычный коммит конечно же, просто он неявно создан не
человеком) merge-коммит в котором будут два родителя: origin/master
коммит и ваш коммит.

git pull это на самом деле синоним к:

    $ git fetch
    $ git merge origin/master

предполагая что текущая master ветка прописана в .git/config что связана
с origin/master и предполагая что fetch автоматически подставляет
"origin" remote. По умолчанию это всё так. Так вот команда merge, по
умолчанию, делает fast-forward (если может), в противном случае
merge-commit. Например ваш master указывает на commit2, а origin/master
(который обновляется после git fetch) на commit 3

    commit2 <- master
    commit1

    commit3 <- origin/master
    commit2
    commit1

Находясь в master ветке, делая git merge origin/master, git просто
передвинет/обновит master указатель на commit3, сравнявшись с
origin/master. Если же в вашем master есть ваш коммит, то fast-forward
невозможен и будет создан промежуточный искусственный коммит:

    MYcommit <- master
    commit2
    commit1

станет после merge origin/master:

    "Merge branch origin/master"
        |
        +-------+
        |       |
        |     commit3
    MYcommit    |
        |     commit2
    commit2     |
        |
    commit1

в рандомно взятом репозитории git log покажет это так:

    * 903bdfe Update vendor 42wim/mm-go-irckit
    * 50a0ba8 Bump version
    *   f8394da Merge branch 'master' of github.com:42wim/matterircd
    |\
    | * ad4a8f0 readme: Add a "Guides" section and one guide (#194)
    * | f7cf55c (tag: v0.18.2) Release v0.18.2
    |/
    * a0ab000 Update vendor nlopes/slack
    * 755960e Bump version
    * 1886628 (tag: v0.18.1) Release v0.18.1
    * 1aea3b8 Add support for mattermost 5.x

Каждый раз когда вы будете делать git-pull, будут создаваться эти "merge
branch" коммиты, так как в вашей ветке верхушка никогда не совпадает
(хэш отличается) с origin/master.

Проблема ли это? Зависит от. Так вы каждый раз видите когда сделали git
pull. Если это делать каждые пять минут, то с появлением коммита в
origin, у вас будет создаваться merge-коммит, объединяющий вереницу
коммитов вашей истории и удалённой. На мой субъективный взгляд, чаще
всего это делает историю некрасивой, загромождает ненужной для человека
информацией (мусорными коммитами) и сложнее разобраться может быть как
что откуда и как растёт.

2) Вариант с отдельной веткой и merge-ем:

    $ git checkout master
    $ git pull
    $ git checkout mybranch
    $ git merge master

аналогичен 1), но у вас локально master ветка будет всегда полностью
соответствовать ветке с origin. Хотя, по факту эта информация всё равно
содержится в origin/master.

3) Вариант с rebase-ом вашей ветки на master:

    $ git checkout master
    $ git pull
    $ git checkout mybranch
    $ git rebase master

rebase это простая операция: она берёт коммит(ы) и меняет их родителя.
Как-бы отрывает и прикрепляет к другому. Безусловно все хэши всех эти
коммитов поменяются (в вырезке из man-а они поэтому со штрихами). Но,
тогда, вместо:

    *   f8394da Merge branch 'master' of github.com:42wim/matterircd
    |\
    | * ad4a8f0 readme: Add a "Guides" section and one guide (#194)
    * | f7cf55c (tag: v0.18.2) Release v0.18.2
    |/
    * a0ab000 Update vendor nlopes/slack

если бы "readme: Add a "Guides"" коммит имел родителя не "Update vendor
nlopes", а "Release v0.18.2", то тогда произошёл бы fast-forward и
история превратилась в:

    * XXXXXXX readme: Add a "Guides" section and one guide (#194)
    * f7cf55c (tag: v0.18.2) Release v0.18.2
    * a0ab000 Update vendor nlopes/slack

и в вашем случае бы ваш mybranch всегда выглядел как:

    ваш коммит
    последний коммит master

как например вот тут:

    * a4ceccf (HEAD -> links) uptodate
    * a51d32b webp
    * 7c53a13 (tag: 8.1, origin/master, master) Hide mmap imports, failing on Windows

последний коммит в master это 7c53a13, а над ним два постоянно
rebase-ящихся коммита ветки "links". На мой взгляд, это очень удобно для
человека, для его восприятия. Но всегда зависит от. Rebase это изменение
цепочки хэшей и моя ветка "links" после каждого rebase никогда не может
быть запушена на удалённый репозиторий просто так, потому что fast
forward невозможен. Если это лично ваша ветка/ваш репозиторий, то уверен
что это не проблема.

4) Вариант с rebase-ом вашей master ветки (с вашим коммитом) на
origin/master:

    $ git pull -r

что равносильно:

    $ git fetch
    $ git rebase origin/master

Так вы будете иметь master полностью совпадающий с origin/master, за
исключением того, что его верхним коммитом будет ваш. На мой взгляд это
самый удобный способ. Я вот использую suckless terminal (st), а в нём
вся конфигурация зашивается на момент компиляции. Я просто делаю rebase
своего master на удалённый и моя история всегда линейна, без
ответвлений:

    * b5a4ee2 (HEAD -> master) My config
    * 92cc580 Coloured italics
    * 43a395a (tag: 0.8.3, origin/master, origin/HEAD) bump version to 0.8.3
    * 72e3f6c Update XIM cursor position only if changed

Но! При любом раскладе возможны конфликты в файлах (origin поправил тоже
место что и вы). В случае с merge-коммитами, один раз разрешив конфликт,
он как-бы зафиксирован и сохранён в истории и three way merge алгоритм
его всегда учитывает и если в origin в том же месте снова что-то
поменяется, то большая вероятность что three way merge автоматически всё
сам разрулит. В случае с rebase, three way merge перестаёт работать.
rebase, можно сказать, это просто:

    $ git checkout коммит-родитель
    $ git cherry-pick ваш-коммит
    $ git cherry-pick ваш-коммит2
    [...]

ну и дальше выставление текущего указателя ветки на то, что получилось.
Никакой магии в rebase нет и его можно делать вручную cherry-pick-ами.
Но когда делается cherry-pick, то это равносильно просто попытке
применения какого-то патча. То есть это буквально two way merge, где
отсутствует ancestor информация и если конфликт постоянно возникает в
каком-то месте, то после каждого rebase это может происходить. Проблема
ли это на практике и часто ли такое может быть? Зависит от. Я не помню
когда последний раз с этим сталкивался.

Но! С какой-то версии в git-е появилась технология rerere: man
git-rerere, которая запоминает разрешения конфликтов и если встречает
аналогичные конфликты, то автоматически применяет сохранённое разршение.
Это в отдельной поддиректории в .git сохраняется всё и может быть
удалено когда угодно. Достаточно просто включить rerere и он начнёт
работать и он спасёт от постоянно одинаково разрешающихся конфликтов,
которые могут во время rebase возникнуть.

А также надо быть очень осторожным с git pull командой, если не уверены
что remote репозиторий не делает форсированных (не fast-forward)
обновлений. Например Gitlab по умолчанию вообще запрещает не
fast-forward push в репозиторий, но всё зависит от настройки конкретного
репозитория и людей в него пушающих. Если человек сделал git push
--force, то у всех кто сделает git pull -- возникнет merge-коммит между
их историей и насильно форсированно изменённой историей удалённого
репозитория. Поэтому говорят что делать push --force чудовищно плохое
действие. И ваш master после такого git pull уже никогда не будет
совпадать с origin/master-ом. Тут только можно сделать (откатив все ваши
изменения, конечно же):

    $ git reset --hard origin/master

Лично я, *никогда* pull не использую, ну кроме личных репозиториев про
которые помню, а всегда делаю fetch чтобы увидеть обновился ли
репозиторий как fast-forward (и я могу сделать git merge origin/) или же
нет, он был насильно изменён и тут... уже смотреть по месту надо что
делать дальше.

Если резюмировать, то лично я бы (сам бы так и делал) для вашего
use-case советовал git pull --rebase в master ветке, где ваш коммит
будет всегда наверху.

оставить комментарий

Сгенерирован: SGBlog 0.34.0