gitx.do 0.0.1 → 0.0.3
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/dist/cli/commands/blame.d.ts +259 -0
- package/dist/cli/commands/blame.d.ts.map +1 -0
- package/dist/cli/commands/blame.js +609 -0
- package/dist/cli/commands/blame.js.map +1 -0
- package/dist/cli/commands/branch.d.ts +249 -0
- package/dist/cli/commands/branch.d.ts.map +1 -0
- package/dist/cli/commands/branch.js +693 -0
- package/dist/cli/commands/branch.js.map +1 -0
- package/dist/cli/commands/commit.d.ts +182 -0
- package/dist/cli/commands/commit.d.ts.map +1 -0
- package/dist/cli/commands/commit.js +437 -0
- package/dist/cli/commands/commit.js.map +1 -0
- package/dist/cli/commands/diff.d.ts +464 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/diff.js +958 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/log.d.ts +239 -0
- package/dist/cli/commands/log.d.ts.map +1 -0
- package/dist/cli/commands/log.js +535 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/review.d.ts +457 -0
- package/dist/cli/commands/review.d.ts.map +1 -0
- package/dist/cli/commands/review.js +533 -0
- package/dist/cli/commands/review.js.map +1 -0
- package/dist/cli/commands/status.d.ts +269 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +493 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/web.d.ts +199 -0
- package/dist/cli/commands/web.d.ts.map +1 -0
- package/dist/cli/commands/web.js +696 -0
- package/dist/cli/commands/web.js.map +1 -0
- package/dist/cli/fs-adapter.d.ts +656 -0
- package/dist/cli/fs-adapter.d.ts.map +1 -0
- package/dist/cli/fs-adapter.js +1179 -0
- package/dist/cli/fs-adapter.js.map +1 -0
- package/dist/cli/index.d.ts +387 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +523 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/components/DiffView.d.ts +7 -0
- package/dist/cli/ui/components/DiffView.d.ts.map +1 -0
- package/dist/cli/ui/components/DiffView.js +11 -0
- package/dist/cli/ui/components/DiffView.js.map +1 -0
- package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -0
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -0
- package/dist/cli/ui/components/ErrorDisplay.js +11 -0
- package/dist/cli/ui/components/ErrorDisplay.js.map +1 -0
- package/dist/cli/ui/components/FuzzySearch.d.ts +9 -0
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -0
- package/dist/cli/ui/components/FuzzySearch.js +12 -0
- package/dist/cli/ui/components/FuzzySearch.js.map +1 -0
- package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -0
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -0
- package/dist/cli/ui/components/LoadingSpinner.js +10 -0
- package/dist/cli/ui/components/LoadingSpinner.js.map +1 -0
- package/dist/cli/ui/components/NavigationList.d.ts +9 -0
- package/dist/cli/ui/components/NavigationList.d.ts.map +1 -0
- package/dist/cli/ui/components/NavigationList.js +11 -0
- package/dist/cli/ui/components/NavigationList.js.map +1 -0
- package/dist/cli/ui/components/ScrollableContent.d.ts +8 -0
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -0
- package/dist/cli/ui/components/ScrollableContent.js +11 -0
- package/dist/cli/ui/components/ScrollableContent.js.map +1 -0
- package/dist/cli/ui/components/index.d.ts +7 -0
- package/dist/cli/ui/components/index.d.ts.map +1 -0
- package/dist/cli/ui/components/index.js +9 -0
- package/dist/cli/ui/components/index.js.map +1 -0
- package/dist/cli/ui/terminal-ui.d.ts +52 -0
- package/dist/cli/ui/terminal-ui.d.ts.map +1 -0
- package/dist/cli/ui/terminal-ui.js +121 -0
- package/dist/cli/ui/terminal-ui.js.map +1 -0
- package/dist/durable-object/object-store.d.ts +401 -23
- package/dist/durable-object/object-store.d.ts.map +1 -1
- package/dist/durable-object/object-store.js +414 -25
- package/dist/durable-object/object-store.js.map +1 -1
- package/dist/durable-object/schema.d.ts +188 -0
- package/dist/durable-object/schema.d.ts.map +1 -1
- package/dist/durable-object/schema.js +160 -0
- package/dist/durable-object/schema.js.map +1 -1
- package/dist/durable-object/wal.d.ts +336 -31
- package/dist/durable-object/wal.d.ts.map +1 -1
- package/dist/durable-object/wal.js +272 -27
- package/dist/durable-object/wal.js.map +1 -1
- package/dist/index.d.ts +379 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +379 -7
- package/dist/index.js.map +1 -1
- package/dist/mcp/adapter.d.ts +579 -38
- package/dist/mcp/adapter.d.ts.map +1 -1
- package/dist/mcp/adapter.js +426 -33
- package/dist/mcp/adapter.js.map +1 -1
- package/dist/mcp/sandbox.d.ts +532 -29
- package/dist/mcp/sandbox.d.ts.map +1 -1
- package/dist/mcp/sandbox.js +389 -22
- package/dist/mcp/sandbox.js.map +1 -1
- package/dist/mcp/sdk-adapter.d.ts +478 -56
- package/dist/mcp/sdk-adapter.d.ts.map +1 -1
- package/dist/mcp/sdk-adapter.js +346 -44
- package/dist/mcp/sdk-adapter.js.map +1 -1
- package/dist/mcp/tools.d.ts +445 -30
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +363 -33
- package/dist/mcp/tools.js.map +1 -1
- package/dist/ops/blame.d.ts +424 -21
- package/dist/ops/blame.d.ts.map +1 -1
- package/dist/ops/blame.js +303 -20
- package/dist/ops/blame.js.map +1 -1
- package/dist/ops/branch.d.ts +583 -32
- package/dist/ops/branch.d.ts.map +1 -1
- package/dist/ops/branch.js +365 -23
- package/dist/ops/branch.js.map +1 -1
- package/dist/ops/commit-traversal.d.ts +164 -24
- package/dist/ops/commit-traversal.d.ts.map +1 -1
- package/dist/ops/commit-traversal.js +68 -2
- package/dist/ops/commit-traversal.js.map +1 -1
- package/dist/ops/commit.d.ts +387 -53
- package/dist/ops/commit.d.ts.map +1 -1
- package/dist/ops/commit.js +249 -29
- package/dist/ops/commit.js.map +1 -1
- package/dist/ops/merge-base.d.ts +195 -21
- package/dist/ops/merge-base.d.ts.map +1 -1
- package/dist/ops/merge-base.js +122 -12
- package/dist/ops/merge-base.js.map +1 -1
- package/dist/ops/merge.d.ts +600 -130
- package/dist/ops/merge.d.ts.map +1 -1
- package/dist/ops/merge.js +408 -60
- package/dist/ops/merge.js.map +1 -1
- package/dist/ops/tag.d.ts +67 -2
- package/dist/ops/tag.d.ts.map +1 -1
- package/dist/ops/tag.js +42 -1
- package/dist/ops/tag.js.map +1 -1
- package/dist/ops/tree-builder.d.ts +102 -6
- package/dist/ops/tree-builder.d.ts.map +1 -1
- package/dist/ops/tree-builder.js +30 -5
- package/dist/ops/tree-builder.js.map +1 -1
- package/dist/ops/tree-diff.d.ts +50 -2
- package/dist/ops/tree-diff.d.ts.map +1 -1
- package/dist/ops/tree-diff.js +50 -2
- package/dist/ops/tree-diff.js.map +1 -1
- package/dist/pack/delta.d.ts +211 -39
- package/dist/pack/delta.d.ts.map +1 -1
- package/dist/pack/delta.js +232 -46
- package/dist/pack/delta.js.map +1 -1
- package/dist/pack/format.d.ts +390 -28
- package/dist/pack/format.d.ts.map +1 -1
- package/dist/pack/format.js +344 -33
- package/dist/pack/format.js.map +1 -1
- package/dist/pack/full-generation.d.ts +313 -28
- package/dist/pack/full-generation.d.ts.map +1 -1
- package/dist/pack/full-generation.js +238 -19
- package/dist/pack/full-generation.js.map +1 -1
- package/dist/pack/generation.d.ts +346 -23
- package/dist/pack/generation.d.ts.map +1 -1
- package/dist/pack/generation.js +269 -21
- package/dist/pack/generation.js.map +1 -1
- package/dist/pack/index.d.ts +407 -86
- package/dist/pack/index.d.ts.map +1 -1
- package/dist/pack/index.js +351 -70
- package/dist/pack/index.js.map +1 -1
- package/dist/refs/branch.d.ts +517 -71
- package/dist/refs/branch.d.ts.map +1 -1
- package/dist/refs/branch.js +410 -26
- package/dist/refs/branch.js.map +1 -1
- package/dist/refs/storage.d.ts +610 -57
- package/dist/refs/storage.d.ts.map +1 -1
- package/dist/refs/storage.js +481 -29
- package/dist/refs/storage.js.map +1 -1
- package/dist/refs/tag.d.ts +677 -67
- package/dist/refs/tag.d.ts.map +1 -1
- package/dist/refs/tag.js +497 -30
- package/dist/refs/tag.js.map +1 -1
- package/dist/storage/lru-cache.d.ts +556 -53
- package/dist/storage/lru-cache.d.ts.map +1 -1
- package/dist/storage/lru-cache.js +439 -36
- package/dist/storage/lru-cache.js.map +1 -1
- package/dist/storage/object-index.d.ts +483 -38
- package/dist/storage/object-index.d.ts.map +1 -1
- package/dist/storage/object-index.js +388 -22
- package/dist/storage/object-index.js.map +1 -1
- package/dist/storage/r2-pack.d.ts +957 -94
- package/dist/storage/r2-pack.d.ts.map +1 -1
- package/dist/storage/r2-pack.js +756 -48
- package/dist/storage/r2-pack.js.map +1 -1
- package/dist/tiered/cdc-pipeline.d.ts +1610 -38
- package/dist/tiered/cdc-pipeline.d.ts.map +1 -1
- package/dist/tiered/cdc-pipeline.js +1131 -22
- package/dist/tiered/cdc-pipeline.js.map +1 -1
- package/dist/tiered/migration.d.ts +903 -41
- package/dist/tiered/migration.d.ts.map +1 -1
- package/dist/tiered/migration.js +646 -24
- package/dist/tiered/migration.js.map +1 -1
- package/dist/tiered/parquet-writer.d.ts +944 -47
- package/dist/tiered/parquet-writer.d.ts.map +1 -1
- package/dist/tiered/parquet-writer.js +667 -39
- package/dist/tiered/parquet-writer.js.map +1 -1
- package/dist/tiered/read-path.d.ts +728 -34
- package/dist/tiered/read-path.d.ts.map +1 -1
- package/dist/tiered/read-path.js +310 -27
- package/dist/tiered/read-path.js.map +1 -1
- package/dist/types/objects.d.ts +457 -0
- package/dist/types/objects.d.ts.map +1 -1
- package/dist/types/objects.js +305 -4
- package/dist/types/objects.js.map +1 -1
- package/dist/types/storage.d.ts +407 -35
- package/dist/types/storage.d.ts.map +1 -1
- package/dist/types/storage.js +27 -3
- package/dist/types/storage.js.map +1 -1
- package/dist/utils/hash.d.ts +133 -12
- package/dist/utils/hash.d.ts.map +1 -1
- package/dist/utils/hash.js +133 -12
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/sha1.d.ts +102 -9
- package/dist/utils/sha1.d.ts.map +1 -1
- package/dist/utils/sha1.js +114 -11
- package/dist/utils/sha1.js.map +1 -1
- package/dist/wire/capabilities.d.ts +896 -88
- package/dist/wire/capabilities.d.ts.map +1 -1
- package/dist/wire/capabilities.js +566 -62
- package/dist/wire/capabilities.js.map +1 -1
- package/dist/wire/pkt-line.d.ts +293 -15
- package/dist/wire/pkt-line.d.ts.map +1 -1
- package/dist/wire/pkt-line.js +251 -15
- package/dist/wire/pkt-line.js.map +1 -1
- package/dist/wire/receive-pack.d.ts +814 -64
- package/dist/wire/receive-pack.d.ts.map +1 -1
- package/dist/wire/receive-pack.js +542 -41
- package/dist/wire/receive-pack.js.map +1 -1
- package/dist/wire/smart-http.d.ts +575 -97
- package/dist/wire/smart-http.d.ts.map +1 -1
- package/dist/wire/smart-http.js +337 -46
- package/dist/wire/smart-http.js.map +1 -1
- package/dist/wire/upload-pack.d.ts +492 -98
- package/dist/wire/upload-pack.d.ts.map +1 -1
- package/dist/wire/upload-pack.js +347 -59
- package/dist/wire/upload-pack.js.map +1 -1
- package/package.json +10 -2
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Git Branch Command
|
|
3
|
+
*
|
|
4
|
+
* This module implements the `gitx branch` command which manages local branches.
|
|
5
|
+
* Features include:
|
|
6
|
+
* - Listing local branches with optional verbose output
|
|
7
|
+
* - Creating new branches from HEAD or a specific start point
|
|
8
|
+
* - Deleting branches (with merge safety check or force)
|
|
9
|
+
* - Renaming branches
|
|
10
|
+
* - Showing upstream tracking information
|
|
11
|
+
*
|
|
12
|
+
* @module cli/commands/branch
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // List all branches
|
|
16
|
+
* const branches = await listBranches(cwd)
|
|
17
|
+
* for (const branch of branches) {
|
|
18
|
+
* console.log(branch.isCurrent ? '* ' : ' ', branch.name)
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Create a new branch
|
|
23
|
+
* await createBranch(cwd, 'feature/new-feature')
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Delete a merged branch
|
|
27
|
+
* await deleteBranch(cwd, 'old-feature', { force: false })
|
|
28
|
+
*/
|
|
29
|
+
import * as fs from 'fs/promises';
|
|
30
|
+
import * as path from 'path';
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Helper Functions
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Check if a directory is a git repository
|
|
36
|
+
*/
|
|
37
|
+
async function isGitRepo(cwd) {
|
|
38
|
+
try {
|
|
39
|
+
const gitDir = path.join(cwd, '.git');
|
|
40
|
+
const stat = await fs.stat(gitDir);
|
|
41
|
+
return stat.isDirectory();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the current HEAD - either a branch name or a commit SHA (detached HEAD)
|
|
49
|
+
*/
|
|
50
|
+
async function getCurrentHead(cwd) {
|
|
51
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
52
|
+
const headContent = (await fs.readFile(headPath, 'utf8')).trim();
|
|
53
|
+
if (headContent.startsWith('ref: refs/heads/')) {
|
|
54
|
+
return { branch: headContent.slice('ref: refs/heads/'.length), sha: null };
|
|
55
|
+
}
|
|
56
|
+
// Detached HEAD - return the SHA
|
|
57
|
+
return { branch: null, sha: headContent };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read a branch ref file and return the SHA
|
|
61
|
+
*/
|
|
62
|
+
async function readBranchSha(cwd, branchName) {
|
|
63
|
+
const refPath = path.join(cwd, '.git', 'refs', 'heads', ...branchName.split('/'));
|
|
64
|
+
try {
|
|
65
|
+
return (await fs.readFile(refPath, 'utf8')).trim();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all local branch names by recursively reading refs/heads
|
|
73
|
+
*/
|
|
74
|
+
async function getAllBranchNames(cwd, subPath = '') {
|
|
75
|
+
const headsDir = path.join(cwd, '.git', 'refs', 'heads', subPath);
|
|
76
|
+
const branches = [];
|
|
77
|
+
try {
|
|
78
|
+
const entries = await fs.readdir(headsDir, { withFileTypes: true });
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const fullName = subPath ? `${subPath}/${entry.name}` : entry.name;
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
// Recursively read subdirectories (for branches like feature/xxx)
|
|
83
|
+
const subBranches = await getAllBranchNames(cwd, fullName);
|
|
84
|
+
branches.push(...subBranches);
|
|
85
|
+
}
|
|
86
|
+
else if (entry.isFile()) {
|
|
87
|
+
branches.push(fullName);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Directory doesn't exist or can't be read
|
|
93
|
+
}
|
|
94
|
+
return branches.sort();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validate a branch name according to git rules
|
|
98
|
+
*/
|
|
99
|
+
function isValidBranchName(name) {
|
|
100
|
+
// Cannot start with a dash
|
|
101
|
+
if (name.startsWith('-'))
|
|
102
|
+
return false;
|
|
103
|
+
// Cannot contain double dots
|
|
104
|
+
if (name.includes('..'))
|
|
105
|
+
return false;
|
|
106
|
+
// Cannot end with .lock
|
|
107
|
+
if (name.endsWith('.lock'))
|
|
108
|
+
return false;
|
|
109
|
+
// Cannot contain spaces
|
|
110
|
+
if (name.includes(' '))
|
|
111
|
+
return false;
|
|
112
|
+
// Cannot contain control characters (ASCII 0-31)
|
|
113
|
+
for (let i = 0; i < name.length; i++) {
|
|
114
|
+
const code = name.charCodeAt(i);
|
|
115
|
+
if (code < 32)
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
// Cannot contain tilde, caret, colon, question, asterisk, open bracket, backslash
|
|
119
|
+
if (/[~^:?*\[\\]/.test(name))
|
|
120
|
+
return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse git config to get branch tracking info
|
|
125
|
+
*/
|
|
126
|
+
async function parseGitConfig(cwd) {
|
|
127
|
+
const configPath = path.join(cwd, '.git', 'config');
|
|
128
|
+
const trackingInfo = new Map();
|
|
129
|
+
try {
|
|
130
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
131
|
+
const lines = content.split('\n');
|
|
132
|
+
let currentBranch = null;
|
|
133
|
+
let currentConfig = {};
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
const branchMatch = line.match(/^\[branch "(.+)"\]$/);
|
|
136
|
+
if (branchMatch) {
|
|
137
|
+
// Save previous branch config if complete
|
|
138
|
+
if (currentBranch && currentConfig.remote && currentConfig.merge) {
|
|
139
|
+
trackingInfo.set(currentBranch, {
|
|
140
|
+
remote: currentConfig.remote,
|
|
141
|
+
merge: currentConfig.merge
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
currentBranch = branchMatch[1];
|
|
145
|
+
currentConfig = {};
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (currentBranch) {
|
|
149
|
+
const remoteMatch = line.match(/^\s*remote\s*=\s*(.+)$/);
|
|
150
|
+
if (remoteMatch) {
|
|
151
|
+
currentConfig.remote = remoteMatch[1];
|
|
152
|
+
}
|
|
153
|
+
const mergeMatch = line.match(/^\s*merge\s*=\s*(.+)$/);
|
|
154
|
+
if (mergeMatch) {
|
|
155
|
+
currentConfig.merge = mergeMatch[1];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Check for new section
|
|
159
|
+
if (line.match(/^\[/) && !line.match(/^\[branch "/)) {
|
|
160
|
+
// Save previous branch config if complete
|
|
161
|
+
if (currentBranch && currentConfig.remote && currentConfig.merge) {
|
|
162
|
+
trackingInfo.set(currentBranch, {
|
|
163
|
+
remote: currentConfig.remote,
|
|
164
|
+
merge: currentConfig.merge
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
currentBranch = null;
|
|
168
|
+
currentConfig = {};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Save last branch config if complete
|
|
172
|
+
if (currentBranch && currentConfig.remote && currentConfig.merge) {
|
|
173
|
+
trackingInfo.set(currentBranch, {
|
|
174
|
+
remote: currentConfig.remote,
|
|
175
|
+
merge: currentConfig.merge
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Config doesn't exist or can't be read
|
|
181
|
+
}
|
|
182
|
+
return trackingInfo;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if a remote tracking ref exists
|
|
186
|
+
*/
|
|
187
|
+
async function remoteRefExists(cwd, remote, branch) {
|
|
188
|
+
const refPath = path.join(cwd, '.git', 'refs', 'remotes', remote, branch);
|
|
189
|
+
try {
|
|
190
|
+
await fs.stat(refPath);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Read ahead/behind counts from mock file (for testing purposes)
|
|
199
|
+
*/
|
|
200
|
+
async function getAheadBehind(cwd, branchName) {
|
|
201
|
+
const mockPath = path.join(cwd, '.git', `mock-ahead-behind-${branchName}`);
|
|
202
|
+
try {
|
|
203
|
+
const content = await fs.readFile(mockPath, 'utf8');
|
|
204
|
+
const aheadMatch = content.match(/ahead=(\d+)/);
|
|
205
|
+
const behindMatch = content.match(/behind=(\d+)/);
|
|
206
|
+
return {
|
|
207
|
+
ahead: aheadMatch ? parseInt(aheadMatch[1], 10) : 0,
|
|
208
|
+
behind: behindMatch ? parseInt(behindMatch[1], 10) : 0
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Resolve a ref to a SHA - can be a branch name, short SHA, or full SHA
|
|
217
|
+
*/
|
|
218
|
+
async function resolveRef(cwd, ref) {
|
|
219
|
+
// First check if it's a branch name
|
|
220
|
+
const branchSha = await readBranchSha(cwd, ref);
|
|
221
|
+
if (branchSha) {
|
|
222
|
+
return branchSha;
|
|
223
|
+
}
|
|
224
|
+
// Check if it's a valid SHA (full or prefix)
|
|
225
|
+
// For this mock implementation, we check existing branches for a SHA prefix match
|
|
226
|
+
const branches = await getAllBranchNames(cwd);
|
|
227
|
+
for (const branch of branches) {
|
|
228
|
+
const sha = await readBranchSha(cwd, branch);
|
|
229
|
+
if (sha && sha.startsWith(ref)) {
|
|
230
|
+
return sha;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Branch Command Handler
|
|
237
|
+
// ============================================================================
|
|
238
|
+
/**
|
|
239
|
+
* Execute the branch command from the CLI.
|
|
240
|
+
*
|
|
241
|
+
* @description Main entry point for the `gitx branch` command. Handles all
|
|
242
|
+
* branch operations based on command-line flags:
|
|
243
|
+
* - No flags: List branches
|
|
244
|
+
* - `-v`: List with commit info
|
|
245
|
+
* - `-vv`: List with upstream info
|
|
246
|
+
* - `-d <name>`: Delete branch (safe)
|
|
247
|
+
* - `-D <name>`: Delete branch (force)
|
|
248
|
+
* - `-m <old> <new>`: Rename branch
|
|
249
|
+
* - `<name> [start]`: Create branch
|
|
250
|
+
*
|
|
251
|
+
* @param ctx - Command context with cwd, args, options, and output functions
|
|
252
|
+
* @returns Promise that resolves when command completes
|
|
253
|
+
* @throws {Error} If not in a git repository
|
|
254
|
+
* @throws {Error} If branch operation fails (see individual functions)
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* // CLI usage examples
|
|
258
|
+
* // gitx branch - List branches
|
|
259
|
+
* // gitx branch -v - List with SHAs
|
|
260
|
+
* // gitx branch feature/auth - Create branch
|
|
261
|
+
* // gitx branch -d old-branch - Delete merged branch
|
|
262
|
+
* // gitx branch -m old new - Rename branch
|
|
263
|
+
*/
|
|
264
|
+
export async function branchCommand(ctx) {
|
|
265
|
+
const { cwd, args, options, stdout, stderr } = ctx;
|
|
266
|
+
// Handle -m (rename) flag
|
|
267
|
+
// Note: -m is defined as `-m <message>` in CLI for commit, but for branch it means rename
|
|
268
|
+
// When options.m has a string value, it captured the first arg (old name)
|
|
269
|
+
if (options.m !== undefined && options.m !== false) {
|
|
270
|
+
// When -m captures a value, the old name is in options.m and new name is in args[0]
|
|
271
|
+
// When -m doesn't capture (just boolean), both names are in args
|
|
272
|
+
let oldName;
|
|
273
|
+
let newName;
|
|
274
|
+
if (typeof options.m === 'string') {
|
|
275
|
+
// -m captured the old name as its value
|
|
276
|
+
oldName = options.m;
|
|
277
|
+
if (args.length < 1) {
|
|
278
|
+
throw new Error('Usage: gitx branch -m <old-name> <new-name>');
|
|
279
|
+
}
|
|
280
|
+
newName = args[0];
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// -m is boolean true, both names in args
|
|
284
|
+
if (args.length < 2) {
|
|
285
|
+
throw new Error('Usage: gitx branch -m <old-name> <new-name>');
|
|
286
|
+
}
|
|
287
|
+
oldName = args[0];
|
|
288
|
+
newName = args[1];
|
|
289
|
+
}
|
|
290
|
+
await renameBranch(cwd, oldName, newName);
|
|
291
|
+
stdout(`Branch '${oldName}' renamed to '${newName}'`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
// Handle -d (delete) flag
|
|
295
|
+
if (options.d) {
|
|
296
|
+
if (args.length < 1) {
|
|
297
|
+
throw new Error('Usage: gitx branch -d <branch-name>');
|
|
298
|
+
}
|
|
299
|
+
const branchName = args[0];
|
|
300
|
+
await deleteBranch(cwd, branchName, { force: false });
|
|
301
|
+
stdout(`Deleted branch ${branchName}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// Handle -D (force delete) flag
|
|
305
|
+
if (options.D) {
|
|
306
|
+
if (args.length < 1) {
|
|
307
|
+
throw new Error('Usage: gitx branch -D <branch-name>');
|
|
308
|
+
}
|
|
309
|
+
const branchName = args[0];
|
|
310
|
+
await deleteBranch(cwd, branchName, { force: true });
|
|
311
|
+
stdout(`Deleted branch ${branchName}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Handle branch creation (when args provided without flags)
|
|
315
|
+
if (args.length > 0 && !options.list) {
|
|
316
|
+
const branchName = args[0];
|
|
317
|
+
const startPoint = args[1];
|
|
318
|
+
await createBranch(cwd, branchName, startPoint);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// Default: list branches
|
|
322
|
+
// Note: -vv is parsed as -v -v by cac, resulting in verbose being an array [true, true]
|
|
323
|
+
const isVeryVerbose = options.vv ||
|
|
324
|
+
(Array.isArray(options.v) && options.v.length >= 2) ||
|
|
325
|
+
(Array.isArray(options.verbose) && options.verbose.length >= 2);
|
|
326
|
+
const isVerbose = options.verbose || options.v;
|
|
327
|
+
const listOptions = {
|
|
328
|
+
verbose: isVerbose,
|
|
329
|
+
veryVerbose: isVeryVerbose
|
|
330
|
+
};
|
|
331
|
+
let branches;
|
|
332
|
+
if (listOptions.veryVerbose) {
|
|
333
|
+
branches = await getBranchesWithUpstream(cwd);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
branches = await listBranches(cwd, listOptions);
|
|
337
|
+
}
|
|
338
|
+
// Format and output
|
|
339
|
+
for (const branch of branches) {
|
|
340
|
+
let line = branch.isCurrent ? '* ' : ' ';
|
|
341
|
+
line += branch.name;
|
|
342
|
+
if (listOptions.verbose || listOptions.veryVerbose) {
|
|
343
|
+
// Add short SHA
|
|
344
|
+
line += ` ${branch.sha.substring(0, 7)}`;
|
|
345
|
+
}
|
|
346
|
+
if (listOptions.veryVerbose && branch.upstream) {
|
|
347
|
+
line += ` [${branch.upstream}`;
|
|
348
|
+
if (branch.upstreamGone) {
|
|
349
|
+
line += ': gone';
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const parts = [];
|
|
353
|
+
if (branch.ahead && branch.ahead > 0) {
|
|
354
|
+
parts.push(`ahead ${branch.ahead}`);
|
|
355
|
+
}
|
|
356
|
+
if (branch.behind && branch.behind > 0) {
|
|
357
|
+
parts.push(`behind ${branch.behind}`);
|
|
358
|
+
}
|
|
359
|
+
if (parts.length > 0) {
|
|
360
|
+
line += `: ${parts.join(', ')}`;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
line += ']';
|
|
364
|
+
}
|
|
365
|
+
stdout(line);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* List all local branches.
|
|
370
|
+
*
|
|
371
|
+
* @description Reads all branch refs from .git/refs/heads and returns
|
|
372
|
+
* information about each branch including which one is currently checked out.
|
|
373
|
+
*
|
|
374
|
+
* @param cwd - Working directory (repository root)
|
|
375
|
+
* @param options - List options (currently unused, reserved for future use)
|
|
376
|
+
* @returns Promise resolving to array of branch info, sorted alphabetically
|
|
377
|
+
* @throws {Error} If not in a git repository
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* const branches = await listBranches('/path/to/repo')
|
|
381
|
+
* const current = branches.find(b => b.isCurrent)
|
|
382
|
+
* console.log(`Current branch: ${current?.name}`)
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* // List all branch names
|
|
386
|
+
* const branches = await listBranches(cwd)
|
|
387
|
+
* console.log(branches.map(b => b.name).join('\n'))
|
|
388
|
+
*/
|
|
389
|
+
export async function listBranches(cwd, options) {
|
|
390
|
+
if (!(await isGitRepo(cwd))) {
|
|
391
|
+
throw new Error('Not a git repository');
|
|
392
|
+
}
|
|
393
|
+
const currentHead = await getCurrentHead(cwd);
|
|
394
|
+
const branchNames = await getAllBranchNames(cwd);
|
|
395
|
+
const branches = [];
|
|
396
|
+
for (const name of branchNames) {
|
|
397
|
+
const sha = await readBranchSha(cwd, name);
|
|
398
|
+
if (sha) {
|
|
399
|
+
branches.push({
|
|
400
|
+
name,
|
|
401
|
+
sha,
|
|
402
|
+
isCurrent: currentHead.branch === name
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return branches;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Create a new branch.
|
|
410
|
+
*
|
|
411
|
+
* @description Creates a new branch ref pointing to either HEAD or a specified
|
|
412
|
+
* commit/branch. The branch name is validated against git naming rules.
|
|
413
|
+
*
|
|
414
|
+
* Branch names cannot:
|
|
415
|
+
* - Start with a dash (-)
|
|
416
|
+
* - Contain double dots (..)
|
|
417
|
+
* - End with .lock
|
|
418
|
+
* - Contain spaces, tildes, carets, colons, question marks, asterisks, or backslashes
|
|
419
|
+
*
|
|
420
|
+
* @param cwd - Working directory (repository root)
|
|
421
|
+
* @param name - Name for the new branch
|
|
422
|
+
* @param startPoint - Optional commit SHA or branch name to start from (defaults to HEAD)
|
|
423
|
+
* @returns Promise that resolves when branch is created
|
|
424
|
+
* @throws {Error} If not in a git repository
|
|
425
|
+
* @throws {Error} If branch name is invalid
|
|
426
|
+
* @throws {Error} If branch already exists
|
|
427
|
+
* @throws {Error} If startPoint reference is invalid
|
|
428
|
+
* @throws {Error} If HEAD cannot be resolved
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* // Create branch from HEAD
|
|
432
|
+
* await createBranch(cwd, 'feature/new-feature')
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* // Create branch from specific commit
|
|
436
|
+
* await createBranch(cwd, 'hotfix/bug-123', 'abc1234')
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* // Create branch from another branch
|
|
440
|
+
* await createBranch(cwd, 'feature/derived', 'main')
|
|
441
|
+
*/
|
|
442
|
+
export async function createBranch(cwd, name, startPoint) {
|
|
443
|
+
if (!(await isGitRepo(cwd))) {
|
|
444
|
+
throw new Error('Not a git repository');
|
|
445
|
+
}
|
|
446
|
+
// Validate branch name
|
|
447
|
+
if (!isValidBranchName(name)) {
|
|
448
|
+
throw new Error(`Invalid branch name: '${name}'`);
|
|
449
|
+
}
|
|
450
|
+
// Check if branch already exists
|
|
451
|
+
const existingSha = await readBranchSha(cwd, name);
|
|
452
|
+
if (existingSha) {
|
|
453
|
+
throw new Error(`A branch named '${name}' already exists`);
|
|
454
|
+
}
|
|
455
|
+
// Determine the SHA to point to
|
|
456
|
+
let targetSha;
|
|
457
|
+
if (startPoint) {
|
|
458
|
+
const resolved = await resolveRef(cwd, startPoint);
|
|
459
|
+
if (!resolved) {
|
|
460
|
+
throw new Error(`Invalid reference: '${startPoint}'`);
|
|
461
|
+
}
|
|
462
|
+
targetSha = resolved;
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// Default to HEAD
|
|
466
|
+
const head = await getCurrentHead(cwd);
|
|
467
|
+
if (head.branch) {
|
|
468
|
+
const branchSha = await readBranchSha(cwd, head.branch);
|
|
469
|
+
if (!branchSha) {
|
|
470
|
+
throw new Error('Failed to resolve HEAD');
|
|
471
|
+
}
|
|
472
|
+
targetSha = branchSha;
|
|
473
|
+
}
|
|
474
|
+
else if (head.sha) {
|
|
475
|
+
targetSha = head.sha;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
throw new Error('Failed to resolve HEAD');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Create the branch ref file
|
|
482
|
+
const refPath = path.join(cwd, '.git', 'refs', 'heads', ...name.split('/'));
|
|
483
|
+
await fs.mkdir(path.dirname(refPath), { recursive: true });
|
|
484
|
+
await fs.writeFile(refPath, targetSha + '\n');
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Delete a branch.
|
|
488
|
+
*
|
|
489
|
+
* @description Deletes a local branch ref. By default, includes a safety check
|
|
490
|
+
* to prevent deleting unmerged branches. Use `force: true` to override.
|
|
491
|
+
*
|
|
492
|
+
* Safety checks:
|
|
493
|
+
* - Cannot delete the currently checked out branch
|
|
494
|
+
* - Cannot delete unmerged branch unless force is true
|
|
495
|
+
*
|
|
496
|
+
* @param cwd - Working directory (repository root)
|
|
497
|
+
* @param name - Name of the branch to delete
|
|
498
|
+
* @param options - Delete options controlling safety behavior
|
|
499
|
+
* @param options.force - If true, skip merge check and force delete
|
|
500
|
+
* @returns Promise that resolves when branch is deleted
|
|
501
|
+
* @throws {Error} If not in a git repository
|
|
502
|
+
* @throws {Error} If branch does not exist
|
|
503
|
+
* @throws {Error} If trying to delete the current branch
|
|
504
|
+
* @throws {Error} If branch is not fully merged and force is false
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* // Delete a merged branch (safe)
|
|
508
|
+
* await deleteBranch(cwd, 'old-feature', { force: false })
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* // Force delete an unmerged branch
|
|
512
|
+
* await deleteBranch(cwd, 'abandoned-work', { force: true })
|
|
513
|
+
*/
|
|
514
|
+
export async function deleteBranch(cwd, name, options) {
|
|
515
|
+
if (!(await isGitRepo(cwd))) {
|
|
516
|
+
throw new Error('Not a git repository');
|
|
517
|
+
}
|
|
518
|
+
// Check if branch exists
|
|
519
|
+
const branchSha = await readBranchSha(cwd, name);
|
|
520
|
+
if (!branchSha) {
|
|
521
|
+
throw new Error(`Branch '${name}' not found`);
|
|
522
|
+
}
|
|
523
|
+
// Check if this is the current branch
|
|
524
|
+
const head = await getCurrentHead(cwd);
|
|
525
|
+
if (head.branch === name) {
|
|
526
|
+
throw new Error(`Cannot delete branch '${name}': it is currently checked out`);
|
|
527
|
+
}
|
|
528
|
+
// Check if branch is fully merged (only for non-force delete)
|
|
529
|
+
if (!options.force) {
|
|
530
|
+
// Get the current branch's SHA
|
|
531
|
+
let currentSha = null;
|
|
532
|
+
if (head.branch) {
|
|
533
|
+
currentSha = await readBranchSha(cwd, head.branch);
|
|
534
|
+
}
|
|
535
|
+
else if (head.sha) {
|
|
536
|
+
currentSha = head.sha;
|
|
537
|
+
}
|
|
538
|
+
// Simple merge check: if the branch SHA differs from current branch SHA, consider it unmerged
|
|
539
|
+
if (currentSha && branchSha !== currentSha) {
|
|
540
|
+
throw new Error(`Branch '${name}' is not fully merged. Use -D to force delete.`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Delete the branch ref file
|
|
544
|
+
const refPath = path.join(cwd, '.git', 'refs', 'heads', ...name.split('/'));
|
|
545
|
+
await fs.rm(refPath);
|
|
546
|
+
// Try to clean up empty parent directories
|
|
547
|
+
let parentDir = path.dirname(refPath);
|
|
548
|
+
const headsDir = path.join(cwd, '.git', 'refs', 'heads');
|
|
549
|
+
while (parentDir !== headsDir) {
|
|
550
|
+
try {
|
|
551
|
+
const entries = await fs.readdir(parentDir);
|
|
552
|
+
if (entries.length === 0) {
|
|
553
|
+
await fs.rmdir(parentDir);
|
|
554
|
+
parentDir = path.dirname(parentDir);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Rename a branch.
|
|
567
|
+
*
|
|
568
|
+
* @description Renames a branch by creating a new ref with the old SHA and
|
|
569
|
+
* deleting the old ref. If renaming the current branch, also updates HEAD.
|
|
570
|
+
*
|
|
571
|
+
* @param cwd - Working directory (repository root)
|
|
572
|
+
* @param oldName - Current branch name
|
|
573
|
+
* @param newName - New branch name (validated against git naming rules)
|
|
574
|
+
* @returns Promise that resolves when branch is renamed
|
|
575
|
+
* @throws {Error} If not in a git repository
|
|
576
|
+
* @throws {Error} If new branch name is invalid
|
|
577
|
+
* @throws {Error} If old branch does not exist
|
|
578
|
+
* @throws {Error} If new branch name already exists
|
|
579
|
+
*
|
|
580
|
+
* @example
|
|
581
|
+
* // Rename a feature branch
|
|
582
|
+
* await renameBranch(cwd, 'feature/old-name', 'feature/new-name')
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* // Rename the current branch
|
|
586
|
+
* await renameBranch(cwd, 'main', 'master') // Also updates HEAD
|
|
587
|
+
*/
|
|
588
|
+
export async function renameBranch(cwd, oldName, newName) {
|
|
589
|
+
if (!(await isGitRepo(cwd))) {
|
|
590
|
+
throw new Error('Not a git repository');
|
|
591
|
+
}
|
|
592
|
+
// Validate new branch name
|
|
593
|
+
if (!isValidBranchName(newName)) {
|
|
594
|
+
throw new Error(`Invalid branch name: '${newName}'`);
|
|
595
|
+
}
|
|
596
|
+
// Check if old branch exists
|
|
597
|
+
const oldSha = await readBranchSha(cwd, oldName);
|
|
598
|
+
if (!oldSha) {
|
|
599
|
+
throw new Error(`Branch '${oldName}' not found`);
|
|
600
|
+
}
|
|
601
|
+
// Check if new branch already exists
|
|
602
|
+
const existingSha = await readBranchSha(cwd, newName);
|
|
603
|
+
if (existingSha) {
|
|
604
|
+
throw new Error(`A branch named '${newName}' already exists`);
|
|
605
|
+
}
|
|
606
|
+
// Create new branch ref
|
|
607
|
+
const newRefPath = path.join(cwd, '.git', 'refs', 'heads', ...newName.split('/'));
|
|
608
|
+
await fs.mkdir(path.dirname(newRefPath), { recursive: true });
|
|
609
|
+
await fs.writeFile(newRefPath, oldSha + '\n');
|
|
610
|
+
// Delete old branch ref
|
|
611
|
+
const oldRefPath = path.join(cwd, '.git', 'refs', 'heads', ...oldName.split('/'));
|
|
612
|
+
await fs.rm(oldRefPath);
|
|
613
|
+
// Update HEAD if renaming current branch
|
|
614
|
+
const head = await getCurrentHead(cwd);
|
|
615
|
+
if (head.branch === oldName) {
|
|
616
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
617
|
+
await fs.writeFile(headPath, `ref: refs/heads/${newName}\n`);
|
|
618
|
+
}
|
|
619
|
+
// Try to clean up empty parent directories of old branch
|
|
620
|
+
let parentDir = path.dirname(oldRefPath);
|
|
621
|
+
const headsDir = path.join(cwd, '.git', 'refs', 'heads');
|
|
622
|
+
while (parentDir !== headsDir) {
|
|
623
|
+
try {
|
|
624
|
+
const entries = await fs.readdir(parentDir);
|
|
625
|
+
if (entries.length === 0) {
|
|
626
|
+
await fs.rmdir(parentDir);
|
|
627
|
+
parentDir = path.dirname(parentDir);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Get branches with upstream tracking information.
|
|
640
|
+
*
|
|
641
|
+
* @description Lists all local branches with additional upstream tracking
|
|
642
|
+
* information including remote name, ahead/behind counts, and whether
|
|
643
|
+
* the upstream branch still exists.
|
|
644
|
+
*
|
|
645
|
+
* This is used for the `-vv` verbose output mode.
|
|
646
|
+
*
|
|
647
|
+
* @param cwd - Working directory (repository root)
|
|
648
|
+
* @returns Promise resolving to branches with upstream info populated
|
|
649
|
+
* @throws {Error} If not in a git repository
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* const branches = await getBranchesWithUpstream(cwd)
|
|
653
|
+
* for (const branch of branches) {
|
|
654
|
+
* if (branch.upstream) {
|
|
655
|
+
* console.log(`${branch.name} tracks ${branch.upstream}`)
|
|
656
|
+
* if (branch.upstreamGone) {
|
|
657
|
+
* console.log(' (upstream deleted)')
|
|
658
|
+
* } else {
|
|
659
|
+
* console.log(` ahead ${branch.ahead}, behind ${branch.behind}`)
|
|
660
|
+
* }
|
|
661
|
+
* }
|
|
662
|
+
* }
|
|
663
|
+
*/
|
|
664
|
+
export async function getBranchesWithUpstream(cwd) {
|
|
665
|
+
if (!(await isGitRepo(cwd))) {
|
|
666
|
+
throw new Error('Not a git repository');
|
|
667
|
+
}
|
|
668
|
+
const branches = await listBranches(cwd);
|
|
669
|
+
const trackingConfig = await parseGitConfig(cwd);
|
|
670
|
+
for (const branch of branches) {
|
|
671
|
+
const tracking = trackingConfig.get(branch.name);
|
|
672
|
+
if (tracking) {
|
|
673
|
+
// Extract the branch name from merge ref (refs/heads/xxx -> xxx)
|
|
674
|
+
const upstreamBranch = tracking.merge.replace(/^refs\/heads\//, '');
|
|
675
|
+
branch.upstream = `${tracking.remote}/${upstreamBranch}`;
|
|
676
|
+
// Check if upstream ref still exists
|
|
677
|
+
const upstreamExists = await remoteRefExists(cwd, tracking.remote, upstreamBranch);
|
|
678
|
+
if (!upstreamExists) {
|
|
679
|
+
branch.upstreamGone = true;
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
// Get ahead/behind counts (from mock file for testing)
|
|
683
|
+
const aheadBehind = await getAheadBehind(cwd, branch.name);
|
|
684
|
+
if (aheadBehind) {
|
|
685
|
+
branch.ahead = aheadBehind.ahead;
|
|
686
|
+
branch.behind = aheadBehind.behind;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return branches;
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=branch.js.map
|