DEV Community

Sai Swaroop Bijinapalli
Sai Swaroop Bijinapalli

Posted on

Deep Git Workflows: Rebasing, Conflict Resolution and Branching Strategies

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Git combines the histories and may create a merge commit:

A---B---C---------M
     \           /
      D---E-----/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Run:

git switch feature
git rebase main
Enter fullscreen mode Exit fullscreen mode

The result becomes:

A---B---C---D'---E'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

You can inspect the last three commits with:

git rebase -i HEAD~3
Enter fullscreen mode Exit fullscreen mode

Git opens a list such as:

pick C Add login
pick D Fix login CSS
pick E Fix login validation
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Change:

pick A Add login
pick B Fix login
pick C Fix login again
pick D Fix validation
pick E Final login fix
Enter fullscreen mode Exit fullscreen mode

to:

pick A Add login
squash B Fix login
squash C Fix login again
squash D Fix validation
squash E Final login fix
Enter fullscreen mode Exit fullscreen mode

The result can become one meaningful commit:

Add login functionality
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

can become:

pick A Add login
pick C Add authentication
pick B Update README
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example:

pick A Add login
edit B Add authentication
pick C Update README
Enter fullscreen mode Exit fullscreen mode

Git stops at commit B.

You can modify the files:

git add .
git commit --amend
git rebase --continue
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

while another branch contains:

const username = "Sai";
Enter fullscreen mode Exit fullscreen mode

If both branches changed the same line, Git may produce:

<<<<<<< HEAD
const username = "Swaroop";
=======
const username = "Sai";
>>>>>>> feature
Enter fullscreen mode Exit fullscreen mode

The developer must decide what the correct code should be.

For example:

const username = "Sai Swaroop";
Enter fullscreen mode Exit fullscreen mode

Then:

git add .
git commit
Enter fullscreen mode Exit fullscreen mode

During a rebase:

git add .
git rebase --continue
Enter fullscreen mode Exit fullscreen mode

If you want to cancel the rebase:

git rebase --abort
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You don't want to commit incomplete code.

Use:

git stash
Enter fullscreen mode Exit fullscreen mode

Git temporarily stores the changes.

Now you can switch branches:

git switch main
Enter fullscreen mode Exit fullscreen mode

After finishing your other work:

git switch feature/login
git stash pop
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You only want commit E on main.

Run:

git switch main
git cherry-pick E
Enter fullscreen mode Exit fullscreen mode

You get:

A---B---C---E'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You discover that D introduced a bug.

You can run:

git revert D
Enter fullscreen mode Exit fullscreen mode

Git creates a new commit that reverses the changes from D:

A---B---C---D---R
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You run:

git reset --hard C
Enter fullscreen mode Exit fullscreen mode

Now the branch points to:

A---B---C
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Moves the branch backward but keeps the changes staged.

Mixed Reset

git reset --mixed HEAD~1
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

Create a tag:

git tag v1.0.0
Enter fullscreen mode Exit fullscreen mode

View tags:

git tag
Enter fullscreen mode Exit fullscreen mode

Push a tag:

git push origin v1.0.0
Enter fullscreen mode Exit fullscreen mode

Tags are useful for software releases:

v1.0.0
v1.1.0
v2.0.0
Enter fullscreen mode Exit fullscreen mode

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:

  1. Git Flow
  2. GitHub Flow
  3. Trunk-Based Development

16. Git Flow

Git Flow typically uses branches such as:

main
develop
feature/*
release/*
hotfix/*
Enter fullscreen mode Exit fullscreen mode

For example:

             feature/login
                  |
                  ↓
develop ----------●
                  |
                  ↓
               release
                  |
                  ↓
main -------------●
Enter fullscreen mode Exit fullscreen mode

A developer might create:

git switch -c feature/login
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then a Pull Request is created.

After review and approval:

feature/login → main
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and seeing:

a81f2 fix
91bd3 changes
7ab12 final
82abc fix again
72ccd testing
61aaa final final
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create your branch:

git switch -c feature/login
Enter fullscreen mode Exit fullscreen mode

Make several commits:

A---B---C
         \
          D---E---F
Enter fullscreen mode Exit fullscreen mode

Your commits might be:

D Add login form
E Fix login CSS
F Fix login validation
Enter fullscreen mode Exit fullscreen mode

Before creating a Pull Request, you decide to clean the history.

Run:

git rebase -i HEAD~3
Enter fullscreen mode Exit fullscreen mode

Change:

pick D Add login form
pick E Fix login CSS
pick F Fix login validation
Enter fullscreen mode Exit fullscreen mode

to:

pick D Add login functionality
squash E Fix login CSS
squash F Fix login validation
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If the branch has already been pushed, rebasing changes its commit history, so pushing may require:

git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

--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
Enter fullscreen mode Exit fullscreen mode

Rule 2 — Be careful rebasing shared history

Shared branch
      ↓
Avoid rewriting history
      ↓
Merge / Revert
Enter fullscreen mode Exit fullscreen mode

Rule 3 — Use stash for unfinished work

Unfinished changes
       ↓
git stash
       ↓
Switch branch
       ↓
git stash pop
Enter fullscreen mode Exit fullscreen mode

Rule 4 — Use cherry-pick for a specific commit

Need ONE particular change
          ↓
     cherry-pick
Enter fullscreen mode Exit fullscreen mode

Rule 5 — Use revert for safely undoing published changes

Published commit
      ↓
    revert
      ↓
New reversing commit
Enter fullscreen mode Exit fullscreen mode

Rule 6 — Use reset carefully

Local history cleanup
        ↓
      reset
Enter fullscreen mode Exit fullscreen mode

Rule 7 — Use tags for releases

Commit
  ↓
Tag
  ↓
v1.0.0
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)