66 66. Sharing a Repository: Collaborators and Forks
Until now, only you pushed to your GitHub repository. This page covers the two ways to work on a repository with other people - collaborators on one shared repository, and forks - and the habits that keep a shared project running smoothly.
66.1 Two ways to work together
| Collaborators (one shared repository) | Fork (your own copy) | |
|---|---|---|
| What it is | The owner invites people; everyone pushes to the same repository | You copy someone else’s repository into your own GitHub account |
| Who can push to the original? | Every collaborator | Only its owners; you propose changes with a pull request |
| Typical use | A class team project, a small team | Open-source projects, starter code from a course or tutorial, any repository you can’t push to |
66.2 Adding collaborators
Maria (GitHub username maria-lopez) wants Sam to work on her git-tutorial-project. On GitHub she:
- Opens the repository and clicks Settings (under the repository name).
- In the Access section of the sidebar, clicks Collaborators.
- Clicks Add people, types Sam’s GitHub username, picks him from the list, and confirms (Add NAME to REPOSITORY).
Sam receives an email invitation. Only after he accepts it can he push to the repository. (If you sign in to Git with a personal access token: GitHub doesn’t let fine-grained tokens push to a repository where you are a collaborator, so use browser sign-in or a classic token instead.) Then he clones it, just as on Cloning and Pulling:
git clone https://github.com/maria-lopez/git-tutorial-project.gitOutput (progress lines omitted):
Cloning into 'git-tutorial-project'...
Sam now has the full project and history, with origin pointing at Maria’s repository. Every collaborator follows the same routine:
git pullbefore you start, so you have everyone’s latest work.- Work and commit (ideally on a branch, as on the next page).
git pullagain just before you push.git push.
66.3 When your push is rejected
On Cloning and Pulling you saw that GitHub rejects a push when it has commits you don’t have, and that the fix is git pull, then git push. On a team this happens all the time, because everyone pushes to the same main.
Sam adds a “Team” section to the README, commits (“Add team section to README”), and pushes. Meanwhile Maria, who hasn’t pulled, adds max-width to css/style.css, commits (“Limit page width”), and pushes:
git pushOutput (the hint: lines that follow are omitted):
To https://github.com/maria-lopez/git-tutorial-project.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/maria-lopez/git-tutorial-project.git'
The two main branches have simply moved apart:
On GitHub: A---B---S (S = Sam's commit)
On Maria's laptop: A---B---L (L = Maria's commit)
A push may only add commits on top of what is already on GitHub. Pushing Maria’s main as it is would replace S with L and throw away Sam’s work, so Git refuses. Maria pulls and then pushes:
git pull
git pushOutput (progress lines omitted):
From https://github.com/maria-lopez/git-tutorial-project
cd24c15..6ea40b6 main -> origin/main
Merge made by the 'ort' strategy.
README.md | 5 +++++
1 file changed, 5 insertions(+)
To https://github.com/maria-lopez/git-tutorial-project.git
6ea40b6..333e3a2 main -> main
Because both sides had new commits, and Git is set to merge when it pulls (the pull.rebase false setting from Installing and Configuring Git), the pull created a merge commit M. In between, Git opened the editor with the message Merge branch 'main' of https://github.com/maria-lopez/git-tutorial-project; Maria saved and closed it (in nano: Ctrl+O, Enter, Ctrl+X). Sam’s commit is now part of her main, so the push could simply add to GitHub’s branch:
A---B---L---M <- main (Maria)
\ /
'-S-' <- Sam's commit, from GitHub
If Sam and Maria had changed the same lines, the pull would have stopped with a conflict; she would resolve it as on Merge Conflicts and then push.
You may see either of two rejection messages. Both mean “GitHub has commits that your branch doesn’t include yet”, and both are fixed by pulling first:
| Message | What it means |
|---|---|
! [rejected] main -> main (fetch first) |
GitHub has commits you have never downloaded. |
! [rejected] main -> main (non-fast-forward) |
You downloaded them (with git fetch), but you haven’t merged them into your branch yet. |
66.4 git fetch vs. git pull
git fetch downloads new commits from GitHub and updates origin/main (Git’s record of GitHub’s main), but it doesn’t change your files or your own main. git pull is a fetch followed by a merge (or a rebase, if you configured that).
| Command | Downloads new commits? | Changes your branch and files? |
|---|---|---|
git fetch |
Yes (updates origin/main) |
No |
git pull |
Yes | Yes |
Fetching first lets you look before you merge. After Maria’s push, Sam runs:
git fetch
git statusOutput (progress lines omitted):
From https://github.com/maria-lopez/git-tutorial-project
6ea40b6..333e3a2 main -> origin/main
On branch main
Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
git log --oneline --graph --all would now show the two new commits on origin/main. When he is ready, Sam merges them, which is exactly the second half of a pull:
git merge origin/mainOutput:
Updating 6ea40b6..333e3a2
Fast-forward
css/style.css | 1 +
1 file changed, 1 insertion(+)
Instead of creating a merge commit, git pull --rebase takes your new local commits and replays them on top of the commits it downloaded, so history stays one straight line. Before (you committed L locally while a teammate pushed T):
.---L <- main (your laptop)
/
A---B---T <- origin/main
After git pull --rebase:
A---B---T---L' <- main
^
origin/main
L' has the same changes as L but a new commit ID. Git prints Successfully rebased and updated refs/heads/main., and you can push. To make rebasing the default, set git config --global pull.rebase true instead of false.
Rebasing rewrites the commits being replayed, so only rebase commits that nobody else has yet (your unpushed local commits, which is exactly what git pull --rebase does). If a rebase hits a conflict, fix the file, run git add, then git rebase --continue (or git rebase --abort to undo it).
66.5 Forks
A fork is your own copy of someone else’s repository, made on GitHub and stored under your account. You can push to your fork freely; to suggest your changes to the original project, you open a pull request (next page). The original repository is usually called upstream.
A fork is not the same as a clone: a fork is a copy on GitHub; a clone is a copy on your computer. Usually you do both:
ORIGINAL-OWNER/REPO on GitHub <-- the original ("upstream")
| ^
| 1. Fork | 5. Pull request
v |
YOUR-USERNAME/REPO on GitHub <-- your fork ("origin")
| ^
| 2. git clone | 4. git push
v |
REPO folder on your computer <-- 3. branch, edit, commit
To fork, open the original repository on GitHub, click Fork, choose your account as the owner, and click Create fork. (The tutorial step Fork the repo and clone it does exactly this with the tutorial’s starter code.) Then clone your fork and add a second remote, conventionally named upstream, that points at the original:
git clone https://github.com/YOUR-USERNAME/REPO.git
cd REPO
git remote add upstream https://github.com/ORIGINAL-OWNER/REPO.git
git remote -vOutput (progress lines omitted):
Cloning into 'REPO'...
origin https://github.com/YOUR-USERNAME/REPO.git (fetch)
origin https://github.com/YOUR-USERNAME/REPO.git (push)
upstream https://github.com/ORIGINAL-OWNER/REPO.git (fetch)
upstream https://github.com/ORIGINAL-OWNER/REPO.git (push)
Your fork doesn’t update itself when the original changes. To bring in the original’s new commits, fetch from upstream, merge, and push to your fork:
git fetch upstream
git switch main
git merge upstream/main
git pushOn GitHub, the Sync fork button on your fork’s page (then Update branch) does the same for the fork on GitHub; afterwards, run git pull on your computer. If you use the GitHub CLI, gh repo fork ORIGINAL-OWNER/REPO --clone creates the fork and clones it in one step (run git remote -v afterwards to see the remotes it set up).
On your own repositories and forks, GitHub may show a banner: “Your main branch isn’t protected. Protect this branch from force pushing or deletion, or require status checks before merging.” It is not an error, and nothing is wrong with your repository. GitHub is suggesting a safety setting, a ruleset, that stops anyone, including you or an AI agent, from deleting main, force-pushing over it or pushing to it without a pull request. You can dismiss it for a practice project. To turn the protection on, see “Protecting main” in Pull Requests and Code Review.
66.6 Team etiquette
- Pull before you start; push when you finish. The longer you wait, the further your copy drifts from everyone else’s.
- Small commits with clear messages. Your teammates have to read them.
- Use branches and pull requests instead of everyone committing straight to
main(see Pull Requests and Code Review). - Never commit secrets such as API keys (see .gitignore and Keeping Secrets Out of Git).
When a push is rejected, you may find advice (sometimes from an AI) to run git push --force. A force push makes GitHub’s branch match yours exactly, deleting any commits on GitHub that you don’t have, such as your teammate’s work from this morning. On a shared branch like main, always pull and then push instead. The variant git push --force-with-lease refuses if GitHub’s branch changed since you last fetched, but it still rewrites shared history. Repository owners can protect main on GitHub with a ruleset that blocks force pushes (see Pull Requests and Code Review).
66.7 Key takeaways
- Collaborators share one repository and can all push; a fork is your own copy of someone else’s repository, and you contribute back with pull requests.
- A rejected push (“fetch first” or “non-fast-forward”) means GitHub has commits you don’t. Fix it with
git pull, thengit push. git fetchonly downloads;git pulldownloads and merges (or rebases).- In a fork,
originis your fork andupstreamis the original; update withgit fetch upstreamandgit merge upstream/main. - Never force-push a shared branch.
Practice Questions
Easy
Question 1 · Multiple choice
In which situation do you need a fork rather than being added as a collaborator?
- You and two classmates are building your team’s project in a repository one of you owns.
- You want to contribute a fix to a public open-source project whose owners you don’t know.
- You want to work on your own repository from a second computer.
- You want to see the history of your own project.
Question 2 · True or false
As soon as Maria adds Sam as a collaborator on GitHub, Sam can push to her repository, even before he responds to the invitation.
Question 3 · Matching
Match each command with what it does.
| Command | What it does | |
|---|---|---|
1. git clone URL |
A. Download new commits and merge them into your current branch | |
2. git fetch |
B. Send your new commits to the remote | |
3. git pull |
C. Copy a repository from GitHub to your computer | |
4. git push |
D. Connect your clone to the original repository of a fork | |
5. git remote add upstream URL |
E. Download new commits without changing your files or branch |
Question 4 · Short answer
What is origin/main?
Question 5 · True or false
git fetch changes the files in your working directory.
Question 6 · Short answer
You forked a project and cloned your fork. By convention, what do the remotes origin and upstream point to?
Medium
Question 7 · Read the output
Maria runs git push and sees:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/maria-lopez/git-tutorial-project.git'
What happened, and what should she do?
Question 8 · Write the command
Write the commands Maria uses to fix the rejected push in Question 7, and describe what might happen between the two commands.
Question 9 · Read the output
After running git fetch, Sam sees this in git status:
Your branch is behind 'origin/main' by 2 commits, and can be fast-forwarded.
What does this mean, and which command brings his main up to date?
Question 10 · Compare
A rejected push can end with (fetch first) or with (non-fast-forward). What is the difference between the two situations, and what do you do in each?
Question 11 · Write the command
Your fork already has an upstream remote. The original project has new commits on main. Write the commands to bring them into your local main and then update your fork on GitHub.
Question 12 · Ordering
Put these steps for contributing to an open-source project in order:
- A. Push your branch to your fork
- B. Click Fork on the original repository
- C. Open a pull request to the original repository
- D. Clone your fork to your computer
- E. Create a branch and commit your changes
Question 13 · Multiple choice
Which statement about git pull is correct?
- It uploads your commits to GitHub.
- It downloads new commits and then merges (or rebases) them into your current branch.
- It only works in forked repositories.
- It deletes your local commits and replaces them with GitHub’s.
Hard
Question 14 · Short answer
Explain why Git refuses a push when GitHub’s main has a commit you don’t have. What would happen to that commit if Git allowed the push?
Question 15 · Scenario
Sam’s push to main is rejected. A classmate says, “Just use git push --force, it always works.” Explain what would happen to the team and what Sam should do instead.
Question 16 · Compare
Compare git pull (with merge) and git pull --rebase. What does each do with your local commits, and why is git pull --rebase considered safe while rebasing commits you already pushed is not?
Question 17 · Read the output
Sam sees this in git status:
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
Explain what “diverged” means here and what Sam should do before pushing.
Question 18 · Long answer
You are setting up a shared repository for a three-person student team that will also use Claude Code. Write five short team rules for working with Git and GitHub, and explain one of them.
Question 19 · Scenario
Your team lets Claude Code work on tasks in background sessions in your shared repository. According to the Claude Code docs (as of Oct 2026), what does such a session do with its finished work, which Git actions does it avoid, and how can your team add its own Git rules?