slipway 0.1.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.
- checksums.yaml +7 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +1013 -0
- data/exe/slipway +10 -0
- data/lib/slipway/cli/builtins.rb +241 -0
- data/lib/slipway/cli/completer.rb +158 -0
- data/lib/slipway/cli/completion_scripts.rb +163 -0
- data/lib/slipway/cli/context.rb +67 -0
- data/lib/slipway/cli/errors.rb +19 -0
- data/lib/slipway/cli/globals.rb +27 -0
- data/lib/slipway/cli/help_renderer.rb +135 -0
- data/lib/slipway/cli/manpage.rb +226 -0
- data/lib/slipway/cli/parser.rb +45 -0
- data/lib/slipway/cli/registry.rb +191 -0
- data/lib/slipway/cli/runner.rb +186 -0
- data/lib/slipway/cli/style.rb +82 -0
- data/lib/slipway/cli/theme.rb +85 -0
- data/lib/slipway/cli/validator.rb +61 -0
- data/lib/slipway/cli.rb +22 -0
- data/lib/slipway/command_line.rb +22 -0
- data/lib/slipway/commands/api_resources.rb +82 -0
- data/lib/slipway/commands/apply.rb +172 -0
- data/lib/slipway/commands/base.rb +50 -0
- data/lib/slipway/commands/config.rb +73 -0
- data/lib/slipway/commands/create.rb +218 -0
- data/lib/slipway/commands/delete.rb +82 -0
- data/lib/slipway/commands/describe.rb +74 -0
- data/lib/slipway/commands/diff.rb +122 -0
- data/lib/slipway/commands/edit.rb +130 -0
- data/lib/slipway/commands/explain.rb +97 -0
- data/lib/slipway/commands/fetch.rb +112 -0
- data/lib/slipway/commands/from_dir.rb +141 -0
- data/lib/slipway/commands/get.rb +167 -0
- data/lib/slipway/commands/label.rb +114 -0
- data/lib/slipway/commands/manual.rb +67 -0
- data/lib/slipway/commands/options.rb +73 -0
- data/lib/slipway/commands/results.rb +57 -0
- data/lib/slipway/commands/rollout.rb +114 -0
- data/lib/slipway/commands/rollout_spec.rb +99 -0
- data/lib/slipway/commands/rollout_undo.rb +126 -0
- data/lib/slipway/commands/scope.rb +156 -0
- data/lib/slipway/commands/sync.rb +140 -0
- data/lib/slipway/commands.rb +54 -0
- data/lib/slipway/drift.rb +87 -0
- data/lib/slipway/editor.rb +71 -0
- data/lib/slipway/error.rb +27 -0
- data/lib/slipway/fetcher.rb +99 -0
- data/lib/slipway/field_selector.rb +86 -0
- data/lib/slipway/git/branch_name.rb +32 -0
- data/lib/slipway/git/commit.rb +13 -0
- data/lib/slipway/git/distance.rb +13 -0
- data/lib/slipway/git/errors.rb +125 -0
- data/lib/slipway/git/fake.rb +147 -0
- data/lib/slipway/git/fast_forward.rb +12 -0
- data/lib/slipway/git/fast_forwarding.rb +148 -0
- data/lib/slipway/git/fetch_result.rb +22 -0
- data/lib/slipway/git/move_back.rb +12 -0
- data/lib/slipway/git/reflog.rb +25 -0
- data/lib/slipway/git/repository.rb +288 -0
- data/lib/slipway/git/rolling_back.rb +98 -0
- data/lib/slipway/git/runner.rb +175 -0
- data/lib/slipway/git/status.rb +110 -0
- data/lib/slipway/git/url.rb +95 -0
- data/lib/slipway/git.rb +20 -0
- data/lib/slipway/inspector.rb +103 -0
- data/lib/slipway/labels.rb +126 -0
- data/lib/slipway/manifest.rb +265 -0
- data/lib/slipway/names.rb +22 -0
- data/lib/slipway/outcome.rb +45 -0
- data/lib/slipway/output/age.rb +70 -0
- data/lib/slipway/output/describe.rb +71 -0
- data/lib/slipway/output/explain.rb +75 -0
- data/lib/slipway/output/serializer.rb +35 -0
- data/lib/slipway/output/table.rb +67 -0
- data/lib/slipway/output.rb +28 -0
- data/lib/slipway/paths.rb +65 -0
- data/lib/slipway/plan.rb +227 -0
- data/lib/slipway/pool.rb +94 -0
- data/lib/slipway/resources.rb +91 -0
- data/lib/slipway/rollback.rb +236 -0
- data/lib/slipway/rollout_history.rb +69 -0
- data/lib/slipway/runtime.rb +65 -0
- data/lib/slipway/scanner.rb +54 -0
- data/lib/slipway/schema.rb +128 -0
- data/lib/slipway/selector.rb +146 -0
- data/lib/slipway/settings.rb +174 -0
- data/lib/slipway/state.rb +82 -0
- data/lib/slipway/store.rb +170 -0
- data/lib/slipway/syncer.rb +139 -0
- data/lib/slipway/version.rb +5 -0
- data/lib/slipway/views/group.rb +35 -0
- data/lib/slipway/views/project.rb +148 -0
- data/lib/slipway/views.rb +10 -0
- data/lib/slipway/yaml.rb +14 -0
- data/lib/slipway.rb +32 -0
- data/man/man1/slipway-api-resources.1 +53 -0
- data/man/man1/slipway-apply.1 +45 -0
- data/man/man1/slipway-completion.1 +29 -0
- data/man/man1/slipway-config-path.1 +20 -0
- data/man/man1/slipway-config-view.1 +25 -0
- data/man/man1/slipway-config.1 +22 -0
- data/man/man1/slipway-create.1 +89 -0
- data/man/man1/slipway-delete.1 +46 -0
- data/man/man1/slipway-describe.1 +95 -0
- data/man/man1/slipway-diff.1 +120 -0
- data/man/man1/slipway-edit.1 +34 -0
- data/man/man1/slipway-explain.1 +36 -0
- data/man/man1/slipway-fetch.1 +77 -0
- data/man/man1/slipway-get.1 +155 -0
- data/man/man1/slipway-help.1 +19 -0
- data/man/man1/slipway-label.1 +53 -0
- data/man/man1/slipway-man.1 +36 -0
- data/man/man1/slipway-rollout-history.1 +28 -0
- data/man/man1/slipway-rollout-pause.1 +21 -0
- data/man/man1/slipway-rollout-resume.1 +21 -0
- data/man/man1/slipway-rollout-undo.1 +73 -0
- data/man/man1/slipway-rollout-unpin.1 +21 -0
- data/man/man1/slipway-rollout.1 +36 -0
- data/man/man1/slipway-sync.1 +90 -0
- data/man/man1/slipway-version.1 +17 -0
- data/man/man1/slipway.1 +243 -0
- metadata +173 -0
data/README.md
ADDED
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
# slipway
|
|
2
|
+
|
|
3
|
+
A kubectl-style registry of the git repositories on your machine: it shows where each one stands
|
|
4
|
+
against its upstream and its manifest, and fast-forwards the ones that can move safely.
|
|
5
|
+
|
|
6
|
+
[](https://github.com/hvpaiva/slipway/actions/workflows/ci.yml)
|
|
7
|
+
|
|
8
|
+
`slipway get projects` shows in one table which of the git repositories you registered are clean,
|
|
9
|
+
dirty, behind their upstream or missing from disk, and `slipway fetch` fetches them all without
|
|
10
|
+
ever stopping at a password prompt. Each registration is a manifest that can also declare where
|
|
11
|
+
its repository should be, such as the remote, the branch or a commit to hold it at: `slipway diff`
|
|
12
|
+
shows how each repository differs from that, `slipway sync` fast-forwards the branches that can
|
|
13
|
+
move without losing anything, and `slipway rollout undo` takes such a move back.
|
|
14
|
+
|
|
15
|
+
Three repositories are cloned under `~/dev`. Register them, two in a `personal` group and one in
|
|
16
|
+
the `default` group:
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
$ slipway create group personal --description "Personal projects"
|
|
20
|
+
group/personal created
|
|
21
|
+
|
|
22
|
+
$ slipway create project hldr --path '~/dev/hldr' -n personal --label lang=rust --description "Site and CLI for hvpaiva.dev" --remote git@github.com:hvpaiva/hldr.git --branch main
|
|
23
|
+
project/hldr created
|
|
24
|
+
|
|
25
|
+
$ slipway create project augur --path '~/dev/augur' -n personal --label lang=bash
|
|
26
|
+
project/augur created
|
|
27
|
+
|
|
28
|
+
$ slipway create project notes --path '~/dev/notes' --label kind=docs
|
|
29
|
+
project/notes created
|
|
30
|
+
|
|
31
|
+
$ slipway get projects -A
|
|
32
|
+
GROUP NAME BRANCH STATUS FETCHED AGE
|
|
33
|
+
default notes main Ahead 2d 0s
|
|
34
|
+
personal augur main Dirty <never> 1s
|
|
35
|
+
personal hldr main Clean 5h 1s
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
STATUS is read from the repository on disk, so it is as fresh as the last fetch, whose age
|
|
39
|
+
FETCHED shows. Fetching every project brings in what was pushed from other machines since:
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
$ slipway fetch -A
|
|
43
|
+
project/notes fetched
|
|
44
|
+
origin/main 1c2d3e4..5f6a7b8
|
|
45
|
+
project/augur skipped (NoRemote)
|
|
46
|
+
no upstream, no origin and no single remote to fetch from
|
|
47
|
+
project/hldr fetched
|
|
48
|
+
origin/main e001395..4c5d6e7
|
|
49
|
+
3 projects: 2 fetched, 1 skipped
|
|
50
|
+
|
|
51
|
+
$ slipway get projects -A
|
|
52
|
+
GROUP NAME BRANCH STATUS FETCHED AGE
|
|
53
|
+
default notes main Diverged 0s 1s
|
|
54
|
+
personal augur main Dirty <never> 1s
|
|
55
|
+
personal hldr main Behind 0s 1s
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
hldr is now behind `origin/main`, and notes, which had a commit of its own, has diverged from
|
|
59
|
+
it. `diff` compares each repository with its manifest and says what `sync` would do, and `sync`
|
|
60
|
+
does it:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
$ slipway diff -A
|
|
64
|
+
project/notes
|
|
65
|
+
Behind: 1 commit behind origin/main
|
|
66
|
+
Diverged: 1 ahead, 1 behind origin/main; sync never merges or rebases
|
|
67
|
+
git -C ~/dev/notes log --oneline --left-right HEAD...@{upstream}
|
|
68
|
+
project/augur
|
|
69
|
+
NoUpstream: main tracks no upstream; sync fast-forwards only a tracking branch
|
|
70
|
+
project/hldr
|
|
71
|
+
Behind: 3 commits behind origin/main; sync will fast-forward
|
|
72
|
+
|
|
73
|
+
$ slipway sync -A
|
|
74
|
+
project/notes skipped (Diverged)
|
|
75
|
+
1 ahead, 1 behind origin/main; sync never merges or rebases
|
|
76
|
+
git -C ~/dev/notes log --oneline --left-right HEAD...@{upstream}
|
|
77
|
+
project/augur skipped (NoRemote)
|
|
78
|
+
no upstream, no origin and no single remote to fetch from
|
|
79
|
+
project/hldr fast-forwarded
|
|
80
|
+
main e001395..4c5d6e7 (3 commits); undo with 'slipway rollout undo project/hldr -n personal'
|
|
81
|
+
3 projects: 1 fast-forwarded, 2 skipped
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A move you did not want is undone with the command printed under it:
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
$ slipway rollout undo project/hldr -n personal
|
|
88
|
+
project/hldr rolled back
|
|
89
|
+
main 4c5d6e7..e001395 (3 commits back to revision 1); held there by spec.revision
|
|
90
|
+
'slipway rollout unpin project/hldr -n personal' follows origin/main again
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The command line follows kubectl: verbs over resource types, groups used the way kubectl uses
|
|
94
|
+
namespaces (`-n personal`, `-A`), label selectors (`-l lang=rust`), manifests you can
|
|
95
|
+
`apply -f`, and table, json or yaml output. STATUS words, drift and the result lines of
|
|
96
|
+
`fetch`, `sync` and `rollout` are slipway's own, and the sections below define each of them.
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
Slipway is not published on RubyGems yet. Until it is, install it from a checkout:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
git clone https://github.com/hvpaiva/slipway.git
|
|
104
|
+
cd slipway
|
|
105
|
+
bundle install
|
|
106
|
+
bundle exec rake install
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Once it is published, `gem install slipway` installs it, and so does `mise use -g gem:slipway`
|
|
110
|
+
with [mise](https://mise.jdx.dev).
|
|
111
|
+
|
|
112
|
+
Slipway needs Ruby 3.4 or newer and git 2.35 or newer on `PATH`, and has no runtime gem
|
|
113
|
+
dependencies. Git before 2.41 cannot tell `unchanged` from `fetched`, so every fetch that
|
|
114
|
+
succeeds reads `fetched`, without the refs that moved. Fetching over ssh without a prompt needs
|
|
115
|
+
OpenSSH 8.4 or newer; an older ssh may still ask on the terminal.
|
|
116
|
+
|
|
117
|
+
## Usage
|
|
118
|
+
|
|
119
|
+
| Verb | What it does |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `get TYPE [NAME...]` | List resources as a table, or as wide, json, yaml or name output. |
|
|
122
|
+
| `describe TYPE [NAME...]` | Print every field of the selected resources, including the repository state. |
|
|
123
|
+
| `create TYPE NAME` | Register a project (`--path DIR`, `--description`, `--label`, `--remote`, `--branch`) or create a group; `-o yaml` prints the manifest. |
|
|
124
|
+
| `create project --from-dir DIR` | Register every git repository at or under a directory, with its origin URL. |
|
|
125
|
+
| `apply -f FILE` | Create or update resources from manifests; prints `created`, `configured` or `unchanged`. |
|
|
126
|
+
| `delete TYPE NAME...` | Remove registrations, a deleted group's projects included, never the repositories on disk; prints `project "hldr" deleted from personal group`. `--ignore-not-found` makes an unknown name a success. |
|
|
127
|
+
| `edit TYPE NAME` | Open the manifest in your editor and save what comes back. |
|
|
128
|
+
| `label TYPE NAME KEY=VALUE...` | Set or remove labels on a resource. |
|
|
129
|
+
| `explain TYPE[.FIELD...]` | Print the fields of a manifest, or of one field, with the type, rule and default of each; `--recursive` prints the whole tree. |
|
|
130
|
+
| `fetch [NAME...]` | Run `git fetch` in the selected projects, without prompts; prints `fetched`, `unchanged`, `skipped`, `paused`, `denied` or `failed`. |
|
|
131
|
+
| `diff [NAME...]` | Show where projects differ from their manifests, without contacting a remote; exit status 3 when any does. |
|
|
132
|
+
| `sync [NAME...]` | Fetch the selected projects and fast-forward each clean branch that is behind; prints `fast-forwarded`, `fetched`, `unchanged`, `skipped`, `paused`, `denied` or `failed`. |
|
|
133
|
+
| `rollout history NAME` | List the revisions slipway moved a project's branch to, read from the branch reflog. |
|
|
134
|
+
| `rollout undo NAME` | Move the branch back to the previous revision, or to `--to-revision=N`, and hold it there with `spec.revision`. |
|
|
135
|
+
| `rollout unpin NAME...` | Remove `spec.revision`, so `sync` follows the upstream again. |
|
|
136
|
+
| `rollout pause NAME...`, `rollout resume NAME...` | Set or remove `spec.paused`, which keeps `fetch` and `sync` away from a project. |
|
|
137
|
+
| `config view`, `config path` | Show the configuration in effect and the file it came from. |
|
|
138
|
+
| `completion SHELL` | Print the completion script for bash, zsh or fish. |
|
|
139
|
+
| `man [COMMAND]` | Open the bundled manual page of a command. |
|
|
140
|
+
| `api-resources` | List the resource types with their short names and kind, and whether they live in a group; `-o wide` adds the verbs that act on each. |
|
|
141
|
+
| `version` | Print the version, the Ruby it runs on and the platform, as `slipway 0.1.0 (ruby 4.0.7) [x86_64-linux]`. |
|
|
142
|
+
| `help [COMMAND]` | Print the same text as `--help`. |
|
|
143
|
+
|
|
144
|
+
Two resource types exist: `projects` (also `project`, `proj`) and `groups` (also `group`), and
|
|
145
|
+
type words are case-insensitive; `slipway api-resources` lists them
|
|
146
|
+
([Discovering resources](#discovering-resources)). A project is named bare (`hldr`) or as
|
|
147
|
+
`project/hldr`, the form `get -o name` prints. `create`, `apply`, `delete`, `label`, `fetch`,
|
|
148
|
+
`sync` and `rollout undo` accept `--dry-run`, which fetches and writes nothing and ends each
|
|
149
|
+
result line with `(dry run)`. `slipway VERB --help` describes each verb, `-h` prints help and
|
|
150
|
+
`-V` the version.
|
|
151
|
+
|
|
152
|
+
### Groups
|
|
153
|
+
|
|
154
|
+
Projects live in groups the way pods live in namespaces. `-n NAME` (`--group`) selects one,
|
|
155
|
+
`-A` (`--all-groups`) lists across all of them and adds a GROUP column. Without either, the
|
|
156
|
+
`default` group is used, or the one set by `SLIPWAY_GROUP` or the `group` config key. The
|
|
157
|
+
default group is created the first time a write needs it; every other group has to be created
|
|
158
|
+
first, and `slipway delete group default` is refused.
|
|
159
|
+
|
|
160
|
+
```console
|
|
161
|
+
$ slipway get groups
|
|
162
|
+
NAME PROJECTS AGE
|
|
163
|
+
default 1 1s
|
|
164
|
+
personal 2 1s
|
|
165
|
+
|
|
166
|
+
$ slipway describe group personal
|
|
167
|
+
Name: personal
|
|
168
|
+
Labels: <none>
|
|
169
|
+
Created: 2026-09-29T07:15:35Z
|
|
170
|
+
Age: 1s
|
|
171
|
+
Description: Personal projects
|
|
172
|
+
Projects: 2
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
PROJECTS counts the projects registered in the group. `-o wide` adds DESCRIPTION, and
|
|
176
|
+
`--show-labels` adds LABELS for groups as it does for projects.
|
|
177
|
+
|
|
178
|
+
### Registering existing clones
|
|
179
|
+
|
|
180
|
+
`slipway create project --from-dir ~/dev/personal -n personal` registers every git repository
|
|
181
|
+
at or under the directory, searching `--depth` levels down (1 by default, at most 8). The search
|
|
182
|
+
stops at a directory with a `.git` entry, so a linked worktree counts and nothing nested inside
|
|
183
|
+
a repository does, and it never follows a symbolic link below the directory. Each project is
|
|
184
|
+
named after its directory, lowercased, with each run of characters other than ASCII letters,
|
|
185
|
+
digits and dashes made one dash (`My.Notes_v2` becomes `my-notes-v2`). `spec.path` starts with
|
|
186
|
+
`~/` under `HOME`, and `spec.remote` is the URL of `origin` without its credentials: the whole
|
|
187
|
+
user part over http and https, the password elsewhere. An origin that still breaks the
|
|
188
|
+
`spec.remote` rule, such as a local path, is left out with a `warning:` line. The checked-out
|
|
189
|
+
branch is not recorded, since it may be a feature branch; set `spec.branch` with `edit` when you
|
|
190
|
+
want it.
|
|
191
|
+
|
|
192
|
+
A path the group already holds prints `project/NAME unchanged`, whatever its name, so running
|
|
193
|
+
the command again registers only the new clones. A derived name that another path already has
|
|
194
|
+
is reported after the other lines, as `error: ~/dev/foo: project "foo" already exists at
|
|
195
|
+
~/dev/Foo`, with exit status 1. `--dry-run -o yaml` prints the projects as one
|
|
196
|
+
`kind: List` without writing anything, ready for `slipway apply -f` on another machine.
|
|
197
|
+
|
|
198
|
+
### Describing
|
|
199
|
+
|
|
200
|
+
`describe` prints the manifest, the repository as git reports it, the last commit and the
|
|
201
|
+
[drift](#drift). hldr, after the rollback above, is held at a commit its upstream has moved past:
|
|
202
|
+
|
|
203
|
+
```console
|
|
204
|
+
$ slipway describe project hldr -n personal
|
|
205
|
+
Name: hldr
|
|
206
|
+
Group: personal
|
|
207
|
+
Labels: lang=rust
|
|
208
|
+
Created: 2026-09-29T07:15:35Z
|
|
209
|
+
Age: 1s
|
|
210
|
+
Path: ~/dev/hldr
|
|
211
|
+
Description: Site and CLI for hvpaiva.dev
|
|
212
|
+
Remote: git@github.com:hvpaiva/hldr.git
|
|
213
|
+
Branch: main
|
|
214
|
+
Revision: e001395 (pinned)
|
|
215
|
+
Sync Policy: FastForward
|
|
216
|
+
Paused: false
|
|
217
|
+
Status: Behind
|
|
218
|
+
Repository:
|
|
219
|
+
Branch: main
|
|
220
|
+
Head: e001395
|
|
221
|
+
Upstream: origin/main
|
|
222
|
+
Ahead: 0
|
|
223
|
+
Behind: 3
|
|
224
|
+
Staged: 0
|
|
225
|
+
Unstaged: 0
|
|
226
|
+
Untracked: 0
|
|
227
|
+
Conflicted: 0
|
|
228
|
+
Stashes: 0
|
|
229
|
+
Remote: git@github.com:hvpaiva/hldr.git
|
|
230
|
+
Last Fetch: 2026-09-29T07:15:35Z
|
|
231
|
+
Last Commit:
|
|
232
|
+
Hash: e001395f1c8d1b0e8a7c0c56f7b0e1a4b3c2d1e0
|
|
233
|
+
Author: Highlander <contact@hvpaiva.dev>
|
|
234
|
+
Date: 2026-09-27T23:15:35Z
|
|
235
|
+
Subject: feat: list posts by year
|
|
236
|
+
Drift: <none>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
STATUS compares the branch with its upstream and ignores the pin, while drift compares the
|
|
240
|
+
repository with the manifest, so hldr is Behind with no drift. When git cannot read the
|
|
241
|
+
repository, the Repository block holds git's reason instead of the fields.
|
|
242
|
+
|
|
243
|
+
### Labels
|
|
244
|
+
|
|
245
|
+
```console
|
|
246
|
+
$ slipway label project hldr tier=web -n personal
|
|
247
|
+
project/hldr labeled
|
|
248
|
+
|
|
249
|
+
$ slipway label project hldr tier=web -n personal
|
|
250
|
+
project/hldr not labeled
|
|
251
|
+
|
|
252
|
+
$ slipway label project hldr tier=api -n personal
|
|
253
|
+
error: 'tier' already has a value (web), and --overwrite is false
|
|
254
|
+
|
|
255
|
+
$ slipway label project hldr --list -n personal
|
|
256
|
+
lang=rust
|
|
257
|
+
tier=web
|
|
258
|
+
|
|
259
|
+
$ slipway label project hldr tier- -n personal
|
|
260
|
+
project/hldr unlabeled
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`KEY=VALUE` sets a label and `KEY-` removes one. A key that already has a different value is
|
|
264
|
+
changed only with `--overwrite`. `not labeled` means nothing changed: the label already had
|
|
265
|
+
that value, or the label to remove was not set.
|
|
266
|
+
|
|
267
|
+
### Editing
|
|
268
|
+
|
|
269
|
+
`slipway edit project hldr -n personal` writes the manifest to a temporary file, opens it in
|
|
270
|
+
`SLIPWAY_EDITOR`, then the `editor` config key, then `VISUAL`, then `EDITOR`, or `vi`, and
|
|
271
|
+
saves what comes back as `project/hldr edited`. Text that changes without changing the object
|
|
272
|
+
prints `project/hldr skipped`. An unchanged file prints `Edit cancelled, no changes made.` on
|
|
273
|
+
stderr; an invalid one is reopened with the failure as a comment block at the top, and saving
|
|
274
|
+
that reopened file unchanged aborts with `error: Edit cancelled, no valid changes were saved.`;
|
|
275
|
+
an empty file aborts with `error: Edit cancelled, saved file was empty.`. Both aborts exit
|
|
276
|
+
with status 1.
|
|
277
|
+
|
|
278
|
+
### Fetching
|
|
279
|
+
|
|
280
|
+
`slipway fetch` runs `git fetch` in the projects of the current group, in the projects named,
|
|
281
|
+
in the ones `-l` selects, or with `-A` in every project. Git fetches from the remote of the
|
|
282
|
+
checked-out branch, else from the only remote, else from origin, as a `git fetch` typed in the
|
|
283
|
+
repository would: slipway passes no remote, and nothing from a manifest reaches git's arguments.
|
|
284
|
+
Git updates the refs the remote's fetch refspecs name (remote-tracking refs by default), tags and
|
|
285
|
+
`FETCH_HEAD`, never the checked-out branch or the working tree. A second fetch right after the
|
|
286
|
+
one above finds nothing new:
|
|
287
|
+
|
|
288
|
+
```console
|
|
289
|
+
$ slipway fetch -A
|
|
290
|
+
project/notes unchanged
|
|
291
|
+
project/augur skipped (NoRemote)
|
|
292
|
+
no upstream, no origin and no single remote to fetch from
|
|
293
|
+
project/hldr unchanged
|
|
294
|
+
3 projects: 2 unchanged, 1 skipped
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Up to `parallel` projects (4 by default) fetch at once. Each prints one result, in the order the
|
|
298
|
+
projects are listed, as soon as it and every project before it are done:
|
|
299
|
+
|
|
300
|
+
| Result | Meaning |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `fetched` | The remote moved refs. Up to five follow, then `and N more`: `origin/main a1b2c3d..e4f5a6b` for a ref that moved, `origin/feature d09a085 (new)` for a new one and `origin/feature deleted (was d09a085)` for one `--prune` removed. Tags appear under their bare name. |
|
|
303
|
+
| `unchanged` | The remote answered and had nothing new. |
|
|
304
|
+
| `skipped (Reason)` | No fetch ran: git could not read the repository (`Missing`, `NotARepo`, `Unsafe`, `Unknown`), there is no upstream, no origin and no single remote (`NoRemote`), or the branch tracks a local branch (`LocalUpstream`). |
|
|
305
|
+
| `paused` | The manifest sets `spec.paused: true`, so no git command ran in the project. |
|
|
306
|
+
| `denied (AuthRequired)` | Git needed a password, a passphrase or a host key. Run the `git -C PATH fetch` printed below it once in a terminal to see what git needs. |
|
|
307
|
+
| `failed (Reason)` | The fetch ran past `networkTimeout` (`Timeout`), used a transport `protocols` leaves out (`ProtocolNotAllowed`), or git failed for another reason (`Unknown`). |
|
|
308
|
+
|
|
309
|
+
When more than one project ran, a count of the results closes the run on stderr. The exit
|
|
310
|
+
status is 1 when any project was denied or failed, once every line has printed.
|
|
311
|
+
|
|
312
|
+
`--prune` also removes the remote-tracking refs of branches deleted on the remote, and it is
|
|
313
|
+
what makes a branch whose upstream was deleted read `Gone`: a plain fetch leaves the old ref in
|
|
314
|
+
place, unless git's `fetch.prune` is set. To bring every STATUS up to date, whatever the fetch
|
|
315
|
+
reports:
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
slipway fetch -A --prune; slipway get projects -A
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Git never prompts during a fetch: slipway sets `GIT_TERMINAL_PROMPT=0`, points `GIT_ASKPASS` and
|
|
322
|
+
`SSH_ASKPASS` at `false`, and sets `SSH_ASKPASS_REQUIRE=force`, so an ssh from OpenSSH 8.4 on
|
|
323
|
+
never reads the terminal.
|
|
324
|
+
Your ssh configuration, `SSH_AUTH_SOCK` and credential helpers are used as they are. Only ssh
|
|
325
|
+
and https remotes are fetched unless the `protocols` setting adds more, so an `http://`,
|
|
326
|
+
`git://` or local path remote fails with `ProtocolNotAllowed` until it does. A fetch that runs
|
|
327
|
+
past `networkTimeout` is killed with every process it started, submodules are not fetched, and
|
|
328
|
+
gc, automatic maintenance and bundle URIs are off. On Ctrl-C slipway stops the git processes it
|
|
329
|
+
started and exits with status 130.
|
|
330
|
+
|
|
331
|
+
### Diffing
|
|
332
|
+
|
|
333
|
+
`slipway diff` compares every project of the current group, the ones named, the ones a selector
|
|
334
|
+
matches, or with `-A` every project, with its manifest. Each project that differs prints its
|
|
335
|
+
name and one line per [drift](#drift) item, and an item that has a git command to show or
|
|
336
|
+
resolve it is followed by that command, as in the example at the top of this page. A project
|
|
337
|
+
that matches its manifest prints nothing. Slipway never runs these commands, never writes to a
|
|
338
|
+
repository or to the registry, and contacts no remote.
|
|
339
|
+
|
|
340
|
+
The exit status is 0 when every project matches its manifest and 3 when any differs or is
|
|
341
|
+
blocked, `NotARepo` and `Unsafe` included. It is 1 on an error, such as an unreadable manifest
|
|
342
|
+
or a project whose state is `Unknown`, so a script or a timer can tell drift from failure.
|
|
343
|
+
|
|
344
|
+
### Syncing
|
|
345
|
+
|
|
346
|
+
`slipway sync` fetches the projects of the current group, the ones named, the ones `-l` selects,
|
|
347
|
+
or with `-A` every project, as `slipway fetch` does. It then compares each one with its manifest
|
|
348
|
+
as `slipway diff` does and fast-forwards the checked-out branch onto its upstream with
|
|
349
|
+
`git merge --ff-only --no-autostash` when nothing blocks it: the branch has commits and tracks
|
|
350
|
+
an upstream that still exists, is behind it and not ahead of it, and has no staged, unstaged or
|
|
351
|
+
conflicted changes and no merge, rebase, cherry-pick, revert, bisect or `git am` in progress.
|
|
352
|
+
Untracked files do not block it; git refuses a fast-forward that would overwrite one, and sync
|
|
353
|
+
reports that. Sync never pulls, merges, rebases, stashes, resets, cleans, pushes, switches a
|
|
354
|
+
branch, changes a remote or removes a lock. [SECURITY.md](SECURITY.md#safety-promises) lists
|
|
355
|
+
every promise slipway makes about the repositories it touches.
|
|
356
|
+
|
|
357
|
+
| Result | Meaning |
|
|
358
|
+
| --- | --- |
|
|
359
|
+
| `fast-forwarded` | The branch moved. For a move onto the upstream, the detail names the commits it gained, as `main a1b2c3d..e4f5a6b (3 commits)`, and the command that undoes the move. |
|
|
360
|
+
| `fetched` | The fetch of a `FetchOnly` project moved refs; the refs follow as in `fetch`. |
|
|
361
|
+
| `unchanged` | The branch stayed where it was and nothing blocked it; the fetch may still have moved remote-tracking refs. |
|
|
362
|
+
| `skipped (Reason)` | The branch stayed where it was: the project was skipped as in `fetch`, a [blocker](#drift) stopped it, or git refused the fast-forward for untracked files in the way (`WouldOverwrite`), changes `git status` hides (`WouldLoseChanges`), another git process's lock (`Busy`) or a commit the branch gained since the check (`NotFastForward`). |
|
|
363
|
+
| `paused` | The manifest sets `spec.paused: true`, so no git command ran in the project. |
|
|
364
|
+
| `denied (AuthRequired)` | The fetch or the fast-forward needed a password, a passphrase or a host key, as in `fetch`. |
|
|
365
|
+
| `failed (Reason)` | The fetch or the fast-forward failed as in `fetch` (`Timeout`, `ProtocolNotAllowed`, `Unknown`). A fast-forward stopped at `networkTimeout` leaves the branch where it was but keeps the files git wrote; the detail names the command that lists them. |
|
|
366
|
+
|
|
367
|
+
A difference sync leaves alone, such as a `Remote` or a `Branch` [drift](#drift), and a branch
|
|
368
|
+
that `FetchOnly` keeps behind follow as detail lines. A project pinned by `spec.revision` is
|
|
369
|
+
fast-forwarded up to that commit, `main a1b2c3d..b2c3d4e (to the pinned revision)`, and then
|
|
370
|
+
stays there whatever its upstream brings. hldr, held by the rollback above, stays put:
|
|
371
|
+
|
|
372
|
+
```console
|
|
373
|
+
$ slipway sync hldr -n personal
|
|
374
|
+
project/hldr unchanged
|
|
375
|
+
held at e001395 by spec.revision
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
A pin the repository lacks is skipped as `RevisionNotFound`, a HEAD past the pin as
|
|
379
|
+
`PastRevision`, and a pin its upstream does not hold, such as a commit on another branch or a
|
|
380
|
+
fork, as `OffUpstream`: a manifest can hold a project back but never send it where its upstream
|
|
381
|
+
has not been.
|
|
382
|
+
|
|
383
|
+
Up to `parallel` projects fetch at once, while fast-forwards run one at a time; each result prints
|
|
384
|
+
in the order the projects are listed, and a count of the results closes the run on stderr. Each
|
|
385
|
+
fast-forward leaves `slipway sync: Fast-forward` in the branch's reflog. The exit status is 1
|
|
386
|
+
when a fetch or a fast-forward was denied or failed, once every line has printed; a skipped
|
|
387
|
+
project never changes it, so a dirty tree does not fail the run. With `--dry-run`, sync
|
|
388
|
+
plans from the last fetch and warns about the projects no fetch has reached.
|
|
389
|
+
|
|
390
|
+
### Rolling back
|
|
391
|
+
|
|
392
|
+
Slipway undoes its own moves. Each fast-forward of `sync` runs with `GIT_REFLOG_ACTION` set to
|
|
393
|
+
`slipway sync` and each move of `rollout undo` with `slipway rollout undo`, so the branch's reflog
|
|
394
|
+
records them and slipway keeps no history of its own. `slipway rollout history NAME` lists them as
|
|
395
|
+
revisions, oldest first: every commit slipway moved the checked-out branch to, and the commit the
|
|
396
|
+
branch stood at before such a move.
|
|
397
|
+
|
|
398
|
+
```console
|
|
399
|
+
$ slipway rollout history hldr -n personal
|
|
400
|
+
REVISION COMMIT DATE CHANGE-CAUSE PINNED
|
|
401
|
+
1 e001395 2026-09-27T23:15:35Z <none> false
|
|
402
|
+
2 4c5d6e7 2026-09-29T07:15:35Z sync: fast-forward 3 commits false
|
|
403
|
+
3 e001395 2026-09-29T07:15:35Z rollout undo to revision 1 true
|
|
404
|
+
|
|
405
|
+
$ slipway rollout unpin hldr -n personal
|
|
406
|
+
project/hldr unpinned
|
|
407
|
+
|
|
408
|
+
$ slipway sync hldr -n personal
|
|
409
|
+
project/hldr fast-forwarded
|
|
410
|
+
main e001395..4c5d6e7 (3 commits); undo with 'slipway rollout undo project/hldr -n personal'
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
CHANGE-CAUSE names the move and PINNED marks the revision `spec.revision` holds. `history`
|
|
414
|
+
fetches and writes nothing; a project without history prints
|
|
415
|
+
`No rollout history found for project/NAME.` on stderr and exits with 0.
|
|
416
|
+
|
|
417
|
+
`slipway rollout undo NAME` moves the checked-out branch to the revision before the current one,
|
|
418
|
+
or to the one `--to-revision=N` names, and then writes that commit to `spec.revision`, so `sync`
|
|
419
|
+
holds the project there. The manifest is written only after git moved the branch. A move back runs
|
|
420
|
+
`git reset --keep`, the one form of reset slipway ever runs, and only when the upstream holds every
|
|
421
|
+
commit the move drops; a move forward, which undoes an undo, runs `git merge --ff-only`. Untracked
|
|
422
|
+
files and unstaged changes to files the move leaves alone are kept. A move back needs an index
|
|
423
|
+
without staged changes, because `reset --keep` resets every index entry.
|
|
424
|
+
|
|
425
|
+
| Result | Meaning |
|
|
426
|
+
| --- | --- |
|
|
427
|
+
| `rolled back` | The branch moved, or it already stood at the revision and only `spec.revision` changed. The detail names the commits it crossed and the command that lets `sync` follow the upstream again. |
|
|
428
|
+
| `unchanged` | The branch already stood at the revision and `spec.revision` already held it. |
|
|
429
|
+
| `skipped (Reason)` | Nothing moved and nothing was written. git could not read the repository (`Missing`, `NotARepo`, `Unsafe`, `Unknown`); the branch cannot move (`Conflicted`, `Detached`, `Unborn`, `NoUpstream`, `Gone`, `InProgress`); the history has no such revision (`NoHistory`, `NoPrevious`, `UnknownRevision`) or the repository no such commit (`RevisionNotFound`); a move back would drop commits the upstream lacks (`LocalCommits`) or staged changes (`Dirty`), or the revision is off the branch's history (`Diverged`); a move forward needs a tree without staged or unstaged changes (`Dirty`) and a revision on the upstream (`OffUpstream`); or git refused the move (`WouldLoseChanges`, `WouldOverwrite` for untracked or ignored files in the way, `Busy`, `NotFastForward` for a branch that moved since the check). |
|
|
430
|
+
| `denied (AuthRequired)` | A partial clone had to fetch the files the move writes and the remote asked for a password, a passphrase or a host key, as in `fetch`. |
|
|
431
|
+
| `failed (Reason)` | The move ran past `networkTimeout` (`Timeout`) or git failed for another reason (`Unknown`), and `spec.revision` was not written, though a move stopped at the deadline keeps the files git had already written, as in `sync`; or the branch moved but `spec.revision` could not be written (`NotPinned`), and the detail names the `--to-revision` command that writes it without moving the branch again. |
|
|
432
|
+
|
|
433
|
+
The exit status is 1 when the project was skipped, denied or failed.
|
|
434
|
+
|
|
435
|
+
`slipway rollout unpin NAME...` removes `spec.revision` and prints `unpinned`, or `not pinned`
|
|
436
|
+
when there was none; the next `sync` fast-forwards onto the upstream as usual.
|
|
437
|
+
`slipway rollout pause NAME...` sets `spec.paused` and `slipway rollout resume NAME...` removes
|
|
438
|
+
it, printing `paused` or `already paused` and `resumed` or `not paused`. None of the three runs
|
|
439
|
+
git. A paused project can still be rolled back: pausing keeps only `fetch` and `sync` away.
|
|
440
|
+
|
|
441
|
+
The history is the local reflog, so it lasts as long as git keeps it (`gc.reflogExpire`, 90 days
|
|
442
|
+
by default; slipway never runs `git gc`) and is empty when `core.logAllRefUpdates` is off. The pin
|
|
443
|
+
lives in the manifest and outlasts the reflog. Rollout moves commits only.
|
|
444
|
+
|
|
445
|
+
### Periodic fetch
|
|
446
|
+
|
|
447
|
+
`fetch` moves no branch and touches no working tree, so it can run on a timer that keeps
|
|
448
|
+
FETCHED current, and with it every STATUS word that compares a branch with its upstream. `sync`
|
|
449
|
+
is not meant for a timer: it moves checked-out branches, and a branch should not move under an
|
|
450
|
+
open editor or a half-done change unless you ask. A systemd user timer that fetches every
|
|
451
|
+
project once an hour:
|
|
452
|
+
|
|
453
|
+
```ini
|
|
454
|
+
# ~/.config/systemd/user/slipway-fetch.service
|
|
455
|
+
[Unit]
|
|
456
|
+
Description=Fetch every project in the slipway registry
|
|
457
|
+
|
|
458
|
+
[Service]
|
|
459
|
+
Type=oneshot
|
|
460
|
+
ExecStart=/path/to/slipway fetch -A
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
```ini
|
|
464
|
+
# ~/.config/systemd/user/slipway-fetch.timer
|
|
465
|
+
[Unit]
|
|
466
|
+
Description=Fetch every project in the slipway registry hourly
|
|
467
|
+
|
|
468
|
+
[Timer]
|
|
469
|
+
OnCalendar=hourly
|
|
470
|
+
RandomizedDelaySec=5m
|
|
471
|
+
Persistent=true
|
|
472
|
+
|
|
473
|
+
[Install]
|
|
474
|
+
WantedBy=timers.target
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
```sh
|
|
478
|
+
systemctl --user daemon-reload
|
|
479
|
+
systemctl --user enable --now slipway-fetch.timer
|
|
480
|
+
journalctl --user -u slipway-fetch.service
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
`ExecStart` takes an absolute path: replace `/path/to/slipway` with what `command -v slipway`
|
|
484
|
+
prints. The service runs with the environment of the systemd user manager, which
|
|
485
|
+
`systemctl --user show-environment` prints, not with your shell's: git has to be on its `PATH`,
|
|
486
|
+
and a `SLIPWAY_*` variable exported in your shell profile does not reach it, so put settings in
|
|
487
|
+
the config file or in `Environment=` lines of the service. `Persistent=true` runs a fetch the
|
|
488
|
+
timer missed while the machine was off as soon as the timer starts again. `journalctl` shows
|
|
489
|
+
the result lines of each run. A run in which a project was denied or failed exits with status 1,
|
|
490
|
+
and `systemctl --user status slipway-fetch.service` reports it as failed; a skipped project does
|
|
491
|
+
not fail the run.
|
|
492
|
+
|
|
493
|
+
The ssh client finds your agent through `SSH_AUTH_SOCK`, which the user manager has only when
|
|
494
|
+
your session imported it. When `show-environment` does not list it, add
|
|
495
|
+
`Environment=SSH_AUTH_SOCK=...` with the agent's socket to the service, run
|
|
496
|
+
`systemctl --user import-environment SSH_AUTH_SOCK` from a shell that has it, or name the agent
|
|
497
|
+
with `IdentityAgent` in `~/.ssh/config`, as the setup of the 1Password SSH agent does.
|
|
498
|
+
|
|
499
|
+
An agent that asks before it signs, as the 1Password one does while it is locked or before it
|
|
500
|
+
has approved the program asking, shows its prompt when the timer runs, whether or not you are
|
|
501
|
+
there to answer. Slipway keeps git and ssh from prompting, but the agent's own dialog is outside
|
|
502
|
+
ssh: the fetch waits until `networkTimeout` (60 seconds by default) ends it and every process it
|
|
503
|
+
started, and reports `failed (Timeout)`; a dismissed prompt reports `denied (AuthRequired)`. The
|
|
504
|
+
other projects fetch either way. `Environment=SLIPWAY_NETWORK_TIMEOUT=20` in the service shortens
|
|
505
|
+
that wait for the timer's runs alone.
|
|
506
|
+
|
|
507
|
+
### A daily routine
|
|
508
|
+
|
|
509
|
+
With the timer running, the first look of the day needs no network:
|
|
510
|
+
|
|
511
|
+
```sh
|
|
512
|
+
slipway get projects -A --field-selector status.state!=Clean
|
|
513
|
+
slipway diff -A
|
|
514
|
+
slipway sync -A
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
`get` lists the projects that need attention, with STATUS as of the timer's last fetch, whose
|
|
518
|
+
age FETCHED shows. `diff` says which of them sync will fast-forward, what blocks the others and
|
|
519
|
+
the git command that resolves each blocker, still without contacting a remote. `sync` fetches
|
|
520
|
+
once more, fast-forwards each clean branch that is behind and reports what it leaves alone with
|
|
521
|
+
the reason. To move only some projects, name them (`slipway sync hldr augur -n personal`) or
|
|
522
|
+
select them with `-l`. `slipway get projects -A --field-selector status.lastFetch=never` lists
|
|
523
|
+
the projects no fetch has reached, including those whose last fetch was denied or failed.
|
|
524
|
+
A fast-forward you did not want is undone with the `slipway rollout undo` command sync prints
|
|
525
|
+
under it, as [Rolling back](#rolling-back) describes.
|
|
526
|
+
|
|
527
|
+
## Output formats
|
|
528
|
+
|
|
529
|
+
`-o table` is the default. `-o wide` adds PATH, HEAD, LAST-COMMIT and DRIFT to projects and
|
|
530
|
+
DESCRIPTION to groups. `-o json` and `-o yaml` print the manifest plus a `status` section, as
|
|
531
|
+
one object when a single name is given and as a `kind: List` otherwise. `-o name` prints
|
|
532
|
+
`project/hldr` lines. `--no-headers` drops the header row and `--show-labels` appends a LABELS
|
|
533
|
+
column with `lang=rust` style pairs.
|
|
534
|
+
|
|
535
|
+
```console
|
|
536
|
+
$ slipway get projects -n personal -o wide
|
|
537
|
+
NAME BRANCH STATUS FETCHED AGE PATH HEAD LAST-COMMIT DRIFT
|
|
538
|
+
augur main Dirty <never> 1s ~/dev/augur 8f9cdbb 11h NoUpstream
|
|
539
|
+
hldr main Clean 0s 1s ~/dev/hldr 4c5d6e7 120m <none>
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
AGE is the time since the resource was registered and LAST-COMMIT the age of the checked-out
|
|
543
|
+
commit, in kubectl's units (`3s`, `4m12s`, `11h`, `2y319d`). FETCHED is the time since the
|
|
544
|
+
repository was last fetched, by slipway or by git itself and from any of its worktrees. It reads
|
|
545
|
+
`<never>` when git answered and no fetch is on record, which includes a last fetch that failed,
|
|
546
|
+
and `<none>` when git could not read the repository at all (`Missing`, `NotARepo`, `Unsafe`,
|
|
547
|
+
`Unknown`), like every other cell that comes from git. BRANCH reads `(detached)` on a detached HEAD.
|
|
548
|
+
DRIFT lists the [drift](#drift) words.
|
|
549
|
+
|
|
550
|
+
`-o json` prints the same object as `-o yaml`. A project's `status` holds `branch`, `head`,
|
|
551
|
+
`upstream`, `ahead`, `behind`, the counts `staged`, `unstaged`, `untracked`, `conflicted` and
|
|
552
|
+
`stashes`, the STATUS word as `state`, `lastFetch`, the [drift](#drift) items as `drift` and the
|
|
553
|
+
checked-out commit as `lastCommit`:
|
|
554
|
+
|
|
555
|
+
```console
|
|
556
|
+
$ slipway get project augur -n personal -o yaml
|
|
557
|
+
kind: Project
|
|
558
|
+
metadata:
|
|
559
|
+
name: augur
|
|
560
|
+
group: personal
|
|
561
|
+
labels:
|
|
562
|
+
lang: bash
|
|
563
|
+
creationTimestamp: '2026-09-29T07:15:35Z'
|
|
564
|
+
spec:
|
|
565
|
+
path: "~/dev/augur"
|
|
566
|
+
status:
|
|
567
|
+
branch: main
|
|
568
|
+
head: 8f9cdbb
|
|
569
|
+
staged: 0
|
|
570
|
+
unstaged: 1
|
|
571
|
+
untracked: 1
|
|
572
|
+
conflicted: 0
|
|
573
|
+
stashes: 0
|
|
574
|
+
state: Dirty
|
|
575
|
+
drift:
|
|
576
|
+
- type: NoUpstream
|
|
577
|
+
message: main tracks no upstream; sync fast-forwards only a tracking branch
|
|
578
|
+
blocker: true
|
|
579
|
+
lastCommit:
|
|
580
|
+
hash: 8f9cdbbc5ff8982322347d8e6a76ea3ab8821b51
|
|
581
|
+
author: Highlander
|
|
582
|
+
email: contact@hvpaiva.dev
|
|
583
|
+
date: '2026-09-28T20:15:35Z'
|
|
584
|
+
subject: 'refactor: split history reader'
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
A field git could not answer is left out: augur has no `upstream`, `ahead`, `behind` or
|
|
588
|
+
`lastFetch`, and a project git could not read, such as a Missing one, keeps only `state` and
|
|
589
|
+
`drift`. A group's status holds `projects`, its project count.
|
|
590
|
+
|
|
591
|
+
## Status words
|
|
592
|
+
|
|
593
|
+
STATUS is one word per project, chosen in this order of precedence:
|
|
594
|
+
|
|
595
|
+
| STATUS | Meaning |
|
|
596
|
+
| --- | --- |
|
|
597
|
+
| `Missing` | The registered path is relative, or is not a directory on this machine. |
|
|
598
|
+
| `NotARepo` | The directory exists but no repository contains it. |
|
|
599
|
+
| `Unsafe` | git refused the repository because another user owns it (`safe.directory`); `describe`, `diff` and `fetch` print the git command that trusts it. |
|
|
600
|
+
| `Conflicted` | The working tree has unmerged paths. |
|
|
601
|
+
| `Detached` | HEAD points at a commit rather than a branch. |
|
|
602
|
+
| `Unborn` | The branch has no commits yet. |
|
|
603
|
+
| `Dirty` | Staged, modified or untracked files are present. |
|
|
604
|
+
| `Gone` | An upstream is configured but its remote-tracking ref is gone, as of the last `fetch --prune` (or a fetch with `fetch.prune` set). |
|
|
605
|
+
| `Diverged` | The branch is both ahead of and behind its upstream, as of the last fetch (FETCHED). |
|
|
606
|
+
| `Ahead` | Commits not yet pushed to the upstream, as of the last fetch (FETCHED). |
|
|
607
|
+
| `Behind` | Commits on the upstream not yet pulled, as of the last fetch (FETCHED). |
|
|
608
|
+
| `Clean` | Nothing to do. |
|
|
609
|
+
| `Unknown` | git could not answer: it is not installed, it did not finish within 10 seconds, or it failed for a reason slipway does not classify. Each distinct reason is printed once on stderr per run. |
|
|
610
|
+
|
|
611
|
+
`get`, `describe` and `diff` never contact a remote, so the words that compare a branch with its
|
|
612
|
+
upstream are as fresh as the last fetch. [Fetching](#fetching) refreshes them.
|
|
613
|
+
|
|
614
|
+
## Selectors
|
|
615
|
+
|
|
616
|
+
`-l EXPR` (`--selector`) filters by labels with kubectl's grammar. Equality:
|
|
617
|
+
|
|
618
|
+
```console
|
|
619
|
+
$ slipway get projects -A -l lang=rust
|
|
620
|
+
GROUP NAME BRANCH STATUS FETCHED AGE
|
|
621
|
+
personal hldr main Clean 0s 1s
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Set-based:
|
|
625
|
+
|
|
626
|
+
```console
|
|
627
|
+
$ slipway get projects -A -l 'lang in (rust,bash)'
|
|
628
|
+
GROUP NAME BRANCH STATUS FETCHED AGE
|
|
629
|
+
personal augur main Dirty <never> 1s
|
|
630
|
+
personal hldr main Clean 0s 1s
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
`key!=value`, `key notin (a,b)`, `key` (exists) and `!key` (does not exist) work as well, and
|
|
634
|
+
comma-separated terms must all hold.
|
|
635
|
+
|
|
636
|
+
`--field-selector EXPR` filters `get` and `describe` on the fields of the object `-o json`
|
|
637
|
+
prints, with kubectl's field grammar: `path=value` (or `path==value`) and `path!=value`,
|
|
638
|
+
comma-separated, all of which must hold. Projects support `metadata.name`, `metadata.group`,
|
|
639
|
+
`spec.path`, `status.state`, `status.branch` and `status.lastFetch`; groups support
|
|
640
|
+
`metadata.name`.
|
|
641
|
+
|
|
642
|
+
```console
|
|
643
|
+
$ slipway get projects -A --field-selector status.state!=Clean
|
|
644
|
+
GROUP NAME BRANCH STATUS FETCHED AGE
|
|
645
|
+
default notes main Diverged 0s 1s
|
|
646
|
+
personal augur main Dirty <never> 1s
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
`status.lastFetch=never` selects the projects no fetch has reached. The `--field-selector` help
|
|
650
|
+
and slipway-get(1) give the matching rules. Neither kind of selector, nor `-A`, can be combined
|
|
651
|
+
with explicit names.
|
|
652
|
+
|
|
653
|
+
## Manifests
|
|
654
|
+
|
|
655
|
+
Every registration is a manifest. `slipway get project hldr -n personal -o yaml` prints it
|
|
656
|
+
followed by `status`; without the status, a Project and a Group look like this:
|
|
657
|
+
|
|
658
|
+
```yaml
|
|
659
|
+
kind: Project
|
|
660
|
+
metadata:
|
|
661
|
+
name: hldr
|
|
662
|
+
group: personal
|
|
663
|
+
labels:
|
|
664
|
+
lang: rust
|
|
665
|
+
creationTimestamp: '2026-09-29T07:15:35Z'
|
|
666
|
+
spec:
|
|
667
|
+
path: "~/dev/hldr"
|
|
668
|
+
description: Site and CLI for hvpaiva.dev
|
|
669
|
+
remote: git@github.com:hvpaiva/hldr.git
|
|
670
|
+
branch: main
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
```yaml
|
|
674
|
+
kind: Group
|
|
675
|
+
metadata:
|
|
676
|
+
name: personal
|
|
677
|
+
labels: {}
|
|
678
|
+
creationTimestamp: '2026-09-29T07:15:35Z'
|
|
679
|
+
spec:
|
|
680
|
+
description: Personal projects
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Names follow the RFC 1123 label rule (lowercase letters, digits and dashes, at most 63
|
|
684
|
+
characters) and labels follow the Kubernetes rules. `metadata.group` defaults to the current
|
|
685
|
+
group and `creationTimestamp` is set on creation; written by hand, it must be a quoted string.
|
|
686
|
+
Unknown fields are errors; `slipway explain` lists the known ones
|
|
687
|
+
([Discovering resources](#discovering-resources)).
|
|
688
|
+
|
|
689
|
+
A project's spec says where its repository is and declares the state it is expected to be in.
|
|
690
|
+
Only `spec.path` is required, and a field at its default is not written:
|
|
691
|
+
|
|
692
|
+
| Field | Meaning | Default |
|
|
693
|
+
| --- | --- | --- |
|
|
694
|
+
| `spec.path` | The directory of the repository: an absolute path, or one starting with `~/`, which is expanded against `HOME` when used so that the manifest means the same on every machine. A relative path is reported as `Missing`. Must not be empty. | required |
|
|
695
|
+
| `spec.description` | What the project is, in free text. | none |
|
|
696
|
+
| `spec.remote` | The URL the `origin` remote is expected to have; `diff` reports another one as `Remote` drift, and no command changes a remote. A password, or any user name over http and https, where it often carries a token, is refused; use a credential helper. Must be `scheme://host/path` with scheme `ssh`, `https`, `http`, `git` or `file`, or `[user@]host:path`, where the host is letters, digits, dots and dashes starting with a letter or digit; at most 2048 characters, without whitespace or control characters. | none |
|
|
697
|
+
| `spec.branch` | The branch expected to be checked out; `diff` reports another one as `Branch` drift, and no command switches branches. Must be letters, digits, ".", "_", "/" and "-", starting with a letter or digit, at most 255 characters, with no "..", no "//", no component that starts with "." or ends with ".lock", no trailing "/" or ".", and not `HEAD`. | none |
|
|
698
|
+
| `spec.revision` | The commit the project is held at, named in full because an abbreviation can become ambiguous. `sync` fast-forwards the branch up to it instead of the upstream, never past it and never back to it. `rollout undo` writes it and `rollout unpin` removes it. Must be a full object name, 40 or 64 lowercase hexadecimal characters. | none |
|
|
699
|
+
| `spec.syncPolicy` | What `sync` may do to the repository: `FastForward` lets it fast-forward the checked-out branch, and `FetchOnly` lets it fetch only. Must be `FastForward` or `FetchOnly`. | `FastForward` |
|
|
700
|
+
| `spec.paused` | When `true`, `fetch` and `sync` leave the project alone: they report it as `paused` and run no git command there. `rollout pause` sets it and `rollout resume` removes it. | `false` |
|
|
701
|
+
|
|
702
|
+
A `~` path is stored as written. `create` resolves any other relative `--path`, `.` included,
|
|
703
|
+
against the current directory and stores it absolute. The shell expands an unquoted `~` before
|
|
704
|
+
slipway sees it, so quote it, as the examples here do, to keep the manifest portable.
|
|
705
|
+
|
|
706
|
+
`fetch` and `sync` leave a project with `spec.paused: true` alone, and `sync` follows
|
|
707
|
+
`spec.syncPolicy` and `spec.revision`. STATUS does not take any of the fields into account; the
|
|
708
|
+
repository is compared with them as [drift](#drift). The fields are checked whenever a manifest
|
|
709
|
+
is read, and a value that breaks its rule is refused with that rule, so nothing that could reach
|
|
710
|
+
git as an option or carry a control character is accepted. A file in the registry that cannot
|
|
711
|
+
be read is reported with a `warning:` line and left out of listings.
|
|
712
|
+
|
|
713
|
+
`slipway apply -f FILE` reads every YAML document in the file, `-f DIR` reads every `*.yaml`
|
|
714
|
+
and `*.yml` file in the directory sorted by name (without descending), and `-f -` reads stdin.
|
|
715
|
+
`-f` may be repeated. A document of kind `List` stands for each manifest under its `items`, in
|
|
716
|
+
order, and one that fails is named by its position (`FILE:3`). Each document prints
|
|
717
|
+
`project/hldr created`, `configured` or `unchanged`; problems are collected and printed as
|
|
718
|
+
`error: FILE[:N]: ...` after the successes, with exit status 1.
|
|
719
|
+
|
|
720
|
+
### Drift
|
|
721
|
+
|
|
722
|
+
Drift is where a project's repository differs from its manifest, and what keeps sync from
|
|
723
|
+
fast-forwarding it. `-o wide` lists the words in the DRIFT column, `-o json` and `-o yaml` list
|
|
724
|
+
the items in `status.drift` (each with `type`, `message` and `blocker`), and `describe` ends
|
|
725
|
+
with a Drift block of one line per item. Nothing is fetched: the repository is read as it is on
|
|
726
|
+
disk, so Behind is as of the last fetch (FETCHED), and `slipway fetch` refreshes it.
|
|
727
|
+
|
|
728
|
+
| Drift | Reported when |
|
|
729
|
+
| --- | --- |
|
|
730
|
+
| `Missing` | The registered path is relative or is not a directory. For a path that is not a directory, a project with `spec.remote` shows the `git clone` command that recreates it. |
|
|
731
|
+
| `Remote` | origin is absent or differs from `spec.remote`. Sync never changes a remote. |
|
|
732
|
+
| `Branch` | HEAD is detached or on another branch than `spec.branch`. Sync never switches branches. |
|
|
733
|
+
| `Revision` | HEAD is not the commit `spec.revision` pins. The pin replaces the upstream, so a pinned project is never Behind; under `FastForward` sync will fast-forward a branch behind the pin to it unless a blocker stops it, and never moves a branch back. |
|
|
734
|
+
| `Behind` | The checked-out branch is behind its upstream. Under `FastForward` sync will fast-forward it unless a blocker stops it; under `FetchOnly`, or while `spec.paused` is true, it is only reported. |
|
|
735
|
+
|
|
736
|
+
A blocker comes after the drift and says why the checked-out branch cannot be fast-forwarded,
|
|
737
|
+
onto its upstream or, for a project pinned by `spec.revision`, onto the pin. The first three mean
|
|
738
|
+
git could not read the repository and apply to every project; the others apply only under
|
|
739
|
+
`FastForward` to a project that is not paused, and `RevisionNotFound`, `PastRevision` and
|
|
740
|
+
`OffUpstream` only to a pinned one. Behind means behind the upstream or, when pinned, behind the
|
|
741
|
+
pin:
|
|
742
|
+
|
|
743
|
+
| Blocker | Meaning |
|
|
744
|
+
| --- | --- |
|
|
745
|
+
| `NotARepo` | The directory exists but holds no repository. |
|
|
746
|
+
| `Unsafe` | git refused the repository because another user owns it (`safe.directory`); the git command that trusts it follows. |
|
|
747
|
+
| `Unknown` | git could not answer; the reason is also printed once on stderr. |
|
|
748
|
+
| `Detached` | HEAD points at a commit rather than a branch. |
|
|
749
|
+
| `Unborn` | The branch has no commits yet. |
|
|
750
|
+
| `Gone` | The upstream is configured but its ref no longer exists. |
|
|
751
|
+
| `NoUpstream` | The branch tracks no upstream. |
|
|
752
|
+
| `RevisionNotFound` | The repository has no commit by the name `spec.revision` pins. |
|
|
753
|
+
| `PastRevision` | HEAD is past the pinned commit or on another line of history, so reaching the pin would move the branch back. |
|
|
754
|
+
| `OffUpstream` | The upstream does not hold the pinned commit, which may be on another branch or a fork; sync moves a branch only along its upstream. |
|
|
755
|
+
| `Conflicted` | The branch is behind and the working tree has unmerged paths. |
|
|
756
|
+
| `Dirty` | The branch is behind and has staged or unstaged changes; untracked files do not block. |
|
|
757
|
+
| `Diverged` | The branch is behind and has commits of its own. |
|
|
758
|
+
| `InProgress` | The branch would be fast-forwarded, but a merge, rebase, cherry-pick, revert, bisect or `git am` is in progress. |
|
|
759
|
+
|
|
760
|
+
## Discovering resources
|
|
761
|
+
|
|
762
|
+
`slipway api-resources` lists the resource types, as `kubectl api-resources` lists the ones a
|
|
763
|
+
cluster serves:
|
|
764
|
+
|
|
765
|
+
```console
|
|
766
|
+
$ slipway api-resources
|
|
767
|
+
NAME SHORTNAMES KIND GROUPED
|
|
768
|
+
groups <none> Group false
|
|
769
|
+
projects proj Project true
|
|
770
|
+
|
|
771
|
+
$ slipway api-resources -o wide
|
|
772
|
+
NAME SHORTNAMES KIND GROUPED VERBS
|
|
773
|
+
groups <none> Group false apply,create,delete,describe,edit,explain,get,label
|
|
774
|
+
projects proj Project true apply,create,delete,describe,diff,edit,explain,fetch,get,label,rollout,sync
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
A command accepts a type by its NAME, its singular or one of its SHORTNAMES, and KIND is what a
|
|
778
|
+
manifest of the type declares in `kind`. GROUPED plays the part of kubectl's NAMESPACED: it says
|
|
779
|
+
whether the resources of a type live in a group, so that `-n` and `-A` scope them. VERBS lists
|
|
780
|
+
the commands that act on the type, and `-o name` prints the names alone.
|
|
781
|
+
|
|
782
|
+
`slipway explain` prints the fields of a manifest in the terminal, in the layout of
|
|
783
|
+
`kubectl explain`: the type of each field in angle brackets, `-required-` when a manifest must
|
|
784
|
+
have it, and a description with the rule its value follows and its default. A type word prints
|
|
785
|
+
the fields at the top of its manifest:
|
|
786
|
+
|
|
787
|
+
```console
|
|
788
|
+
$ slipway explain group
|
|
789
|
+
KIND: Group
|
|
790
|
+
|
|
791
|
+
DESCRIPTION:
|
|
792
|
+
A namespace that holds projects, the way a Kubernetes namespace holds pods.
|
|
793
|
+
Deleting a group removes the registrations of its projects.
|
|
794
|
+
|
|
795
|
+
FIELDS:
|
|
796
|
+
kind <string> -required-
|
|
797
|
+
The kind of resource the manifest describes. Must be Group.
|
|
798
|
+
|
|
799
|
+
metadata <Object> -required-
|
|
800
|
+
Identifies the group: its name, its labels, and when it was created.
|
|
801
|
+
|
|
802
|
+
spec <Object>
|
|
803
|
+
What the group is for.
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
A dotted path after the type word names a field, and `--recursive` prints the whole tree of names
|
|
807
|
+
and types instead of the descriptions:
|
|
808
|
+
|
|
809
|
+
```console
|
|
810
|
+
$ slipway explain project.spec.syncPolicy
|
|
811
|
+
KIND: Project
|
|
812
|
+
|
|
813
|
+
FIELD: syncPolicy <string>
|
|
814
|
+
|
|
815
|
+
DESCRIPTION:
|
|
816
|
+
What sync may do to the repository: FastForward lets it fast-forward the
|
|
817
|
+
checked-out branch, and FetchOnly lets it fetch only. Must be FastForward or
|
|
818
|
+
FetchOnly. Defaults to FastForward.
|
|
819
|
+
|
|
820
|
+
$ slipway explain project --recursive
|
|
821
|
+
KIND: Project
|
|
822
|
+
|
|
823
|
+
DESCRIPTION:
|
|
824
|
+
A registered git repository: where it is on this machine and where it is
|
|
825
|
+
expected to be, as the remote, the branch and a commit to hold it at.
|
|
826
|
+
|
|
827
|
+
FIELDS:
|
|
828
|
+
kind <string> -required-
|
|
829
|
+
metadata <Object> -required-
|
|
830
|
+
name <string> -required-
|
|
831
|
+
group <string>
|
|
832
|
+
labels <map[string]string>
|
|
833
|
+
creationTimestamp <string>
|
|
834
|
+
spec <Object> -required-
|
|
835
|
+
path <string> -required-
|
|
836
|
+
description <string>
|
|
837
|
+
remote <string>
|
|
838
|
+
branch <string>
|
|
839
|
+
revision <string>
|
|
840
|
+
syncPolicy <string>
|
|
841
|
+
paused <boolean>
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Type words take their aliases and any case, as everywhere else, while field names are exact, as
|
|
845
|
+
in a manifest. A field that does not exist is a usage error, `error: field "x" does not exist`,
|
|
846
|
+
with exit status 2. [Shell completion](#shell-completion) offers the type words and then, after
|
|
847
|
+
each dot, the fields one level down as whole paths: `slipway explain project.spec.<TAB>` lists
|
|
848
|
+
`project.spec.path` through `project.spec.paused`, and no space follows a field that has fields
|
|
849
|
+
under it, so you can go on with a dot.
|
|
850
|
+
|
|
851
|
+
## Configuration
|
|
852
|
+
|
|
853
|
+
Settings are resolved in this order: command-line flags, then `SLIPWAY_*` environment
|
|
854
|
+
variables, then the config file, then the built-in defaults. The file lives at
|
|
855
|
+
`$XDG_CONFIG_HOME/slipway/config.yaml` (`~/.config/slipway/config.yaml`) unless `--config PATH`
|
|
856
|
+
or `SLIPWAY_CONFIG` names another one. The default file is optional, and so is every key in it;
|
|
857
|
+
a file named by `--config` or `SLIPWAY_CONFIG` must exist.
|
|
858
|
+
|
|
859
|
+
```yaml
|
|
860
|
+
# ~/.config/slipway/config.yaml
|
|
861
|
+
color: auto # auto, always or never
|
|
862
|
+
theme: light # dark or light
|
|
863
|
+
editor: code --wait
|
|
864
|
+
group: personal # used when -n is not given
|
|
865
|
+
networkTimeout: 60 # seconds before a git network command is killed, from 1 to 86400
|
|
866
|
+
parallel: 4 # git network commands at once, from 1 to 16
|
|
867
|
+
protocols: [ssh, https] # transports git may use; add file for local mirrors
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
The defaults are `color: auto`, `theme: dark`, `group: default`, `networkTimeout: 60`,
|
|
871
|
+
`parallel: 4` and `protocols: [ssh, https]`; `editor` has none, so `edit` falls back to
|
|
872
|
+
`VISUAL`, `EDITOR` and `vi`. `slipway config view` prints the values in effect with the file
|
|
873
|
+
path as a comment on the first line, and `slipway config path` prints the path alone. An unknown
|
|
874
|
+
key or a wrong value is an error naming the file. `protocols` refuses `ext` and `fd` even when
|
|
875
|
+
listed: `ext` runs a command named in the URL, and `fd` reads from file descriptors.
|
|
876
|
+
|
|
877
|
+
| Variable | Effect |
|
|
878
|
+
| --- | --- |
|
|
879
|
+
| `SLIPWAY_CONFIG` | Path of the configuration file; `--config` outranks it. |
|
|
880
|
+
| `SLIPWAY_DATA_HOME` | Directory holding the registry. |
|
|
881
|
+
| `SLIPWAY_COLOR` | `auto`, `always` or `never`; `--color` outranks it. |
|
|
882
|
+
| `SLIPWAY_THEME` | `dark` or `light`. |
|
|
883
|
+
| `SLIPWAY_EDITOR` | Editor for `edit`; outranks the config key, `VISUAL` and `EDITOR`. |
|
|
884
|
+
| `SLIPWAY_GROUP` | Group used when `-n` is not given. |
|
|
885
|
+
| `SLIPWAY_NETWORK_TIMEOUT` | Seconds a git network command may run before it is killed, from 1 to 86400. |
|
|
886
|
+
| `SLIPWAY_PARALLEL` | How many git network commands run at once, from 1 to 16. |
|
|
887
|
+
| `SLIPWAY_PROTOCOLS` | Transports git may use in network commands, separated by colons: `ssh:https`. |
|
|
888
|
+
| `SLIPWAY_DEBUG` | When non-empty, unexpected errors also print their class and backtrace. |
|
|
889
|
+
| `NO_COLOR` | When non-empty, disables color in `auto` mode. |
|
|
890
|
+
| `FORCE_COLOR` | When non-empty, enables color in `auto` mode even on a pipe. |
|
|
891
|
+
| `CLICOLOR_FORCE` | Same as `FORCE_COLOR`. |
|
|
892
|
+
| `VISUAL` | Editor for `edit` when `SLIPWAY_EDITOR` and the `editor` config key are unset. |
|
|
893
|
+
| `EDITOR` | Editor for `edit` when `VISUAL` is unset as well. |
|
|
894
|
+
| `XDG_CONFIG_HOME` | Base of the configuration directory (default `~/.config`). |
|
|
895
|
+
| `XDG_DATA_HOME` | Base of the data directory (default `~/.local/share`). |
|
|
896
|
+
| `TERM` | `dumb` turns color off in `auto` mode. |
|
|
897
|
+
| `MANPAGER` | When non-empty, `slipway man` leaves the pager palette alone. |
|
|
898
|
+
| `MANROFFOPT` | Same as `MANPAGER`. |
|
|
899
|
+
| `LESS_TERMCAP_md` | Same as `MANPAGER`. |
|
|
900
|
+
| `GROFF_NO_SGR` | Same as `MANPAGER`. |
|
|
901
|
+
|
|
902
|
+
The registry is a directory of plain YAML files under `$SLIPWAY_DATA_HOME`, by default
|
|
903
|
+
`$XDG_DATA_HOME/slipway` (`~/.local/share/slipway`):
|
|
904
|
+
|
|
905
|
+
```
|
|
906
|
+
groups/<group>.yaml
|
|
907
|
+
projects/<group>/<name>.yaml
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
Each file is the manifest shown above and nothing else is stored, so the directory can be
|
|
911
|
+
backed up, versioned or synced between machines. To rebuild a registry from a copy, apply
|
|
912
|
+
`groups/` first and then each `projects/<group>/` directory:
|
|
913
|
+
`slipway apply -f copy/groups -f copy/projects/personal`.
|
|
914
|
+
|
|
915
|
+
## Colors
|
|
916
|
+
|
|
917
|
+
`--color[=auto|always|never]` decides per stream; a bare `--color` means `always`. In `auto`
|
|
918
|
+
mode (the default) a non-empty `NO_COLOR` turns color off, then a non-empty `FORCE_COLOR` or
|
|
919
|
+
`CLICOLOR_FORCE` turns it on, then `TERM=dumb` turns it off, and otherwise stdout and stderr
|
|
920
|
+
are colored only when they are terminals. `SLIPWAY_COLOR` or the `color` key set the mode
|
|
921
|
+
without a flag. Two themes exist, `dark` (default) and `light`, selected with `SLIPWAY_THEME`
|
|
922
|
+
or the `theme` key. The palette follows kubecolor's defaults: bold headers, cycling column
|
|
923
|
+
colors, green for `Clean`, yellow for the states that ask for a git action (`Detached` through
|
|
924
|
+
`Behind` in the [STATUS table](#status-words)), red for `Missing`, `NotARepo`, `Unsafe` and
|
|
925
|
+
`Conflicted` and for the `-required-` mark of `explain`, and grey for `Unknown`.
|
|
926
|
+
|
|
927
|
+
## Shell completion
|
|
928
|
+
|
|
929
|
+
`slipway completion SHELL` prints the script; the header of each script says where it goes.
|
|
930
|
+
|
|
931
|
+
```sh
|
|
932
|
+
# bash: load it in the current session, or add the line to ~/.bashrc
|
|
933
|
+
eval "$(slipway completion bash)"
|
|
934
|
+
# bash: install it for bash-completion to load on demand
|
|
935
|
+
mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions"
|
|
936
|
+
slipway completion bash > "${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions/slipway"
|
|
937
|
+
|
|
938
|
+
# zsh: put _slipway in a directory on your fpath (fpath+=~/.zfunc before compinit), or source it directly
|
|
939
|
+
mkdir -p ~/.zfunc
|
|
940
|
+
slipway completion zsh > ~/.zfunc/_slipway
|
|
941
|
+
source <(slipway completion zsh)
|
|
942
|
+
|
|
943
|
+
# fish
|
|
944
|
+
mkdir -p ~/.config/fish/completions
|
|
945
|
+
slipway completion fish > ~/.config/fish/completions/slipway.fish
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Completion covers commands, flags, flag values (`-o js<TAB>` gives `json`), resource types, the
|
|
949
|
+
names of your projects and groups and the field paths of `explain`, asked from the program
|
|
950
|
+
itself each time you press TAB.
|
|
951
|
+
|
|
952
|
+
## Manual pages
|
|
953
|
+
|
|
954
|
+
The pages are installed with slipway. `slipway man` opens `slipway(1)` and `slipway man get`
|
|
955
|
+
opens `slipway-get(1)` with `man(1)`, colored like the help page when color is on; if any of
|
|
956
|
+
`MANPAGER`, `MANROFFOPT`, `LESS_TERMCAP_md` or `GROFF_NO_SGR` is non-empty, your pager
|
|
957
|
+
settings win and slipway passes nothing of its own.
|
|
958
|
+
|
|
959
|
+
`slipway man --install` copies the pages to `${XDG_DATA_HOME:-~/.local/share}/man/man1` (a
|
|
960
|
+
relative `XDG_DATA_HOME` is ignored), and `slipway man --install=DIR` copies them into DIR,
|
|
961
|
+
which must be named `man1`: `man` finds section 1 pages in the `man1` directory under each
|
|
962
|
+
`MANPATH` entry, so any other DIR is refused with exit status 2. DIR is optional, so it must
|
|
963
|
+
follow the `=`; `slipway man --install DIR` is refused as well. When the pages land in
|
|
964
|
+
`~/.local/share/man/man1`, man-db looks there on its own as long as `~/.local/bin` is on
|
|
965
|
+
`PATH`, and the command ends by saying so. Anywhere else, such as under a custom
|
|
966
|
+
`XDG_DATA_HOME`, it ends with the `MANPATH` line for the parent directory, which makes `man`
|
|
967
|
+
find the pages. To read them without installing:
|
|
968
|
+
|
|
969
|
+
```sh
|
|
970
|
+
export MANPATH="$(dirname "$(slipway man --path)"):$MANPATH"
|
|
971
|
+
man slipway-get
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
`slipway help COMMAND` and `slipway COMMAND --help` print the same content in the terminal.
|
|
975
|
+
|
|
976
|
+
## Exit status
|
|
977
|
+
|
|
978
|
+
| Status | Meaning |
|
|
979
|
+
| --- | --- |
|
|
980
|
+
| `0` | Success. |
|
|
981
|
+
| `1` | Runtime error, such as a missing resource or an unreadable manifest. |
|
|
982
|
+
| `2` | Usage error: unknown command, unknown flag or invalid argument. |
|
|
983
|
+
| `130` | Interrupted by SIGINT. |
|
|
984
|
+
|
|
985
|
+
`slipway diff` also exits with 3 when a project differs from its manifest. The man pages of
|
|
986
|
+
`diff`, `sync` and `rollout undo` list their statuses.
|
|
987
|
+
|
|
988
|
+
## Development
|
|
989
|
+
|
|
990
|
+
```sh
|
|
991
|
+
git clone https://github.com/hvpaiva/slipway.git
|
|
992
|
+
cd slipway
|
|
993
|
+
bin/setup
|
|
994
|
+
bundle exec rake # tests and RuboCop
|
|
995
|
+
bundle exec rake check # what CI runs
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
`bin/setup` installs the development dependencies and reports the tools it found.
|
|
999
|
+
`bin/sandbox` opens a shell whose `slipway` is the checkout, against an empty registry and
|
|
1000
|
+
configuration in a temporary directory, to [try a change by
|
|
1001
|
+
hand](CONTRIBUTING.md#trying-a-change-by-hand).
|
|
1002
|
+
|
|
1003
|
+
## Other documents
|
|
1004
|
+
|
|
1005
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) for the rake tasks, the conventions, the generated files and the release process.
|
|
1006
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md) for a map of the code.
|
|
1007
|
+
- [CHANGELOG.md](CHANGELOG.md) for what changed in each version.
|
|
1008
|
+
- [SECURITY.md](SECURITY.md) for what slipway promises about the repositories it touches and how to report a vulnerability.
|
|
1009
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for the rules of the community.
|
|
1010
|
+
|
|
1011
|
+
## License
|
|
1012
|
+
|
|
1013
|
+
MIT. See [LICENSE.txt](LICENSE.txt).
|