Time to study: ~105 min
You will learn: what a branch really is, how to make and switch them, HEAD and refs/heads, merging, fixing conflicts without panicking, and worktrees.
← Chapter 4 | back to Git 101 | next: Chapter 6 →
1. what is a branch
a branch is a pointer to a commit. that is it, that is the whole thing. but let’s give it an example so it sits in ur head:
i have a project with a
mainbranch that i am working on, and i want to change the colour theme. i do not want to touchmainwhile i am experimenting, becausemainis the version that works. so i open a new branch, call itfeat/colortheme, and do the work there. when it is finished and tested, i merge it back intomain.
feat/colortheme
│
●───●───●
/ \
●───●───●───●───────────────●───────────●─────────● ...
main commits branched merged back main continues
and remember Chapter 3: a commit already knows its parent. so a branch does not have to copy anything. it is one file with one hash in it, saying “this branch is currently here”. that is why making a branch in git is instant, even on a huge project.
2. the branch commands
to see which branches exist and which one u are on:
git branch* main
the
*is u. if the default branch of ur repo is still calledmaster, u can rename it:
git branch -m main # -m = move / rename
git branch* main
- and to make
mainthe default for every new repo, so u never do this again (this was Chapter 2):
git config --global init.defaultBranch mainto create a branch:
git branch feat/colorthemebut that only creates it, it does not take u there, so it is not what u actually want. the good way is to create it and switch to it in one command:
git checkout -b feat/colorthemeSwitched to a new branch 'feat/colortheme'
or the newer command that does exactly the same thing:
git switch -c feat/colorthemeSwitched to a new branch 'feat/colortheme'
checkoutis the old command that does about 8 different jobs, andswitchandrestoreare the newer ones that split those jobs up. both work, pick one. i learnedcheckoutfirst and still type it.
once the branch exists, u just move between them, no flag:
git switch main
git switch feat/colortheme
# or the checkout version
git checkout main
git checkout feat/colorthemeand
git branch -vshows u where each branch is pointing right now:
git branch -v* feat/colortheme fb31aea adding colortheme
main 79e91e0 2nd commit
- name ur branches properly.
feat/colortheme,fix/login-button,docs/readme. the/is not a folder, it is just a naming habit everyone follows, and it keeps a repo with 40 branches readable.
3. HEAD and refs/heads
now let’s prove that a branch is just a file with a hash in it. we are on
feat/colortheme:
cat .git/HEADref: refs/heads/feat/colortheme
HEAD answers “which branch am i on”. it does not hold a commit, it holds the name of a branch. and the branch itself:
cat .git/refs/heads/main79e91e04f6fb4309c982a0107af75cd239dfd261
a branch is one line of text containing one commit hash. that is a branch. that is all a branch has ever been.
git repository
│
┌────────────┴────────────┐
│ │
HEAD .git/refs/heads/
│ │
▼ ├── main
"which branch am i on?" │ └──► 79e91e0
│
└── feat/colortheme
└──► fb31aea
when u commit, git writes the new commit and then rewrites that one line to point at it. when u switch branches, git reads the other branch’s line and puts those files in ur folder. everything else in this chapter is built on those 2 sentences.
4. merging
merging is combining 2 branches into one. we finished
feat/colorthemeand tested it, and we want it inmain.
the branch that receives the merge is the branch u are standing on, so switch to
mainfirst. this is the mistake everyone makes once:
git switch main
git merge feat/colorthemeUpdating 79e91e0..fb31aea
Fast-forward
Readme.md | 1 +
1 file changed, 1 insertion(+)
git log --onelinefb31aea (HEAD -> main, feat/colortheme) adding colortheme
79e91e0 2nd commit
70d5c6e add Readme.md
fast forward:
mainhad not moved at all since we branched, so there was nothing to combine. git just slid themainpointer forward to the same commit. no merge commit, nothing to think about.
before: after fast forward:
●───● main ●───●───● main, feat/colortheme
\ (same commit)
● feat/colortheme
when
mainhas moved on, git cannot slide anything, so it makes a merge commit: a commit with 2 parents, one from each side.
git merge feat/theme2Merge made by the 'ort' strategy.
Readme.md | 1 +
1 file changed, 1 insertion(+)
git log --oneline --graph* 9834467 (HEAD -> main) Merge branch 'feat/theme2'
|\
| * e7a199a (feat/theme2) dark theme
* | e837085 light theme
|/
* fb31aea adding colortheme
u can see both lines of work in the graph, and the commit at the top that joins them. that is what “keeps the history” means.
when the branch is merged and u are done with it, delete it:
git branch -d feat/colorthemeDeleted branch feat/colortheme (was fb31aea).
-dis the safe one, it refuses if the branch has commits that were never merged anywhere.-Ddeletes it anyway. use-dand only reach for-Dwhen u are sure u are throwing work away on purpose.- deleting a branch deletes a pointer, not the commits. the commits are still there, and Chapter 7 shows u how to find them again with
reflog.
5. merge conflicts
a conflict happens for exactly one reason: 2 branches changed the same lines of the same file, and git will not guess which one u meant.
everything else it merges silently. different files, different parts of the same file, no problem. the same line, 2 different ways, it stops and asks.
let’s cause one on purpose. on
feat/theme2the last line ofReadme.mdis# dark theme, and onmainthe same line is# light theme:
git switch main
git merge feat/theme2Auto-merging Readme.md
CONFLICT (content): Merge conflict in Readme.md
Automatic merge failed; fix conflicts and then commit the result.
do not panic and do not close the terminal. ask git where u are, it will literally tell u what to do next:
git statusOn branch main
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: Readme.md
no changes added to commit (use "git add" and/or "git commit -a")
now open the file:
cat Readme.mdHello World
# added colortheme
<<<<<<< HEAD
# light theme
=======
# dark theme
>>>>>>> feat/theme2
how to read those markers
this is the thing that scares people, and it is 3 markers:
<<<<<<< HEAD
↑ everything from here to the ======= is what is on the branch u are ON.
u are on main, so this is YOUR side, main's version.
=======
↑ the divider. above = yours, below = theirs.
below is what is coming IN, from the branch u named in the merge command.
>>>>>>> feat/theme2
↑ and this tells u exactly which branch that was.
so
HEADis always where u are standing, and the name at the bottom is what u are pulling in. if this happened during agit pull, the bottom part is what ur teammate pushed.
how to fix it
u edit the file until it says what u want, and u delete all 3 marker lines. that is the whole job. there is no magic command, u are the one who decides.
say we want both lines:
# after editing, the file is:
cat Readme.mdHello World
# added colortheme
# light theme
# dark theme
then u tell git it is resolved, and commit:
git add Readme.md
git commit[main 9834467] Merge branch 'feat/theme2'
git commiton a merge already has the message written for u, so u just save and close the editor. that is it, the conflict is over.
git merge --abortis the escape hatch. it puts everything back exactly as it was before u typedmerge, as if it never happened. when u are lost, use it, look at the branches again, and start over:
git merge --abort
git status -s # clean, nothing happenedwhen u just want one side
sometimes u do not want to merge the lines at all, u know one of the 2 versions is simply correct:
git checkout --ours Readme.md # keep MY version (the branch i am on)
git checkout --theirs Readme.md # keep THEIR version (the branch coming in)
git add Readme.md
git commitand for every conflicted file at once:
git checkout --ours .
git add .
git commit- careful with
--oursand--theirs: “ours” is whatever branch u are standing on, and during a rebase (Chapter 6) those 2 words swap meaning, because rebase replays ur commits onto the other branch. when in doubt, open the file and look at theHEADmarker instead of trusting the words.
more than one file
nothing changes, it is the same job N times.
git statuslists every conflicted file underUnmerged paths, u fix them one by one, and ugit addeach one as u finish it. the merge is done when nothing is left unmerged:
git status -sUU Readme.md
UU src/app.js
AA config.json
UUmeans both sides modified it,AAmeans both sides added a file with that name. same fix for both: open, decide, delete the markers,git add.
- u can list only the files still in conflict:
git diff --name-only --diff-filter=U6. how to have fewer conflicts
conflicts are not a git problem, they are a “2 people edited the same lines” problem. so:
- pull often. a conflict against 1 commit is small, a conflict against 3 weeks of commits is a day of ur life.
- keep branches short. a branch alive for 2 days conflicts far less than one alive for 2 months.
- one job per branch. the colour theme branch changes the colour theme, nothing else.
- and when u are working alone on 2 machines, this is the same problem with 1 person, so pull before u start on the other machine.
7. worktree, one repo in two folders
u are working on
feat/colorthemewith unfinished changes on disk. someone saysmainis broken and needs a fix now. u cannot switch cleanly, so u either stash (Chapter 8) or u make a mess.
the other answer is a worktree: a second folder on ur disk, checked out to a different branch, sharing the same repository.
git worktree add ../git-test-main mainPreparing worktree (checking out 'main')
HEAD is now at 953afcd commit 10
now:
~/projects/
├── git-test/ → feat/colortheme (ur unfinished work, untouched)
└── git-test-main/ → main (clean, ready for the hotfix)
both folders are the same repo. commit in one, and
git login the other sees it immediately. no stashing, no switching, and ur editor keeps its open files in both.
a branch and a worktree at the same time:
git worktree add -b hotfix ../git-test-hotfixPreparing worktree (new branch 'hotfix')
HEAD is now at 953afcd commit 10
see them all:
git worktree list/home/ati/projects/git-test 953afcd [feat/colortheme]
/home/ati/projects/git-test-hot 953afcd [hotfix]
and when u are done, remove the folder properly (do not just
rm -rfit, git keeps a record of it):
git worktree remove ../git-test-hotfix
git branch -d hotfix # if u also want the branch gone| branch | worktree | |
|---|---|---|
| what it is | a pointer to a commit | a folder on disk, checked out to a branch |
| how many at once | as many as u want | as many as u want |
| how many visible | 1, the one u switched to | all of them, side by side |
| switching cost | ur files change under u | none, u cd |
| unfinished work | blocks the switch | stays where it is |
| disk cost | nothing | a second copy of ur files (the history is shared) |
| when | always | when u need 2 branches at the same time |
- a branch can only be checked out in one worktree at a time, and git tells u so:
git worktree add ../another-copy mainfatal: 'main' is already used by worktree at '/home/ati/projects/git-test'
- u do not need this on day one. u will need it the first time a hotfix arrives while ur branch is half finished, and on that day u will remember this section.
NOTES
git switch -goes back to the branch u were just on, exactly likecd -in Terminal 101:
git switch -- branches on the remote are their own thing. deleting a branch locally leaves it on github. to delete it there:
git push origin --delete feat/colorthemegit branch --mergedlists branches whose work is already in the branch u are on. those are the ones that are safe to delete:
git branch --merged-
a detached HEAD happens when u check out a commit instead of a branch (
git checkout 79e91e0). HEAD then points at a commit directly, with no branch name on it. u can look around, but commits u make there belong to nothing and are ez to lose. get out withgit switch main, or keep the work withgit switch -c a-new-branch. -
never delete a branch to “cancel” a merge. the merge already happened in the branch u merged into. u undo a merge, not the branch, and that is Chapter 7.
Assignment
- In
practice-git, list ur branches and see which one u are on. - Make sure ur main branch is called
main, renaming it if it is not. - Create a branch
feat/colorthemeand switch to it in one command. - Prove with
catwhich branch u are on and which commit it points to, using the files inside.git. - Make 2 commits on that branch, then switch back to
mainand show that ur file does not have them. - Merge the branch into
main, and read the first word of the output. Was it a fast forward? Why? - Delete the branch, then show ur history as a graph.
- Now cause a conflict on purpose: make a branch, change line 1 of a file on it and commit, go back to
main, change the same line differently, commit, and merge. - Read
git statusduring the conflict, then open the file and say out loud which part is yours and which is theirs. - Abort the merge and prove nothing changed.
- Do the merge again, resolve it by keeping both lines, and commit it.
- Do a third conflict, and this time resolve it by keeping only ur side, with one command instead of an editor.
- Add a worktree in a second folder, on a new branch called
hotfix, list ur worktrees, then remove it. Then try to add a second worktree formainwhile u are standing in the folder that already hasmainchecked out, and read the refusal.
stuck, or done and want to check? the solutions are here