Chapter 4 of 5

Git tutorial: teamwork with branches, merge and rebase

verified on 7 September 2026 · 10 min

Quick answer

Create a branch with git switch -c nom, do the work, then merge it with git merge from the destination branch. When there is a conflict, Git inserts markers in the file: write the correct content, delete the markers, then git add and git commit. git merge --abort undoes the whole thing at any point.

A branch is a parallel line of development. It lets you work on a feature without touching the stable version, and lets a colleague do the same on their side. This chapter covers the full cycle: creating a branch, merging it, resolving a conflict by hand, and choosing between merge and rebase.

git switch rather than git checkout

Historically, git checkout did everything: switching branches, creating one, restoring a file, moving onto a specific commit. That pile-up of roles is the first source of confusion for beginners, and a source of accidents for everyone else.

Git 2.23, released in August 2019, split the command in two: git switch to move between branches, git restore to undo changes. Both were marked experimental when they shipped, the official documentation no longer says so. This chapter uses them, and always gives the checkout equivalent, which you will keep meeting in tutorials and on Stack Overflow for a long time yet.

Intent Modern command Older form
Switch branch git switch nom git checkout nom
Create a branch and move onto it git switch -c nom git checkout -b nom
Go back to the previous branch git switch - git checkout -
Undo the changes made to a file git restore fichier git checkout -- fichier
Take a file out of the index git restore --staged fichier git reset HEAD fichier

Creating a branch and working on it

bash
git switch -c feature/panier
Sortie réelle · git switch -c
Switched to a new branch 'feature/panier'

The branch starts from the commit you were on. You can now edit, add and commit as usual: none of it will show up on main until you merge it.

Git imposes no rule on branch names, but the prefixes feature/, fix/ and hotfix/ are a widespread convention, and hosting platforms group them visually in their interfaces.

To see where you stand:

bash
git branch              # the local branches
git branch -vv          # with the last commit and the remote branch being tracked
git branch -a           # local and remote
git switch -            # back to the previous branch

To publish the branch and make it visible to the team:

bash
git push -u origin feature/panier

Picking up a colleague’s branch

The other way round, git fetch updates what you know about the server without changing anything in your own branches:

bash
git fetch origin feature/collegue   # one specific branch
git fetch --all                     # every remote
git branch -r                       # what you know about the server
Sortie réelle · git branch -r après un fetch
  origin/HEAD -> origin/main
  origin/feature/collegue
  origin/main

All that is left is to switch onto it. Git creates the local branch and links it to the one on the server for you:

bash
git switch feature/collegue
Sortie réelle · git switch sur une branche distante
branch 'feature/collegue' set up to track 'origin/feature/collegue'.
Switched to a new branch 'feature/collegue'

When Git refuses to switch branches

This is one of the first errors you will run into:

Sortie réelle · git switch avec des modifications non committées
error: Your local changes to the following files would be overwritten by checkout:
	index.html
Please commit your changes or stash them before you switch branches.
Aborting

Git is protecting your work: the file you have modified does not have the same content on the other branch, and switching would overwrite it. Three ways out, in order of preference.

Commit, if the work holds together. This is almost always the right answer, an imperfect commit on a working branch costs nothing and can be fixed later with the undo commands covered in the previous chapter.

Stash, if the work is half done:

bash
git stash          # stashes the changes
git switch main    # …you do whatever you had to do…
git switch -
git stash pop      # brings the changes back
Sortie réelle · git stash
Saved working directory and index state WIP on main: 6d495d9 Page d accueil

Throw it away, with git restore ., if the changes were worth nothing. With no way back.

Merging a branch

Once the feature is finished, move onto the destination branch and merge:

bash
git switch main
git merge feature/panier

When the two branches have changed different files, Git merges on its own and has nothing to ask you. The interesting case is the other one.

Resolving a conflict

Here is the situation set up for this chapter. On feature/panier, the second line of index.html was replaced by an empty-basket message. On main, the first line was changed to add the brand name. Both branches touched the same file.

Sortie réelle · git merge feature/panier
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Nothing is broken. Git merged what it could and leaves the rest for you to settle. First thing to do, ask where you stand:

bash
git status
Sortie réelle · git status pendant un conflit
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   index.html

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

To get only the list of files to deal with:

bash
git diff --name-only --diff-filter=U

Open the file. Git has inserted markers in it:

index.html pendant le conflit
<<<<<<< HEAD
<h1>Boutique Gekkode</h1>
<p>Bienvenue sur la boutique.</p>
=======
<h1>Boutique</h1>
<p>Votre panier est vide.</p>
>>>>>>> feature/panier

Reading it is mechanical. Between <<<<<<< HEAD and ======= is the version from the branch you are on, here main. Between ======= and >>>>>>> is the version from the branch you are merging in.

Writing the correct result is your job. It is not necessarily one side or the other: here, both changes are good and have to live together.

index.html après résolution
<h1>Boutique Gekkode</h1>
<p>Votre panier est vide.</p>

Delete the three markers, they must not stay in the file. Then tell Git the matter is settled, and finish the merge:

bash
git add index.html
git commit -m "Fusionne feature/panier dans main"
Sortie réelle · git log --oneline --graph après la fusion
*   0899b0c Fusionne feature/panier dans main
|  
| * aacf4c0 Panier : message quand le panier est vide
* | 11ff16b Ajoute le nom de la marque au titre
|/  
* 6d495d9 Page d accueil

The merge commit has two parents, which the graph makes visible.

Calling it off and starting over

If the conflict is beyond you, nothing forces your hand:

bash
git merge --abort

The repository goes back to exactly the state it was in before the merge command. Nothing is lost: you can try again later, or talk it over with the author of the other branch.

merge or rebase

Both bring the work of one branch into another, but not in the same way.

git merge creates a merge commit that ties the two histories together. The history keeps an exact record of what happened, including the fact that two branches existed in parallel. The graph is faithful, but it forks.

git rebase replays your commits on top of the target branch, as if you had worked from its latest version. The history stays a straight line, easier to read, but it is rewritten: your commits get new hashes.

bash
git switch feature/panier
git rebase main

A rebase can hit the same conflicts as a merge, commit by commit. Resolving them is identical, only the command to carry on differs:

Sortie réelle · conflit pendant un rebase
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
error: could not apply 4937109... Promo
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply 4937109... Promo
bash
git add index.html
git rebase --continue
# or, to call the whole thing off
git rebase --abort

The golden rule fits in one sentence: never rebase a branch that other people have already pulled. Rewriting a shared history forces your colleagues to repair theirs by hand. On your own working branch, before you put it forward, a rebase is perfectly legitimate and gives a far more readable history.

The everyday case: someone pushed before you

You try to push, and Git refuses:

Sortie réelle · git push refusé
To /root/o.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '/root/o.git'
hint: Updates were rejected because the remote contains work that you do not

The server has commits you do not. You have to bring them in first. And on a fresh repository, Git asks you a question instead of acting:

Sortie réelle · git pull sans stratégie configurée
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:

Git has refused to choose on your behalf since version 2.27. Answer once and for all, as suggested in the installation chapter:

bash
git config --global pull.rebase true

The sequence then looks like this:

bash
git pull --rebase
git push
Sortie réelle · git pull --rebase puis git log
From /root/o
   622a60b..38b7dc5  main       -> origin/main
Rebasing (1/1)Successfully rebased and updated refs/heads/main.

* 5b45117 Ajoute app.js
* 38b7dc5 Ajoute la doc
* 622a60b Depart

Your commit has been replayed on top of your colleague’s and the history has stayed linear, with no stray merge commit. This is the setting to recommend to a team that is starting out.

One warning to finish with: never try to unblock a rejected push with git push --force. That command overwrites whatever is on the server. If you really do have to force, after rebasing a branch of your own, use git push --force-with-lease, which refuses to overwrite commits you had not seen.

Cleaning up

A merged branch has no reason to exist any more. If it was published with -u, push its latest commits first: Git refuses to delete a local branch that is ahead of its remote branch, even when it is merged into main.

bash
git push origin feature/panier
git branch -d feature/panier
Sortie réelle · suppression d'une branche fusionnée
Deleted branch feature/panier (was bc2203a).

Without that prior push, the message is explicit:

Sortie réelle · branche locale en avance sur origin
warning: not deleting branch 'feature/panier' that is not yet merged to
         'refs/remotes/origin/feature/panier', even though it is merged to HEAD
error: the branch 'feature/panier' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/panier'
hint: Disable this message with "git config set advice.forceDeleteBranch false"

If the branch has not been merged, Git stops you, which is a useful safety net:

Sortie réelle · suppression d'une branche non fusionnée
error: the branch 'feature/promo' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D feature/promo'
hint: Disable this message with "git config set advice.forceDeleteBranch false"

To delete the branch on the server as well, and clear out local references that have gone stale:

bash
git push origin --delete feature/panier
git fetch --prune

git fetch retrieves the state of the server without merging anything into your branches, unlike git pull. The --prune option also removes the remote branches that no longer exist, the ones your colleagues have merged and deleted.

You now know how to collaborate. The last chapter is about organisation: which branching model to adopt for your team.

Common errors

Your local changes would be overwritten by checkout Commit your changes, or stash them with git stash, before switching branches.
You have divergent branches Since Git 2.27, git pull refuses to choose between merging and rebasing on its own. Settle the question with git config --global pull.rebase true.
! [rejected] non-fast-forward The server has commits you do not. Bring them in with git pull --rebase. Never use git push --force to get past it: it overwrites other people’s work.
Conflict markers left behind Lines starting with <<<<<<<, ======= and >>>>>>> have to be gone from the file before the git add.
Rebasing a shared branch Rewriting a history that others have already pulled forces them to repair theirs. Only rebase your own unpublished branches.
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.