Install
openclaw skills install @ivangdavila/gitCommits, branches, merges, and rebases Git repositories, resolves conflicts, and recovers lost history. Use when the work touches a repo, commit, branch, merge, rebase, stash, tag, or submodule; when Git refuses a command — index.lock exists, push rejected as non-fast-forward, detached HEAD, dubious ownership, unrelated histories, conflict markers left behind; when something looks lost after a hard reset, a bad rebase, a deleted branch, or a dropped stash; when splitting a mixed change, wording commit messages, cleaning history before review, or force-pushing without wrecking a teammate's work; when a credential or a huge file got committed; when setting up worktrees, hooks, LFS, sparse checkout, signing, or separate work and personal identities. Not for CI pipeline YAML (github-actions, gitlab) or for composing the pull request description itself (pull-request).
openclaw skills install @ivangdavila/gitUser preferences and memory live in ~/Clawic/data/git/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/git/ or ~/clawic/git/), move it to ~/Clawic/data/git/.
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/git/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| integration_style | rebase | merge | squash | repo-default | repo-default | How branches get combined and what pull does; repo-default reads the last 20 merges (git log --merges --oneline -20) and follows what the repo already does |
| commit_style | conventional | plain | repo-default | repo-default | Shape of every generated commit subject (Commit Discipline); repo-default copies the style found in git log --oneline -20 |
| branch_naming | text (pattern) | type/topic | Template used when creating branches, lowercase and hierarchical by default |
| protected_branches | list | main, master | Branches never committed to, rewritten, or force-pushed (Core Rule 2, Push gate); a request that targets one becomes "branch first, then propose the merge" |
| force_push_policy | never | own-branches | any | own-branches | Governs the Push gate: never turns every rewrite into a git revert; any still requires --force-with-lease |
| subject_max | number (chars, 50-100) | 72 | Hard stop for every generated commit subject (Core Rule 6) and the length any commit-msg gate enforces |
| message_language | text (language) | repo-default | repo-default | Language of generated commit subjects, bodies, and tag messages; repo-default copies the language already in git log --oneline -20 |
| remote_host | github | gitlab | bitbucket | gitea | other | github | Selects PR-vs-MR wording, blob size limits, and squash-merge semantics quoted in review and release guidance |
| signing | off | ssh | gpg | off | Adds signing flags and commit.gpgsign setup to commit and tag flows |
Preference areas to record as the user reveals them:
Co-authored-by, monorepo tag prefixes--above=), reflog retention (gc.reflogExpireUnreachable), clone depth and filter in CIfilter-repo); compliance regimes demanding signed or reviewed commits--hard/clean -f/history rewritesrecovery.md).index.lock, non-fast-forward, dubious ownership, detached HEAD) → errors.md.forensics.md.github-actions, GitLab pipelines are gitlab; writing the PR description itself is pull-request.| Situation | File |
|---|---|
| Staging surgery, splitting a mixed change, wording a message, stashing | commits.md |
| Creating or switching branches, merge vs rebase, stacked branches | branching.md |
| A merge or rebase stopped on conflicts | conflicts.md |
| Undoing: reset, revert, amend, rewriting a branch before review | history.md |
| Work looks lost: bad reset, dropped stash, deleted branch, detached commits | recovery.md |
| Git printed an error and refuses to proceed | errors.md |
| "When did this break", "who wrote this line", "where did this code go" | forensics.md |
| Push/pull friction, review flow, force-pushing on a shared branch | collaboration.md |
| Forks, upstreams, mirrors, moving or splitting a repository | remotes.md |
| Two checkouts at once: hotfix mid-refactor, parallel agent tasks | worktrees.md |
| Nested repositories: submodules, subtrees, vendored code | submodules.md |
| Slow clone or status, monorepo scale, huge files, LFS | large-repos.md |
| Pre-commit/pre-push automation, lint gates, a hook that never runs | hooks.md |
| A credential or a giant blob reached the history | secrets.md |
| Wrong author identity, auth failures, ignore rules, CRLF churn | config.md |
| Tags, version bumps, backports, release branches, changelogs | releases.md |
| A flag or one-time config that removes a whole trap class | commands.md |
| Git driven from a script, a CI job, or a non-interactive agent | scripting.md |
| User states a workflow preference | setup.md + memory-template.md |
| Anything else | Stay here — Core Rules, Revision Syntax, and the Recovery Playbook cover the default path |
git log @{u}.. lists exactly the commits safe to amend/rebase/squash. A commit missing from that output is published — fix forward with git revert instead.--force-with-lease, own branch only, never a branch in protected_branches. The lease is void if anything fetched after the remote moved — IDE auto-fetch does this without warning — so add --force-if-includes (git >=2.30) to close that hole.git status before reset --hard, git clean -n before git clean -f, git diff @{u} before force push. If you cannot list what dies, you are not ready to run it. Cheap insurance when you are unsure: git branch backup/$(date +%F-%H%M) costs one ref and zero bytes.git reflog before panic. Committed work survives 90 days (reachable) / 30 days (unreachable) — those are reflog-entry lifetimes, and a commit some reflog entry still names does not count as unreachable, so gc's shorter two-week prune window never applies to it (→ recovery.md). The only truly unrecoverable losses: never-staged edits killed by reset --hard/restore, and untracked files killed by clean -f.rerere so each resolution is recorded once.git add -p to split. Subject: aim ≤50 chars (convention), hard stop subject_max, default 72 because git log indents bodies by four spaces inside an 80-column terminal — hosts truncate around the same width in list views. Body explains why, not what; subject and body both follow message_language.git restore (one file, nothing else moves) → git revert (one commit, safe on shared branches) → git reset (moves your branch pointer only) → git rebase (new SHAs, everyone tracking the branch pays) → git filter-repo (every clone in existence is now wrong). Reaching for the right-hand tools when a left-hand one suffices is the single most common self-inflicted Git incident.Most "Git did something I did not ask for" reports are a misread revision expression. Decoder:
| Expression | Means | Trap |
|---|---|---|
HEAD~2 | Two commits back along first parents | Follows the mainline through merges, skipping the merged branch |
HEAD^2 | The SECOND PARENT of a merge commit | Not "two back": ^ selects a parent, ~ walks generations |
HEAD@{2} | Where HEAD pointed two reflog moves ago | Time, not ancestry — and local to this clone only |
@{u} / @{push} | The tracked upstream / where a push would land | They differ in triangular setups (fork: pull from upstream, push to origin) |
A..B | Commits reachable from B but not A | In git diff, the same expression means the plain A-to-B diff |
A...B | log: commits on either side but not both. diff: B against the MERGE-BASE | The one expression whose meaning changes between log and diff — the source of "my diff shows unrelated files" |
:/fix login | Most recent commit whose message contains that text | Searches all reachable history, not just this branch |
:1:file :2:file :3:file | During a conflict: base, ours, theirs | Numbering is fixed; "ours/theirs" inverts under rebase (→ Conflict Basics) |
main@{yesterday} | Branch tip as of yesterday | Reflog-based: empty in a fresh clone, and gone after reflog expiry |
v1.2^{} | The commit an annotated tag points at | Without peeling, a tag is its own object — git diff v1.2 compares against the tag object's target, scripts that assume a commit break |
Ambiguity between a branch and a path is resolved with --: git checkout -- config is always the file.
| Symptom | Play |
|---|---|
| Just finished a bad rebase/merge/reset | git reset --hard ORIG_HEAD — Git saved your pre-operation position |
| Commits vanished after a rewrite | git reflog → git reset --hard HEAD@{n} (the entry just before the damage) |
| Deleted a branch | git reflog → git branch restored <sha> |
| Committed on the wrong branch (unpushed) | git switch right-branch && git cherry-pick <sha>, then git switch - && git reset --hard HEAD~1 |
| Committed on detached HEAD, then switched away | Commit stays in git reflog for 30 days → git branch rescue <sha> |
| Need one file as it was N commits ago | git restore --source HEAD~N -- path — nothing else moves |
reset --hard ate staged-but-uncommitted work | git fsck --lost-found — staged content survives as dangling blobs; never-staged edits are gone |
| Dropped a stash | git fsck --unreachable | grep commit → git stash apply <sha> |
| Bad commit already on a shared branch | git revert <sha> — never rewrite shared history |
| Unsure what happened / anything else | git reflog first; before experimenting, git branch backup — a branch is free insurance |
Deeper chains (deleted file archaeology, corrupted index, post-filter-repo rescue, what expiry actually deletes): recovery.md.
git add -p splits mixed work. git commit --fixup <sha> + git rebase -i --autosquash batches corrections without "fix typo" noise.git log --oneline -20, or follow commit_style and message_language when the user set them. Conventional commits (type(scope): description) only where the log or release tooling already uses them.git pull --rebase --autostash before push: no surprise merge commits, works with a dirty tree.git push -u origin HEAD, or set push.autoSetupRemote (git >=2.37) once and never type -u again.merge.conflictStyle zdiff3 (git >=2.35) once: markers include the base version, so you see what each side changed instead of guessing intent.git grep -nE '^(<{7}|={7}|>{7})' must return nothing, then build/tests, then --continue.conflicts.md.Before declaring Git work done:
git log @{u}.. before I touched itgit status, git clean -n) before any --hard / -fprotected_branches and not a shared branch; if forcing at all, --force-with-lease minimum, and within force_push_policygit config user.email is the right identity for this repo (work vs personal)git diff --cached --stat reviewed — no credential, no build artifact, no blob the host will reject| Trap | Why it fails | Do instead |
|---|---|---|
git stash before a risky operation, expecting untracked files saved | Plain stash skips untracked files | git stash -u |
Assuming stash pop dropped the stash after a conflict | Pop only drops on clean apply; on conflict the entry stays | Resolve, then git stash drop |
git clean -fdx to "clean build artifacts" | -x also deletes ignored files: .env, IDE config, local secrets | git clean -fd, always preview with -n |
Renaming Foo.js → foo.js works locally, breaks CI | macOS/Windows filesystems are case-insensitive, Linux is not | git mv Foo.js tmp && git mv tmp foo.js |
git branch -d refuses to delete after squash-merge | Squashed commits are unreachable from main, so Git thinks the branch is unmerged | Verify the PR merged, then -D |
| Committing a large binary "just this once" | GitHub warns >50 MB, blocks >100 MB — and history keeps the blob forever | Git LFS before first commit; after the fact, git filter-repo (secrets.md) |
"Sharing" hooks via .git/hooks | .git/hooks is never versioned | Commit a hooks dir + git config core.hooksPath (hooks.md) |
| Cloned repo has empty submodule directories | Submodules need explicit initialization | git clone --recurse-submodules, or git submodule update --init |
Adding a path to .gitignore to stop tracking it | Ignore rules never apply to already-tracked files — the file keeps showing up in every diff | git rm --cached <path>, commit the removal, then ignore it |
git reset --hard when the intent was "unstage this" | --hard also throws away the working tree; unstaging needs no destructive flag | git restore --staged <path> (index only) |
Marking a config file --assume-unchanged so local edits stop appearing | Git now lies in status, and any incoming change to that file breaks pull with a confusing error | Commit a .example template, gitignore the real file (config.md) |
More Clawic skills, get them at https://clawic.com/skills/git (install if the user confirms):
pull-request — when the branch is ready and the PR itself has to be written and validatedreview-code — when the job is judging someone else's diff, not producing itgithub-actions — when the failure is in workflow YAML rather than in the repositorygitlab — GitLab CI/CD pipelines and merge-request settingscode — planning, implementing, and verifying the change the commits will carryPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/git.