Push and Pull - Synchronizing Changes

Push and Pull - Synchronizing Changes

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 name
  • branch-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:

  1. Checks if the remote branch has commits you don't have locally
  2. If so, rejects the push (preventing data loss)
  3. If not, uploads your commits to the remote
  4. Updates the remote branch pointer to your latest commit
  5. 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:

  1. Fetches commits from the remote branch
  2. Updates your local copy of the remote branch (e.g., origin/main)
  3. Merges (or rebases) those commits into your current local branch
  4. 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 push knows where to push
  • git pull knows where to pull from
  • git status shows 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 branch
  • git push -u origin branch - Push and set up tracking
  • git push --force-with-lease - Force push safely (own branches only)

Pull - Download remote commits and integrate them:

  • git pull - Fetch and merge
  • git pull --rebase - Fetch and rebase
  • git 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 -u or git 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.