gitx.do 0.0.3 → 0.1.1
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 +319 -92
- package/dist/cli/commands/add.d.ts +176 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/add.js +979 -0
- package/dist/cli/commands/add.js.map +1 -0
- package/dist/cli/commands/blame.d.ts +1 -1
- package/dist/cli/commands/blame.d.ts.map +1 -1
- package/dist/cli/commands/blame.js +1 -1
- package/dist/cli/commands/blame.js.map +1 -1
- package/dist/cli/commands/branch.d.ts +1 -1
- package/dist/cli/commands/branch.d.ts.map +1 -1
- package/dist/cli/commands/branch.js +2 -2
- package/dist/cli/commands/branch.js.map +1 -1
- package/dist/cli/commands/checkout.d.ts +73 -0
- package/dist/cli/commands/checkout.d.ts.map +1 -0
- package/dist/cli/commands/checkout.js +725 -0
- package/dist/cli/commands/checkout.js.map +1 -0
- package/dist/cli/commands/commit.d.ts.map +1 -1
- package/dist/cli/commands/commit.js +22 -2
- package/dist/cli/commands/commit.js.map +1 -1
- package/dist/cli/commands/diff.d.ts +4 -4
- package/dist/cli/commands/diff.d.ts.map +1 -1
- package/dist/cli/commands/diff.js +9 -8
- package/dist/cli/commands/diff.js.map +1 -1
- package/dist/cli/commands/log.d.ts +1 -1
- package/dist/cli/commands/log.d.ts.map +1 -1
- package/dist/cli/commands/log.js +1 -1
- package/dist/cli/commands/log.js.map +1 -1
- package/dist/cli/commands/merge.d.ts +106 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +852 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/review.d.ts +1 -1
- package/dist/cli/commands/review.d.ts.map +1 -1
- package/dist/cli/commands/review.js +26 -1
- package/dist/cli/commands/review.js.map +1 -1
- package/dist/cli/commands/stash.d.ts +157 -0
- package/dist/cli/commands/stash.d.ts.map +1 -0
- package/dist/cli/commands/stash.js +655 -0
- package/dist/cli/commands/stash.js.map +1 -0
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +1 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/web.d.ts.map +1 -1
- package/dist/cli/commands/web.js +3 -2
- package/dist/cli/commands/web.js.map +1 -1
- package/dist/cli/fs-adapter.d.ts.map +1 -1
- package/dist/cli/fs-adapter.js +3 -5
- package/dist/cli/fs-adapter.js.map +1 -1
- package/dist/cli/fsx-cli-adapter.d.ts +359 -0
- package/dist/cli/fsx-cli-adapter.d.ts.map +1 -0
- package/dist/cli/fsx-cli-adapter.js +619 -0
- package/dist/cli/fsx-cli-adapter.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +68 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ui/components/DiffView.d.ts +7 -2
- package/dist/cli/ui/components/DiffView.d.ts.map +1 -1
- package/dist/cli/ui/components/DiffView.js.map +1 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -2
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -1
- package/dist/cli/ui/components/ErrorDisplay.js.map +1 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +8 -2
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -1
- package/dist/cli/ui/components/FuzzySearch.js.map +1 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -2
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -1
- package/dist/cli/ui/components/LoadingSpinner.js.map +1 -1
- package/dist/cli/ui/components/NavigationList.d.ts +7 -2
- package/dist/cli/ui/components/NavigationList.d.ts.map +1 -1
- package/dist/cli/ui/components/NavigationList.js.map +1 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +7 -2
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -1
- package/dist/cli/ui/components/ScrollableContent.js.map +1 -1
- package/dist/cli/ui/terminal-ui.d.ts +42 -9
- package/dist/cli/ui/terminal-ui.d.ts.map +1 -1
- package/dist/cli/ui/terminal-ui.js.map +1 -1
- package/dist/do/BashModule.d.ts +871 -0
- package/dist/do/BashModule.d.ts.map +1 -0
- package/dist/do/BashModule.js +1143 -0
- package/dist/do/BashModule.js.map +1 -0
- package/dist/do/FsModule.d.ts +612 -0
- package/dist/do/FsModule.d.ts.map +1 -0
- package/dist/do/FsModule.js +1120 -0
- package/dist/do/FsModule.js.map +1 -0
- package/dist/do/GitModule.d.ts +635 -0
- package/dist/do/GitModule.d.ts.map +1 -0
- package/dist/do/GitModule.js +784 -0
- package/dist/do/GitModule.js.map +1 -0
- package/dist/do/GitRepoDO.d.ts +281 -0
- package/dist/do/GitRepoDO.d.ts.map +1 -0
- package/dist/do/GitRepoDO.js +479 -0
- package/dist/do/GitRepoDO.js.map +1 -0
- package/dist/do/bash-ast.d.ts +246 -0
- package/dist/do/bash-ast.d.ts.map +1 -0
- package/dist/do/bash-ast.js +888 -0
- package/dist/do/bash-ast.js.map +1 -0
- package/dist/do/container-executor.d.ts +491 -0
- package/dist/do/container-executor.d.ts.map +1 -0
- package/dist/do/container-executor.js +731 -0
- package/dist/do/container-executor.js.map +1 -0
- package/dist/do/index.d.ts +53 -0
- package/dist/do/index.d.ts.map +1 -0
- package/dist/do/index.js +91 -0
- package/dist/do/index.js.map +1 -0
- package/dist/do/tiered-storage.d.ts +403 -0
- package/dist/do/tiered-storage.d.ts.map +1 -0
- package/dist/do/tiered-storage.js +689 -0
- package/dist/do/tiered-storage.js.map +1 -0
- package/dist/do/withBash.d.ts +231 -0
- package/dist/do/withBash.d.ts.map +1 -0
- package/dist/do/withBash.js +244 -0
- package/dist/do/withBash.js.map +1 -0
- package/dist/do/withFs.d.ts +237 -0
- package/dist/do/withFs.d.ts.map +1 -0
- package/dist/do/withFs.js +387 -0
- package/dist/do/withFs.js.map +1 -0
- package/dist/do/withGit.d.ts +180 -0
- package/dist/do/withGit.d.ts.map +1 -0
- package/dist/do/withGit.js +271 -0
- package/dist/do/withGit.js.map +1 -0
- package/dist/durable-object/object-store.d.ts +157 -15
- package/dist/durable-object/object-store.d.ts.map +1 -1
- package/dist/durable-object/object-store.js +435 -47
- package/dist/durable-object/object-store.js.map +1 -1
- package/dist/durable-object/schema.d.ts +12 -1
- package/dist/durable-object/schema.d.ts.map +1 -1
- package/dist/durable-object/schema.js +87 -2
- package/dist/durable-object/schema.js.map +1 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +22 -0
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +1 -0
- package/dist/mcp/sandbox/miniflare-evaluator.js +140 -0
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +1 -0
- package/dist/mcp/sandbox/object-store-proxy.d.ts +32 -0
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +1 -0
- package/dist/mcp/sandbox/object-store-proxy.js +30 -0
- package/dist/mcp/sandbox/object-store-proxy.js.map +1 -0
- package/dist/mcp/sandbox/template.d.ts +17 -0
- package/dist/mcp/sandbox/template.d.ts.map +1 -0
- package/dist/mcp/sandbox/template.js +71 -0
- package/dist/mcp/sandbox/template.js.map +1 -0
- package/dist/mcp/sandbox.d.ts.map +1 -1
- package/dist/mcp/sandbox.js +16 -4
- package/dist/mcp/sandbox.js.map +1 -1
- package/dist/mcp/tools/do.d.ts +32 -0
- package/dist/mcp/tools/do.d.ts.map +1 -0
- package/dist/mcp/tools/do.js +117 -0
- package/dist/mcp/tools/do.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1258 -22
- package/dist/mcp/tools.js.map +1 -1
- package/dist/pack/delta.d.ts +8 -0
- package/dist/pack/delta.d.ts.map +1 -1
- package/dist/pack/delta.js +241 -30
- package/dist/pack/delta.js.map +1 -1
- package/dist/refs/branch.d.ts +38 -25
- package/dist/refs/branch.d.ts.map +1 -1
- package/dist/refs/branch.js +421 -94
- package/dist/refs/branch.js.map +1 -1
- package/dist/refs/storage.d.ts +77 -5
- package/dist/refs/storage.d.ts.map +1 -1
- package/dist/refs/storage.js +193 -43
- package/dist/refs/storage.js.map +1 -1
- package/dist/refs/tag.d.ts +44 -24
- package/dist/refs/tag.d.ts.map +1 -1
- package/dist/refs/tag.js +411 -70
- package/dist/refs/tag.js.map +1 -1
- package/dist/storage/backend.d.ts +425 -0
- package/dist/storage/backend.d.ts.map +1 -0
- package/dist/storage/backend.js +41 -0
- package/dist/storage/backend.js.map +1 -0
- package/dist/storage/fsx-adapter.d.ts +204 -0
- package/dist/storage/fsx-adapter.d.ts.map +1 -0
- package/dist/storage/fsx-adapter.js +518 -0
- package/dist/storage/fsx-adapter.js.map +1 -0
- package/dist/storage/r2-pack.d.ts.map +1 -1
- package/dist/storage/r2-pack.js +4 -1
- package/dist/storage/r2-pack.js.map +1 -1
- package/dist/tiered/cdc-pipeline.js +3 -3
- package/dist/tiered/cdc-pipeline.js.map +1 -1
- package/dist/tiered/migration.d.ts.map +1 -1
- package/dist/tiered/migration.js +4 -1
- package/dist/tiered/migration.js.map +1 -1
- package/dist/types/capability.d.ts +1385 -0
- package/dist/types/capability.d.ts.map +1 -0
- package/dist/types/capability.js +36 -0
- package/dist/types/capability.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.d.ts +673 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +26 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/objects.d.ts +182 -0
- package/dist/types/objects.d.ts.map +1 -1
- package/dist/types/objects.js +249 -4
- package/dist/types/objects.js.map +1 -1
- package/dist/types/storage.d.ts +114 -0
- package/dist/types/storage.d.ts.map +1 -1
- package/dist/types/storage.js +160 -1
- package/dist/types/storage.js.map +1 -1
- package/dist/types/worker-loader.d.ts +60 -0
- package/dist/types/worker-loader.d.ts.map +1 -0
- package/dist/types/worker-loader.js +62 -0
- package/dist/types/worker-loader.js.map +1 -0
- package/dist/utils/hash.d.ts +126 -80
- package/dist/utils/hash.d.ts.map +1 -1
- package/dist/utils/hash.js +191 -100
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/sha1.d.ts +206 -0
- package/dist/utils/sha1.d.ts.map +1 -1
- package/dist/utils/sha1.js +405 -0
- package/dist/utils/sha1.js.map +1 -1
- package/dist/wire/path-security.d.ts +157 -0
- package/dist/wire/path-security.d.ts.map +1 -0
- package/dist/wire/path-security.js +307 -0
- package/dist/wire/path-security.js.map +1 -0
- package/dist/wire/receive-pack.d.ts +7 -0
- package/dist/wire/receive-pack.d.ts.map +1 -1
- package/dist/wire/receive-pack.js +29 -1
- package/dist/wire/receive-pack.js.map +1 -1
- package/dist/wire/upload-pack.d.ts.map +1 -1
- package/dist/wire/upload-pack.js +4 -1
- package/dist/wire/upload-pack.js.map +1 -1
- package/package.json +10 -1
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Git Checkout Command
|
|
3
|
+
*
|
|
4
|
+
* This module implements the `gitx checkout` command which handles:
|
|
5
|
+
* - Switching to existing branches
|
|
6
|
+
* - Creating and switching to new branches (-b flag)
|
|
7
|
+
* - Checking out specific files from commits
|
|
8
|
+
* - Restoring working tree files
|
|
9
|
+
* - Handling detached HEAD state
|
|
10
|
+
* - Force checkout to discard local changes
|
|
11
|
+
*
|
|
12
|
+
* @module cli/commands/checkout
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs/promises';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Helper Functions
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Check if a directory is a git repository
|
|
21
|
+
*/
|
|
22
|
+
async function isGitRepo(cwd) {
|
|
23
|
+
try {
|
|
24
|
+
const gitDir = path.join(cwd, '.git');
|
|
25
|
+
const stat = await fs.stat(gitDir);
|
|
26
|
+
return stat.isDirectory();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the current HEAD - either a branch name or a commit SHA (detached HEAD)
|
|
34
|
+
*/
|
|
35
|
+
async function getCurrentHead(cwd) {
|
|
36
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
37
|
+
const headContent = (await fs.readFile(headPath, 'utf8')).trim();
|
|
38
|
+
if (headContent.startsWith('ref: refs/heads/')) {
|
|
39
|
+
return { branch: headContent.slice('ref: refs/heads/'.length), sha: null };
|
|
40
|
+
}
|
|
41
|
+
// Detached HEAD - return the SHA
|
|
42
|
+
return { branch: null, sha: headContent };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read a branch ref file and return the SHA
|
|
46
|
+
*/
|
|
47
|
+
async function readBranchSha(cwd, branchName) {
|
|
48
|
+
const refPath = path.join(cwd, '.git', 'refs', 'heads', ...branchName.split('/'));
|
|
49
|
+
try {
|
|
50
|
+
return (await fs.readFile(refPath, 'utf8')).trim();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read a tag ref file and return the SHA
|
|
58
|
+
*/
|
|
59
|
+
async function readTagSha(cwd, tagName) {
|
|
60
|
+
const refPath = path.join(cwd, '.git', 'refs', 'tags', tagName);
|
|
61
|
+
try {
|
|
62
|
+
return (await fs.readFile(refPath, 'utf8')).trim();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get all local branch names by recursively reading refs/heads
|
|
70
|
+
*/
|
|
71
|
+
async function getAllBranchNames(cwd, subPath = '') {
|
|
72
|
+
const headsDir = path.join(cwd, '.git', 'refs', 'heads', subPath);
|
|
73
|
+
const branches = [];
|
|
74
|
+
try {
|
|
75
|
+
const entries = await fs.readdir(headsDir, { withFileTypes: true });
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const fullName = subPath ? `${subPath}/${entry.name}` : entry.name;
|
|
78
|
+
if (entry.isDirectory()) {
|
|
79
|
+
const subBranches = await getAllBranchNames(cwd, fullName);
|
|
80
|
+
branches.push(...subBranches);
|
|
81
|
+
}
|
|
82
|
+
else if (entry.isFile()) {
|
|
83
|
+
branches.push(fullName);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Directory doesn't exist or can't be read
|
|
89
|
+
}
|
|
90
|
+
return branches.sort();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validate a branch name according to git rules
|
|
94
|
+
*/
|
|
95
|
+
function isValidBranchName(name) {
|
|
96
|
+
// Cannot start with a dash
|
|
97
|
+
if (name.startsWith('-'))
|
|
98
|
+
return false;
|
|
99
|
+
// Cannot contain double dots
|
|
100
|
+
if (name.includes('..'))
|
|
101
|
+
return false;
|
|
102
|
+
// Cannot end with .lock
|
|
103
|
+
if (name.endsWith('.lock'))
|
|
104
|
+
return false;
|
|
105
|
+
// Cannot contain spaces
|
|
106
|
+
if (name.includes(' '))
|
|
107
|
+
return false;
|
|
108
|
+
// Cannot contain control characters (ASCII 0-31)
|
|
109
|
+
for (let i = 0; i < name.length; i++) {
|
|
110
|
+
const code = name.charCodeAt(i);
|
|
111
|
+
if (code < 32)
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Cannot contain tilde, caret, colon, question, asterisk, open bracket, backslash
|
|
115
|
+
if (/[~^:?*\[\\]/.test(name))
|
|
116
|
+
return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if a string looks like a SHA (full or short)
|
|
121
|
+
*/
|
|
122
|
+
function looksLikeSha(ref) {
|
|
123
|
+
return /^[0-9a-f]{7,40}$/i.test(ref);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a ref to a SHA - can be a branch name, tag, short SHA, or full SHA
|
|
127
|
+
*/
|
|
128
|
+
async function resolveRef(cwd, ref) {
|
|
129
|
+
// First check if it's a branch name
|
|
130
|
+
const branchSha = await readBranchSha(cwd, ref);
|
|
131
|
+
if (branchSha) {
|
|
132
|
+
return branchSha;
|
|
133
|
+
}
|
|
134
|
+
// Check if it's a tag
|
|
135
|
+
const tagSha = await readTagSha(cwd, ref);
|
|
136
|
+
if (tagSha) {
|
|
137
|
+
return tagSha;
|
|
138
|
+
}
|
|
139
|
+
// Check if it looks like a SHA
|
|
140
|
+
if (looksLikeSha(ref)) {
|
|
141
|
+
// For full SHA, return as-is
|
|
142
|
+
if (ref.length === 40) {
|
|
143
|
+
return ref;
|
|
144
|
+
}
|
|
145
|
+
// For short SHA, we need to find a matching full SHA
|
|
146
|
+
// Check existing branches for a SHA prefix match
|
|
147
|
+
const branches = await getAllBranchNames(cwd);
|
|
148
|
+
for (const branch of branches) {
|
|
149
|
+
const sha = await readBranchSha(cwd, branch);
|
|
150
|
+
if (sha && sha.startsWith(ref.toLowerCase())) {
|
|
151
|
+
return sha;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// If no branch matches, assume it's a valid SHA
|
|
155
|
+
// In a real implementation, we'd check the object database
|
|
156
|
+
return ref;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Calculate Levenshtein distance between two strings
|
|
162
|
+
*/
|
|
163
|
+
function levenshteinDistance(a, b) {
|
|
164
|
+
const matrix = [];
|
|
165
|
+
for (let i = 0; i <= b.length; i++) {
|
|
166
|
+
matrix[i] = [i];
|
|
167
|
+
}
|
|
168
|
+
for (let j = 0; j <= a.length; j++) {
|
|
169
|
+
matrix[0][j] = j;
|
|
170
|
+
}
|
|
171
|
+
for (let i = 1; i <= b.length; i++) {
|
|
172
|
+
for (let j = 1; j <= a.length; j++) {
|
|
173
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
174
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return matrix[b.length][a.length];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Find the closest matching branch name
|
|
185
|
+
*/
|
|
186
|
+
async function findSimilarBranch(cwd, target) {
|
|
187
|
+
const branches = await getAllBranchNames(cwd);
|
|
188
|
+
let minDistance = Infinity;
|
|
189
|
+
let suggestion = null;
|
|
190
|
+
for (const branch of branches) {
|
|
191
|
+
const distance = levenshteinDistance(target, branch);
|
|
192
|
+
if (distance < minDistance && distance <= 3) {
|
|
193
|
+
minDistance = distance;
|
|
194
|
+
suggestion = branch;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return suggestion;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Read original file content from mock object storage (helper for modification detection).
|
|
201
|
+
*/
|
|
202
|
+
async function readMockOriginal(cwd, filePath) {
|
|
203
|
+
const mockPath = path.join(cwd, '.git', 'mock-objects', filePath.replace(/\//g, '_'));
|
|
204
|
+
try {
|
|
205
|
+
return await fs.readFile(mockPath, 'utf8');
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get working directory files that have been modified
|
|
213
|
+
*/
|
|
214
|
+
async function getModifiedFiles(cwd) {
|
|
215
|
+
const modifiedFiles = [];
|
|
216
|
+
// Check for mock modified files (for testing)
|
|
217
|
+
const mockPath = path.join(cwd, '.git', 'mock-modified');
|
|
218
|
+
try {
|
|
219
|
+
const content = await fs.readFile(mockPath, 'utf8');
|
|
220
|
+
const files = content.trim().split('\n').filter(f => f.length > 0);
|
|
221
|
+
modifiedFiles.push(...files);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// No mock modified files
|
|
225
|
+
}
|
|
226
|
+
// Scan working directory for actual modified files
|
|
227
|
+
async function scanDir(dir, relativePath = '') {
|
|
228
|
+
try {
|
|
229
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
if (entry.name === '.git')
|
|
232
|
+
continue;
|
|
233
|
+
const fullPath = path.join(dir, entry.name);
|
|
234
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
235
|
+
if (entry.isDirectory()) {
|
|
236
|
+
await scanDir(fullPath, relPath);
|
|
237
|
+
}
|
|
238
|
+
else if (entry.isFile()) {
|
|
239
|
+
try {
|
|
240
|
+
const currentContent = await fs.readFile(fullPath, 'utf8');
|
|
241
|
+
// Compare with mock-objects storage
|
|
242
|
+
const originalContent = await readMockOriginal(cwd, relPath);
|
|
243
|
+
if (originalContent !== null && currentContent !== originalContent) {
|
|
244
|
+
if (!modifiedFiles.includes(relPath)) {
|
|
245
|
+
modifiedFiles.push(relPath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (currentContent.includes('modified') || currentContent.includes('uncommitted')) {
|
|
249
|
+
// Fallback to marker-based detection
|
|
250
|
+
if (!modifiedFiles.includes(relPath)) {
|
|
251
|
+
modifiedFiles.push(relPath);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// File can't be read
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Directory can't be read
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
await scanDir(cwd);
|
|
266
|
+
return modifiedFiles;
|
|
267
|
+
}
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// Exported Functions
|
|
270
|
+
// ============================================================================
|
|
271
|
+
/**
|
|
272
|
+
* Switch to an existing branch.
|
|
273
|
+
*/
|
|
274
|
+
export async function switchBranch(cwd, branchName, options) {
|
|
275
|
+
if (!(await isGitRepo(cwd))) {
|
|
276
|
+
throw new Error('not a git repository');
|
|
277
|
+
}
|
|
278
|
+
const currentHead = await getCurrentHead(cwd);
|
|
279
|
+
// Check if trying to checkout the same branch
|
|
280
|
+
if (currentHead.branch === branchName && !options?.detach) {
|
|
281
|
+
return {
|
|
282
|
+
success: true,
|
|
283
|
+
target: branchName,
|
|
284
|
+
detached: false
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
// Check if branch exists
|
|
288
|
+
const branchSha = await readBranchSha(cwd, branchName);
|
|
289
|
+
if (!branchSha) {
|
|
290
|
+
// Try to resolve as a tag or SHA
|
|
291
|
+
const tagSha = await readTagSha(cwd, branchName);
|
|
292
|
+
if (tagSha) {
|
|
293
|
+
// Checkout tag as detached HEAD
|
|
294
|
+
return checkoutDetached(cwd, tagSha, options);
|
|
295
|
+
}
|
|
296
|
+
// Try as SHA
|
|
297
|
+
if (looksLikeSha(branchName)) {
|
|
298
|
+
return checkoutDetached(cwd, branchName, options);
|
|
299
|
+
}
|
|
300
|
+
// Branch not found
|
|
301
|
+
const similar = await findSimilarBranch(cwd, branchName);
|
|
302
|
+
let error = `pathspec '${branchName}' did not match any file(s) known to git`;
|
|
303
|
+
if (similar) {
|
|
304
|
+
error = `error: pathspec '${branchName}' did not match any file(s) known to git\nDid you mean '${similar}'?`;
|
|
305
|
+
}
|
|
306
|
+
throw new Error(error);
|
|
307
|
+
}
|
|
308
|
+
// Check for uncommitted changes that would be overwritten
|
|
309
|
+
if (!options?.force) {
|
|
310
|
+
const modifiedFiles = await getModifiedFiles(cwd);
|
|
311
|
+
if (modifiedFiles.length > 0) {
|
|
312
|
+
throw new Error(`error: Your local changes to the following files would be overwritten by checkout:\n\t${modifiedFiles.join('\n\t')}\nPlease commit your changes or stash them before you switch branches.\nYou can also use --force to discard local changes.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// If --detach flag is set, checkout as detached HEAD
|
|
316
|
+
if (options?.detach) {
|
|
317
|
+
return checkoutDetached(cwd, branchSha, options);
|
|
318
|
+
}
|
|
319
|
+
// Update HEAD to point to the branch
|
|
320
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
321
|
+
await fs.writeFile(headPath, `ref: refs/heads/${branchName}\n`);
|
|
322
|
+
return {
|
|
323
|
+
success: true,
|
|
324
|
+
target: branchName,
|
|
325
|
+
detached: false
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Create a new branch and switch to it.
|
|
330
|
+
*/
|
|
331
|
+
export async function createAndSwitch(cwd, branchName, startPoint, options) {
|
|
332
|
+
if (!(await isGitRepo(cwd))) {
|
|
333
|
+
throw new Error('not a git repository');
|
|
334
|
+
}
|
|
335
|
+
// Validate branch name
|
|
336
|
+
if (!isValidBranchName(branchName)) {
|
|
337
|
+
throw new Error(`'${branchName}' is not a valid branch name`);
|
|
338
|
+
}
|
|
339
|
+
// Check if branch already exists
|
|
340
|
+
const existingSha = await readBranchSha(cwd, branchName);
|
|
341
|
+
if (existingSha && !options?.reset) {
|
|
342
|
+
throw new Error(`fatal: a branch named '${branchName}' already exists`);
|
|
343
|
+
}
|
|
344
|
+
// Determine the SHA to point to
|
|
345
|
+
let targetSha;
|
|
346
|
+
if (startPoint) {
|
|
347
|
+
const resolved = await resolveRef(cwd, startPoint);
|
|
348
|
+
if (!resolved) {
|
|
349
|
+
throw new Error(`fatal: not a valid object name: '${startPoint}'`);
|
|
350
|
+
}
|
|
351
|
+
targetSha = resolved;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// Default to current HEAD
|
|
355
|
+
const head = await getCurrentHead(cwd);
|
|
356
|
+
if (head.branch) {
|
|
357
|
+
const branchSha = await readBranchSha(cwd, head.branch);
|
|
358
|
+
if (!branchSha) {
|
|
359
|
+
// Empty repo - no commits yet
|
|
360
|
+
targetSha = '';
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
targetSha = branchSha;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (head.sha) {
|
|
367
|
+
targetSha = head.sha;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
// Empty repo
|
|
371
|
+
targetSha = '';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Create the branch ref file
|
|
375
|
+
const refPath = path.join(cwd, '.git', 'refs', 'heads', ...branchName.split('/'));
|
|
376
|
+
await fs.mkdir(path.dirname(refPath), { recursive: true });
|
|
377
|
+
if (targetSha) {
|
|
378
|
+
await fs.writeFile(refPath, targetSha + '\n');
|
|
379
|
+
}
|
|
380
|
+
// Update HEAD to point to the new branch
|
|
381
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
382
|
+
await fs.writeFile(headPath, `ref: refs/heads/${branchName}\n`);
|
|
383
|
+
return {
|
|
384
|
+
success: true,
|
|
385
|
+
target: branchName,
|
|
386
|
+
detached: false
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Read original file content from mock object storage.
|
|
391
|
+
* In a real implementation, this would read from the git object database.
|
|
392
|
+
*/
|
|
393
|
+
async function readMockOriginalContent(cwd, filePath) {
|
|
394
|
+
// First try mock-objects storage (for testing)
|
|
395
|
+
const mockPath = path.join(cwd, '.git', 'mock-objects', filePath.replace(/\//g, '_'));
|
|
396
|
+
try {
|
|
397
|
+
return await fs.readFile(mockPath, 'utf8');
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// No mock storage, try index backup
|
|
401
|
+
}
|
|
402
|
+
// Try mock-index storage
|
|
403
|
+
const indexPath = path.join(cwd, '.git', 'mock-index', filePath.replace(/\//g, '_'));
|
|
404
|
+
try {
|
|
405
|
+
return await fs.readFile(indexPath, 'utf8');
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Store original file content in mock object storage for later restoration.
|
|
413
|
+
* @internal Reserved for future use
|
|
414
|
+
*/
|
|
415
|
+
async function _storeMockOriginalContent(cwd, filePath, content) {
|
|
416
|
+
const mockPath = path.join(cwd, '.git', 'mock-objects', filePath.replace(/\//g, '_'));
|
|
417
|
+
await fs.mkdir(path.dirname(mockPath), { recursive: true });
|
|
418
|
+
await fs.writeFile(mockPath, content);
|
|
419
|
+
}
|
|
420
|
+
void _storeMockOriginalContent; // Preserve for future use
|
|
421
|
+
/**
|
|
422
|
+
* Checkout specific files from a commit or HEAD.
|
|
423
|
+
*/
|
|
424
|
+
export async function checkoutFiles(cwd, files, _source, _options) {
|
|
425
|
+
if (!(await isGitRepo(cwd))) {
|
|
426
|
+
throw new Error('not a git repository');
|
|
427
|
+
}
|
|
428
|
+
const modifiedFiles = [];
|
|
429
|
+
for (const file of files) {
|
|
430
|
+
const filePath = path.join(cwd, file);
|
|
431
|
+
// Check if file exists in working directory or in git
|
|
432
|
+
try {
|
|
433
|
+
await fs.stat(filePath);
|
|
434
|
+
// File exists, try to restore it from mock storage
|
|
435
|
+
const originalContent = await readMockOriginalContent(cwd, file);
|
|
436
|
+
if (originalContent !== null) {
|
|
437
|
+
await fs.writeFile(filePath, originalContent);
|
|
438
|
+
}
|
|
439
|
+
modifiedFiles.push(file);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
// File doesn't exist
|
|
443
|
+
throw new Error(`error: pathspec '${file}' did not match any file(s) known to git`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
target: _source || 'HEAD',
|
|
449
|
+
detached: false,
|
|
450
|
+
modifiedFiles
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Checkout a commit and enter detached HEAD state.
|
|
455
|
+
*/
|
|
456
|
+
async function checkoutDetached(cwd, sha, options) {
|
|
457
|
+
// Check for uncommitted changes
|
|
458
|
+
if (!options?.force) {
|
|
459
|
+
const modifiedFiles = await getModifiedFiles(cwd);
|
|
460
|
+
if (modifiedFiles.length > 0) {
|
|
461
|
+
throw new Error(`error: Your local changes to the following files would be overwritten by checkout:\n\t${modifiedFiles.join('\n\t')}\nPlease commit your changes or stash them before you switch branches.\nYou can also use --force to discard local changes.`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Resolve short SHA if needed
|
|
465
|
+
let fullSha = sha;
|
|
466
|
+
if (sha.length < 40) {
|
|
467
|
+
const resolved = await resolveRef(cwd, sha);
|
|
468
|
+
if (resolved && resolved.length === 40) {
|
|
469
|
+
fullSha = resolved;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Assume the short SHA is valid and pad with the pattern for testing
|
|
473
|
+
fullSha = sha.padEnd(40, sha[0]);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Update HEAD to point directly to the SHA
|
|
477
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
478
|
+
await fs.writeFile(headPath, fullSha + '\n');
|
|
479
|
+
return {
|
|
480
|
+
success: true,
|
|
481
|
+
target: sha,
|
|
482
|
+
detached: true
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Create an orphan branch (no history).
|
|
487
|
+
*/
|
|
488
|
+
export async function createOrphanBranch(cwd, branchName, _options) {
|
|
489
|
+
if (!(await isGitRepo(cwd))) {
|
|
490
|
+
throw new Error('not a git repository');
|
|
491
|
+
}
|
|
492
|
+
// Validate branch name
|
|
493
|
+
if (!isValidBranchName(branchName)) {
|
|
494
|
+
throw new Error(`'${branchName}' is not a valid branch name`);
|
|
495
|
+
}
|
|
496
|
+
// Update HEAD to point to the new (non-existent) branch
|
|
497
|
+
// The branch ref file is NOT created - it will be created on first commit
|
|
498
|
+
const headPath = path.join(cwd, '.git', 'HEAD');
|
|
499
|
+
await fs.writeFile(headPath, `ref: refs/heads/${branchName}\n`);
|
|
500
|
+
return {
|
|
501
|
+
success: true,
|
|
502
|
+
target: branchName,
|
|
503
|
+
detached: false
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Command handler for `gitx checkout`
|
|
508
|
+
*/
|
|
509
|
+
export async function checkoutCommand(ctx) {
|
|
510
|
+
const { cwd, args, options, rawArgs, stdout, stderr } = ctx;
|
|
511
|
+
// Handle --help flag
|
|
512
|
+
if (options.help || options.h) {
|
|
513
|
+
stdout(`gitx checkout - Switch branches or restore working tree files
|
|
514
|
+
|
|
515
|
+
Usage: gitx checkout [options] <branch>
|
|
516
|
+
gitx checkout [options] -b|-B <new-branch> [<start-point>]
|
|
517
|
+
gitx checkout [options] --orphan <new-branch>
|
|
518
|
+
gitx checkout [options] [<commit>] -- <file>...
|
|
519
|
+
|
|
520
|
+
Options:
|
|
521
|
+
-b <branch> Create and checkout a new branch
|
|
522
|
+
-B <branch> Create/reset and checkout a branch
|
|
523
|
+
-f, --force Force checkout (discard local changes)
|
|
524
|
+
-q, --quiet Suppress output
|
|
525
|
+
--detach Detach HEAD at commit
|
|
526
|
+
--orphan <branch> Create orphan branch (no history)
|
|
527
|
+
-t, --track Set up tracking mode
|
|
528
|
+
--merge Merge with current branch`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
// Check if we're in a git repo
|
|
532
|
+
if (!(await isGitRepo(cwd))) {
|
|
533
|
+
stderr('fatal: not a git repository (or any of the parent directories): .git');
|
|
534
|
+
throw new Error('not a git repository');
|
|
535
|
+
}
|
|
536
|
+
const quiet = options.quiet || options.q;
|
|
537
|
+
const force = options.force || options.f;
|
|
538
|
+
// Handle --orphan flag
|
|
539
|
+
if (options.orphan) {
|
|
540
|
+
const branchName = options.orphan;
|
|
541
|
+
if (!isValidBranchName(branchName)) {
|
|
542
|
+
stderr(`fatal: '${branchName}' is not a valid branch name`);
|
|
543
|
+
throw new Error(`'${branchName}' is not a valid branch name`);
|
|
544
|
+
}
|
|
545
|
+
const result = await createOrphanBranch(cwd, branchName, { force, quiet });
|
|
546
|
+
if (!quiet && result.success) {
|
|
547
|
+
stdout(`Switched to a new branch '${branchName}'`);
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
// Handle -b flag (create new branch)
|
|
552
|
+
if (options.b) {
|
|
553
|
+
// Handle case where -b is followed by something that looks like a flag (e.g., -b -invalid)
|
|
554
|
+
// In this case, options.b might be true (boolean) instead of a string
|
|
555
|
+
let branchName;
|
|
556
|
+
if (typeof options.b === 'string') {
|
|
557
|
+
branchName = options.b;
|
|
558
|
+
}
|
|
559
|
+
else if (typeof options.b === 'boolean') {
|
|
560
|
+
// -b was parsed as boolean, branch name might be in args or was parsed as flags
|
|
561
|
+
if (args.length > 0) {
|
|
562
|
+
branchName = args.shift();
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
// Check if the intended branch name started with a dash and got parsed as flags
|
|
566
|
+
// This happens with e.g., `-b -invalid` where `-invalid` becomes `-i -n -v -a -l -i -d`
|
|
567
|
+
// In this case, we should error that branch names starting with dash are invalid
|
|
568
|
+
if (options.i !== undefined) {
|
|
569
|
+
stderr(`fatal: '-invalid' is not a valid branch name`);
|
|
570
|
+
throw new Error(`'-invalid' is not a valid branch name`);
|
|
571
|
+
}
|
|
572
|
+
stderr('fatal: You must specify a branch name with -b');
|
|
573
|
+
throw new Error('No branch name specified');
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
stderr('fatal: You must specify a branch name with -b');
|
|
578
|
+
throw new Error('No branch name specified');
|
|
579
|
+
}
|
|
580
|
+
if (!isValidBranchName(branchName)) {
|
|
581
|
+
stderr(`fatal: '${branchName}' is not a valid branch name`);
|
|
582
|
+
throw new Error(`'${branchName}' is not a valid branch name`);
|
|
583
|
+
}
|
|
584
|
+
const startPoint = args[0];
|
|
585
|
+
try {
|
|
586
|
+
const result = await createAndSwitch(cwd, branchName, startPoint, { force, quiet });
|
|
587
|
+
if (!quiet && result.success) {
|
|
588
|
+
stdout(`Switched to a new branch '${branchName}'`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (err) {
|
|
592
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
593
|
+
stderr(error.message);
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
// Handle -B flag (create/reset branch)
|
|
599
|
+
if (options.B) {
|
|
600
|
+
// Handle case where -B is followed by something that looks like a flag
|
|
601
|
+
let branchName;
|
|
602
|
+
if (typeof options.B === 'string') {
|
|
603
|
+
branchName = options.B;
|
|
604
|
+
}
|
|
605
|
+
else if (typeof options.B === 'boolean' && args.length > 0) {
|
|
606
|
+
branchName = args.shift();
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
stderr('fatal: You must specify a branch name with -B');
|
|
610
|
+
throw new Error('No branch name specified');
|
|
611
|
+
}
|
|
612
|
+
if (!isValidBranchName(branchName)) {
|
|
613
|
+
stderr(`fatal: '${branchName}' is not a valid branch name`);
|
|
614
|
+
throw new Error(`'${branchName}' is not a valid branch name`);
|
|
615
|
+
}
|
|
616
|
+
const startPoint = args[0];
|
|
617
|
+
try {
|
|
618
|
+
const result = await createAndSwitch(cwd, branchName, startPoint, { force, quiet, reset: true });
|
|
619
|
+
if (!quiet && result.success) {
|
|
620
|
+
stdout(`Switched to and reset branch '${branchName}'`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
625
|
+
stderr(error.message);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
// Handle file checkout (after --)
|
|
631
|
+
if (rawArgs.length > 0) {
|
|
632
|
+
const source = args.length > 0 ? args[0] : undefined;
|
|
633
|
+
try {
|
|
634
|
+
const result = await checkoutFiles(cwd, rawArgs, source, { force, quiet });
|
|
635
|
+
if (!quiet && result.success && result.modifiedFiles) {
|
|
636
|
+
stdout(`Updated ${result.modifiedFiles.length} path(s) from ${result.target}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
641
|
+
stderr(error.message);
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
// Must have a target (branch, tag, or commit)
|
|
647
|
+
if (args.length === 0) {
|
|
648
|
+
stderr('fatal: You must specify a branch to checkout or use -b to create a new branch.\nUsage: gitx checkout <branch>');
|
|
649
|
+
throw new Error('No branch specified');
|
|
650
|
+
}
|
|
651
|
+
const target = args[0];
|
|
652
|
+
// Try to checkout the target
|
|
653
|
+
try {
|
|
654
|
+
const currentHead = await getCurrentHead(cwd);
|
|
655
|
+
// Check if trying to checkout the same branch
|
|
656
|
+
if (currentHead.branch === target && !options.detach) {
|
|
657
|
+
if (!quiet) {
|
|
658
|
+
stdout(`Already on '${target}'`);
|
|
659
|
+
}
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
// Check if target is a branch
|
|
663
|
+
const branchSha = await readBranchSha(cwd, target);
|
|
664
|
+
if (branchSha) {
|
|
665
|
+
if (options.detach) {
|
|
666
|
+
// Detach HEAD at the branch's commit
|
|
667
|
+
await checkoutDetached(cwd, branchSha, { force, quiet });
|
|
668
|
+
if (!quiet) {
|
|
669
|
+
stdout(`Note: switching to '${target}'.`);
|
|
670
|
+
stdout('');
|
|
671
|
+
stdout('You are in \'detached HEAD\' state.');
|
|
672
|
+
stdout('');
|
|
673
|
+
stdout(`HEAD is now at ${branchSha.substring(0, 7)}`);
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const switchResult = await switchBranch(cwd, target, { force, quiet });
|
|
678
|
+
if (!quiet && switchResult.success) {
|
|
679
|
+
stdout(`Switched to branch '${target}'`);
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
// Check if target is a tag
|
|
684
|
+
const tagSha = await readTagSha(cwd, target);
|
|
685
|
+
if (tagSha) {
|
|
686
|
+
await checkoutDetached(cwd, tagSha, { force, quiet });
|
|
687
|
+
if (!quiet) {
|
|
688
|
+
stdout(`Note: switching to '${target}'.`);
|
|
689
|
+
stdout('');
|
|
690
|
+
stdout('You are in \'detached HEAD\' state.');
|
|
691
|
+
stdout('');
|
|
692
|
+
stdout(`HEAD is now at ${tagSha.substring(0, 7)}`);
|
|
693
|
+
}
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
// Check if target looks like a SHA
|
|
697
|
+
if (looksLikeSha(target)) {
|
|
698
|
+
await checkoutDetached(cwd, target, { force, quiet });
|
|
699
|
+
if (!quiet) {
|
|
700
|
+
stdout(`Note: switching to '${target}'.`);
|
|
701
|
+
stdout('');
|
|
702
|
+
stdout('You are in \'detached HEAD\' state.');
|
|
703
|
+
stdout('');
|
|
704
|
+
stdout(`HEAD is now at ${target.substring(0, 7)}`);
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
// Target not found
|
|
709
|
+
const similar = await findSimilarBranch(cwd, target);
|
|
710
|
+
let errorMsg = `error: pathspec '${target}' did not match any file(s) known to git`;
|
|
711
|
+
if (similar) {
|
|
712
|
+
errorMsg = `error: pathspec '${target}' did not match any file(s) known to git\nDid you mean '${similar}'?`;
|
|
713
|
+
}
|
|
714
|
+
stderr(errorMsg);
|
|
715
|
+
throw new Error(errorMsg);
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
719
|
+
if (!error.message.includes('pathspec')) {
|
|
720
|
+
stderr(error.message);
|
|
721
|
+
}
|
|
722
|
+
throw error;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
//# sourceMappingURL=checkout.js.map
|