git-chopstick-core 0.1.8 → 0.1.10
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/CHANGELOG.md +29 -1
- package/README.md +3 -2
- package/dist/git/remote.d.ts +6 -0
- package/dist/git/remote.js +10 -2
- package/dist/git/remote.js.map +1 -1
- package/dist/git/rev-parse.d.ts +20 -0
- package/dist/git/rev-parse.js +26 -0
- package/dist/git/rev-parse.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixture-helpers.ts +105 -0
- package/src/__tests__/fixtures/test-repo.bundle +0 -0
- package/src/__tests__/integration.test.ts +234 -47
- package/docs/skills/release.md +0 -82
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.10] — 2026-06-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`getRepositorySummary(path)`**: New helper combining `getRepositoryType` + `git rev-parse HEAD` + `getCurrentBranch` into one call. Returns `{ path, head, currentBranch? }` or `null` for bare/missing repos.
|
|
7
|
+
- **`getRemoteUrl(path, name)`**: Path-based helper using `git config --get remote.<name>.url`. `getRemoteURL(repository, name)` now delegates to it.
|
|
8
|
+
- **`spawnGit` progress example**: `examples/spawn-git-progress.ts` demonstrating how to wire `spawnGit` to `CheckoutProgressParser`, `FetchProgressParser`, `PushProgressParser`, `PullProgressParser`, and `CloneProgressParser`.
|
|
9
|
+
- **Integration tests**: 18 new tests covering `getRepositorySummary` (5), `getRemoteUrl` (3), `getRemotesFromPath` (2), `addRemote`/`removeRemote`/`setRemoteURL` (4), `merge` (2), `rebase` (1), and `stash` (1). Total: 31 tests.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **README**: Updated API reference with `getRepositorySummary` and `getRemoteUrl`/`getRemoteURL` exports.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [0.1.9] — 2026-06-13
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Test fixtures**: Created `src/__tests__/fixtures/test-repo.bundle` — a pre-built git bundle with 6 commits, 3 branches, and 1 tag for faster test setup. Added `src/__tests__/fixture-helpers.ts` with `setupFixtureRepo()` to clone the bundle in ~10ms instead of building a repo from scratch. Refactored integration tests to use the fixture.
|
|
20
|
+
- **Release skill moved**: Moved from `docs/skills/release.md` to `.agents/skills/release-npm.md` and renamed to `release-npm`.
|
|
21
|
+
- **Global default branch**: Set `git config --global init.defaultBranch main`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Fixture rebuild instructions**: Updated to use `git init -q` (relies on global `init.defaultBranch`).
|
|
25
|
+
- **`prepublishOnly` now runs tests**: Integration tests run during `npm publish` to catch regressions before they ship.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
3
29
|
## [0.1.8] — 2026-06-13
|
|
4
30
|
|
|
5
31
|
### Added
|
|
@@ -129,6 +155,8 @@
|
|
|
129
155
|
|
|
130
156
|
---
|
|
131
157
|
|
|
158
|
+
[0.1.10]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.10
|
|
159
|
+
[0.1.9]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.9
|
|
132
160
|
[0.1.8]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.8
|
|
133
161
|
[0.1.7]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.7
|
|
134
162
|
[0.1.6]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.6
|
|
@@ -138,4 +166,4 @@
|
|
|
138
166
|
[0.1.2]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.2
|
|
139
167
|
[0.1.1]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.1
|
|
140
168
|
[0.1.0]: https://github.com/parkiyong/git-chopstick-core/releases/tag/v0.1.0
|
|
141
|
-
[Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.
|
|
169
|
+
[Unreleased]: https://github.com/parkiyong/git-chopstick-core/compare/v0.1.10...HEAD
|
package/README.md
CHANGED
|
@@ -228,12 +228,12 @@ try {
|
|
|
228
228
|
| `rebase` | `rebase`, `continueRebase`, `abortRebase`, `rebaseInteractive`, `getRebaseInternalState`, `getRebaseSnapshot` | Rebase operations |
|
|
229
229
|
| `reflog` | `getRecentBranches`, `getBranchCheckouts` | Reflog inspection |
|
|
230
230
|
| `refs` | `formatAsLocalRef`, `getSymbolicRef` | Ref manipulation |
|
|
231
|
-
| `remote` | `getRemotes`, `addRemote`, `removeRemote` | Remote management |
|
|
231
|
+
| `remote` | `getRemotes`, `addRemote`, `removeRemote`, `getRemoteURL`, `getRemoteUrl`, `getRemotesFromPath`, `setRemoteURL` | Remote management |
|
|
232
232
|
| `reorder` | `reorder` | Interactive rebase reordering |
|
|
233
233
|
| `reset` | `reset`, `resetPaths`, `unstageAll` | Reset operations |
|
|
234
234
|
| `revert` | `revertCommit` | Revert a commit |
|
|
235
235
|
| `rev-list` | `getAheadBehind`, `getBranchAheadBehind`, `revRange`, `revSymmetricDifference` | Commit range queries |
|
|
236
|
-
| `rev-parse` | `getRepositoryType`, `getCurrentBranch`, `getUpstreamRefForRef`, `getCurrentUpstreamRef` | Rev parsing / branch detection |
|
|
236
|
+
| `rev-parse` | `getRepositoryType`, `getCurrentBranch`, `getRepositorySummary`, `getUpstreamRefForRef`, `getCurrentUpstreamRef` | Rev parsing / branch detection |
|
|
237
237
|
| `rm` | `removeConflictedFile` | Remove files |
|
|
238
238
|
| `squash` | `squash` | Interactive rebase squashing |
|
|
239
239
|
| `stage` | `stageManualConflictResolution`, `stageResolvedConflictFiles` | Stage conflict resolutions |
|
|
@@ -341,6 +341,7 @@ npm pack --dry-run
|
|
|
341
341
|
npx tsx examples/get-status.ts /path/to/repo
|
|
342
342
|
npx tsx examples/branch-operations.ts /path/to/repo
|
|
343
343
|
npx tsx examples/create-commit.ts /path/to/repo
|
|
344
|
+
npx tsx examples/spawn-git-progress.ts /path/to/repo
|
|
344
345
|
```
|
|
345
346
|
|
|
346
347
|
## Status
|
package/dist/git/remote.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export declare function setRemoteURL(repository: Repository, name: string, url:
|
|
|
21
21
|
* Returns null if the remote could not be found
|
|
22
22
|
*/
|
|
23
23
|
export declare function getRemoteURL(repository: Repository, name: string): Promise<string | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Get the URL for a remote by name using a path string.
|
|
26
|
+
*
|
|
27
|
+
* Returns null if the remote could not be found.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getRemoteUrl(path: string, name: string): Promise<string | null>;
|
|
24
30
|
/**
|
|
25
31
|
* Update the HEAD ref of the remote, which is the default branch.
|
|
26
32
|
*
|
package/dist/git/remote.js
CHANGED
|
@@ -45,11 +45,19 @@ export async function setRemoteURL(repository, name, url) {
|
|
|
45
45
|
* Returns null if the remote could not be found
|
|
46
46
|
*/
|
|
47
47
|
export async function getRemoteURL(repository, name) {
|
|
48
|
-
|
|
48
|
+
return getRemoteUrl(repository.path, name);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the URL for a remote by name using a path string.
|
|
52
|
+
*
|
|
53
|
+
* Returns null if the remote could not be found.
|
|
54
|
+
*/
|
|
55
|
+
export async function getRemoteUrl(path, name) {
|
|
56
|
+
const result = await git(['config', '--get', `remote.${name}.url`], path, 'getRemoteUrl', { successExitCodes: new Set([0, 1]) });
|
|
49
57
|
if (result.exitCode !== 0) {
|
|
50
58
|
return null;
|
|
51
59
|
}
|
|
52
|
-
return result.stdout;
|
|
60
|
+
return result.stdout.trim();
|
|
53
61
|
}
|
|
54
62
|
/**
|
|
55
63
|
* Update the HEAD ref of the remote, which is the default branch.
|
package/dist/git/remote.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/git/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,UAAU,MAAM,aAAa,CAAA;AAEpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAsB;IAEtB,OAAO,0BAA0B,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;QAC7D,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;KACtD,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QACnD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAChE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CACnC,CAAA;AACH,CAAC;AACD,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAA;AAExE,2CAA2C;AAC3C,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAAsB,EACtB,IAAY,EACZ,GAAW;IAEX,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;IAErE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;AACtB,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY;IAEZ,MAAM,OAAO,GAAG;QACd,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;KACvC,CAAA;IAED,MAAM,GAAG,CACP,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAC1B,UAAU,CAAC,IAAI,EACf,cAAc,EACd,OAAO,CACR,CAAA;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY,EACZ,GAAW;IAEX,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAC5E,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,CAAC,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/git/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,UAAU,MAAM,aAAa,CAAA;AAEpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAsB;IAEtB,OAAO,0BAA0B,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;QAC7D,cAAc,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;KACtD,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QACnD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAChE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CACnC,CAAA;AACH,CAAC;AACD,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAA;AAExE,2CAA2C;AAC3C,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,UAAsB,EACtB,IAAY,EACZ,GAAW;IAEX,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;IAErE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;AACtB,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY;IAEZ,MAAM,OAAO,GAAG;QACd,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;KACvC,CAAA;IAED,MAAM,GAAG,CACP,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAC1B,UAAU,CAAC,IAAI,EACf,cAAc,EACd,OAAO,CACR,CAAA;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY,EACZ,GAAW;IAEX,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAC5E,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAsB,EACtB,IAAY;IAEZ,OAAO,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,IAAI,MAAM,CAAC,EACzC,IAAI,EACJ,cAAc,EACd,EAAE,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CACtC,CAAA;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAsB,EACtB,MAAe,EACf,gBAAyB;IAEzB,MAAM,OAAO,GAAG;QACd,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACtC,GAAG,EAAE,MAAM,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC;QAC5C,gBAAgB;KACjB,CAAA;IAED,MAAM,GAAG,CACP,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EACzC,UAAU,CAAC,IAAI,EACf,kBAAkB,EAClB,OAAO,CACR,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAsB,EACtB,MAAc;IAEd,MAAM,eAAe,GAAG,gBAAgB,MAAM,GAAG,CAAA;IACjD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,UAAU,EAAE,GAAG,eAAe,MAAM,CAAC,CAAA;IACxE,IACE,KAAK,IAAI,IAAI;QACb,KAAK,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM;QACrC,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,EACjC,CAAC;QACD,0DAA0D;QAC1D,2CAA2C;QAC3C,8BAA8B;QAC9B,OAAO,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,MAAM,CAAC,CAAA;IAChD,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
|
package/dist/git/rev-parse.d.ts
CHANGED
|
@@ -29,3 +29,23 @@ export declare const getCurrentUpstreamRemoteName: (path: string) => Promise<str
|
|
|
29
29
|
* or `undefined` if HEAD is detached.
|
|
30
30
|
*/
|
|
31
31
|
export declare function getCurrentBranch(path: string): Promise<string | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* Summary of a repository opened at a path.
|
|
34
|
+
*/
|
|
35
|
+
export interface RepositorySummary {
|
|
36
|
+
/** The resolved repository path. */
|
|
37
|
+
path: string;
|
|
38
|
+
/** The full SHA of HEAD. */
|
|
39
|
+
head: string;
|
|
40
|
+
/** The current branch name, or undefined if HEAD is detached. */
|
|
41
|
+
currentBranch?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get a summary of a repository at the given path in a single call.
|
|
45
|
+
*
|
|
46
|
+
* This replaces the common pattern of calling `getRepositoryType` +
|
|
47
|
+
* `git rev-parse HEAD` + `getCurrentBranch` separately.
|
|
48
|
+
*
|
|
49
|
+
* Returns `null` for missing, bare, or unsafe repositories.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getRepositorySummary(path: string): Promise<RepositorySummary | null>;
|
package/dist/git/rev-parse.js
CHANGED
|
@@ -75,4 +75,30 @@ export async function getCurrentBranch(path) {
|
|
|
75
75
|
}
|
|
76
76
|
return result.stdout.trim();
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Get a summary of a repository at the given path in a single call.
|
|
80
|
+
*
|
|
81
|
+
* This replaces the common pattern of calling `getRepositoryType` +
|
|
82
|
+
* `git rev-parse HEAD` + `getCurrentBranch` separately.
|
|
83
|
+
*
|
|
84
|
+
* Returns `null` for missing, bare, or unsafe repositories.
|
|
85
|
+
*/
|
|
86
|
+
export async function getRepositorySummary(path) {
|
|
87
|
+
const repoType = await getRepositoryType(path);
|
|
88
|
+
if (repoType.kind !== 'regular') {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const headResult = await git(['rev-parse', 'HEAD'], path, 'getRepositorySummary', {
|
|
92
|
+
successExitCodes: new Set([0, 128]),
|
|
93
|
+
});
|
|
94
|
+
if (headResult.exitCode !== 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const currentBranch = await getCurrentBranch(path);
|
|
98
|
+
return {
|
|
99
|
+
path: repoType.topLevelWorkingDirectory,
|
|
100
|
+
head: headResult.stdout.trim(),
|
|
101
|
+
currentBranch,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
78
104
|
//# sourceMappingURL=rev-parse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rev-parse.js","sourceRoot":"","sources":["../../src/git/rev-parse.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rev-parse.js","sourceRoot":"","sources":["../../src/git/rev-parse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* To rebuild the fixture bundle:
|
|
3
|
+
*
|
|
4
|
+
* cd /tmp && rm -rf fixture-builder && mkdir fixture-builder && cd fixture-builder
|
|
5
|
+
* git init -q && git config user.email f@t.t && git config user.name F
|
|
6
|
+
* echo '# Test Repo' > README.md && echo 'node_modules/' > .gitignore
|
|
7
|
+
* git add .gitignore README.md && git commit -m 'initial: add README and gitignore'
|
|
8
|
+
* echo 'hello' > hello.txt && mkdir -p src && echo 'console.log("hi")' > src/index.js
|
|
9
|
+
* git add hello.txt src/index.js && git commit -m 'feat: add hello.txt and src/index.js'
|
|
10
|
+
* echo 'world' > hello.txt && echo 'console.log("hello world")' > src/index.js
|
|
11
|
+
* git add -A && git commit -m 'feat: update hello and src/index.js'
|
|
12
|
+
* git checkout -b feature/one && echo 'feature one content' > feature-one.txt
|
|
13
|
+
* git add feature-one.txt && git commit -m 'feat: feature one'
|
|
14
|
+
* git checkout main && echo 'another change' > another.txt
|
|
15
|
+
* git add another.txt && git commit -m 'feat: add another.txt'
|
|
16
|
+
* git merge feature/one --no-ff --no-edit -m 'merge: feature/one into main'
|
|
17
|
+
* git checkout -b feature/two && echo 'feature two content' > feature-two.txt
|
|
18
|
+
* git add feature-two.txt && git commit -m 'feat: feature two'
|
|
19
|
+
* git checkout main && git tag -a v0.1.0 -m 'v0.1.0'
|
|
20
|
+
* git bundle create test-repo.bundle --all
|
|
21
|
+
* cp test-repo.bundle <this-dir>/fixtures/
|
|
22
|
+
*
|
|
23
|
+
* This creates a bundle with 6 commits on main, 3 branches, and 1 annotated tag.
|
|
24
|
+
*/
|
|
25
|
+
import { mkdtempSync } from 'fs'
|
|
26
|
+
import { execSync } from 'child_process'
|
|
27
|
+
import { tmpdir } from 'os'
|
|
28
|
+
import { dirname, join } from 'path'
|
|
29
|
+
import { fileURLToPath } from 'url'
|
|
30
|
+
import { Repository } from '../models/repository.js'
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Path to the pre-built fixture repo bundle.
|
|
36
|
+
*/
|
|
37
|
+
export const FIXTURE_BUNDLE_PATH = join(__dirname, 'fixtures', 'test-repo.bundle')
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clone the fixture bundle to a temp directory, configure git identity,
|
|
41
|
+
* create local branches for all remote-tracking branches, and return
|
|
42
|
+
* the path and a Repository object.
|
|
43
|
+
*
|
|
44
|
+
* Use this in `beforeAll` for faster test setup — avoids creating a repo
|
|
45
|
+
* from scratch for every test suite.
|
|
46
|
+
*/
|
|
47
|
+
export function setupFixtureRepo(): { repoPath: string; repo: Repository } {
|
|
48
|
+
const repoPath = mkdtempSync(join(tmpdir(), 'gcctest-fixture-'))
|
|
49
|
+
|
|
50
|
+
execSync(`git clone "${FIXTURE_BUNDLE_PATH}" "${repoPath}"`, {
|
|
51
|
+
stdio: 'pipe',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
execSync('git config user.email test@test.test', {
|
|
55
|
+
cwd: repoPath,
|
|
56
|
+
stdio: 'pipe',
|
|
57
|
+
})
|
|
58
|
+
execSync('git config user.name Test', {
|
|
59
|
+
cwd: repoPath,
|
|
60
|
+
stdio: 'pipe',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Create local branches for all remote-tracking branches (bundle clones
|
|
64
|
+
// only create a local branch for HEAD — everything else becomes origin/*)
|
|
65
|
+
const localBranches = execSync(
|
|
66
|
+
'git branch --format="%(refname:short)"',
|
|
67
|
+
{ cwd: repoPath, encoding: 'utf-8', stdio: 'pipe' }
|
|
68
|
+
)
|
|
69
|
+
.trim()
|
|
70
|
+
.split('\n')
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
|
|
73
|
+
const remoteBranches = execSync(
|
|
74
|
+
'git branch -r --format="%(refname:lstrip=3)"',
|
|
75
|
+
{ cwd: repoPath, encoding: 'utf-8', stdio: 'pipe' }
|
|
76
|
+
)
|
|
77
|
+
.trim()
|
|
78
|
+
.split('\n')
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.filter(b => b !== 'HEAD' && !localBranches.includes(b))
|
|
81
|
+
|
|
82
|
+
for (const branch of remoteBranches) {
|
|
83
|
+
execSync(`git branch --track "${branch}" "origin/${branch}"`, {
|
|
84
|
+
cwd: repoPath,
|
|
85
|
+
stdio: 'pipe',
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const repo = new Repository(repoPath)
|
|
90
|
+
return { repoPath, repo }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run a git command in the fixture repo.
|
|
95
|
+
*/
|
|
96
|
+
export function git(repoPath: string, args: string) {
|
|
97
|
+
execSync(`git ${args}`, { cwd: repoPath, stdio: 'pipe' })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clean up a fixture repo directory.
|
|
102
|
+
*/
|
|
103
|
+
export function cleanupFixtureRepo(repoPath: string) {
|
|
104
|
+
execSync(`rm -rf ${repoPath}`)
|
|
105
|
+
}
|
|
Binary file
|
|
@@ -6,32 +6,28 @@ import { join } from 'path'
|
|
|
6
6
|
import {
|
|
7
7
|
Repository, getStatus, getCommits, getBranches,
|
|
8
8
|
createCommit, createBranch, deleteLocalBranch, renameBranch,
|
|
9
|
-
getCurrentBranch, getRepositoryType,
|
|
9
|
+
getCurrentBranch, getRepositoryType, getRepositorySummary,
|
|
10
|
+
getRemoteUrl, getRemotesFromPath, getAllTags,
|
|
11
|
+
addRemote, removeRemote, setRemoteURL,
|
|
12
|
+
merge, MergeResult,
|
|
13
|
+
rebase, RebaseResult,
|
|
14
|
+
getStashes, createDesktopStashEntry, popStashEntry,
|
|
10
15
|
} from '../index.js'
|
|
16
|
+
import {
|
|
17
|
+
setupFixtureRepo, cleanupFixtureRepo, git,
|
|
18
|
+
} from './fixture-helpers.js'
|
|
11
19
|
|
|
12
20
|
let repoPath: string
|
|
13
21
|
let repo: Repository
|
|
14
22
|
|
|
15
|
-
function git(args: string) {
|
|
16
|
-
execSync(`git ${args}`, { cwd: repoPath, stdio: 'pipe' })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function writeFile(path: string, content: string) {
|
|
20
|
-
writeFileSync(join(repoPath, path), content)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
23
|
beforeAll(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
git('config user.name Test')
|
|
24
|
+
const fixture = setupFixtureRepo()
|
|
25
|
+
repoPath = fixture.repoPath
|
|
26
|
+
repo = fixture.repo
|
|
28
27
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
git('
|
|
32
|
-
git('commit -m "initial"')
|
|
33
|
-
|
|
34
|
-
repo = new Repository(repoPath)
|
|
28
|
+
// Set a remote URL so getRemoteUrl / getRemotesFromPath have a known value
|
|
29
|
+
// (the bundle clone already creates an origin remote pointing to the bundle file)
|
|
30
|
+
git(repoPath, 'remote set-url origin https://github.com/user/repo.git')
|
|
35
31
|
})
|
|
36
32
|
|
|
37
33
|
describe('getRepositoryType', () => {
|
|
@@ -52,16 +48,15 @@ describe('getRepositoryType', () => {
|
|
|
52
48
|
describe('getCurrentBranch', () => {
|
|
53
49
|
it('returns the current branch name', async () => {
|
|
54
50
|
const branch = await getCurrentBranch(repoPath)
|
|
55
|
-
expect(branch).
|
|
56
|
-
expect(typeof branch).toBe('string')
|
|
51
|
+
expect(branch).toBe('main')
|
|
57
52
|
})
|
|
58
53
|
|
|
59
54
|
it('returns undefined when HEAD is detached', async () => {
|
|
60
|
-
git('checkout --detach')
|
|
55
|
+
git(repoPath, 'checkout --detach')
|
|
61
56
|
const branch = await getCurrentBranch(repoPath)
|
|
62
57
|
expect(branch).toBeUndefined()
|
|
63
58
|
// Reset back to a branch for subsequent tests
|
|
64
|
-
git('checkout
|
|
59
|
+
git(repoPath, 'checkout main')
|
|
65
60
|
})
|
|
66
61
|
})
|
|
67
62
|
|
|
@@ -69,12 +64,11 @@ describe('getStatus', () => {
|
|
|
69
64
|
it('returns a status with branch info', async () => {
|
|
70
65
|
const status = await getStatus(repo)
|
|
71
66
|
expect(status).toBeTruthy()
|
|
72
|
-
expect(status!.currentBranch).
|
|
73
|
-
expect(typeof status!.currentBranch).toBe('string')
|
|
67
|
+
expect(status!.currentBranch).toBe('main')
|
|
74
68
|
})
|
|
75
69
|
|
|
76
70
|
it('detects untracked files', async () => {
|
|
77
|
-
|
|
71
|
+
writeFileSync(join(repoPath, 'untracked.txt'), 'hello')
|
|
78
72
|
const status = await getStatus(repo)
|
|
79
73
|
expect(status!.workingDirectory.files.length).toBeGreaterThanOrEqual(1)
|
|
80
74
|
const untracked = status!.workingDirectory.files.find(
|
|
@@ -92,46 +86,57 @@ describe('getStatus', () => {
|
|
|
92
86
|
})
|
|
93
87
|
|
|
94
88
|
describe('getCommits', () => {
|
|
95
|
-
it('returns commits in order', async () => {
|
|
96
|
-
// Create another commit so we have history to read
|
|
97
|
-
writeFile('commits-test.txt', 'data')
|
|
98
|
-
git('add commits-test.txt')
|
|
99
|
-
git('commit -m "test commit for getCommits"')
|
|
100
|
-
|
|
89
|
+
it('returns commits in order (fixture has 6 commits reachable from main)', async () => {
|
|
101
90
|
const commits = await getCommits(repo, 'HEAD', 10)
|
|
102
|
-
expect(commits.length).
|
|
103
|
-
expect(commits[0].summary).toBe('test commit for getCommits')
|
|
91
|
+
expect(commits.length).toBe(6)
|
|
104
92
|
expect(commits[0].sha).toBeTruthy()
|
|
105
93
|
expect(commits[0].author).toBeTruthy()
|
|
94
|
+
// The most recent commit should be the merge
|
|
95
|
+
expect(commits[0].summary).toBe('merge: feature/one into main')
|
|
96
|
+
// The initial commit should be the last one
|
|
97
|
+
expect(commits[5].summary).toBe('initial: add README and gitignore')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('returns commits for feature/two branch', async () => {
|
|
101
|
+
const commits = await getCommits(repo, 'feature/two', 10)
|
|
102
|
+
expect(commits.length).toBeGreaterThanOrEqual(1)
|
|
103
|
+
expect(commits[0].summary).toBe('feat: feature two')
|
|
106
104
|
})
|
|
107
105
|
})
|
|
108
106
|
|
|
109
107
|
describe('Branch operations', () => {
|
|
110
|
-
it('
|
|
111
|
-
|
|
108
|
+
it('includes feature/one and feature/two', async () => {
|
|
109
|
+
const branches = await getBranches(repo)
|
|
110
|
+
const names = branches.map(b => b.nameWithoutRemote)
|
|
111
|
+
expect(names).toContain('main')
|
|
112
|
+
expect(names).toContain('feature/one')
|
|
113
|
+
expect(names).toContain('feature/two')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('creates, renames, and deletes a branch', async () => {
|
|
112
117
|
await createBranch(repo, 'test-feature', 'HEAD')
|
|
113
118
|
let branches = await getBranches(repo)
|
|
114
|
-
const names = branches.map(b => b.
|
|
119
|
+
const names = branches.map(b => b.nameWithoutRemote)
|
|
115
120
|
expect(names).toContain('test-feature')
|
|
116
121
|
|
|
117
122
|
// Rename the branch
|
|
118
|
-
const featureBranch = branches.find(b => b.
|
|
123
|
+
const featureBranch = branches.find(b => b.nameWithoutRemote === 'test-feature')!
|
|
119
124
|
await renameBranch(repo, featureBranch, 'test-feature-renamed')
|
|
120
125
|
branches = await getBranches(repo)
|
|
121
|
-
expect(branches.map(b => b.
|
|
122
|
-
expect(branches.map(b => b.
|
|
126
|
+
expect(branches.map(b => b.nameWithoutRemote)).toContain('test-feature-renamed')
|
|
127
|
+
expect(branches.map(b => b.nameWithoutRemote)).not.toContain('test-feature')
|
|
123
128
|
|
|
124
129
|
// Delete the branch
|
|
125
130
|
await deleteLocalBranch(repo, 'test-feature-renamed')
|
|
126
131
|
branches = await getBranches(repo)
|
|
127
|
-
expect(branches.map(b => b.
|
|
132
|
+
expect(branches.map(b => b.nameWithoutRemote)).not.toContain('test-feature-renamed')
|
|
128
133
|
})
|
|
129
134
|
})
|
|
130
135
|
|
|
131
136
|
describe('createCommit', () => {
|
|
132
137
|
it('creates a commit with files', async () => {
|
|
133
|
-
|
|
134
|
-
git('add commit-test.txt')
|
|
138
|
+
writeFileSync(join(repoPath, 'commit-test.txt'), 'commit data')
|
|
139
|
+
git(repoPath, 'add commit-test.txt')
|
|
135
140
|
|
|
136
141
|
const status = await getStatus(repo)
|
|
137
142
|
const files = status!.workingDirectory.files.filter(
|
|
@@ -140,17 +145,199 @@ describe('createCommit', () => {
|
|
|
140
145
|
|
|
141
146
|
const sha = await createCommit(repo, 'feat: add commit-test.txt', files)
|
|
142
147
|
expect(sha).toBeTruthy()
|
|
143
|
-
// createCommit returns the abbreviated SHA from git commit output (7+ chars)
|
|
144
148
|
expect(sha.length).toBeGreaterThanOrEqual(7)
|
|
145
149
|
|
|
146
|
-
// Verify the commit exists
|
|
147
|
-
// with the abbreviated SHA returned by createCommit
|
|
150
|
+
// Verify the commit exists
|
|
148
151
|
const commits = await getCommits(repo, 'HEAD', 5)
|
|
149
152
|
expect(commits[0].sha.startsWith(sha)).toBe(true)
|
|
150
153
|
expect(commits[0].summary).toBe('feat: add commit-test.txt')
|
|
151
154
|
})
|
|
152
155
|
})
|
|
153
156
|
|
|
157
|
+
describe('Tag operations', () => {
|
|
158
|
+
it('lists tags from the fixture', async () => {
|
|
159
|
+
const tags = await getAllTags(repo)
|
|
160
|
+
expect(tags.size).toBeGreaterThanOrEqual(1)
|
|
161
|
+
expect(tags.has('v0.1.0')).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('getRepositorySummary', () => {
|
|
166
|
+
it('returns path, head, and currentBranch for a normal repo', async () => {
|
|
167
|
+
const summary = await getRepositorySummary(repoPath)
|
|
168
|
+
expect(summary).toBeTruthy()
|
|
169
|
+
expect(summary!.path).toBe(repoPath)
|
|
170
|
+
// HEAD should be a 40-char hex SHA
|
|
171
|
+
expect(summary!.head).toMatch(/^[a-f0-9]{40}$/)
|
|
172
|
+
expect(summary!.currentBranch).toBe('main')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('returns currentBranch as undefined when HEAD is detached', async () => {
|
|
176
|
+
git(repoPath, 'checkout --detach')
|
|
177
|
+
const summary = await getRepositorySummary(repoPath)
|
|
178
|
+
expect(summary).toBeTruthy()
|
|
179
|
+
expect(summary!.path).toBe(repoPath)
|
|
180
|
+
expect(summary!.head).toMatch(/^[a-f0-9]{40}$/)
|
|
181
|
+
expect(summary!.currentBranch).toBeUndefined()
|
|
182
|
+
// Reset back for subsequent tests
|
|
183
|
+
git(repoPath, 'checkout main')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('returns null for a bare repository', async () => {
|
|
187
|
+
const barePath = mkdtempSync(join(tmpdir(), 'gcctest-bare-'))
|
|
188
|
+
execSync(`git init --bare ${barePath}`, { stdio: 'pipe' })
|
|
189
|
+
const summary = await getRepositorySummary(barePath)
|
|
190
|
+
expect(summary).toBeNull()
|
|
191
|
+
execSync(`rm -rf ${barePath}`)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('returns null for a non-existent path', async () => {
|
|
195
|
+
const summary = await getRepositorySummary('/nonexistent/path')
|
|
196
|
+
expect(summary).toBeNull()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('returns null for a path that exists but is not a git repo', async () => {
|
|
200
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
201
|
+
const summary = await getRepositorySummary(nonRepoPath)
|
|
202
|
+
expect(summary).toBeNull()
|
|
203
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('getRemoteUrl', () => {
|
|
208
|
+
it('returns the URL for an existing remote', async () => {
|
|
209
|
+
const url = await getRemoteUrl(repoPath, 'origin')
|
|
210
|
+
expect(url).toBe('https://github.com/user/repo.git')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('returns null for a non-existent remote', async () => {
|
|
214
|
+
const url = await getRemoteUrl(repoPath, 'nonexistent')
|
|
215
|
+
expect(url).toBeNull()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('returns null for a non-repo path', async () => {
|
|
219
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
220
|
+
const url = await getRemoteUrl(nonRepoPath, 'origin')
|
|
221
|
+
expect(url).toBeNull()
|
|
222
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('getRemotesFromPath', () => {
|
|
227
|
+
it('lists all remotes for a valid repo', async () => {
|
|
228
|
+
const remotes = await getRemotesFromPath(repoPath)
|
|
229
|
+
expect(remotes.length).toBeGreaterThanOrEqual(1)
|
|
230
|
+
const origin = remotes.find(r => r.name === 'origin')
|
|
231
|
+
expect(origin).toBeTruthy()
|
|
232
|
+
expect(origin!.url).toBe('https://github.com/user/repo.git')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('returns an empty array for a non-repo path', async () => {
|
|
236
|
+
const nonRepoPath = mkdtempSync(join(tmpdir(), 'gcctest-nonrepo-'))
|
|
237
|
+
const remotes = await getRemotesFromPath(nonRepoPath)
|
|
238
|
+
expect(remotes).toEqual([])
|
|
239
|
+
execSync(`rm -rf ${nonRepoPath}`)
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
describe('addRemote', () => {
|
|
244
|
+
it('adds a new remote and returns it', async () => {
|
|
245
|
+
const remote = await addRemote(repo, 'upstream', 'https://github.com/upstream/repo.git')
|
|
246
|
+
expect(remote.name).toBe('upstream')
|
|
247
|
+
expect(remote.url).toBe('https://github.com/upstream/repo.git')
|
|
248
|
+
|
|
249
|
+
// Verify it was persisted
|
|
250
|
+
const url = await getRemoteUrl(repoPath, 'upstream')
|
|
251
|
+
expect(url).toBe('https://github.com/upstream/repo.git')
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('setRemoteURL', () => {
|
|
256
|
+
it('updates the URL of an existing remote', async () => {
|
|
257
|
+
const result = await setRemoteURL(repo, 'origin', 'https://github.com/user/new-repo.git')
|
|
258
|
+
expect(result).toBe(true)
|
|
259
|
+
|
|
260
|
+
// Verify it was changed
|
|
261
|
+
const url = await getRemoteUrl(repoPath, 'origin')
|
|
262
|
+
expect(url).toBe('https://github.com/user/new-repo.git')
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe('removeRemote', () => {
|
|
267
|
+
it('removes an existing remote silently', async () => {
|
|
268
|
+
// First confirm the remote exists
|
|
269
|
+
const beforeUrl = await getRemoteUrl(repoPath, 'upstream')
|
|
270
|
+
expect(beforeUrl).toBe('https://github.com/upstream/repo.git')
|
|
271
|
+
|
|
272
|
+
await removeRemote(repo, 'upstream')
|
|
273
|
+
|
|
274
|
+
// Verify it's gone
|
|
275
|
+
const afterUrl = await getRemoteUrl(repoPath, 'upstream')
|
|
276
|
+
expect(afterUrl).toBeNull()
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('silently succeeds when removing a non-existent remote', async () => {
|
|
280
|
+
// Should not throw
|
|
281
|
+
await expect(removeRemote(repo, 'nonexistent')).resolves.toBeUndefined()
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
describe('merge', () => {
|
|
286
|
+
it('merges feature/two into main', async () => {
|
|
287
|
+
const result = await merge(repo, 'feature/two')
|
|
288
|
+
expect(result).toBe(MergeResult.Success)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('returns AlreadyUpToDate when merging an already-merged branch', async () => {
|
|
292
|
+
const result = await merge(repo, 'feature/two')
|
|
293
|
+
expect(result).toBe(MergeResult.AlreadyUpToDate)
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
describe('rebase', () => {
|
|
298
|
+
it('rebases feature/two onto feature/one', async () => {
|
|
299
|
+
// Get Branch objects with tip.sha required by the rebase API
|
|
300
|
+
const branches = await getBranches(repo)
|
|
301
|
+
const baseBranch = branches.find(b => b.nameWithoutRemote === 'feature/one')!
|
|
302
|
+
const targetBranch = branches.find(b => b.nameWithoutRemote === 'feature/two')!
|
|
303
|
+
expect(baseBranch).toBeTruthy()
|
|
304
|
+
expect(targetBranch).toBeTruthy()
|
|
305
|
+
|
|
306
|
+
const result = await rebase(repo, baseBranch, targetBranch)
|
|
307
|
+
expect(result).toBe(RebaseResult.CompletedWithoutError)
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('stash', () => {
|
|
312
|
+
it('creates a stash entry and pops it back', async () => {
|
|
313
|
+
// Create a working directory change to stash
|
|
314
|
+
writeFileSync(join(repoPath, 'stash-test.txt'), 'stash me')
|
|
315
|
+
git(repoPath, 'add stash-test.txt')
|
|
316
|
+
|
|
317
|
+
// Create a stash entry
|
|
318
|
+
const created = await createDesktopStashEntry(repo, 'main', [], null)
|
|
319
|
+
expect(created).toBe(true)
|
|
320
|
+
|
|
321
|
+
// List stashes — should include our new entry
|
|
322
|
+
const stashes = await getStashes(repo)
|
|
323
|
+
expect(stashes.desktopEntries.length).toBeGreaterThanOrEqual(1)
|
|
324
|
+
const stashSha = stashes.desktopEntries[0].stashSha
|
|
325
|
+
expect(stashSha).toBeTruthy()
|
|
326
|
+
|
|
327
|
+
// Pop it back — changes should be restored
|
|
328
|
+
await popStashEntry(repo, stashSha)
|
|
329
|
+
const statusAfterPop = await getStatus(repo)
|
|
330
|
+
expect(statusAfterPop).toBeTruthy()
|
|
331
|
+
const stashFile = statusAfterPop!.workingDirectory.files.find(
|
|
332
|
+
f => f.path === 'stash-test.txt'
|
|
333
|
+
)
|
|
334
|
+
expect(stashFile).toBeTruthy()
|
|
335
|
+
|
|
336
|
+
// Clean up: discard the restored file (stash pop restores to working tree, not index)
|
|
337
|
+
git(repoPath, 'checkout -- stash-test.txt')
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
154
341
|
afterAll(() => {
|
|
155
|
-
|
|
342
|
+
cleanupFixtureRepo(repoPath)
|
|
156
343
|
})
|
package/docs/skills/release.md
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: release
|
|
3
|
-
description: Automated release workflow for git-chopstick-core — version bump, changelog, build, validation, and npm publish.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Release: git-chopstick-core
|
|
7
|
-
|
|
8
|
-
> Loaded via `skill("release")`. Run this whenever you need to publish a new release.
|
|
9
|
-
|
|
10
|
-
## Process
|
|
11
|
-
|
|
12
|
-
### 1. Read current state
|
|
13
|
-
|
|
14
|
-
Read `package.json`, `CHANGELOG.md`, and run `git log --oneline -5` to understand what has changed since the last release.
|
|
15
|
-
|
|
16
|
-
### 2. Determine version bump
|
|
17
|
-
|
|
18
|
-
Check what has changed:
|
|
19
|
-
|
|
20
|
-
- **Breaking API changes** (removed exports, changed signatures) → minor bump (0.x.0)
|
|
21
|
-
- **New features** (new functions, new exports) → patch bump (0.0.x)
|
|
22
|
-
- **Bug fixes, docs, internal refactoring** → patch bump (0.0.x)
|
|
23
|
-
|
|
24
|
-
Current version: read from `package.json`. Bump accordingly.
|
|
25
|
-
|
|
26
|
-
### 3. Update `CHANGELOG.md`
|
|
27
|
-
|
|
28
|
-
Insert a new `## [VERSION] — YYYY-MM-DD` section at the top (below the `# Changelog` heading) with:
|
|
29
|
-
|
|
30
|
-
- `### Added` — new features, exports, helpers
|
|
31
|
-
- `### Changed` — behavioral changes, renames, config changes
|
|
32
|
-
- `### Fixed` — bug fixes
|
|
33
|
-
|
|
34
|
-
Then update the link references at the bottom:
|
|
35
|
-
|
|
36
|
-
- Add `[VERSION]: https://github.com/parkiyong/git-chopstick-core/releases/tag/vVERSION`
|
|
37
|
-
- Update `[Unreleased]: .../compare/vVERSION...HEAD`
|
|
38
|
-
|
|
39
|
-
### 4. Bump `package.json`
|
|
40
|
-
|
|
41
|
-
Update `"version"` field.
|
|
42
|
-
|
|
43
|
-
### 5. Build and validate
|
|
44
|
-
|
|
45
|
-
Run all of these (use parallel agents):
|
|
46
|
-
|
|
47
|
-
- `npm run typecheck` — must pass with zero errors
|
|
48
|
-
- `npm run build` — must produce a clean `dist/`
|
|
49
|
-
- `npm test` — all integration tests must pass
|
|
50
|
-
- `npm pack --dry-run` — confirm all of:
|
|
51
|
-
- `dist/` is included (expect ~351 files, ~700kB)
|
|
52
|
-
- `CHANGELOG.md`, `docs/`, and `src/__tests__/integration.test.ts` appear in the file list
|
|
53
|
-
- `package.json`, `README.md`, `LICENSE` are present (npm includes these automatically)
|
|
54
|
-
- No unexpected files are leaking through (check for test artifacts, `.ts` sources outside `dist/`)
|
|
55
|
-
|
|
56
|
-
If any fail, fix before proceeding.
|
|
57
|
-
|
|
58
|
-
### 6. Stage and commit
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
git add -A
|
|
62
|
-
git commit -m "vVERSION: <short summary of changes>"
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### 7. Push and publish
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
git push origin main
|
|
69
|
-
npm publish
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 8. Verify
|
|
73
|
-
|
|
74
|
-
- Confirm npm shows the new version: `npm view git-chopstick-core version`
|
|
75
|
-
- Confirm the tag exists on GitHub: `git tag -l`
|
|
76
|
-
- Suggest followup tasks
|
|
77
|
-
|
|
78
|
-
## Notes
|
|
79
|
-
|
|
80
|
-
- `prepublishOnly` runs `npm run typecheck && npm test && npm run build` automatically — integration tests must pass before publish can proceed. This catches API regressions, broken imports, and test failures before they ship.
|
|
81
|
-
You still validate in step 5 beforehand rather than relying solely on `prepublishOnly`.
|
|
82
|
-
- If this is the first release in a session, run `setup-matt-pocock-skills` first to configure issue tracker context.
|