Git and pull requests
The multi-repository layout plus squash merges makes some ordinary-looking commands destructive. This guide covers the gotchas and how to avoid them.
The layout
edtech4good consists of independent repositories, each with its own remote. A change spanning repos requires a branch, a commit and a pull request per repository. Nothing in the tooling enforces merge order.
Practical consequences:
- Use the same branch name across all affected repositories so the set is findable.
- If one piece of a multi-repo change merges ahead of the others, a feature may become unreachable. The joi validator may reject unknown keys before the request leaves the browser, so the client-side validator can block a request the server would happily accept.
- State the merge order explicitly when opening PRs. Nothing in the tooling enforces it.
Merge conventions
Most edtech4good repositories squash merge pull requests into main, with history recorded as Title (#N).
Before opening a pull request
Check that your local main branch is not ahead of origin/main:
git rev-list --count origin/main..mainIf this is not 0, stop. Your branch sits on commits the remote has never seen, and they will appear in your PR under your title. Push main first after checking with whoever owns those commits.
Recheck before merging
After a PR has been open for a while, the base branch may move under you. Immediately before merging, check that the PR includes only your work:
gh pr view <n> --json commits,filesIf it lists commits or files that are not yours, the base moved under you. Rebase onto current origin/main first.
Stacked pull requests
Stacking is appropriate when work has real ordering: schema, then the fix that needs it, then the docs. Each PR targets its parent.
Do not delete the base of an open PR
Do not gh pr merge --delete-branch a PR that is the base of another PR.
GitHub closes the child PR instead of retargeting it. A closed PR whose base branch no longer exists cannot be reopened. The work is not lost (the head branches survive), but the PR, its review and its history are gone.
Do this instead:
- Merge the parent without
--delete-branch. - Confirm the child is still open.
- Retarget it (see below).
- Delete branches only once the whole stack has landed.
Squash merge breaks rebasing
Squashing collapses the parent's commits into one new commit on main. The child branch still holds the parent's original commits. Rebasing replays them against main's squashed version, which may drop them as empty or create conflicts with intermediate states.
The reliable approach: reset and cherry-pick only what is new
After the parent merges, on your child branch:
git reset --hard origin/main
git cherry-pick <child-only-commits>
git push --force-with-lease origin <child-branch>Check the diff size. If it looks like the parent's work is in there too, you cherry-picked too much.
Always use --force-with-lease only on your own unmerged branch, and ask before force-pushing anyone else's work.
Retargeting a PR base
gh pr edit --base main fails in this organization with a deprecated GraphQL error and does not change the base. Use REST instead:
gh api -X PATCH repos/edtech4good/<repo>/pulls/<n> -f base=main --jq '.base.ref'The --jq flag prints the base back so you can verify the change took.
Verify a merge by content, not by status
"Merged" is not "correct". After merging, confirm the work is actually on main:
git fetch origin
git show origin/main --stat --oneline | head
git ls-tree -r origin/main --name-only | grep <expected-file>Grep is case-sensitive. A lowercase filename will not match a capitalized pattern, and it will look exactly like a missing file. Check the tree before raising an alarm.
PR descriptions
The diff shows what changed. The description carries what a reviewer cannot recover: why this shape over alternatives, what was ruled out and why, and what was deliberately left undone.