Forks and the fork workflow
Intermediate Core Git GitLab UI GitHub UI
Why this matters
Most of your work will be on projects where you are a member and can push branches directly. Sooner or later you will want to fix something in a project where you are not: another team's documentation, an internal library, an open-source tool. A fork is how that works, and it is the standard route for every open-source contribution ever made.
What a fork is
Teams use forks in three situations: contributing to a project you cannot push to, a project that accepts contributions from strangers, and organizations whose policy is that nobody pushes branches to the main repository.
The setup
The fork itself is one button on the website: Fork on GitLab (top right of the project page) or Fork on GitHub (top right of the repository). Then, on your computer:
$ git clone https://gitlab.com/you/trailguide.git
$ cd trailguide
$ git remote add upstream https://gitlab.com/northwind-trails/trailguide.git
$ git remote -vorigin https://gitlab.com/you/trailguide.git (fetch)
origin https://gitlab.com/you/trailguide.git (push)
upstream https://gitlab.com/northwind-trails/trailguide.git (fetch)
upstream https://gitlab.com/northwind-trails/trailguide.git (push)Two remotes, and the naming is a universal convention:
originis your fork. You push here.upstreamis the original. You fetch from here and never push.
Working
The daily loop is the same as always, with one extra step at the start:
$ git fetch upstream
$ git switch main
$ git merge upstream/main
$ git switch -c docs/12-windows-install-stepsFetching upstream brings in what the original project has done since you forked, and merging it into your main keeps your fork current. Then branch, work, commit, and push to your fork:
$ git push -u origin docs/12-windows-install-stepsFinally open the merge request from your fork's branch to the original project's main. Both platforms detect the relationship and preselect it.
How to do it
$ git clone <your-fork-url>
$ cd <project>
$ git remote add upstream <original-url>
$ git fetch upstream
$ git switch main && git merge upstream/main
$ git switch -c fix/12-something
$ git push -u origin fix/12-somethingThen open the merge request on the website. The only difference from normal work is the extra remote and remembering that origin is your copy.
Clone your fork as usual. Adding the second remote has no menu entry; use the integrated terminal for git remote add upstream ….
Afterwards both remotes appear in the branch picker, so Git: Fetch From All Remotes and checking out upstream/main work normally. With the GitHub Pull Requests and Issues extension, the pull request it creates from a forked repository targets the original automatically.
Clone your fork, then Git → Manage Remotes… → + to add upstream with the original's URL.
Git → Fetch then fetches from both. In the branches popup, upstream/main appears under Remote and can be merged into your main. IntelliJ's GitHub integration creates cross-repository pull requests from the Pull Requests tool window.
Fork is at the top right of the project page; you choose the namespace for the copy. The fork's page then shows a line saying which project it was forked from, and a Sync fork control to bring in the original's new commits.
When you push a branch to your fork, GitLab offers Create merge request; in the form, the source branch is your fork's branch and the target branch is the original project's main. GitLab calls this a merge request from a fork, and it works exactly like any other, with the same review, approvals and pipelines.
Fork is at the top right of the repository page. The fork's page shows "forked from …" under the name, and a Sync fork button with Update branch when the original has moved ahead.
Pushing a branch to your fork makes GitHub show a Compare & pull request banner. In the pull request form, the base repository is the original and the head repository is your fork. Maintainers can be allowed to push to your branch, which is what the Allow edits by maintainers checkbox does.
Common mistakes
- Cloning the original instead of your fork. Then
originpoints at a repository you cannot push to, and your first push is refused with a permission error. Checkgit remote -v:originmust be your copy. - Pushing to
upstream. It will be refused, correctly. Only evergit push origin. - A stale fork. Your
mainstill shows the state at fork time, and branches started from it conflict. Sync before each new branch. - Opening the merge request in the wrong direction. The form has two sides; the target is the original project.
- Expecting the fork to update itself. It does not; that is what
Sync forkandgit merge upstream/mainare for.
Try it yourself
Goal: rehearse the two-remote setup, using two projects of your own.
- On GitLab or GitHub, create a second empty private project called
trailguide-originaland push your playground into it. Treat it as "the original". - Use the platform's Fork button to fork it into a copy (on GitHub, forking your own repository requires a different owner or a different name; if that is blocked, create
trailguide-forkmanually and push the same content, which is equivalent for the exercise). - Clone the fork, add the original as
upstream, and rungit remote -v. - Branch, make a change, push to
origin, and open the merge request from the fork to the original.
Expected result: git remote -v lists four lines, two for origin (your fork) and two for upstream (the original), and the merge request form shows two different projects.
Show solution
The important habit is the check in step 3: whenever a push is refused with a permission error on a fork workflow, git remote -v almost always shows that origin is the original rather than the fork. git remote set-url origin <your-fork-url> fixes it.