@tertium/gith 0.3.1 → 0.3.2

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 CHANGED
@@ -1,15 +1,21 @@
1
- ISC License
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2026 Vitalii Balabanov
4
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.
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,4 +1,4 @@
1
- # gith
1
+ # @tertium/gith
2
2
 
3
3
  ```
4
4
  o
@@ -101,6 +101,7 @@ gith stat --last-week -limit 50 # the same for last week, looking at no more
101
101
 
102
102
  # Remote and health
103
103
  gith sync # fetch all remotes and tags, prune stale refs
104
+ gith sync develop:develop # two-way sync of one branch with origin
104
105
  gith open # open origin in the browser
105
106
  gith doctor # check the repo is ready for the branch flow
106
107
  gith changelog --dry-run # preview the CHANGELOG.md entry for package.json's version
@@ -177,6 +178,7 @@ Commands that choose between flags (for example `create -b`, `-r` and `-h`, or `
177
178
  - **`open`** opens origin in the browser, turning an `ssh` or `https` remote into a web address. GitHub and GitLab style addresses work.
178
179
  - **`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.
179
180
  - **`sync`** runs `git fetch --all --tags --prune`.
181
+ - **`sync <branch>:<branch>`** (for example `sync develop:develop`) syncs one branch with origin in both directions. If origin is ahead, it fetches the branch like `git fetch origin develop:develop`; if local is ahead, it pushes it; if it is the same, it says so. A checked-out branch is fast-forwarded with `merge --ff-only`. Diverged branches stop with an error, and nothing is forced. A branch missing on one side is created there. `sync develop` alone works too.
180
182
  - **`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.
181
183
  - **`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.
182
184
  - **`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.
@@ -221,3 +223,7 @@ bun run typecheck # tsc --noEmit
221
223
  ## Publishing
222
224
 
223
225
  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.
226
+
227
+ ## Support
228
+
229
+ If this project is useful to you, you can support its development on [Ko-fi](https://ko-fi.com/tertiumnon).
package/bin/gith.js CHANGED
@@ -168,6 +168,14 @@ function capture(args) {
168
168
  function hasOnOrigin(branch) {
169
169
  return capture(`ls-remote --heads origin ${branch}`) !== "";
170
170
  }
171
+ function isAncestor(ancestor, descendant) {
172
+ try {
173
+ captureArgs(["merge-base", "--is-ancestor", ancestor, descendant]);
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
171
179
  function listTags() {
172
180
  const local = capture("tag --list").split(`
173
181
  `);
@@ -1294,6 +1302,39 @@ function sync(flags) {
1294
1302
  noFlags2(flags, "sync");
1295
1303
  gitArgs(["fetch", "--all", "--tags", "--prune"]);
1296
1304
  }
1305
+ function syncBranch(args) {
1306
+ const [spec = ""] = args;
1307
+ const [name = "", other = name] = spec.split(":");
1308
+ if (args.length !== 1 || other !== name || name === "")
1309
+ throw new Error("usage: gith sync <branch>:<branch>, the same name on both sides.");
1310
+ assertBranchName(name);
1311
+ const local = hasLocal(name);
1312
+ const remote = hasOnOrigin(name);
1313
+ if (!local && !remote)
1314
+ throw new Error(`'${name}' does not exist, locally or on origin.`);
1315
+ if (!remote) {
1316
+ gitArgs(["push", "origin", `${name}:${name}`]);
1317
+ return;
1318
+ }
1319
+ gitArgs(["fetch", "origin", name]);
1320
+ if (!local) {
1321
+ gitArgs(["fetch", "origin", `${name}:${name}`]);
1322
+ return;
1323
+ }
1324
+ const here = capture(`rev-parse refs/heads/${name}`);
1325
+ const there = capture(`rev-parse refs/remotes/origin/${name}`);
1326
+ if (here === there) {
1327
+ console.log(`'${name}' is already in sync with origin.`);
1328
+ } else if (isAncestor(there, here)) {
1329
+ gitArgs(["push", "origin", `${name}:${name}`]);
1330
+ } else if (!isAncestor(here, there)) {
1331
+ throw new Error(`'${name}' has diverged from origin/${name}; merge or rebase one side first.`);
1332
+ } else if (currentBranch2() === name) {
1333
+ gitArgs(["merge", "--ff-only", `origin/${name}`]);
1334
+ } else {
1335
+ gitArgs(["fetch", "origin", `${name}:${name}`]);
1336
+ }
1337
+ }
1297
1338
  async function reset(flags) {
1298
1339
  noFlags2(flags, "reset");
1299
1340
  const changes = captureArgs(["status", "--short"]).split(`
@@ -2019,6 +2060,7 @@ Look at the repo
2019
2060
 
2020
2061
  Remote and health
2021
2062
  gith sync fetch all remotes and tags, prune stale refs
2063
+ gith sync develop:develop two-way sync of one branch with origin: fetch it when behind, push it when ahead, stop when diverged
2022
2064
  gith open open origin in the browser
2023
2065
  gith doctor check the repo is ready for the branch flow (exit 1 on failures)
2024
2066
  gith changelog [--dry-run] add an entry for package.json's version to CHANGELOG.md (commits since the last tag)
@@ -2068,7 +2110,10 @@ try {
2068
2110
  await remove(parseFlags(rest));
2069
2111
  break;
2070
2112
  case "sync":
2071
- sync(parseFlags(rest));
2113
+ if (rest[0] !== undefined && !rest[0].startsWith("-"))
2114
+ syncBranch(rest);
2115
+ else
2116
+ sync(parseFlags(rest));
2072
2117
  break;
2073
2118
  case "clean":
2074
2119
  await clean(parseFlags(rest));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tertium/gith",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Git helper: branch flow with goal branches, release and hotfix containers, and everyday git commands.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "release:major": "node bin/gith.js release --major",
25
25
  "version": "bun run build && node bin/gith.js changelog"
26
26
  },
27
- "license": "ISC",
27
+ "license": "MIT",
28
28
  "author": "Vitalii Balabanov",
29
29
  "repository": {
30
30
  "type": "git",