Git Project: Two Clone Sync
Push a repository, clone it a second time, exchange commits, and prove both copies share the intended history.
Publish one small repository to GitHub, clone it a second time, exchange commits in both directions, recover a rejected push, and prove both working copies share the same tip.
One remote, two folders
Laptop and desk are two clones of the same GitHub repository. GitHub stores the objects. Each folder is a working copy. A second clone is not a fork.
Tips you can prove
After the exchange and the recovered rejection, git rev-parse HEAD on both clones equals git ls-remote origin refs/heads/main. Copying files between folders is not the proof.
Project brief
You keep a shared log in log.md. You write from a laptop folder, then from a desk folder. GitHub is the named remote both folders push to and fetch from. The Git Remote lesson already practiced this with a bare folder. This project uses a GitHub repository you own, so authentication, clone URLs, and rejected pushes are real.
Evidence tracker
Mark a gate complete only after the named command or review proves it. Progress is saved in this browser.
Safety boundary
Disposable repository only
Create a new parent folder named two-clone-sync. Never run these commands inside SovranCode, your home directory, or valuable work.
A GitHub remote you own
Create sovrancode-two-clone-sync under your account. Do not push to someone else’s repository. Delete the practice repository when the evidence is recorded.
No force-push
A rejected push is a signal to fetch and integrate. git push --force and --force-with-lease fail this project.
No pull requests or forks
Stay on main. Do not open a pull request, fork, or add a second GitHub repository. The second folder is a clone of the same origin.
Repository contract
Two sibling folders
Create history in shared-log-laptop. Clone GitHub into shared-log-desk beside it. Do not copy log.md between folders by hand.
README.md
Tells a stranger how to clone the GitHub URL and the one rule: pull before you push.
log.md
Append-only dated lines. Each intended commit adds exactly one new line.
Empty GitHub create
No GitHub-generated README, license, or gitignore. Those files would be a different first commit than yours.
two-clone-sync/
├── shared-log-laptop/ ← first working copy (you create this)
└── shared-log-desk/ ← second working copy (git clone of GitHub)
GitHub repository (origin): sovrancode-two-clone-sync
Both folders talk to that one remote. They are not two GitHub repositories.GitHub is the host. Git is the tool that records commits and talks to that host. The GitHub website is not a third working copy. A fork would be a different GitHub repository; this project has one.
Required graphs
Three commits, one line
* <afternoon> (HEAD -> main, origin/main) Add afternoon desk note
* <morning> Add morning laptop note
* <initial> Initialize shared logOne merge, both notes kept
* <sync> (HEAD -> main, origin/main) Merge concurrent log notes
|\
| * <desk-extra> Add desk concurrent note
* | <laptop-extra> Add laptop concurrent note
|/
* <afternoon> Add afternoon desk note
* <morning> Add morning laptop note
* <initial> Initialize shared logSame tip everywhere
Laptop HEAD, desk HEAD, and origin/main name one SHA.
git rev-parse HEADBoth directions
A laptop commit reached desk through origin. A desk commit reached laptop through origin.
git log --oneline --decorate --graph --allRejected, then recovered
Evidence of a non-fast-forward rejection, then a merge that keeps both concurrent lines. No force-push.
git ls-remote origin refs/heads/mainMilestone 1 · Build the laptop copy
Make a parent directory, then initialize only the laptop folder. Commit the README contract and the first log line together. Record the commit ID. There is no remote yet.
mkdir -p two-clone-sync/shared-log-laptop
cd two-clone-sync/shared-log-laptop
git init -b main
git status
# Write README.md and log.md from the contracts below, then:
git add README.md log.md
git diff --cached
git commit -m "Initialize shared log"
git status
git rev-parse HEADClone instructions and the one rule
# Shared log
A tiny notebook two clones keep in sync through GitHub.
## Clone
git clone https://github.com/OWNER/sovrancode-two-clone-sync.git
## Rule
Pull before you push. Never force-push. Append one dated line to log.md per commit.One dated starting line
# Shared log
- 2026-09-21 initialize: project opened on laptop
Replace OWNER in the README with your GitHub username after the repository exists. The first commit can say OWNER; a later commit may correct the URL. Do not invent a second project inside this folder.
Milestone 2 · Publish an empty GitHub remote
Stay in shared-log-laptop. Create the GitHub repository empty, then inspect origin before you trust the push. Use the credential you already proved in GitHub Authentication. Public or private is your choice for a disposable practice repo; public is easier to delete later without leftover access lists.
git rev-parse --show-toplevel
gh auth status
gh repo create sovrancode-two-clone-sync --public --source=. --remote=origin --push
git remote -v
git branch -vv
git ls-remote origin
git rev-parse HEADgh repo create ... --push creates the GitHub repository, names it origin, and sends main. Omit any flag that adds a README. If you used the website instead, add origin yourself with the HTTPS or SSH URL that matches your credential, then run git push -u origin main.
Proof: git ls-remote origin lists the same object as git rev-parse HEAD. The GitHub page showing files is a website. The remote is the Git URL in git remote -v.
Milestone 3 · Clone a second working copy
Leave the laptop folder. Clone from GitHub into a sibling directory. Do not copy the laptop folder. Do not add a second remote. The new folder’s origin must be the same GitHub repository.
cd ..
git clone https://github.com/OWNER/sovrancode-two-clone-sync.git shared-log-desk
cd shared-log-desk
git remote -v
git branch -vv
git log --oneline --decorate --graph --all
git rev-parse HEAD
git ls-remote origin refs/heads/mainIf you authenticated with SSH, clone git@github.com:OWNER/sovrancode-two-clone-sync.git instead. The transport must match a credential that works. After this milestone, laptop HEAD, desk HEAD, and origin/main are one commit. Record that SHA. You now have two working copies of one history.
Milestone 4 · Laptop publishes; desk pulls
On laptop, append one morning line to log.md, commit, and push. On desk, fetch first. Desk main has not moved yet. origin/main has. Then pull so desk fast-forwards.
# shared-log-laptop
echo "- 2026-09-21 morning: wrote from laptop" >> log.md
git add log.md
git diff --cached
git commit -m "Add morning laptop note"
git push origin main
git ls-remote origin refs/heads/main
# shared-log-desk — inspect before integrating
git fetch origin
git status
git log --oneline --decorate --graph --all
git branch -vv
git pull origin main
git rev-parse HEADAfter fetch and before pull, desk status should show that local main is behind origin/main by one commit. Pull here is fetch-plus-merge of a fast-forward. The morning line must appear in desk log.md because Git brought the commit, not because you pasted the sentence.
Milestone 5 · Desk publishes; laptop pulls
Reverse the direction. Desk appends an afternoon line, commits, and pushes. Laptop is now behind. Fetch, read the graph, then pull. Record the three commit IDs. Both clones and origin must name the afternoon tip.
# shared-log-desk
echo "- 2026-09-21 afternoon: wrote from desk" >> log.md
git add log.md
git diff --cached
git commit -m "Add afternoon desk note"
git push origin main
# shared-log-laptop
git fetch origin
git log --oneline --decorate --graph --all
git pull origin main
git rev-parse HEAD
git ls-remote origin refs/heads/mainThis is the take-turns graph: initialize, morning, afternoon. Keep that evidence. The next milestone will add concurrent commits on purpose. Do not skip the three-commit proof to rush the conflict.
Milestone 6 · Recover a rejected push
Both clones now add a different extra line without pulling first. Laptop pushes successfully. Desk push is rejected as a non-fast-forward. That rejection is required evidence. Recover on desk with fetch, inspect, and merge. Keep both new lines. Then push. Laptop pulls the merge. Never force-push.
# shared-log-laptop — do not pull desk first
echo "- 2026-09-21 laptop concurrent: extra laptop line" >> log.md
git add log.md
git commit -m "Add laptop concurrent note"
git push origin main
# shared-log-desk — this push must fail
echo "- 2026-09-21 desk concurrent: extra desk line" >> log.md
git add log.md
git commit -m "Add desk concurrent note"
git push origin main
# Recover on desk. Do not run git push --force.
git fetch origin
git log --oneline --decorate --graph --all
git merge origin/main
# If log.md conflicts, keep both concurrent lines, then:
git add log.md
git diff --cached
git commit -m "Merge concurrent log notes"
git push origin main
# shared-log-laptop
git fetch origin
git pull origin main
git rev-parse HEAD
git ls-remote origin refs/heads/mainThe rejection message means origin has a commit desk does not have. Fetch downloads it. Merge combines it with desk’s local commit. Both notes belong in log.md. If you take only one side, the project fails the exchange contract.
Milestone 7 · Prove the tips match
Run the audit in both folders. The SHA from git rev-parse HEAD must equal the object listed by git ls-remote origin refs/heads/main. Status is clean. git remote -v still names the same GitHub URL. The README still explains how to clone.
git rev-parse --show-toplevel
git remote -v
git status
git branch -vv
git log --oneline --decorate --graph --all
git rev-parse HEAD
git ls-remote origin refs/heads/main
git merge-base --is-ancestor HEAD origin/mainRun the same block in shared-log-laptop and shared-log-desk. If one clone is behind, fetch and pull. Do not copy files. Do not add a second GitHub repository to “fix” a mismatch.
Required failure drills
Rejected push, no force
Milestone 6 is the drill. Save the rejection text. A successful force-push is an automatic rebuild.
Fetch is not a merge
After a fetch that shows you behind, record status and the graph before you pull. Fetch alone must leave the working tree unchanged.
Wrong URL
Compare git remote -v on both clones. If one points at a different repository, stop. Fix origin; do not push “somewhere” to see what happens.
File copy is not sync
Do not copy log.md from laptop to desk. If the files match but the commit IDs differ, you did not sync Git history.
Evidence package
- Laptop and desk paths from
git rev-parse --show-toplevel, plus matchinggit remote -vURLs. - Empty-create proof: GitHub repository name, no extra initializer commit, first
git ls-remote origin. - Second-clone command and the shared SHA immediately after
git clone. - Take-turns graph with morning and afternoon commits, and IDs after each pull.
- Rejected-push text, the fetch graph that explained it, and the merge that kept both concurrent lines.
- Final matching SHAs from both clones and
git ls-remote origin refs/heads/main, plus clean status.
Self-assessment rubric: 25 points
5 · Hosted origin
Empty GitHub create, correct remote URL, first push proven with git ls-remote origin.
5 · Second clone
Desk came from git clone, not a copied folder; remotes and first SHA match.
5 · Two-way exchange
Morning reached desk through origin; afternoon reached laptop through origin; graphs recorded.
5 · Rejection recovery
Non-fast-forward captured; fetch and merge used; both concurrent lines kept; no force-push.
5 · Final audit
Matching SHAs, clean status on both clones, README clone instructions, complete evidence package.
Automatic stop
Force-push, someone else’s repository, a fork used as the second copy, pasted secrets, or unexplained lost commits requires rebuilding safely.
Project checklist
- I pushed one empty GitHub repository I own and cloned it into a second folder.
- I published a laptop commit and a desk commit through origin, inspecting with fetch before each pull.
- I reproduced a rejected push and recovered with fetch and merge, not force-push.
- Laptop HEAD, desk HEAD, and origin/main are the same SHA.
- I never copied log.md between folders or opened a pull request.
Final audit commands
git rev-parse --show-toplevel
git remote -v
git status
git branch -vv
git log --oneline --decorate --graph --all
git rev-parse HEAD
git ls-remote origin refs/heads/main
git merge-base --is-ancestor HEAD origin/mainThose commands answer one question: do both working copies and the GitHub remote name the same commit? When they do, and the log contains every intended line, the unit project is complete. Pull requests stay for the next unit.