From 38eb0745b7a5eddf55776ddb2906c4e84702edc4 Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Sun, 30 Aug 2026 13:10:53 +0000 Subject: [PATCH] refactor(git-remotes): retrofit to the ADR-0020 context contract Description 582 -> 237 chars, body 1217 -> 290 words. The single remotes.md splits into config, fetch, push, and pull flow files. Restores the confirm: true token to the force-push gate -- it is the git plugin's cross-skill contract, gated on by git-orchestrate and git-branches. Moves push.md's worked example off main, which the skill's own Step 1 refuses, and restores the never-bare---force directive. --- plugins/git/.apm/skills/git-remotes/README.md | 14 ++- plugins/git/.apm/skills/git-remotes/SKILL.md | 106 ++++-------------- .../skills/git-remotes/references/README.md | 5 +- .../skills/git-remotes/references/fetch.md | 29 +++++ .../skills/git-remotes/references/pull.md | 37 ++++++ .../skills/git-remotes/references/push.md | 67 +++++++++++ .../git-remotes/references/remote-config.md | 39 +++++++ .../skills/git-remotes/references/remotes.md | 82 -------------- .../skills/git-remotes/references/sources.md | 19 ++-- plugins/git/skills/git-remotes/README.md | 14 ++- plugins/git/skills/git-remotes/SKILL.md | 106 ++++-------------- .../skills/git-remotes/references/README.md | 5 +- .../skills/git-remotes/references/fetch.md | 29 +++++ .../git/skills/git-remotes/references/pull.md | 37 ++++++ .../git/skills/git-remotes/references/push.md | 67 +++++++++++ .../git-remotes/references/remote-config.md | 39 +++++++ .../skills/git-remotes/references/remotes.md | 82 -------------- .../skills/git-remotes/references/sources.md | 19 ++-- 18 files changed, 440 insertions(+), 356 deletions(-) create mode 100644 plugins/git/.apm/skills/git-remotes/references/fetch.md create mode 100644 plugins/git/.apm/skills/git-remotes/references/pull.md create mode 100644 plugins/git/.apm/skills/git-remotes/references/push.md create mode 100644 plugins/git/.apm/skills/git-remotes/references/remote-config.md delete mode 100644 plugins/git/.apm/skills/git-remotes/references/remotes.md create mode 100644 plugins/git/skills/git-remotes/references/fetch.md create mode 100644 plugins/git/skills/git-remotes/references/pull.md create mode 100644 plugins/git/skills/git-remotes/references/push.md create mode 100644 plugins/git/skills/git-remotes/references/remote-config.md delete mode 100644 plugins/git/skills/git-remotes/references/remotes.md diff --git a/plugins/git/.apm/skills/git-remotes/README.md b/plugins/git/.apm/skills/git-remotes/README.md index 33382a1..0895aa2 100644 --- a/plugins/git/.apm/skills/git-remotes/README.md +++ b/plugins/git/.apm/skills/git-remotes/README.md @@ -18,7 +18,17 @@ Describe your remote operation: add a remote, push, pull, fetch, or configure tr | File | Purpose | |------|---------| -| `SKILL.md` | Skill instructions for agents | +| `SKILL.md` | Skill instructions for agents — force-push gate, dispatch table, return format | | `references/README.md` | Describes the references directory contents | -| `references/remotes.md` | Full `set-url` variants, shallow-clone/fetch options, force-push mitigation detail, and pull config precedence | +| `references/remote-config.md` | Read when adding, removing, renaming, inspecting or re-pointing a remote, or configuring tracking, mirroring, or `set-url` | +| `references/fetch.md` | Read when fetching or pruning remote-tracking refs, or doing a shallow or partial fetch | +| `references/push.md` | Read when pushing branches or tags, writing refspecs, or force-pushing | +| `references/pull.md` | Read when integrating remote changes into the current branch, including the divergence rule | | `references/sources.md` | Research sources and provenance | + +## Composition + +Callers that need submodule initialization after a `--recurse-submodules` pull hand off to +`git-submodules`; local-only work (commits, branches, history) belongs to `git-commits`, +`git-branches`, and `git-history`. The `git-workflow` skill routes humans here for any +remote-touching request. diff --git a/plugins/git/.apm/skills/git-remotes/SKILL.md b/plugins/git/.apm/skills/git-remotes/SKILL.md index 9163466..5cc0eca 100644 --- a/plugins/git/.apm/skills/git-remotes/SKILL.md +++ b/plugins/git/.apm/skills/git-remotes/SKILL.md @@ -2,13 +2,11 @@ name: git-remotes description: > - Manage git remote repositories — add/remove/configure remotes, push/pull with safety checks, - handle fetch patterns and tracking branch updates, support multi-remote workflows. - Use when automating remote operations, pushing with force-push safety, fetching with pruning, - pulling with divergence resolution, or managing multi-remote tracking. Include indirect triggers: - any git operation that touches a remote, even if the user doesn't explicitly name the remote. - Do not use when working with local git history, commits, branches, or staging — use git-history - or git-branches instead. + Use when a git operation — remote config, fetch, push, or pull — touches a + remote, even when the user does not name it. + Not local commits -> `git-commits`. + Not local branches -> `git-branches`. + Not submodule pointers -> `git-submodules`. metadata: category: git-workflow @@ -23,91 +21,29 @@ metadata: ## Gotchas -- **Never force-push `main` or `master`, under any circumstances** — this is a hard refusal, not a `confirm: true` gate. If a force-push targets one of these branches, decline and explain why, regardless of how the request is confirmed. -- **Force-push to any other branch requires explicit confirmation** — never execute `git push --force` or `git push -f` without user/agent approval. Always ask or require `confirm: true` flag first. -- **`--force-with-lease` alone is not safe** — background processes (IDE plugins, cron jobs) that run `git fetch` silently defeat the protection. Always combine with `--force-if-includes` or use explicit SHA form `--force-with-lease=:`. -- **Prune doesn't touch tags by default** — `git fetch --prune` leaves orphaned tags. Use `git fetch --prune --prune-tags` or configure `fetch.pruneTags true` globally. -- **Pull with rebase rewrites history** — only safe for unpublished work. Rebasing already-pushed commits breaks everyone downstream. Check what's been pushed before rebasing. -- **`git remote show` requires network access** — use `-n` flag for cached data if working offline. `git remote -v` lists URLs without network queries. -- **Pull behavior defaults shift between Git versions** — older versions default to merge, newer versions to `--ff-only`. Always set `pull.ff only` explicitly for deterministic behavior. +- **`--force-with-lease` alone is not safe** — background processes (IDE plugins, cron jobs) running `git fetch` silently defeat the protection. Combine it with `--force-if-includes`, or pin the explicit `--force-with-lease=:` form. +- **Prune does not touch tags by default** — `git fetch --prune` leaves orphaned tags behind. Use `--prune --prune-tags`, or set `fetch.pruneTags true`. +- **Pull defaults shift between Git versions** — older ones default to merge, newer to `--ff-only`. Set `pull.ff only` explicitly rather than trusting the installed default. -## Operations +## Step 1 — Clear the force-push gate -### Remote Management +`main` and `master` are a hard refusal: decline a force-push targeting either, whatever confirmation accompanies it, because no local approval can restore what the remote loses. On any other branch, `git push --force` and `-f` run only after the caller passes `confirm: true` for that specific push — for a human caller, prompt instead of failing. -Use these to configure which remotes you push to and pull from: +## Step 2 — Dispatch -- **Add a remote**: `git remote add ` or `git remote add -f ` to fetch immediately -- **Remove a remote**: `git remote remove ` (deletes remote + all tracking refs + config) -- **Rename a remote**: `git remote rename ` -- **Inspect remotes**: `git remote -v` (show URLs) or `git remote show ` (live tracking status, requires network) -- **Set-url separately for fetch vs. push**: `git remote set-url --push ` changes only where pushes go — but fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead; `--push` cannot do this. Full `set-url` variants (regex-targeted replace, `--add`, `--delete`): `references/remotes.md`. -- **Remove a stale URL**: `git remote set-url --delete ` -- **Inspect effective URLs**: `git remote get-url ` (shows URL after `insteadOf` rewrites) or `git remote get-url --push --all ` (all push URLs) -- **Track only one branch**: `git remote add -t ` (repeatable), or suppress tag import entirely with `git remote add --no-tags ` -- **Mirror a remote**: `git remote add --mirror=fetch ` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror` -- **Prune stale tracking refs without fetching**: `git remote prune ` (add `--dry-run` to preview first) -- **Set the remote's default branch pointer**: `git remote set-head -a` (auto-detect, requires a prior fetch), `git remote set-head ` (explicit), or `git remote set-head -d` (delete `refs/remotes//HEAD`) +Read the row matching the operation, and only that row — each file is self-contained. A task spanning two operations reads both. -### Fetch Operations - -Use these to update your tracking branches without touching your local branches: - -- **Fetch from one remote**: `git fetch ` — fetches all branches -- **Fetch one branch only**: `git fetch ` — stores the result in `FETCH_HEAD`, not a tracking ref -- **Fetch from all remotes**: `git fetch --all` with optional `--prune` to clean up stale tracking refs -- **Prune properly**: Use `git fetch --all --prune --prune-tags` to clean both branches and tags -- **Configure auto-prune**: Set `git config --global fetch.prune true` to auto-prune on every fetch across all remotes (or `remote..prune` to scope it to one remote) -- **Shallow clones**: `--depth=` to deepen or create a shallow clone, `--unshallow` to convert to full history, `--update-shallow` to allow the shallow boundary to move. Details and the default fetch refspec: `references/remotes.md`. - -Fetch never modifies your local branches — it only updates remote-tracking branches (`refs/remotes/origin/*`). - -### Push Operations - -Use these to send your commits upstream. Default: safe push to same-named branch on the remote. - -- **Basic push**: `git push ` — pushes to same-named remote branch -- **Set upstream**: `git push -u ` — push and configure this branch to track the remote -- **Multi-remote push**: `git push origin develop` and `git push staging develop` sequentially, or use `git remote set-url --add ` to push to multiple remotes with one command -- **Force-push safety**: Always use `git push --force-with-lease --force-if-includes ` over bare `--force`. Require explicit confirmation first — and never for `main`/`master` (see Gotchas). `--force-if-includes` is a no-op without `--force-with-lease`. If background tools (IDE, cron) auto-fetch and could poison the lease check, use a dedicated push-only remote instead — see `references/remotes.md`. -- **Server-side enforcement**: `receive.denyDeletes`, `receive.denyDeleteCurrent`, and `receive.denyNonFastForwards` are enforced on the remote regardless of local flags — a hardened server rejects the push even with `--force`. -- **Delete remote branch**: `git push --delete ` (not `:` syntax; clearer and cleaner) -- **Push everything**: `git push --all` (all local branches) or `git push --tags` (all tags) -- **Push a single tag**: `git push origin ` -- **Delete remote branches with no local counterpart**: `git push --prune origin 'refs/heads/*:refs/heads/*'` -- **Force only part of a multi-ref push**: prefix the one refspec that needs it with `+`, e.g. `git push origin +main develop` forces `main` while safe-pushing `develop` - -Refspec syntax is `[+][:]`: - -| Pattern | Meaning | +| Operation | Read | |---|---| -| `` | Push to same-named remote branch | -| `:` | Push `` local ref to `` remote ref | -| `+:` | Force this refspec (non-fast-forward allowed) | -| `:` | Delete remote `` | -| `refs/heads/*:refs/heads/*` | Glob: push all matching branches | -| `^refs/heads/dev-*` | Negative: exclude matching refs | -| `tag ` | Sugar for `refs/tags/:refs/tags/` | +| Add, remove, rename, inspect, or re-point a remote; tracking, mirror, and `set-url` config | `references/remote-config.md` | +| Fetch or prune remote-tracking refs; shallow or partial fetch | `references/fetch.md` | +| Push branches or tags; refspecs; force-push | `references/push.md` | +| Pull — integrate remote changes into the current branch | `references/pull.md` | -### Pull Operations +## Step 3 — Return format -Use these to fetch and integrate remote changes. Default strategy: `--ff-only` (fail if diverged, forcing a conscious choice). +For agent callers, return: -- **Pull with fast-forward only**: `git pull --ff-only` (recommended default — fails if you've diverged, forcing a rebase/merge decision) -- **Pull with rebase**: `git pull --rebase` (replays your unpublished commits on top; linear history, but rewrites SHAs — only safe for unpublished work) -- **Pull with merge**: `git pull --no-rebase` (three-way merge commit; preserves original commits, non-linear) -- **Pull with rebase, preserving merges**: `git pull --rebase=merges` (like `--rebase`, but keeps intentional local merge commits during replay) -- **Pull without integrating**: `git pull --squash` collapses incoming commits into staged changes without committing — you write the commit message -- **Set pull strategy globally**: `git config pull.ff only` (or `pull.rebase true`; respects branch-specific overrides via `branch..rebase`). Full precedence order (CLI flag > `pull.rebase` > `branch..rebase` > `branch.autoSetupRebase`): `references/remotes.md`. -- **Check before rebasing**: Always verify your commits haven't been pushed before using `--rebase`. Rebasing published commits breaks everyone downstream. -- **Merge strategy default**: Git 2.34+ defaults to the `ort` merge strategy (`recursive` is now just an alias for it). Strategy options like `-X ours`, `-X theirs`, `-X ignore-space-change` still pass through unchanged. -- **Submodules on pull**: `--recurse-submodules` only fetches submodules that are already checked out — newly added submodules are not initialized automatically. Use the `git-submodules` skill to initialize new ones. - -If pull diverges and you haven't set a strategy, the operation fails — this is good, forces a conscious choice. Never auto-merge diverged branches without asking. - -### Return Format (for agents) - -Return structured output: ```json { "success": true, @@ -116,8 +52,8 @@ Return structured output: "branch": "main", "output": "...", "warnings": ["force-with-lease not confirmed"], - "recommendations": ["set pull.ff=only globally"] + "recommendations": ["set `pull.ff only` so the default does not vary by Git version"] } ``` -On failure, include `error` field with root cause and recovery suggestion. +On failure, set `success: false` and add an `error` field holding the root cause and a recovery suggestion. diff --git a/plugins/git/.apm/skills/git-remotes/references/README.md b/plugins/git/.apm/skills/git-remotes/references/README.md index ea38459..a9ca455 100644 --- a/plugins/git/.apm/skills/git-remotes/references/README.md +++ b/plugins/git/.apm/skills/git-remotes/references/README.md @@ -14,4 +14,7 @@ This directory contains provenance metadata and research sources for the `git-re ## Files - `sources.md` — Extracted research sources and their contributing documents -- `remotes.md` — Full `set-url` variants, shallow-clone/fetch options, default fetch refspec, force-push mitigation detail, server-side deny policies, and pull config precedence +- `remote-config.md` — Remote add/remove/rename/inspect, tracking and mirror options, housekeeping, and the full `set-url` form +- `fetch.md` — Fetch and prune options, shallow and partial fetch, the default fetch refspec +- `push.md` — Push options, refspec syntax, force-push safety in full, server-side deny policies +- `pull.md` — Pull strategies, submodule caveat, the divergence rule, and pull config precedence diff --git a/plugins/git/.apm/skills/git-remotes/references/fetch.md b/plugins/git/.apm/skills/git-remotes/references/fetch.md new file mode 100644 index 0000000..e0c05ff --- /dev/null +++ b/plugins/git/.apm/skills/git-remotes/references/fetch.md @@ -0,0 +1,29 @@ +--- +topic: fetch +source_keys: + - git-scm-fetch-docs + - context7-git-htmldocs +--- + +# Fetching + +Fetch updates remote-tracking branches (`refs/remotes//*`) and never modifies a local branch, so it is always safe to run. + +- **One remote**: `git fetch ` — all branches +- **One branch**: `git fetch ` — the result lands in `FETCH_HEAD`, not a tracking ref +- **All remotes**: `git fetch --all` +- **Prune properly**: `git fetch --all --prune --prune-tags` cleans stale branches *and* tags +- **Auto-prune**: `git config --global fetch.prune true` (or `remote..prune` to scope it to one remote), and `fetch.pruneTags true` for tags + +## Shallow and partial fetch + +```bash +git fetch --depth= # deepen history, or create a shallow clone +git fetch --unshallow # convert a shallow clone to full history +git fetch --update-shallow # allow the fetch to update the shallow boundary +git fetch --refmap='' # fetch without updating any tracking ref (FETCH_HEAD only) +``` + +## Default fetch refspec + +The default is `+refs/heads/*:refs/remotes//*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and offer no protection for local history. diff --git a/plugins/git/.apm/skills/git-remotes/references/pull.md b/plugins/git/.apm/skills/git-remotes/references/pull.md new file mode 100644 index 0000000..3a8a25a --- /dev/null +++ b/plugins/git/.apm/skills/git-remotes/references/pull.md @@ -0,0 +1,37 @@ +--- +topic: pull +source_keys: + - git-scm-pull-docs + - context7-git-htmldocs +--- + +# Pulling + +Default strategy: `--ff-only`. It fails on divergence, which forces a conscious choice instead of an accidental merge commit. + +- **Fast-forward only**: `git pull --ff-only` — the recommended default +- **Rebase**: `git pull --rebase` replays your commits on top for linear history, but rewrites SHAs. Verify nothing being replayed has been pushed: rebasing published commits breaks everyone downstream. +- **Merge**: `git pull --no-rebase` — three-way merge commit, preserves original commits, non-linear +- **Rebase preserving merges**: `git pull --rebase=merges` keeps intentional local merge commits during the replay +- **Stage without committing**: `git pull --squash` collapses incoming commits into staged changes; you write the message +- **Merge strategy**: Git 2.34+ defaults to `ort` (`recursive` is now an alias for it). Strategy options such as `-X ours`, `-X theirs`, `-X ignore-space-change` pass through unchanged. +- **Submodules**: `--recurse-submodules` only fetches submodules already checked out. Newly added ones are not initialized — use the `git-submodules` skill for those. + +## On divergence + +A pull that diverges with no strategy configured fails, and that failure is the useful outcome. Report the divergence and the three ways out — `--ff-only`, `--rebase`, `--no-rebase` — and let the caller choose. Auto-merging a diverged branch buries a decision that belongs to the human. + +## Config precedence + +Highest wins: + +1. Command-line flag (`--ff-only` / `--rebase` / `--no-rebase`) +2. `pull.rebase` config (global or local) +3. `branch..rebase` (branch-specific override) +4. `branch.autoSetupRebase` (set automatically when the tracking branch was created) + +```bash +git config pull.ff only # deterministic default across Git versions +git config --global pull.rebase true +git config branch.develop.rebase false # develop always merges, regardless of the global default +``` diff --git a/plugins/git/.apm/skills/git-remotes/references/push.md b/plugins/git/.apm/skills/git-remotes/references/push.md new file mode 100644 index 0000000..dd032fb --- /dev/null +++ b/plugins/git/.apm/skills/git-remotes/references/push.md @@ -0,0 +1,67 @@ +--- +topic: push +source_keys: + - git-scm-push-docs + - context7-git-htmldocs +--- + +# Pushing + +Default: safe push to the same-named branch on the remote. + +- **Force-push**: never bare `--force`. Use `git push --force-with-lease --force-if-includes `, after the SKILL.md Step 1 gate. +- **Basic**: `git push ` +- **Set upstream**: `git push -u ` — push and configure tracking +- **Multi-remote**: push sequentially (`git push origin develop`, `git push staging develop`), or add a second push URL with `git remote set-url --add ` to reach both in one command +- **Delete a remote branch**: `git push --delete ` — clearer than the `:` form +- **Bulk**: `git push --all` (all local branches), `git push --tags` (all tags), `git push origin ` (one tag) +- **Delete remote branches with no local counterpart**: `git push --prune origin 'refs/heads/*:refs/heads/*'` +- **Force only part of a multi-ref push**: prefix the one refspec that needs it with `+` — `git push origin +release develop` forces `release` while safe-pushing `develop`. A `+` prefix is a force-push and passes the SKILL.md Step 1 gate like any other. + +## Refspec syntax — `[+][:]` + +| Pattern | Meaning | +|---|---| +| `` | Push to same-named remote branch | +| `:` | Push `` local ref to `` remote ref | +| `+:` | Force this refspec (non-fast-forward allowed) — a force-push; passes the SKILL.md Step 1 gate | +| `:` | Delete remote `` | +| `refs/heads/*:refs/heads/*` | Glob: push all matching branches | +| `^refs/heads/dev-*` | Negative: exclude matching refs | +| `tag ` | Sugar for `refs/tags/:refs/tags/` | + +## Force-push safety — full detail + +`--force-with-lease` rejects the push if the remote ref moved since your last fetch. Three forms: + +| Form | What it protects | +|---|---| +| `--force-with-lease` (bare) | All refs being pushed, checked against your remote-tracking branch | +| `--force-with-lease=` | Named ref only | +| `--force-with-lease=:` | Named ref must be at exact SHA — most stable | + +**Caveat with the bare form:** any background process that runs `git fetch` (IDE plugin, cron job, editor auto-fetch) updates your remote-tracking branch, which can make the lease check pass even though someone else pushed in between. The protection is silently defeated. + +Two mitigations: + +```bash +# Option 1 — dedicated push-only remote: background tools fetch `origin`, you push +# through a separate remote that nothing else touches, so its tracking ref can't be +# poisoned by an unrelated fetch. +git remote add origin-push $(git config remote.origin.url) +git push --force-with-lease origin-push + +# Option 2 — explicit SHA via a local tag, unaffected by tracking-branch state +git fetch +git tag base master +git rebase -i master +git push --force-with-lease=master:base master:master +``` + +`--force-if-includes` adds a second check on top of bare `--force-with-lease`: it verifies the remote-tracking tip actually appears in your local branch's reflog, i.e. you genuinely integrated it before rewriting. It is a no-op without `--force-with-lease`, and has no effect with the `--force-with-lease=:` form, which already pins an exact SHA. + +Safest combination: `git push --force-with-lease --force-if-includes origin`. + +## Server-side policy + +`receive.denyDeletes`, `receive.denyDeleteCurrent` and `receive.denyNonFastForwards` are enforced on the remote regardless of any local flag — a hardened server rejects the push even with `--force`. diff --git a/plugins/git/.apm/skills/git-remotes/references/remote-config.md b/plugins/git/.apm/skills/git-remotes/references/remote-config.md new file mode 100644 index 0000000..5d44eef --- /dev/null +++ b/plugins/git/.apm/skills/git-remotes/references/remote-config.md @@ -0,0 +1,39 @@ +--- +topic: remote-config +source_keys: + - git-scm-remote-docs + - context7-git-htmldocs +--- + +# Remote configuration + +Which remotes exist, where they point, and what they track. + +`git remote show ` needs network access — use `-n` for cached data offline, or `git remote -v`, which lists URLs without querying. + +## Add, remove, rename, inspect + +- **Add**: `git remote add `, or `-f` to fetch immediately +- **Remove**: `git remote remove ` — deletes the remote, all its tracking refs, and its config +- **Rename**: `git remote rename ` +- **Inspect**: `git remote -v` (URLs, offline) or `git remote show ` (live tracking status) +- **Effective URLs**: `git remote get-url ` shows the URL after `insteadOf` rewrites; `git remote get-url --push --all ` lists every push URL + +## Tracking, mirroring, housekeeping + +- **Track one branch**: `git remote add -t ` (repeatable); `--no-tags` suppresses tag import entirely +- **Mirror**: `--mirror=fetch` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror` +- **Prune stale tracking refs without fetching**: `git remote prune `, with `--dry-run` to preview +- **Default branch pointer**: `git remote set-head -a` (auto-detect, needs a prior fetch), `... ` (explicit), `... -d` (delete `refs/remotes//HEAD`) + +## `set-url` — full form + +```bash +git remote set-url # replace the first fetch URL +git remote set-url # replace only the URL matching regex +git remote set-url --push # change push URL only (must point at same repo) +git remote set-url --add # add an extra push URL (push to multiple remotes) +git remote set-url --delete # remove URLs matching regex +``` + +`--push` changes only where pushes go — fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead; `--push` cannot do this. diff --git a/plugins/git/.apm/skills/git-remotes/references/remotes.md b/plugins/git/.apm/skills/git-remotes/references/remotes.md deleted file mode 100644 index ef765d9..0000000 --- a/plugins/git/.apm/skills/git-remotes/references/remotes.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -topic: remotes -source_keys: - - git-scm-remote-docs - - git-scm-fetch-docs - - git-scm-push-docs - - git-scm-pull-docs ---- - -## `set-url` — full form - -```bash -git remote set-url # replace the first fetch URL -git remote set-url # replace only the URL matching regex -git remote set-url --push # change push URL only (must point at same repo) -git remote set-url --add # add an extra push URL (push to multiple remotes) -git remote set-url --delete # remove URLs matching regex -``` - -`--push` changes only where pushes go — fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead. - -## Shallow clones and partial fetch - -```bash -git fetch # fetch one branch only, stored in FETCH_HEAD (not a local/tracking ref) -git fetch --depth= # deepen history, or create a shallow clone -git fetch --unshallow # convert a shallow clone to full history -git fetch --update-shallow # allow the fetch to update the shallow boundary -git fetch --refmap='' # fetch without updating any tracking ref (FETCH_HEAD only) -``` - -## Default fetch refspec - -The default fetch refspec is `+refs/heads/*:refs/remotes//*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and provide no protection for local history. Fetch never touches your local branches, only remote-tracking refs. - -## Force-push safety — full detail - -`--force-with-lease` rejects the push if the remote ref moved since your last fetch. Three forms: - -| Form | What it protects | -|---|---| -| `--force-with-lease` (bare) | All refs being pushed, checked against your remote-tracking branch | -| `--force-with-lease=` | Named ref only | -| `--force-with-lease=:` | Named ref must be at exact SHA — most stable | - -**Caveat with the bare form:** any background process that runs `git fetch` (IDE plugin, cron job, editor auto-fetch) updates your remote-tracking branch, which can make the lease check pass even though someone else pushed in between. The protection is silently defeated. - -Two mitigations: - -```bash -# Option 1 — dedicated push-only remote: background tools fetch `origin`, you push -# through a separate remote that nothing else touches, so its tracking ref can't be -# poisoned by an unrelated fetch. -git remote add origin-push $(git config remote.origin.url) -git push --force-with-lease origin-push - -# Option 2 — explicit SHA via a local tag, unaffected by tracking-branch state -git fetch -git tag base master -git rebase -i master -git push --force-with-lease=master:base master:master -``` - -`--force-if-includes` adds a second check on top of bare `--force-with-lease`: it verifies the remote-tracking tip actually appears in your local branch's reflog, i.e. you genuinely integrated it before rewriting. It is a no-op without `--force-with-lease`, and has no effect when the `--force-with-lease=:` form is used (that form already pins an exact SHA). - -Safest combination: `git push --force-with-lease --force-if-includes origin`. - -Remote-side policies (`receive.denyDeletes`, `receive.denyDeleteCurrent`, `receive.denyNonFastForwards`) are enforced server-side regardless of any local flag — a server configured this way rejects the push even with `--force`. - -## Pull config precedence - -Highest wins: - -1. Command-line flag (`--ff-only` / `--rebase` / `--no-rebase`) -2. `pull.rebase` config (global or local) -3. `branch..rebase` (branch-specific override) -4. `branch.autoSetupRebase` (set automatically when the tracking branch was created) - -```bash -git config --global pull.rebase true -git config branch.develop.rebase false # develop always merges, regardless of the global default -``` diff --git a/plugins/git/.apm/skills/git-remotes/references/sources.md b/plugins/git/.apm/skills/git-remotes/references/sources.md index 694c756..f2ad70e 100644 --- a/plugins/git/.apm/skills/git-remotes/references/sources.md +++ b/plugins/git/.apm/skills/git-remotes/references/sources.md @@ -12,8 +12,7 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Remote Management (`git remote`)` **Contributing files:** -- SKILL.md (Remote Management section) -- references/remotes.md (`set-url` full form) +- references/remote-config.md --- @@ -26,8 +25,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Fetching (`git fetch`)` **Contributing files:** -- SKILL.md (Fetch Operations section, Gotchas) -- references/remotes.md (shallow clones, default fetch refspec) +- SKILL.md (Gotchas — prune does not touch tags) +- references/fetch.md --- @@ -40,8 +39,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Pushing (`git push`)` **Contributing files:** -- SKILL.md (Push Operations section, Gotchas) -- references/remotes.md (force-push safety full detail, server-side deny policies) +- SKILL.md (Gotchas — `--force-with-lease` caveat; Step 1 force-push gate) +- references/push.md --- @@ -54,8 +53,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Pulling (`git pull`)` **Contributing files:** -- SKILL.md (Pull Operations section, Gotchas) -- references/remotes.md (pull config precedence) +- SKILL.md (Gotchas — pull default drift) +- references/pull.md (divergence rule; strategies; config precedence) --- @@ -69,3 +68,7 @@ **Contributing files:** - SKILL.md (all sections) +- references/remote-config.md +- references/fetch.md +- references/push.md +- references/pull.md diff --git a/plugins/git/skills/git-remotes/README.md b/plugins/git/skills/git-remotes/README.md index 33382a1..0895aa2 100644 --- a/plugins/git/skills/git-remotes/README.md +++ b/plugins/git/skills/git-remotes/README.md @@ -18,7 +18,17 @@ Describe your remote operation: add a remote, push, pull, fetch, or configure tr | File | Purpose | |------|---------| -| `SKILL.md` | Skill instructions for agents | +| `SKILL.md` | Skill instructions for agents — force-push gate, dispatch table, return format | | `references/README.md` | Describes the references directory contents | -| `references/remotes.md` | Full `set-url` variants, shallow-clone/fetch options, force-push mitigation detail, and pull config precedence | +| `references/remote-config.md` | Read when adding, removing, renaming, inspecting or re-pointing a remote, or configuring tracking, mirroring, or `set-url` | +| `references/fetch.md` | Read when fetching or pruning remote-tracking refs, or doing a shallow or partial fetch | +| `references/push.md` | Read when pushing branches or tags, writing refspecs, or force-pushing | +| `references/pull.md` | Read when integrating remote changes into the current branch, including the divergence rule | | `references/sources.md` | Research sources and provenance | + +## Composition + +Callers that need submodule initialization after a `--recurse-submodules` pull hand off to +`git-submodules`; local-only work (commits, branches, history) belongs to `git-commits`, +`git-branches`, and `git-history`. The `git-workflow` skill routes humans here for any +remote-touching request. diff --git a/plugins/git/skills/git-remotes/SKILL.md b/plugins/git/skills/git-remotes/SKILL.md index 9163466..5cc0eca 100644 --- a/plugins/git/skills/git-remotes/SKILL.md +++ b/plugins/git/skills/git-remotes/SKILL.md @@ -2,13 +2,11 @@ name: git-remotes description: > - Manage git remote repositories — add/remove/configure remotes, push/pull with safety checks, - handle fetch patterns and tracking branch updates, support multi-remote workflows. - Use when automating remote operations, pushing with force-push safety, fetching with pruning, - pulling with divergence resolution, or managing multi-remote tracking. Include indirect triggers: - any git operation that touches a remote, even if the user doesn't explicitly name the remote. - Do not use when working with local git history, commits, branches, or staging — use git-history - or git-branches instead. + Use when a git operation — remote config, fetch, push, or pull — touches a + remote, even when the user does not name it. + Not local commits -> `git-commits`. + Not local branches -> `git-branches`. + Not submodule pointers -> `git-submodules`. metadata: category: git-workflow @@ -23,91 +21,29 @@ metadata: ## Gotchas -- **Never force-push `main` or `master`, under any circumstances** — this is a hard refusal, not a `confirm: true` gate. If a force-push targets one of these branches, decline and explain why, regardless of how the request is confirmed. -- **Force-push to any other branch requires explicit confirmation** — never execute `git push --force` or `git push -f` without user/agent approval. Always ask or require `confirm: true` flag first. -- **`--force-with-lease` alone is not safe** — background processes (IDE plugins, cron jobs) that run `git fetch` silently defeat the protection. Always combine with `--force-if-includes` or use explicit SHA form `--force-with-lease=:`. -- **Prune doesn't touch tags by default** — `git fetch --prune` leaves orphaned tags. Use `git fetch --prune --prune-tags` or configure `fetch.pruneTags true` globally. -- **Pull with rebase rewrites history** — only safe for unpublished work. Rebasing already-pushed commits breaks everyone downstream. Check what's been pushed before rebasing. -- **`git remote show` requires network access** — use `-n` flag for cached data if working offline. `git remote -v` lists URLs without network queries. -- **Pull behavior defaults shift between Git versions** — older versions default to merge, newer versions to `--ff-only`. Always set `pull.ff only` explicitly for deterministic behavior. +- **`--force-with-lease` alone is not safe** — background processes (IDE plugins, cron jobs) running `git fetch` silently defeat the protection. Combine it with `--force-if-includes`, or pin the explicit `--force-with-lease=:` form. +- **Prune does not touch tags by default** — `git fetch --prune` leaves orphaned tags behind. Use `--prune --prune-tags`, or set `fetch.pruneTags true`. +- **Pull defaults shift between Git versions** — older ones default to merge, newer to `--ff-only`. Set `pull.ff only` explicitly rather than trusting the installed default. -## Operations +## Step 1 — Clear the force-push gate -### Remote Management +`main` and `master` are a hard refusal: decline a force-push targeting either, whatever confirmation accompanies it, because no local approval can restore what the remote loses. On any other branch, `git push --force` and `-f` run only after the caller passes `confirm: true` for that specific push — for a human caller, prompt instead of failing. -Use these to configure which remotes you push to and pull from: +## Step 2 — Dispatch -- **Add a remote**: `git remote add ` or `git remote add -f ` to fetch immediately -- **Remove a remote**: `git remote remove ` (deletes remote + all tracking refs + config) -- **Rename a remote**: `git remote rename ` -- **Inspect remotes**: `git remote -v` (show URLs) or `git remote show ` (live tracking status, requires network) -- **Set-url separately for fetch vs. push**: `git remote set-url --push ` changes only where pushes go — but fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead; `--push` cannot do this. Full `set-url` variants (regex-targeted replace, `--add`, `--delete`): `references/remotes.md`. -- **Remove a stale URL**: `git remote set-url --delete ` -- **Inspect effective URLs**: `git remote get-url ` (shows URL after `insteadOf` rewrites) or `git remote get-url --push --all ` (all push URLs) -- **Track only one branch**: `git remote add -t ` (repeatable), or suppress tag import entirely with `git remote add --no-tags ` -- **Mirror a remote**: `git remote add --mirror=fetch ` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror` -- **Prune stale tracking refs without fetching**: `git remote prune ` (add `--dry-run` to preview first) -- **Set the remote's default branch pointer**: `git remote set-head -a` (auto-detect, requires a prior fetch), `git remote set-head ` (explicit), or `git remote set-head -d` (delete `refs/remotes//HEAD`) +Read the row matching the operation, and only that row — each file is self-contained. A task spanning two operations reads both. -### Fetch Operations - -Use these to update your tracking branches without touching your local branches: - -- **Fetch from one remote**: `git fetch ` — fetches all branches -- **Fetch one branch only**: `git fetch ` — stores the result in `FETCH_HEAD`, not a tracking ref -- **Fetch from all remotes**: `git fetch --all` with optional `--prune` to clean up stale tracking refs -- **Prune properly**: Use `git fetch --all --prune --prune-tags` to clean both branches and tags -- **Configure auto-prune**: Set `git config --global fetch.prune true` to auto-prune on every fetch across all remotes (or `remote..prune` to scope it to one remote) -- **Shallow clones**: `--depth=` to deepen or create a shallow clone, `--unshallow` to convert to full history, `--update-shallow` to allow the shallow boundary to move. Details and the default fetch refspec: `references/remotes.md`. - -Fetch never modifies your local branches — it only updates remote-tracking branches (`refs/remotes/origin/*`). - -### Push Operations - -Use these to send your commits upstream. Default: safe push to same-named branch on the remote. - -- **Basic push**: `git push ` — pushes to same-named remote branch -- **Set upstream**: `git push -u ` — push and configure this branch to track the remote -- **Multi-remote push**: `git push origin develop` and `git push staging develop` sequentially, or use `git remote set-url --add ` to push to multiple remotes with one command -- **Force-push safety**: Always use `git push --force-with-lease --force-if-includes ` over bare `--force`. Require explicit confirmation first — and never for `main`/`master` (see Gotchas). `--force-if-includes` is a no-op without `--force-with-lease`. If background tools (IDE, cron) auto-fetch and could poison the lease check, use a dedicated push-only remote instead — see `references/remotes.md`. -- **Server-side enforcement**: `receive.denyDeletes`, `receive.denyDeleteCurrent`, and `receive.denyNonFastForwards` are enforced on the remote regardless of local flags — a hardened server rejects the push even with `--force`. -- **Delete remote branch**: `git push --delete ` (not `:` syntax; clearer and cleaner) -- **Push everything**: `git push --all` (all local branches) or `git push --tags` (all tags) -- **Push a single tag**: `git push origin ` -- **Delete remote branches with no local counterpart**: `git push --prune origin 'refs/heads/*:refs/heads/*'` -- **Force only part of a multi-ref push**: prefix the one refspec that needs it with `+`, e.g. `git push origin +main develop` forces `main` while safe-pushing `develop` - -Refspec syntax is `[+][:]`: - -| Pattern | Meaning | +| Operation | Read | |---|---| -| `` | Push to same-named remote branch | -| `:` | Push `` local ref to `` remote ref | -| `+:` | Force this refspec (non-fast-forward allowed) | -| `:` | Delete remote `` | -| `refs/heads/*:refs/heads/*` | Glob: push all matching branches | -| `^refs/heads/dev-*` | Negative: exclude matching refs | -| `tag ` | Sugar for `refs/tags/:refs/tags/` | +| Add, remove, rename, inspect, or re-point a remote; tracking, mirror, and `set-url` config | `references/remote-config.md` | +| Fetch or prune remote-tracking refs; shallow or partial fetch | `references/fetch.md` | +| Push branches or tags; refspecs; force-push | `references/push.md` | +| Pull — integrate remote changes into the current branch | `references/pull.md` | -### Pull Operations +## Step 3 — Return format -Use these to fetch and integrate remote changes. Default strategy: `--ff-only` (fail if diverged, forcing a conscious choice). +For agent callers, return: -- **Pull with fast-forward only**: `git pull --ff-only` (recommended default — fails if you've diverged, forcing a rebase/merge decision) -- **Pull with rebase**: `git pull --rebase` (replays your unpublished commits on top; linear history, but rewrites SHAs — only safe for unpublished work) -- **Pull with merge**: `git pull --no-rebase` (three-way merge commit; preserves original commits, non-linear) -- **Pull with rebase, preserving merges**: `git pull --rebase=merges` (like `--rebase`, but keeps intentional local merge commits during replay) -- **Pull without integrating**: `git pull --squash` collapses incoming commits into staged changes without committing — you write the commit message -- **Set pull strategy globally**: `git config pull.ff only` (or `pull.rebase true`; respects branch-specific overrides via `branch..rebase`). Full precedence order (CLI flag > `pull.rebase` > `branch..rebase` > `branch.autoSetupRebase`): `references/remotes.md`. -- **Check before rebasing**: Always verify your commits haven't been pushed before using `--rebase`. Rebasing published commits breaks everyone downstream. -- **Merge strategy default**: Git 2.34+ defaults to the `ort` merge strategy (`recursive` is now just an alias for it). Strategy options like `-X ours`, `-X theirs`, `-X ignore-space-change` still pass through unchanged. -- **Submodules on pull**: `--recurse-submodules` only fetches submodules that are already checked out — newly added submodules are not initialized automatically. Use the `git-submodules` skill to initialize new ones. - -If pull diverges and you haven't set a strategy, the operation fails — this is good, forces a conscious choice. Never auto-merge diverged branches without asking. - -### Return Format (for agents) - -Return structured output: ```json { "success": true, @@ -116,8 +52,8 @@ Return structured output: "branch": "main", "output": "...", "warnings": ["force-with-lease not confirmed"], - "recommendations": ["set pull.ff=only globally"] + "recommendations": ["set `pull.ff only` so the default does not vary by Git version"] } ``` -On failure, include `error` field with root cause and recovery suggestion. +On failure, set `success: false` and add an `error` field holding the root cause and a recovery suggestion. diff --git a/plugins/git/skills/git-remotes/references/README.md b/plugins/git/skills/git-remotes/references/README.md index ea38459..a9ca455 100644 --- a/plugins/git/skills/git-remotes/references/README.md +++ b/plugins/git/skills/git-remotes/references/README.md @@ -14,4 +14,7 @@ This directory contains provenance metadata and research sources for the `git-re ## Files - `sources.md` — Extracted research sources and their contributing documents -- `remotes.md` — Full `set-url` variants, shallow-clone/fetch options, default fetch refspec, force-push mitigation detail, server-side deny policies, and pull config precedence +- `remote-config.md` — Remote add/remove/rename/inspect, tracking and mirror options, housekeeping, and the full `set-url` form +- `fetch.md` — Fetch and prune options, shallow and partial fetch, the default fetch refspec +- `push.md` — Push options, refspec syntax, force-push safety in full, server-side deny policies +- `pull.md` — Pull strategies, submodule caveat, the divergence rule, and pull config precedence diff --git a/plugins/git/skills/git-remotes/references/fetch.md b/plugins/git/skills/git-remotes/references/fetch.md new file mode 100644 index 0000000..e0c05ff --- /dev/null +++ b/plugins/git/skills/git-remotes/references/fetch.md @@ -0,0 +1,29 @@ +--- +topic: fetch +source_keys: + - git-scm-fetch-docs + - context7-git-htmldocs +--- + +# Fetching + +Fetch updates remote-tracking branches (`refs/remotes//*`) and never modifies a local branch, so it is always safe to run. + +- **One remote**: `git fetch ` — all branches +- **One branch**: `git fetch ` — the result lands in `FETCH_HEAD`, not a tracking ref +- **All remotes**: `git fetch --all` +- **Prune properly**: `git fetch --all --prune --prune-tags` cleans stale branches *and* tags +- **Auto-prune**: `git config --global fetch.prune true` (or `remote..prune` to scope it to one remote), and `fetch.pruneTags true` for tags + +## Shallow and partial fetch + +```bash +git fetch --depth= # deepen history, or create a shallow clone +git fetch --unshallow # convert a shallow clone to full history +git fetch --update-shallow # allow the fetch to update the shallow boundary +git fetch --refmap='' # fetch without updating any tracking ref (FETCH_HEAD only) +``` + +## Default fetch refspec + +The default is `+refs/heads/*:refs/remotes//*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and offer no protection for local history. diff --git a/plugins/git/skills/git-remotes/references/pull.md b/plugins/git/skills/git-remotes/references/pull.md new file mode 100644 index 0000000..3a8a25a --- /dev/null +++ b/plugins/git/skills/git-remotes/references/pull.md @@ -0,0 +1,37 @@ +--- +topic: pull +source_keys: + - git-scm-pull-docs + - context7-git-htmldocs +--- + +# Pulling + +Default strategy: `--ff-only`. It fails on divergence, which forces a conscious choice instead of an accidental merge commit. + +- **Fast-forward only**: `git pull --ff-only` — the recommended default +- **Rebase**: `git pull --rebase` replays your commits on top for linear history, but rewrites SHAs. Verify nothing being replayed has been pushed: rebasing published commits breaks everyone downstream. +- **Merge**: `git pull --no-rebase` — three-way merge commit, preserves original commits, non-linear +- **Rebase preserving merges**: `git pull --rebase=merges` keeps intentional local merge commits during the replay +- **Stage without committing**: `git pull --squash` collapses incoming commits into staged changes; you write the message +- **Merge strategy**: Git 2.34+ defaults to `ort` (`recursive` is now an alias for it). Strategy options such as `-X ours`, `-X theirs`, `-X ignore-space-change` pass through unchanged. +- **Submodules**: `--recurse-submodules` only fetches submodules already checked out. Newly added ones are not initialized — use the `git-submodules` skill for those. + +## On divergence + +A pull that diverges with no strategy configured fails, and that failure is the useful outcome. Report the divergence and the three ways out — `--ff-only`, `--rebase`, `--no-rebase` — and let the caller choose. Auto-merging a diverged branch buries a decision that belongs to the human. + +## Config precedence + +Highest wins: + +1. Command-line flag (`--ff-only` / `--rebase` / `--no-rebase`) +2. `pull.rebase` config (global or local) +3. `branch..rebase` (branch-specific override) +4. `branch.autoSetupRebase` (set automatically when the tracking branch was created) + +```bash +git config pull.ff only # deterministic default across Git versions +git config --global pull.rebase true +git config branch.develop.rebase false # develop always merges, regardless of the global default +``` diff --git a/plugins/git/skills/git-remotes/references/push.md b/plugins/git/skills/git-remotes/references/push.md new file mode 100644 index 0000000..dd032fb --- /dev/null +++ b/plugins/git/skills/git-remotes/references/push.md @@ -0,0 +1,67 @@ +--- +topic: push +source_keys: + - git-scm-push-docs + - context7-git-htmldocs +--- + +# Pushing + +Default: safe push to the same-named branch on the remote. + +- **Force-push**: never bare `--force`. Use `git push --force-with-lease --force-if-includes `, after the SKILL.md Step 1 gate. +- **Basic**: `git push ` +- **Set upstream**: `git push -u ` — push and configure tracking +- **Multi-remote**: push sequentially (`git push origin develop`, `git push staging develop`), or add a second push URL with `git remote set-url --add ` to reach both in one command +- **Delete a remote branch**: `git push --delete ` — clearer than the `:` form +- **Bulk**: `git push --all` (all local branches), `git push --tags` (all tags), `git push origin ` (one tag) +- **Delete remote branches with no local counterpart**: `git push --prune origin 'refs/heads/*:refs/heads/*'` +- **Force only part of a multi-ref push**: prefix the one refspec that needs it with `+` — `git push origin +release develop` forces `release` while safe-pushing `develop`. A `+` prefix is a force-push and passes the SKILL.md Step 1 gate like any other. + +## Refspec syntax — `[+][:]` + +| Pattern | Meaning | +|---|---| +| `` | Push to same-named remote branch | +| `:` | Push `` local ref to `` remote ref | +| `+:` | Force this refspec (non-fast-forward allowed) — a force-push; passes the SKILL.md Step 1 gate | +| `:` | Delete remote `` | +| `refs/heads/*:refs/heads/*` | Glob: push all matching branches | +| `^refs/heads/dev-*` | Negative: exclude matching refs | +| `tag ` | Sugar for `refs/tags/:refs/tags/` | + +## Force-push safety — full detail + +`--force-with-lease` rejects the push if the remote ref moved since your last fetch. Three forms: + +| Form | What it protects | +|---|---| +| `--force-with-lease` (bare) | All refs being pushed, checked against your remote-tracking branch | +| `--force-with-lease=` | Named ref only | +| `--force-with-lease=:` | Named ref must be at exact SHA — most stable | + +**Caveat with the bare form:** any background process that runs `git fetch` (IDE plugin, cron job, editor auto-fetch) updates your remote-tracking branch, which can make the lease check pass even though someone else pushed in between. The protection is silently defeated. + +Two mitigations: + +```bash +# Option 1 — dedicated push-only remote: background tools fetch `origin`, you push +# through a separate remote that nothing else touches, so its tracking ref can't be +# poisoned by an unrelated fetch. +git remote add origin-push $(git config remote.origin.url) +git push --force-with-lease origin-push + +# Option 2 — explicit SHA via a local tag, unaffected by tracking-branch state +git fetch +git tag base master +git rebase -i master +git push --force-with-lease=master:base master:master +``` + +`--force-if-includes` adds a second check on top of bare `--force-with-lease`: it verifies the remote-tracking tip actually appears in your local branch's reflog, i.e. you genuinely integrated it before rewriting. It is a no-op without `--force-with-lease`, and has no effect with the `--force-with-lease=:` form, which already pins an exact SHA. + +Safest combination: `git push --force-with-lease --force-if-includes origin`. + +## Server-side policy + +`receive.denyDeletes`, `receive.denyDeleteCurrent` and `receive.denyNonFastForwards` are enforced on the remote regardless of any local flag — a hardened server rejects the push even with `--force`. diff --git a/plugins/git/skills/git-remotes/references/remote-config.md b/plugins/git/skills/git-remotes/references/remote-config.md new file mode 100644 index 0000000..5d44eef --- /dev/null +++ b/plugins/git/skills/git-remotes/references/remote-config.md @@ -0,0 +1,39 @@ +--- +topic: remote-config +source_keys: + - git-scm-remote-docs + - context7-git-htmldocs +--- + +# Remote configuration + +Which remotes exist, where they point, and what they track. + +`git remote show ` needs network access — use `-n` for cached data offline, or `git remote -v`, which lists URLs without querying. + +## Add, remove, rename, inspect + +- **Add**: `git remote add `, or `-f` to fetch immediately +- **Remove**: `git remote remove ` — deletes the remote, all its tracking refs, and its config +- **Rename**: `git remote rename ` +- **Inspect**: `git remote -v` (URLs, offline) or `git remote show ` (live tracking status) +- **Effective URLs**: `git remote get-url ` shows the URL after `insteadOf` rewrites; `git remote get-url --push --all ` lists every push URL + +## Tracking, mirroring, housekeeping + +- **Track one branch**: `git remote add -t ` (repeatable); `--no-tags` suppresses tag import entirely +- **Mirror**: `--mirror=fetch` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror` +- **Prune stale tracking refs without fetching**: `git remote prune `, with `--dry-run` to preview +- **Default branch pointer**: `git remote set-head -a` (auto-detect, needs a prior fetch), `... ` (explicit), `... -d` (delete `refs/remotes//HEAD`) + +## `set-url` — full form + +```bash +git remote set-url # replace the first fetch URL +git remote set-url # replace only the URL matching regex +git remote set-url --push # change push URL only (must point at same repo) +git remote set-url --add # add an extra push URL (push to multiple remotes) +git remote set-url --delete # remove URLs matching regex +``` + +`--push` changes only where pushes go — fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead; `--push` cannot do this. diff --git a/plugins/git/skills/git-remotes/references/remotes.md b/plugins/git/skills/git-remotes/references/remotes.md deleted file mode 100644 index ef765d9..0000000 --- a/plugins/git/skills/git-remotes/references/remotes.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -topic: remotes -source_keys: - - git-scm-remote-docs - - git-scm-fetch-docs - - git-scm-push-docs - - git-scm-pull-docs ---- - -## `set-url` — full form - -```bash -git remote set-url # replace the first fetch URL -git remote set-url # replace only the URL matching regex -git remote set-url --push # change push URL only (must point at same repo) -git remote set-url --add # add an extra push URL (push to multiple remotes) -git remote set-url --delete # remove URLs matching regex -``` - -`--push` changes only where pushes go — fetch and push URLs must still reference the same repository. For genuine fetch-from-A / push-to-B workflows, use two separate named remotes instead. - -## Shallow clones and partial fetch - -```bash -git fetch # fetch one branch only, stored in FETCH_HEAD (not a local/tracking ref) -git fetch --depth= # deepen history, or create a shallow clone -git fetch --unshallow # convert a shallow clone to full history -git fetch --update-shallow # allow the fetch to update the shallow boundary -git fetch --refmap='' # fetch without updating any tracking ref (FETCH_HEAD only) -``` - -## Default fetch refspec - -The default fetch refspec is `+refs/heads/*:refs/remotes//*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and provide no protection for local history. Fetch never touches your local branches, only remote-tracking refs. - -## Force-push safety — full detail - -`--force-with-lease` rejects the push if the remote ref moved since your last fetch. Three forms: - -| Form | What it protects | -|---|---| -| `--force-with-lease` (bare) | All refs being pushed, checked against your remote-tracking branch | -| `--force-with-lease=` | Named ref only | -| `--force-with-lease=:` | Named ref must be at exact SHA — most stable | - -**Caveat with the bare form:** any background process that runs `git fetch` (IDE plugin, cron job, editor auto-fetch) updates your remote-tracking branch, which can make the lease check pass even though someone else pushed in between. The protection is silently defeated. - -Two mitigations: - -```bash -# Option 1 — dedicated push-only remote: background tools fetch `origin`, you push -# through a separate remote that nothing else touches, so its tracking ref can't be -# poisoned by an unrelated fetch. -git remote add origin-push $(git config remote.origin.url) -git push --force-with-lease origin-push - -# Option 2 — explicit SHA via a local tag, unaffected by tracking-branch state -git fetch -git tag base master -git rebase -i master -git push --force-with-lease=master:base master:master -``` - -`--force-if-includes` adds a second check on top of bare `--force-with-lease`: it verifies the remote-tracking tip actually appears in your local branch's reflog, i.e. you genuinely integrated it before rewriting. It is a no-op without `--force-with-lease`, and has no effect when the `--force-with-lease=:` form is used (that form already pins an exact SHA). - -Safest combination: `git push --force-with-lease --force-if-includes origin`. - -Remote-side policies (`receive.denyDeletes`, `receive.denyDeleteCurrent`, `receive.denyNonFastForwards`) are enforced server-side regardless of any local flag — a server configured this way rejects the push even with `--force`. - -## Pull config precedence - -Highest wins: - -1. Command-line flag (`--ff-only` / `--rebase` / `--no-rebase`) -2. `pull.rebase` config (global or local) -3. `branch..rebase` (branch-specific override) -4. `branch.autoSetupRebase` (set automatically when the tracking branch was created) - -```bash -git config --global pull.rebase true -git config branch.develop.rebase false # develop always merges, regardless of the global default -``` diff --git a/plugins/git/skills/git-remotes/references/sources.md b/plugins/git/skills/git-remotes/references/sources.md index 694c756..f2ad70e 100644 --- a/plugins/git/skills/git-remotes/references/sources.md +++ b/plugins/git/skills/git-remotes/references/sources.md @@ -12,8 +12,7 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Remote Management (`git remote`)` **Contributing files:** -- SKILL.md (Remote Management section) -- references/remotes.md (`set-url` full form) +- references/remote-config.md --- @@ -26,8 +25,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Fetching (`git fetch`)` **Contributing files:** -- SKILL.md (Fetch Operations section, Gotchas) -- references/remotes.md (shallow clones, default fetch refspec) +- SKILL.md (Gotchas — prune does not touch tags) +- references/fetch.md --- @@ -40,8 +39,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Pushing (`git push`)` **Contributing files:** -- SKILL.md (Push Operations section, Gotchas) -- references/remotes.md (force-push safety full detail, server-side deny policies) +- SKILL.md (Gotchas — `--force-with-lease` caveat; Step 1 force-push gate) +- references/push.md --- @@ -54,8 +53,8 @@ - **Research doc:** plugins/git/docs/research/docs/git/remotes.md → `## Pulling (`git pull`)` **Contributing files:** -- SKILL.md (Pull Operations section, Gotchas) -- references/remotes.md (pull config precedence) +- SKILL.md (Gotchas — pull default drift) +- references/pull.md (divergence rule; strategies; config precedence) --- @@ -69,3 +68,7 @@ **Contributing files:** - SKILL.md (all sections) +- references/remote-config.md +- references/fetch.md +- references/push.md +- references/pull.md