git-sich 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +224 -2
- package/dist/cli.js +1768 -0
- package/package.json +42 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marin Sokol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,225 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sich
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/git-sich)
|
|
4
|
+
|
|
5
|
+
CLI for managing multiple git repositories within a single base git repo.
|
|
6
|
+
|
|
7
|
+
Useful for:
|
|
8
|
+
- versioning private files alongside your public files, in the same folder
|
|
9
|
+
- access control per file: some files can be public while others belong to Org1 and others to Org2, all living side by side
|
|
10
|
+
|
|
11
|
+
`sich` lets a normal git repo (**base**, contained in `.git`) coexist smoothly with any number of other git repos (**layers**, contained in `.sich/X`). They share the same working tree, each with its own separate commit history and remote.
|
|
12
|
+
|
|
13
|
+
Works with GitHub out of the box: every new layer can automatically become a new private repo, and GitHub then handles access control for it.
|
|
14
|
+
|
|
15
|
+
Plain `git` commands only ever see **base** files. Files from other layers never appear in the
|
|
16
|
+
**base** repo: not in commits, not in `.gitignore`. Other contributors never even know that `sich` is in use or that the other repositories exist; everything works for them as usual.
|
|
17
|
+
|
|
18
|
+
**The name** is a shortened (easier-to-type) version of *sietch*, the hidden cave communities of the Fremen in Frank Herbert's *Dune*, where only the tribe knows the way in. Such are the layers of `sich`: only visible to those with access to them. It's pronounced like *sietch* ("seech").
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
- Notes, LLM conversations, roadmaps, or random scratch files you want versioned and synced, but not public.
|
|
23
|
+
- Secrets or local config shared with a few collaborators or your GitHub org, but not the world.
|
|
24
|
+
- No submodules, no symlinks, no separate folders. Files stay where they belong.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Requires Node ≥ 22 and git ≥ 2.28.
|
|
29
|
+
Optionally the GitHub CLI (`gh`) 2.x, for `sich new --gh`.
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm install -g git-sich # the package is git-sich; the command is sich
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
# Start in an existing git repo, called base from now on.
|
|
39
|
+
# Say it has a public remote at git@github.com:user/myproject.git.
|
|
40
|
+
cd myproject
|
|
41
|
+
|
|
42
|
+
# Create an empty .sich folder, exclude it from base (.git/info/exclude)
|
|
43
|
+
# and install the commit guard hooks (every layer gets them too).
|
|
44
|
+
sich init
|
|
45
|
+
|
|
46
|
+
# Create a layer (a sietch) called "personal": .sich/personal is a separate
|
|
47
|
+
# git repo, in the same format as the top-level .git that defines base.
|
|
48
|
+
# --gh also creates the private GitHub repo user/myproject-personal
|
|
49
|
+
# and sets it as the layer's remote.
|
|
50
|
+
sich new personal --gh
|
|
51
|
+
|
|
52
|
+
# The personal layer claims my-notes.md; base now ignores it automatically.
|
|
53
|
+
sich claim personal my-notes.md
|
|
54
|
+
|
|
55
|
+
# Commit in the personal repo.
|
|
56
|
+
# (`sich commit -m "..."` commits base + every layer with changes at once.)
|
|
57
|
+
sich personal commit -m "adding notes"
|
|
58
|
+
|
|
59
|
+
# Push personal to its remote, setting its upstream the first time.
|
|
60
|
+
# (`sich push` pushes base + all layers.)
|
|
61
|
+
sich push personal
|
|
62
|
+
|
|
63
|
+
# Create another layer, "team", and the private repo user/myproject-team,
|
|
64
|
+
# which you can share with your team.
|
|
65
|
+
sich new team --gh
|
|
66
|
+
|
|
67
|
+
# Claim these for the team layer; base's exclude now hides all three.
|
|
68
|
+
sich claim team roadmap.md api-keys.env llm-transcripts/
|
|
69
|
+
|
|
70
|
+
# A collaborator with sich installed and access to user/myproject-team
|
|
71
|
+
# clones base, then attaches the team layer.
|
|
72
|
+
git clone git@github.com:user/myproject.git && cd myproject
|
|
73
|
+
sich attach team git@github.com:user/myproject-team.git
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To see every file this generates, look at
|
|
77
|
+
**[section 4 of the example](docs/example.md#4-the-files-sich-generates)**.
|
|
78
|
+
|
|
79
|
+
## CLI
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
❯ sich --help
|
|
83
|
+
sich - private git layers that share one working tree with a normal repo
|
|
84
|
+
|
|
85
|
+
base your normal repo (.git), usually public
|
|
86
|
+
layer a private repo in .sich/<layer>/ with its own remote and access list
|
|
87
|
+
claim a file or folder a layer owns; base and other layers never see it
|
|
88
|
+
|
|
89
|
+
usage: sich [-C <dir>] <command> [args]
|
|
90
|
+
sich [-C <dir>] <layer|base> <git args...>
|
|
91
|
+
|
|
92
|
+
setup
|
|
93
|
+
init set up .sich/, excludes, commit guards
|
|
94
|
+
new <layer> [--remote <url> | --gh [name]]
|
|
95
|
+
create a layer (--gh: private GitHub repo)
|
|
96
|
+
attach <layer> <url> join an existing layer (collaborators)
|
|
97
|
+
|
|
98
|
+
ownership
|
|
99
|
+
claim <layer> <path...> [--move] claim files/folders for a layer, stage them
|
|
100
|
+
(--move: take them from base/another layer)
|
|
101
|
+
unclaim <layer> <path...> release claims; files stay on disk
|
|
102
|
+
which <path> show who owns a path
|
|
103
|
+
ls [layer] list claims and tracked files
|
|
104
|
+
|
|
105
|
+
everyday
|
|
106
|
+
status [-v] [--fetch] per repo: branch, ahead/behind, changes
|
|
107
|
+
commit [repo...] -m <msg> add -A + commit base + all layers (or named)
|
|
108
|
+
pull [repo...] pull --rebase base + all layers (or named)
|
|
109
|
+
push [repo...] push base + all layers (or named)
|
|
110
|
+
sync [repo...] pull, then push
|
|
111
|
+
check [--fix] [--staged] find leaks and ownership problems
|
|
112
|
+
(--staged: what the commit/merge hooks run)
|
|
113
|
+
|
|
114
|
+
passthrough
|
|
115
|
+
<layer> <git args...> run git in a layer, e.g. sich notes log
|
|
116
|
+
base <git args...> run git in the base repo
|
|
117
|
+
|
|
118
|
+
options
|
|
119
|
+
-C <dir> run as if started in <dir>
|
|
120
|
+
-h, --help show help (also: sich <command> --help)
|
|
121
|
+
-V, --version print version
|
|
122
|
+
|
|
123
|
+
environment
|
|
124
|
+
SICH_BIN sich binary the commit/merge hooks run
|
|
125
|
+
(default: sich on PATH)
|
|
126
|
+
SICH_GH gh binary for new --gh (default: gh)
|
|
127
|
+
NO_COLOR disable colors
|
|
128
|
+
|
|
129
|
+
Layer names match ^[a-z0-9][a-z0-9._-]*$; base and command names are reserved.
|
|
130
|
+
Claims can't nest across layers.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## How it works
|
|
134
|
+
|
|
135
|
+
It's plain git all the way down; `sich` is a tiny wrapper on top.
|
|
136
|
+
|
|
137
|
+
**Layers.** Layer `L` is a regular, non-bare git dir at `.sich/L/` with
|
|
138
|
+
`core.worktree = ../..`, so its working tree is the project root. sich always
|
|
139
|
+
runs git with explicit `--git-dir`/`--work-tree`, and strips `GIT_DIR`,
|
|
140
|
+
`GIT_INDEX_FILE` and friends from the environment.
|
|
141
|
+
|
|
142
|
+
**Manifests.** `.sich/L.paths` lists what `L` owns: one root-relative path per
|
|
143
|
+
line, directories end with `/`, `#` comments allowed. The manifest is tracked by
|
|
144
|
+
layer `L` itself, so collaborators get it on `attach`/`pull`. It is the single
|
|
145
|
+
source of truth for ownership; sich keeps it sorted (comments move to the top).
|
|
146
|
+
Paths that a line can't hold (starting with `#`, starting or ending with
|
|
147
|
+
whitespace, containing line breaks) can't be claimed.
|
|
148
|
+
|
|
149
|
+
**Generated excludes.** sich owns a marked block in each repo's `info/exclude`
|
|
150
|
+
(`# >>> sich: managed, do not edit >>>` … `# <<< sich <<<`; anything outside the
|
|
151
|
+
block is left alone) and regenerates it on every command:
|
|
152
|
+
|
|
153
|
+
- base: `/.sich/` plus every claim of every layer.
|
|
154
|
+
- layer `L`: a whitelist. `/*` ignores everything, then each claim is re-included
|
|
155
|
+
along with its parent chain (git can't re-include a file inside an excluded
|
|
156
|
+
directory), e.g. `a/b/c.md` becomes `!/a/` `/a/*` `!/a/b/` `/a/b/*` `!/a/b/c.md`.
|
|
157
|
+
|
|
158
|
+
Claims never nest across layers: if `notes` owns `docs/`, no other layer can
|
|
159
|
+
claim `docs/api.env` (and `keys` owning `docs/api.env` stops `notes` from
|
|
160
|
+
claiming `docs/` without `--move`, which takes the whole folder). Two files in
|
|
161
|
+
the same folder can still belong to different layers.
|
|
162
|
+
|
|
163
|
+
Because each layer only "sees" its claims, `git add -A` in a layer is safe, and
|
|
164
|
+
new files created inside a claimed directory automatically belong to that layer.
|
|
165
|
+
|
|
166
|
+
**Guard.** Base and every layer get a `pre-commit` and a `pre-merge-commit`
|
|
167
|
+
hook that run `sich check --staged`, since a layer may be public or shared with
|
|
168
|
+
a different audience too. It checks the repo whose index git is committing, so
|
|
169
|
+
the same lines also work in a `core.hooksPath` shared by every repo. In base it
|
|
170
|
+
blocks a commit that stages a claimed path or anything under `.sich/` (e.g.
|
|
171
|
+
after `git add -f`; on case-insensitive filesystems under any spelling). In
|
|
172
|
+
layer `L` it blocks a commit that stages a path `L` doesn't own: unclaimed (e.g.
|
|
173
|
+
after `sich L add -f`) or not yet in the claims list being committed, claimed by
|
|
174
|
+
another layer, or under `.sich/` (other than `L`'s own claims list). Staged
|
|
175
|
+
deletions never block, so untracking a stray file (`sich L rm --cached -- <path>`)
|
|
176
|
+
can be committed. Every hook also blocks while ownership between layers is
|
|
177
|
+
ambiguous (overlapping claims, a file tracked by the wrong layer), since that
|
|
178
|
+
could leak one layer's files into another. Stale exclude rules only warn. Where
|
|
179
|
+
sich isn't set up (a linked `git worktree`) the hooks do nothing. Skip the check
|
|
180
|
+
once with `git commit --no-verify` (in a layer: `sich L commit --no-verify`).
|
|
181
|
+
|
|
182
|
+
**Merges.** Merge commits are guarded too: git runs `pre-merge-commit` for a
|
|
183
|
+
merge commit made without conflicts (`git merge`, `git pull` without
|
|
184
|
+
`--rebase`), and `pre-commit` when you finish a conflicted merge with
|
|
185
|
+
`git commit`. The check covers what the merge brings in, e.g. a stray file
|
|
186
|
+
committed on a branch with `--no-verify`. By then git has already written the
|
|
187
|
+
merge result to the working tree, possibly over another repo's copy of an
|
|
188
|
+
incoming path, so a blocked merge says how to back out (`git merge --abort`)
|
|
189
|
+
and restore that copy afterwards, rather than how to unstage it. Skip the check
|
|
190
|
+
once with `git merge --no-verify` (in a layer: `sich L merge --no-verify`).
|
|
191
|
+
Fast-forward merges, rebase and cherry-pick run no hook at all, so what they
|
|
192
|
+
bring in isn't checked as it arrives; plain `sich check` catches it afterwards.
|
|
193
|
+
`sich check` also warns about any repo whose hooks don't run sich, and
|
|
194
|
+
`sich init` installs the missing ones.
|
|
195
|
+
|
|
196
|
+
## FAQ
|
|
197
|
+
|
|
198
|
+
**Why not just keep private files gitignored?**
|
|
199
|
+
Then they aren't versioned, synced between your machines or shareable with collaborators.
|
|
200
|
+
|
|
201
|
+
**Why not just a second, private GitHub repo?**
|
|
202
|
+
That's essentially what a layer is, but its files stay in the same folder as the main repo, next to the code that uses them or the context that explains them, and it scales easily to any number of layers.
|
|
203
|
+
|
|
204
|
+
**Why not a git submodule?**
|
|
205
|
+
Besides the benefit above (files stay next to what uses them), changing a layer never needs a commit in base, since their commit histories are fully separate, and the layer's repo name never appears in the main repo: from the outside, it doesn't exist.
|
|
206
|
+
|
|
207
|
+
That said, sometimes you do want a base commit to pin a submodule version: when the two only work with specific versions of each other (say, a package, library or API spec inside your own package) and must always move together, so checking out base at `HEAD~1` has to move the other as well. Submodules are the better fit there.
|
|
208
|
+
|
|
209
|
+
**Can't GitHub do per-file permissions?**
|
|
210
|
+
No. Visibility is set per repo, and anyone who can clone a repo gets every file in it.
|
|
211
|
+
Their per-repo access control is great, though, and sich builds on it!
|
|
212
|
+
|
|
213
|
+
**What's the difference between `sich claim notes x` and `sich notes add x`?**
|
|
214
|
+
`sich claim` gives `x` to the layer: it records the claim in `.sich/notes.paths`, hides `x` from base and the other layers, and stages it. `sich notes add` is plain `git add` run in the layer, which refuses files the layer hasn't claimed. Likewise `sich unclaim` releases a claim and keeps the file on disk, while `sich notes rm` is plain `git rm` and deletes it.
|
|
215
|
+
|
|
216
|
+
**Is this like git-crypt or sops?**
|
|
217
|
+
No. sich doesn't encrypt anything; it relies on who can access each repo. You can combine the two for real secrets.
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
[MIT](LICENSE)
|