Chapter 04Lesson 05~165 minutes

Checkpoint Lab — Branches, Forks, Remotes, Synchronization, and Contribution Workflows

Operate a complete two-repository contribution checkpoint: create independent upstream and contributor changes, prove branch ownership, synchronize safely, clean topic branches only after dependency checks, and document the workflow as an auditable operating model.

CheckpointTwo-repository topologyRef evidenceCleanup

Learning objectives

  • Create a complete two-repository contribution topology from scratch with explicit owner, visibility, authentication, and cleanup assumptions.
  • Predict hosted/local branch and remote-tracking ref changes before performing them, then verify each prediction independently.
  • Make independent canonical and contributor commits, synchronize the contributor topic without pushing to the wrong repository, and prove exact OIDs.
  • Demonstrate the distinction between hosted destination synchronization and local fetch/update state.
  • Clean up topic branches only after dependency checks, then delete only the disposable repositories created by the lab.
  • Produce a reusable contribution-topology runbook and bridge the operating model into Issues/triage in Chapter 05.
Availability: Required: GitHub.com personal account with permission to create/delete disposable repositories, Git, and GitHub CLI. GitHub Free is sufficient. The lab uses two owned repositories to simulate fork mechanics so no paid plan/private-fork policy is required. Optional actual-fork comparison is documentation/read-only unless you deliberately create a disposable public fork.

1. Checkpoint mission: prove who owns every ref

Your final artifact is not “two repositories that seem to work.” It is an evidence package: topology diagram, remote URLs, initial and final OIDs, hosted branch inspection, permission assumptions, synchronization decisions, branch cleanup evidence, and a short runbook another engineer could follow without guessing where a push goes.

Do not reuse Chapter 02/03 lab repositories. This checkpoint creates fresh resources so its ref graph and cleanup scope are unambiguous.

2. Setup and preflight

gh auth status --active --hostname github.com
      OWNER=$(gh api user --jq .login)
      ROOT="atlas-c04-checkpoint"
      UP="$ROOT-upstream"
      FORKSIM="$ROOT-contrib"

      git --version
      gh --version
      for R in "$UP" "$FORKSIM"; do
        gh repo view "$OWNER/$R" >/dev/null 2>&1 && {
          echo "STOP: $OWNER/$R already exists" >&2
          exit 2
        }
      done
      printf 'owner=%s
upstream=%s
contrib=%s
' "$OWNER" "$UP" "$FORKSIM"

Assumptions: you are authenticated as the intended personal account; repository content is synthetic; public visibility is acceptable. If your policy requires private repositories, use --private for both repositories. This still remains a two-repository simulation, not a GitHub private fork.

3. Write four predictions before mutation

  1. P1: initial upstream and contributor default branches can point to the same commit even though GitHub reports the contributor repository is not a fork.
  2. P2: pushing topic/telemetry-docs to contributor origin creates that branch only in the contributor repository.
  3. P3: after upstream advances and the contributor runs git fetch upstream, only local upstream-tracking knowledge changes; hosted contributor refs do not change yet.
  4. P4: after the topic is synchronized and pushed, deleting the contributor topic branch will remove that hosted ref only after you prove no open PR/automation dependency exists; local commits remain while reachable from local refs/reflog.

4. Target topology

Checkpoint topology — simulated fork mechanics
flowchart LR
  U[("GitHub canonical
OWNER/atlas-c04-checkpoint-upstream")]
  C[("GitHub contributor
OWNER/atlas-c04-checkpoint-contrib
NOT a GitHub fork")]
  L[("Local contributor clone
origin -> contributor
upstream -> canonical")]
  M[("Canonical maintainer clone")]
  U <--> |"origin"| M
  C <--> |"origin"| L
  U --> |"fetch upstream"| L
  L --> |"push topic only"| C

Every arrow is a Git network operation. There is intentionally no GitHub fork-network relationship arrow. The lab therefore proves the portable Git mechanics while keeping hosted fork metadata clearly separate.

5. Create canonical repository and baseline commit

gh repo create "$OWNER/$UP" --public --add-readme --clone
      cd "$UP"
      git config user.name "Checkpoint Maintainer"
      git config user.email "maintainer@example.invalid"
      mkdir -p docs
      printf '%s
' '# Telemetry Contract' '' 'version: 1' > docs/telemetry.md
      git add docs/telemetry.md
      git commit -m "docs: seed telemetry contract"
      git push origin HEAD
      BASE_BRANCH=$(git branch --show-current)
      BASE_OID=$(git rev-parse HEAD)
      cd ..
      printf 'base_branch=%s
base_oid=%s
' "$BASE_BRANCH" "$BASE_OID"

Record $BASE_OID. This is the common ancestor you will use to prove the later independent changes.

6. Create contributor repository with identical history and prove P1

git clone "https://github.com/$OWNER/$UP.git" "$FORKSIM"
cd "$FORKSIM"
git config user.name "Checkpoint Contributor"
git config user.email "contributor@example.invalid"
git remote rename origin upstream
gh repo create "$OWNER/$FORKSIM" --public
git remote add origin "https://github.com/$OWNER/$FORKSIM.git"
git push -u origin "$BASE_BRANCH"

CONTRIB_BASE=$(git ls-remote origin "refs/heads/$BASE_BRANCH" | cut -f1)
test "$CONTRIB_BASE" = "$BASE_OID"
gh repo view "$OWNER/$FORKSIM" --json isFork,parent,nameWithOwner         --jq '{nameWithOwner,isFork,parent:(.parent.nameWithOwner // null)}'

P1 passes if the branch OID equals $BASE_OID while isFork=false. Same objects and branch contents are not enough to create hosted fork lineage.

7. Prove remote ownership before contributor work

printf '%s
' '--- remotes ---'
      git remote -v
      printf '%s
' '--- remote refs ---'
      git for-each-ref refs/remotes/ --format='%(refname:short) %(objectname)'
      printf '%s
' '--- hosted repos ---'
      gh repo view "$OWNER/$UP" --json nameWithOwner,viewerPermission --jq .
      gh repo view "$OWNER/$FORKSIM" --json nameWithOwner,viewerPermission --jq .

Write one sentence in your evidence file: “origin publishes contributor refs; upstream is the canonical fetch source.” Do not proceed if the URLs disagree.

8. Contributor change: create topic and prove P2

git switch -c topic/telemetry-docs
      printf '%s
' '' 'consumer: dashboards' >> docs/telemetry.md
      git add docs/telemetry.md
      git commit -m "docs: add telemetry consumer"
      CONTRIBUTOR_OID=$(git rev-parse HEAD)

      git push -u origin topic/telemetry-docs
      C_REMOTE=$(git ls-remote origin refs/heads/topic/telemetry-docs | cut -f1)
      U_REMOTE=$(git ls-remote upstream refs/heads/topic/telemetry-docs | cut -f1)
      test "$C_REMOTE" = "$CONTRIBUTOR_OID"
      test -z "$U_REMOTE"
      printf 'contrib_topic=%s
upstream_topic=%s
' "$C_REMOTE" "${U_REMOTE:-<absent>}"

P2 is verified by two remote queries: contributor owns the branch; canonical upstream does not.

9. Independent upstream change

cd "../$UP"
      printf '%s
' '' 'retention: 7d' >> docs/telemetry.md
      git add docs/telemetry.md
      git commit -m "docs: define telemetry retention"
      git push origin HEAD
      UPSTREAM_OID=$(git rev-parse HEAD)
      cd "../$FORKSIM"
      printf 'upstream_oid=%s
' "$UPSTREAM_OID"

The contributor topic and canonical default branch now both descend from $BASE_OID but contain different changes. This is the synchronization problem to solve.

10. Fetch upstream and prove P3

TRACKING_BEFORE=$(git rev-parse "refs/remotes/upstream/$BASE_BRANCH")
      HOSTED_CONTRIB_BEFORE=$(git ls-remote origin "refs/heads/$BASE_BRANCH" | cut -f1)

      git fetch upstream --prune

      TRACKING_AFTER=$(git rev-parse "refs/remotes/upstream/$BASE_BRANCH")
      HOSTED_CONTRIB_AFTER=$(git ls-remote origin "refs/heads/$BASE_BRANCH" | cut -f1)
      printf 'tracking_before=%s
tracking_after=%s
hosted_contrib_before=%s
hosted_contrib_after=%s
'         "$TRACKING_BEFORE" "$TRACKING_AFTER" "$HOSTED_CONTRIB_BEFORE" "$HOSTED_CONTRIB_AFTER"
      test "$TRACKING_AFTER" = "$UPSTREAM_OID"
      test "$HOSTED_CONTRIB_BEFORE" = "$HOSTED_CONTRIB_AFTER"

P3 passes: local upstream/$BASE_BRANCH learned the new canonical OID while the hosted contributor default branch stayed untouched.

11. Inspect divergence before choosing integration

git merge-base topic/telemetry-docs "upstream/$BASE_BRANCH"
git log --oneline --left-right --graph         "topic/telemetry-docs...upstream/$BASE_BRANCH" -12
git diff --stat "upstream/$BASE_BRANCH...topic/telemetry-docs"

You should see one contributor-side commit and one upstream-side commit beyond the shared base. Because both edit the same file, the merge may conflict. That is useful evidence, not a reason to discard a side.

12. Synchronize the published topic with a merge

git switch topic/telemetry-docs
git merge "upstream/$BASE_BRANCH"

If Git reports a conflict, run git status and inspect docs/telemetry.md. Keep both intended lines—consumer: dashboards and retention: 7d—remove conflict markers, stage, and commit:

# Run these commands only if the merge stopped for a conflict.
git status
git add docs/telemetry.md
git commit

After the merge has completed—automatically or after the conflict-resolution block—capture the final topic OID, push it to the contributor repository, and verify the exact hosted branch:

SYNCED_OID=$(git rev-parse HEAD)
git push origin topic/telemetry-docs
HOSTED_TOPIC=$(gh api -H "X-GitHub-Api-Version: 2026-03-10"         "/repos/$OWNER/$FORKSIM/branches/topic/telemetry-docs" --jq .commit.sha)
test "$SYNCED_OID" = "$HOSTED_TOPIC"

The exact hosted OID proves the contributor topic is synchronized. Canonical upstream still does not contain that topic ref.

13. Synchronize the hosted contributor default branch independently

cd ..
gh repo sync "$OWNER/$FORKSIM"         --source "$OWNER/$UP"         --branch "$BASE_BRANCH"

C_DEFAULT=$(gh api -H "X-GitHub-Api-Version: 2026-03-10"         "/repos/$OWNER/$FORKSIM/commits/$BASE_BRANCH" --jq .sha)
test "$C_DEFAULT" = "$UPSTREAM_OID"
cd "$FORKSIM"

Again, this changes hosted contributor state. Refresh local knowledge separately:

LOCAL_ORIGIN_BEFORE=$(git rev-parse "origin/$BASE_BRANCH")
      git fetch origin "$BASE_BRANCH"
      LOCAL_ORIGIN_AFTER=$(git rev-parse "origin/$BASE_BRANCH")
      printf 'origin_before=%s
origin_after=%s
' "$LOCAL_ORIGIN_BEFORE" "$LOCAL_ORIGIN_AFTER"
      test "$LOCAL_ORIGIN_AFTER" = "$UPSTREAM_OID"

This double verification is the heart of the chapter: hosted sync and local fetch are distinct state transitions.

14. Pre-delete dependency check for the topic branch

There is no pull request in this simulation. Still, prove that assumption instead of relying on memory. Query open PRs and record the branch OID before deletion.

TOPIC_BEFORE_DELETE=$(git ls-remote origin refs/heads/topic/telemetry-docs | cut -f1)
      gh pr list --repo "$OWNER/$FORKSIM" --state open         --json number,headRefName,headRepositoryOwner,url         --jq '.[] | select(.headRefName=="topic/telemetry-docs")'
      printf 'topic_before_delete=%s
' "$TOPIC_BEFORE_DELETE"

The query should return no matching open PR. In a production repository, you would also inventory workflow/deployment/external automation dependencies. This lab has no workflows, releases, packages, or Pages, so deletion scope is intentionally small.

Destructive ref operation next: the command deletes only the disposable contributor topic branch. The exact OID is recorded first.

15. Delete the disposable hosted topic and prove P4

git push origin --delete topic/telemetry-docs
REMOTE_AFTER=$(git ls-remote origin refs/heads/topic/telemetry-docs | cut -f1)
test -z "$REMOTE_AFTER"

git show --no-patch --oneline "$SYNCED_OID"
git branch --contains "$SYNCED_OID"

The hosted ref is gone. The commit can still exist locally while reachable from the local topic branch/reflog and because its objects are present. This demonstrates why “branch deleted” and “commit instantly erased everywhere” are different claims.

Now remove the local topic branch only after switching away:

git switch "$BASE_BRANCH"
git branch -d topic/telemetry-docs || {
  echo "Branch is not fully merged into local base; preserve it or inspect before forced local deletion."
}

Do not replace the safety check with -D just to make cleanup succeed. If Git says the local topic is not merged, that is valid evidence that your local base does not contain the contribution.

16. Final evidence table

Evidence Expected result
Contributor repo isFork False (simulation), despite shared initial Git history
Initial upstream/contributor default OID Same $BASE_OID
Contributor topic after first push Exists only in contributor repo, OID $CONTRIBUTOR_OID
Upstream default after independent change $UPSTREAM_OID
Local upstream tracking after fetch Matches $UPSTREAM_OID
Contributor topic after merge/push Matches $SYNCED_OID
Hosted contributor default after gh repo sync Matches $UPSTREAM_OID
Local origin/$BASE_BRANCH after fetch Matches $UPSTREAM_OID
Hosted topic after dependency-checked delete Absent

17. Write the contribution runbook

Your runbook should fit on one page but encode the operating model:

  1. Confirm account/host and repository owner/name.
  2. Inspect git remote -v; origin must be the contributor repository and upstream the canonical source.
  3. Fetch upstream before creating or refreshing a topic branch.
  4. Create work on a topic branch, not the fork/default synchronization branch.
  5. Push topic only to origin; verify exact hosted OID.
  6. Before review, inspect merge-base/diff and merge or rebase according to publication/history policy.
  7. Treat fork-originated automation as untrusted until policy/review permits it.
  8. After merge/closure, inventory PR/automation dependencies before deleting the hosted topic branch.
  9. Prune local tracking refs only after hosted deletion is intentional and verified.

18. Cleanup the two disposable repositories

Capture your evidence/runbook first. Then delete only $OWNER/$UP and $OWNER/$FORKSIM. The safest course path uses the GitHub web Settings danger zone so the owner/name confirmation is visually explicit. If you use CLI deletion in your own environment, read the current command help and verify the target twice; repository deletion is destructive and requires appropriate authorization.

Do not rely on deleted-repository restoration as cleanup rollback. Keep the lab synthetic and recreate it if needed.

19. Verification checklist

  • Preflight proved authenticated account, tool availability, and repository-name uniqueness.
  • Four predictions were written before mutation and independently checked.
  • Topology diagram correctly separated local clone, hosted contributor repository, and hosted canonical repository.
  • Initial repositories shared an OID but contributor correctly reported no GitHub fork relationship.
  • Topic branch existed only in contributor repository until cleanup.
  • Upstream default advanced independently and fetch updated only local upstream-tracking knowledge.
  • Published topic was synchronized by merge, not silent history rewrite.
  • Hosted contributor default sync and local origin fetch were verified as separate operations.
  • Open-PR dependency check and branch OID capture happened before remote branch deletion.
  • No force sync, force push, bypass, token exposure, workflow privilege change, or production resource was used.
  • Only the two disposable repositories were selected for final cleanup.

Knowledge check

P1 passed even though isFork=false. What did that prove?

Which operation proved P3: hosted sync or local fetch?

Why did the checkpoint merge upstream into the published topic instead of rebasing it?

What must you verify before deleting a hosted topic branch?

What production control does Chapter 04 add?

20. What Chapter 04 adds to the production GitHub operating model

Chapters 01–03 established platform boundaries, credentials, and repository provisioning. Chapter 04 adds change topology: where contributor branches live, which remote may receive pushes, how upstream state is learned, how fork/default branches are synchronized, and how ref deletion is governed. This topology becomes the substrate for pull requests, reviews, rulesets, and Actions security later in the course.

A production team can now write a contribution policy that is testable: actor class → repository ownership → branch location → remote convention → synchronization method → review path → automation trust → cleanup evidence.

21. Bridge to Chapter 05

The next chapter moves from code/ref topology to work-intake topology: Issues, labels, milestones, issue forms/templates, Discussions, and triage. The same principle carries forward—identify the hosted resource, owner, permission, lifecycle state, and automation consequences before changing it.

Next chapter

Organize the work around the change graph

Chapter 05 teaches Issues, labels, milestones, templates/forms, Discussions, and triage so contribution branches and pull requests connect to a structured work-management model.

Authoritative references

 Forks
 Configuring a remote repository for a fork
 Syncing a fork
 Deleting and restoring branches in a pull request
 gh repo sync
 gh repo view
 REST API endpoints for branches
 REST API versions
 git-remote
 git-fetch
 git-merge
 git-push

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.