Git на практике. Учимся поддерживать репозиторий в порядке

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Всем разработчикам приходится ежедневно работать с Git, но далеко не все уделяют стоящего внимания этому инструменту. Цель этой статьи ознакомить с некоторым полезным функционалом Git и показать, как это используется на практике. В первую очередь, эта статья может быть полезна начинающим разработчикам, которые хотят улучшить свою экспертизу в работе с Git, а также людям, которые хотят, но не знают, как держать свой репозиторий в чистоте и порядке.

Для начала хотелось бы описать мое видение «правильного» репозитория. Основным отличием такого репозитория является чистая история коммитов. Каждый из этих коммитов должен быть осмысленной атомарной единицей изменений в проекте. Это значит, что наша история не должна содержать коммиты с сообщениями по типу «feature in progress». Ваша задача как разработчика научиться делить все вносимые изменения на такие атомарные единицы. Описанный ниже материал поможет вам этого добиться.

Stash

Первая очень полезная команда, которую хотелось упомянуть, это git stash. Она позволяет сохранить наши изменения без создания коммитов.

Такая опция хороша, когда мы хотим переключиться на другую ветку, например, чтобы срочно пофиксить баг, но текущие изменения не готовы, чтобы сделать коммит. Мы, конечно, могли бы сделать коммит, но он бы содержал незаконченную работу и не нес бы в себе особого смысла и в довесок еще бы загрязнял наш репозиторий.

Допустим, мы внесли какие-то изменения:

git status

On branch feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   file

no changes added to commit (use "git add" and/or "git commit -a")

Сохраним наши изменения в stash:

git stash

Saved working directory and index state WIP on feature: cdb8f82 Merge pull request #2 from germankhivrenko/feature

Также мы можем просмотреть список таких сохраненных изменений:

git stash list

stash@{0}: WIP on feature: cdb8f82 Merge pull request #2 from germankhivrenko/feature

Чтобы вернуть изменения из stash:

git stash apply

On branch feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   file

no changes added to commit (use "git add" and/or "git commit -a")

По умолчанию применяются последние изменения из списка, и чтобы выбрать конкретные мы можем указать индекс:

git stash apply --index 0

Теперь мы знаем как переключать контекст задач и не загрязнять репозиторий.

Cherry-pick

Переходим к cherry-pick. Эта команда позволяет вставить выбранный вами коммит (и конечно же его изменения) в текущую ветку. Это полезно, когда мы хотим перенести конкретные изменения из другой ветки, но не хотим сливать ее полностью.

Представим, что у нас есть две ветки main и release с такой историей.

В main:

commit 818136b3eec70b04ca27b5de55abb1dc5de36cb9 (HEAD -> main)
    bad changes

commit ffd31c519d29eae6f9573f42562708ae8034a0f4
    cherry-pick me

commit 7128c5d11ef768dc21b1171752502231fb274b21
    Initial commit

В release:

commit 7128c5d11ef768dc21b1171752502231fb274b21 (HEAD -> release)
    Initial commit

Мы хотим получить изменения коммита с сообщением «cherry-pick me», но не хотим иметь изменения commit-а с сообщением «bad changes» в ветке release. Для этого мы должны сделать cherry-pick нужного коммита:

git cherry-pick ffd31c519d29eae6f9573f42562708ae8034a0f4

Для выбора коммита мы используем его hash. Теперь все готово, и мы можем увидеть нужные изменения в release:

commit e6a95034d392d295741a14ab0eaf084258116f5d (HEAD -> release)
    cherry-pick me

commit 7128c5d11ef768dc21b1171752502231fb274b21
    Initial commit

Примечание: во время выполнения cherry-pick могут возникнуть конфликты, после их решения Вы можете завершить cherry-pick c помощью команды git cherry-pick --continue.

Сама суть этой команды заставляет задуматься над атомарностью коммита. Правильная история коммитов очень сильно помогает при использовании команды cherry-pick.

Rebase

В сети достаточно информации со сравнением merge и rebase (как мне кажется, один из самых распространенных вопросов по поводу Git), так что я не стану их сравнивать, а просто расскажу, как я использую rebase в повседневной рабочей жизни.

Для ясности кратко рассмотрим цикл разработки какого-то функционала с точки зрения Git. Разработчик берет за основу общую ветку (обычно она называется dev), создает новую ветку и вносит в нее свои изменения, потом создает merge/pull request, чтобы залить свои изменения в общую ветку.

Я советую делать rebase перед созданием merge/pull request-а своей ветки в общую. Все это делается для того, чтобы решить потенциально возможные конфликты, а также поддерживать чистоту истории в репозитории. Чистая и понятная история сильно облегчает потенциально возможные манипуляции над репозиторием в будущем.

Итак, зачем вообще что-то делать перед созданием merge/pull request-а? Главной целью для слияния своей ветки с общей перед созданием это решение возможных конфликтов. Держу пари, каждый разработчик когда-то видел сообщение по типу «There merge conflicts» или «Cannot merge automatically». Следовательно, перед нами стоит выбор: merge и rebase. Конечно, rebase не всегда уместен, но в данной ситуации стоит выбирать именно его. Для наглядности посмотрим на разницу между merge и rebase в следующем примере.

Допустим, нам нужно реализовать какой-то новый функционал. Для начала мы переключаемся на общую ветку (в нашем примере main) и делаем pull, чтобы подтянуть последние обновления:

git checkout main
git pull origin main

Создаем ветку feature из main для локальной разработки:

git checkout -b feature

Представим, что мы реализовали новый функционал и сделали новый коммит:

git add .
git commit -m “implemented feature”

Поскольку над нашим репозиторием трудится множество людей разработчиков, пока мы реализовывали наш функционал, в общей ветке появились изменения, которые конфликтуют с нашими. Поэтому мы должны предотвратить возможные конфликты:

git pull --rebase origin main

Потом мы должны решить те самые конфликты (если они присутствуют) и затем продолжить:

git rebase --continue

Примечание: rebase перезаписывает Ваш созданный коммит, hash-ы старого и нового коммитов отличаются.

После чего мы можем отправлять наши изменения на сервер:

git push origin feature

И теперь можно спокойно создавать наш merge/pull request и не переживать по поводу конфликтов.

Теперь давайте посмотрим на историю коммитов нашей общей ветки после слияния нашего request-а (для этого переключитесь на main и сделайте pull):

git log --pretty=short

commit eb560c0af5cfb63073592650bcb3e9050342b6bb (HEAD -> main, origin/main, origin/HEAD)
Merge: 795882a 9f5011e
    Merge pull request #1 from germankhivrenko/feature

commit 9f5011eeed9d6ffd5f693e7136599bc13f93a768 (origin/feature, feature)
    implemented feature

commit 795882a02fc51fea1b2f46df8a87c2bec3d6dc86
    added some changes

Коммит с сообщением «added some changes» — это те самые изменения, которые были добавлены другим разработчиком во время нашей работы над feature. Итого, мы с нашей стороны имеем два коммита. Один из них несет смысловую нагрузку (разработанный нами функционал), а другой — это merge-коммит двух других коммитов (мы можем это увидеть из его описания: Merge: 795882a 9f5011e), его за нас создала система.

Теперь давайте взглянем на историю коммитов, в случае если бы мы использовали git pull origin main вместо git pull --rebase origin main, то есть использовали merge вместо rebase. Прежде всего, после решения конфликтов, нам бы пришлось создать новый коммит:

git add .
git commit -m “resolved merge conflicts”

Взглянем на историю коммитов main ветки:

commit cdb8f82bd78dfbcb9381147ad30a5f44f7c8072e (HEAD -> main, origin/main, origin/HEAD)
Merge: 795882a eb36d47
    Merge pull request #2 from germankhivrenko/feature

commit eb36d479e83f899b5dcb7b25fc881c7fa5ff6f16 (origin/feature-1, feature-1)
Merge: 69beef2 795882a
    resolved merge conflicts

commit 69beef25fb0ad295fda1471c9c46f8e9d777b821
    implemented feature

commit 795882a02fc51fea1b2f46df8a87c2bec3d6dc86
    added some changes

Вместо 3 коммитов получаем 4, где коммит с сообщением «resolved merge conflicts» не несет никакой смысловой нагрузки.

Если вам нужно синхронизировать вашу ветку и общую по ходу разработки, использование merge может существенно загрязнять историю, особенно если такая синхронизация проходит часто.

Reset & revert

Далее уделим немного внимания удалению и исправлению уже существующих в репозитории изменений и коммитов. Рассмотрим команды reset и revert.

Допустим, мы хотим добавить изменения к предыдущему коммиту. Для этого нам нужна команда reset с параметром —soft или —mixed (mixed используется по умолчанию, если указывать параметры). Вводим:

git reset --soft HEAD~1

Запись HEAD~1 означает, что мы хотим перейти на один коммит назад от текущего положения HEAD. Чтобы посмотреть, что же произошло, можно использовать git log и git status. В списке коммитов больше не будет коммита, который мы удалили, а изменения этого коммита остались в рабочей директории в статусе staged. Далее можно дополнить/исправить эти изменения и сделать новый коммит.

Если мы хотим просто удалить коммит и его изменения следует применять reset с параметром —hard, он удалит коммит их изменения не останутся в нашей рабочей директории.

Также для изменения последнего коммита в Git существует параметр —amend для команды commit, но мне почему то больше нравится пользоваться reset.

Стоит отметить, что описанные выше подходы именно изменяют историю коммитов в репозитории, что не всегда является безопасным. В общих ветках, которые обычно названы как master, dev или main не принято вносить изменения в историю коммитов, так как это может иметь негативные последствия. Также, если попробовать изменить коммит который уже есть на сервере, например, с помощью git reset и попробовать отправить текущую ветку на сервер, то мы получим подобное сообщение:

git push origin main

To github.com:germankhivrenko/git-in-practice.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'git@github.com:germankhivrenko/git-in-practice.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 советует нам сначала сделать pull, потом решить конфликты, создать новый коммит и уже потом попробовать отправить наши изменения еще раз. Все это делается для того, чтобы не допустить изменения истории коммитов на сервере. Конечно, это можно обойти с помощью параметра -f (—force) для команды push:

git push -f origin main

И если изменять историю на сервере в своих ветках, созданных для разработки конкретного функционала, до слияния их в общую вполне приемлемо, то изменять историю таких веток как master крайне не рекомендуется. Тут на помощь к нам приходит revert. Эта команда может удалить изменения выбранного коммита посредством создания нового коммита. Таким образом можно удалять изменения, но не изменять историю коммитов.

Примечание: revert может вызывать merge конфликты, после их решения нужно продолжить revert с помощью git revert --continue.

Надеюсь, что данная статья хоть немного улучшить ваш опыт работы с Git. Спасибо, что дочитали до конца, буду рад любой критике и комментариям.

👍НравитсяПонравилось30
В избранноеВ избранном27
LinkedIn

Лучшие комментарии пропустить

Я таки предпочитаю для временного переключения на задачу с другой ветки сделать незаконченный коммит с комментом DRAFT, вместо stash. Если работа будет продолжена, то докомитить с amend куда удобнее, чем вспоминать что ты где то там что то прятал. В принципе не мудрено даже хук запилить чтобы не давал коммитить без amend, если последний был с комментом DRAFT, хотя и пофиксить не запушенное то не беда.

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Также для изменения последнего коммита в Git существует параметр —amend для команды commit, но мне почему то больше нравится пользоваться reset.

так аменд нормально можна зробити лише для незапушеного ще коміта. Чи я помиляюся?

так и ресет вносит изменения локально. для изменения истории на сервере нужно в обоих случаях пушить с флагом —force

Недавно наткнулся на Git Magic — сравнительно компактное практическое руководство.
Возможно, будет полезно.

Чудова стаття! Я б ще додав пару слів про інтерактивний ребейз, і про байсект.

ще є інтерактивний «комміт» точніше «add», теж корисна штука

git add -e ще зручніше у будь-яких складних випадках :)

і commit, i checkout, i add мають інтерактивний режим

Такая опция хороша, когда мы хотим переключиться на другую ветку, например, чтобы срочно пофиксить баг, но текущие изменения не готовы, чтобы сделать коммит.

git worktree

Мне кажется, что stash больше используется чтобы перенести изменения в другую ветку, всё таки при переключении проще git commit -a -m tmp и потом при возврате что-нить вроде git reset HEAD^

Из того, что я делаю часто ещё, это git add -i some_file, что позволяет добавить в индекс только часть правок, например отладочные принты. Или добавить небольшой фикс, например, к прошлому коммиту (git commit —amend —no-edit) или к любому другому коммиту в истории (git commit -m fix-00face, git rebase -i 00face^, после чего надо просто перенести его после нужного и поменять pick на fixup)

Далее, для быстрой синхронизации между машинами в сети я часто юзаю что-то вроде git remote add laptop ssh://user@server/path/to/repository

Вот ещё интересный скрипт, который позволяет докоммитить в бранч разницу до другой бранчи:

git checkout b2       # We are starting from branch b2
git checkout -b tmp   # We creating a temporary branch for safety reason
git reset b1          # Now tmp is pointer to b1, but current file are from b1 (maybe --soft for indexing?)
git add ...           # Add changes
git commit            # And commit them!
git diff HEAD b2      # Check that we did not do any changes
git checkout b1 && git reset --hard tmp && git branch -d tmp

Плюс за rebase в фича ветках, минус — за stash. Сам двумя руками за squash. Предпочитаю избегать «наслаждения» десятками бессмысленных коммитов «изменил одну букву», «изменил её обратно» и т.д. (при чём, с мерджами и обычными коммитами вразброс для полного счастья, чтобы история выглядела максимально похожей на кусок понятно какой субстанции).

И что не так со stash?

Stash отлично работает как минимум в следующих сценариях:
1. Осознание того, что для действия требуется более глубокое влезание в дебри и недри — текущие правки откладываются и идётся вниз (вплоть до серьёзного рефакторинга).
2. Rebase (—autostash ему помогает).
3. «А что я сломал? откатываем по частям»
4. Полное временное переключение на другую ветку (обычно требует -a, -u).

Разумеется, так как stash это просто коммит в специальном списке, можно делать это и коммитами. Но если понимать его характер и общий принцип работы со stashʼами как с LIFO стеком — проблем не будет.

На это:

> Репозиторий же копируется один раз и постоянно контексты переключать уже не нужно.

отвечу, что всегда имеет смысл держать баланс между переключениями контекстов в одном репозитории и разных. У вас этот баланс перекошен в одну сторону, а у кого-то будет в другую. Может, ему тупо места не хватит :)

> По причине выше он подойдёт без геморроя только для кратковременной работы с другой веткой.

Не обязательно _кратковременной_ - иногда бывает, что фича останавливается и продолжается через месяцы. Важнее то, что эта работа _неразветвляющаяся_ (или минимально ветвящаяся).

со сташем все так, подозреваю, что его просто не к месту применяют

--force-with-lease безопаснее

чому checkout для світчингу між бранчами? вже мабуть рік як для цього є АПІ switch
усім хто писав про проблеми с git stash — у вас або декомпозиція задач хромає або workflow поганий, це ж класична проблема редагування одного файлу багатьма, навіть довелось гуглити git stash problem :)

„git switch” и „git restore” — это просто подмножества от „git checkout”.

Ну стиль местами заметно отличается. git checkout $release — $file — кладёт сразу файлы в индекс, а git restore -s $release — $file — нет.
Привыкать надо.
Ну и местами народ работает там, где 2.21 ещё не завезли.

Когда в мастер уже намерджили несколько фича бранчей, то решение конфликта с рибейз может стать адом. В таких случаях предпочитаю просто гит мердж.

а мы просто запретили делать рибейз в master и stage ветки

а в свои ветки да, настоятельно рекомендовано обновлять рибейзом.
у нас условно помодульные, а не фичевые, и две — hotfix для прода, и bugfix для стейджа, в них бывают фичебранчи.
в комменте к комиту обязательный номер таски, чтобы джира подтянула в ворк лог

и жизнь наладилась.

Ну, я про свои фича бранчи и говорю: если приходит конфликт, разбитый на 7 коммитов, то при рибейзе в свою фича бранчу этот конфликт придется решать 7 раз, и при этом не видно общей картины конфликта. Это ад )
А в мастер понятное дело никто руками не лезет локально, только после аппрува фича бранчи на сервере выполняется мердж в мастер (а оттуда уже все вытягивают на свои локальные машины).

при рибейзе в свою фича бранчу этот конфликт придется решать 7 раз

git-scm.com/docs/git-rerere не помогает?

можно сделать squash собственной фича ветки (из консоли можно тем же интерактивным ребейзом)

За переписування публічної історії (rebase, squash, etc) давно пора відбирати клавіатури і видавати віники.

таким обычно занимаются
— зеленые июни, по незнанию
— лиды и архитекты, т.к. имеют «god mod»

публічної історії

1. локальной истории (очепятки и проч)
2. геррит, эниван?

Приватні гілки на те й приватні, щоб там хоч на голові можна було ходити :)

Про Герріт я не поняв. Знаю що це, але не працював.

с герритом только через ребейз можно работать

одно дело, как заапрувленый код попадает в основную ветку, а другое — как ты обновляешь свой коммит

Що там недостатньо додати черговий коміт в feature-гілку/MR/PR/чи_як_воно_там_зветься щоб він просто з’явився у рецензента?

насколько мне известно — нет. коммит-ребейз+скваш-пуш

По-моему в геррите как и в любой другой code-review можно настроить обычные коммиты в ветку и пулл реквест на мерж в master/release/other
Все зависит от того, как вы свой воркфлоу настроите.

хз. я такого не встречал. да и нету в геррите пулл-реквестов, он сам по себе пулл-реквест тэкскээть

Какие ограничения настроишь, такие и будут.
У нас в соседнем проекте сплошные мержи.

У меня сплошніе ребейзы и сквошик — гитхаю поверх гита удобен. Только станет напрягать — можно перейти к обычному GithubFlow/GitFlow. Или к тому, что подойдет. Мне кажется, что в погоне за серебряной пулей начали забывать нафиг она нужна. Против Герита ничего не имею. Но флоу настраивается базируясь на определенных критериях. И пока что серебряной пули там не изобрели

Я немного не о том.
На моих проектах, например, merge commits пушить в Gerrit нельзя, но можно и нужно ребейзить по необходимости (и CI на каждый коммит проверяет его корректность). А вот при сабмите допускается, что сам Gerrit сделает merge.
При этом есть небольшая вероятность расхождения, что после merge что-то подерётся (хрестоматийно: в коммите вызвали foo(), а в B переименовали foo -> bar), ловится суточными тестами CI и разнообразными проверками QA. Пару раз в год такое таки происходит. Но это считается разумной ценой.

А вот у одного соседнего компонента делать merge самому Gerritʼу запрещено, только FF относительно текущей головы ветки, зато разрабы могут вливать мержи. Вот так той команде удобнее. Сам merge точно так же проверяется CI. Цена — если кто-то тебя опередил, ты должен под него приспособиться локальным мержем и перевливанием — зато результат гарантированно проверен в CI.

Ссылаться на всякие GitFlow и т.п. не хочу — они ортогональны этим вопросам. Где-то удобнее толпы feature-веток, где-то 2-4 трейна... У нас по максимуму вливка в транк (master) и разливы черипиками из него. А у тех соседей, где мержи — нормально вливать в транк из уже форкнутой релизной ветки.

На моих проектах, например, merge commits пушить в Gerrit нельзя, но можно и нужно ребейзить по необходимости (и CI на каждый коммит проверяет его корректность).

так собственно есть хоть один проект с герритом, на котором патчсет добавляют мерджем?

Я ж как раз про соседей говорил:

А вот у одного соседнего компонента делать merge самому Gerritʼу запрещено, только FF относительно текущей головы ветки, зато разрабы могут вливать мержи.

или вы о чём?

или вы о чём

об обновлении патчсетов в коммите
как геррит вливает в основную ветку после аппрува это другая история

об обновлении патчсетов в коммите

Да.

В коментарях жалілись на stash та конфлікти — в таких випадках
git stash branch має допомогти

Як зробити, щоб Idea бачила перейменовані / переміщені файли не як видалені / створені?

Може git add зробити в новій локації? Принаймні з tortoise git це допомагає

Я таки предпочитаю для временного переключения на задачу с другой ветки сделать незаконченный коммит с комментом DRAFT, вместо stash. Если работа будет продолжена, то докомитить с amend куда удобнее, чем вспоминать что ты где то там что то прятал. В принципе не мудрено даже хук запилить чтобы не давал коммитить без amend, если последний был с комментом DRAFT, хотя и пофиксить не запушенное то не беда.

Кстати, хорошее решение
Правда в идеале стоит обговорить с командой и хук сделать частью бутстрапа проекта на локальной машине. Это не обязательно. Но это может действительно быть удобнее стеша

Тоже к этому пришли. И даже в репо разрешено пушить.

И даже в репо разрешено пушить.

Що мається на увазі?

ну пушить незаконченные коммиты в общак на ремоут, а не только хранить в локальном репо.

Якщо мається на увазі свій форк репи на сервері, то в чому снес взалагі якось обмежувати роботу із нею? В github/gitlab проблем з форками немає. А якщо в майстер-репу заливати без МР та код-ревью — це щось новеньке, навіть якщо якийсь девелопмент-бранч.
От едине, де додаткові умови пушу можна обговорювати — про роботі із геррітом, але й там можна просто заливати приватні патч-сети, які нікому не видимі, поки не готові.

Якщо мається на увазі свій форк репи на сервері,

мова про коміт незакінченого коду

А якщо в майстер-репу заливати без МР та код-ревью

ніхто draft коміти у загальні бранчі master, stage, hotfix, bugfix не заливає.

тобто ви робите припущення для якихось супер ідіотів :)

які нікому не видимі, поки не готові.

тому й дозволено заливати драфти у дев бранчі, що буває корисно подивитися заздалегіть, як там виходить.
а коли взагалі треба обговорити проблемку на «80%» реалізованого, то взагалі самий простий спосіб — подивитися той драфт у себе в IDE

тобто ви робите припущення для якихось супер ідіотів :)

Я роблю припущення виходячи із наявної інофрмації :) Було б більше інформації, були б інші припущення.

тому й дозволено заливати драфти

Так драфти для gerrit-а — це стандартний механізм зберігання-обміну кодом, не передаючи його в CI. Ось мене й збило з толку, чому

И ДАЖЕ в репо разрешено пушить

? В чому підступ? :)

Я роблю припущення виходячи із наявної інофрмації :)

яка була неповною.
а далі — ваш особистий вибір, чим доповнювати.

? В чому підступ? :)

бо є така «бестпрактика» що у репо не повинно бути непрацюючого коду

за такє правовірні можуть побити камінням.

або, у нас в пих світі — сінкати файли прямісінько на прод. за такє теж — аутодафе
а ми інколи так робимо. буває :)

+++
или, как вариант, просто сделать ещё один коммит потом и засквошить их (работаю через TortoiseGit и там это очень легко и быстро)

Теж таким часто користуюся. Правда, я потім не amend роблю, а reset —soft HEAD~1.

Користуюсь все життя вбудованими гуі клієнтами в jetbrains ide, майже ніколи не треба писати руками команди в консолі. Я щось втрачаю?

Не думаю, что ГУИ это плохо. Главное понимать какие есть инструменты и когда (и как) ими пользоваться.

Если вам так удобнее, и вы понимаете «как оно там работает», и в состоянии починить, когда «ой все сломалось», то — нет, не теряете.

Починить репозиторий гита очень просто — «rm -rf .git» + клон заново.

В некотором классе гитовых проблем это решение будет ещё и самым оптимальным.

А яка різниця? Принципи гіту треба і в гуі розуміти. Плюс консолею буває точніше.

Так. Швидкість, можливість автоматизації команд. Ну і через ssh підключення гуі не працює.

Хз може intellij вміє, по ссш стукатись так точно

Якщо ти користуєшся лише push, pull та commit та тобі цього вистачає, то може й не треба. Переваги командного рядку:
1. Пошук відповідей. Коли ти не знаєш як зробити шось, то зазвичай шукаєш у мануалі або Google. Команди набагато точніші, замість скріншотів з позначенням куди клікати. Також комʼюніті значно більше.
2. Автоматизація. Часті послідовності дій можна поєднати у скрипти (bash, python). Це простіше ніж Selenity та інші засоби GUI.
3. Пошук в історії. Коли ти щось робив два тижні тому, але забув в деталях що саме, то можна подивитися history та відтворити. Якщо брати GUI, то... як? Записати відео можна, але як шукати? Це моя велика головна біль з GUI, я памʼятаю, що я це робив, але забув де воно було.

Allow commiting only specific lines : IDEA-186988. 3 роки тікет без руху.
Плюс нема аналога `git commit -p` + `e` коли редагуєш чанк прям під час коміту, але на диск зміни не потраплять.

Кожен раз коли у вас виникає ідея «о, а тут я можу зробити git stash» зупиніться, видихніть і зробіть копію репо в якій і працюйте над іншою гілкою. Це дозволить уникнути необхідності розбиратися у конфліктах які не дають витягти файли зі стеша.

Що стосується rebase — відео «Гітле про git» розкриває цю тему.

Кожен раз коли у вас виникає ідея «о, а тут я можу зробити git stash» зупиніться, видихніть і зробіть копію репо в якій і працюйте над іншою гілкою. Це дозволить уникнути необхідності розбиратися у конфліктах які не дають витягти файли зі стеша.

Так та ні, коли треба зберегти контекст та свічнутися в іншу гілку (для перевірки чогось, наприклад), то сташ — логічніший, якщо лише код не готовий до проміжного комміта

Так, спочатку свіжевипущені програмісти гигикають з того як у тих хто в індустрії вже дуже давно ціла купа гілок одного репо в різних директоріях. А потім коли їм тривіально треба файли з різних гілок порівняти, чи сташ не мержиться, а ребейс робить лише все гірше вони сидять і сумно переписують усе вручну заново. Потім проходить рік-два, вони перестають вірити рекламі і теж вже мають кілька копій одного репо в яких можуть працювати одночасно та незалежно.

Не дай моєму доу тайтлу ввести себе в оману. Не «свіжевипущений». І не гигакую. Я навів життєвий приклад коли до стешу покласти зміни може бути краще. Декілька копій окремого репо — то інший випадок

Я не про тебе говорив. А про те що в цілому менш досвідчені деви сильно вірять в магію технологій які усе порішають варто лише на них перейти і «робити все правильно».

Але стаття як на мене більше не про «магію» а просто про можливості технологій якими багато хто і так користується

Так, можливо я побачив у написаному того, що туди не закладалося. І стеш дійсно зручний коли треба швиденько перейти на інший бренч і потім невдовзі повернутися назад.
Але вже ребейс вимагає розуміння того, що саме відбувається. Мені доводилося бачити команди в яких ребейс було заборонено використовувати після того як команда кілька днів провела переписуючи та відновлюючи вручну результати «ща ми швиденько тут ребейсом все порішаємо».

Ну щодо ребейзу там є теж приклад, використовувати лише в локальні (особисті) гілки. Щоб не руйнувати іншим флоу. Наприклад можна перед ПР привести до тями локальну гілку. Або як в статті — розрулити конфлікти з публічною гілкою локально та зробити гарну історію коммітів, яку можна потім використовувати як квазі-документацію
Ребейз публічної гілки — то табу (або тре домовитися і стопнути девелоп. приклад: треба зробити у деві/стейджі всі комміти «зеленими» за якихось причин)

Такі люди просто не вміють користуватися гітом

Копирование целого репозиория может тянуть за собой неудобства посерьезнее чем конфликты, например, настройки окружения. Все зависит от проекта, но каждый раз так делать может быстро надоесть. Плюс git stash используется не только для того чтобы переключится на работу в другой ветке. Пример из жизни: локально я разрабатываю в одной ветке (с докер сетапом), а на стейдже используется другая ветка со старым сетапом (без докера), то есть я должен разрабатывать и тестить в первой ветке, но коммитить в другую) Тут мне обычно помагает git stash. Я и считаю, что это далеко не последний случай, где Вам может помочь git stash. Так что, не думаю, что стоит быть таким категоричным. И спасибо, что прочли)

Кстати, тоже не понимаю, откуда такой хейт по отношению к stash... для сценариев вида «переключиться временно на другую ветку» в моём лично опыте stash был крайне полезным инструментом.

Ага, я теж любитель тимчасово переключаюсь на нову гілку. Тепер у мене сім сотень сташнутих змін (за 1.5 роки).

Якщо ви робите копії свого репозиторія, навіщо вам гіт?

гіт для того ж, для чого й усім іншим. Копії — щоб були одночасно кілька копій доступні.

Ну якщо різні версії проекту (копії у вашому випадку) вирішуються за допомогою копіювання папочек, ще раз питання — яку роль виконує гіт? Як переносити зміни між цими папками? Як подивитись діф між цими двума версіями?

різні версії проекту

Різні гілки, або навіть кілька копій однієї гілки.

а допомогою копіювання папочек

за допомогою git clone

яку роль виконує гіт?

git виконує роль системи контроля версій

Як переносити зміни між цими папками?

Так само як зміни переносяться між гілками

Як подивитись діф між цими двума версіями?

так само як подивитися діф між двома гілками

Тобто ви використовуєте окрему копію як ще один ремоут? Який в цьому сенс? Так само можна мати різні гілки в одному проекті.
Взагалі ви ні стеш не використовуєте, ні ребейз. Тобто для вас git — це просто svn. Commit, push і все... Ви і pull —rebase теж не використовуєте?
Я 10 років використовую ребейз і інтерактивний ребейз. І жодних проблем. Гіт флоу використовується тисячами команд. В величезному проекті лінукса спокійно уживаються і гілки і стеши роками. Робити ще однин клон репозиторія тільки для того, шоб не стешити поточні зміни — це дитяча хвороба, яка лікується досвідом.

Тобто ви використовуєте окрему копію як ще один ремоут? Який в цьому сенс?

Сенс в тому щоб мати кілька копій для зручності внесення незалежних змін, порівняння, тощо.

Тобто для вас git — це просто svn

git і є просто svc, який є просто cvs і так далі в історію. Система контроля версій потрібна для контролю версій, а усякі приємні доповнення просто додають зручності.

Ви і pull —rebase теж не використовуєте?

Використовую коли вони потрібні.

Я 10 років використовую ребейз і інтерактивний ребейз. І жодних проблем.

Я більше 20 років використовую системи контролю версій як відкриті, так і заточені під одного конкретного клієнта. І проблеми є завжди.

Робити ще однин клон репозиторія тільки для того, шоб не стешити поточні зміни — це дитяча хвороба, яка лікується досвідом.

ЛОЛ. Як щодо досвіду «мати дві копії репо»?

Що в лоб, що по лбу.
— Навіщо ще одна копія, якщо все те саме можна робити в одному репо?
— Для того, щоб мати другу копію.
Ось наш діалог. Facepalm.
Якщо у вас «є проблеми завжди», можливо треба краще вивчити інструмент?

Навіщо ще одна копія, якщо все те саме можна робити в одному репо?

Щоб мати більше однієї копії в яких можна робити все що завгодно незалежно, паралельно та без конфліктів.

Якщо у вас «є проблеми завжди», можливо треба краще вивчити інструмент?

Якщо у тебе проблем нема «ніколи» то маже варто замислитися про обмеженість досвіду?

Та ясно. Це ж мені потрібно декілька копій, бо в одному репо я не вмію працювати

Це «вміння» дещо ідеалізоване та іноді приводить до проблем, які набагато швидше можна розв’язати маючи кілька локальних копій одного репо.

Ну я обычно создаю по рабочей копии на тикет. В сложных случаях (отработать сначала на старой ветке, потом перенести в мастер и оттуда распространять на прочие активные ветки) — от двух копий на тикет.
Переключаться между разными тикетами (которые могут в сложных случаях тянуться месяцами, в неделю по чайной ложке) тяжелее, чем держать десяток копий в процессе.

Но это, разумеется, при отсутствии сильных взаимовлияний разных работ. При таких влияниях лучше уже ветки в одной рабочей копии (или stash — если между работами явная LIFO зависимость).

Робити ще однин клон репозиторія тільки для того, шоб не стешити поточні зміни — це дитяча хвороба, яка лікується досвідом.

Подход со stash’ем более error-prone, если нужно делать более одной независимой фичи одновременно, потому что предполагает гораздо больше ручных действий при переключении контекстов. Репозиторий же копируется один раз и постоянно контексты переключать уже не нужно. Впору бросать ссылку на Википедию, что значит слово «аргумент», а то про лоб ниже очень метко — молиться тоже надо уметь). Говорить, что есть stash, значит, все обязаны его использовать — это ну очень по-взрослому). По причине выше он подойдёт без геморроя только для кратковременной работы с другой веткой.

А я десь написав що всі мають обов’язково використовувати stash? Час кидати посилання на софізми і демагогію.
А аргумент дуже простий. Гіт створений для контролю версій і ніде в документації чи книжках не сказано — зробіть іншу копію і працюйте там. Штатних інструментів достатньо, щоб зробити все, що потрібно в одному репо. Якщо друга копія потрібна — щось не так з воркфлоу або руками.
Так, stash саме для того, щоб ненадовго сховати зміни. Ключове слово ненадовго.

ніде в документації чи книжках не сказано — зробіть іншу копію і працюйте там

Ніде в документації не написано, що перед роботою з гітом комп’ютер треба вмикати в розетку.

Все ж таки посилання на демагогію потрібно додати. Закінчились аргументи? uk.m.wikipedia.org/wiki/Фальшива_дилема

Закінчились аргументи?

Аргументи для чого? Якщо для того щоб підтвердити, що іноді зручніше та швидше працювати у кількох копіях репо то у мене той самий беззаперечний аргумент. А саме — я з таким стикався і знаю це з практики.

Так, stash саме для того, щоб ненадовго сховати зміни. Ключове слово ненадовго.

Александр с этим согласился ещё 4 мая в одном из предыдущих постов:

Так, можливо я побачив у написаному того, що туди не закладалося. І стеш дійсно зручний коли треба швиденько перейти на інший бренч і потім невдовзі повернутися назад.

Но Ваши посты об одном и том же не прекратились почему-то, из чего был сделан соответствующий вывод.

Мої пости не про стеш, а про шкідливу рекомендацію плодити репозиторії замість того, щоб вчитись працювати в одному.

Прийдуть люди, почитають таку пораду і потім у них теж 20 років проблеми будуть

Як подивитись діф між цими двума версіями

diff3 version1 version2

чи щось змінилося?
Вершн контроль — корисна штука, але флоу на ній — то в першу чергу (задокументований) конвіньєнс команди) імхо

git — г-но, переписывание истории — зло. Пользуйтесь mercurial, обретите счастье!

Ты устал, отдохни :) Добро это пластик. Но это не точно. А если серьезно — гит неплох для многих задач. Но да в нем есть куча проблем, если, к примеру, менеджить бинарники. Переписывание истории же при общем согласии тимы может помочь исправить случайные косяки с историей, которые не перекрыли раньше ci/cd
Хммм... Я как-то комментирую воздух. Что за баг?

Меркуриал хорош, но есть фатальные недостатки
— переименование делается через delete/add, что сильно раздувает размер репозитория
— когда размер репо приближается к гигабайту, начиает сильно тормозить. Простое клонирование репозитория — приключение на целый день
+ им мало кто умеет поьзоваться, например создают «branch» вместо «bookmark» (аналог «branch» из git)

Пользуйтесь mercurial, обретите счастье!

Не позволяет самое важное и ценное, не нужен, уже сдох. Отдельные фанаты начинают идти лесом.

переписывание истории — зло.

Алюминь.

dev.to/...​ville/on-git-history-12c2
Если есть желание совместно писать статьи — можешь скооперироваться вот с этим автором. Он мой давний знакомый, можете делать 2 версии (английский для dev.to и украинский для dou). У него в разработке есть еще несколько статей по проблеме, насколько я знаю. Если неудобно напрямую — могу «подружить» вас в телеграмме.

Спасибо за предложение, но по данной теме пока ничего пистать не хочется)

Автор явно не потрапляв в ситуацію коли стеш не спрацьовує бо стешились з іншого коміта) При цьому нормально розрулити як конфлікт при мержі не виходить бо стеш просто не аплається.. Я двічі бачив таку ситуацію і обидва рази це був повний фейл і повторне написання/перенос коду

Я доречі дуже часто використовую стеш і теж не потрапляв до такої ситуації. Завжди є конфлікт і є «ми зберегли стеш про всяк випадок бо не аплаївся».
Тобто можно розрулити конфлікт в теорії а потім дропнути стеш. Ну то можливо тому що я тримаю там лише «свіжі» спроби і роблю локальні гілки/комміти коли воно ± працює як я хотів

А почему не получаеться решить конфликты? Я частенько комичю стешы с других коммитов — просто решаю конфликты.

Ну там виходить щось типу кеннот апплай стеш і все, ваш код живе в цьому стеші вічно) тобто до того щоб розрулювати конфлікти і не доходить

Як я вже писав вище — стеш аплаіться (не зустрічав випадків коли ні, але то може специфіка використання стешу, от чесно не дуже хочу лізти дивитися сорси Лінуса). Тобто зміни є, але конфлікти треба вирішувати. А стеш потім дропнути.

Інструкцію пробували читати?

Він показує конфлікт. Спокійно його вирішуєте звичайними методами, а потім робите git stash drop на той стеш.

А ще можна його зберегти в окрему гілку (аж два методи) і мержити вже з гілок.

стеш не спрацьовує бо стешились з іншого коміта

Там сохраняется, из какого комита делался стеш. Если стеш не зааплаился, то
— можно мержить руками, как при мерж-конфликтах
— можно откатиться на старый комит, создать новый бранч, зааплаить стеш, и опять мержить руками (или попросить кого-то умного, чтобы смержил)

жаль мат, даже в цензурной версии выглядит не к месту.

глаз режет

в украинском языке мат режет слух, в русском звучит довольно органично

в украинском языке мат режет слух

Треба просто «сука» замінити на «курва».

Так это уже другой язык:

O kurwa, coś poszło bardzo nie tak, powiedz że Git ma jakiś magiczny wehikuł czasu!?!
O kurwa, zrobiłem(am) commit i zaraz potem zauważyłem(am), że muszę dodać jedną małą zmianę!
O kurwa, muszę zmienić opis mojego ostatniego commitu!

O kurwa, nie tylko polacy korzystaja z tego slowa

На логотипе git изображено единственное действие, понятное в этой системе.

Спасибо за статью.

Подскажите, как в потоке разработки правильно подходить к обновлению ключевых веток (dev, stage, master):
1. последовательный перенос «функционала» (с тестированием в каждой среде):
feature —> dev —> stage —> master

2. или перенос «функционала» независимо отдельно на каждую ветку:
feature —> dev
feature —> stage
feature —> master

Как в этом случае избежать генерации системных коммитов об merge?

Конкретно у нас в репе нет ключевых веток, только master, а далее деплоится на dev, staging и в конце на прод. Хотфиксы по тому же маршруту идут.

В нормальном мире — никак. Но чисто теоретически можно ребазировать ветки локально (например dev на stage) и смержить stage с dev, и после это напрямую пушить stage на сервер.

Такие merge коммиты бывают полезны если что-то не туда замержили, то проще ревертить один коммит

Подскажите, как в потоке разработки правильно подходить к обновлению ключевых веток (dev, stage, master):

С самого начала, вам стоить выбрать какую-то тактику, и далее ее придержитьваться.

Из «знаменитых» есть
— git flow nvie.com/...​sful-git-branching-model
— git lab flow docs.gitlab.com/...​e/topics/gitlab_flow.html
— github flow guides.github.com/introduction/flow

Подписаться на комментарии