As Java developers, we spend most of our time in IDEs like IntelliJ IDEA, but a messy Git history can waste hours during code reviews, debugging, and releases. In this comprehensive guide, we'll transform how you work with Git to achieve a professional, clean, and maintainable project history that every senior engineer will appreciate.
You'll learn why branching from feature branches is dangerous, what linear history really means, when and how to use rebase vs merge, and why "Squash and Merge" should be your default strategy in 2025.
1. The Problem: What Actually Happened in Real Projects
You created branch1 → merged to main → later created branch2 from old branch1 → made one tiny URL change → opened PR → suddenly 5 files changed instead of 1.
flowchart TD
A[main (old)] --> B[branch1 created]
B --> C[4 files changed]
C --> D[branch1 merged to main]
D --> E[main now has those 4 changes]
F[Your local branch1 - NOT updated] --> G[branch2 created from stale branch1]
G --> H[Only 1 URL change]
H --> I[PR branch2 → main]
I --> J[Git sees 4 + 1 = 5 changes!]
style J fill:#f9f,stroke:#f33,stroke-width:3px
2. Root Cause: Stale Base Commit
Your local main was not updated after merging branch1, so branch2 was created from an outdated point in history.
3. Golden Rule #1: Always Branch from an Updated Main
# Always do this before creating a new branch
git checkout main
git pull origin main # Critical step!
git checkout -b feature/login-update
flowchart LR
subgraph Correct Way
M[main] --> |git pull| M2[Updated main]
M2 --> NB[New Branch]
end
subgraph Wrong Way
OLD[Old local main] --> OB[Branch from stale]
end
style Correct Way fill:#e6ffe6
style Wrong Way fill:#ffe6e6
4. What is Linear History? (With Visual Proof)
Linear history = commits form a straight line. No merge bubbles, no diamonds, no confusion.
gitGraph
commit id: "A"
commit id: "B"
branch feature/login
checkout feature/login
commit id: "C"
commit id: "D"
checkout main
commit id: "E (someone else's work)"
checkout feature/login
commit id: "F"
checkout main
merge feature/login
commit id: "Merge commit" tag: "Messy!"
gitGraph
commit id: "A"
commit id: "B"
branch feature/login
checkout feature/login
commit id: "C"
commit id: "D"
checkout main
commit id: "E (someone else's work)"
checkout feature/login
commit id: "F (rebased!)"
checkout main
merge feature/login
commit id: "G (fast-forward)" tag: "Clean!"
Benefits of Linear History
- git bisect becomes 10x faster
- git revert entire feature with one command
- Code reviews are cleaner
- Release notes are meaningful
- IntelliJ Git Log looks professional
5. Rebase vs Merge: Complete Comparison
| Strategy | History | Use When | Preserves Original Commits? |
|---|---|---|---|
git merge |
Non-linear (merge commits) | Never for feature branches | Yes |
git rebase |
Linear | Before creating PR | Rewrites (new hashes) |
| Squash & Merge | Perfectly linear | Default for all PRs | No (one clean commit) |
6. The Power of Squash and Merge (Why It's the #1 Strategy in 2025)
Transforms 15 messy commits into ONE meaningful commit.
sequenceDiagram
participant Dev as Developer
participant Branch as Feature Branch
participant Main as main branch
Dev->>Branch: commit "WIP: start login"
Dev->>Branch: commit "fix typo"
Dev->>Branch: commit "add validation"
Dev->>Branch: commit "address review"
Dev->>Branch: commit "fix tests"
Note right of Branch: 5 messy commits
Branch->>Main: Squash and Merge
Note over Main: Single commit:
"feat: implement user login with validation"
Real Example: Before vs After Squash
# Without squash - actual history
a3f2e1d fix typo in error message
b7c9d8f add remember me checkbox
e2d1c9b address code review comments
f8a7b6c fix failing integration test
d4e5f6g initial login controller
c1b2a3d add login form HTML
# With squash - clean history
f1a2b3c feat(auth): implement complete user login
- Email/password authentication
- Remember me functionality
- Client + server validation
- Integration tests
- Responsive design
7. Step-by-Step: Fix Your Current Messy PR
# Method 1: Rebase (Recommended)
git checkout main
git pull origin main
git checkout branch2
git rebase main # Now only your URL change remains
git push --force-with-lease
# Method 2: Create Clean Branch (Safest)
git checkout main
git pull origin main
git checkout -b branch2-clean
git cherry-pick <commit-hash-of-url-change>
git push origin branch2-clean
8. IntelliJ IDEA Configuration for Clean Git Forever
Go to Settings → Version Control → Git
- Update method → Rebase
- Warn if CRLF line separators are detected → Enable
- Auto-stash on rebase → Enable
Pull dialog → Always select "Rebase"
9. Team Git Workflow Every Java Team Should Adopt
flowchart TD
Start[Start Work] --> Update[git checkout main\ngit pull]
Update --> Create[git checkout -b feat/payment-gateway]
Create --> Work[Code + Commit]
Work --> Rebase[git fetch\ngit rebase origin/main]
Rebase --> Test[Run Tests]
Test --> PR[Create PR]
PR --> Squash[Squash and Merge]
Squash --> Delete[Delete branch]
Delete --> Celebrate[Celebrate clean history!]
style Squash fill:#90EE90
10. Bonus: Conventional Commits for Perfect Squash Messages
feat(auth): add user login with JWT
fix(payment): resolve concurrency issue in transaction
refactor(order): extract service layer
docs: update API documentation for v2
test: add integration tests for user registration
Conclusion
Clean Git history is not optional in professional Java development. By following these practices:
- Always branch from updated main
- Rebase before PR
- Use Squash and Merge by default
- Write meaningful squashed commit messages
- Configure IntelliJ properly
Your team will thank you, code reviews will be faster, debugging will be easier, and your Git graph in IntelliJ will finally look like a senior engineer's repository.
Start today: Make "Squash and Merge" your default button on GitHub/GitLab!
Quick Checklist Before Every PR
- ☐ Branched from updated main?
- ☐ Ran
git fetch && git rebase origin/main? - ☐ Only intended changes in PR?
- ☐ Ready to squash into one beautiful commit?

.png)