Git Basics: commit, branch, and merge
1. Overview
Git is a version control tool that almost every project uses, but many beginners only remember a few commands without understanding the concepts behind them. This article covers three core concepts: commit (saving a snapshot), branch (independent working branches), and merge (combining branches). After reading, you’ll confidently create new branches, commit correctly, handle conflicts during merge, and know when to use merge instead of rebase.
2. Commit – saving a snapshot
2.1. What is a commit
Each commit is a “snapshot” of the entire state of files at a specific point in time, along with a pointer to the parent commit. Git doesn’t store diffs; it stores snapshots — this makes branching and merging fast and cheap.
# Initialize repo and make the first commit
git init order-service
cd order-service
echo "# Order Service" > README.md
git add README.md
git commit -m "docs: add README for Order Service"
Explanation:
git initcreates a new repo and generates the hidden.gitdirectory.git add README.mdmoves the file to the staging area (buffer waiting for commit).git commit -m "..."creates a snapshot with a descriptive message of the changes.
Check the history:
git log --oneline
a1b2c3d docs: add README for Order Service
2.2. Write good commit messages
A commit should answer the question “what does this change do, and why?” Common convention (conventional commits):
feat: add API to create orders
fix: fix incorrect total calculation when applying coupon
refactor: extract OrderValidator from OrderService
Note: Avoid vague commit messages like
"update"or"fix bug". Months later when looking back atgit log, you won’t know what that commit fixed.
2.3. Commit in parts with git add -p
When a file has multiple unrelated changes, don’t commit them all at once.
git add -p src/main/java/com/nonladev/OrderService.java
Git will ask which “hunk” (chunk of changes) you want to stage, allowing you to split them into small, reviewable commits that are easy to revert if needed.
3. Branch – working independently on multiple branches
3.1. Create and switch branches
A branch is just a lightweight pointer to a specific commit. Creating a new branch doesn’t copy all the code; it just adds another pointer.
# Create a new branch and switch to it
git branch feature/add-payment
git checkout feature/add-payment
# Or combine the two commands above (Git ≥ 2.23)
git switch -c feature/add-payment
Explanation:
git branch <name>creates a branch but stays on the current branch.git checkout <name>orgit switch <name>movesHEADto that branch.-cingit switchis a shortcut to create-and-switch at once.
3.2. View list of branches
git branch -v
* feature/add-payment d4e5f6a add PaymentService skeleton
main a1b2c3d docs: add README for Order Service
The * indicates the current branch (where HEAD is pointing).
3.3. Follow naming conventions for branches
Clear branch names help the whole team understand what the branch does:
feature/add-payment # add new feature
fix/order-total-wrong # fix bug
refactor/extract-validator
hotfix/prod-null-pointer
3.4. Delete branch after completion
# Delete local branch that has been merged
git branch -d feature/add-payment
# Delete unmerged branch (force delete, careful not to lose commits)
git branch -D feature/add-payment
-d (lowercase) is safer because Git will warn if the branch hasn’t been merged anywhere.
4. Merge – combining branches
4.1. Fast-forward merge
If main has no new commits since you branched off, Git only needs to move the main pointer to the last commit of the feature branch — this is called fast-forward.
git switch main
git merge feature/add-payment
Updating a1b2c3d..d4e5f6a
Fast-forward
PaymentService.java | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
No new merge commit is created; history remains a straight line.
4.2. Merge commit (3-way merge)
If both branches have separate commits since the branch point, Git creates a merge commit with two parents, merging content from both branches.
git switch main
git commit -am "fix: fix incorrect item quantity validation" # main has a new commit
git switch feature/add-payment
git switch main
git merge feature/add-payment
Merge made by the 'recursive' strategy.
PaymentService.java | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
Git automatically creates a merge commit with the default message Merge branch 'feature/add-payment'.
4.3. Handle conflicts during merge
A conflict occurs when two branches modify the same section of code. Example with OrderService class:
// In main
public class OrderService {
public BigDecimal calculateTotal(Order order) {
return order.getSubtotal().multiply(BigDecimal.valueOf(1.1)); // add 10% tax
}
}
// In feature/add-payment
public class OrderService {
public BigDecimal calculateTotal(Order order) {
return order.getSubtotal().subtract(order.getDiscount()); // subtract discount
}
}
During merge, Git can’t decide which line to keep, so markers appear in the file:
public class OrderService {
public BigDecimal calculateTotal(Order order) {
<<<<<<< HEAD
return order.getSubtotal().multiply(BigDecimal.valueOf(1.1));
=======
return order.getSubtotal().subtract(order.getDiscount());
>>>>>>> feature/add-payment
}
}
You need to manually fix the logic to combine correctly (for example: subtract discount first, then add tax), remove the markers <<<<<<<, =======, >>>>>>>, then:
git add OrderService.java
git commit
Note: When committing after resolving a conflict, you don’t need
-m; Git will open an editor with a default message clearly stating this is a merge commit — keep that message as-is unless you want to add notes.
4.4. Merge vs Rebase
| Criteria | merge |
rebase |
|---|---|---|
| History | Preserved with merge commit | Rewrites history, branch is linear |
| Safety | Doesn’t modify old commits | Modifies old commits (changes hashes) |
| Use when | Public shared branches (main, develop) |
Personal branch, want clean history before creating PR |
# Rebase feature branch onto latest main
git switch feature/add-payment
git rebase main
Note: Don’t
rebasea branch that’s already pushed and others are pulling from — you’ll corrupt their history and cause massive conflicts when they pull again.
5. Test a merge scenario with JUnit
Although Git isn’t Java code, you can completely write tests to verify that business logic doesn’t break after merging two branches — this is why CI always runs tests on merge commits. Example of OrderService after resolving the conflict in section 4.3:
public class OrderService {
public BigDecimal calculateTotal(Order order) {
BigDecimal afterDiscount = order.getSubtotal().subtract(order.getDiscount());
return afterDiscount.multiply(BigDecimal.valueOf(1.1));
}
}
class OrderServiceTest {
private final OrderService orderService = new OrderService();
@Test
void shouldApplyDiscountThenTax() {
Order order = new Order(
BigDecimal.valueOf(100), // subtotal
BigDecimal.valueOf(10) // discount
);
BigDecimal total = orderService.calculateTotal(order);
assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(99.0));
}
}
Explanation: subtotal 100 minus discount 10 leaves 90, multiply by 10% tax gives 99. This test is a “safety net” ensuring the merge of two logics (tax and discount) don’t cancel each other out.
✓ shouldApplyDiscountThenTax()
6. Suggested workflow for small teams
- Always create a new branch from
mainfor each task:git switch -c feature/xxx. - Small, frequent commits with clear messages.
- Before creating a Pull Request, update your branch with the latest
main:git switch feature/add-payment git fetch origin git merge origin/main - Resolve conflicts (if any) on your local machine, re-run tests.
- Push and create a PR, letting CI run tests on the merge commit before merging into
main.
Note: Don’t commit directly to
mainin a multi-person team — always go through a branch + PR for review and CI checks first.
7. Common mistakes
- Forgetting
git addbefore commit: Git reportsnothing to commit, working tree cleaneven though you’ve edited files — because they haven’t been staged. - Merging the wrong branch: always run
git statusandgit branchto check where you are beforemerge. - Leftover conflict markers in code: forgetting to remove
<<<<<<<prevents compilation — always build/test immediately after resolving conflicts. - Force push to shared branch:
git push --forceafter rebasing a shared branch deletes your colleague’s history. Only use--force-with-leaseand only on personal branches.
8. Conclusion
Commit, branch, and merge are three operations you use daily with Git. Mastering commits keeps your code history clear, branches allow parallel work without interference, and understanding merge (including conflict resolution) gives you confidence when combining code from multiple people. Practice small commits, follow branch naming conventions, and always run tests before merging into main.