@ottocode/server 0.1.271 → 0.1.273
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/package.json +3 -3
- package/src/openapi/schemas.ts +33 -0
- package/src/routes/git/index.ts +2 -0
- package/src/routes/git/init.ts +24 -1
- package/src/routes/git/rebase.ts +178 -0
- package/src/routes/git/schemas.ts +5 -0
- package/src/routes/git/staging-service.ts +4 -1
- package/src/routes/git/status.ts +12 -5
- package/src/routes/git/types.ts +17 -0
- package/src/routes/git/utils.ts +103 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.273",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typecheck": "tsc --noEmit"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@ottocode/database": "0.1.
|
|
65
|
-
"@ottocode/sdk": "0.1.
|
|
64
|
+
"@ottocode/database": "0.1.273",
|
|
65
|
+
"@ottocode/sdk": "0.1.273",
|
|
66
66
|
"@hono/zod-openapi": "^1.1.5",
|
|
67
67
|
"ai-sdk-ollama": "^3.8.3",
|
|
68
68
|
"drizzle-orm": "^0.44.5",
|
package/src/openapi/schemas.ts
CHANGED
|
@@ -267,6 +267,13 @@ export const schemas = {
|
|
|
267
267
|
type: 'object',
|
|
268
268
|
properties: {
|
|
269
269
|
branch: { type: 'string' },
|
|
270
|
+
headSha: { type: 'string' },
|
|
271
|
+
shortHeadSha: { type: 'string' },
|
|
272
|
+
isDetached: { type: 'boolean' },
|
|
273
|
+
operation: {
|
|
274
|
+
$ref: '#/components/schemas/GitOperation',
|
|
275
|
+
nullable: true,
|
|
276
|
+
},
|
|
270
277
|
ahead: { type: 'integer' },
|
|
271
278
|
behind: { type: 'integer' },
|
|
272
279
|
staged: {
|
|
@@ -295,6 +302,10 @@ export const schemas = {
|
|
|
295
302
|
},
|
|
296
303
|
required: [
|
|
297
304
|
'branch',
|
|
305
|
+
'headSha',
|
|
306
|
+
'shortHeadSha',
|
|
307
|
+
'isDetached',
|
|
308
|
+
'operation',
|
|
298
309
|
'ahead',
|
|
299
310
|
'behind',
|
|
300
311
|
'staged',
|
|
@@ -307,6 +318,28 @@ export const schemas = {
|
|
|
307
318
|
'remotes',
|
|
308
319
|
],
|
|
309
320
|
},
|
|
321
|
+
GitOperation: {
|
|
322
|
+
type: 'object',
|
|
323
|
+
properties: {
|
|
324
|
+
type: {
|
|
325
|
+
type: 'string',
|
|
326
|
+
enum: [
|
|
327
|
+
'rebase',
|
|
328
|
+
'rebase-interactive',
|
|
329
|
+
'merge',
|
|
330
|
+
'cherry-pick',
|
|
331
|
+
'revert',
|
|
332
|
+
'bisect',
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
label: { type: 'string' },
|
|
336
|
+
current: { type: 'integer' },
|
|
337
|
+
total: { type: 'integer' },
|
|
338
|
+
headName: { type: 'string' },
|
|
339
|
+
onto: { type: 'string' },
|
|
340
|
+
},
|
|
341
|
+
required: ['type', 'label'],
|
|
342
|
+
},
|
|
310
343
|
GitFile: {
|
|
311
344
|
type: 'object',
|
|
312
345
|
properties: {
|
package/src/routes/git/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { registerStagingRoutes } from './staging.ts';
|
|
|
6
6
|
import { registerCommitRoutes } from './commit.ts';
|
|
7
7
|
import { registerPushRoute } from './push.ts';
|
|
8
8
|
import { registerPullRoute } from './pull.ts';
|
|
9
|
+
import { registerRebaseRoute } from './rebase.ts';
|
|
9
10
|
import { registerInitRoute } from './init.ts';
|
|
10
11
|
import { registerRemoteRoutes } from './remote.ts';
|
|
11
12
|
|
|
@@ -19,6 +20,7 @@ export function registerGitRoutes(app: Hono) {
|
|
|
19
20
|
registerCommitRoutes(app);
|
|
20
21
|
registerPushRoute(app);
|
|
21
22
|
registerPullRoute(app);
|
|
23
|
+
registerRebaseRoute(app);
|
|
22
24
|
registerInitRoute(app);
|
|
23
25
|
registerRemoteRoutes(app);
|
|
24
26
|
}
|
package/src/routes/git/init.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
3
5
|
import { promisify } from 'node:util';
|
|
4
6
|
import { gitStatusSchema } from './schemas.ts';
|
|
7
|
+
import { validateAndGetGitRoot } from './utils.ts';
|
|
5
8
|
import { openApiRoute } from '../../openapi/route.ts';
|
|
6
9
|
|
|
7
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -89,7 +92,27 @@ export function registerInitRoute(app: Hono) {
|
|
|
89
92
|
try {
|
|
90
93
|
const body = await c.req.json().catch(() => ({}));
|
|
91
94
|
const { project } = gitStatusSchema.parse(body);
|
|
92
|
-
const requestedPath = project || process.cwd();
|
|
95
|
+
const requestedPath = await realpath(resolve(project || process.cwd()));
|
|
96
|
+
|
|
97
|
+
const pathStats = await stat(requestedPath);
|
|
98
|
+
if (!pathStats.isDirectory()) {
|
|
99
|
+
return c.json(
|
|
100
|
+
{
|
|
101
|
+
status: 'error',
|
|
102
|
+
error: 'Git repository can only be initialized in a directory',
|
|
103
|
+
code: 'NOT_A_DIRECTORY',
|
|
104
|
+
},
|
|
105
|
+
400,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const existing = await validateAndGetGitRoot(requestedPath);
|
|
110
|
+
if (!('error' in existing)) {
|
|
111
|
+
return c.json({
|
|
112
|
+
status: 'ok',
|
|
113
|
+
data: { initialized: false, path: existing.gitRoot },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
93
116
|
|
|
94
117
|
await execFileAsync('git', ['init'], { cwd: requestedPath });
|
|
95
118
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { gitRebaseSchema } from './schemas.ts';
|
|
5
|
+
import { getGitOperationState, validateAndGetGitRoot } from './utils.ts';
|
|
6
|
+
import { openApiRoute } from '../../openapi/route.ts';
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
const REBASE_ACTION_ARGS = {
|
|
11
|
+
continue: '--continue',
|
|
12
|
+
abort: '--abort',
|
|
13
|
+
skip: '--skip',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export function registerRebaseRoute(app: Hono) {
|
|
17
|
+
openApiRoute(
|
|
18
|
+
app,
|
|
19
|
+
{
|
|
20
|
+
method: 'post',
|
|
21
|
+
path: '/v1/git/rebase',
|
|
22
|
+
tags: ['git'],
|
|
23
|
+
operationId: 'performGitRebaseAction',
|
|
24
|
+
summary: 'Perform a git rebase action',
|
|
25
|
+
description:
|
|
26
|
+
'Runs git rebase --continue, --abort, or --skip for an in-progress rebase.',
|
|
27
|
+
requestBody: {
|
|
28
|
+
required: true,
|
|
29
|
+
content: {
|
|
30
|
+
'application/json': {
|
|
31
|
+
schema: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
project: { type: 'string' },
|
|
35
|
+
action: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
enum: ['continue', 'abort', 'skip'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['action'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
responses: {
|
|
46
|
+
'200': {
|
|
47
|
+
description: 'OK',
|
|
48
|
+
content: {
|
|
49
|
+
'application/json': {
|
|
50
|
+
schema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
status: { type: 'string', enum: ['ok'] },
|
|
54
|
+
data: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
action: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
enum: ['continue', 'abort', 'skip'],
|
|
60
|
+
},
|
|
61
|
+
output: { type: 'string' },
|
|
62
|
+
},
|
|
63
|
+
required: ['action', 'output'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
required: ['status', 'data'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
'400': {
|
|
72
|
+
description: 'Error',
|
|
73
|
+
content: {
|
|
74
|
+
'application/json': {
|
|
75
|
+
schema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
status: { type: 'string', enum: ['error'] },
|
|
79
|
+
error: { type: 'string' },
|
|
80
|
+
code: { type: 'string' },
|
|
81
|
+
},
|
|
82
|
+
required: ['status', 'error'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
'409': {
|
|
88
|
+
description: 'No rebase is currently in progress',
|
|
89
|
+
content: {
|
|
90
|
+
'application/json': {
|
|
91
|
+
schema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
status: { type: 'string', enum: ['error'] },
|
|
95
|
+
error: { type: 'string' },
|
|
96
|
+
code: { type: 'string' },
|
|
97
|
+
},
|
|
98
|
+
required: ['status', 'error'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
'500': {
|
|
104
|
+
description: 'Error',
|
|
105
|
+
content: {
|
|
106
|
+
'application/json': {
|
|
107
|
+
schema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
status: { type: 'string', enum: ['error'] },
|
|
111
|
+
error: { type: 'string' },
|
|
112
|
+
code: { type: 'string' },
|
|
113
|
+
},
|
|
114
|
+
required: ['status', 'error'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
async (c) => {
|
|
122
|
+
try {
|
|
123
|
+
const body = await c.req.json().catch(() => ({}));
|
|
124
|
+
const { project, action } = gitRebaseSchema.parse(body);
|
|
125
|
+
const requestedPath = project || process.cwd();
|
|
126
|
+
|
|
127
|
+
const validation = await validateAndGetGitRoot(requestedPath);
|
|
128
|
+
if ('error' in validation) {
|
|
129
|
+
return c.json(
|
|
130
|
+
{ status: 'error', error: validation.error, code: validation.code },
|
|
131
|
+
400,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const operation = await getGitOperationState(validation.gitRoot);
|
|
136
|
+
if (!operation?.type.startsWith('rebase')) {
|
|
137
|
+
return c.json(
|
|
138
|
+
{
|
|
139
|
+
status: 'error',
|
|
140
|
+
error: 'No rebase is currently in progress',
|
|
141
|
+
code: 'NO_REBASE_IN_PROGRESS',
|
|
142
|
+
},
|
|
143
|
+
409,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const { stdout, stderr } = await execFileAsync(
|
|
148
|
+
'git',
|
|
149
|
+
['rebase', REBASE_ACTION_ARGS[action]],
|
|
150
|
+
{
|
|
151
|
+
cwd: validation.gitRoot,
|
|
152
|
+
env: {
|
|
153
|
+
...process.env,
|
|
154
|
+
GIT_EDITOR: 'true',
|
|
155
|
+
GIT_SEQUENCE_EDITOR: 'true',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return c.json({
|
|
161
|
+
status: 'ok',
|
|
162
|
+
data: { action, output: `${stdout}${stderr}`.trim() },
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return c.json(
|
|
166
|
+
{
|
|
167
|
+
status: 'error',
|
|
168
|
+
error:
|
|
169
|
+
error instanceof Error
|
|
170
|
+
? error.message
|
|
171
|
+
: 'Failed to perform rebase action',
|
|
172
|
+
},
|
|
173
|
+
500,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -51,6 +51,11 @@ export const gitPullSchema = z.object({
|
|
|
51
51
|
project: z.string().optional(),
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
export const gitRebaseSchema = z.object({
|
|
55
|
+
project: z.string().optional(),
|
|
56
|
+
action: z.enum(['continue', 'abort', 'skip']),
|
|
57
|
+
});
|
|
58
|
+
|
|
54
59
|
export const gitRemoteAddSchema = z.object({
|
|
55
60
|
project: z.string().optional(),
|
|
56
61
|
name: z.string().min(1),
|
|
@@ -24,7 +24,10 @@ const actionConfig: Record<
|
|
|
24
24
|
> = {
|
|
25
25
|
stage: {
|
|
26
26
|
schema: gitStageSchema,
|
|
27
|
-
command: (files) =>
|
|
27
|
+
command: (files) =>
|
|
28
|
+
files.length === 1 && files[0] === '.'
|
|
29
|
+
? ['add', '-A']
|
|
30
|
+
: ['add', '--', ...files],
|
|
28
31
|
dataKey: 'staged',
|
|
29
32
|
fallbackError: 'Failed to stage files',
|
|
30
33
|
},
|
package/src/routes/git/status.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
validateAndGetGitRoot,
|
|
7
7
|
parseGitStatus,
|
|
8
8
|
getAheadBehind,
|
|
9
|
-
|
|
9
|
+
getHeadInfo,
|
|
10
|
+
getGitOperationState,
|
|
10
11
|
} from './utils.ts';
|
|
11
12
|
import { openApiRoute } from '../../openapi/route.ts';
|
|
12
13
|
|
|
@@ -133,9 +134,11 @@ export function registerStatusRoute(app: Hono) {
|
|
|
133
134
|
gitRoot,
|
|
134
135
|
);
|
|
135
136
|
|
|
136
|
-
const { ahead, behind } = await
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const [{ ahead, behind }, headInfo, operation] = await Promise.all([
|
|
138
|
+
getAheadBehind(gitRoot),
|
|
139
|
+
getHeadInfo(gitRoot),
|
|
140
|
+
getGitOperationState(gitRoot),
|
|
141
|
+
]);
|
|
139
142
|
|
|
140
143
|
let hasUpstream = false;
|
|
141
144
|
try {
|
|
@@ -168,7 +171,11 @@ export function registerStatusRoute(app: Hono) {
|
|
|
168
171
|
return c.json({
|
|
169
172
|
status: 'ok',
|
|
170
173
|
data: {
|
|
171
|
-
branch,
|
|
174
|
+
branch: headInfo.branch,
|
|
175
|
+
headSha: headInfo.headSha,
|
|
176
|
+
shortHeadSha: headInfo.shortHeadSha,
|
|
177
|
+
isDetached: headInfo.isDetached,
|
|
178
|
+
operation,
|
|
172
179
|
ahead,
|
|
173
180
|
behind,
|
|
174
181
|
hasUpstream,
|
package/src/routes/git/types.ts
CHANGED
|
@@ -21,6 +21,23 @@ export interface GitFile {
|
|
|
21
21
|
| 'both-deleted';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export type GitOperationType =
|
|
25
|
+
| 'rebase'
|
|
26
|
+
| 'rebase-interactive'
|
|
27
|
+
| 'merge'
|
|
28
|
+
| 'cherry-pick'
|
|
29
|
+
| 'revert'
|
|
30
|
+
| 'bisect';
|
|
31
|
+
|
|
32
|
+
export interface GitOperationState {
|
|
33
|
+
type: GitOperationType;
|
|
34
|
+
label: string;
|
|
35
|
+
current?: number;
|
|
36
|
+
total?: number;
|
|
37
|
+
headName?: string;
|
|
38
|
+
onto?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
export interface GitRoot {
|
|
25
42
|
gitRoot: string;
|
|
26
43
|
}
|
package/src/routes/git/utils.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { extname, isAbsolute, join } from 'node:path';
|
|
3
4
|
import { promisify } from 'node:util';
|
|
4
|
-
import type { GitFile, GitRoot, GitError } from './types.ts';
|
|
5
|
+
import type { GitFile, GitRoot, GitError, GitOperationState } from './types.ts';
|
|
5
6
|
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
|
|
@@ -163,7 +164,9 @@ export function parseGitStatus(
|
|
|
163
164
|
const xy = parts[1];
|
|
164
165
|
const x = xy[0];
|
|
165
166
|
const y = xy[1];
|
|
166
|
-
const
|
|
167
|
+
const pathStartIndex = line.startsWith('2 ') ? 9 : 8;
|
|
168
|
+
const rawPath = parts.slice(pathStartIndex).join(' ');
|
|
169
|
+
const [path, oldPath] = rawPath.split('\t');
|
|
167
170
|
const absPath = join(gitRoot, path);
|
|
168
171
|
|
|
169
172
|
if (x !== '.') {
|
|
@@ -173,6 +176,7 @@ export function parseGitStatus(
|
|
|
173
176
|
status: getStatusFromCodeV2(x),
|
|
174
177
|
staged: true,
|
|
175
178
|
isNew: x === 'A',
|
|
179
|
+
oldPath,
|
|
176
180
|
});
|
|
177
181
|
}
|
|
178
182
|
|
|
@@ -183,6 +187,7 @@ export function parseGitStatus(
|
|
|
183
187
|
status: getStatusFromCodeV2(y),
|
|
184
188
|
staged: false,
|
|
185
189
|
isNew: false,
|
|
190
|
+
oldPath,
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
193
|
} else if (line.startsWith('? ')) {
|
|
@@ -247,3 +252,98 @@ export async function getCurrentBranch(gitRoot: string): Promise<string> {
|
|
|
247
252
|
return 'unknown';
|
|
248
253
|
}
|
|
249
254
|
}
|
|
255
|
+
|
|
256
|
+
export async function getHeadInfo(gitRoot: string): Promise<{
|
|
257
|
+
branch: string;
|
|
258
|
+
headSha: string;
|
|
259
|
+
shortHeadSha: string;
|
|
260
|
+
isDetached: boolean;
|
|
261
|
+
}> {
|
|
262
|
+
const [branchResult, headResult, shortHeadResult] = await Promise.allSettled([
|
|
263
|
+
execFileAsync('git', ['branch', '--show-current'], { cwd: gitRoot }),
|
|
264
|
+
execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: gitRoot }),
|
|
265
|
+
execFileAsync('git', ['rev-parse', '--short', 'HEAD'], { cwd: gitRoot }),
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
const branch =
|
|
269
|
+
branchResult.status === 'fulfilled' ? branchResult.value.stdout.trim() : '';
|
|
270
|
+
const headSha =
|
|
271
|
+
headResult.status === 'fulfilled' ? headResult.value.stdout.trim() : '';
|
|
272
|
+
const shortHeadSha =
|
|
273
|
+
shortHeadResult.status === 'fulfilled'
|
|
274
|
+
? shortHeadResult.value.stdout.trim()
|
|
275
|
+
: headSha.slice(0, 7);
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
branch: branch || 'HEAD',
|
|
279
|
+
headSha,
|
|
280
|
+
shortHeadSha,
|
|
281
|
+
isDetached: !branch,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function getGitDir(gitRoot: string): Promise<string | null> {
|
|
286
|
+
try {
|
|
287
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--git-dir'], {
|
|
288
|
+
cwd: gitRoot,
|
|
289
|
+
});
|
|
290
|
+
const gitDir = stdout.trim();
|
|
291
|
+
return isAbsolute(gitDir) ? gitDir : join(gitRoot, gitDir);
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function readGitStateFile(dir: string, file: string): string | undefined {
|
|
298
|
+
const path = join(dir, file);
|
|
299
|
+
if (!existsSync(path)) return undefined;
|
|
300
|
+
return readFileSync(path, 'utf8').trim() || undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readRebaseState(
|
|
304
|
+
gitDir: string,
|
|
305
|
+
dirName: 'rebase-merge' | 'rebase-apply',
|
|
306
|
+
): GitOperationState | null {
|
|
307
|
+
const rebaseDir = join(gitDir, dirName);
|
|
308
|
+
if (!existsSync(rebaseDir)) return null;
|
|
309
|
+
|
|
310
|
+
const current = Number(readGitStateFile(rebaseDir, 'msgnum')) || undefined;
|
|
311
|
+
const total = Number(readGitStateFile(rebaseDir, 'end')) || undefined;
|
|
312
|
+
const isInteractive = existsSync(join(rebaseDir, 'interactive'));
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
type: isInteractive ? 'rebase-interactive' : 'rebase',
|
|
316
|
+
label: isInteractive ? 'Interactive rebase' : 'Rebase',
|
|
317
|
+
current,
|
|
318
|
+
total,
|
|
319
|
+
headName: readGitStateFile(rebaseDir, 'head-name'),
|
|
320
|
+
onto: readGitStateFile(rebaseDir, 'onto'),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function getGitOperationState(
|
|
325
|
+
gitRoot: string,
|
|
326
|
+
): Promise<GitOperationState | null> {
|
|
327
|
+
const gitDir = await getGitDir(gitRoot);
|
|
328
|
+
if (!gitDir) return null;
|
|
329
|
+
|
|
330
|
+
const rebaseState =
|
|
331
|
+
readRebaseState(gitDir, 'rebase-merge') ??
|
|
332
|
+
readRebaseState(gitDir, 'rebase-apply');
|
|
333
|
+
if (rebaseState) return rebaseState;
|
|
334
|
+
|
|
335
|
+
if (existsSync(join(gitDir, 'MERGE_HEAD'))) {
|
|
336
|
+
return { type: 'merge', label: 'Merge' };
|
|
337
|
+
}
|
|
338
|
+
if (existsSync(join(gitDir, 'CHERRY_PICK_HEAD'))) {
|
|
339
|
+
return { type: 'cherry-pick', label: 'Cherry-pick' };
|
|
340
|
+
}
|
|
341
|
+
if (existsSync(join(gitDir, 'REVERT_HEAD'))) {
|
|
342
|
+
return { type: 'revert', label: 'Revert' };
|
|
343
|
+
}
|
|
344
|
+
if (existsSync(join(gitDir, 'BISECT_LOG'))) {
|
|
345
|
+
return { type: 'bisect', label: 'Bisect' };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return null;
|
|
349
|
+
}
|