Deep Git Workflows: Rebasing, Conflict Resolution and Branching Strategies
Git is more than just a tool for storing code. It helps developers manage changes, collaborate with teams, experiment with new features, fix bugs, and maintain a clear history of how a project evolved.
As projects become larger and multiple developers work on the same codebase, basic commands such as git add, git commit, and git push are not enough. Developers need to understand advanced Git workflows such as rebasing, merging, conflict resolution, stashing, cherry-picking, reverting, resetting, tagging, and branching strategies.
A well-managed Git history can also act as documentation for a project because it tells us what changed, when it changed, and why it changed.
1. Merge vs Rebase
Two common ways to combine changes from different branches are merge and rebase.
Suppose the project looks like this:
A---B---C main
\
D---E feature
The feature branch was created from main, and both branches continued to receive commits.
Git Merge
To merge the feature branch:
git switch main
git merge feature
Git combines the histories and may create a merge commit:
A---B---C---------M
\ /
D---E-----/
The merge approach preserves the original development history.
Advantages of Merge
- Preserves the complete history.
- Does not rewrite existing commits.
- Safe for shared branches.
- Clearly shows that two branches were combined.
Disadvantage
If a project has many branches and frequent merges, the history can become complicated.
2. Git Rebase
Rebase takes commits from one branch and replays them on top of another branch.
For example:
A---B---C main
\
D---E feature
Run:
git switch feature
git rebase main
The result becomes:
A---B---C---D'---E'
Git reapplies the feature commits on top of the latest main. The Git documentation describes rebase as reapplying commits on top of another base.
The ' is important. D' and E' are new commit objects because their parent history has changed.
Advantages of Rebase
- Produces a cleaner, linear history.
- Makes project history easier to read.
- Helps clean up local feature branches.
- Makes Pull Request history easier to understand.
Disadvantage
Rebase rewrites commit history.
Therefore, you should be careful when rebasing commits that other developers have already based their work on. Git's documentation specifically warns that rewriting a branch others depend on can force downstream developers to repair their history.
3. Merge vs Rebase
The basic difference can be remembered like this:
Merge → Preserve the story
Rebase → Rewrite the story more cleanly
| Merge | Rebase |
|---|---|
| Preserves history | Rewrites history |
| May create merge commit | Creates a linear history |
| Good for shared branches | Good for private/local branches |
| Safer for collaboration | Useful for cleaning history |
| Shows branch structure | Hides unnecessary branching details |
A practical rule is:
Do not casually rebase shared/public history. Rebase your own local feature work when you need a cleaner history.
4. Interactive Rebase
Interactive rebase provides more control over your commits.
Suppose you have:
A---B---C---D---E
You can inspect the last three commits with:
git rebase -i HEAD~3
Git opens a list such as:
pick C Add login
pick D Fix login CSS
pick E Fix login validation
You can modify these commands to clean up your history.
Common operations include:
-
pick— keep the commit. -
reword— change the commit message. -
edit— stop and modify the commit. -
squash— combine the commit with the previous one. -
fixup— combine commits while discarding the later commit message. -
drop— remove a commit.
Git's interactive rebase supports reordering and combining commits.
5. Squashing Commits
During development, you may create commits such as:
Add login
Fix login
Fix login again
Fix validation
Final login fix
These commits may be useful while developing, but they are not necessarily useful in the final project history.
You can squash them:
git rebase -i HEAD~5
Change:
pick A Add login
pick B Fix login
pick C Fix login again
pick D Fix validation
pick E Final login fix
to:
pick A Add login
squash B Fix login
squash C Fix login again
squash D Fix validation
squash E Final login fix
The result can become one meaningful commit:
Add login functionality
This makes the history much easier to understand.
Git's workflow documentation recommends keeping changes as small logical steps and notes that interactive rebase can be used to clean up commits before publishing them.
6. Reordering Commits
You can also change the order of commits.
For example:
pick A Add login
pick B Update README
pick C Add authentication
can become:
pick A Add login
pick C Add authentication
pick B Update README
This can make the history follow a more logical development sequence.
However, reordering commits can cause conflicts if later commits depend on earlier ones.
7. Editing Commits
Suppose you realize that a commit is missing a file.
You can use:
edit
For example:
pick A Add login
edit B Add authentication
pick C Update README
Git stops at commit B.
You can modify the files:
git add .
git commit --amend
git rebase --continue
This allows you to correct or improve an existing commit.
8. Resolving Merge Conflicts
A conflict happens when Git cannot automatically decide which version of a change should be kept.
For example, one branch contains:
const username = "Swaroop";
while another branch contains:
const username = "Sai";
If both branches changed the same line, Git may produce:
<<<<<<< HEAD
const username = "Swaroop";
=======
const username = "Sai";
>>>>>>> feature
The developer must decide what the correct code should be.
For example:
const username = "Sai Swaroop";
Then:
git add .
git commit
During a rebase:
git add .
git rebase --continue
If you want to cancel the rebase:
git rebase --abort
Git's documentation recommends resolving the conflict, staging the resolved files, and then continuing the rebase.
9. Git Stash
Sometimes you have unfinished work but need to switch branches.
For example:
feature/login
↓
unfinished changes
You don't want to commit incomplete code.
Use:
git stash
Git temporarily stores the changes.
Now you can switch branches:
git switch main
After finishing your other work:
git switch feature/login
git stash pop
Your unfinished changes are restored.
Think of git stash as:
Temporarily putting unfinished work aside without committing it.
Git includes stash as one of its standard branching and merging tools.
10. Git Cherry-Pick
Cherry-pick is useful when you want to copy a particular commit from another branch.
Suppose:
main:
A---B---C
feature:
\
D---E
You only want commit E on main.
Run:
git switch main
git cherry-pick E
You get:
A---B---C---E'
The changes from E are applied as a new commit on main.
A common use case is a bug fix.
For example:
feature/payment
↓
Fix payment calculation
You may want that specific fix in a release branch without bringing the entire feature branch.
11. Git Revert
Suppose your history is:
A---B---C---D
You discover that D introduced a bug.
You can run:
git revert D
Git creates a new commit that reverses the changes from D:
A---B---C---D---R
R represents the revert commit.
This is generally safer for shared branches because the original history remains intact.
Git defines revert as creating a new commit that reverses the changes introduced by an earlier commit.
12. Git Reset
Reset works differently.
Suppose:
A---B---C---D
You run:
git reset --hard C
Now the branch points to:
A---B---C
Commit D is no longer part of the current branch history.
Git documentation describes reset as moving the branch tip and therefore changing the branch history.
There are three important reset modes.
Soft Reset
git reset --soft HEAD~1
Moves the branch backward but keeps the changes staged.
Mixed Reset
git reset --mixed HEAD~1
Moves the branch backward and keeps the changes in the working directory, but unstages them.
This is the default reset mode.
Hard Reset
git reset --hard HEAD~1
Moves the branch backward and discards the changes.
Be careful with --hard.
13. Revert vs Reset
The easiest way to remember the difference:
Revert → Undo using a NEW commit
Reset → Move the branch BACKWARD
| Revert | Reset |
|---|---|
| Creates a new commit | Moves branch pointer |
| Preserves history | Changes history |
| Safer for shared branches | Better for local cleanup |
| Good for undoing published changes | Useful for undoing local commits |
If a commit has already been pushed and other developers may depend on it, git revert is generally the safer choice.
14. Git Tags
Tags are used to mark important points in Git history.
For example:
A---B---C---D---E
↑
v1.0.0
Create a tag:
git tag v1.0.0
View tags:
git tag
Push a tag:
git push origin v1.0.0
Tags are useful for software releases:
v1.0.0
v1.1.0
v2.0.0
A tag makes it easy to identify the exact commit associated with a release.
15. Branching Strategies
A branching strategy defines how developers organize branches within a project.
Three commonly discussed approaches are:
- Git Flow
- GitHub Flow
- Trunk-Based Development
16. Git Flow
Git Flow typically uses branches such as:
main
develop
feature/*
release/*
hotfix/*
For example:
feature/login
|
↓
develop ----------●
|
↓
release
|
↓
main -------------●
A developer might create:
git switch -c feature/login
After development, the feature is integrated into develop.
When a release is prepared, a release branch can be created and eventually merged into main.
Advantages
- Clear separation between development and production.
- Useful for scheduled release cycles.
- Provides dedicated release and hotfix branches.
Disadvantages
- More complicated.
- More branches to maintain.
- Can be unnecessary for projects that deploy continuously.
17. GitHub Flow
GitHub Flow is simpler.
main
|
↓
feature branch
|
↓
Pull Request
|
↓
main
Example:
git switch main
git pull
git switch -c feature/login
git add .
git commit -m "Add login functionality"
git push -u origin feature/login
Then a Pull Request is created.
After review and approval:
feature/login → main
This workflow is popular for projects where changes are integrated and deployed frequently.
18. Trunk-Based Development
Trunk-based development focuses on keeping the main branch continuously integrated.
main ──●────●────●────●────●────●──
↑ ↑ ↑ ↑
small feature changes
Branches, when used, are usually short-lived.
Instead of keeping a feature branch for several weeks, developers integrate small changes frequently.
The main idea is:
Small changes + frequent integration = fewer large conflicts.
This approach works particularly well with automated testing and Continuous Integration/Continuous Deployment (CI/CD).
19. Comparing Branching Strategies
| Strategy | Complexity | Branches | Suitable For |
|---|---|---|---|
| Git Flow | High | Many | Scheduled releases |
| GitHub Flow | Medium | Feature branches | Web applications |
| Trunk-Based | Low | Very short-lived | CI/CD and frequent deployment |
There is no single strategy that is best for every project. Teams should choose based on release frequency, team size, deployment process, and project requirements.
20. Clean Git History as Documentation
One of the most valuable Git practices is maintaining a clean and meaningful history.
Imagine running:
git log --oneline
and seeing:
a81f2 fix
91bd3 changes
7ab12 final
82abc fix again
72ccd testing
61aaa final final
This tells very little about the project.
Now compare:
a81f2 Add employee search functionality
91bd3 Add employee authentication
7ab12 Add employee management API
82abc Add PostgreSQL integration
The second history is much easier to understand.
A developer joining the project later can use the Git history to understand the evolution of the application.
Git's workflow documentation recommends small, logical commits because they make code review easier and make history more useful for later inspection and analysis.
Git history can help answer questions such as:
- When was this feature introduced?
- Which commit changed this file?
- When did this bug appear?
- Which commit introduced the database integration?
- What changes were part of version 1.0?
- Which commit should be reverted?
Therefore:
A good Git history is a form of technical documentation.
21. A Practical Workflow
Imagine you're developing a login feature.
Start with:
A---B---C main
Create your branch:
git switch -c feature/login
Make several commits:
A---B---C
\
D---E---F
Your commits might be:
D Add login form
E Fix login CSS
F Fix login validation
Before creating a Pull Request, you decide to clean the history.
Run:
git rebase -i HEAD~3
Change:
pick D Add login form
pick E Fix login CSS
pick F Fix login validation
to:
pick D Add login functionality
squash E Fix login CSS
squash F Fix login validation
Now the feature branch has a cleaner history.
Next, update it with the latest main:
git switch main
git pull
git switch feature/login
git rebase main
If the branch has already been pushed, rebasing changes its commit history, so pushing may require:
git push --force-with-lease
--force-with-lease is preferable to blindly using --force because it provides a safety check against overwriting unexpected remote updates.
22. The Golden Rules of Advanced Git
Keep these rules in mind:
Rule 1 — Rebase your own local work
Private feature branch
↓
Rebase
↓
Clean history
Rule 2 — Be careful rebasing shared history
Shared branch
↓
Avoid rewriting history
↓
Merge / Revert
Rule 3 — Use stash for unfinished work
Unfinished changes
↓
git stash
↓
Switch branch
↓
git stash pop
Rule 4 — Use cherry-pick for a specific commit
Need ONE particular change
↓
cherry-pick
Rule 5 — Use revert for safely undoing published changes
Published commit
↓
revert
↓
New reversing commit
Rule 6 — Use reset carefully
Local history cleanup
↓
reset
Rule 7 — Use tags for releases
Commit
↓
Tag
↓
v1.0.0
Conclusion
Advanced Git is not about memorizing dozens of commands. It is about understanding how and when to manipulate project history.
The most important concepts are:
merge → combine branches while preserving history
rebase → replay commits on a new base
interactive → clean/reorder/edit commits
stash → temporarily save unfinished changes
cherry-pick → copy a specific commit
revert → undo changes with a new commit
reset → move the branch pointer
tag → mark important releases
The biggest decision is often merge vs rebase.
Rebase can produce a clean and readable history, but it rewrites commits. Merge preserves the original history and is safer for shared branches. Git's own documentation emphasizes that rebasing published/shared history can create problems for developers who depend on the original commits.
Ultimately, the goal is not simply to make Git history look pretty. The goal is to make history understandable, useful, and safe for the team.
A clean history tells the story of a project—and that story can become valuable technical documentation for developers in the future.
Top comments (0)