sync-worktrees 1.7.5 → 2.0.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 +10 -5
- package/dist/index.js +2862 -203
- package/dist/index.js.map +7 -1
- package/package.json +19 -11
- package/dist/constants.d.ts +0 -54
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -66
- package/dist/constants.js.map +0 -1
- package/dist/errors/index.d.ts +0 -51
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js +0 -119
- package/dist/errors/index.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/services/config-loader.service.d.ts +0 -9
- package/dist/services/config-loader.service.d.ts.map +0 -1
- package/dist/services/config-loader.service.js +0 -193
- package/dist/services/config-loader.service.js.map +0 -1
- package/dist/services/git.service.d.ts +0 -49
- package/dist/services/git.service.d.ts.map +0 -1
- package/dist/services/git.service.js +0 -746
- package/dist/services/git.service.js.map +0 -1
- package/dist/services/path-resolution.service.d.ts +0 -7
- package/dist/services/path-resolution.service.d.ts.map +0 -1
- package/dist/services/path-resolution.service.js +0 -58
- package/dist/services/path-resolution.service.js.map +0 -1
- package/dist/services/worktree-metadata.service.d.ts +0 -22
- package/dist/services/worktree-metadata.service.d.ts.map +0 -1
- package/dist/services/worktree-metadata.service.js +0 -276
- package/dist/services/worktree-metadata.service.js.map +0 -1
- package/dist/services/worktree-status.service.d.ts +0 -28
- package/dist/services/worktree-status.service.d.ts.map +0 -1
- package/dist/services/worktree-status.service.js +0 -229
- package/dist/services/worktree-status.service.js.map +0 -1
- package/dist/services/worktree-sync.service.d.ts +0 -16
- package/dist/services/worktree-sync.service.d.ts.map +0 -1
- package/dist/services/worktree-sync.service.js +0 -434
- package/dist/services/worktree-sync.service.js.map +0 -1
- package/dist/types/index.d.ts +0 -32
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/types/sync-metadata.d.ts +0 -16
- package/dist/types/sync-metadata.d.ts.map +0 -1
- package/dist/types/sync-metadata.js +0 -3
- package/dist/types/sync-metadata.js.map +0 -1
- package/dist/utils/cli.d.ts +0 -14
- package/dist/utils/cli.d.ts.map +0 -1
- package/dist/utils/cli.js +0 -117
- package/dist/utils/cli.js.map +0 -1
- package/dist/utils/config-generator.d.ts +0 -4
- package/dist/utils/config-generator.d.ts.map +0 -1
- package/dist/utils/config-generator.js +0 -112
- package/dist/utils/config-generator.js.map +0 -1
- package/dist/utils/date-filter.d.ts +0 -10
- package/dist/utils/date-filter.d.ts.map +0 -1
- package/dist/utils/date-filter.js +0 -47
- package/dist/utils/date-filter.js.map +0 -1
- package/dist/utils/git-url.d.ts +0 -15
- package/dist/utils/git-url.d.ts.map +0 -1
- package/dist/utils/git-url.js +0 -46
- package/dist/utils/git-url.js.map +0 -1
- package/dist/utils/interactive.d.ts +0 -3
- package/dist/utils/interactive.d.ts.map +0 -1
- package/dist/utils/interactive.js +0 -195
- package/dist/utils/interactive.js.map +0 -1
- package/dist/utils/lfs-error.d.ts +0 -23
- package/dist/utils/lfs-error.d.ts.map +0 -1
- package/dist/utils/lfs-error.js +0 -45
- package/dist/utils/lfs-error.js.map +0 -1
- package/dist/utils/retry.d.ts +0 -15
- package/dist/utils/retry.d.ts.map +0 -1
- package/dist/utils/retry.js +0 -78
- package/dist/utils/retry.js.map +0 -1
|
@@ -1,746 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.GitService = void 0;
|
|
40
|
-
const fs = __importStar(require("fs/promises"));
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
|
-
const simple_git_1 = __importDefault(require("simple-git"));
|
|
43
|
-
const git_url_1 = require("../utils/git-url");
|
|
44
|
-
const lfs_error_1 = require("../utils/lfs-error");
|
|
45
|
-
const worktree_metadata_service_1 = require("./worktree-metadata.service");
|
|
46
|
-
class GitService {
|
|
47
|
-
config;
|
|
48
|
-
git = null;
|
|
49
|
-
bareRepoPath;
|
|
50
|
-
mainWorktreePath;
|
|
51
|
-
defaultBranch = "main"; // Will be updated after detection
|
|
52
|
-
metadataService;
|
|
53
|
-
constructor(config) {
|
|
54
|
-
this.config = config;
|
|
55
|
-
this.bareRepoPath = this.config.bareRepoDir || (0, git_url_1.getDefaultBareRepoDir)(this.config.repoUrl);
|
|
56
|
-
this.mainWorktreePath = path.join(this.config.worktreeDir, "main"); // Temporary, will be updated
|
|
57
|
-
this.metadataService = new worktree_metadata_service_1.WorktreeMetadataService();
|
|
58
|
-
}
|
|
59
|
-
async initialize() {
|
|
60
|
-
const { repoUrl } = this.config;
|
|
61
|
-
try {
|
|
62
|
-
// Check if bare repo already exists
|
|
63
|
-
await fs.access(path.join(this.bareRepoPath, "HEAD"));
|
|
64
|
-
console.log(`Bare repository at "${this.bareRepoPath}" already exists. Using it.`);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Clone as bare repository
|
|
68
|
-
console.log(`Cloning from "${repoUrl}" as bare repository into "${this.bareRepoPath}"...`);
|
|
69
|
-
await fs.mkdir(path.dirname(this.bareRepoPath), { recursive: true });
|
|
70
|
-
const cloneGit = this.isLfsSkipEnabled() ? (0, simple_git_1.default)().env({ GIT_LFS_SKIP_SMUDGE: "1" }) : (0, simple_git_1.default)();
|
|
71
|
-
await cloneGit.clone(repoUrl, this.bareRepoPath, ["--bare"]);
|
|
72
|
-
console.log("✅ Clone successful.");
|
|
73
|
-
}
|
|
74
|
-
// Configure bare repository for worktrees
|
|
75
|
-
const bareGit = (0, simple_git_1.default)(this.bareRepoPath);
|
|
76
|
-
// Check if fetch config already exists
|
|
77
|
-
try {
|
|
78
|
-
const existingConfig = await bareGit.raw(["config", "--get-all", "remote.origin.fetch"]);
|
|
79
|
-
const targetConfig = "+refs/heads/*:refs/remotes/origin/*";
|
|
80
|
-
if (!existingConfig.includes(targetConfig)) {
|
|
81
|
-
await bareGit.addConfig("remote.origin.fetch", targetConfig);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Config doesn't exist, add it
|
|
86
|
-
await bareGit.addConfig("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*");
|
|
87
|
-
}
|
|
88
|
-
// Fetch all remote branches to ensure they exist locally
|
|
89
|
-
console.log("Fetching remote branches...");
|
|
90
|
-
await bareGit.fetch(["--all"]);
|
|
91
|
-
// Detect the default branch
|
|
92
|
-
this.defaultBranch = await this.detectDefaultBranch(bareGit);
|
|
93
|
-
this.mainWorktreePath = path.join(this.config.worktreeDir, this.defaultBranch);
|
|
94
|
-
console.log(`Detected default branch: ${this.defaultBranch}`);
|
|
95
|
-
// Check if main worktree exists
|
|
96
|
-
let needsMainWorktree = true;
|
|
97
|
-
try {
|
|
98
|
-
const worktrees = await this.getWorktreesFromBare(bareGit);
|
|
99
|
-
needsMainWorktree = !worktrees.some((w) => path.resolve(w.path) === path.resolve(this.mainWorktreePath));
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// If worktree list fails, assume we need main worktree
|
|
103
|
-
}
|
|
104
|
-
if (needsMainWorktree) {
|
|
105
|
-
// Create main worktree if it doesn't exist
|
|
106
|
-
console.log(`Creating ${this.defaultBranch} worktree at "${this.mainWorktreePath}"...`);
|
|
107
|
-
await fs.mkdir(this.config.worktreeDir, { recursive: true });
|
|
108
|
-
// Use absolute path for worktree add to avoid relative path issues
|
|
109
|
-
const absoluteWorktreePath = path.resolve(this.mainWorktreePath);
|
|
110
|
-
try {
|
|
111
|
-
// Check if local branch exists
|
|
112
|
-
const branches = await bareGit.branch();
|
|
113
|
-
const defaultBranchExists = branches.all.includes(this.defaultBranch);
|
|
114
|
-
if (defaultBranchExists) {
|
|
115
|
-
await bareGit.raw(["worktree", "add", absoluteWorktreePath, this.defaultBranch]);
|
|
116
|
-
// Set upstream tracking after creating worktree
|
|
117
|
-
const worktreeGit = this.isLfsSkipEnabled()
|
|
118
|
-
? (0, simple_git_1.default)(absoluteWorktreePath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
119
|
-
: (0, simple_git_1.default)(absoluteWorktreePath);
|
|
120
|
-
await worktreeGit.branch(["--set-upstream-to", `origin/${this.defaultBranch}`, this.defaultBranch]);
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
// Create new branch tracking the remote branch
|
|
124
|
-
await bareGit.raw([
|
|
125
|
-
"worktree",
|
|
126
|
-
"add",
|
|
127
|
-
"--track",
|
|
128
|
-
"-b",
|
|
129
|
-
this.defaultBranch,
|
|
130
|
-
absoluteWorktreePath,
|
|
131
|
-
`origin/${this.defaultBranch}`,
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
const errorMessage = (0, lfs_error_1.getErrorMessage)(error);
|
|
137
|
-
// Check if error is because directory already exists
|
|
138
|
-
if (errorMessage.includes("already exists")) {
|
|
139
|
-
console.log(`${this.defaultBranch} worktree directory already exists at '${absoluteWorktreePath}', skipping creation.`);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// Fallback to simple add if tracking setup fails
|
|
143
|
-
console.warn(`Failed to create ${this.defaultBranch} worktree with tracking, using simple add: ${error}`);
|
|
144
|
-
try {
|
|
145
|
-
await bareGit.raw(["worktree", "add", absoluteWorktreePath, this.defaultBranch]);
|
|
146
|
-
}
|
|
147
|
-
catch (fallbackError) {
|
|
148
|
-
const fallbackErrorMessage = (0, lfs_error_1.getErrorMessage)(fallbackError);
|
|
149
|
-
if (fallbackErrorMessage.includes("already exists")) {
|
|
150
|
-
console.log(`${this.defaultBranch} worktree directory already exists at '${absoluteWorktreePath}', skipping creation.`);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
throw fallbackError;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Ensure the worktree is registered by checking it exists in the list
|
|
159
|
-
const updatedWorktrees = await this.getWorktreesFromBare(bareGit);
|
|
160
|
-
const mainWorktreeRegistered = updatedWorktrees.some((w) => path.resolve(w.path) === path.resolve(this.mainWorktreePath));
|
|
161
|
-
if (!mainWorktreeRegistered) {
|
|
162
|
-
// Only warn in non-test environments as this is common in tests due to Git state
|
|
163
|
-
if (process.env.NODE_ENV !== "test") {
|
|
164
|
-
console.warn(`Main worktree was created but not found in worktree list. This may cause issues.`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// Use the main worktree as our primary git instance
|
|
169
|
-
this.git = (0, simple_git_1.default)(this.mainWorktreePath);
|
|
170
|
-
return this.git;
|
|
171
|
-
}
|
|
172
|
-
getGit() {
|
|
173
|
-
if (!this.git) {
|
|
174
|
-
throw new Error("Git service not initialized. Call initialize() first.");
|
|
175
|
-
}
|
|
176
|
-
return this.git;
|
|
177
|
-
}
|
|
178
|
-
getDefaultBranch() {
|
|
179
|
-
return this.defaultBranch;
|
|
180
|
-
}
|
|
181
|
-
async fetchAll() {
|
|
182
|
-
const git = this.getGit();
|
|
183
|
-
console.log("Fetching latest data from remote...");
|
|
184
|
-
if (this.isLfsSkipEnabled()) {
|
|
185
|
-
await git.env({ GIT_LFS_SKIP_SMUDGE: "1" }).fetch(["--all", "--prune"]);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
await git.fetch(["--all", "--prune"]);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
async fetchBranch(branchName) {
|
|
192
|
-
const git = this.getGit();
|
|
193
|
-
// Update only the remote ref for the branch to keep refs/remotes/origin/* fresh
|
|
194
|
-
if (this.isLfsSkipEnabled()) {
|
|
195
|
-
await git.env({ GIT_LFS_SKIP_SMUDGE: "1" }).fetch(["origin", branchName, "--prune"]);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
await git.fetch(["origin", branchName, "--prune"]);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async getRemoteBranches() {
|
|
202
|
-
const git = this.getGit();
|
|
203
|
-
const branches = await git.branch(["-r"]);
|
|
204
|
-
return branches.all
|
|
205
|
-
.filter((b) => b.startsWith("origin/") && !b.endsWith("/HEAD"))
|
|
206
|
-
.map((b) => b.replace("origin/", ""))
|
|
207
|
-
.filter((b) => b !== "origin" && b.length > 0);
|
|
208
|
-
}
|
|
209
|
-
async getRemoteBranchesWithActivity() {
|
|
210
|
-
const git = this.getGit();
|
|
211
|
-
// Use for-each-ref to get branch names with their last commit dates
|
|
212
|
-
const result = await git.raw([
|
|
213
|
-
"for-each-ref",
|
|
214
|
-
"--format=%(refname:short)|%(committerdate:iso8601)",
|
|
215
|
-
"refs/remotes/origin",
|
|
216
|
-
]);
|
|
217
|
-
const branches = [];
|
|
218
|
-
const lines = result
|
|
219
|
-
.trim()
|
|
220
|
-
.split("\n")
|
|
221
|
-
.filter((line) => line);
|
|
222
|
-
for (const line of lines) {
|
|
223
|
-
const [ref, dateStr] = line.split("|", 2);
|
|
224
|
-
if (ref && dateStr && !ref.endsWith("/HEAD")) {
|
|
225
|
-
const branch = ref.replace("origin/", "");
|
|
226
|
-
// Skip invalid branch names
|
|
227
|
-
if (branch === "origin" || branch.length === 0) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
const lastActivity = new Date(dateStr);
|
|
231
|
-
// Skip if the date is invalid
|
|
232
|
-
if (!isNaN(lastActivity.getTime())) {
|
|
233
|
-
branches.push({ branch, lastActivity });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return branches;
|
|
238
|
-
}
|
|
239
|
-
async createWorktreeMetadata(bareGit, worktreePath, branchName) {
|
|
240
|
-
try {
|
|
241
|
-
const worktreeGit = this.isLfsSkipEnabled()
|
|
242
|
-
? (0, simple_git_1.default)(worktreePath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
243
|
-
: (0, simple_git_1.default)(worktreePath);
|
|
244
|
-
const currentCommit = await worktreeGit.revparse(["HEAD"]);
|
|
245
|
-
const parentCommit = await bareGit.revparse([this.defaultBranch]);
|
|
246
|
-
await this.metadataService.createInitialMetadataFromPath(this.bareRepoPath, worktreePath, currentCommit.trim(), `origin/${branchName}`, this.defaultBranch, parentCommit.trim());
|
|
247
|
-
}
|
|
248
|
-
catch (metadataError) {
|
|
249
|
-
console.error(` - ❌ Failed to create metadata for '${branchName}': ${metadataError}`);
|
|
250
|
-
throw new Error(`Metadata creation failed for ${branchName}. This worktree cannot be auto-managed.`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
async addWorktree(branchName, worktreePath) {
|
|
254
|
-
const bareGit = this.isLfsSkipEnabled()
|
|
255
|
-
? (0, simple_git_1.default)(this.bareRepoPath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
256
|
-
: (0, simple_git_1.default)(this.bareRepoPath);
|
|
257
|
-
// Use absolute path for worktree add to avoid relative path issues
|
|
258
|
-
const absoluteWorktreePath = path.resolve(worktreePath);
|
|
259
|
-
// Ensure parent directory exists for nested branch paths
|
|
260
|
-
await fs.mkdir(path.dirname(absoluteWorktreePath), { recursive: true });
|
|
261
|
-
// Check if directory already exists (could be from a failed previous attempt)
|
|
262
|
-
try {
|
|
263
|
-
await fs.access(absoluteWorktreePath);
|
|
264
|
-
// Directory exists - check if it's already a valid worktree
|
|
265
|
-
const worktrees = await this.getWorktreesFromBare(bareGit);
|
|
266
|
-
const isValidWorktree = worktrees.some((w) => path.resolve(w.path) === absoluteWorktreePath);
|
|
267
|
-
if (isValidWorktree) {
|
|
268
|
-
console.log(` - Worktree for '${branchName}' already exists at '${absoluteWorktreePath}'`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
// Directory exists but is not a valid worktree - clean it up
|
|
273
|
-
console.log(` - Cleaning up orphaned directory at '${absoluteWorktreePath}'`);
|
|
274
|
-
await fs.rm(absoluteWorktreePath, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
// Directory doesn't exist, which is expected - continue with creation
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
// Check if local branch already exists
|
|
282
|
-
const branches = await bareGit.branch();
|
|
283
|
-
const localBranchExists = branches.all.includes(branchName);
|
|
284
|
-
if (localBranchExists || branchName.includes("/")) {
|
|
285
|
-
await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
|
|
286
|
-
const worktreeGit = this.isLfsSkipEnabled()
|
|
287
|
-
? (0, simple_git_1.default)(absoluteWorktreePath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
288
|
-
: (0, simple_git_1.default)(absoluteWorktreePath);
|
|
289
|
-
await worktreeGit.branch(["--set-upstream-to", `origin/${branchName}`, branchName]);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
// Create new branch tracking the remote branch
|
|
293
|
-
await bareGit.raw([
|
|
294
|
-
"worktree",
|
|
295
|
-
"add",
|
|
296
|
-
"--track",
|
|
297
|
-
"-b",
|
|
298
|
-
branchName,
|
|
299
|
-
absoluteWorktreePath,
|
|
300
|
-
`origin/${branchName}`,
|
|
301
|
-
]);
|
|
302
|
-
}
|
|
303
|
-
console.log(` - Created worktree for '${branchName}' with tracking to origin/${branchName}`);
|
|
304
|
-
// Create metadata for the new worktree
|
|
305
|
-
await this.createWorktreeMetadata(bareGit, absoluteWorktreePath, branchName);
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
const errorMessage = (0, lfs_error_1.getErrorMessage)(error);
|
|
309
|
-
// Re-throw metadata creation errors - these are fatal and should not fall back
|
|
310
|
-
if (errorMessage.includes("Metadata creation failed")) {
|
|
311
|
-
throw error;
|
|
312
|
-
}
|
|
313
|
-
// Check if this is an "already registered" error
|
|
314
|
-
if (errorMessage.includes("already registered worktree")) {
|
|
315
|
-
console.warn(` - Worktree already registered but missing. Pruning and retrying...`);
|
|
316
|
-
await bareGit.raw(["worktree", "prune"]);
|
|
317
|
-
// Clean up directory if it exists
|
|
318
|
-
try {
|
|
319
|
-
await fs.rm(absoluteWorktreePath, { recursive: true, force: true });
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// Directory might not exist, ignore
|
|
323
|
-
}
|
|
324
|
-
// Retry once after pruning
|
|
325
|
-
try {
|
|
326
|
-
await bareGit.raw([
|
|
327
|
-
"worktree",
|
|
328
|
-
"add",
|
|
329
|
-
"--track",
|
|
330
|
-
"-b",
|
|
331
|
-
branchName,
|
|
332
|
-
absoluteWorktreePath,
|
|
333
|
-
`origin/${branchName}`,
|
|
334
|
-
]);
|
|
335
|
-
console.log(` - Created worktree for '${branchName}' after pruning`);
|
|
336
|
-
await this.createWorktreeMetadata(bareGit, absoluteWorktreePath, branchName);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
catch (retryError) {
|
|
340
|
-
console.error(` - Failed to create worktree after pruning: ${retryError}`);
|
|
341
|
-
throw retryError;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// If the worktree add fails with tracking, fall back to non-tracking version
|
|
345
|
-
// This handles edge cases where the remote branch might not exist yet
|
|
346
|
-
console.warn(` - Failed to create worktree with tracking, falling back to simple add: ${error}`);
|
|
347
|
-
// Check again if directory exists before fallback attempt
|
|
348
|
-
try {
|
|
349
|
-
await fs.access(absoluteWorktreePath);
|
|
350
|
-
// Directory exists - check if it's already a valid worktree
|
|
351
|
-
const worktrees = await this.getWorktreesFromBare(bareGit);
|
|
352
|
-
const isValidWorktree = worktrees.some((w) => path.resolve(w.path) === absoluteWorktreePath);
|
|
353
|
-
if (isValidWorktree) {
|
|
354
|
-
console.log(` - Worktree for '${branchName}' already exists at '${absoluteWorktreePath}'`);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
// Directory exists but is not a valid worktree - clean it up
|
|
359
|
-
console.log(` - Cleaning up orphaned directory at '${absoluteWorktreePath}' before fallback attempt`);
|
|
360
|
-
await fs.rm(absoluteWorktreePath, { recursive: true, force: true });
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
// Directory doesn't exist, which is expected - continue with fallback
|
|
365
|
-
}
|
|
366
|
-
await bareGit.raw(["worktree", "add", absoluteWorktreePath, branchName]);
|
|
367
|
-
console.log(` - Created worktree for '${branchName}' (without tracking)`);
|
|
368
|
-
// Try to create metadata even without tracking
|
|
369
|
-
await this.createWorktreeMetadata(bareGit, absoluteWorktreePath, branchName);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
async removeWorktree(worktreePath) {
|
|
373
|
-
const bareGit = (0, simple_git_1.default)(this.bareRepoPath);
|
|
374
|
-
await bareGit.raw(["worktree", "remove", worktreePath, "--force"]);
|
|
375
|
-
console.log(` - ✅ Safely removed stale worktree at '${worktreePath}'.`);
|
|
376
|
-
// Clean up metadata using the worktree path
|
|
377
|
-
try {
|
|
378
|
-
await this.metadataService.deleteMetadataFromPath(this.bareRepoPath, worktreePath);
|
|
379
|
-
}
|
|
380
|
-
catch (metadataError) {
|
|
381
|
-
console.warn(`Failed to delete metadata for worktree: ${metadataError}`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async pruneWorktrees() {
|
|
385
|
-
const bareGit = (0, simple_git_1.default)(this.bareRepoPath);
|
|
386
|
-
await bareGit.raw(["worktree", "prune"]);
|
|
387
|
-
console.log("Pruned worktree metadata.");
|
|
388
|
-
}
|
|
389
|
-
async checkWorktreeStatus(worktreePath) {
|
|
390
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
391
|
-
const status = await worktreeGit.status();
|
|
392
|
-
return status.isClean();
|
|
393
|
-
}
|
|
394
|
-
async isDetachedHead(worktreeGit) {
|
|
395
|
-
try {
|
|
396
|
-
const branchSummary = await worktreeGit.branch();
|
|
397
|
-
return !branchSummary.current || branchSummary.detached;
|
|
398
|
-
}
|
|
399
|
-
catch {
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
async hasUnpushedCommits(worktreePath) {
|
|
404
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
405
|
-
try {
|
|
406
|
-
// Check if in detached HEAD state
|
|
407
|
-
if (await this.isDetachedHead(worktreeGit)) {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
// Get the current branch name
|
|
411
|
-
const branchSummary = await worktreeGit.branch();
|
|
412
|
-
const currentBranch = branchSummary.current;
|
|
413
|
-
// Check if upstream is gone
|
|
414
|
-
const upstreamGone = await this.hasUpstreamGone(worktreePath);
|
|
415
|
-
if (upstreamGone) {
|
|
416
|
-
// Load metadata to check for commits after last sync (use path-based method)
|
|
417
|
-
const metadata = await this.metadataService.loadMetadataFromPath(this.bareRepoPath, worktreePath);
|
|
418
|
-
if (metadata?.lastSyncCommit) {
|
|
419
|
-
try {
|
|
420
|
-
// Check for commits after last sync
|
|
421
|
-
const newCommitsResult = await worktreeGit.raw(["rev-list", "--count", `${metadata.lastSyncCommit}..HEAD`]);
|
|
422
|
-
const newCommitsCount = parseInt(newCommitsResult.trim(), 10);
|
|
423
|
-
return newCommitsCount > 0;
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
// If lastSyncCommit doesn't exist, fall through to regular check
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
// Count commits that exist in the current branch but not in any remote
|
|
431
|
-
const result = await worktreeGit.raw(["rev-list", "--count", currentBranch, "--not", "--remotes"]);
|
|
432
|
-
const unpushedCount = parseInt(result.trim(), 10);
|
|
433
|
-
return unpushedCount > 0;
|
|
434
|
-
}
|
|
435
|
-
catch (error) {
|
|
436
|
-
// If the command fails (e.g., branch doesn't exist), assume it's safe
|
|
437
|
-
console.error(`Error checking unpushed commits: ${error}`);
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
async hasUpstreamGone(worktreePath) {
|
|
442
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
443
|
-
try {
|
|
444
|
-
// Check if in detached HEAD state
|
|
445
|
-
if (await this.isDetachedHead(worktreeGit)) {
|
|
446
|
-
return false;
|
|
447
|
-
}
|
|
448
|
-
const branchSummary = await worktreeGit.branch();
|
|
449
|
-
const currentBranch = branchSummary.current;
|
|
450
|
-
// Try to get upstream branch
|
|
451
|
-
const upstream = await worktreeGit.raw(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`]);
|
|
452
|
-
// Check if upstream exists in remotes
|
|
453
|
-
const remoteBranches = await worktreeGit.branch(["-r"]);
|
|
454
|
-
return !remoteBranches.all.includes(upstream.trim());
|
|
455
|
-
}
|
|
456
|
-
catch (error) {
|
|
457
|
-
const errorMessage = (0, lfs_error_1.getErrorMessage)(error);
|
|
458
|
-
if (errorMessage.includes("fatal: no upstream configured") ||
|
|
459
|
-
errorMessage.includes("no upstream configured for branch")) {
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
if (errorMessage.includes("fatal: ambiguous argument") || errorMessage.includes("unknown revision or path")) {
|
|
463
|
-
try {
|
|
464
|
-
const branchSummary = await worktreeGit.branch();
|
|
465
|
-
const currentBranch = branchSummary.current;
|
|
466
|
-
const remoteResult = await worktreeGit
|
|
467
|
-
.raw(["config", "--get", `branch.${currentBranch}.remote`])
|
|
468
|
-
.catch(() => "");
|
|
469
|
-
const mergeResult = await worktreeGit
|
|
470
|
-
.raw(["config", "--get", `branch.${currentBranch}.merge`])
|
|
471
|
-
.catch(() => "");
|
|
472
|
-
const remote = remoteResult.trim();
|
|
473
|
-
const merge = mergeResult.trim();
|
|
474
|
-
if (remote && merge) {
|
|
475
|
-
const remoteBranchName = merge.replace("refs/heads/", "");
|
|
476
|
-
const expectedUpstream = `${remote}/${remoteBranchName}`;
|
|
477
|
-
const remoteBranches = await worktreeGit.branch(["-r"]);
|
|
478
|
-
return !remoteBranches.all.includes(expectedUpstream);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
// Can't determine config, be conservative
|
|
483
|
-
}
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
486
|
-
console.error(`Unexpected error checking upstream status for ${worktreePath}. ` +
|
|
487
|
-
`This might indicate a real issue rather than a missing upstream. ` +
|
|
488
|
-
`Error: ${errorMessage}`);
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
async hasStashedChanges(worktreePath) {
|
|
493
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
494
|
-
try {
|
|
495
|
-
const stashList = await worktreeGit.stashList();
|
|
496
|
-
return stashList.total > 0;
|
|
497
|
-
}
|
|
498
|
-
catch (error) {
|
|
499
|
-
// If stash check fails, assume it's unsafe to delete
|
|
500
|
-
console.error(`Error checking stash: ${error}`);
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
async hasModifiedSubmodules(worktreePath) {
|
|
505
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
506
|
-
try {
|
|
507
|
-
const result = await worktreeGit.raw(["submodule", "status"]);
|
|
508
|
-
// Check for '+' or '-' prefix indicating modifications
|
|
509
|
-
return /^[+-]/m.test(result);
|
|
510
|
-
}
|
|
511
|
-
catch {
|
|
512
|
-
return false; // No submodules or submodule command failed
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async hasOperationInProgress(worktreePath) {
|
|
516
|
-
// Resolve the actual git directory; in worktrees .git is a file pointing to the real gitdir
|
|
517
|
-
let resolvedGitDir = path.join(worktreePath, ".git");
|
|
518
|
-
try {
|
|
519
|
-
const stat = await fs.stat(resolvedGitDir);
|
|
520
|
-
if (stat.isFile()) {
|
|
521
|
-
const content = await fs.readFile(resolvedGitDir, "utf-8");
|
|
522
|
-
const match = content.match(/gitdir:\s*(.*)/i);
|
|
523
|
-
if (match && match[1]) {
|
|
524
|
-
resolvedGitDir = match[1].trim();
|
|
525
|
-
if (!path.isAbsolute(resolvedGitDir)) {
|
|
526
|
-
resolvedGitDir = path.resolve(worktreePath, resolvedGitDir);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
catch {
|
|
532
|
-
// Fall back to default .git directory
|
|
533
|
-
}
|
|
534
|
-
const checkFiles = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "BISECT_LOG", "rebase-merge", "rebase-apply"];
|
|
535
|
-
for (const file of checkFiles) {
|
|
536
|
-
try {
|
|
537
|
-
await fs.access(path.join(resolvedGitDir, file));
|
|
538
|
-
return true; // Operation in progress
|
|
539
|
-
}
|
|
540
|
-
catch {
|
|
541
|
-
// File doesn't exist, continue checking
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
async getCurrentBranch() {
|
|
547
|
-
const git = this.getGit();
|
|
548
|
-
const branchSummary = await git.branch();
|
|
549
|
-
return branchSummary.current;
|
|
550
|
-
}
|
|
551
|
-
async detectDefaultBranch(bareGit) {
|
|
552
|
-
try {
|
|
553
|
-
// Try to get the symbolic ref for origin/HEAD
|
|
554
|
-
const headRef = await bareGit.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
555
|
-
// Extract branch name from refs/remotes/origin/main or refs/remotes/origin/master
|
|
556
|
-
const branch = headRef.trim().split("/").pop();
|
|
557
|
-
if (branch) {
|
|
558
|
-
return branch;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
catch {
|
|
562
|
-
// If that fails, try to set HEAD automatically
|
|
563
|
-
try {
|
|
564
|
-
await bareGit.raw(["remote", "set-head", "origin", "-a"]);
|
|
565
|
-
const headRef = await bareGit.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
566
|
-
const branch = headRef.trim().split("/").pop();
|
|
567
|
-
if (branch) {
|
|
568
|
-
return branch;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
// If all else fails, try to detect from remote branches
|
|
573
|
-
try {
|
|
574
|
-
const remoteBranches = await bareGit.branch(["-r"]);
|
|
575
|
-
// Common default branch names in order of preference
|
|
576
|
-
const commonDefaults = ["main", "master", "develop", "trunk"];
|
|
577
|
-
for (const defaultName of commonDefaults) {
|
|
578
|
-
if (remoteBranches.all.some((branch) => branch === `origin/${defaultName}`)) {
|
|
579
|
-
return defaultName;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
catch {
|
|
584
|
-
// Ignore and fall through to default
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
// Final fallback
|
|
589
|
-
return "main";
|
|
590
|
-
}
|
|
591
|
-
isLfsSkipEnabled() {
|
|
592
|
-
return this.config.skipLfs || process.env.GIT_LFS_SKIP_SMUDGE === "1";
|
|
593
|
-
}
|
|
594
|
-
async getWorktrees() {
|
|
595
|
-
const bareGit = (0, simple_git_1.default)(this.bareRepoPath);
|
|
596
|
-
return this.getWorktreesFromBare(bareGit);
|
|
597
|
-
}
|
|
598
|
-
async isWorktreeBehind(worktreePath) {
|
|
599
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
600
|
-
try {
|
|
601
|
-
// Get the current branch
|
|
602
|
-
const branchSummary = await worktreeGit.branch();
|
|
603
|
-
const currentBranch = branchSummary.current;
|
|
604
|
-
// Check if the branch has an upstream
|
|
605
|
-
const upstreamInfo = await worktreeGit.raw(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`]);
|
|
606
|
-
if (!upstreamInfo.trim()) {
|
|
607
|
-
return false; // No upstream, can't be behind
|
|
608
|
-
}
|
|
609
|
-
// Count commits behind upstream
|
|
610
|
-
const behindCount = await worktreeGit.raw(["rev-list", "--count", `HEAD..${upstreamInfo.trim()}`]);
|
|
611
|
-
return parseInt(behindCount.trim(), 10) > 0;
|
|
612
|
-
}
|
|
613
|
-
catch {
|
|
614
|
-
// If any command fails, assume not behind
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
async updateWorktree(worktreePath) {
|
|
619
|
-
const worktreeGit = this.isLfsSkipEnabled()
|
|
620
|
-
? (0, simple_git_1.default)(worktreePath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
621
|
-
: (0, simple_git_1.default)(worktreePath);
|
|
622
|
-
// Perform a fast-forward merge
|
|
623
|
-
const branchSummary = await worktreeGit.branch();
|
|
624
|
-
const currentBranch = branchSummary.current;
|
|
625
|
-
await worktreeGit.merge([`origin/${currentBranch}`, "--ff-only"]);
|
|
626
|
-
// Skip metadata update for main worktree
|
|
627
|
-
const isMainWorktree = path.resolve(worktreePath) === path.resolve(this.mainWorktreePath);
|
|
628
|
-
if (isMainWorktree) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
// Update metadata after successful update (use path-based method)
|
|
632
|
-
try {
|
|
633
|
-
const currentCommit = await worktreeGit.revparse(["HEAD"]);
|
|
634
|
-
await this.metadataService.updateLastSyncFromPath(this.bareRepoPath, worktreePath, currentCommit.trim(), "updated", this.defaultBranch);
|
|
635
|
-
}
|
|
636
|
-
catch (metadataError) {
|
|
637
|
-
console.warn(`Failed to update metadata for worktree: ${metadataError}`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
async hasDivergedHistory(worktreePath, expectedBranch) {
|
|
641
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
642
|
-
// Validate branch matches
|
|
643
|
-
const branchInfo = await worktreeGit.branch();
|
|
644
|
-
if (branchInfo.current !== expectedBranch) {
|
|
645
|
-
console.warn(`Branch mismatch in hasDivergedHistory: expected ${expectedBranch}, got ${branchInfo.current}`);
|
|
646
|
-
return false; // Conservative: assume can fast-forward
|
|
647
|
-
}
|
|
648
|
-
try {
|
|
649
|
-
// Check if HEAD is an ancestor of the remote branch (can fast-forward)
|
|
650
|
-
await worktreeGit.raw(["merge-base", "--is-ancestor", "HEAD", `origin/${expectedBranch}`]);
|
|
651
|
-
return false; // Can fast-forward
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
return true; // Histories have diverged
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
async canFastForward(worktreePath, branch) {
|
|
658
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
659
|
-
try {
|
|
660
|
-
// Get the merge base between HEAD and the remote branch
|
|
661
|
-
const mergeBase = await worktreeGit.raw(["merge-base", "HEAD", `origin/${branch}`]);
|
|
662
|
-
const mergeBaseSha = mergeBase.trim();
|
|
663
|
-
// Get current HEAD SHA
|
|
664
|
-
const headSha = await worktreeGit.revparse(["HEAD"]);
|
|
665
|
-
const headShaTrimmed = headSha.trim();
|
|
666
|
-
// If merge base equals HEAD, then HEAD is an ancestor of remote and can fast-forward
|
|
667
|
-
return mergeBaseSha === headShaTrimmed;
|
|
668
|
-
}
|
|
669
|
-
catch {
|
|
670
|
-
// If merge-base fails, branches have diverged
|
|
671
|
-
return false;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
async compareTreeContent(worktreePath, branch) {
|
|
675
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
676
|
-
try {
|
|
677
|
-
// Get the tree SHA for the current HEAD
|
|
678
|
-
const localTree = await worktreeGit.raw(["rev-parse", "HEAD^{tree}"]);
|
|
679
|
-
// Get the tree SHA for the remote branch
|
|
680
|
-
const remoteTree = await worktreeGit.raw(["rev-parse", `origin/${branch}^{tree}`]);
|
|
681
|
-
return localTree.trim() === remoteTree.trim();
|
|
682
|
-
}
|
|
683
|
-
catch (error) {
|
|
684
|
-
console.error(`Error comparing tree content: ${error}`);
|
|
685
|
-
return false; // Assume trees are different if we can't compare
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
async resetToUpstream(worktreePath, branch) {
|
|
689
|
-
const worktreeGit = this.isLfsSkipEnabled()
|
|
690
|
-
? (0, simple_git_1.default)(worktreePath).env({ GIT_LFS_SKIP_SMUDGE: "1" })
|
|
691
|
-
: (0, simple_git_1.default)(worktreePath);
|
|
692
|
-
await worktreeGit.reset(["--hard", `origin/${branch}`]);
|
|
693
|
-
// Update metadata after reset (use path-based method)
|
|
694
|
-
try {
|
|
695
|
-
const currentCommit = await worktreeGit.revparse(["HEAD"]);
|
|
696
|
-
await this.metadataService.updateLastSyncFromPath(this.bareRepoPath, worktreePath, currentCommit.trim(), "updated", this.defaultBranch);
|
|
697
|
-
}
|
|
698
|
-
catch (metadataError) {
|
|
699
|
-
console.warn(`Failed to update metadata after reset: ${metadataError}`);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
async getCurrentCommit(worktreePath) {
|
|
703
|
-
const worktreeGit = (0, simple_git_1.default)(worktreePath);
|
|
704
|
-
const commit = await worktreeGit.revparse(["HEAD"]);
|
|
705
|
-
return commit.trim();
|
|
706
|
-
}
|
|
707
|
-
async getRemoteCommit(ref) {
|
|
708
|
-
// Use the bare repository to read remote commit to avoid dependency on main worktree path
|
|
709
|
-
const git = (0, simple_git_1.default)(this.bareRepoPath);
|
|
710
|
-
const commit = await git.revparse([ref]);
|
|
711
|
-
return commit.trim();
|
|
712
|
-
}
|
|
713
|
-
async getWorktreesFromBare(bareGit) {
|
|
714
|
-
const result = await bareGit.raw(["worktree", "list", "--porcelain"]);
|
|
715
|
-
const worktrees = [];
|
|
716
|
-
const lines = result.trim().split("\n");
|
|
717
|
-
let currentWorktree = {};
|
|
718
|
-
for (const line of lines) {
|
|
719
|
-
if (line.startsWith("worktree ")) {
|
|
720
|
-
currentWorktree.path = line.substring(9);
|
|
721
|
-
}
|
|
722
|
-
else if (line.startsWith("branch ")) {
|
|
723
|
-
currentWorktree.branch = line.substring(7).replace("refs/heads/", "");
|
|
724
|
-
}
|
|
725
|
-
else if (line === "detached") {
|
|
726
|
-
currentWorktree.detached = true;
|
|
727
|
-
}
|
|
728
|
-
else if (line.trim() === "") {
|
|
729
|
-
if (currentWorktree.path) {
|
|
730
|
-
// Only include worktrees that have a branch (not detached)
|
|
731
|
-
if (currentWorktree.branch && !currentWorktree.detached) {
|
|
732
|
-
worktrees.push({ path: currentWorktree.path, branch: currentWorktree.branch });
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
currentWorktree = {};
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
// Handle the last worktree if there's no trailing empty line
|
|
739
|
-
if (currentWorktree.path && currentWorktree.branch && !currentWorktree.detached) {
|
|
740
|
-
worktrees.push({ path: currentWorktree.path, branch: currentWorktree.branch });
|
|
741
|
-
}
|
|
742
|
-
return worktrees;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
exports.GitService = GitService;
|
|
746
|
-
//# sourceMappingURL=git.service.js.map
|