plugin-git-manager 1.1.10 → 1.2.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.
Files changed (38) hide show
  1. package/dist/client/187.d5545b7cc8b90bfc.js +1 -0
  2. package/dist/client/228.7588a0707cb3694a.js +0 -9
  3. package/dist/client/index.js +1 -10
  4. package/dist/externalVersion.js +5 -14
  5. package/dist/index.js +0 -9
  6. package/dist/locale/en-US.json +2 -0
  7. package/dist/locale/vi-VN.json +2 -0
  8. package/dist/server/actions/git-actions.js +15 -21
  9. package/dist/server/actions/gitlab-api.js +0 -9
  10. package/dist/server/actions/poller.js +0 -9
  11. package/dist/server/actions/review.d.ts +5 -2
  12. package/dist/server/actions/review.js +430 -60
  13. package/dist/server/ai-tools.js +0 -9
  14. package/dist/server/collections/gitCodeReviews.d.ts +1 -1
  15. package/dist/server/collections/gitCodeReviews.js +1 -9
  16. package/dist/server/collections/gitRepositories.d.ts +1 -1
  17. package/dist/server/collections/gitRepositories.js +0 -9
  18. package/dist/server/collections/gitReviewFlows.d.ts +1 -1
  19. package/dist/server/collections/gitReviewFlows.js +0 -9
  20. package/dist/server/index.js +0 -9
  21. package/dist/server/migrations/20260508000000-add-auto-review-flow-id.js +0 -9
  22. package/dist/server/plugin.js +5 -9
  23. package/dist/server/poller.js +0 -9
  24. package/dist/server/utils/gitlab-url.js +0 -9
  25. package/dist/server/utils/redact.js +0 -9
  26. package/package.json +2 -2
  27. package/src/client/components/CommitHistory.tsx +3 -0
  28. package/src/client/components/FileExplorer.tsx +29 -24
  29. package/src/client/components/GitOperations.tsx +3 -0
  30. package/src/client/components/ReviewFlows.tsx +11 -1
  31. package/src/client/components/ReviewHistory.tsx +14 -1
  32. package/src/locale/en-US.json +2 -0
  33. package/src/locale/vi-VN.json +2 -0
  34. package/src/server/actions/git-actions.ts +15 -12
  35. package/src/server/actions/review.ts +504 -63
  36. package/src/server/collections/gitCodeReviews.ts +1 -0
  37. package/src/server/plugin.ts +25 -20
  38. package/dist/client/187.08dd0bf4d0f68036.js +0 -10
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("@nocobase/database").CollectionOptions;
2
2
  export default _default;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -166,6 +157,7 @@ var gitCodeReviews_default = (0, import_database.defineCollection)({
166
157
  { value: "pending_approval", label: "Pending Approval" },
167
158
  { value: "approved", label: "Approved" },
168
159
  { value: "posted", label: "Posted" },
160
+ { value: "post_failed", label: "Post Failed" },
169
161
  { value: "skipped", label: "Skipped" },
170
162
  { value: "rejected", label: "Rejected" }
171
163
  ]
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("@nocobase/database").CollectionOptions;
2
2
  export default _default;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,2 +1,2 @@
1
- declare const _default: any;
1
+ declare const _default: import("@nocobase/database").CollectionOptions;
2
2
  export default _default;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __create = Object.create;
11
2
  var __defProp = Object.defineProperty;
12
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __create = Object.create;
11
2
  var __defProp = Object.defineProperty;
12
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -105,6 +96,7 @@ class PluginGitManagerServer extends import_server.Plugin {
105
96
  }
106
97
  return next();
107
98
  });
99
+ (0, import_review.registerReviewQueue)(this.app);
108
100
  (0, import_ai_tools.registerGitReviewAiTools)(this.app);
109
101
  this.app.on("afterStart", async () => {
110
102
  await ensureAutoReviewFlowSchema(this.app).catch(
@@ -122,9 +114,11 @@ class PluginGitManagerServer extends import_server.Plugin {
122
114
  (0, import_poller.startPoller)(this.app);
123
115
  });
124
116
  this.app.on("beforeStop", () => {
117
+ (0, import_review.unregisterReviewQueue)(this.app);
125
118
  (0, import_poller.stopPoller)();
126
119
  });
127
120
  this.app.on("beforeDestroy", () => {
121
+ (0, import_review.unregisterReviewQueue)(this.app);
128
122
  (0, import_poller.stopPoller)();
129
123
  });
130
124
  this.app.acl.registerSnippet({
@@ -207,9 +201,11 @@ class PluginGitManagerServer extends import_server.Plugin {
207
201
  await ((_a = this.app.db.getCollection("gitRepositories")) == null ? void 0 : _a.sync());
208
202
  }
209
203
  async beforeDisable() {
204
+ (0, import_review.unregisterReviewQueue)(this.app);
210
205
  (0, import_poller.stopPoller)();
211
206
  }
212
207
  async beforeUnload() {
208
+ (0, import_review.unregisterReviewQueue)(this.app);
213
209
  (0, import_poller.stopPoller)();
214
210
  }
215
211
  }
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -1,12 +1,3 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
1
  var __defProp = Object.defineProperty;
11
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Git Manager",
4
4
  "displayName.zh-CN": "Git 管理器",
5
5
  "description": "Manage Git repositories with PAT authentication - pull, push, fetch, diff, file browsing",
6
- "version": "1.1.10",
6
+ "version": "1.2.0",
7
7
  "license": "Apache-2.0",
8
8
  "main": "dist/server/index.js",
9
9
  "files": [
@@ -34,4 +34,4 @@
34
34
  "dependencies": {
35
35
  "simple-git": "^3.27.0"
36
36
  }
37
- }
37
+ }
@@ -49,6 +49,9 @@ export const CommitHistory: React.FC = () => {
49
49
  });
50
50
  const responseData = data?.data?.data || data?.data;
51
51
  setCommits(responseData?.all || []);
52
+ } catch (error) {
53
+ console.warn('Failed to load commit history:', error);
54
+ setCommits([]);
52
55
  } finally {
53
56
  setLoading(false);
54
57
  }
@@ -75,30 +75,35 @@ export const FileExplorer: React.FC = () => {
75
75
  const loadTree = useCallback(
76
76
  async (treePath = '', ref = currentRef) => {
77
77
  if (!selectedRepo) return [];
78
- const { data } = await api.request({
79
- url: 'gitManager:fileTree',
80
- params: { repositoryId: selectedRepo.id, ref, treePath },
81
- });
82
- const responseData = data?.data || data || [];
83
- const list = Array.isArray(responseData) ? responseData : (Array.isArray(responseData?.data) ? responseData.data : []);
84
-
85
- return list.map((item: any) => ({
86
- key: item.path,
87
- title: (
88
- <Text style={{ fontSize: 13 }}>
89
- {item.name}
90
- {item.type === 'blob' && item.size > 0 && (
91
- <Text type="secondary" style={{ fontSize: 11, marginLeft: 8 }}>
92
- {item.size > 1024 ? `${(item.size / 1024).toFixed(1)}KB` : `${item.size}B`}
93
- </Text>
94
- )}
95
- </Text>
96
- ),
97
- icon: getFileIcon(item.name, item.type),
98
- isLeaf: item.type === 'blob',
99
- filePath: item.path,
100
- fileType: item.type,
101
- }));
78
+ try {
79
+ const { data } = await api.request({
80
+ url: 'gitManager:fileTree',
81
+ params: { repositoryId: selectedRepo.id, ref, treePath },
82
+ });
83
+ const responseData = data?.data || data || [];
84
+ const list = Array.isArray(responseData) ? responseData : (Array.isArray(responseData?.data) ? responseData.data : []);
85
+
86
+ return list.map((item: any) => ({
87
+ key: item.path,
88
+ title: (
89
+ <Text style={{ fontSize: 13 }}>
90
+ {item.name}
91
+ {item.type === 'blob' && item.size > 0 && (
92
+ <Text type="secondary" style={{ fontSize: 11, marginLeft: 8 }}>
93
+ {item.size > 1024 ? `${(item.size / 1024).toFixed(1)}KB` : `${item.size}B`}
94
+ </Text>
95
+ )}
96
+ </Text>
97
+ ),
98
+ icon: getFileIcon(item.name, item.type),
99
+ isLeaf: item.type === 'blob',
100
+ filePath: item.path,
101
+ fileType: item.type,
102
+ }));
103
+ } catch (error) {
104
+ console.warn('Failed to load file tree:', error);
105
+ return [];
106
+ }
102
107
  },
103
108
  [api, selectedRepo, currentRef],
104
109
  );
@@ -50,6 +50,9 @@ export const GitOperations: React.FC = () => {
50
50
  });
51
51
  const responseData = data?.data?.data || data?.data;
52
52
  setStatusData(responseData);
53
+ } catch (error) {
54
+ console.warn('Failed to load git status:', error);
55
+ setStatusData(null);
53
56
  } finally {
54
57
  setActionLoading(null);
55
58
  }
@@ -22,6 +22,15 @@ const POST_LABELS: Record<string, string> = {
22
22
  disabled: 'Disabled',
23
23
  };
24
24
 
25
+ const DEFAULT_INSTRUCTION = `Please review the code changes with a focus on:
26
+ 1. Best Practices & Style:
27
+ - C#/VB.NET: Follow standard naming conventions (PascalCase for methods/properties, camelCase for fields). Ensure proper use of async/await (avoid .Result/.Wait()). Use LINQ efficiently.
28
+ - JS/TS: Use strict equality (===), prefer const/let. Ensure TypeScript types are explicit and avoid 'any'.
29
+ 2. Performance: Watch out for N+1 queries, unnecessary loops, and memory leaks.
30
+ 3. Security: Identify potential SQL injections, XSS vulnerabilities, and improper data validation.
31
+ 4. Maintainability: Check for readability, SOLID principles, and DRY. Ensure meaningful naming.
32
+ 5. Error Handling: Verify that exceptions are properly caught, handled, and logged.`;
33
+
25
34
  export const ReviewFlows: React.FC = () => {
26
35
  const t = useT();
27
36
  const api = useAPIClient();
@@ -92,6 +101,7 @@ export const ReviewFlows: React.FC = () => {
92
101
  enabled: true,
93
102
  triggerMode: 'manual',
94
103
  postMode: 'manual',
104
+ instructions: DEFAULT_INSTRUCTION,
95
105
  });
96
106
  setOpen(true);
97
107
  };
@@ -259,7 +269,7 @@ export const ReviewFlows: React.FC = () => {
259
269
  <Input placeholder="^(feature|hotfix)/.*$" />
260
270
  </Form.Item>
261
271
  <Form.Item name="instructions" label={t('Additional Instructions (optional)')}>
262
- <Input.TextArea rows={4} placeholder={t('Extra guidance appended to every review prompt')} />
272
+ <Input.TextArea rows={8} placeholder={t('Extra guidance appended to every review prompt')} />
263
273
  </Form.Item>
264
274
  <Form.Item name="enabled" label={t('Enabled')} valuePropName="checked">
265
275
  <Switch />
@@ -25,6 +25,7 @@ const POST_STATUS_COLOR: Record<string, string> = {
25
25
  pending_approval: 'orange',
26
26
  approved: 'cyan',
27
27
  posted: 'green',
28
+ post_failed: 'red',
28
29
  skipped: 'default',
29
30
  rejected: 'red',
30
31
  };
@@ -278,6 +279,7 @@ export const ReviewHistory: React.FC<{ initialFilter?: 'all' | 'pending_approval
278
279
  { value: 'pending_approval', label: t('pending_approval') },
279
280
  { value: 'approved', label: t('approved') },
280
281
  { value: 'posted', label: t('posted') },
282
+ { value: 'post_failed', label: t('post_failed') },
281
283
  { value: 'skipped', label: t('skipped') },
282
284
  { value: 'rejected', label: t('rejected') },
283
285
  ]}
@@ -395,7 +397,9 @@ const ReviewDetailView: React.FC<{
395
397
  const canApprove =
396
398
  review.status === 'completed' &&
397
399
  review.targetType === 'mr' &&
398
- (review.postStatus === 'pending_approval' || review.postStatus === 'approved');
400
+ (review.postStatus === 'pending_approval' ||
401
+ review.postStatus === 'approved' ||
402
+ review.postStatus === 'post_failed');
399
403
 
400
404
  return (
401
405
  <div>
@@ -417,6 +421,15 @@ const ReviewDetailView: React.FC<{
417
421
  style={{ marginBottom: 16 }}
418
422
  />
419
423
  )}
424
+ {review.status === 'completed' && review.postStatus === 'post_failed' && review.error && (
425
+ <Alert
426
+ type="error"
427
+ showIcon
428
+ message={t('Post Failed')}
429
+ description={<pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>{review.error}</pre>}
430
+ style={{ marginBottom: 16 }}
431
+ />
432
+ )}
420
433
 
421
434
  {/* Action bar */}
422
435
  {canApprove && (
@@ -125,6 +125,8 @@
125
125
  "Posted": "Posted",
126
126
  "Pending Approval": "Pending Approval",
127
127
  "Approved": "Approved",
128
+ "Post Failed": "Post Failed",
129
+ "post_failed": "Post failed",
128
130
  "Rejected": "Rejected",
129
131
  "Skipped": "Skipped",
130
132
  "Running": "Running",
@@ -125,6 +125,8 @@
125
125
  "Posted": "Đã đăng",
126
126
  "Pending Approval": "Chờ duyệt",
127
127
  "Approved": "Đã duyệt",
128
+ "Post Failed": "Đăng thất bại",
129
+ "post_failed": "Đăng thất bại",
128
130
  "Rejected": "Đã từ chối",
129
131
  "Skipped": "Đã bỏ qua",
130
132
  "Running": "Đang chạy",
@@ -91,7 +91,10 @@ function getAuthUrl(repoUrl: string, pat: string, username?: string): string {
91
91
  return url.toString();
92
92
  }
93
93
 
94
- function getGit(localPath: string): SimpleGit {
94
+ function getGit(ctx: Context, localPath: string): SimpleGit {
95
+ if (!fs.existsSync(localPath)) {
96
+ ctx.throw(400, 'Repository directory does not exist. Please clone the repository first.');
97
+ }
95
98
  return simpleGit(localPath);
96
99
  }
97
100
 
@@ -170,7 +173,7 @@ export async function pull(ctx: Context, next: () => Promise<void>) {
170
173
  const repoUrl = (repo.get('repoUrl') as string || '').trim();
171
174
  const username = (repo.get('username') as string || '').trim();
172
175
 
173
- const git = getGit(localPath);
176
+ const git = getGit(ctx, localPath);
174
177
  const result = await withAuth(git, localPath, repoUrl, pat, () => git.pull(), username);
175
178
 
176
179
  ctx.body = { success: true, data: result };
@@ -184,7 +187,7 @@ export async function push(ctx: Context, next: () => Promise<void>) {
184
187
  const repoUrl = (repo.get('repoUrl') as string || '').trim();
185
188
  const username = (repo.get('username') as string || '').trim();
186
189
 
187
- const git = getGit(localPath);
190
+ const git = getGit(ctx, localPath);
188
191
  const result = await withAuth(git, localPath, repoUrl, pat, () => git.push(), username);
189
192
 
190
193
  ctx.body = { success: true, data: result };
@@ -198,7 +201,7 @@ export async function fetch(ctx: Context, next: () => Promise<void>) {
198
201
  const repoUrl = (repo.get('repoUrl') as string || '').trim();
199
202
  const username = (repo.get('username') as string || '').trim();
200
203
 
201
- const git = getGit(localPath);
204
+ const git = getGit(ctx, localPath);
202
205
  const result = await withAuth(git, localPath, repoUrl, pat, () => git.fetch(), username);
203
206
 
204
207
  ctx.body = { success: true, data: result };
@@ -210,7 +213,7 @@ export async function diff(ctx: Context, next: () => Promise<void>) {
210
213
  const localPath = validateLocalPath(repo.get('localPath'));
211
214
  const { file, commitHash, compareHash } = ctx.action.params;
212
215
 
213
- const git = getGit(localPath);
216
+ const git = getGit(ctx, localPath);
214
217
  const args: string[] = [];
215
218
  if (commitHash && compareHash) {
216
219
  args.push(validateRef(commitHash), validateRef(compareHash));
@@ -230,7 +233,7 @@ export async function diff(ctx: Context, next: () => Promise<void>) {
230
233
  export async function status(ctx: Context, next: () => Promise<void>) {
231
234
  const repo = await getRepo(ctx);
232
235
  const localPath = validateLocalPath(repo.get('localPath'));
233
- const result = await getGit(localPath).status();
236
+ const result = await getGit(ctx, localPath).status();
234
237
  ctx.body = { success: true, data: result };
235
238
  await next();
236
239
  }
@@ -247,7 +250,7 @@ export async function log(ctx: Context, next: () => Promise<void>) {
247
250
  options.file = file;
248
251
  }
249
252
 
250
- const result = await getGit(localPath).log(options);
253
+ const result = await getGit(ctx, localPath).log(options);
251
254
  ctx.body = { success: true, data: result };
252
255
  await next();
253
256
  }
@@ -255,7 +258,7 @@ export async function log(ctx: Context, next: () => Promise<void>) {
255
258
  export async function branches(ctx: Context, next: () => Promise<void>) {
256
259
  const repo = await getRepo(ctx);
257
260
  const localPath = validateLocalPath(repo.get('localPath'));
258
- const result = await getGit(localPath).branch();
261
+ const result = await getGit(ctx, localPath).branch();
259
262
  ctx.body = { success: true, data: result };
260
263
  await next();
261
264
  }
@@ -265,7 +268,7 @@ export async function checkout(ctx: Context, next: () => Promise<void>) {
265
268
  const localPath = validateLocalPath(repo.get('localPath'));
266
269
  const { branch } = ctx.action.params;
267
270
  validateBranch(branch);
268
- await getGit(localPath).checkout(branch);
271
+ await getGit(ctx, localPath).checkout(branch);
269
272
  ctx.body = { success: true, message: `Switched to branch ${branch}` };
270
273
  await next();
271
274
  }
@@ -275,7 +278,7 @@ export async function fileTree(ctx: Context, next: () => Promise<void>) {
275
278
  const localPath = validateLocalPath(repo.get('localPath'));
276
279
  const { ref = 'HEAD', treePath = '' } = ctx.action.params;
277
280
 
278
- const git = getGit(localPath);
281
+ const git = getGit(ctx, localPath);
279
282
  validateRef(ref);
280
283
  if (treePath && treePath.includes('..')) {
281
284
  ctx.throw(400, 'Invalid tree path');
@@ -324,7 +327,7 @@ export async function fileContent(ctx: Context, next: () => Promise<void>) {
324
327
  }
325
328
 
326
329
  validateRef(ref);
327
- const git = getGit(localPath);
330
+ const git = getGit(ctx, localPath);
328
331
  const content = await git.show([`${ref}:${filePath}`]);
329
332
  ctx.body = { success: true, data: { content, filePath, ref } };
330
333
  await next();
@@ -339,7 +342,7 @@ export async function commitDetail(ctx: Context, next: () => Promise<void>) {
339
342
  ctx.throw(400, 'commitHash is required');
340
343
  }
341
344
 
342
- const git = getGit(localPath);
345
+ const git = getGit(ctx, localPath);
343
346
  validateRef(commitHash);
344
347
 
345
348
  // Use %x00 in format string to tell git to output null bytes, avoiding null bytes in args