Aller au contenu

Comparing and managing branches

The homelab repository now has two branches: main, and monitoring with one extra commit. This page compares them, then renames and deletes branches.

Comparing branches: git diff

Recap of git diff

Comparing versions introduced the forms of git diff:

Command Compares
git diff Unstaged changes, for all files
git diff README.md Unstaged changes, for one file
git diff --staged Staged changes and the latest commit, for all files
git diff --staged README.md Same, for one file
git diff 2553c7d dd032b5 Two commits, by hash
git diff HEAD~2 HEAD Two commits, relative to HEAD

Comparing two branches

A branch name stands for the latest commit of that branch, so it can be used anywhere a commit hash is accepted. To compare the state of main and monitoring:

$ git diff main monitoring
diff --git a/inventory.csv b/inventory.csv
index fb08d5e..b3112a3 100644
--- a/inventory.csv
+++ b/inventory.csv
@@ -2,3 +2,4 @@ hostname,ip,role
 pi-dns,192.168.1.10,dns
 nas,192.168.1.20,storage
 web01,192.168.1.30,web
+mon01,192.168.1.50,monitoring
diff --git a/services.md b/services.md
index df0ab76..a6c5662 100644
--- a/services.md
+++ b/services.md
@@ -2,3 +2,4 @@
 - pi-dns: Pi-hole
 - nas: Samba shares
 - web01: Nginx
+- mon01: Prometheus and Grafana

The output reads exactly like a diff between two commits:

  • Version a is the first branch given (main), version b the second (monitoring).
  • Each file that differs gets its own diff --git section.
  • + lines exist on monitoring but not on main. Swapping the arguments (git diff monitoring main) swaps the signs.

The diff compares the files, not the history. To list the commits that are on monitoring but not on main, use git log with two dots:

$ git log --oneline main..monitoring
eeb2e47 Add monitoring server

For a quick overview, --stat summarises the changes per file:

$ git diff --stat main monitoring
 inventory.csv | 1 +
 services.md   | 1 +
 2 files changed, 2 insertions(+)

Navigating long outputs

When an output is longer than the terminal, Git shows it in a pager (usually less). Press Space to go down one page, b to go back, / to search, and q to quit and return to the prompt.

Renaming a branch: git branch -m

Sam started a branch with a vague name, new-server, and added a VPN server on it:

$ git switch -c new-server
Switched to a new branch 'new-server'
$ echo "vpn01,192.168.1.60,vpn" >> inventory.csv
$ git commit -am "Add VPN server"
[new-server 0caa26a] Add VPN server
 1 file changed, 1 insertion(+)

Another new server is planned, so new-server is no longer a clear name. git branch -m (move) renames it:

$ git branch -m new-server vpn
$ git branch
  main
  monitoring
* vpn
Command Effect
git branch -m <old> <new> Rename the branch <old> to <new>, from any branch
git branch -m <new> Rename the current branch

The commits do not change: only the label moves to a new name. -M forces the rename even if a branch called <new> already exists, which overwrites it.

Deleting a branch: git branch -d

Large projects can accumulate many branches. Once a branch has been merged, delete it to keep the list readable. The commits stay in the history of the branch they were merged into.

git branch -d deletes a branch only if it has been merged (into its upstream or into the current branch). You cannot delete the branch you are on, so switch away first:

$ git switch main
Switched to branch 'main'
$ git branch -d vpn
error: the branch 'vpn' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D vpn'
hint: Disable this message with "git config set advice.forceDeleteBranch false"

The vpn commit exists only on vpn, so deleting the branch would leave that commit unreachable: Git refuses. This safety check is the whole point of -d. An example of a successful -d is on the Merging branches page.

Force-deleting: git branch -D

The VPN idea is dropped. To delete a branch whether or not it has been merged, use the capital -D (shorthand for --delete --force):

$ git branch -D vpn
Deleted branch vpn (was 0caa26a).
$ git branch
* main
  monitoring

Unmerged commits become hard to find

After -D, the commits of the deleted branch are no longer listed by git log or git branch. They are still in the repository for a while, so the branch can be recreated from the hash in the message: git branch vpn 0caa26a. If the message is gone, git reflog lists the recent positions of HEAD, which usually include the lost commit. Do not count on it: check that you no longer need a branch before force-deleting it.

Summary

Command Result
git diff main monitoring Compare the latest commits of main and monitoring
git log --oneline main..monitoring List the commits on monitoring that are not on main
git branch List all branches
git branch -m old_name new_name Rename the branch old_name to new_name
git branch -d monitoring Delete monitoring, which has been merged
git branch -D vpn Delete vpn, even though it has not been merged

Common mistakes

  • Reading a branch diff backwards. git diff A B shows what changes from A to B: + lines are in B.
  • Trying to delete the current branch. Git refuses with cannot delete branch ... used by worktree. Switch to another branch first.
  • Reaching for -D by reflex. When -d refuses, it is protecting unmerged work. Check with git log main..<branch> before forcing.
  • Confusing -d and -D, or -m and -M. Lower case is the safe version, upper case forces.

Hands-on labs

Three labs, from guided to more autonomous. Each one has its own setup, so you can do them in any order and repeat them as often as you like. Type the commands rather than pasting them, and read every output before moving on: the goal is to build reflexes, not to reach the end.

Lab 1: compare, rename, delete

Objective: compare two branches, rename a branch, and delete branches with and without unmerged work.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/manage with main and two branches, tmp and test, each with one commit.

Setup:

mkdir -p ~/git-practice/manage && cd ~/git-practice/manage
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
git switch -q -c tmp
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -q -am "Add NAS"
git switch -q -c test main
echo "lab01,192.168.1.99,test" >> inventory.csv
git commit -q -am "Add lab machine"
git switch -q main

Tasks:

  1. Show the differences between main and tmp. Which line does tmp add?
  2. List the commits that are on test but not on main.
  3. Rename tmp to nas, without switching to it.
  4. Try to delete test with the safe option. Read the message, then delete it anyway.
  5. Create a branch scratch from main, then delete it with the safe option.

Expected result and verification:

  • Task 1 shows +nas,192.168.1.20,storage.
  • Task 2 shows a single commit, Add lab machine.
  • git branch finally lists * main and nas only.
  • At task 4, git branch -d test fails with the branch 'test' is not fully merged.
  • At task 5, git branch -d scratch succeeds: a branch with no commits of its own is already fully merged.
Solution
# 1. Compare two branches (a = main, b = tmp)
git diff main tmp                 # +nas,192.168.1.20,storage

# 2. Commits on test that are not on main
git log --oneline main..test      # Add lab machine

# 3. Rename another branch
git branch -m tmp nas

# 4. Safe delete refuses, forced delete works
git branch -d test                # error: the branch 'test' is not fully merged
git branch -D test                # Deleted branch test (was ...)

# 5. A branch with nothing of its own is "merged"
git branch scratch
git branch -d scratch             # Deleted branch scratch (was ...)

git branch                        # * main, nas
  • git branch -m <old> <new> works from any branch; git branch -m <new> renames only the current one.
  • scratch points to the same commit as main, so everything on it is already on main, and -d accepts.
  • The hash printed by git branch -D is your last chance to recover the branch easily: git branch test <hash>.

Clean up when you are done: rm -rf ~/git-practice/manage.

Lab 2: rename master, resurrect a branch

Objective: rename the current branch, then bring back a branch deleted by mistake.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/resurrect, a repository whose first branch is still called master, with a branch grafana holding one commit of its own.

Setup:

mkdir -p ~/git-practice/resurrect && cd ~/git-practice/resurrect
git init -q -b master
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
git switch -q -c grafana
printf '# Services\n- mon01: Grafana\n' > services.md
git add . && git commit -q -m "Document Grafana"
git switch -q master

Tasks:

  1. You are on master. Rename it to main, and check.
  2. Force-delete grafana. Copy the hash printed by Git.
  3. Change your mind: that commit was useful. Recreate grafana at that hash, and check that Document Grafana is back.
  4. Rename grafana to monitoring without switching to it.
  5. Delete monitoring again, this time with the safe option. What happens, and why?

Expected result and verification:

  • Task 1: git branch shows grafana and * main; git status says On branch main.
  • Task 2 prints Deleted branch grafana (was 1a2b3c4)., with your own hash.
  • Task 3: git log --oneline grafana shows Document Grafana and Create inventory.
  • Task 5 fails with error: the branch 'monitoring' is not fully merged: its commit is not on main. The branch is kept.
Solution
# 1. Rename the current branch
git branch -m main
git branch                        # grafana, * main

# 2. Force-delete: note the hash
git branch -D grafana             # Deleted branch grafana (was 1a2b3c4).

# 3. A branch is only a label: put it back on the commit
git branch grafana 1a2b3c4        # use your own hash
git log --oneline grafana         # Document Grafana, Create inventory

# 4. Rename another branch
git branch -m grafana monitoring

# 5. The safe option protects unmerged work
git branch -d monitoring          # error: the branch 'monitoring' is not fully merged
git branch                        # * main, monitoring
  • Deleting a branch deletes the label, not the commits. As long as you know the hash, git branch <name> <hash> brings the branch back.
  • Renaming master to main is exactly what you do on an older repository; the rename is local until you push.
  • -d refuses to drop commits that exist only on that branch: it is the option to use by default.

Clean up when you are done: rm -rf ~/git-practice/resurrect.

Lab 3: spring cleaning

Objective: decide, for each branch of a cluttered repository, whether it can be deleted, using comparisons before any deletion.

Prerequisites and initial state: Git installed. The setup creates ~/git-practice/cleaning with six branches: one misspelled, one already included in main, two throwaway tests, and two real work branches.

Setup (copy the whole block):

mkdir -p ~/git-practice/cleaning && cd ~/git-practice/cleaning
git init -q -b main
git config user.name "Practice User"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
git branch old-docs
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -q -am "Add NAS"
for b in moniotring backups test-1 test-2; do git branch "$b"; done
git switch -q moniotring && echo "mon01,192.168.1.50,monitoring" >> inventory.csv && git commit -q -am "Add monitoring server"
git switch -q backups && echo "backup01,192.168.1.60,backup" >> inventory.csv && git commit -q -am "Add backup server"
git switch -q test-1 && echo "test" >> inventory.csv && git commit -q -am "Test 1"
git switch -q test-2 && echo "test again" >> inventory.csv && git commit -q -am "Test 2"
git switch -q main

Tasks:

  1. List the branches. For each one, list the commits it has that main does not have.
  2. Delete old-docs with the safe option. Why does Git accept?
  3. Check what test-1 and test-2 changed compared to main, then delete both in a single command.
  4. Fix the typo: moniotring should be monitoring.
  5. Check the final list of branches.

Expected result and verification:

  • Task 1: old-docs has no commit of its own; moniotring, backups, test-1, and test-2 have one each.
  • Task 2 prints Deleted branch old-docs (was ...): its only commit is already part of main.
  • Task 3: the diffs only add test or test again; git branch -d refuses them, git branch -D deletes both.
  • Task 5: git branch shows backups, * main, and monitoring.
Solution
# 1. What would be lost?
git branch
for b in old-docs moniotring backups test-1 test-2; do echo "== $b"; git log --oneline main..$b; done

# 2. Nothing of its own: the safe delete works
git branch -d old-docs

# 3. Inspect, then force-delete both
git diff main test-1              # +test
git diff main test-2              # +test again
git branch -d test-1 test-2       # error: ... not fully merged (twice)
git branch -D test-1 test-2

# 4. Fix the name
git branch -m moniotring monitoring

# 5. Result
git branch                        # backups, * main, monitoring
  • git log --oneline main..<branch> answers the only question that matters before deleting: "which commits exist only on this branch?". An empty answer means -d is safe.
  • old-docs was created before Add NAS, so it points to an older commit of main: it is behind, not diverged, and fully merged.
  • Like git branch -d, git branch -D accepts several names at once.

Clean up when you are done: rm -rf ~/git-practice/cleaning.