mirror of
https://github.com/gopasspw/gopass.git
synced 2026-10-18 02:47:37 +02:00
* feat(backend): add pure-Go go-git storage backend Add an opt-in storage/RCS backend (gogit) that implements backend.Storage using github.com/go-git/go-git/v5. It removes the runtime dependency on the git binary for cross-compiled and single-binary distributions and for Windows users without Git for Windows on PATH. - New package internal/backend/storage/gogit implementing the existing backend.Storage interface (file ops delegate to fs.Store, RCS ops use go-git). The interface contract is unchanged. - Registered as gogit with priority 12 (after gitfs) so the CLI backend remains the auto-detected default when git is present. - Selectable via storage.backend=gogit, GOPASS_GIT_BACKEND=gogit or --storage=gogit. - Automatic fallback to gogit when the git binary is absent: the gitfs loader now fails Handles/New/Init/Clone with ErrGitBinaryNotFound and backend.ResolveStorageBackend downgrades a requested GitFS backend. - File-level merge with a two-parent merge commit for non-fast-forward pulls. A true conflict never discards a secret: the local version is kept and the remote version is written to <name>.conflict-<shortsha>. - All transport/auth logic isolated in auth.go (the go-git v6 seam). SSH via x/crypto/ssh + ssh-agent; HTTPS via git credential fill when git is available, otherwise URL userinfo. - Signed commits shell out to git commit -S when configured and git is present; otherwise an unsigned commit is created with a warning. gpg.format=ssh signing is unsupported. - doctor is aware of the pure-Go backend. - Docs: docs/backends/gogit.md, docs/backends.md, docs/config.md, docs/components.dot, docs/commands/git.md, ADR A-17 marked implemented. - Tests: unit + merge-matrix tests, a cross-backend object-graph parity test, and an opt-in integration test. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * fix(backend): address review feedback on the go-git backend Harden and correct the pure-Go go-git storage backend following review of the initial implementation. Security and correctness: - Validate every path from a remote tree before materialising it: reject absolute paths, paths escaping the store root, and anything inside .git. Also reject .git/.git/... in fs.Store.safePath as defence in depth, so a crafted remote commit can no longer plant a hook or rewrite the config. - Pull before pushing so a remote that is ahead no longer makes every push fail with a non-fast-forward error. - Replace HardReset with MergeReset when fast-forwarding: untracked files are preserved and uncommitted edits abort the reset instead of being silently discarded. Materialise an unborn HEAD first, which is what `gopass setup --remote` needs. - Stage explicit paths with AddOptions{Path} only. go-git checks All before Path, so the previous {All:true, Path:rel} staged the whole worktree and swept unrelated files into every "Save secret" commit. - Never resolve a concurrent .gpg-id (recipient) change automatically: abort the pull with a clear error and leave the worktree untouched. - Make the merge transactional: compute the whole resolution before writing anything and restore the index and worktree to HEAD on failure. - Recreate symlinks from the remote tree instead of writing the target as a regular file, refuse submodules, and include the file mode in the identical-on-both-sides check. - Route the merge commit through the same signing path as Commit. - Pick a collision-free conflict-file name by growing the hash prefix. - Fix Revisions: go-git's PathFilter compares each commit with the next in iteration order, so walk the log and diff against the first parent. - Honour commit.gpgsign from the global scope and pass --no-verify to the signing shell-out so git hooks are never run. - Fix urlUserInfo: only short-circuit on a URL that carries a password and percent-decode the userinfo. Run `git credential fill` in the store directory. Redact the password from clone errors. Backend selection: - Centralise the flag > env > config > default precedence in ResolveGitBackend and GitBackendFromEnvOrConfig, and use it from init, rcs and clone so GOPASS_GIT_BACKEND is honoured everywhere. - Only honour GOPASS_GIT_BACKEND when the configured/detected backend is already a git-family backend, so a cryptfs or jjfs store is not reopened as plain gogit/gitfs. - Resolve the cryptfs substorage so cryptfs stores open on git-less systems. - Move fossilfs to priority 20 to resolve the collision with gogit (12). Also stop reporting the expected "nothing to commit" result of the .gitattributes commit as a failure. Add unit tests for the new behaviour (add isolation, push-pulls-first, untracked preservation, symlink merge, .git rejection, .gpg-id conflict, conflict-name collision, revisions across merges, path validation, auth, no-git path), a cross-backend merge test, and a `gopass sync` integration test. Reconcile the ADR and docs/backends/gogit.md with the implementation. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * test(gogit): make cross-backend git CLI checks work on Windows The cross-backend tests invoked the git CLI in ways the MSYS2 git used by the Windows CI job cannot handle: - `git -C <dir>` is implemented with a chdir() that does not accept a Windows-style backslash path (e.g. `C:\msys64\tmp\...\001`), so it failed with exit status 128 even though the repository is valid. Set the process working directory via cmd.Dir instead, matching the gitfs backend. - `rev-parse HEAD^{tree}` is mangled by the MSYS2 runtime's brace expansion (`{tree}` -> `tree`), yielding "ambiguous argument 'HEAD^tree'". Use `show -s --format=%T HEAD` instead. Also capture stderr so future failures are diagnosable. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * fix(backend): address review feedback on the go-git backend Second review round on the pure-Go go-git storage backend: - fs: refuse to write through symlinked path components and replace a symlink with a regular file instead of following it, so a remote tree cannot plant a link into .git and then write through it. SetSymlink now only accepts relative targets that stay inside the store and outside .git. The .git guard also covers Windows aliases (GIT~1, trailing dots/spaces, alternate data streams) and nested components. - gogit: skip local-only deletions in the merge plan (staging them failed with "entry not found"), refuse a non-fast-forward merge over uncommitted changes, and restore only the affected paths on failure. - gogit: preserve staged files across an unborn-HEAD pull (gopass setup --remote), return early when the remote is an ancestor of local instead of creating an empty merge commit, and report a missing remote branch instead of silently doing nothing. - gogit: make the unsigned-commit fallback opt-in via gogit.allow-unsigned-commits, accept yes/on/1 for commit.gpgsign, sign merge commits via .git/MERGE_HEAD (git commit -p is --patch), and honour the commit timestamp for signed commits. - gogit: strip userinfo from remote URLs before handing them to go-git so credentials cannot leak through go-git's error messages; fix splitHTTPURL for username-only URLs and disable the terminal prompt for git credential fill. - gogit: resolve user.name/user.email local-over-global, compare revisions against all parents, exclude untracked files from staged changes, delete loose objects on prune, and treat .age-recipients/.age-ids/ .gopass-mapping as recipient files. - backend: only honour storage.backend for git-family backends and apply GOPASS_GIT_BACKEND after auto-detection, so a cryptfs/jjfs store is not reopened as plain gogit/gitfs. - docs: reconcile the ADR, docs/backends/gogit.md and docs/config.md with the implementation; drop the duplicate A-17 index row. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * fix(backend): address remaining review feedback on the go-git backend - fs: reject .jj (and .git) metadata paths in safePath; rename the guard to rejectVCSDir/IsVCSDirName and cover the .jj aliases in the tests. - gogit: preserve the executable bit when materialising remote tree entries so a merge does not record a spurious mode change. - gogit: add tests for executable-mode preservation, retry after a partial merge failure, symlink-then-regular-file against .git/config, and the opt-in signed-commit requirement. - fossilfs: rephrase the priority comment (highest among VCS backends). - docs: reconcile the ADR and gogit backend docs with the implementation (all-parents TREESAME history, prune handler, opt-in unsigned fallback, .jj guard, executable mode). Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> * test(gogit): make executable-mode merge assertion Windows-aware TestGoGitMergePreservesExecutableMode asserted Unix semantics on all platforms. Windows has no executable bit: os.Chmod cannot set it and go-git derives the index mode from the OS file mode (filemode.NewFromOSFileMode), so both the worktree bit and the recorded index mode are non-executable there. ADR A-17 already accepts this ("gopass secrets are never executable"), so gate the Unix-only assertions on GOOS and assert the regular-file outcome on Windows. Also document that the executable bit is preserved except on Windows in docs/backends/gogit.md and in the applyFileMode comment, and reword the test comment to describe the resolved mode rather than a Unix mode. Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org> --------- Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>