@vibe-validate/git 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/branch-sync.d.ts +68 -0
- package/dist/branch-sync.d.ts.map +1 -0
- package/dist/branch-sync.js +139 -0
- package/dist/branch-sync.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/post-merge-cleanup.d.ts +77 -0
- package/dist/post-merge-cleanup.d.ts.map +1 -0
- package/dist/post-merge-cleanup.js +221 -0
- package/dist/post-merge-cleanup.js.map +1 -0
- package/dist/tree-hash.d.ts +44 -0
- package/dist/tree-hash.d.ts.map +1 -0
- package/dist/tree-hash.js +110 -0
- package/dist/tree-hash.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @vibe-validate/git
|
|
2
|
+
|
|
3
|
+
Git utilities for vibe-validate - deterministic tree hash calculation, branch synchronization, and post-merge cleanup.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Deterministic Git Tree Hash**: Content-based hashing using `git write-tree` (no timestamps)
|
|
8
|
+
- **Branch Sync Checking**: Safe branch synchronization verification without auto-merging
|
|
9
|
+
- **Post-Merge Cleanup**: Automated cleanup of merged branches after PR completion
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @vibe-validate/git
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Git Tree Hash (Deterministic)
|
|
20
|
+
|
|
21
|
+
Calculate a content-based hash of the working tree including staged, unstaged, and untracked files:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { getGitTreeHash } from '@vibe-validate/git';
|
|
25
|
+
|
|
26
|
+
const treeHash = await getGitTreeHash();
|
|
27
|
+
console.log(`Tree hash: ${treeHash}`);
|
|
28
|
+
// Deterministic - same content = same hash (no timestamp variance)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Branch Sync Checking
|
|
32
|
+
|
|
33
|
+
Check if the current branch is behind origin/main without auto-merging:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { BranchSyncChecker } from '@vibe-validate/git';
|
|
37
|
+
|
|
38
|
+
const checker = new BranchSyncChecker();
|
|
39
|
+
const result = await checker.checkSync();
|
|
40
|
+
|
|
41
|
+
if (!result.isUpToDate) {
|
|
42
|
+
console.log(`Branch is ${result.behindBy} commits behind origin/main`);
|
|
43
|
+
console.log('Manual merge required');
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Post-Merge Cleanup
|
|
48
|
+
|
|
49
|
+
Clean up local branches after PR merge:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { PostPRMergeCleanup } from '@vibe-validate/git';
|
|
53
|
+
|
|
54
|
+
const cleanup = new PostPRMergeCleanup();
|
|
55
|
+
const result = await cleanup.runCleanup();
|
|
56
|
+
|
|
57
|
+
console.log(`Deleted ${result.branchesDeleted.length} merged branches`);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API Reference
|
|
61
|
+
|
|
62
|
+
### `getGitTreeHash()`
|
|
63
|
+
|
|
64
|
+
Returns a deterministic content-based hash of the working tree.
|
|
65
|
+
|
|
66
|
+
**Implementation Details:**
|
|
67
|
+
- Uses `git add --intent-to-add .` to mark untracked files (without staging)
|
|
68
|
+
- Uses `git write-tree` for content-based hashing (no timestamps)
|
|
69
|
+
- Resets index after hash calculation
|
|
70
|
+
- Falls back to `HEAD^{tree}` if no changes exist
|
|
71
|
+
|
|
72
|
+
**Returns:** `Promise<string>` - Git tree SHA-1 hash
|
|
73
|
+
|
|
74
|
+
### `BranchSyncChecker`
|
|
75
|
+
|
|
76
|
+
Class for checking branch synchronization status.
|
|
77
|
+
|
|
78
|
+
**Methods:**
|
|
79
|
+
- `checkSync()`: Check if current branch is behind origin/main
|
|
80
|
+
- `printStatus(result)`: Display formatted status information
|
|
81
|
+
- `getExitCode(result)`: Get appropriate exit code (0=ok, 1=needs merge, 2=error)
|
|
82
|
+
|
|
83
|
+
### `PostPRMergeCleanup`
|
|
84
|
+
|
|
85
|
+
Class for post-merge cleanup operations.
|
|
86
|
+
|
|
87
|
+
**Methods:**
|
|
88
|
+
- `runCleanup()`: Execute complete cleanup workflow
|
|
89
|
+
1. Switch to main branch
|
|
90
|
+
2. Sync main with origin/main
|
|
91
|
+
3. Delete merged branches
|
|
92
|
+
4. Prune remote references
|
|
93
|
+
|
|
94
|
+
## Design Decisions
|
|
95
|
+
|
|
96
|
+
### Deterministic Git Tree Hash
|
|
97
|
+
|
|
98
|
+
**Problem**: `git stash create` includes timestamps, making hashes non-deterministic.
|
|
99
|
+
|
|
100
|
+
**Solution**: Use `git write-tree` with intent-to-add for untracked files:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Old approach (non-deterministic - includes timestamps)
|
|
104
|
+
git stash create // Different hash on each run even with same content
|
|
105
|
+
|
|
106
|
+
// New approach (deterministic - content-based only)
|
|
107
|
+
git add --intent-to-add . // Mark untracked files (no staging)
|
|
108
|
+
git write-tree // Content-based hash (no timestamps)
|
|
109
|
+
git reset // Restore index to clean state
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Benefits:**
|
|
113
|
+
- Same content always produces same hash
|
|
114
|
+
- Enables reliable validation state caching
|
|
115
|
+
- Includes all files (staged, unstaged, untracked)
|
|
116
|
+
- No side effects (index restored after hash)
|
|
117
|
+
|
|
118
|
+
### Safe Branch Sync
|
|
119
|
+
|
|
120
|
+
**Philosophy**: Never auto-merge. Always require manual conflict resolution.
|
|
121
|
+
|
|
122
|
+
**Why:**
|
|
123
|
+
- Preserves visibility of conflicts
|
|
124
|
+
- Prevents accidental code overwrites
|
|
125
|
+
- Explicit developer control over merges
|
|
126
|
+
|
|
127
|
+
### Post-Merge Cleanup
|
|
128
|
+
|
|
129
|
+
**Safety Features:**
|
|
130
|
+
- Only deletes branches confirmed merged into main
|
|
131
|
+
- Never deletes main branch
|
|
132
|
+
- Provides clear feedback on all operations
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Branch Sync Checker
|
|
3
|
+
*
|
|
4
|
+
* Safely checks if the current branch is behind a remote branch without auto-merging.
|
|
5
|
+
* Provides clear status reporting and next-step instructions.
|
|
6
|
+
*
|
|
7
|
+
* Key safety features:
|
|
8
|
+
* - Never auto-merges (preserves conflict visibility)
|
|
9
|
+
* - Clear exit codes for CI/agent integration
|
|
10
|
+
* - Explicit instructions when manual intervention needed
|
|
11
|
+
* - Cross-platform compatibility
|
|
12
|
+
*/
|
|
13
|
+
declare const GIT_OPTIONS: {
|
|
14
|
+
timeout: number;
|
|
15
|
+
encoding: "utf8";
|
|
16
|
+
maxBuffer: number;
|
|
17
|
+
};
|
|
18
|
+
export interface SyncCheckResult {
|
|
19
|
+
isUpToDate: boolean;
|
|
20
|
+
behindBy: number;
|
|
21
|
+
currentBranch: string;
|
|
22
|
+
hasRemote: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export type ExecAsyncFunction = (command: string, options: typeof GIT_OPTIONS) => Promise<{
|
|
26
|
+
stdout: string;
|
|
27
|
+
stderr: string;
|
|
28
|
+
}>;
|
|
29
|
+
export interface SyncCheckOptions {
|
|
30
|
+
remoteBranch?: string;
|
|
31
|
+
execAsync?: ExecAsyncFunction;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Branch Sync Checker
|
|
35
|
+
*
|
|
36
|
+
* Checks if current branch is behind a remote branch
|
|
37
|
+
*/
|
|
38
|
+
export declare class BranchSyncChecker {
|
|
39
|
+
private readonly remoteBranch;
|
|
40
|
+
private readonly execAsync;
|
|
41
|
+
constructor(options?: SyncCheckOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Check if the current branch is synchronized with remote branch
|
|
44
|
+
*
|
|
45
|
+
* @returns Promise resolving to sync status information
|
|
46
|
+
*/
|
|
47
|
+
checkSync(): Promise<SyncCheckResult>;
|
|
48
|
+
private getCurrentBranch;
|
|
49
|
+
private hasRemoteBranch;
|
|
50
|
+
private fetchRemote;
|
|
51
|
+
private getCommitsBehind;
|
|
52
|
+
/**
|
|
53
|
+
* Get appropriate exit code based on sync result
|
|
54
|
+
*
|
|
55
|
+
* @param result - The sync check result
|
|
56
|
+
* @returns Exit code (0=success, 1=needs merge, 2=error)
|
|
57
|
+
*/
|
|
58
|
+
getExitCode(result: SyncCheckResult): number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Convenience function for quick sync checking
|
|
62
|
+
*
|
|
63
|
+
* @param options - Sync check options
|
|
64
|
+
* @returns Sync check result
|
|
65
|
+
*/
|
|
66
|
+
export declare function checkBranchSync(options?: SyncCheckOptions): Promise<SyncCheckResult>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=branch-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-sync.d.ts","sourceRoot":"","sources":["../src/branch-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,QAAA,MAAM,WAAW;;;;CAIhB,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,WAAW,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE9H,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;gBAElC,OAAO,GAAE,gBAAqB;IAK1C;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;YA0C7B,gBAAgB;YAUhB,eAAe;YASf,WAAW;YAUX,gBAAgB;IAW9B;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM;CAK7C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAG9F"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Branch Sync Checker
|
|
3
|
+
*
|
|
4
|
+
* Safely checks if the current branch is behind a remote branch without auto-merging.
|
|
5
|
+
* Provides clear status reporting and next-step instructions.
|
|
6
|
+
*
|
|
7
|
+
* Key safety features:
|
|
8
|
+
* - Never auto-merges (preserves conflict visibility)
|
|
9
|
+
* - Clear exit codes for CI/agent integration
|
|
10
|
+
* - Explicit instructions when manual intervention needed
|
|
11
|
+
* - Cross-platform compatibility
|
|
12
|
+
*/
|
|
13
|
+
import { exec } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
const GIT_TIMEOUT = 10000; // 10 seconds timeout for git operations
|
|
16
|
+
const GIT_OPTIONS = {
|
|
17
|
+
timeout: GIT_TIMEOUT,
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
maxBuffer: 1024 * 1024 // 1MB buffer
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Branch Sync Checker
|
|
23
|
+
*
|
|
24
|
+
* Checks if current branch is behind a remote branch
|
|
25
|
+
*/
|
|
26
|
+
export class BranchSyncChecker {
|
|
27
|
+
remoteBranch;
|
|
28
|
+
execAsync;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.remoteBranch = options.remoteBranch || 'origin/main';
|
|
31
|
+
this.execAsync = options.execAsync || promisify(exec);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if the current branch is synchronized with remote branch
|
|
35
|
+
*
|
|
36
|
+
* @returns Promise resolving to sync status information
|
|
37
|
+
*/
|
|
38
|
+
async checkSync() {
|
|
39
|
+
try {
|
|
40
|
+
// Get current branch name
|
|
41
|
+
const currentBranch = await this.getCurrentBranch();
|
|
42
|
+
// Check if remote branch exists
|
|
43
|
+
const hasRemote = await this.hasRemoteBranch();
|
|
44
|
+
if (!hasRemote) {
|
|
45
|
+
return {
|
|
46
|
+
isUpToDate: true,
|
|
47
|
+
behindBy: 0,
|
|
48
|
+
currentBranch,
|
|
49
|
+
hasRemote: false,
|
|
50
|
+
error: `No remote branch ${this.remoteBranch} found`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Fetch latest from remote
|
|
54
|
+
await this.fetchRemote();
|
|
55
|
+
// Check how many commits behind
|
|
56
|
+
const behindBy = await this.getCommitsBehind();
|
|
57
|
+
return {
|
|
58
|
+
isUpToDate: behindBy === 0,
|
|
59
|
+
behindBy,
|
|
60
|
+
currentBranch,
|
|
61
|
+
hasRemote: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
return {
|
|
67
|
+
isUpToDate: false,
|
|
68
|
+
behindBy: -1,
|
|
69
|
+
currentBranch: 'unknown',
|
|
70
|
+
hasRemote: false,
|
|
71
|
+
error: errorMessage
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async getCurrentBranch() {
|
|
76
|
+
try {
|
|
77
|
+
const { stdout } = await this.execAsync('git rev-parse --abbrev-ref HEAD', GIT_OPTIONS);
|
|
78
|
+
return stdout.trim();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
82
|
+
throw new Error(`Not in a git repository or unable to determine current branch: ${errorMessage}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async hasRemoteBranch() {
|
|
86
|
+
try {
|
|
87
|
+
await this.execAsync(`git rev-parse --verify ${this.remoteBranch}`, GIT_OPTIONS);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async fetchRemote() {
|
|
95
|
+
try {
|
|
96
|
+
const [remote, branch] = this.remoteBranch.split('/');
|
|
97
|
+
await this.execAsync(`git fetch --quiet ${remote} ${branch}`, GIT_OPTIONS);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
101
|
+
throw new Error(`Failed to fetch from ${this.remoteBranch}: ${errorMessage}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async getCommitsBehind() {
|
|
105
|
+
try {
|
|
106
|
+
const { stdout } = await this.execAsync(`git rev-list --count HEAD..${this.remoteBranch}`, GIT_OPTIONS);
|
|
107
|
+
const count = parseInt(stdout.trim(), 10);
|
|
108
|
+
return isNaN(count) ? 0 : count;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
112
|
+
throw new Error(`Failed to check commits behind: ${errorMessage}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get appropriate exit code based on sync result
|
|
117
|
+
*
|
|
118
|
+
* @param result - The sync check result
|
|
119
|
+
* @returns Exit code (0=success, 1=needs merge, 2=error)
|
|
120
|
+
*/
|
|
121
|
+
getExitCode(result) {
|
|
122
|
+
if (result.error)
|
|
123
|
+
return 2; // Error condition
|
|
124
|
+
if (!result.hasRemote)
|
|
125
|
+
return 0; // No remote, consider OK
|
|
126
|
+
return result.isUpToDate ? 0 : 1; // 0 = up to date, 1 = needs merge
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Convenience function for quick sync checking
|
|
131
|
+
*
|
|
132
|
+
* @param options - Sync check options
|
|
133
|
+
* @returns Sync check result
|
|
134
|
+
*/
|
|
135
|
+
export async function checkBranchSync(options = {}) {
|
|
136
|
+
const checker = new BranchSyncChecker(options);
|
|
137
|
+
return checker.checkSync();
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=branch-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-sync.js","sourceRoot":"","sources":["../src/branch-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,wCAAwC;AACnE,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,MAAe;IACzB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC,aAAa;CACrC,CAAC;AAkBF;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IACX,YAAY,CAAS;IACrB,SAAS,CAAoB;IAE9C,YAAY,UAA4B,EAAE;QACxC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,aAAa,CAAC;QAC1D,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC;YACH,0BAA0B;YAC1B,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAEpD,gCAAgC;YAChC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;YAC/C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO;oBACL,UAAU,EAAE,IAAI;oBAChB,QAAQ,EAAE,CAAC;oBACX,aAAa;oBACb,SAAS,EAAE,KAAK;oBAChB,KAAK,EAAE,oBAAoB,IAAI,CAAC,YAAY,QAAQ;iBACrD,CAAC;YACJ,CAAC;YAED,2BAA2B;YAC3B,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YAEzB,gCAAgC;YAChC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAE/C,OAAO;gBACL,UAAU,EAAE,QAAQ,KAAK,CAAC;gBAC1B,QAAQ;gBACR,aAAa;gBACb,SAAS,EAAE,IAAI;aAChB,CAAC;QAEJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO;gBACL,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,CAAC,CAAC;gBACZ,aAAa,EAAE,SAAS;gBACxB,SAAS,EAAE,KAAK;gBAChB,KAAK,EAAE,YAAY;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iCAAiC,EAAE,WAAW,CAAC,CAAC;YACxF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,IAAI,KAAK,CAAC,kEAAkE,YAAY,EAAE,CAAC,CAAC;QACpG,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,eAAe;QAC3B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC,0BAA0B,IAAI,CAAC,YAAY,EAAE,EAAE,WAAW,CAAC,CAAC;YACjF,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,WAAW;QACvB,IAAI,CAAC;YACH,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACtD,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,MAAM,IAAI,MAAM,EAAE,EAAE,WAAW,CAAC,CAAC;QAC7E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,8BAA8B,IAAI,CAAC,YAAY,EAAE,EAAE,WAAW,CAAC,CAAC;YACxG,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,MAAM,IAAI,KAAK,CAAC,mCAAmC,YAAY,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,MAAuB;QACjC,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB;QAC9C,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,OAAO,CAAC,CAAC,CAAC,yBAAyB;QAC1D,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kCAAkC;IACtE,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAA4B,EAAE;IAClE,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,OAAO,CAAC,SAAS,EAAE,CAAC;AAC7B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-validate/git
|
|
3
|
+
*
|
|
4
|
+
* Git utilities for vibe-validate - deterministic tree hash calculation,
|
|
5
|
+
* branch synchronization, and post-merge cleanup.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { getGitTreeHash, getHeadTreeHash, hasWorkingTreeChanges } from './tree-hash.js';
|
|
10
|
+
export { BranchSyncChecker, checkBranchSync, type SyncCheckResult, type SyncCheckOptions } from './branch-sync.js';
|
|
11
|
+
export { PostPRMergeCleanup, cleanupMergedBranches, type CleanupResult, type CleanupOptions } from './post-merge-cleanup.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-validate/git
|
|
3
|
+
*
|
|
4
|
+
* Git utilities for vibe-validate - deterministic tree hash calculation,
|
|
5
|
+
* branch synchronization, and post-merge cleanup.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
// Tree hash calculation (deterministic, content-based)
|
|
10
|
+
export { getGitTreeHash, getHeadTreeHash, hasWorkingTreeChanges } from './tree-hash.js';
|
|
11
|
+
// Branch sync checking (safe, no auto-merge)
|
|
12
|
+
export { BranchSyncChecker, checkBranchSync } from './branch-sync.js';
|
|
13
|
+
// Post-merge cleanup (delete merged branches)
|
|
14
|
+
export { PostPRMergeCleanup, cleanupMergedBranches } from './post-merge-cleanup.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uDAAuD;AACvD,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAExB,6CAA6C;AAC7C,OAAO,EACL,iBAAiB,EACjB,eAAe,EAGhB,MAAM,kBAAkB,CAAC;AAE1B,8CAA8C;AAC9C,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EAGtB,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-PR Merge Cleanup Tool
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive post-PR cleanup workflow that:
|
|
5
|
+
* 1. Switches to main branch
|
|
6
|
+
* 2. Syncs main branch with remote origin
|
|
7
|
+
* 3. Deletes local branches that have been merged
|
|
8
|
+
* 4. Provides clean workspace for next PR
|
|
9
|
+
*
|
|
10
|
+
* Safe operations:
|
|
11
|
+
* - Only deletes branches that are confirmed merged
|
|
12
|
+
* - Never deletes the current main branch or unmerged branches
|
|
13
|
+
* - Provides clear feedback on all actions taken
|
|
14
|
+
*/
|
|
15
|
+
export interface CleanupResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
branchesDeleted: string[];
|
|
19
|
+
currentBranch: string;
|
|
20
|
+
mainSynced: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface CleanupOptions {
|
|
23
|
+
mainBranch?: string;
|
|
24
|
+
remoteName?: string;
|
|
25
|
+
dryRun?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Post-PR Merge Cleanup
|
|
29
|
+
*
|
|
30
|
+
* Cleans up merged branches and syncs main branch after PR merge
|
|
31
|
+
*/
|
|
32
|
+
export declare class PostPRMergeCleanup {
|
|
33
|
+
private readonly mainBranch;
|
|
34
|
+
private readonly remoteName;
|
|
35
|
+
private readonly dryRun;
|
|
36
|
+
constructor(options?: CleanupOptions);
|
|
37
|
+
/**
|
|
38
|
+
* Run comprehensive post-PR merge cleanup workflow
|
|
39
|
+
*/
|
|
40
|
+
runCleanup(): Promise<CleanupResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Get the current git branch name
|
|
43
|
+
*/
|
|
44
|
+
private getCurrentBranch;
|
|
45
|
+
/**
|
|
46
|
+
* Switch to main branch
|
|
47
|
+
*/
|
|
48
|
+
private switchToMain;
|
|
49
|
+
/**
|
|
50
|
+
* Sync main branch with remote origin
|
|
51
|
+
*/
|
|
52
|
+
private syncMainBranch;
|
|
53
|
+
/**
|
|
54
|
+
* Fetch remote branch information
|
|
55
|
+
*/
|
|
56
|
+
private fetchRemoteInfo;
|
|
57
|
+
/**
|
|
58
|
+
* Find and delete branches that have been merged
|
|
59
|
+
*/
|
|
60
|
+
private deleteMergedBranches;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a branch has been merged into main
|
|
63
|
+
*/
|
|
64
|
+
private isBranchMerged;
|
|
65
|
+
/**
|
|
66
|
+
* Clean up remote tracking references
|
|
67
|
+
*/
|
|
68
|
+
private pruneRemoteReferences;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convenience function for quick cleanup
|
|
72
|
+
*
|
|
73
|
+
* @param options - Cleanup options
|
|
74
|
+
* @returns Cleanup result
|
|
75
|
+
*/
|
|
76
|
+
export declare function cleanupMergedBranches(options?: CleanupOptions): Promise<CleanupResult>;
|
|
77
|
+
//# sourceMappingURL=post-merge-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-merge-cleanup.d.ts","sourceRoot":"","sources":["../src/post-merge-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,OAAO,GAAE,cAAmB;IAMxC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;IAqC1C;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAW9B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAGhG"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-PR Merge Cleanup Tool
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive post-PR cleanup workflow that:
|
|
5
|
+
* 1. Switches to main branch
|
|
6
|
+
* 2. Syncs main branch with remote origin
|
|
7
|
+
* 3. Deletes local branches that have been merged
|
|
8
|
+
* 4. Provides clean workspace for next PR
|
|
9
|
+
*
|
|
10
|
+
* Safe operations:
|
|
11
|
+
* - Only deletes branches that are confirmed merged
|
|
12
|
+
* - Never deletes the current main branch or unmerged branches
|
|
13
|
+
* - Provides clear feedback on all actions taken
|
|
14
|
+
*/
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
const TIMEOUT = 30000; // 30 seconds timeout for git operations
|
|
17
|
+
/**
|
|
18
|
+
* Post-PR Merge Cleanup
|
|
19
|
+
*
|
|
20
|
+
* Cleans up merged branches and syncs main branch after PR merge
|
|
21
|
+
*/
|
|
22
|
+
export class PostPRMergeCleanup {
|
|
23
|
+
mainBranch;
|
|
24
|
+
remoteName;
|
|
25
|
+
dryRun;
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.mainBranch = options.mainBranch || 'main';
|
|
28
|
+
this.remoteName = options.remoteName || 'origin';
|
|
29
|
+
this.dryRun = options.dryRun || false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run comprehensive post-PR merge cleanup workflow
|
|
33
|
+
*/
|
|
34
|
+
async runCleanup() {
|
|
35
|
+
const result = {
|
|
36
|
+
success: false,
|
|
37
|
+
branchesDeleted: [],
|
|
38
|
+
currentBranch: '',
|
|
39
|
+
mainSynced: false
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
// Step 1: Get current branch
|
|
43
|
+
result.currentBranch = this.getCurrentBranch();
|
|
44
|
+
// Step 2: Switch to main branch
|
|
45
|
+
this.switchToMain();
|
|
46
|
+
// Step 3: Sync main branch with remote
|
|
47
|
+
this.syncMainBranch();
|
|
48
|
+
result.mainSynced = true;
|
|
49
|
+
// Step 4: Fetch remote branch information
|
|
50
|
+
this.fetchRemoteInfo();
|
|
51
|
+
// Step 5: Find and delete merged branches
|
|
52
|
+
result.branchesDeleted = this.deleteMergedBranches();
|
|
53
|
+
// Step 6: Clean up remote tracking branches
|
|
54
|
+
this.pruneRemoteReferences();
|
|
55
|
+
result.success = true;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the current git branch name
|
|
65
|
+
*/
|
|
66
|
+
getCurrentBranch() {
|
|
67
|
+
try {
|
|
68
|
+
return execSync('git branch --show-current', {
|
|
69
|
+
encoding: 'utf8',
|
|
70
|
+
timeout: TIMEOUT
|
|
71
|
+
}).trim();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new Error(`Failed to get current branch: ${error}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Switch to main branch
|
|
79
|
+
*/
|
|
80
|
+
switchToMain() {
|
|
81
|
+
try {
|
|
82
|
+
execSync(`git checkout ${this.mainBranch}`, {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
timeout: TIMEOUT,
|
|
85
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
throw new Error(`Failed to switch to ${this.mainBranch} branch: ${error}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sync main branch with remote origin
|
|
94
|
+
*/
|
|
95
|
+
syncMainBranch() {
|
|
96
|
+
try {
|
|
97
|
+
// Fetch latest changes from remote
|
|
98
|
+
execSync(`git fetch ${this.remoteName} ${this.mainBranch}`, {
|
|
99
|
+
encoding: 'utf8',
|
|
100
|
+
timeout: TIMEOUT,
|
|
101
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
102
|
+
});
|
|
103
|
+
// Fast-forward merge remote/main
|
|
104
|
+
execSync(`git merge ${this.remoteName}/${this.mainBranch} --ff-only`, {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
timeout: TIMEOUT,
|
|
107
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new Error(`Failed to sync ${this.mainBranch} branch: ${error}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fetch remote branch information
|
|
116
|
+
*/
|
|
117
|
+
fetchRemoteInfo() {
|
|
118
|
+
try {
|
|
119
|
+
execSync(`git fetch ${this.remoteName} --prune`, {
|
|
120
|
+
encoding: 'utf8',
|
|
121
|
+
timeout: TIMEOUT,
|
|
122
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw new Error(`Failed to fetch remote info: ${error}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Find and delete branches that have been merged
|
|
131
|
+
*/
|
|
132
|
+
deleteMergedBranches() {
|
|
133
|
+
try {
|
|
134
|
+
// Get list of local branches (excluding main)
|
|
135
|
+
const allBranches = execSync('git branch --format="%(refname:short)"', {
|
|
136
|
+
encoding: 'utf8',
|
|
137
|
+
timeout: TIMEOUT
|
|
138
|
+
})
|
|
139
|
+
.trim()
|
|
140
|
+
.split('\n')
|
|
141
|
+
.filter(branch => branch && branch !== this.mainBranch && !branch.startsWith('*'));
|
|
142
|
+
const deletedBranches = [];
|
|
143
|
+
for (const branch of allBranches) {
|
|
144
|
+
if (this.isBranchMerged(branch)) {
|
|
145
|
+
if (this.dryRun) {
|
|
146
|
+
deletedBranches.push(branch);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
execSync(`git branch -d "${branch}"`, {
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
timeout: TIMEOUT,
|
|
153
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
154
|
+
});
|
|
155
|
+
deletedBranches.push(branch);
|
|
156
|
+
}
|
|
157
|
+
catch (deleteError) {
|
|
158
|
+
// Try force delete if regular delete fails
|
|
159
|
+
try {
|
|
160
|
+
execSync(`git branch -D "${branch}"`, {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
timeout: TIMEOUT,
|
|
163
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
164
|
+
});
|
|
165
|
+
deletedBranches.push(branch);
|
|
166
|
+
}
|
|
167
|
+
catch (forceDeleteError) {
|
|
168
|
+
// Couldn't delete - skip this branch
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return deletedBranches;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if a branch has been merged into main
|
|
181
|
+
*/
|
|
182
|
+
isBranchMerged(branch) {
|
|
183
|
+
try {
|
|
184
|
+
const mergedBranches = execSync(`git branch --merged ${this.mainBranch} --format="%(refname:short)"`, {
|
|
185
|
+
encoding: 'utf8',
|
|
186
|
+
timeout: TIMEOUT
|
|
187
|
+
});
|
|
188
|
+
return mergedBranches.includes(branch);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// If we can't determine merge status, don't delete the branch
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Clean up remote tracking references
|
|
197
|
+
*/
|
|
198
|
+
pruneRemoteReferences() {
|
|
199
|
+
try {
|
|
200
|
+
execSync(`git remote prune ${this.remoteName}`, {
|
|
201
|
+
encoding: 'utf8',
|
|
202
|
+
timeout: TIMEOUT,
|
|
203
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
// Non-critical operation - don't fail on error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Convenience function for quick cleanup
|
|
213
|
+
*
|
|
214
|
+
* @param options - Cleanup options
|
|
215
|
+
* @returns Cleanup result
|
|
216
|
+
*/
|
|
217
|
+
export async function cleanupMergedBranches(options = {}) {
|
|
218
|
+
const cleanup = new PostPRMergeCleanup(options);
|
|
219
|
+
return cleanup.runCleanup();
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=post-merge-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-merge-cleanup.js","sourceRoot":"","sources":["../src/post-merge-cleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Git Tree Hash Calculation
|
|
3
|
+
*
|
|
4
|
+
* Provides content-based hashing of working tree state including:
|
|
5
|
+
* - Staged changes (index)
|
|
6
|
+
* - Unstaged changes (working tree modifications)
|
|
7
|
+
* - Untracked files
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL FIX: Uses git write-tree instead of git stash create for determinism.
|
|
10
|
+
* git stash create includes timestamps, making hashes non-deterministic.
|
|
11
|
+
* git write-tree produces content-based hashes only (no timestamps).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Get deterministic git tree hash representing current working tree state
|
|
15
|
+
*
|
|
16
|
+
* Implementation:
|
|
17
|
+
* 1. Mark untracked files with --intent-to-add (no actual staging)
|
|
18
|
+
* 2. Calculate tree hash with git write-tree (content-based, no timestamps)
|
|
19
|
+
* 3. Reset index to clean state (no side effects)
|
|
20
|
+
*
|
|
21
|
+
* Why this is better than git stash create:
|
|
22
|
+
* - git stash create: includes timestamps in commit → different hash each time
|
|
23
|
+
* - git write-tree: content-based only → same content = same hash (deterministic)
|
|
24
|
+
*
|
|
25
|
+
* @returns Git tree SHA-1 hash (40 hex characters)
|
|
26
|
+
* @throws Error if not in a git repository or git command fails
|
|
27
|
+
*/
|
|
28
|
+
export declare function getGitTreeHash(): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Get tree hash for HEAD commit (committed state only, no working tree changes)
|
|
31
|
+
*
|
|
32
|
+
* This is useful for comparing committed state vs working tree state.
|
|
33
|
+
*
|
|
34
|
+
* @returns Git tree SHA-1 hash of HEAD commit
|
|
35
|
+
* @throws Error if not in a git repository or HEAD doesn't exist
|
|
36
|
+
*/
|
|
37
|
+
export declare function getHeadTreeHash(): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if working tree has any changes compared to HEAD
|
|
40
|
+
*
|
|
41
|
+
* @returns true if working tree differs from HEAD, false if clean
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasWorkingTreeChanges(): Promise<boolean>;
|
|
44
|
+
//# sourceMappingURL=tree-hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-hash.d.ts","sourceRoot":"","sources":["../src/tree-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CA6CtD;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAQvD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Git Tree Hash Calculation
|
|
3
|
+
*
|
|
4
|
+
* Provides content-based hashing of working tree state including:
|
|
5
|
+
* - Staged changes (index)
|
|
6
|
+
* - Unstaged changes (working tree modifications)
|
|
7
|
+
* - Untracked files
|
|
8
|
+
*
|
|
9
|
+
* CRITICAL FIX: Uses git write-tree instead of git stash create for determinism.
|
|
10
|
+
* git stash create includes timestamps, making hashes non-deterministic.
|
|
11
|
+
* git write-tree produces content-based hashes only (no timestamps).
|
|
12
|
+
*/
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
const GIT_TIMEOUT = 10000; // 10 seconds
|
|
15
|
+
const GIT_OPTIONS = {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
timeout: GIT_TIMEOUT,
|
|
18
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Get deterministic git tree hash representing current working tree state
|
|
22
|
+
*
|
|
23
|
+
* Implementation:
|
|
24
|
+
* 1. Mark untracked files with --intent-to-add (no actual staging)
|
|
25
|
+
* 2. Calculate tree hash with git write-tree (content-based, no timestamps)
|
|
26
|
+
* 3. Reset index to clean state (no side effects)
|
|
27
|
+
*
|
|
28
|
+
* Why this is better than git stash create:
|
|
29
|
+
* - git stash create: includes timestamps in commit → different hash each time
|
|
30
|
+
* - git write-tree: content-based only → same content = same hash (deterministic)
|
|
31
|
+
*
|
|
32
|
+
* @returns Git tree SHA-1 hash (40 hex characters)
|
|
33
|
+
* @throws Error if not in a git repository or git command fails
|
|
34
|
+
*/
|
|
35
|
+
export async function getGitTreeHash() {
|
|
36
|
+
try {
|
|
37
|
+
// Check if we're in a git repository
|
|
38
|
+
execSync('git rev-parse --is-inside-work-tree', GIT_OPTIONS);
|
|
39
|
+
// Step 1: Mark all untracked files with --intent-to-add
|
|
40
|
+
// This adds them to the index WITHOUT staging their content
|
|
41
|
+
// --force: include ignored files for complete state tracking
|
|
42
|
+
try {
|
|
43
|
+
execSync('git add --intent-to-add --all --force', {
|
|
44
|
+
...GIT_OPTIONS,
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'] // Capture stderr for error handling
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (addError) {
|
|
49
|
+
// If no untracked files, git add fails with "nothing to add"
|
|
50
|
+
// This is fine - just means we only have tracked files
|
|
51
|
+
const errorMessage = addError instanceof Error ? addError.message : String(addError);
|
|
52
|
+
if (!errorMessage.includes('nothing')) {
|
|
53
|
+
// Real error - re-throw
|
|
54
|
+
throw addError;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Step 2: Get tree hash (content-based, no timestamps)
|
|
58
|
+
const treeHash = execSync('git write-tree', GIT_OPTIONS).trim();
|
|
59
|
+
// Step 3: Reset index to clean state (remove intent-to-add marks)
|
|
60
|
+
// This ensures no side effects from our hash calculation
|
|
61
|
+
execSync('git reset', GIT_OPTIONS);
|
|
62
|
+
return treeHash;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Handle not-in-git-repo case
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
if (errorMessage.includes('not a git repository')) {
|
|
68
|
+
// Not in git repo - fall back to timestamp-based hash
|
|
69
|
+
console.warn('⚠️ Not in git repository, using timestamp-based hash');
|
|
70
|
+
return `nogit-${Date.now()}`;
|
|
71
|
+
}
|
|
72
|
+
// Other git errors
|
|
73
|
+
throw new Error(`Failed to calculate git tree hash: ${errorMessage}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get tree hash for HEAD commit (committed state only, no working tree changes)
|
|
78
|
+
*
|
|
79
|
+
* This is useful for comparing committed state vs working tree state.
|
|
80
|
+
*
|
|
81
|
+
* @returns Git tree SHA-1 hash of HEAD commit
|
|
82
|
+
* @throws Error if not in a git repository or HEAD doesn't exist
|
|
83
|
+
*/
|
|
84
|
+
export async function getHeadTreeHash() {
|
|
85
|
+
try {
|
|
86
|
+
const treeHash = execSync('git rev-parse HEAD^{tree}', GIT_OPTIONS).trim();
|
|
87
|
+
return treeHash;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
91
|
+
throw new Error(`Failed to get HEAD tree hash: ${errorMessage}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if working tree has any changes compared to HEAD
|
|
96
|
+
*
|
|
97
|
+
* @returns true if working tree differs from HEAD, false if clean
|
|
98
|
+
*/
|
|
99
|
+
export async function hasWorkingTreeChanges() {
|
|
100
|
+
try {
|
|
101
|
+
const workingTreeHash = await getGitTreeHash();
|
|
102
|
+
const headTreeHash = await getHeadTreeHash();
|
|
103
|
+
return workingTreeHash !== headTreeHash;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// If we can't determine, assume there are changes (safe default)
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=tree-hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-hash.js","sourceRoot":"","sources":["../src/tree-hash.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibe-validate/git",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Git utilities for vibe-validate - tree hash calculation, branch sync, and post-merge cleanup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"git",
|
|
27
|
+
"validation",
|
|
28
|
+
"tree-hash",
|
|
29
|
+
"branch-sync",
|
|
30
|
+
"cleanup",
|
|
31
|
+
"sdlc"
|
|
32
|
+
],
|
|
33
|
+
"author": "Jeff Dutton",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/jdutton/vibe-validate.git",
|
|
38
|
+
"directory": "packages/git"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.0.0"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.0.0",
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"vitest": "^2.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|