[О блоге]
[наверх]
[пред]
[2020-05-23 14:46:24+03:00]
[be0132b6d3f1508ca0b33b9499bb6b9535615d3f]
Темы: [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 ветке, где ваш коммит
будет всегда наверху.
[оставить комментарий]