The git rebase command has a reputation for being magical Git voodoo that beginners should stay away from, but it can actually make life much easier for a development team when used with care. In this article, we’ll compare git rebase with the related git merge command and identify all of the potential opportunities to incorporate rebasing into the typical Git workflow.

Conceptual Overview

První věcí, kterou je ohledně příkazu git rebase třeba pochopit, je, že řeší stejný problém jako příkaz git merge. Oba tyto příkazy jsou navrženy k integraci změn z jedné větve do druhé. Liší se jen způsob, jak to provádějí.

Zamyslete se nad tím, co se stane, když začnete pracovat na nové feature ve vyhrazené větvi a jiný člen týmu aktualizuje větev master novými commity. Vznikne tím rozvětvená historie, která je dobře známá každému, kdo někdy spolupracoval pomocí Gitu.

A forked commit history

Nyní řekněme, že jsou nové commity ve větvi master relevantní k feature, na které právě pracujete. Chcete-li nové commity do větve své feature začlenit, máte dvě možnosti: merge nebo rebasing.

The Merge Option

The easiest option is to merge the master branch into the feature branch using something like the following:

git checkout feature
git merge master

Or, you can condense this to a one-liner:

git merge feature master

This creates a new “merge commit” in the feature branch that ties together the histories of both branches, giving you a branch structure that looks like this:

Merging master into the feature branch

Výhoda merge spočívá v tom, že se jedná o neškodnou operaci. Existující větve se žádným způsobem nezmění. Tím se vyhnete všem potenciálním nástrahám rebasingu (viz níže).

Na druhou stranu to také znamená, že se u větve feature provede další commit merge pokaždé, když budete chtít začlenit předchozí změny. Pokud je ve větvi master velká aktivita, může se tím značně zaneřádit historie větve vaší feature. I když je tento problém možné zmírnit díky pokročilým možnostem příkazu git log, může to dalším vývojářům znesnadnit pochopení historie projektu.

The Rebase Option

As an alternative to merging, you can rebase the feature branch onto master branch using the following commands:

git checkout feature
git rebase master

Tento příkaz přesune celou větev feature na začátek špičky větve master a efektivně do větve master začlení všechny nové commity. Narozdíl od commitu merge se však při rebasingu přepíše historie projektu. Provede se to tak, že se vytvoří zcela nové commity pro každý commit v původní větvi.

Rebasing the feature branch onto master

Hlavní výhodou rebasingu je mnohem čistší historie projektu. V první řadě se eliminují všechny zbytečné commity merge vyžadované příkazem git merge. Za druhé (jak můžete vidět v diagramu výše) rebasing také vytváří dokonale lineární historii projektu – můžete sledovat špičku feature až k začátku projektu bez jakéhokoli forku (větvení). Díky tomu je navigace v projektu pomocí příkazů jako git log, git bisect a gitk o poznání snazší.

Tato jasná historie commitů má však i dvě stinné stránky: bezpečnost a sledovatelnost. Pokud se neřídíte zlatým pravidlem rebasingu, může mít přepisování historie projektu pro váš workflow spolupráce katastrofální důsledky. Navíc se při rebasingu ztrácí kontext, který poskytuje commit merge: nevidíte, kdy byly předchozí změny do feature začleněny.

Interactive Rebasing

Interaktivní rebasing vám dává možnost měnit commity při jejich přesunu do nové větve. Je to ještě mocnější funkce než automatický rebasing, protože vám dává úplnou kontrolu nad historií commitů větve. Obvykle se používá k vyčištění nepřehledné historie před provedením merge větve feature do větve master.

To begin an interactive rebasing session, pass the i option to the git rebase command:

git checkout feature
git rebase -i master

This will open a text editor listing all of the commits that are about to be moved:

pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

This listing defines exactly what the branch will look like after the rebase is performed. By changing the pick command and/or re-ordering the entries, you can make the branch’s history look like whatever you want. For example, if the 2nd commit fixes a small problem in the 1st commit, you can condense them into a single commit with the fixup command:

pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

When you save and close the file, Git will perform the rebase according to your instructions, resulting in project history that looks like the following:

Squashing a commit with an interactive rebase

Když odstraníte takovéto nedůležité commity, bude historie vaší feature mnohem srozumitelnější. Něco takového příkaz git merge prostě neumí.

The Golden Rule of Rebasing

Jakmile pochopíte, co to rebasing vůbec je, musíte se naučit to nejdůležitější: kdy ho neprovádět. Zlatým pravidlem příkazu git rebase je nikdy jej nepoužívat u veřejných větví.

Zamyslete se například nad tím, co by se stalo, kdybyste provedli rebasing větve master do větve své feature:

Rebasing the master branch

The rebase moves all of the commits in master onto the tip of feature. The problem is that this only happened in your repository. All of the other developers are still working with the original master. Since rebasing results in brand new commits, Git will think that your master branch’s history has diverged from everybody else’s.

Jediný způsob, jak by bylo možné tyto dvě větve master synchronizovat, by bylo provést jejich merge. Tím by vznikl dodatečný commit merge a k tomu dvě sady commitů, které by obsahovaly stejné změny (původní změny a změny z větve, u níž byl proveden rebasing). Není třeba dodávat, že se jedná o velmi nepřehlednou situaci.

So, before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking about a non-destructive way to make your changes (e.g., the git revert command). Otherwise, you’re safe to re-write history as much as you like.

Force-Pushing

Pokud se pokusíte provést push větve master, u které byl proveden rebasing, zpět do vzdáleného repozitáře, Git vám v tom zabrání kvůli konfliktu se vzdálenou větví master. Push však můžete vynutit zadáním příznaku --force, například takto:

# Be very careful with this command!
git push --force

Přepíše se tím vzdálená větev master tak, aby odpovídala té z vašeho repozitáře, u které byl proveden rebasing. Pro zbytek vašeho týmu to může být velmi matoucí. Buďte proto velmi opatrní a používejte tento příkaz jen tehdy, když budete naprosto přesně vědět, co děláte.

Jedním z mála případů, kdy je vhodné použít vynucený push, je provádění místního čištění po provedení push soukromé větve feature do vzdáleného repozitáře (například za účelem zálohy). Je to jako říct: „Jejda, provést push originální verze větve feature jsem vlastně nechtěl. Vezměte si místo ní tu aktuální.“ Opět připomínáme, že je důležité, aby na základě commitů z původní verze větve feature nikdo dále nepracoval.

Workflow Walkthrough

Rebasing can be incorporated into your existing Git workflow as much or as little as your team is comfortable with. In this section, we’ll take a look at the benefits that rebasing can offer at the various stages of a feature’s development.

The first step in any workflow that leverages git rebase is to create a dedicated branch for each feature. This gives you the necessary branch structure to safely utilize rebasing:

Developing a feature in a dedicated branch

Local Cleanup

One of the best ways to incorporate rebasing into your workflow is to clean up local, in-progress features. By periodically performing an interactive rebase, you can make sure each commit in your feature is focused and meaningful. This lets you write your code without worrying about breaking it up into isolated commits—you can fix it up after the fact.

Při použití příkazu git rebase máte dvě možnosti nové báze: nadřazenou větev dané feature (například master) nebo dřívější commit vaší feature. Příklad první možnosti jsme viděli v části Interaktivní rebasing. Druhá možnost se hodí v případě, že potřebujete opravit jen několik posledních commitů. U následujícího příkazu se například provede interaktivní rebasing jen pro poslední tři commity.

git checkout feature
git rebase -i HEAD~3

Když určíte HEAD~3 jako novou bázi, ve skutečnosti větev nepřesouváte – pouze interaktivně přepisujete tři commity, které po ní následují. Nezapomeňte, že se v tomto případě nezačlení předchozí změny do větve feature.

Rebasing onto Head~3

Pokud chcete pomocí této metody přepsat celou feature, můžete k nalezení původní báze větve feature použít příkaz git merge-base. Následující příkaz vrátí ID commitu původní báze, které pak můžete použít u příkazu git rebase:

git merge-base feature master

Takovéto použití interaktivního rebasingu je skvělým způsobem, jak do workflow začlenit příkaz git rebase, protože ovlivňuje pouze místní větve. Jediná věc, kterou vývojáři uvidí, je dokončený produkt, kterým je čistá a snadno sledovatelná historie větve feature.

Opět však platí, že to funguje jen u soukromých větví feature. Pokud v rámci stejné větve feature spolupracujete s dalšími vývojáři, je tato větev veřejná a její historii nemůžete přepsat.

Žádná alternativa příkazu git merge k vyčištění místních commitů pomocí interaktivního rebasingu neexistuje.

Incorporating Upstream Changes Into a Feature

In the Conceptual Overview section, we saw how a feature branch can incorporate upstream changes from master using either git merge or git rebase. Merging is a safe option that preserves the entire history of your repository, while rebasing creates a linear history by moving your feature branch onto the tip of master.

This use of git rebase is similar to a local cleanup (and can be performed simultaneously), but in the process it incorporates those upstream commits from master.

Mějte na paměti, že je povoleno provést rebasing do vzdálené větve místo větve master. To se může stát, když na stejné feature spolupracujete s jiným vývojářem a potřebujete do svého repozitáře začlenit jeho změny.

For example, if you and another developer named John added commits to the feature branch, your repository might look like the following after fetching the remote feature branch from John’s repository:

Collaborating on the same feature branch

Toto rozvětvení fork můžete vyřešit úplně stejně, jako když integrujete předchozí změny z větve master: buď provedete merge své místní feature s větví john/feature nebo provedete rebasing své feature na špičku větve john/feature.

Merging vs. rebasing onto a remote branch

Note that this rebase doesn’t violate the Golden Rule of Rebasing because only your local feature commits are being moved—everything before that is untouched. This is like saying, “add my changes to what John has already done.” In most circumstances, this is more intuitive than synchronizing with the remote branch via a merge commit.

By default, the git pull command performs a merge, but you can force it to integrate the remote branch with a rebase by passing it the --rebase option.

Reviewing a Feature With a Pull Request

Pokud v rámci procesu kontroly kódu používáte pull requesty, nemůžete po vytvoření pull requestu příkaz git rebase použít. Jakmile vytvoříte pull request, budou se ostatní vývojáři moci dívat na vaše commity, a tím pádem bude vaše větev veřejná. Pokud přepíšete její historii, bude pro Git a vaše kolegy nemožné sledovat jakékoli následné commity, které k feature přidáte.

Všechny změny, které provedou jiní vývojáři, je nutné začlenit pomocí příkazu git merge, nikoli git rebase.

Z tohoto důvodu je obvykle vhodné vyčistit kód před odesláním pull requestu interaktivním rebasingem.

Integrating an Approved Feature

Jakmile váš tým feature schválí, budete u ní moci provést rebasing na špičku větve master (před integrováním feature do hlavní báze kódu pomocí příkazu git merge).

Jedná se o podobnou situaci, jako když začleňujete předchozí změny do větve feature, s tím rozdílem, že nemůžete přepisovat commity ve větvi master, a tak k integraci feature musíte nakonec použít příkaz git merge. Provedením rebasingu před merge však zajistíte, že merge bude rychlý a povede k dokonale lineární historii. Také tím získáte možnost provést squash pro všechny předchozí commity přidané během pull requestu.

Integrating a feature into master with and without a rebase

Pokud si nejste ohledně používání příkazu git rebase zcela jistí, můžete rebasing vždy provádět v dočasné větvi. Díky tomu můžete v případě, že omylem pokazíte historii své feature, provést checkout původní větve a zkusit to znovu. Například:

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch

Souhrn

To je vše, co potřebujete vědět, pokud chcete začít provádět rebasing svých větví. Pokud preferujete čistou lineární historii bez zbytečných commitů merge, měli byste při integraci změn z jiné větve použít příkaz git rebase, nikoli git merge.

On the other hand, if you want to preserve the complete history of your project and avoid the risk of re-writing public commits, you can stick with git merge. Either option is perfectly valid, but at least now you have the option of leveraging the benefits of git rebase.