@tertium/gith 0.0.0-stage → 0.1.1

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.
Files changed (4) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +205 -2
  3. package/bin/gith.js +1791 -0
  4. package/package.json +52 -4
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Vitalii Balabanov
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,206 @@
1
- # Temporary Holding Version
1
+ # gith
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ```
4
+ o
5
+ /
6
+ o----o----o
7
+ \ | /
8
+ \ | /
9
+ (o_o) gith v0.1.0
10
+ /|_|\ git happens.
11
+ d b
12
+ ```
13
+
14
+ Git helper: the branch flow from [git-convention](https://github.com/Tertiumnon/git-convention) (goal-named work branches, `release/*` and `hotfix/*` containers, merges that sync both sides with origin), plus everyday git commands: status, switch, update, undo, save, squash, stash, remove, list, tree, sync, clean, reset, open, doctor and stat. Any command takes `--dry-run` to show the git commands it would run. No dependencies beyond git and [Bun](https://bun.com).
15
+
16
+ > **Very early state (0.1.0).** Commands and flags may change without notice, and the tool has only been run against throwaway test repositories. Check the result with `git log` and `git status` before you push.
17
+
18
+ ## Install
19
+
20
+ Needs [Node.js](https://nodejs.org) 18 or newer and git on the PATH.
21
+
22
+ ```bash
23
+ npm install -g @tertium/gith
24
+ gith --version
25
+ ```
26
+
27
+ From a clone of this repository, build the CLI and link it: `bun install`, `bun run build`, `npm link`.
28
+
29
+ ## Usage
30
+
31
+ ```bash
32
+ # Start and finish work
33
+ gith create -b login-redirect # work branch from develop, named by its goal; stays on the current branch
34
+ gith create -r 1.2.0 # release/1.2.0 from develop
35
+ gith create -h 1.2.1 # hotfix/1.2.1 from main
36
+ gith create --release # release, version = minor after the latest tag
37
+ gith create --hotfix # hotfix, version = patch after the latest tag
38
+ gith switch -b login-redirect # switch to the branch, creating it from develop if it is missing
39
+ gith switch -commit 3f2a1b9 # detach HEAD at that commit, to look at old code
40
+ gith update # merge origin/develop into this work branch
41
+ gith merge # this branch into develop
42
+ gith merge -r 1.2.0 # this branch into release/1.2.0
43
+ gith merge -h 1.2.1 # this branch into hotfix/1.2.1
44
+ gith merge --current-release # this branch into the newest release/* (local or origin)
45
+ gith merge --current-hotfix # this branch into the newest hotfix/* (local or origin)
46
+ gith release --patch # npm version patch, published through main
47
+ gith release --minor # npm version minor, from develop into main
48
+ gith release --major # npm version major, from develop into main
49
+
50
+ # Fix history
51
+ gith undo # undo the last commit, keep its changes staged (not if pushed)
52
+ gith save [-m "msg"] # fold staged changes into the last commit, or make a new commit with them if the branch has none yet (asks for a message); pushes if origin differs
53
+ gith squash -limit 3 [-m "msg"] # combine the last 3 commits into one (keeps the oldest message)
54
+
55
+ # Save and reset work
56
+ gith stash -n wip # stash changes, including untracked files, under a name
57
+ gith unstash # bring back the latest stash
58
+ gith reset # discard uncommitted changes, then clean
59
+ gith clean # remove untracked files and directories
60
+
61
+ # Clean up branches (every removal shows what it will remove first)
62
+ gith remove -b login-redirect # delete the branch here and on origin (rm is an alias)
63
+ gith remove -b login-redirect --local # delete only the local branch
64
+ gith remove -b login-redirect --remote # delete only the origin branch
65
+ gith remove --branch # list branch pairs (here and on origin) and pick which to delete
66
+ gith remove --branch -l 10 # only the first 10 in the list
67
+ gith remove --branch --local # list local branches and pick
68
+ gith remove --branch --remote # list origin branches and pick
69
+ gith remove --obsolete # remove branches untouched for 90 days, on both sides, after Y/n
70
+ gith remove --obsolete --local # the same, here only
71
+ gith remove --obsolete -days 30 # untouched for 30 days instead
72
+ gith remove --detached # pick local branches that have no origin copy
73
+ gith remove --detached --all # remove all of them: lists them, then asks Y/n
74
+
75
+ # Look at the repo
76
+ gith status # branch vs origin, and staged, unstaged and untracked counts
77
+ gith list --commits # the last 5 commits on this branch
78
+ gith list --commits --graph # the same, as a branch graph
79
+ gith list --commits -l 10 # the last 10 (-limit 10 works too)
80
+ gith list --tags # the newest 5 tags with a LOCAL and ORIGIN column
81
+ gith list --tags -l 20 # the newest 20
82
+ gith list --stashes # the newest 5 stashes (-l for more)
83
+ gith show --pockets # the same, with Gorlum: my precious pockets!
84
+ gith find -commit "login" -limit 10 # commits whose message mentions login (search is the same; default 5)
85
+ gith search -file "*api.ts" -limit 3 # commits that changed a matching file, with the files listed
86
+ gith find -change "TODO" # commits that added or removed the text in their diff
87
+ gith find -file "*api.ts" -commit fix # filters combine: fixes that touched api.ts
88
+ gith tree # working trees of this repo: path, branch, HEAD (current marked *)
89
+ gith stat # fun facts about this branch: biggest commit, busiest day, streaks...
90
+ gith stat --last-week -limit 50 # the same for last week, looking at no more than 50 commits
91
+
92
+ # Remote and health
93
+ gith sync # fetch all remotes and tags, prune stale refs
94
+ gith open # open origin in the browser
95
+ gith doctor # check the repo is ready for the branch flow
96
+
97
+ # Help
98
+ gith help # full usage
99
+ gith --version # the version, with the mascot
100
+
101
+ # Any command: add --verbose to see the git commands it runs, including read-only ones.
102
+ gith list --tags --verbose
103
+ ```
104
+
105
+ ## Flags
106
+
107
+ One rule applies to every command: **a single dash always takes a value, and a double dash never does.** So `-limit 10` is right, and `--limit 10` and `-remote` are errors that name the right spelling.
108
+
109
+ | Flag | Kind | Used by |
110
+ | --- | --- | --- |
111
+ | `-b <name>`, `-branch <name>` | value | `create`, `switch`, `remove` |
112
+ | `--branch` | alone: open the picker | `remove` |
113
+ | `-r <version>`, `-release <version>` | value | `create`, `merge` |
114
+ | `--release` | alone: version from the latest tag | `create` |
115
+ | `--current-release`, `--current-hotfix` | switch: merge into the newest release or hotfix | `merge` |
116
+ | `-h <version>`, `-hotfix <version>` | value | `create`, `merge` |
117
+ | `--hotfix` | alone: version from the latest tag | `create` |
118
+ | `-limit <n>` | value | `squash`: how many commits to combine |
119
+ | `-m <msg>`, `-message <msg>` | value | `squash` |
120
+ | `-n <name>`, `-name <name>` | value | `stash` |
121
+ | `-l <n>`, `-limit <n>` | value, positive whole number | every listing: `list`, `tree`, the picker |
122
+ | `--patch`, `--minor`, `--major` | switch | `release` |
123
+ | `--overall`, `--current-year`, `--current-month`, `--current-week`, `--last-year`, `--last-month`, `--last-week` | switch, one at a time | `stat` |
124
+ | `--local`, `--remote` | switch | `remove` (both sides when neither is given) |
125
+ | `--obsolete`, `--detached` | switch | `remove` |
126
+ | `-days <n>` | value | `remove --obsolete` |
127
+ | `--tags`, `--commits`, `--graph`, `--stashes`, `--pockets` | switch | `list` (also `show`) |
128
+ | `--verbose` | switch | every command |
129
+ | `--dry-run` | switch | every command |
130
+
131
+ Commands that choose between flags (for example `create -b`, `-r` and `-h`, or `list --tags` and `--commits`) accept only one of them. `-limit` caps listings and `stat`, and on `squash` it sets how many commits to combine. `-message` only applies to `squash`.
132
+
133
+ `--verbose` and `--dry-run` can go anywhere on the line:
134
+ - `--dry-run` prints each git command that would change something, and runs none of them. Read-only checks still run, so a refusal (for example, "HEAD is already on origin") appears in a dry run too. `clean` and `reset` also list the files they would remove or discard.
135
+ - `--verbose` also prints the read-only git calls, such as `rev-parse` and `ls-remote`. Action commands always print the git commands they run, such as `> git push -u origin login`. Commands run git directly, without a shell, so branch names are never parsed by it.
136
+
137
+ ## Git commands
138
+
139
+ - **`switch -b`** switches to a branch. If the branch exists neither locally nor on origin, it is created first, the same way as `create -b`, and then checked out.
140
+ - **`switch -commit <sha>`** detaches HEAD at that commit so you can look at old code. It refuses a commit that does not exist, and it refuses while tracked files have changes (untracked files do not block it). It lists the files that block it, and points to `gith stash`. On a detached HEAD, `gith switch -b <name>` creates the branch at the current commit, so commits made there stay on it. It does not create or switch branches for other commits, so commits made on a detached HEAD are easy to lose: name a branch before you leave.
141
+ - **`remove -b <branch>`** removes the branch on both sides: the local branch first (with `git branch -d`, so git refuses to delete an unmerged branch), then the origin branch. A side that does not exist is skipped, so a branch that lives only here is still removed. A name that exists in neither place is an error. `--local` touches only this clone; `--remote` touches only origin. Containers (`develop`, `main`, `release/*`, `hotfix/*`) are refused.
142
+ - **`remove --branch`** with no name lists the branches and asks which to remove. By default it lists branch pairs, meaning branches that exist both here and on origin. `--local` lists local branches and `--remote` lists origin branches. Each choice is removed on the same sides as above. `-limit <n>` shows only the first `n` (all by default), and the picker numbers only those. Answer with numbers or ranges, such as `1,3-4`; an empty answer cancels. The list needs a terminal, and that is checked before anything is listed. The checked-out branch and containers are never listed. Choosing items is the confirmation, so nothing else is asked. If a delete fails (for example, an unmerged branch), the removal stops there and earlier picks stay deleted.
143
+ - **`status`** prints the branch and how far it is ahead of or behind its origin branch, then counts staged, unstaged, untracked and conflicting files. It changes nothing.
144
+ - **`update`** fetches `origin/develop` and merges it into the current work branch. It needs a clean working tree and refuses containers. If the merge conflicts, it aborts the merge and says so. It does not push.
145
+ - **`undo`** runs `git reset --soft HEAD~1`: the last commit is removed and its changes stay staged. It refuses when HEAD is already on origin, because undoing a pushed commit needs a force-push.
146
+ - **`save`** saves the staged changes. If the branch has commits of its own (commits origin's `develop`, or `main`, lacks), it folds them into the last commit with `git commit --amend --no-edit`, keeping its message. `-m` replaces the message. If the branch has no commits of its own, it never touches the base commit: it creates a new commit, asking for its message (an empty answer is refused, and `-m` skips the question). It refuses with nothing staged. It pushes only when origin already has this branch and its copy differs from the new commit: a plain `git push origin <branch>` when the commit only moves forward, or, when a pushed commit was rewritten, a `Save it and force-push ...? [Y/n]` question (Enter means yes; a no changes nothing) followed by `git push --force-with-lease origin <branch>`, which refuses if origin moved since your last fetch. If origin has no such branch, the save stays local. The questions need a terminal, so a script cannot force-push or commit by accident.
147
+ - **`stash`** saves changes with `git stash push --include-untracked`. `-n` names the stash. It refuses when there is nothing to save.
148
+ - **`find`** (also `search`) lists the newest commits on this branch that match every filter given:
149
+ - `-commit <text>` matches the commit message, ignoring case, and the text is literal.
150
+ - `-file <pattern>` matches the paths changed by the commit, using git's pattern rules (`*api.ts` matches `src/api.ts`). Each commit lists the matching files it changed.
151
+ - `-change <text>` matches commits whose diff adds or removes the text (`git log -S`). With `--regex`, it matches a regular expression against the diff (`git log -G`).
152
+ - `-limit <n>` sets how many to show (default 5). The filters combine with AND.
153
+ - **`show`** is a synonym for `list`: `gith show --tags` and `gith list --tags` do the same. `--pockets` is a fun name for `--stashes`, and it prints a Gorlum figure above the list.
154
+ - **`list --stashes`** shows the stashes, newest first, with their date and description. Like the other listings, it shows 5 by default, and `-limit` changes that.
155
+ - **`squash -limit <n>`** combines the last `n` commits on the current work branch into one, using `reset --soft` and a single commit. Without `-m` the result keeps the oldest of the combined commits, its message and author. It refuses when `n` is below 2, when `n` would reach the first commit, when a merge is among them, when any of them is already on origin, on a container, or with a dirty working tree.
156
+ - **`stat`** reads the history of the current branch and prints a table of facts: commit count, top author, the biggest commit (lines added and removed), average size, the most changed file, the busiest day, weekday and hour, the longest streak of days with a commit, night owl and weekend commits, and the conventional commit types. The period is one switch: `--overall` (the default), `--current-year`, `--current-month`, `--current-week` (weeks start on Monday), or the matching `--last-` switch for the previous year, month or week. `-limit <n>` looks only at the last `n` commits. Stat changes nothing.
157
+ - **`unstash`** runs `git stash pop`. If it conflicts, git keeps the stash in the list.
158
+ - **`remove --obsolete`** fetches and prunes origin, then lists the branches untouched for `-days` (default 90): no commit newer than the cutoff on either side. Each line shows the sides and the last commit date, and the command asks `Remove these N branches here and on origin? [Y/n]`. Enter means yes. `--local` looks at and removes only local branches. `-limit` shows only the first `n`, and the prompt covers only those. `main`, `develop`, `release/*` and `hotfix/*` are never removed, and neither is the checked-out branch. Each branch is deleted locally first (`git branch -d`), so a branch that git does not see as merged stops the run before its origin copy is touched.
159
+ - **`remove --detached --all`** fetches and prunes origin, lists every local branch with no origin copy, and asks `Remove these N branches? [Y/n]` once. Nothing is deleted without the yes, and `--all` can't be combined with `-limit`.
160
+ - **`remove --detached`** fetches and prunes origin, then lists local branches that have no origin copy and lets you pick from them, the same as `remove --branch --local` but for branches with no origin copy only.
161
+ - **Every removal shows what it will remove** before deleting anything, including the direct `remove -b` form. The picker and the prompt both show it first.
162
+ - **`open`** opens origin in the browser, turning an `ssh` or `https` remote into a web address. GitHub and GitLab style addresses work.
163
+ - **`doctor`** checks git, a repository, origin, its reachability, `main` and `develop` on origin, and a clean working tree. It exits 1 if a required check fails, and only warns about the tree.
164
+ - **`sync`** runs `git fetch --all --tags --prune`.
165
+ - **`tree`** lists the working trees of the repository (`git worktree list`): each path, its branch or `(detached)`, and its short HEAD. The current one is marked `*`, and bare, locked or prunable trees are noted. `-limit <n>` caps the rows; all are shown by default.
166
+ - **`list --tags`** shows every tag once, with yes/no columns for local and origin, so you can see which tags are unpushed or missing locally. Version tags are listed newest first, and only the first 5 are shown by default. `-limit <n>` (or `-l <n>`) changes that and adds a note when rows are left out.
167
+ - **`list --commits`** shows the newest commits on the current branch, one per line: short hash, date, author and subject. With `--graph` it draws the branch graph instead. It shows 5 by default; `-l <n>` (or `-limit <n>`) changes that and must be a positive whole number. `-limit` only applies to listings. `gith list` with no flag prints usage.
168
+ - **`clean`** runs `git clean -fd`. It permanently removes untracked files and directories, but keeps ignored files.
169
+ - **`reset`** runs `git reset --hard` (discards staged and unstaged edits on this branch), then `git clean -fd`. It cannot be undone, so commit or stash first. It does not move the branch to origin.
170
+
171
+ ## What it does
172
+
173
+ - **`create -b`** creates the branch from `origin/develop`, pushes it with upstream tracking, and leaves the current branch checked out. It does not touch the working tree, so it works with uncommitted changes.
174
+ - **`create -r` / `create -h`** creates a container from its base (`develop` for release, `main` for hotfix), refusing names that already exist on origin. Like `create -b`, it does not switch.
175
+ - **With `--release` or `--hotfix` (no value)**, the version is worked out from the highest `vX.Y.Z` tag across local tags and origin: `-r` takes the next minor (`1.2.1` → `1.3.0`), `-h` takes the next patch (`1.2.1` → `1.2.2`). With no tags yet, the first release is `0.1.0` and the first hotfix `0.0.1`. The version is printed before the branch is created.
176
+ - **`merge --current-release`** and **`--current-hotfix`** pick the newest `release/*` or `hotfix/*` branch by version (numbers compare as numbers, so `1.10.0` beats `1.9.0`), checking both local branches and origin, and merge into it. The target must exist on origin, because the merge pushes it; a newest branch that only exists here stops the command with a message.
177
+ - **`merge`** pushes the work branch, merges the target into it first (so conflicts happen on the work branch and are aborted cleanly), then merges it into the target with `--no-ff`, pushes, and checks that both branches match origin.
178
+
179
+ Containers (`develop`, `main`, `release/*`, `hotfix/*`) can't be merged from, and only `develop`, `release/*` and `hotfix/*` can be merged into.
180
+
181
+ ## `release`
182
+
183
+ Run as `gith release --patch`, `--minor` or `--major`.
184
+
185
+ - **`patch`** runs from `main`: bumps the version, pushes with tags, then rebases `develop` onto `main`. The push back to `develop` uses `--force-with-lease`, so it refuses to overwrite a `develop` that moved on origin.
186
+ - **`minor` and `major`** run from `develop`: bump the version, push with tags, merge `develop` into `main`, then bring `main` back to `develop`.
187
+
188
+ Both need a clean working tree and a `package.json` with a version, because they call `npm version`.
189
+
190
+ ## Errors
191
+
192
+ Each failure exits with code 1 and a message. Conflicts leave you on the work branch with the merge aborted, so you can resolve and retry.
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ bun install
198
+ bun run test # unit tests
199
+ bun run lint # biome check
200
+ bun run lint:fix # biome check --write
201
+ bun run typecheck # tsc --noEmit
202
+ ```
203
+
204
+ ## Publishing
205
+
206
+ The package ships one built file, `bin/gith.js`, which runs on Node. `npm pack --dry-run` lists what would be published. `prepublishOnly` runs lint, typecheck, the tests and the build before any publish. `.github/workflows/publish.yml` runs the same checks, then `npm publish --access public`, whenever a `v*` tag is pushed. It uses npm trusted publishing (OIDC), so no npm token is stored in the repo. Bump the version with `gith release` or `npm version`, which creates the tag.