refactor(skills): retrofit the corpus to the ADR-0020 context contract #129
@@ -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.
|
||||
|
||||
@@ -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=<ref>:<sha>`.
|
||||
- **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=<ref>:<sha>` 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 <name> <url>` or `git remote add -f <name> <url>` to fetch immediately
|
||||
- **Remove a remote**: `git remote remove <name>` (deletes remote + all tracking refs + config)
|
||||
- **Rename a remote**: `git remote rename <old> <new>`
|
||||
- **Inspect remotes**: `git remote -v` (show URLs) or `git remote show <name>` (live tracking status, requires network)
|
||||
- **Set-url separately for fetch vs. push**: `git remote set-url --push <name> <url>` 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 <name> <regex>`
|
||||
- **Inspect effective URLs**: `git remote get-url <name>` (shows URL after `insteadOf` rewrites) or `git remote get-url --push --all <name>` (all push URLs)
|
||||
- **Track only one branch**: `git remote add -t <branch> <name> <url>` (repeatable), or suppress tag import entirely with `git remote add --no-tags <name> <url>`
|
||||
- **Mirror a remote**: `git remote add --mirror=fetch <name> <url>` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror`
|
||||
- **Prune stale tracking refs without fetching**: `git remote prune <name>` (add `--dry-run` to preview first)
|
||||
- **Set the remote's default branch pointer**: `git remote set-head <name> -a` (auto-detect, requires a prior fetch), `git remote set-head <name> <branch>` (explicit), or `git remote set-head <name> -d` (delete `refs/remotes/<name>/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 <remote>` — fetches all branches
|
||||
- **Fetch one branch only**: `git fetch <remote> <branch>` — 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.<name>.prune` to scope it to one remote)
|
||||
- **Shallow clones**: `--depth=<n>` 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 <remote> <branch>` — pushes to same-named remote branch
|
||||
- **Set upstream**: `git push -u <remote> <branch>` — 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 <name> <url>` to push to multiple remotes with one command
|
||||
- **Force-push safety**: Always use `git push --force-with-lease --force-if-includes <remote> <branch>` 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 <remote> --delete <branch>` (not `:<branch>` 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 <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 `+`, e.g. `git push origin +main develop` forces `main` while safe-pushing `develop`
|
||||
|
||||
Refspec syntax is `[+]<src>[:<dst>]`:
|
||||
|
||||
| Pattern | Meaning |
|
||||
| Operation | Read |
|
||||
|---|---|
|
||||
| `<branch>` | Push to same-named remote branch |
|
||||
| `<src>:<dst>` | Push `<src>` local ref to `<dst>` remote ref |
|
||||
| `+<src>:<dst>` | Force this refspec (non-fast-forward allowed) |
|
||||
| `:<branch>` | Delete remote `<branch>` |
|
||||
| `refs/heads/*:refs/heads/*` | Glob: push all matching branches |
|
||||
| `^refs/heads/dev-*` | Negative: exclude matching refs |
|
||||
| `tag <name>` | Sugar for `refs/tags/<name>:refs/tags/<name>` |
|
||||
| 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.<name>.rebase`). Full precedence order (CLI flag > `pull.rebase` > `branch.<name>.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.
|
||||
|
||||
@@ -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
|
||||
|
||||
29
plugins/git/.apm/skills/git-remotes/references/fetch.md
Normal file
29
plugins/git/.apm/skills/git-remotes/references/fetch.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
topic: fetch
|
||||
source_keys:
|
||||
- git-scm-fetch-docs
|
||||
- context7-git-htmldocs
|
||||
---
|
||||
|
||||
# Fetching
|
||||
|
||||
Fetch updates remote-tracking branches (`refs/remotes/<name>/*`) and never modifies a local branch, so it is always safe to run.
|
||||
|
||||
- **One remote**: `git fetch <remote>` — all branches
|
||||
- **One branch**: `git fetch <remote> <branch>` — 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.<name>.prune` to scope it to one remote), and `fetch.pruneTags true` for tags
|
||||
|
||||
## Shallow and partial fetch
|
||||
|
||||
```bash
|
||||
git fetch --depth=<n> # 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='' <remote> <branch> # fetch without updating any tracking ref (FETCH_HEAD only)
|
||||
```
|
||||
|
||||
## Default fetch refspec
|
||||
|
||||
The default is `+refs/heads/*:refs/remotes/<name>/*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and offer no protection for local history.
|
||||
37
plugins/git/.apm/skills/git-remotes/references/pull.md
Normal file
37
plugins/git/.apm/skills/git-remotes/references/pull.md
Normal file
@@ -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.<name>.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
|
||||
```
|
||||
67
plugins/git/.apm/skills/git-remotes/references/push.md
Normal file
67
plugins/git/.apm/skills/git-remotes/references/push.md
Normal file
@@ -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 <remote> <branch>`, after the SKILL.md Step 1 gate.
|
||||
- **Basic**: `git push <remote> <branch>`
|
||||
- **Set upstream**: `git push -u <remote> <branch>` — 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 <name> <url>` to reach both in one command
|
||||
- **Delete a remote branch**: `git push <remote> --delete <branch>` — clearer than the `:<branch>` form
|
||||
- **Bulk**: `git push --all` (all local branches), `git push --tags` (all tags), `git push origin <tag>` (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 — `[+]<src>[:<dst>]`
|
||||
|
||||
| Pattern | Meaning |
|
||||
|---|---|
|
||||
| `<branch>` | Push to same-named remote branch |
|
||||
| `<src>:<dst>` | Push `<src>` local ref to `<dst>` remote ref |
|
||||
| `+<src>:<dst>` | Force this refspec (non-fast-forward allowed) — a force-push; passes the SKILL.md Step 1 gate |
|
||||
| `:<branch>` | Delete remote `<branch>` |
|
||||
| `refs/heads/*:refs/heads/*` | Glob: push all matching branches |
|
||||
| `^refs/heads/dev-*` | Negative: exclude matching refs |
|
||||
| `tag <name>` | Sugar for `refs/tags/<name>:refs/tags/<name>` |
|
||||
|
||||
## 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=<refname>` | Named ref only |
|
||||
| `--force-with-lease=<refname>:<sha>` | 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=<ref>:<sha>` 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`.
|
||||
@@ -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 <name>` 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 <name> <url>`, or `-f` to fetch immediately
|
||||
- **Remove**: `git remote remove <name>` — deletes the remote, all its tracking refs, and its config
|
||||
- **Rename**: `git remote rename <old> <new>`
|
||||
- **Inspect**: `git remote -v` (URLs, offline) or `git remote show <name>` (live tracking status)
|
||||
- **Effective URLs**: `git remote get-url <name>` shows the URL after `insteadOf` rewrites; `git remote get-url --push --all <name>` lists every push URL
|
||||
|
||||
## Tracking, mirroring, housekeeping
|
||||
|
||||
- **Track one branch**: `git remote add -t <branch> <name> <url>` (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 <name>`, with `--dry-run` to preview
|
||||
- **Default branch pointer**: `git remote set-head <name> -a` (auto-detect, needs a prior fetch), `... <branch>` (explicit), `... -d` (delete `refs/remotes/<name>/HEAD`)
|
||||
|
||||
## `set-url` — full form
|
||||
|
||||
```bash
|
||||
git remote set-url <name> <newurl> # replace the first fetch URL
|
||||
git remote set-url <name> <newurl> <oldurl-regex> # replace only the URL matching regex
|
||||
git remote set-url --push <name> <url> # change push URL only (must point at same repo)
|
||||
git remote set-url --add <name> <url> # add an extra push URL (push to multiple remotes)
|
||||
git remote set-url --delete <name> <regex> # 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.
|
||||
@@ -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 <name> <newurl> # replace the first fetch URL
|
||||
git remote set-url <name> <newurl> <oldurl-regex> # replace only the URL matching regex
|
||||
git remote set-url --push <name> <url> # change push URL only (must point at same repo)
|
||||
git remote set-url --add <name> <url> # add an extra push URL (push to multiple remotes)
|
||||
git remote set-url --delete <name> <regex> # 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 <remote> <branch> # fetch one branch only, stored in FETCH_HEAD (not a local/tracking ref)
|
||||
git fetch --depth=<n> # 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='' <remote> <branch> # fetch without updating any tracking ref (FETCH_HEAD only)
|
||||
```
|
||||
|
||||
## Default fetch refspec
|
||||
|
||||
The default fetch refspec is `+refs/heads/*:refs/remotes/<name>/*`. 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=<refname>` | Named ref only |
|
||||
| `--force-with-lease=<refname>:<sha>` | 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=<ref>:<sha>` 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.<name>.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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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=<ref>:<sha>`.
|
||||
- **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=<ref>:<sha>` 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 <name> <url>` or `git remote add -f <name> <url>` to fetch immediately
|
||||
- **Remove a remote**: `git remote remove <name>` (deletes remote + all tracking refs + config)
|
||||
- **Rename a remote**: `git remote rename <old> <new>`
|
||||
- **Inspect remotes**: `git remote -v` (show URLs) or `git remote show <name>` (live tracking status, requires network)
|
||||
- **Set-url separately for fetch vs. push**: `git remote set-url --push <name> <url>` 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 <name> <regex>`
|
||||
- **Inspect effective URLs**: `git remote get-url <name>` (shows URL after `insteadOf` rewrites) or `git remote get-url --push --all <name>` (all push URLs)
|
||||
- **Track only one branch**: `git remote add -t <branch> <name> <url>` (repeatable), or suppress tag import entirely with `git remote add --no-tags <name> <url>`
|
||||
- **Mirror a remote**: `git remote add --mirror=fetch <name> <url>` mirrors all refs locally (bare repos only); `--mirror=push` makes every push behave like `--mirror`
|
||||
- **Prune stale tracking refs without fetching**: `git remote prune <name>` (add `--dry-run` to preview first)
|
||||
- **Set the remote's default branch pointer**: `git remote set-head <name> -a` (auto-detect, requires a prior fetch), `git remote set-head <name> <branch>` (explicit), or `git remote set-head <name> -d` (delete `refs/remotes/<name>/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 <remote>` — fetches all branches
|
||||
- **Fetch one branch only**: `git fetch <remote> <branch>` — 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.<name>.prune` to scope it to one remote)
|
||||
- **Shallow clones**: `--depth=<n>` 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 <remote> <branch>` — pushes to same-named remote branch
|
||||
- **Set upstream**: `git push -u <remote> <branch>` — 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 <name> <url>` to push to multiple remotes with one command
|
||||
- **Force-push safety**: Always use `git push --force-with-lease --force-if-includes <remote> <branch>` 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 <remote> --delete <branch>` (not `:<branch>` 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 <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 `+`, e.g. `git push origin +main develop` forces `main` while safe-pushing `develop`
|
||||
|
||||
Refspec syntax is `[+]<src>[:<dst>]`:
|
||||
|
||||
| Pattern | Meaning |
|
||||
| Operation | Read |
|
||||
|---|---|
|
||||
| `<branch>` | Push to same-named remote branch |
|
||||
| `<src>:<dst>` | Push `<src>` local ref to `<dst>` remote ref |
|
||||
| `+<src>:<dst>` | Force this refspec (non-fast-forward allowed) |
|
||||
| `:<branch>` | Delete remote `<branch>` |
|
||||
| `refs/heads/*:refs/heads/*` | Glob: push all matching branches |
|
||||
| `^refs/heads/dev-*` | Negative: exclude matching refs |
|
||||
| `tag <name>` | Sugar for `refs/tags/<name>:refs/tags/<name>` |
|
||||
| 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.<name>.rebase`). Full precedence order (CLI flag > `pull.rebase` > `branch.<name>.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.
|
||||
|
||||
@@ -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
|
||||
|
||||
29
plugins/git/skills/git-remotes/references/fetch.md
Normal file
29
plugins/git/skills/git-remotes/references/fetch.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
topic: fetch
|
||||
source_keys:
|
||||
- git-scm-fetch-docs
|
||||
- context7-git-htmldocs
|
||||
---
|
||||
|
||||
# Fetching
|
||||
|
||||
Fetch updates remote-tracking branches (`refs/remotes/<name>/*`) and never modifies a local branch, so it is always safe to run.
|
||||
|
||||
- **One remote**: `git fetch <remote>` — all branches
|
||||
- **One branch**: `git fetch <remote> <branch>` — 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.<name>.prune` to scope it to one remote), and `fetch.pruneTags true` for tags
|
||||
|
||||
## Shallow and partial fetch
|
||||
|
||||
```bash
|
||||
git fetch --depth=<n> # 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='' <remote> <branch> # fetch without updating any tracking ref (FETCH_HEAD only)
|
||||
```
|
||||
|
||||
## Default fetch refspec
|
||||
|
||||
The default is `+refs/heads/*:refs/remotes/<name>/*`. The leading `+` forces the update — remote-tracking branches always mirror the remote exactly and offer no protection for local history.
|
||||
37
plugins/git/skills/git-remotes/references/pull.md
Normal file
37
plugins/git/skills/git-remotes/references/pull.md
Normal file
@@ -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.<name>.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
|
||||
```
|
||||
67
plugins/git/skills/git-remotes/references/push.md
Normal file
67
plugins/git/skills/git-remotes/references/push.md
Normal file
@@ -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 <remote> <branch>`, after the SKILL.md Step 1 gate.
|
||||
- **Basic**: `git push <remote> <branch>`
|
||||
- **Set upstream**: `git push -u <remote> <branch>` — 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 <name> <url>` to reach both in one command
|
||||
- **Delete a remote branch**: `git push <remote> --delete <branch>` — clearer than the `:<branch>` form
|
||||
- **Bulk**: `git push --all` (all local branches), `git push --tags` (all tags), `git push origin <tag>` (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 — `[+]<src>[:<dst>]`
|
||||
|
||||
| Pattern | Meaning |
|
||||
|---|---|
|
||||
| `<branch>` | Push to same-named remote branch |
|
||||
| `<src>:<dst>` | Push `<src>` local ref to `<dst>` remote ref |
|
||||
| `+<src>:<dst>` | Force this refspec (non-fast-forward allowed) — a force-push; passes the SKILL.md Step 1 gate |
|
||||
| `:<branch>` | Delete remote `<branch>` |
|
||||
| `refs/heads/*:refs/heads/*` | Glob: push all matching branches |
|
||||
| `^refs/heads/dev-*` | Negative: exclude matching refs |
|
||||
| `tag <name>` | Sugar for `refs/tags/<name>:refs/tags/<name>` |
|
||||
|
||||
## 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=<refname>` | Named ref only |
|
||||
| `--force-with-lease=<refname>:<sha>` | 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=<ref>:<sha>` 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`.
|
||||
39
plugins/git/skills/git-remotes/references/remote-config.md
Normal file
39
plugins/git/skills/git-remotes/references/remote-config.md
Normal file
@@ -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 <name>` 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 <name> <url>`, or `-f` to fetch immediately
|
||||
- **Remove**: `git remote remove <name>` — deletes the remote, all its tracking refs, and its config
|
||||
- **Rename**: `git remote rename <old> <new>`
|
||||
- **Inspect**: `git remote -v` (URLs, offline) or `git remote show <name>` (live tracking status)
|
||||
- **Effective URLs**: `git remote get-url <name>` shows the URL after `insteadOf` rewrites; `git remote get-url --push --all <name>` lists every push URL
|
||||
|
||||
## Tracking, mirroring, housekeeping
|
||||
|
||||
- **Track one branch**: `git remote add -t <branch> <name> <url>` (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 <name>`, with `--dry-run` to preview
|
||||
- **Default branch pointer**: `git remote set-head <name> -a` (auto-detect, needs a prior fetch), `... <branch>` (explicit), `... -d` (delete `refs/remotes/<name>/HEAD`)
|
||||
|
||||
## `set-url` — full form
|
||||
|
||||
```bash
|
||||
git remote set-url <name> <newurl> # replace the first fetch URL
|
||||
git remote set-url <name> <newurl> <oldurl-regex> # replace only the URL matching regex
|
||||
git remote set-url --push <name> <url> # change push URL only (must point at same repo)
|
||||
git remote set-url --add <name> <url> # add an extra push URL (push to multiple remotes)
|
||||
git remote set-url --delete <name> <regex> # 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.
|
||||
@@ -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 <name> <newurl> # replace the first fetch URL
|
||||
git remote set-url <name> <newurl> <oldurl-regex> # replace only the URL matching regex
|
||||
git remote set-url --push <name> <url> # change push URL only (must point at same repo)
|
||||
git remote set-url --add <name> <url> # add an extra push URL (push to multiple remotes)
|
||||
git remote set-url --delete <name> <regex> # 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 <remote> <branch> # fetch one branch only, stored in FETCH_HEAD (not a local/tracking ref)
|
||||
git fetch --depth=<n> # 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='' <remote> <branch> # fetch without updating any tracking ref (FETCH_HEAD only)
|
||||
```
|
||||
|
||||
## Default fetch refspec
|
||||
|
||||
The default fetch refspec is `+refs/heads/*:refs/remotes/<name>/*`. 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=<refname>` | Named ref only |
|
||||
| `--force-with-lease=<refname>:<sha>` | 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=<ref>:<sha>` 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.<name>.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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user