Skip to content

Commit a43779c

Browse files
fix
1 parent ca9069e commit a43779c

4 files changed

Lines changed: 67 additions & 45 deletions

File tree

packages/backend/src/git.ts

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,59 @@
11
import { CheckRepoActions, GitConfigScope, simpleGit, SimpleGitProgressEvent } from 'simple-git';
2+
import { mkdir } from 'node:fs/promises';
23

34
type onProgressFn = (event: SimpleGitProgressEvent) => void;
45

5-
export const cloneRepository = async (cloneURL: string, path: string, onProgress?: onProgressFn) => {
6-
const git = simpleGit({
7-
progress: onProgress,
8-
});
6+
export const cloneRepository = async (
7+
remoteUrl: URL,
8+
path: string,
9+
onProgress?: onProgressFn
10+
) => {
911
try {
10-
await git.clone(
11-
cloneURL,
12-
path,
13-
[
14-
"--bare",
15-
]
16-
);
12+
const git = simpleGit({
13+
progress: onProgress,
14+
});
15+
16+
await mkdir(path, { recursive: true });
1717

1818
await git.cwd({
1919
path,
20-
}).addConfig("remote.origin.fetch", "+refs/heads/*:refs/heads/*");
20+
}).init(/*bare = */ true);
21+
22+
await git.cwd({
23+
path
24+
}).fetch([
25+
remoteUrl.toString(),
26+
// See https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
27+
"+refs/heads/*:refs/heads/*",
28+
"--progress",
29+
]);
2130
} catch (error: unknown) {
2231
if (error instanceof Error) {
2332
throw new Error(`Failed to clone repository: ${error.message}`);
2433
} else {
2534
throw new Error(`Failed to clone repository: ${error}`);
2635
}
2736
}
28-
}
29-
37+
};
3038

31-
export const fetchRepository = async (path: string, onProgress?: onProgressFn) => {
39+
export const fetchRepository = async (
40+
remoteUrl: URL,
41+
path: string,
42+
onProgress?: onProgressFn
43+
) => {
3244
const git = simpleGit({
3345
progress: onProgress,
3446
});
3547

3648
try {
3749
await git.cwd({
3850
path: path,
39-
}).fetch(
40-
"origin",
41-
[
42-
"--prune",
43-
"--progress"
44-
]
45-
);
51+
}).fetch([
52+
remoteUrl.toString(),
53+
"+refs/heads/*:refs/heads/*",
54+
"--prune",
55+
"--progress"
56+
]);
4657
} catch (error: unknown) {
4758
if (error instanceof Error) {
4859
throw new Error(`Failed to fetch repository ${path}: ${error.message}`);

packages/backend/src/repoManager.ts

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,32 @@ export class RepoManager implements IRepoManager {
237237
await promises.rm(repoPath, { recursive: true, force: true });
238238
}
239239

240+
const credentials = await this.getCloneCredentialsForRepo(repo, this.db);
241+
const remoteUrl = new URL(repo.cloneUrl);
242+
if (credentials) {
243+
// @note: URL has a weird behavior where if you set the password but
244+
// _not_ the username, the ":" delimiter will still be present in the
245+
// URL (e.g., https://:password@example.com). To get around this, if
246+
// we only have a password, we set the username to the password.
247+
// @see: https://www.typescriptlang.org/play/?#code/MYewdgzgLgBArgJwDYwLwzAUwO4wKoBKAMgBQBEAFlFAA4QBcA9I5gB4CGAtjUpgHShOZADQBKANwAoREj412ECNhAIAJmhhl5i5WrJTQkELz5IQAcxIy+UEAGUoCAJZhLo0UA
248+
if (!credentials.username) {
249+
remoteUrl.username = credentials.password;
250+
} else {
251+
remoteUrl.username = credentials.username;
252+
remoteUrl.password = credentials.password;
253+
}
254+
}
255+
240256
if (existsSync(repoPath) && !isReadOnly) {
241257
logger.info(`Fetching ${repo.displayName}...`);
242258

243-
const { durationMs } = await measure(() => fetchRepository(repoPath, ({ method, stage, progress }) => {
244-
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
245-
}));
259+
const { durationMs } = await measure(() => fetchRepository(
260+
remoteUrl,
261+
repoPath,
262+
({ method, stage, progress }) => {
263+
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
264+
}
265+
));
246266
const fetchDuration_s = durationMs / 1000;
247267

248268
process.stdout.write('\n');
@@ -251,25 +271,14 @@ export class RepoManager implements IRepoManager {
251271
} else if (!isReadOnly) {
252272
logger.info(`Cloning ${repo.displayName}...`);
253273

254-
const auth = await this.getCloneCredentialsForRepo(repo, this.db);
255-
const cloneUrl = new URL(repo.cloneUrl);
256-
if (auth) {
257-
// @note: URL has a weird behavior where if you set the password but
258-
// _not_ the username, the ":" delimiter will still be present in the
259-
// URL (e.g., https://:password@example.com). To get around this, if
260-
// we only have a password, we set the username to the password.
261-
// @see: https://www.typescriptlang.org/play/?#code/MYewdgzgLgBArgJwDYwLwzAUwO4wKoBKAMgBQBEAFlFAA4QBcA9I5gB4CGAtjUpgHShOZADQBKANwAoREj412ECNhAIAJmhhl5i5WrJTQkELz5IQAcxIy+UEAGUoCAJZhLo0UA
262-
if (!auth.username) {
263-
cloneUrl.username = auth.password;
264-
} else {
265-
cloneUrl.username = auth.username;
266-
cloneUrl.password = auth.password;
274+
// Use the new secure cloning method that doesn't store credentials in .git/config
275+
const { durationMs } = await measure(() => cloneRepository(
276+
remoteUrl,
277+
repoPath,
278+
({ method, stage, progress }) => {
279+
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
267280
}
268-
}
269-
270-
const { durationMs } = await measure(() => cloneRepository(cloneUrl.toString(), repoPath, ({ method, stage, progress }) => {
271-
logger.debug(`git.${method} ${stage} stage ${progress}% complete for ${repo.displayName}`)
272-
}));
281+
));
273282
const cloneDuration_s = durationMs / 1000;
274283

275284
process.stdout.write('\n');
@@ -540,7 +549,7 @@ export class RepoManager implements IRepoManager {
540549

541550
public async validateIndexedReposHaveShards() {
542551
logger.info('Validating indexed repos have shards...');
543-
552+
544553
const indexedRepos = await this.db.repo.findMany({
545554
where: {
546555
repoIndexingStatus: RepoIndexingStatus.INDEXED
@@ -556,7 +565,7 @@ export class RepoManager implements IRepoManager {
556565
const reposToReindex: number[] = [];
557566
for (const repo of indexedRepos) {
558567
const shardPrefix = getShardPrefix(repo.orgId, repo.id);
559-
568+
560569
// TODO: this doesn't take into account if a repo has multiple shards and only some of them are missing. To support that, this logic
561570
// would need to know how many total shards are expected for this repo
562571
let hasShards = false;

packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const CodePreviewPanel = async ({ path, repoName, revisionName, domain }:
4545
displayName: repoInfoResponse.displayName,
4646
webUrl: repoInfoResponse.webUrl,
4747
}}
48+
branchDisplayName={revisionName}
4849
/>
4950
{(fileSourceResponse.webUrl && codeHostInfo) && (
5051
<a

packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const TreePreviewPanel = async ({ path, repoName, revisionName, domain }:
4040
}}
4141
pathType="tree"
4242
isFileIconVisible={false}
43+
branchDisplayName={revisionName}
4344
/>
4445
</div>
4546
<Separator />

0 commit comments

Comments
 (0)