linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Chapters
  • How it works
  • Lessons
  • Knowledge base
  • Interview prep
home/git/lessons/git-lab-12-1-fork-flow

lesson ── git-labs ── ~25 мин ── 11 шагов

Fork flow with two remotes

The goal is to walk the fork workflow by hand: clone, remote add upstream, branch from a fresh upstream, push to your own origin, sync via fetch + rebase + force-with-lease. No real GitHub: you simulate the original and the fork with two local bare repositories.

This is the same scenario as in the book, but with an automatic check on every step.

▶ интерактивный sandbox

Поднимется контейнер gitlab/git-base с git, bash, pre-commit. В браузере откроется терминал, можно сразу git init. Каждый шаг проверяется автоматически. Сеть air-gapped, github.com недоступен.

запустить sandbox →

stack ── git · bash · 256 MB RAM · air-gapped · самоуничтожается через 30 мин простоя

Шаги

  1. 01

    Create two bare repositories: the original and the fork

    A bare repository is the server side of git, with no working tree. You use it to simulate the central server and the fork.

    bash
    cd /home/student/work
    mkdir -p fork-lab && cd fork-lab
    git init --bare original.git  # --bare = server repo with no working tree
    git init --bare fork.git
    ls -la

    You see two .git folders. This is "GitHub, sort of".

    ✓ Both bare repositories are in place.

  2. 02

    Seed the original with an initial commit

    Create a temporary working repo, make a commit, push it to the bare.

    bash
    cd /home/student/work/fork-lab
    mkdir seed && cd seed
    git init -b main
    echo "v1" > version.txt
    git add . && git commit -m "init"
    # remote = a named URL for push/fetch; origin is the local path to the bare repo
    git remote add origin /home/student/work/fork-lab/original.git
    git push -u origin main       # -u = set-upstream: remember the link main -> origin/main
    cd .. && rm -rf seed          # the temporary repo is no longer needed

    The original now holds one commit on main.

    ✓ The original is seeded. Next you simulate the fork.

  3. 03

    Simulate a fork: copy the original into fork.git

    On real GitHub, fork means "copy the repo into my namespace". Here you just clone the original and push it to fork.git.

    bash
    cd /home/student/work/fork-lab
    git clone original.git seed2
    cd seed2
    # set-url = change the address of an existing remote, do not add a new one
    git remote set-url origin /home/student/work/fork-lab/fork.git
    git push origin main          # push to fork.git now, not to original.git
    cd .. && rm -rf seed2

    Now fork.git holds the same main as original.git. From this point on they are independent.

    ✓ Fork simulated. Next you clone it as a developer.

  4. 04

    Clone your fork as a developer

    bash
    cd /home/student/work/fork-lab
    git clone fork.git my-work
    cd my-work
    git remote -v                 # -v (verbose) = show the URLs for fetch and push

    You see one origin -> fork.git. This is your fresh clone of the fork.

    ✓ You have a clone. Now add upstream.

  5. 05

    Add the original as upstream

    bash
    cd /home/student/work/fork-lab/my-work
    git remote add upstream /home/student/work/fork-lab/original.git
    git fetch upstream            # pulls refs/remotes/upstream/*, leaves local branches untouched
    git remote -v                 # two remotes now: origin and upstream

    You now have two remotes: origin (your fork) and upstream (the original). git fetch upstream downloads refs/remotes/upstream/* without touching your local branches.

    ✓ Both remotes are set up. Now simulate activity in the original.

  6. 06

    Simulate someone else's commit in the original

    Another developer pushed something to the original. Simulate it:

    bash
    cd /home/student/work/fork-lab
    git clone original.git orig-clone
    cd orig-clone
    echo "v2" > version.txt       # overwrite v1 -> v2
    git commit -am "bump to v2"
    git push origin main          # push to the original (origin here points to original.git)
    cd .. && rm -rf orig-clone

    The original moved ahead. Your fork did not, your clone did not.

    ✓ The original has 2 commits, the fork and my-work have 1. They have diverged.

  7. 07

    Branch from a fresh upstream/main, not from your own main

    bash
    cd /home/student/work/fork-lab/my-work
    git fetch upstream            # refresh refs/remotes/upstream/main
    # switch -c <new> <start-point>: new branch from upstream/main, not from local main
    git switch -c feat/add-changelog upstream/main

    Note: the branch starts from upstream/main, not from main. Your fork's main lags behind at v2. upstream/main is fresh.

    Make a commit:

    bash
    echo "# Changelog" > CHANGELOG.md
    git add . && git commit -m "add changelog"

    ✓ The branch is made from upstream and has your commit. Next, push to origin.

  8. 08

    Push the branch to your own fork, not to upstream

    bash
    git push -u origin feat/add-changelog   # -u = set up tracking origin/feat/add-changelog

    Remember: push to origin (your fork), not to upstream (the original). On real GitHub, upstream is usually read-only for you.

    ✓ Your branch is in your fork. On GitHub you open a PR from here.

  9. 09

    Simulate one more commit in upstream

    While you worked on the branch, the original gained one more commit:

    bash
    cd /home/student/work/fork-lab
    git clone original.git orig-clone
    cd orig-clone
    echo 'docs added' > README.md
    git add . && git commit -m "add readme"
    git push origin main
    cd .. && rm -rf orig-clone

    ✓ The original moved one more commit ahead. Next, sync.

  10. 10

    Sync via rebase onto the fresh upstream

    bash
    cd /home/student/work/fork-lab/my-work
    git fetch upstream
    git rebase upstream/main      # replay your commits on top of the fresh upstream/main

    This replays your add changelog on top of the fresh upstream/main (which now holds init + v2 + readme).

    ✓ Rebase finished. The history is now linear.

  11. 11

    Force-push to your fork with lease protection

    After the rebase the commit's SHA changed. A plain push would refuse. Use force-with-lease:

    bash
    # --force-with-lease: force-push with a check that the remote has not moved ahead of your view
    git push --force-with-lease

    --force-with-lease is more than just --force. It checks that your fork is still what you last saw. If someone managed to push there, the command aborts with a (stale info) error.

    On a public repo with a single owner the difference is invisible. On shared branches it saves you from clobbering other people's commits.

    ✓ Your fork is synced and the history is linear. That is "sync fork", done from the terminal.

Что ты узнал

A fork means two remotes: origin (your copy) and upstream (the original). A branch is created from upstream/main, pushed to origin/*, synced via fetch+rebase. Force only with with-lease.

команды

  • git remote add upstream <url>add the original as upstream
  • git fetch upstreamget updates from the original
  • git switch -c feat/x upstream/mainbranch from a fresh upstream
  • git rebase upstream/maincatch up with the original after long work
  • git push --force-with-leasepush a rewritten branch safely

концепции

  • · fork = your namespace on the server, a separate bare repo
  • · origin is yours, upstream is the original: do not confuse them
  • · rebase onto upstream/main instead of merge for a clean history

← предыдущая

Trunk-based: features behind flags in main

следующая →

git worktree: work on two branches in parallel without stash

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies