Клавиша / esc
Ветвистое дерево-бонсай растёт из тарелочки и до облаков, одна из веток пересажена и привита в другое место
Иллюстрация: Кира Кустова

Как ребейзить ветку и не думать ни о чём

Чтобы легко управлять вашими изменениями и историей git, используйте git rebase --onto.

Время чтения: 5 мин

Управление историей git

Скопировано

Представим, что у вас есть git-репозиторий с несколькими ветками. Вы работаете в одной из них и хотите перенести свои изменения в другую ветку. Такое приходится делать очень часто, если хотите актуализировать свои изменения.

Рассмотрим несколько распространённых сценариев.

Вы начали разрабатывать фичу и создали ветку my-branch из основной main. За время разработки в ветку main приехали изменения коллег. Как переместить ваши изменения на актуальный main?

Схема веток Гит, где одна ветка отстаёт от главной

В вашей команде есть фронтенд- и бэкенд-разработчики. Вы работаете в одном репозитории, создали ветку frontend из основной ветки main, а ваш коллега — ветку backend тоже из main. В ветке frontend два коммита: Кнопочка 1 и Тултипчик 2, в backend их тоже два — API 1 и API 2. Теперь вы хотите потестировать ваш фронтенд с новым бэкендом. Как это сделать?

Схема двух веток Гит, бэкенд и фронтенд

В вашей ветке есть несколько коммитов, которые нужны коллеге: Кнопочка 1 и Тултипчик 2. Кроме них в ветке есть коммит, который сломает ветку коллеги — Собака-всё-ломака. Как потестировать только не ломающие изменения?

Схема двух веток Гит, где одна содержит коммит, ломающий код из другой ветки

Все эти задачи решаются с помощью команды git rebase --onto.

Немного теории

Скопировано

Git хранит коммиты (commits). Коммит — всего лишь ссылка на некоторое состояние репозитория. Состояние — это немножко метаданных и ссылка на предыдущий коммит. Сам по себе коммит — это 40 символов SHA-1 хеша этого состояния.

Ветка (branch) — это ссылка на определённый коммит. У вас может быть только одна активная ветка. На неё (т. е. на определённый коммит) указывает специальный указатель HEAD.

Многие команды гит принимают в качестве параметра ревизию. Так как всё в гите это захешированные объекты, то в качестве ревизии можете передавать:

  • длинный хеш, например вот так 29aeb39eac6b8d9e0f4cdc1459376599d1aba43c;
  • короткий хеш, например вот так 29aeb39e;
  • тег, если он имеется, например вот так v1.0.0;
  • имя ссылки; в основном используется для веток, например main, origin/dev, refs/heads/foo.

Можете добавлять магические символы (@{n}, ^, :) к этим параметрам, чтобы ещё эффективнее выбирать нужные коммиты. Подробности можно посмотреть в документации про gitrevisions. Особенно полезен символ ^. Он позволяет выбирать предыдущие коммиты.

Как готовить

Скопировано

Подробности можно найти, например, в официальной документации.

Команда git rebase с флагом --onto принимает несколько аргументов:

  • --onto — коммит, на который нужно перенести изменения. Новые коммиты добавятся сразу после него;
  • from — коммит, с которого нужно перенести изменения. Коммит, который укажете, не перенесётся, а следующие за ним перенесутся;
  • to — коммит, до которого нужно перенести изменения. Коммит, который укажете, перенесётся.

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

Например:

        
          
          git rebase --onto main feature1
          git rebase --onto main feature1

        
        
          
        
      

Помните, что коммит — это всего лишь хеш? В качестве каждого аргумента можете указывать хеш, например вот так:

        
          
          git rebase --onto 29aeb39e 5fd88295
          git rebase --onto 29aeb39e 5fd88295

        
        
          
        
      

Хеши, вообще-то, длиннее 8 символов, но git понимает короткую версию. Можете также указать полный хеш, например вот так:

        
          
          git rebase --onto 29aeb39eac6b8d9e0f4cdc1459376599d1aba43c 5fd882953924b47a10794619c3063e7a50257af6
          git rebase --onto 29aeb39eac6b8d9e0f4cdc1459376599d1aba43c 5fd882953924b47a10794619c3063e7a50257af6

        
        
          
        
      

Вы можете указать название ветки вместо хеша, например вот так:

        
          
          git rebase --onto main feature
          git rebase --onto main feature

        
        
          
        
      

Если хотите перенести несколько коммитов из основной ветки, можете использовать магию HEAD нотации.

        
          
          git rebase --onto main HEAD^3 HEAD^
          git rebase --onto main HEAD^3 HEAD^

        
        
          
        
      

Эта команда перенесёт 2 коммита из текущей ветки в ветку main. Самый последний не перенесёт.

Рассмотрим один из примеров выше.

Было так:

Схема двух веток Гит, бэкенд и фронтенд

А нужно, чтобы коммиты Кнопочка 1 и Тултипчик 2 оказались в ветке backend.

Схема Гит, где коммиты из одной ветки перенесены в другую

Для этого выполните команду:

        
          
          git rebase --onto backend main frontend
          git rebase --onto backend main frontend

        
        
          
        
      
  • git rebase --onto — переносим;
  • backend — на последний коммит ветки backend, т. е. на API 2;
  • main — все коммиты в моей ветке frontend, начиная с того, которым заканчивается ветка main (Статья 3);
  • frontend — заканчивая последним коммитом ветки frontend (Тултипчик 2).

Бонус

Скопировано

Если хотите посмотреть какой красивой стала ваша история, используйте эту команду:

        
          
          git log --oneline --decorate --graph --all
          git log --oneline --decorate --graph --all

        
        
          
        
      

На собеседовании

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Владимир Стругов  отвечает

Скопировано

Обе команды нужны для одной цели, но имеют существенные отличия в том, как работают.

git merge объединяет изменения из одной ветки в другую, создавая новый коммит слияния merge commit, который сохраняет полную историю проекта. Это позволяет проследить все коммиты и увидеть, когда и как ветки были объединены, хотя такая история может быть сложнее для чтения. Однако, если целевая ветка не имеет новых коммитов, Git использует fast-forward merge, просто перемещая указатель вперёд без создания дополнительного коммита, что сохраняет линейную и более простую для чтения историю.

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

        
          
          git checkout maingit merge feature
          git checkout main
git merge feature

        
        
          
        
      

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

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

        
          
          git checkout featuregit rebase main
          git checkout feature
git rebase main

        
        
          
        
      

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

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