Skip to main content

Git Collaboration: Remotes, Fetch, Pull, and Pull Requests

·2062 words·10 mins
Linux Learning Lab
Author
Linux Learning Lab
Writing about code, tools, and workflows.
Table of Contents
git-essentials - This article is part of a series.
Part 4: This Article

Working with Others
#

So far in this series, Git has been mostly a personal tool — tracking your own changes, branching, and exploring history. But Git was designed for collaboration. Its distributed model means every developer has a full copy of the repository and can work independently, syncing changes when ready.

This post covers the mechanics of that syncing: how remotes work, how to pull and push changes, how to contribute to projects you don’t own, and the pull request workflow that most teams use daily.

Remotes in Depth
#

A remote is a reference to another copy of the repository — usually on a server like GitHub, GitLab, or Bitbucket.

Viewing remotes
#

git remote -v
origin  git@github.com:mike/my-project.git (fetch)
origin  git@github.com:mike/my-project.git (push)

origin is the default name for the remote you cloned from. You can have multiple remotes.

Adding remotes
#

# Add a remote
git remote add upstream https://github.com/original-author/project.git

# Verify
git remote -v
origin    git@github.com:mike/project.git (fetch)
origin    git@github.com:mike/project.git (push)
upstream  https://github.com/original-author/project.git (fetch)
upstream  https://github.com/original-author/project.git (push)

Common remote names:

NameTypical use
originYour copy (the one you cloned or push to)
upstreamThe original project you forked from

Renaming and removing remotes
#

# Rename
git remote rename origin mine

# Remove
git remote remove upstream

Inspecting a remote
#

git remote show origin

Shows the remote’s URL, tracked branches, and which local branches are configured to push/pull from it.

Fetch vs Pull
#

These two commands both get changes from a remote, but they behave differently.

Fetch — download without changing anything
#

git fetch origin

This downloads new commits and branches from the remote but doesn’t touch your working directory or current branch. The changes sit in origin/main, origin/feature/xyz, etc. — remote-tracking branches that you can inspect before deciding what to do.

# See what's new
git fetch origin
git log main..origin/main --oneline

# Look at a remote branch
git log origin/feature/new-api --oneline

Pull — fetch and merge in one step
#

git pull

This is equivalent to:

git fetch origin
git merge origin/main

It downloads new commits and immediately merges them into your current branch.

Pull with rebase
#

git pull --rebase

Instead of creating a merge commit, this replays your local commits on top of the remote changes. Keeps history linear:

# Without rebase (merge commit):
A---B---C---M  (main)
         \ /
          D    (your commit)

# With rebase (linear):
A---B---C---D  (main, your commit replayed on top)

To make rebase the default for pull:

git config --global pull.rebase true

When to use which
#

SituationUse
Quick sync on a solo projectgit pull
Want to see what changed firstgit fetch then inspect
Want clean linear historygit pull --rebase
Shared branch with others’ commitsgit pull (merge preserves context)

Pushing
#

Basic push
#

git push origin main

Sends your local commits on main to the remote.

First push of a new branch
#

git push -u origin feature/user-auth

The -u flag sets up tracking so future pushes only need git push.

Push rejected — remote has new commits
#

If someone else pushed while you were working:

! [rejected]        main -> main (non-fast-forward)

You need to integrate their changes first:

# Option 1: Pull and merge
git pull
git push

# Option 2: Pull with rebase (cleaner)
git pull --rebase
git push

Push a specific branch
#

# Push current branch
git push

# Push a different branch
git push origin feature/login

Tracking Branches
#

A tracking branch is a local branch linked to a remote branch. When they’re linked, git push and git pull know where to send/get changes without specifying the remote and branch name.

# See tracking relationships
git branch -vv
* main          9f4a2b1 [origin/main] Add password reset endpoint
  feature/login 3a1b2c3 [origin/feature/login] Fix session handling
  experiment    abc1234 (no tracking)

Set up tracking
#

# When pushing a new branch
git push -u origin feature/login

# For an existing branch
git branch --set-upstream-to=origin/feature/login feature/login

Check how far ahead/behind you are
#

git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.

Or:

git log origin/main..main --oneline    # Commits you have that remote doesn't
git log main..origin/main --oneline    # Commits remote has that you don't

The Fork and Pull Request Workflow
#

Most open source projects and many teams use this workflow. You don’t push directly to the main repository — you fork it, work in your fork, and submit a pull request.

Step 1: Fork
#

On GitHub/GitLab, click “Fork” to create your own copy of the repository under your account.

Step 2: Clone your fork
#

git clone git@github.com:mike/project.git
cd project

Step 3: Add upstream remote
#

git remote add upstream https://github.com/original-author/project.git

Now you have:

  • origin — your fork (you can push here)
  • upstream — the original project (read-only for you)

Step 4: Create a feature branch
#

git checkout -b fix/typo-in-readme

Never work directly on main in the fork — keep it clean for syncing with upstream.

Step 5: Make changes and push to your fork
#

# Work...
git add .
git commit -m "Fix typo in installation section"
git push -u origin fix/typo-in-readme

Step 6: Open a pull request
#

On GitHub/GitLab, create a pull request from your branch to the original project’s main branch. Include:

  • A clear title describing the change
  • Context about what and why in the description
  • Reference any related issues

Step 7: Keep your fork in sync
#

While your PR is being reviewed (or anytime):

git checkout main
git fetch upstream
git merge upstream/main
git push origin main

If your PR branch needs updating:

git checkout fix/typo-in-readme
git rebase main
git push --force-with-lease

Pull Request Best Practices
#

Writing a good PR
#

A pull request is a communication tool. Make it easy for reviewers:

## Summary
Add rate limiting to the login endpoint to prevent brute force attacks.

## Changes
- Add express-rate-limit middleware to auth routes
- Configure: 5 attempts per 15 minutes per IP
- Return 429 status with Retry-After header
- Add tests for rate limiting behavior

## Testing
- Unit tests pass (npm test)
- Manual test: confirmed 429 after 5 rapid requests
- Verified Retry-After header value is correct

## Related
Closes #42

Keep PRs small and focused
#

PR sizeReview experience
< 200 linesEasy to review, fast feedback
200-500 linesReasonable, may need focused time
500+ linesHard to review well, consider splitting

One PR should do one thing. If you fixed a bug and refactored nearby code, consider splitting into two PRs.

Responding to review feedback
#

# Make requested changes
git add .
git commit -m "Address review: add input validation"
git push

The PR updates automatically. Some teams prefer you squash fixup commits before merge — others don’t. Follow the project’s convention.

Working on Shared Branches
#

When multiple people push to the same branch (common on main or develop):

Stay current
#

# Before starting work each day
git pull

# Before pushing
git pull --rebase
git push

Avoid conflicts
#

  • Communicate about who’s working on what
  • Keep changes small and push frequently
  • Pull often — the longer you wait, the bigger the divergence
  • Don’t rewrite history on shared branches (no rebase, no force push)

Handle diverged branches
#

git push
# ! [rejected] — remote has commits you don't have
# Merge approach
git pull
# Resolve any conflicts...
git push

# Rebase approach (cleaner history)
git pull --rebase
# Resolve any conflicts...
git push

Stashing — Temporary Shelving
#

Stash lets you save uncommitted work temporarily — useful when you need to pull or switch branches but aren’t ready to commit.

Save current work
#

git stash

Your working directory is now clean. The changes are saved in the stash.

Restore stashed work
#

# Apply most recent stash and remove it from the list
git stash pop

# Apply without removing from list
git stash apply

Named stashes
#

git stash push -m "WIP: login form validation"

List and manage stashes
#

# List all stashes
git stash list

# Apply a specific stash
git stash apply stash@{2}

# Drop a stash
git stash drop stash@{0}

# Clear all stashes
git stash clear

Common stash workflow
#

# You're mid-work but need to pull or fix something else
git stash

# Do the other thing
git pull
# or: git checkout main && fix bug && git checkout -

# Come back to your work
git stash pop

Tags — Marking Important Points
#

Tags mark specific commits — typically releases.

Create a tag
#

# Lightweight tag
git tag v1.0.0

# Annotated tag (recommended — includes message and metadata)
git tag -a v1.0.0 -m "Release version 1.0.0"

# Tag a specific commit
git tag -a v1.0.0 -m "Release 1.0.0" abc1234

Push tags to remote
#

# Push a specific tag
git push origin v1.0.0

# Push all tags
git push origin --tags

Tags don’t push automatically with git push.

List and inspect tags
#

# List all tags
git tag

# Filter tags
git tag -l "v1.*"

# Show tag details
git show v1.0.0

Delete a tag
#

# Local
git tag -d v1.0.0

# Remote
git push origin --delete v1.0.0

Force Push — When and How
#

Force pushing overwrites remote history. It’s necessary after rebasing a branch, but dangerous on shared branches.

Safe force push
#

git push --force-with-lease

This only force pushes if the remote branch is where you expect it to be. If someone else pushed in the meantime, it fails instead of overwriting their work.

When force push is appropriate
#

ScenarioSafe?
After rebasing your own feature branchYes
Cleaning up commits before reviewYes
On a shared branch (main, develop)No — never
After amending your last unpushed commitNot needed (regular push works)

Rule of thumb
#

If anyone else might have pulled the branch, don’t force push it. Use --force-with-lease as a safety net, and only on branches you own.

Practical Workflows
#

Daily workflow on a team
#

# Start of day — sync up
git checkout main
git pull

# Start your work
git checkout -b feature/new-dashboard

# Work throughout the day...
git add .
git commit -m "Add dashboard layout"

# End of day — push your branch
git push -u origin feature/new-dashboard

Contributing to an open source project
#

# Fork on GitHub, then:
git clone git@github.com:mike/open-source-project.git
cd open-source-project
git remote add upstream https://github.com/org/open-source-project.git

# Create a branch for your contribution
git checkout -b fix/improve-error-messages

# Make changes, commit, push to your fork
git push -u origin fix/improve-error-messages

# Open PR on GitHub from your branch to upstream/main

Syncing a long-running branch with main
#

git checkout feature/big-refactor
git fetch origin
git rebase origin/main

# If conflicts arise during rebase:
# 1. Fix the conflicted files
# 2. git add the resolved files
# 3. git rebase --continue

git push --force-with-lease

Quick Reference
#

TaskCommand
List remotesgit remote -v
Add remotegit remote add name url
Fetch changesgit fetch origin
Pull (merge)git pull
Pull (rebase)git pull --rebase
Pushgit push
Push new branchgit push -u origin branch
Set trackinggit branch --set-upstream-to=origin/branch
Stash workgit stash
Restore stashgit stash pop
Create taggit tag -a v1.0.0 -m "msg"
Push tagsgit push origin --tags
Safe force pushgit push --force-with-lease
Sync forkgit fetch upstream && git merge upstream/main

Best Practices
#

  • Fetch before making decisions — see what’s changed on the remote before merging or rebasing
  • Use --force-with-lease instead of --force — it prevents accidentally overwriting someone else’s work
  • Keep your fork’s main branch clean — only sync it from upstream, never commit directly to it
  • Pull with rebase for cleaner history on feature branches — set pull.rebase true globally if your team prefers linear history
  • Push feature branches early — even before they’re ready for review, pushing creates a remote backup
  • Write descriptive PR descriptions — reviewers shouldn’t have to read every diff to understand the intent
  • Keep PRs small — large PRs get superficial reviews; small PRs get thoughtful ones
  • Use stash instead of making “WIP” commits you’ll need to clean up later
  • Tag releases — tags are cheap and make it trivial to check out or compare any release
  • Never force push shared branches — if others work off main or develop, rewriting those breaks everyone’s local copies
git-essentials - This article is part of a series.
Part 4: This Article