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 -vorigin 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 -vorigin 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:
| Name | Typical use |
|---|---|
origin | Your copy (the one you cloned or push to) |
upstream | The original project you forked from |
Renaming and removing remotes#
# Rename
git remote rename origin mine
# Remove
git remote remove upstreamInspecting a remote#
git remote show originShows 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 originThis 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 --onelinePull — fetch and merge in one step#
git pullThis is equivalent to:
git fetch origin
git merge origin/mainIt downloads new commits and immediately merges them into your current branch.
Pull with rebase#
git pull --rebaseInstead 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 trueWhen to use which#
| Situation | Use |
|---|---|
| Quick sync on a solo project | git pull |
| Want to see what changed first | git fetch then inspect |
| Want clean linear history | git pull --rebase |
| Shared branch with others’ commits | git pull (merge preserves context) |
Pushing#
Basic push#
git push origin mainSends your local commits on main to the remote.
First push of a new branch#
git push -u origin feature/user-authThe -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 pushPush a specific branch#
# Push current branch
git push
# Push a different branch
git push origin feature/loginTracking 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/loginCheck how far ahead/behind you are#
git statusOn 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'tThe 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 projectStep 3: Add upstream remote#
git remote add upstream https://github.com/original-author/project.gitNow 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-readmeNever 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-readmeStep 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 mainIf your PR branch needs updating:
git checkout fix/typo-in-readme
git rebase main
git push --force-with-leasePull 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 #42Keep PRs small and focused#
| PR size | Review experience |
|---|---|
| < 200 lines | Easy to review, fast feedback |
| 200-500 lines | Reasonable, may need focused time |
| 500+ lines | Hard 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 pushThe 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 pushAvoid 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 pushStashing — 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 stashYour 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 applyNamed 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 clearCommon 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 popTags — 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" abc1234Push tags to remote#
# Push a specific tag
git push origin v1.0.0
# Push all tags
git push origin --tagsTags 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.0Delete a tag#
# Local
git tag -d v1.0.0
# Remote
git push origin --delete v1.0.0Force 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-leaseThis 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#
| Scenario | Safe? |
|---|---|
| After rebasing your own feature branch | Yes |
| Cleaning up commits before review | Yes |
| On a shared branch (main, develop) | No — never |
| After amending your last unpushed commit | Not 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-dashboardContributing 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/mainSyncing 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-leaseQuick Reference#
| Task | Command |
|---|---|
| List remotes | git remote -v |
| Add remote | git remote add name url |
| Fetch changes | git fetch origin |
| Pull (merge) | git pull |
| Pull (rebase) | git pull --rebase |
| Push | git push |
| Push new branch | git push -u origin branch |
| Set tracking | git branch --set-upstream-to=origin/branch |
| Stash work | git stash |
| Restore stash | git stash pop |
| Create tag | git tag -a v1.0.0 -m "msg" |
| Push tags | git push origin --tags |
| Safe force push | git push --force-with-lease |
| Sync fork | git 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-leaseinstead 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 trueglobally 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
mainordevelop, rewriting those breaks everyone’s local copies


