Push and Pull - Synchronizing Changes
Introduction
Git is designed for collaboration, and the key to effective collaboration is synchronizing your work with others. The git push and git pull commands are your primary tools for sharing your commits with teammates and receiving their changes. Understanding how these commands work, when to use them, and how to handle the challenges that arise is essential for smooth teamwork.
This lesson dives deep into pushing and pulling, covering best practices, common scenarios, and how to resolve synchronization issues.
Understanding Push and Pull
At its core, Git synchronization involves two operations:
Push - Upload your local commits to a remote repository, sharing your work with others.
Pull - Download commits from a remote repository and integrate them into your local branch.
These operations keep your local repository in sync with the remote, enabling collaboration while maintaining Git's distributed nature—everyone has a complete copy of the repository.
The Remote Repository
Before diving into push and pull, understand what you're synchronizing with.
Remote - A version of your repository hosted elsewhere (GitHub, GitLab, Bitbucket, or a private server).
Origin - The default name for the primary remote repository. When you clone a repository, Git automatically names that remote "origin."
Remote branches - References to branches as they exist on the remote. Named like origin/main or origin/feature-branch.
View your remotes:
git remote -v
Output:
origin https://github.com/username/repo.git (fetch)
origin https://github.com/username/repo.git (push)
Git Push: Sharing Your Work
Pushing uploads your local commits to the remote repository, making them available to your team.
Basic Push
After making and committing changes locally:
git push
Git pushes your current branch to its tracked remote branch. This only works after you've set up tracking (covered below).
First-Time Push (Setting Up Tracking)
When pushing a branch for the first time:
git push -u origin branch-name
Breaking this down:
git push- The push command-u- Set upstream tracking (short for--set-upstream)origin- The remote namebranch-name- The branch to push
The -u flag creates a tracking relationship. After this, you can simply use git push without specifying the remote and branch.
Example:
git switch -c feature-login
# Make commits
git push -u origin feature-login
# Future pushes on this branch:
git push
Pushing Specific Branch
Push a branch explicitly without setting up tracking:
git push origin branch-name
This pushes once but doesn't create a tracking relationship.
Pushing All Branches
Push all local branches to remote:
git push --all origin
Use cautiously—typically you push branches individually as they're ready.
Pushing to Different Remote
If you have multiple remotes:
git push backup main
This pushes main to the backup remote instead of origin.
What Happens During Push
When you push, Git:
- Checks if the remote branch has commits you don't have locally
- If so, rejects the push (preventing data loss)
- If not, uploads your commits to the remote
- Updates the remote branch pointer to your latest commit
- Updates your local tracking branch reference (e.g.,
origin/main)
Push is safe by design - Git won't let you accidentally overwrite others' work.
Git Pull: Getting Others' Work
Pulling downloads commits from the remote and integrates them into your local branch.
Basic Pull
Update your current branch with remote changes:
git pull
This fetches commits from the tracked remote branch and merges them into your current branch.
What Pull Actually Does
git pull is a combination of two commands:
git fetch origin # Download commits
git merge origin/main # Merge them into current branch
Understanding this separation helps when things go wrong.
Pull Specific Branch
Pull from a specific remote and branch:
git pull origin main
Explicitly states which remote and branch to pull from.
Pull with Rebase
Instead of merging, you can rebase your changes on top of the remote changes:
git pull --rebase
This creates a linear history instead of a merge commit. We'll explore when to use this later.
What Happens During Pull
When you pull, Git:
- Fetches commits from the remote branch
- Updates your local copy of the remote branch (e.g.,
origin/main) - Merges (or rebases) those commits into your current local branch
- Updates your working directory to reflect the merged result
If conflicts occur, Git pauses and asks you to resolve them.
Git Fetch: Downloading Without Merging
Sometimes you want to see what's changed remotely without merging immediately.
Basic Fetch
Download all remote changes:
git fetch origin
This updates your local copies of remote branches (like origin/main) but doesn't touch your local branches.
Fetch Specific Branch
git fetch origin branch-name
Fetches only one branch from the remote.
Fetch All Remotes
If you have multiple remotes:
git fetch --all
Updates from all configured remotes.
Why Use Fetch?
Fetch gives you control over when to integrate changes:
git fetch origin # Download updates
git log origin/main # Review what's new
git diff origin/main # See differences
git merge origin/main # Merge when ready
This workflow is safer when you want to review changes before integrating them.
Fetch vs. Pull:
- Fetch - Downloads changes, doesn't modify your working directory
- Pull - Downloads changes AND merges them immediately
Use fetch when you want to review first, pull when you're ready to integrate immediately.
Tracking Branches
Tracking branches create a relationship between local and remote branches, simplifying push and pull operations.
Setting Up Tracking
During first push:
git push -u origin feature-branch
For existing local branch:
git branch --set-upstream-to=origin/feature-branch
Or shorter:
git branch -u origin/feature-branch
During checkout: When you switch to a remote branch that doesn't exist locally, Git automatically creates a tracking branch:
git switch feature-branch
# If origin/feature-branch exists, creates local tracking branch
Viewing Tracking Relationships
git branch -vv
Output:
* main a3f2e9b [origin/main] Latest commit
feature-login d8c5f1a [origin/feature-login: ahead 2] Add login
feature-new e7b4c9a No tracking information
This shows:
- Which remote branch each local branch tracks
- Whether you're ahead (unpushed commits) or behind (unpulled commits)
Benefits of Tracking
With tracking set up:
git pushknows where to pushgit pullknows where to pull fromgit statusshows sync status with remote- Commands are simpler and less error-prone
Synchronization Workflows
Daily Workflow: Start of Day
Begin work by getting latest changes:
git switch main
git pull # Get updates
git switch feature-branch
git merge main # Integrate updates into feature
# Or: git rebase main
This ensures you're building on the latest code.
Daily Workflow: End of Day
Share your progress:
git status # Check what's uncommitted
git add .
git commit -m "WIP: Feature progress"
git push # Share with team
Even incomplete work can be pushed (to feature branches, not main) for backup and collaboration.
Collaborative Feature Development
Multiple developers working on the same feature branch:
# Developer A:
git pull # Get latest
# Make changes
git commit -am "Add login form"
git push
# Developer B (later):
git pull # Gets Developer A's changes
# Make changes
git commit -am "Add login validation"
git push
Regular pulling keeps everyone in sync.
Preparing to Merge Feature to Main
Before merging your feature into main, sync with latest main:
git switch main
git pull # Get latest main
git switch feature-branch
git merge main # Integrate main into feature
# Resolve any conflicts
git push # Push integrated feature
# Now create pull request to merge feature into main
This catches integration issues before the official merge.
Push and Pull with Branches
Pushing New Branch
Create and share a new branch:
git switch -c feature-new-api
# Make commits
git push -u origin feature-new-api
The branch now exists on the remote, and others can access it.
Pulling Someone Else's Branch
Teammate created a branch you want to work on:
git fetch origin
git switch feature-new-api
# Git automatically creates local tracking branch
Or explicitly:
git fetch origin
git switch -c feature-new-api origin/feature-new-api
Deleting Remote Branch
After merging, delete the feature branch from remote:
git push origin --delete feature-branch
The branch is removed from the remote but remains locally until you delete it:
git branch -d feature-branch
Pushing Tags
Tags aren't pushed by default:
git tag v1.0.0
git push origin v1.0.0
Or push all tags:
git push --tags
Handling Push Rejection
The most common issue with pushing is rejection due to remote having commits you don't have.
Understanding the Error
git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:user/repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
What this means: Someone else pushed commits to the remote branch since you last pulled. Your local branch doesn't include those commits.
Solution: Pull Then Push
git pull # Get remote changes
# Resolve any conflicts if necessary
git push # Now push succeeds
Why Git does this: Prevents you from overwriting others' work. Git forces you to integrate their changes first.
Visualizing the Problem
Remote: A---B---C---D (someone else pushed D)
Local: A---B---C---E (you're trying to push E)
These histories have diverged. Git requires you to merge them:
After pull: A---B---C---D---M (merge commit)
\ /
E---/
Now you can push M.
Force Pushing (Danger Zone)
Force pushing overwrites remote history with your local history. Use with extreme caution.
When Force Push is Necessary
Amending pushed commits - You amended a commit already pushed:
git commit --amend
git push --force-with-lease origin feature-branch
Rebasing pushed branch - You rebased your feature branch:
git rebase main
git push --force-with-lease origin feature-branch
Only on your own feature branches - Never force push shared branches like main.
Force Push vs. Force-with-Lease
git push --force - Overwrites remote unconditionally. Dangerous.
git push --force-with-lease - Safer. Checks that the remote hasn't changed since you last fetched.
Always use --force-with-lease when force pushing:
git push --force-with-lease origin feature-branch
If someone else pushed to the branch, this command fails, protecting their work. Regular --force would overwrite it silently.
When NOT to Force Push
Never force push to:
- Main/master branch
- Shared feature branches with multiple developers
- Any branch others depend on
Why: Force pushing rewrites history. Others working on that branch will have their work orphaned, causing major headaches.
Pull Conflicts
When pulling, conflicts can occur if you and someone else modified the same lines.
Recognizing Pull Conflicts
git pull
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git merged what it could but paused for you to resolve conflicts manually.
Resolving Pull Conflicts
Step 1: Check conflicted files
git status
Shows which files have conflicts:
Unmerged paths:
both modified: index.html
Step 2: Open conflicted file
You'll see conflict markers:
<header>
<<<<<<< HEAD
<h1>Welcome to My Site</h1>
=======
<h1>Welcome to Our Website</h1>
>>>>>>> origin/main
</header>
Conflict markers explained:
<<<<<<< HEAD- Your local changes start=======- Separator>>>>>>> origin/main- Remote changes end
Step 3: Resolve conflict
Edit the file to keep what you want:
<header>
<h1>Welcome to Our Website</h1>
</header>
Remove conflict markers and choose (or combine) the changes.
Step 4: Stage resolved file
git add index.html
Step 5: Complete the merge
git commit
Git opens editor with default merge message. Save and close to complete.
Step 6: Push the resolution
git push
Your merged changes are now shared.
Aborting a Conflicted Pull
If conflicts are too complex:
git merge --abort
Returns to state before pulling. You can try pulling again later or ask teammates for help.
Checking Sync Status
Before pushing or pulling, check your sync status.
Comparing Local and Remote
See commits you'll push:
git log origin/main..HEAD --oneline
Shows commits on your local branch not yet on remote.
See commits you'll pull:
git log HEAD..origin/main --oneline
Shows commits on remote not yet in your local branch.
See divergence:
git log --oneline --graph --all
Visual graph showing how local and remote branches relate.
Git Status Hints
After fetching, git status shows sync status:
git fetch origin
git status
Output:
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Or:
Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
Or:
Your branch and 'origin/main' have diverged,
and have 2 and 3 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
These messages guide you on what action to take.
Pull Strategies
Merge (Default)
Creates merge commits when integrating remote changes:
git pull
Pros:
- Preserves complete history
- Shows when integration happened
- Safe and predictable
Cons:
- Creates merge commits (some find this noisy)
- History can become complex with many merges
Rebase
Replays your commits on top of remote changes:
git pull --rebase
Pros:
- Linear history (no merge commits)
- Cleaner, easier-to-read history
Cons:
- Rewrites commit history (can be confusing)
- Conflicts can be harder to resolve
When to use each:
- Merge - Default for most situations, especially on shared branches
- Rebase - When maintaining linear history is important, typically on feature branches
Setting Default Pull Strategy
Configure your preference globally:
git config --global pull.rebase false # Always merge
Or:
git config --global pull.rebase true # Always rebase
Or let Git decide based on branch type:
git config --global pull.rebase interactive
Best Practices
Pull before pushing - Always get latest changes before pushing:
git pull
git push
Commit before pulling - Don't pull with uncommitted changes. Either commit or stash first:
git status # Check for uncommitted work
git commit -am "message" # Commit it
git pull # Then pull
Push regularly - Don't let local and remote diverge too much. Push at least daily when actively working.
Use descriptive commit messages - Others pull your commits. Make messages clear.
Don't force push shared branches - Only force push your own feature branches when necessary.
Fetch regularly - Even if not pulling, fetch to stay aware of remote changes:
git fetch origin
Communicate with team - Before force pushing or rebasing shared branches, coordinate with teammates.
Keep main clean - Only push tested, working code to main branch. Use feature branches for development.
Review before pushing - Check what you're about to push:
git log origin/main..HEAD # See unpushed commits
git diff origin/main # See unpushed changes
Troubleshooting Common Issues
Issue: Changes Not Appearing After Pull
Problem: You pulled but don't see expected changes.
Causes:
- Pulled wrong branch
- Changes on different remote
- Didn't switch to updated branch
Solution:
git branch -vv # Verify current branch and tracking
git fetch --all # Fetch from all remotes
git log origin/main # Check if commits are there
Issue: Lost Commits After Pull
Problem: Your commits disappeared after pulling.
Cause: Usually a rebase issue or accidentally checking out wrong branch.
Solution: Use reflog to recover:
git reflog # Find your lost commits
git reset --hard HEAD@{n} # Return to previous state
Issue: Merge Conflicts Every Pull
Problem: Constantly getting conflicts.
Causes:
- Working on same files as teammates
- Not pulling frequently enough
- Auto-generated files being tracked
Solutions:
- Communicate about who's working where
- Pull more frequently (several times daily)
- Add generated files to .gitignore
- Consider breaking work into smaller, more focused features
Issue: Can't Push or Pull (Authentication)
Problem: "Authentication failed" error.
Solutions:
- Verify credentials (use personal access token, not password)
- Check SSH key is added to your account
- Verify remote URL:
git remote -v - Test connection:
ssh -T git@github.com
Summary
Pushing and pulling are essential for collaboration. Key concepts:
Push - Upload local commits to remote, sharing your work:
git push- Push current branchgit push -u origin branch- Push and set up trackinggit push --force-with-lease- Force push safely (own branches only)
Pull - Download remote commits and integrate them:
git pull- Fetch and mergegit pull --rebase- Fetch and rebasegit fetch- Download without integrating (safer)
Best practices:
- Pull before pushing
- Commit before pulling
- Push regularly
- Never force push shared branches
- Review before pushing
- Communicate with team
Handling conflicts:
- Edit conflicted files
- Remove conflict markers
- Stage and commit resolution
- Or abort with
git merge --abort
Tracking branches:
- Simplify push/pull commands
- Show sync status in
git status - Set up with
git push -uorgit branch -u
Mastering push and pull enables smooth collaboration, keeping your team synchronized while preserving everyone's work safely. These commands are the heartbeat of team-based Git workflows—use them confidently and regularly.
In the next lesson, we'll explore collaboration workflows in depth, including pull requests, code reviews, and team strategies for managing shared repositories effectively.