Хабрахабр

Как и зачем красть деревья в git

trees

Проще говоря, как получить нужное состояние проекта на какой-либо ветке, если это состояние уже когда-то и где-то было в репозитории раньше. В этой статье я расскажу об одном полезном, но малоизвестном приеме работы с git — как можно легко создать коммит, используя дерево из другого коммита. И в частности я расскажу о найденном мной методе, который позволяет значительно упростить исправление множественных конфликтов при rebase. Будет приведено несколько примеров того, как это позволяет элегантно решать некоторые практические задачи. Кроме того, эта статья — отличный способ понять на практике, что из себя представляет коммит в git-е.

Содержание

Про коммиты и деревья
git commit-tree
Практическая часть
1. Теоретическая часть. Сравнение двух веток
3. Синхронизация с другой веткой
2. Частичный реверт
5. Реверт ветки
4. Метод rebase через merge — описание
6b. Искуственный merge
6a. Алиас
Заключение Метод rebase через merge — скрипт
7.

Теоретическая часть. Про коммиты и деревья

Каждый коммит имеет свой уникальный идентификатор в виде хэша, например 5e45ecb. Коммит, наверное, самое основное понятие в git-е, давайте посмотрим из чего он состоит. И с помощью следующей команды, зная хэш, мы можем посмотреть его содержимое.

git cat-file -p 5e45ecb

tree 8640790949c12690fc71f9abadd7b57ec0539376
parent 930741d1f5fd2a78258aa1999bb4be897ba3d015
author Mark Tareshawty <tareby...@github.com> 1542718283 -0500
committer Mark Tareshawty <tareby...@github.com> 1542718283 -0500
gpgsig -----BEGIN PGP SIGNATURE-----
... -----END PGP SIGNATURE----- Fix scoping so that we don't break the naming convention

Эти несколько строчек и есть всё содержимое коммита:

  • tree — ссылка на конкретное состояние проекта
  • parent — ссылка на родительский коммит
  • author — автор оригинального коммита + дата
  • committer — создатель данного коммита + дата
  • gpgsig — цифровая подпись (при наличии)
  • message — текст коммита

Тут все наоборот: каждый коммит описывает конкретное состояние проекта целиком, а то что мы видим как вносимые им изменения — это на самом деле динамически вычисляемая разница в сравнении с предыдущим состоянием. Напомню, что коммит в git-е (в отличие от других VCS) не описывает сделанные изменения. Также стоит отметить, что все коммиты иммутабельны (неизменяемы), то есть, например, при rebase/cherry-pick/amend создаются абсолютно новые коммиты.

Объект типа tree содержит поименный список файлов с конкретным содержимым (blobs) и подпапок (trees). tree (дерево) — по сути это просто папка с конкретным иммутабельным содержимым. А дерево, на которое указыват каждый коммит — это корневая папка проекта, точнее ее определенное состояние.

посмотреть содержимое дерева

Сделать это можно точно так же и для любого другого объекта (commit / tree / blob), при этом достаточно взять первые несколько уникальных симоволов хэша: 8640790949c12690fc71f9abadd7b57ec0539376 -> 8640790.

git cat-file -p 8640790

100644 blob 7ab08294a46f158c51460be3e7df6a190e15023b .env.example
100644 blob 0a1a4d1ad9ff3f35b67678ca893811e91b423af5 .gemset
040000 tree 033aa38ce0eab11fe229067c14ccce95e2b8b601 .github
100644 blob ca49bb7ffa6273b0be4ce7ba1accba456032fb11 .gitignore
100644 blob c99d2e7396e14ac072c63ec8419d9b8fede28d86 .rspec
100644 blob 65e77a2f59f635a8f24eb4714e8e43745c5c0eb9 .rubocop.yml
100644 blob 8e8299dcc068356889b365e23c948b92c6dfcd78 .ruby-version
100644 blob 19028f9885948aca2ba61f9d062e9dc21c21ad03 .stylelintrc.json
100644 blob 2f7a032fbc3f4f7195bfd91cb33889a684b572b9 .travis.yml
100644 blob 121615722a6c206a9fe24b9a1c9b647662a460d2 ARCHITECTURE.md
100644 blob 898195daeea0bbf8c5930deeaf1020ba8abab34a Gemfile
100644 blob de7ca707f9fe9172db941b65cdacaba7e024fc06 Gemfile.lock
100644 blob e6ff62fefd071b1a8ca279bae94ddbc4dd17b7a3 Gruntfile.js
100644 blob 0cac5b30fb32d36cce2aeb7d936be7b6207d68c7 MIT-LICENSE.txt
100644 blob c2c566e8cc3d440d3ee8041b79cded416db28136 Procfile
100644 blob d1fb2f575380e1e093a4d82e3f19e51f0b99a0a1 Procfile.dev
100644 blob 3a88e138f10fa65bd2cfe1a1d3292348205508b5 README.md
100644 blob 5366e6e073cc426518894cc379d3a07cf3c9cfb3 Rakefile
100644 blob e6d3d2d3e9d5122c5f75bbeee8ed0917ad38c131 app.json
040000 tree 94f83cf03bd6f1cf14672034877b14604744b7a2 app
040000 tree d4d859e82564250b4c4f2047de21e089e7555475 bin
100644 blob 1f71007621f17334fd6f2dd71c87b7a16867119c config.ru
040000 tree 9e8e4bf5ec44541aefff544672b94ca8a9d07bbf config
040000 tree 31b8d0e1fa2bb789dbd6319e04fc9f115952cf2a db
040000 tree 38e7a13e0e772c2a13e46d2007e239f679045bee doc
040000 tree a6e35ded8b35837660cf786e637912377f845515 lib
040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e log
100644 blob 843523565ddee5e00f580d9c4e37fc2478fdaecc package-lock.json
100644 blob 791ee833ad316d75b1d2c83a64a3053fc952d254 package.json
040000 tree 4645317c52675d9889f89b26f4dd4d2ae1d8cbad public
040000 tree 31d3f8ae4a4ffe62787134642743ed32a35dbae2 resources
040000 tree 807ffa29868ef9c25ddb4b4126a4bb7f1b041bf0 script
040000 tree 4c3bf9a7f3679ba059b0f1c214a500d197546462 spec
040000 tree 136c8174412345531a9542cafef25ce558d2664f test
040000 tree e6524eafe066819e4181bc56c503320548d8009b vendor

Точно так же как и хэши вложенных в него объектов (trees, blobs). На самом деле это важнейшая особенность того, как работает git, идентификатор коммита — это действительно хэш от его содержимого.

Теперь посмотрим что происходит когда мы делаем

git commit -m "Fixed bug"

Эта команда создает новый коммит который фиксирует в себе следующее:

  • состояние проекта staging (сохраняется как новый объект tree и берется его хэш)
  • ссылку на текущий (родительский) коммит
  • автора + коммитера + две даты
  • текст коммита

И команда автоматически поднимает на него указатель текущей ветки. Это все сохраяется, хэшируется, и получается новый объект commit.

немного про терминологию

Как мы уже знаем, tree — это объект, который содержит состояние проекта целиком на какой-то определенный момент в прошлом — когда был создан коммит с этим деревом.

Рабочую папку называют working tree / working copy / working directory, что вполне логично.

Но логически это тоже дерево, точнее то состояние, которое сохраняется при коммите как дерево. Также у нас есть — staging area / index — область подготовленных изменений. Поэтому, мне кажется, более логичным это было бы называть staged tree.

git commit-tree

Формально это одна из низкоуровневых команд, поэтому она редко упоминается и используется. И наконец мы можем перейти к описанию нужной нам команды git commit-tree. Нам интересно только одно частное следствие: с помощью этой команды мы можем легко скопировать (переиспользовать) дерево состояния проекта из любого другого коммита. Мы не будем рассматривать связанные с ней прочие низкоуровневые команды (такие как git write-tree, git-update-index, они также известны как plumbing commands).

Посмотрим на ее вызов

git commit-tree 4c835c2 -m "Fixed bug" -p a8fc5e3

d9aded78bf57ca906322e26883644f5f36cfdca5

Здесь необходимо явно указать уже существующее дерево (tree) 4c835c2 и ссылку на родительский коммит a8fc5e3. Команда git commit-tree тоже создает коммит, но низкоуровневым способом. И она возвращает хэш нового коммита d9aded7, а положение ветки не меняет (поэтому этот коммит как бы зависает в воздухе).

Практическая часть

Примеры использования этой команды демонстрируются на следующем простом репозитории.

Он содержит три ветки:

master — основная ветка
alpha — ветка, на которой мы работаем и находимся
beta — ветка, которая уже была ранее смерджена в master

Это исходное состояние — общее для всех примеров. Все действия легко повторить локально, для этого достаточно клонировать репозиторий, встать на ветку alpha и далее исполнять команды из примеров.

git clone https://github.com/capslocky/git-commit-tree-example.git
cd ./git-commit-tree-example/
git checkout alpha

под windows

Только нужно открыть bash терминал в папке проекта, например, так Все команды, включая скрипт, работают и под windows.

"C:\Program Files\Git\git-bash.exe" --cd="D:\path\project"

1. Синхронизация с другой веткой

Задача:

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

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

Так как это первый пример, вся его логика рассмотрена достаточно подробно. Самое простое решение заключается в том, чтобы взять существующее дерево, на которое указывает ветка beta, и просто указать на него из нового коммита для ветки alpha.

Для начала найдем хэш коммита, на который указывает ветка beta:

git rev-parse origin/beta

280c30ff81a574f8dd41721726cf60b22fb2eced

280c30f — достаточно взять несколько первых символов

Теперь найдем хэш его дерева, отобразив содержимое коммита через git cat-file:

git cat-file -p 280c30f

tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <---
parent 560b449675513bc8f8f4d6cda56a922d4e36917a
author Baur <atanov...@gmail.com> 1540619512 +0600
committer Baur <atanov...@gmail.com> 1540619512 +0600 Added info about windows

3c1afe7 — это и есть нужное нам дерево

И теперь создадим коммит, указывающий на это дерево, а родительским коммитом укажем текущий коммит:

git commit-tree 3c1afe7 -m "Synced with branch 'beta'" -p HEAD

eb804d403d4ec0dbeee36aa09da706052a7cc687

Причем это значение будет всегда уникально, ведь оно вычисляется не только от дерева, но и от автора и времени. Всё, коммит создан, команда вывела его хэш. Нам достаточно взять несколько первых символов: eb804d4, это уникальное для каждого случая значение я обозначу как xxxxxxx. Сам коммит завис в воздухе, так как пока он не входит ни в одну из веток. Давайте посмотрим на его содержимое:

git cat-file -p xxxxxxx

tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <---
parent 64fafc79e8f6d22f5226490daa5023062299fd6c
author Peter <peter...@gmail.com> 1545230299 +0600
committer Peter <peter...@gmail.com> 1545230299 +0600 Synced with branch 'beta'

И так как этот коммит является прямым потомком текущей ветки, то чтобы включить его в ветку, достаточно просто сделать fast-forward merge Отлично, у него такое же дерево, как у коммита на ветке origin/beta.

git merge --ff xxxxxxx

Updating 64fafc7..xxxxxxx
Fast-forward Azure.txt | 3 --- Bill.txt | 6 +----- Linus.txt | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 8 deletions(-)

Теперь состояние проекта на ветке alpha в точности такое же, как на ветке beta. Готово. [update] А если посмотреть на то, что же все-таки изменил этот коммит, мы увидим: он ревертнул все собственные коммиты (изменения) ветки alpha и добавил все уникальные коммиты (изменения) ветки beta, относительно их общего коммита-предка.

2. Сравнение двух веток

Задача:

Сравнить ветку alpha с веткой beta.

Это его свойство позволяет легко сравнить одну ветку с другой. Из первого примера видно, что созданный коммит показывает все те изменения, которые являются фактической разницей между этими двумя ветками. Достаточно создать третью временную ветку и в ней сделать подобный коммит.

Итак, сначала вернем ветку alpha к исходному состоянию

git reset --hard origin/alpha

Создадим на текущем коммите ветку temp и встанем на нее

git checkout -b temp

Но в этот раз мы уложимся в одну строчку. И нам осталость сделать то же, что и в предыдущем примере. Для этого мы воспользуемся специальным синтаксисом для обращения к дереву коммита origin/beta^ или что то же самое 280c30f^{tree}.

git merge --ff $(git commit-tree origin/beta^{tree} -m "Diff with branch 'beta'" -p HEAD)

Готово, по сути мы материализовали в виде коммита дифф между двумя ветками

git show

git diff alpha origin/beta

Разумеется, такой "сравнительный" коммит мы можем создать вообще для любых двух коммитов (состояний) в репозитории.

3. Реверт ветки

Задача:

Откатить несколько последних коммитов

Вернемся на ветку alpha и удалим ветку temp

git checkout alpha
git branch -D temp

Есть два классических способа это сделать: Предположим, что нам нужно откатить два последних коммита на ветке alpha.

  1. Два раза выполнить git revert — на каждый коммит
  2. git reset, то есть сброс положения ветки

Но можно это сделать и третим способом:

git merge --ff $(git commit-tree 7a714bf^{tree} -m "Reverted to commit 7a714bf" -p HEAD)

В отличие от первого способа, создается всего один коммит, даже если надо откатить десять последних коммитов. Это добавит один новый коммит, который как бы откатывает изменения двух предыдущих коммитов. А отличие способа с git reset заключается в том, что мы не выкидываем эти коммиты из самой ветки.

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

git merge --ff $(git commit-tree 64fafc7^{tree} -m "Reverted back to commit 64fafc7" -p HEAD)

При этом в истории ветки останутся эти два коммита, по которым будет видно, что ее откатывали и возвращали назад.

4. Частичный реверт

Задача:

Откатить изменения в некоторых файлах за несколько последних коммитов.

Снова вернем ветку alpha к исходному состоянию

git reset --hard origin/alpha

Предположим, что нам нужно откатить изменения в файле Bill.txt за последние 2 коммита, при этом не трогая какие-либо другие файлы. Ветка alpha содержит 3 коммита, в каждом из которых вносятся изменения в файле Bill.txt, в последнем коммите также добавляется файл Azure.txt.

Для начала откатим все файлы на 2 коммита назад

git merge --ff $(git commit-tree 7a714bf^{tree} -m "any text" -p HEAD)

Далее вернем ветку на предыдущий коммит, но не трогая состояние проекта на диске

git reset HEAD~1

И теперь достаточно застейджить нужные файлы и сделать коммит, а прочие изменения можно отбросить.

git add Bill.txt
git commit -m "Reverted file Bill.txt to 7a714bf"
git reset --hard HEAD

5. Искуственный merge

Задача:

Смерджить одну ветку в другую, получив заранее предопределенный результат.

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

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

А кто в кого смерджен определяется просто — первый parent коммит считается главным. Для начала вспомним вообще, что такое merge commit — на самом деле это тот же самый обычный коммит, но просто у него два родительских коммита, это хорошо видно, если посмотреть на его содержимое (то, как формируется его дерево — это уже отдельный вопрос).

git cat-file -p 7229df8

tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a
parent fd54ab7dde87593b9892b6d1ffbf1afd39ba6f9e
parent 280c30ff81a574f8dd41721726cf60b22fb2eced
author Baur <atanov...@gmail.com> 1540619579 +0600
committer Baur <atanov...@gmail.com> 1540619592 +0600 Merge branch 'beta' into 'master'

Сбросим к исходному состоянию текущую ветку alpha и переключимся на master

git reset --hard origin/alpha
git checkout master

И теперь та же команда, но уже с двумя родительскими коммитами

git merge --ff $(git commit-tree alpha^{tree} -m "Merge 'alpha' into 'master', but take 'alpha' tree" -p HEAD -p alpha)

Готово, мы смерджили ветку alpha в master, и нам не пришлось удалять временный код и решать конфликты, так как в данном случае нужно было просто перезаписать последние изменения.

Такие ситуации могут возникнуть, потому что git это очень гибкий инструмент, который позволяет реализовать самые разные подходы для работы с ветками и репозиториями. На самом деле это не так уж и странно — создавать merge с деревом, которое копируется из другого коммита. Но все же самый банальный пример был в нашем репозитории с самого начала — попробуйте его разобрать самостоятельно или откройте спойлер, чтобы прочитать объяснение.

спойлер

За то время, когда в ней появилось два коммита, в самой ветке master — новых коммитов не было. Обратим внимание на то, как ветка beta была смерджена обратно в master. Это значит, что при мердже beta в master исключены какие-либо конфликты из-за одновременных изменений.


Было бы вот так: Если бы мы сделали git merge beta — то произошел бы fast-forward merge (поведение по умолчанию), то есть ветка master просто встала бы на том же коммите, где находится ветка beta, и никакого мердж коммита не было бы.

То есть мы форсировали создание мердж коммита, хотя он был и не обязателен. Но тут был сделан no-fast-forward merge с помощью команды git merge beta --no-ff. И так как нужное конечное состояние проекта для будущего мерджа было известно — это дерево ветки beta, то git просто скопировал ссылку на это дерево в новый коммит:

git cat-file -p origin/beta

tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <---
parent 560b449675513bc8f8f4d6cda56a922d4e36917a
author Baur <atanov...@gmail.com> 1540619512 +0600
committer Baur <atanov...@gmail.com> 1540619512 +0600 Added info about windows

git cat-file -p 7229df8

tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <---
parent fd54ab7dde87593b9892b6d1ffbf1afd39ba6f9e
parent 280c30ff81a574f8dd41721726cf60b22fb2eced
author Baur <atanov...@gmail.com> 1540619579 +0600
committer Baur <atanov...@gmail.com> 1540619592 +0600 Merge branch 'beta' into 'master'

6a. Метод rebase через merge — описание

Задача:

Нужно сделать "тяжелый" rebase ветки (множество конфликтов на разных коммитах)

Но холиварить я не буду. Есть такая классическая холиварная тема в git — rebase vs merge. Наоборот, я расскажу о том, как их можно подружить в контексте указанной задачи.

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

Если мы как обычно начнем делать rebase, первый и последний коммит породят два разных конфликта в двух разных местах. Итак, нам нужно сделать rebase ветки alpha на develop, чтобы потом смерджить ее так же красиво, как была смерджена ветка beta. Если хочется убедиться в этом, предлагаю готовые команды под спойлером. Но если бы мы вместо rebase просто делали merge, то был бы всего один конфликт в одном месте в одном мердже коммите.

Скрытый текст

Возвращаем ветки в исходное состояние

git checkout master
git reset --hard origin/master
git checkout alpha
git reset --hard origin/alpha

Создадим и встанем на ветку alpha-rebase-conflicts и сделаем ребейз на master

git checkout -b alpha-rebase-conflicts
git rebase master

Будут конфликты на разных коммитах, включая "фантомный" конфликт.

А теперь попробуем мердж, вернемся на ветку alpha и удалим ветку для ребейза.

git checkout alpha
git branch -D alpha-rebase-conflicts

Переключимся на master и сделаем мердж

git checkout master
git merge alpha

Будет всего один простой конфликт, исправляем его и делаем

git add Bill.txt
git commit -m "Merge branch 'alpha' into 'master'"

Мердж успешно завершен.

В реальном проекте эта разница измеряется гораздо большим объемом потраченного времени и нервов. git-конфликты это естественная часть нашей жизни, и этот простой пример показывает, что merge в этом отношении однозначно удобнее rebase. Поэтому, например, существует неоднозначная рекомендация сквошить перед мерджем все коммиты фича-ветки в один коммит.

Запомнить результат (tree). Идея этого метода заключается в том, чтобы сделать временный скрытый merge, в котором мы за один раз разрешим все конфликты. И в конце добавить на ветку один дополнительный коммит, который восстановит корректное дерево. Далее запустить обычный rebase, но с опцией "автоматически разрешать конфликты, выбирая наши изменения".

Снова вернем обе ветки в исходное состояние. Приступим.

git checkout master
git reset --hard origin/master
git checkout alpha
git reset --hard origin/alpha

Cоздадим временную ветку temp, в которой сделаем merge.

git checkout -b temp
git merge origin/master

Конфликт.

Резолвим один простой конфликт в файле Bill.txt как обычно (в любом редакторе).
Обратим внимание, что тут всего один конфликт, а не два, как при rebase.

git add Bill.txt
git commit -m "Merge branch 'origin/master' into 'temp'"

Возвращаемся на ветку alpha, делаем rebase с автоматическим разрешением всех конфликтов в нашу пользу, и приводим к состоянию ветки temp, а саму ветку temp удаляем.

git checkout alpha
git rebase origin/master -X theirs
git merge --ff $(git commit-tree temp^{tree} -m "Fix after rebase" -p HEAD)
git branch -D temp

И наконец красиво мерджим alpha в master.

git checkout master
git merge alpha --no-ff --no-edit

Отметим, что master, alpha и удаленная ветка temp — все три указывают на одно и то же дерево, хотя это три разных коммита.

Минусы данного метода:

  • Нет ручного исправления каждого конфликтного коммита — конфликты решаются автоматом. Такой промежуточный коммит может и не скомпилироваться.
  • Добавляется (но не всегда) дополнительный коммит при каждом rebase

Плюсы:

  • Мы исправляем только реальные конфликты (никаких фантомных конфликтов)
  • Исправление всех конфликтов происходит только один раз
  • Первые два пункта дают экономию времени
  • Сохраняется полная история коммитов и всех изменений (можно, например, сделать cherry-pick)
  • Метод реализован в виде скрипта и может использоваться всегда при необходимости сделать rebase (при этом не требуется каких-либо знаний про деревья и прочее)

6b. Метод rebase через merge — скрипт

Скрипт опубликован здесь: https://github.com/capslocky/git-rebase-via-merge

Снова вернем обе ветки в исходное состояние Проверим его работу на нашем примере.

git checkout master
git reset --hard origin/master
git checkout alpha
git reset --hard origin/alpha

Скачиваем скрипт и делаем его исполняемым

curl -L https://git.io/rebase-via-merge -o ~/git-rebase-via-merge.sh
chmod +x ~/git-rebase-via-merge.sh

windows

Файл появится тут: C:\Users\user-name\git-rebase-via-merge.sh

Меняем ветку по умолчанию, на которую нужно делать ребейз, в нашем случае нужен origin/master

nano ~/git-rebase-via-merge.sh

default_base_branch='origin/master'

Давайте также создадим и встанем на временную ветку, чтобы не трогать саму ветку alpha

git checkout -b alpha-rebase-test

И теперь можно запустить скрипт (вместо традиционного git rebase origin/master)

~/git-rebase-via-merge.sh

результат работы скрипта

$ ~/git-rebase-via-merge.sh
This script will perform rebase via merge. Current branch:
alpha-rebase-test (64fafc7) Base branch:
origin/master (9c6b60a) Continue (c) / Abort (a)
c Auto-merging Bill.txt
CONFLICT (content): Merge conflict in Bill.txt
Automatic merge failed; fix conflicts and then commit the result. You have at least one merge conflict. Fix all conflicts in the following files, stage them up and type 'c':
Bill.txt
Continue (c) / Abort (a)
c [detached HEAD 785d49e] Hidden temp commit to save result of merging 'origin/master' into 'alpha-rebase-test' as detached head.
Merge succeeded on hidden commit:
785d49e Starting rebase automatically resolving any conflicts in favor of current branch. First, rewinding head to replay your work on top of it... Auto-merging Bill.txt
[detached HEAD a680316] Added history of windows Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 11:45:50 2018 +0600 1 file changed, 6 insertions(+), 3 deletions(-)
Committed: 0001 Added history of windows Auto-merging Bill.txt
[detached HEAD dcd34a8] Replaced history of windows Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 11:55:42 2018 +0600 1 file changed, 4 insertions(+), 5 deletions(-)
Committed: 0002 Replaced history of windows Auto-merging Bill.txt
[detached HEAD 8d6d82c] Added file about Azure and info about Windows 10 Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 12:06:27 2018 +0600 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Azure.txt Committed: 0003 Added file about Azure and info about Windows 10 All done. Restoring project state from hidden merge with single additional commit. Updating 8d6d82c..268b320
Fast-forward Bill.txt | 4 ++++ 1 file changed, 4 insertions(+) Done.

Базовую ветку также можно указать явно

~/git-rebase-via-merge.sh origin/develop

Также стоит упомянуть, что ours / theirs в конфликтах становятся более интуитивными:

ours — это наши изменения на текущей ветке (HEAD)
theirs — это изменения из другой ветки (например, origin/develop)

В то время как при rebase — у них противоположное значение.

7. Алиас

С помощью следующего git алиаса копирование дерева становится очень простой командой.

Создаем алиас в конфиге текущего репозитория.

git config alias.copy '!git merge --ff $(git commit-tree ${1}^{tree} -p HEAD -m "Tree copy from ${1}")'

И теперь достаточно выполнить git copy xxx, где xxx — любая ссылка на коммит.

git copy xxx

Это будет то же самое что и

git merge --ff $(git commit-tree xxx^{tree} -p HEAD -m "Tree copy from xxx")

Примеры

git copy a8fc5e3

git copy origin/beta

git copy HEAD~3

А исправить стандартный текст созданного коммита на произвольный можно через git amend.

git commit --amend -m "Just for test"

Или запустив редактор:

git commit --amend

Заключение

Иногда делаю сравнение веток, тегов и коммитов. По правде говоря, из описанного я сам регулярно пользуюсь только данным методом для ребейза, очень хорошо помогает на текущем проекте. Для большинства привиденные примеры, наверное, не будут актуальны. Остальное — единичные случаи. А приемчики "как украсть дерево" или "сделать ребейз без боли" могут однажды пригодится. Тем не менее знать про коммиты и деревья будет всегда полезно. Если есть идеи зачем еще можно красть деревья (в частности, в практиках devops), пишите, будет интересно обсудить.

Теги
Показать больше

Похожие статьи

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть