As a version control system used by millions of software teams, Git enables developers to collaborate efficiently on code. Pushing code changes to the correct Git branch is vital for organizing development and keeping the main codebase stable.
This comprehensive guide takes an in-depth look at pushing commits to a specific branch in Git, both locally and remotely.
Understanding Git Branch Workflows
Before diving into the specifics of pushing to Git branches, it‘s important to understand branch-based development workflows.
There are two main models used when working with Git branches:
Gitflow Workflow
The Gitflow model uses two main branches – master and develop, along with feature, release, and hotfix branches sprouting off develop. This provides a robust framework for managing larger projects and is widely adopted. New features and bug fixes are isolated, reviewed, and merged into develop, then periodically merged back into master for production releases.
Trunk-Based Development
Some teams favor a more simplified "Trunk" centered approach. Here developers commit small, frequent changes directly into the master branch. Short-lived feature and bug fix branches may still be used, but the key distinction is that master contains shippable code at all times. This promotes constant integration and collaboration on shared code rather than siloed branches.
Deciding which branching model works for your team depends on project size and culture. But an understanding of these workflows provides context around when and why you might push to feature or bug fix branches during development.
When Should You Push Code to a Branch?
Pushing code to branches enables isolated and parallel workstreams. Some specific cases where you would branch and push commits include:
Developing New Features
Major new features should always be built in separate branches via feature-driven development. Isolating big chunks of code keeps master stable, while allowing lots of experimentation. Once the feature is complete and tested, the branch can be approved and merged upstream.
Fixing Bugs
Similar to features, distinct branches should be spawned to address bugs. This again limits risk exposure to the core code. Bug fix branches allow developing a solution, adding test cases, getting peer reviews, and finally releasing the patched code via integration into upstream branches down the road.
Experimenting Safely
Developers often need a separate isolated environment for researching new libraries, testing cutting edge concepts, upgrading dependencies…etc. By experimenting in branches, failure is contained and can‘t break production code. Once research branches are reaped of any rewards, they can be promoted to official features or simply discarded.
Collaborating With a Team
A major incentive for branching is enabling parallel workflows across a developer team. Different engineers can claim distinct branches to own new issues or improvements. Each team member can independently research, develop locally in branches, have changes reviewed, and eventually merge their collaborative efforts upstream.
Compartmentalizing Releases
Mature projects will compartmentalize various releases using semantic versions branches like release/1.2
and release/1.3
. Hot fixes and upgrades relevant to a given release can be isolated for patching. Once changes are validated, that release branch can be merged to master to publish an updated production version.
There are other valid reasons for branching and pushing changes, but these covers some of the major scenarios. Overall, the main incentives are isolating risk, enabling experimentation, and working in parallel without impacting production code.
Step-By-Step Guide to Pushing Code to Branches
Now that we‘ve covered the key workflows and use cases for branching, let‘s outline the exact steps for pushing commits.
We‘ll cover pushing a local branch, pushing directly to a remote branch, and setting an upstream branch to track remote counterparts.
Pushing Local Branches
When starting new work locally, you should branch off to isolate changes:
> git checkout -b new-feature main
Switched to new branch ‘new-feature‘
This checkout command simultaneously creates and switches to this branch. Now code away on the new feature!
Commit changes on your branch:
> git add files...
> git commit -m "Start developing new feature X"
To share the branch, first publish it to the remote:
> git push -u origin new-feature
Branch ‘new-feature‘ set up to track remote branch ‘new-feature‘ from ‘origin‘.
The -u
flag links this branch as upstream tracking the remote. Future pushes will be automatic. Any other developers can now fetch your origin and checkout this branch or open pull requests into it.
Pushing to Existing Remote Branch
Alternatively, you may be starting from an existing remote branch that you don‘t have checked out locally:
> git branch -a
* main
remotes/origin/fixbug126
Let‘s push directly to fixbug126
on origin from local main branch:
> git fetch # Grab latest remote branches
> git push origin main:fixbug126
This pushes main onto the tip of the remote branch. Now changes are shared without requiring creating a local tracking branch first.
Setting an Upstream Branch
It‘s also common to have a local branch that you want to link up with an associated remote branch:
> git branch
main
* feature/new-module
The easiest way is to set up branch tracking:
> git branch -u origin/feature/new-module
Branch ‘feature/new-module‘ set up to track remote branch ‘feature/new-module‘ from ‘origin‘.
Now git knows that feature/new-module
is related to its remote version, simplifying pushing and pulling changes moving forward.
Resolving Git Merge Conflicts
A risk of integrating work between shared branches is merge conflicts. This occurs when competing changes alter the same portion of a file causing ambiguity that Git cannot reconcile autonomously.
Some common causes of frustrating merge conflicts include:
- Team members editing the same parts of the same files
- Rebasing branches and back-porting commits
- Large intricate changes on a branch that destabilize main
- Failing to pull latest remote commits before pushing own changes
Thankfully there are some best practices to avoid merge hell:
- Communicate with team members about who is working on what
- Pull remote changes often to surface conflicts early
- Keep branches small, focused, and short-lived
- Test branches locally and before integrating upstream
- Enable main branch protection rules to limit direct pushes
But despite best efforts, merge issues do happen. Here is the workflow to resolve them:
- Pull remote changes with merge flag:
git merge --no-ff origin/main
- Manually edit conflicted files and accept/discard changes
- Add and commit merged files:
git add/commit -m "Resolved merge conflicts"
- Push cleaned up branch with fixed conflicts:
git push
Now remote and local branches are synchronized with a linear commit history.
While messy, some developers enjoy investigating merge conflicts to understand how competing solutions impact the code. So don‘t dread conflicts too much!
Recovering from Errant Pushes
One risk specific to Git is the ability to force push changes, overwriting branch history. This can lead to integrating unwanted commits if you accidentally push to the wrong branch.
Thankfully there are multiple safety nets to recover from errant pushes both pre and post-damage:
Undo Local Commits
If caught fast enough, you can undo problematic local commits with:
> git reset HEAD~ --hard
> git push -f
This resets branch pointer to before broken commit and force overwrites remote to match local again.
Revert Commits
To surgically remove specific commits, use Git revert instead of resetting wholesale:
> git revert 07152a6
> git push
This generates a new commit that rolls back changes introduced by the target hash.
Restore Deleted Branches
If a branch was deleted entirely both remote and local, recovery takes a few more steps:
> git reflog # Find tip commit hash from lost branch
> git checkout -b my-branch-help cc12603
> git push -u origin my-branch-help
First the reflog shows branch pointer history. Then we manually recreate branch from hash and force push it to origin as reconstructed branch.
While allowing force pushes is risky by nature, thankfully Git provides escape hatches to undo mistakes both major and minor.
Best Practices for Branching & Pushing
Branching is a vital tool but also introduces complexity if overused or poorly managed:
Keep Branches Focused
Sprawling multi-purpose branches tangled with different changes are hard to review and safely integrate. Stick to small single-issue branches.
Delete Stale Branches
Prune any branches lingering too long post-merge or where work was abandoned. This keeps the environment clean and navigable.
Limit Direct Master Commits
Avoid pushing directly to master branch, even small tweaks. Reserve master for integrated, vetted code to minimize instability.
Enable Branch Protection
Restrict direct pushes to master or develop branches. Force pulls requests and peer reviews for improved quality.
Communicate Across Team
Verbally sync up on who is working on what features across the team. Surfacing overlapping work early massively avoids headaches.
Automate Testing
Set up branch-based continuous integration and delivery pipelines to automatically build and test changes before human review.
There are certainly other best practices, but these represent high leverage guiding principles related specifically to branching and pushing code changes.
Wrapping Up
Pushing commits to focused Git branches enables powerful collaboration and accelerated development while protecting the production codebase. Mastering both high-level Git workflows and hands-on branching mechanics is a vital skill for developers using this ubiquitous version control system.
Hopefully this guide shed light on:
- Common branch-based development models
- When and why to isolate work through branches
- Step-by-step instructions for pushing local branch changes
- Resolving messy merge conflicts between branches
- Recovering from faulty pushes and lost work
- Best practices for balanced use of branching
Learning these branching techniques unlocks the true power of Git for both individual developers and teams alike.
Now you have the knowledge to improve everything from solo coding techniques to large team engineering procedures by strategically using Git branches and pushes. So embrace branching and become a Git collaboration pro!