From 94667a87ed66c430769cc85d34d517cdf28b6938 Mon Sep 17 00:00:00 2001 From: Anoop Narang Date: Mon, 28 Sep 2026 11:03:47 +0530 Subject: [PATCH 1/3] docs: use the standard fork remote layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything in this file that warned about remote names, spelled commands with full URLs, or repeated that `FETCH_HEAD` holds only the last fetch existed because this clone had `origin` pointing at upstream and the fork on a second remote named `fork` — the reverse of the convention. That is a solved problem, and I wrote a workaround for it instead of fixing it. The workaround then produced its own bugs: three wrong instructions from `FETCH_HEAD` being last-fetch-wins, and a verify step that compared our main against our own fork and so could not fail. The file now states the standard layout up front -- `origin` ours, `upstream` theirs -- with the rename commands to get there, and uses `origin/main` and `upstream/main` throughout. Those are stable refs, so the ordering hazard disappears rather than needing a warning at each site. 153 lines to 128, and `FETCH_HEAD` from eleven mentions to one. Every command in the file was executed against this repo. That found `git fetch origin upstream`, which does not fetch two remotes -- git reads `upstream` as a refspec on `origin` and fails with "couldn't find remote ref upstream". It is `git fetch --multiple origin upstream`. --- CLAUDE.md | 115 +++++++++++++++++++++--------------------------------- 1 file changed, 45 insertions(+), 70 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 20cd2478..3d50230e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,61 +2,49 @@ `hotdata-dev/liquid-cache` is a fork of `datafusion-contrib/liquid-cache`. `main` contains upstream's history in full plus our patches, so -`git merge-base main ` resolves. +`git merge-base main upstream/main` resolves. -Nothing here states where upstream currently is, or where we are: both move. -Ask git instead. +## Set your remotes up first + +The standard fork layout, which everything below assumes: ``` -git fetch https://github.com/datafusion-contrib/liquid-cache main -git log --oneline FETCH_HEAD..main # ours that upstream does not have -git log --oneline main..FETCH_HEAD # upstream's that we do not have +origin hotdata-dev/liquid-cache ours, where we push +upstream datafusion-contrib/liquid-cache theirs, read-only ``` -`AGENTS.md` and `README.md` are upstream's and describe the project itself. -This file is ours and describes only what differs here. Nothing in this repo -should edit an upstream-owned file to record a fork convention: that conflicts -on every sync. Add a file upstream does not have instead. - -## Remotes - -**Check before using a remote name. They vary by clone and they are not what -you would guess** — in at least one working copy `origin` is *upstream* and the -fork is a second remote named `fork`, which is the reverse of the usual -arrangement. +Check with `git remote -v`. A clone made from upstream has it backwards — +`origin` pointing at *upstream* — which has already been misread as the +opposite of what it meant. Fix it once: ``` -git remote -v +git remote rename origin upstream +git remote add origin git@github.com:hotdata-dev/liquid-cache.git +git remote set-url upstream https://github.com/datafusion-contrib/liquid-cache.git ``` -Commands below name repositories by URL rather than by remote, so they are -correct in any clone. Do the same when writing instructions for anyone else: a -bare `origin/main` is ambiguous here and has already been misread as the -opposite of what it meant. - -## Branches +Then `origin/main` and `upstream/main` are stable refs that mean one thing. +Prefer them over `FETCH_HEAD`, which holds only the most recent fetch and has +repeatedly produced instructions in this file that silently checked the wrong +thing. -- Work off `main`, and fetch before branching — a local `main` goes stale with - no signal, and branching off a stale one silently drops everything merged - since. -- **Upstream PRs branch from upstream, not from `main`**, and are named - `upstream/`. A branch cut from `main` carries our whole patch stack - into the PR diff. +`AGENTS.md` and `README.md` are upstream's and describe the project itself. +This file is ours and describes only what differs here. Nothing in this repo +should edit an upstream-owned file to record a fork convention: that conflicts +on every sync. Add a file upstream does not have instead. - ``` - git fetch https://github.com/datafusion-contrib/liquid-cache main - git checkout -b upstream/ FETCH_HEAD - ``` +## Branches - Before pushing, this must list only the commits you wrote — anything else is - a fork patch that would land in the upstream diff. Re-fetch upstream on the - line above it: `FETCH_HEAD` holds whatever the last fetch wrote, so after - fetching any other remote it is no longer upstream and the check hides every - fork patch. +- `git fetch origin` before branching, and branch from `origin/main`. A local + `main` goes stale with no signal, and branching off a stale one silently + drops everything merged since. +- **Upstream PRs branch from upstream**, named `upstream/`. A branch cut + from our `main` carries the whole patch stack into the PR diff. ``` - git fetch https://github.com/datafusion-contrib/liquid-cache main - git log --oneline FETCH_HEAD..HEAD + git fetch upstream + git checkout -b upstream/ upstream/main + git log --oneline upstream/main..HEAD # must list only your own commits ``` `.github/workflows/upstream-branch-guard.yml` checks the same property on @@ -73,45 +61,32 @@ opposite of what it meant. ## Syncing from upstream -Merge upstream into a branch off `main` and raise a PR; do not use GitHub's -"Sync fork" button, which offers to discard our commits when the merge is not -a fast-forward. - -Fetch *this fork* first and branch from what came back, not from local `main` -— the upstream fetch below never refreshes `main`, so a stale one would make -the sync PR revert fork commits merged since. Use each `FETCH_HEAD` -immediately: it holds only the last fetch. - ``` -git fetch https://github.com/hotdata-dev/liquid-cache main -git checkout -b sync/upstream- FETCH_HEAD - -git fetch https://github.com/datafusion-contrib/liquid-cache main -git merge FETCH_HEAD +git fetch --multiple origin upstream +git checkout -b sync/upstream- origin/main +git merge upstream/main ``` -**Merge this PR, do not squash it.** A squash gives the result a single parent, -so upstream's history never enters `main`'s ancestry: the merge-base does not -move, the same upstream commits stay missing, and nothing reports it. +Raise a PR; do not use GitHub's "Sync fork" button, which offers to discard our +commits when the merge is not a fast-forward. -Verify afterwards. The merge happens on GitHub, so local `main` does not have -it and must not be what you check; and `FETCH_HEAD` holds only the last fetch, -so capture upstream before fetching the fork over it: +**Merge that PR, do not squash it.** A squash gives the result a single parent, +so upstream's history never enters `main`'s ancestry: the merge-base does not +move, the same upstream commits stay missing, and nothing reports it. Verify +afterwards — the merge happens on GitHub, so fetch before checking: ``` -git fetch https://github.com/datafusion-contrib/liquid-cache main -upstream=$(git rev-parse FETCH_HEAD) -git fetch https://github.com/hotdata-dev/liquid-cache main -git merge-base --is-ancestor "$upstream" FETCH_HEAD && echo ok +git fetch --multiple origin upstream +git merge-base --is-ancestor upstream/main origin/main && echo ok ``` A change we contributed upstream comes back as their squash of it. The content matches but the commit does not, so the merge conflicts where both sides -touched the same lines — typically a module list that each side appended to. -Keep ours *for the returned change*, and keep any other upstream edit in the -same hunk: upstream may have appended something of its own next to it, and -taking the whole hunk from our side drops that silently. Read the hunk rather -than resolving by rule. +touched the same lines — typically a module list each side appended to. Keep +ours *for the returned change*, and keep any other upstream edit in the same +hunk: upstream may have appended something of its own next to it, and taking +the whole hunk from our side drops that silently. Read the hunk rather than +resolving by rule. Sync promptly rather than letting such a conflict wait: alone it is obvious, bundled with real upstream work later it is not. From 04fb9017a769c5bf7917b905de3baba21bb3eb37 Mon Sep 17 00:00:00 2001 From: Anoop Narang Date: Mon, 28 Sep 2026 11:08:17 +0530 Subject: [PATCH 2/3] docs: give the remote setup for both kinds of clone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The setup block assumed a clone made from upstream and opened with `git remote rename origin upstream`. Run against a clone of this fork, whose `origin` is already correct, that renames the right remote away. Worse, it fails silently. Running part of the block on a fork clone — the rename and the add, without the `set-url` that used to follow — leaves both remotes pointing at this fork. `upstream/main` then means our own `main`, so the sync merges nothing, the post-sync check passes, and the branch guard sees no fork commits. Every recipe here reports success while comparing us against ourselves. Now split by what the clone actually is, with `git remote -v` first: a clone of the fork adds `upstream`, a clone of upstream renames and adds `origin`. Both paths run from scratch in a throwaway repo and end correct; the previous block left case one wrong. Adds the check that matters, since the failure has no symptom: the two remotes must name different repositories. --- CLAUDE.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3d50230e..1635be66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,14 +13,34 @@ origin hotdata-dev/liquid-cache ours, where we push upstream datafusion-contrib/liquid-cache theirs, read-only ``` -Check with `git remote -v`. A clone made from upstream has it backwards — -`origin` pointing at *upstream* — which has already been misread as the -opposite of what it meant. Fix it once: +See what you have first — the two cases need different commands, and running +the wrong one leaves both remotes pointing at this fork, where `upstream/main` +silently means our `main` and every recipe below is wrong with no error. + +``` +git remote -v +``` + +**Cloned this fork** (`origin` already correct) — add the other: + +``` +git remote add upstream https://github.com/datafusion-contrib/liquid-cache.git +``` + +**Cloned upstream** (`origin` points at *upstream*, which has already been +misread as the opposite of what it meant) — rename it, then add ours: ``` git remote rename origin upstream git remote add origin git@github.com:hotdata-dev/liquid-cache.git -git remote set-url upstream https://github.com/datafusion-contrib/liquid-cache.git +``` + +Either way, confirm the two point at *different* repositories before relying +on anything below: + +``` +git remote get-url origin # must be hotdata-dev +git remote get-url upstream # must be datafusion-contrib ``` Then `origin/main` and `upstream/main` are stable refs that mean one thing. From e840bfcd3f937fe73f93e91a1cac67253374f8d6 Mon Sep 17 00:00:00 2001 From: Anoop Narang Date: Mon, 28 Sep 2026 11:10:40 +0530 Subject: [PATCH 3/3] docs: repoint main after the rename, and stop checking local main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `git remote rename` rewrites the tracking config of every branch that followed the renamed remote. The documented rename therefore leaves local `main` following `upstream/main`: a `git pull` on `main` merges upstream straight into it, and nothing says so. Verified in a throwaway repo — before the rename `main` tracks `origin`, after it tracks `upstream`. The recipe now fetches and re-points `main` at `origin/main` afterwards. The opening line had the same weakness from the other direction. It claimed `git merge-base main upstream/main` resolves, which rests on local `main` being both correctly tracked and current — and with the tracking bug above it would pass whatever the fork's state was. It now names `origin/main`, and says why: a local branch can track the wrong remote or be stale, and neither condition announces itself. This repo escaped the tracking bug by ordering luck. I re-pointed `main` at the fork before renaming the remotes, so the rename rewrote it to `origin` rather than away from it. --- CLAUDE.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1635be66..767b24bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,8 +1,10 @@ # Working in this fork `hotdata-dev/liquid-cache` is a fork of `datafusion-contrib/liquid-cache`. -`main` contains upstream's history in full plus our patches, so -`git merge-base main upstream/main` resolves. +`origin/main` contains upstream's history in full plus our patches, so +`git merge-base origin/main upstream/main` resolves. Check `origin/main` +rather than local `main`: a local branch can track the wrong remote or simply +be stale, and neither says so. ## Set your remotes up first @@ -33,8 +35,16 @@ misread as the opposite of what it meant) — rename it, then add ours: ``` git remote rename origin upstream git remote add origin git@github.com:hotdata-dev/liquid-cache.git +git fetch origin +git branch --set-upstream-to=origin/main main ``` +The last two matter: `git remote rename` rewrites the tracking config of every +branch that followed it, so after the rename local `main` follows +`upstream/main`. A `git pull` on `main` would then merge upstream straight into +it, and `git merge-base main upstream/main` would pass whatever the fork's +state is. + Either way, confirm the two point at *different* repositories before relying on anything below: