Skip to content

Commit 1c34bd1

Browse files
committed
implement raw remote git repo support
1 parent 672832c commit 1c34bd1

File tree

4 files changed

+123
-3
lines changed

4 files changed

+123
-3
lines changed

packages/backend/src/git.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { GitRepository } from './types.js';
1+
import { GitRepository, AppContext } from './types.js';
22
import { simpleGit, SimpleGitProgressEvent } from 'simple-git';
33
import { existsSync } from 'fs';
44
import { createLogger } from './logger.js';
5+
import { GitConfig } from './schemas/v2.js';
6+
import path from 'path';
57

68
const logger = createLogger('git');
79

@@ -48,4 +50,81 @@ export const fetchRepository = async (repo: GitRepository, onProgress?: (event:
4850
"--progress"
4951
]
5052
);
53+
}
54+
55+
const isValidGitRepo = async (url: string): Promise<boolean> => {
56+
const git = simpleGit();
57+
try {
58+
await git.listRemote([url]);
59+
return true;
60+
} catch (error) {
61+
logger.debug(`Error checking if ${url} is a valid git repo: ${error}`);
62+
return false;
63+
}
64+
}
65+
66+
const stripProtocolAndGitSuffix = (url: string): string => {
67+
return url.replace(/^[a-zA-Z]+:\/\//, '').replace(/\.git$/, '');
68+
}
69+
70+
const getRepoNameFromUrl = (url: string): string => {
71+
const strippedUrl = stripProtocolAndGitSuffix(url);
72+
return strippedUrl.split('/').slice(-2).join('/');
73+
}
74+
75+
export const getGitRepoFromConfig = async (config: GitConfig, ctx: AppContext) => {
76+
const repoValid = await isValidGitRepo(config.url);
77+
if (!repoValid) {
78+
logger.error(`Git repo provided in config with url ${config.url} is not valid`);
79+
return null;
80+
}
81+
82+
const cloneUrl = config.url;
83+
const repoId = stripProtocolAndGitSuffix(cloneUrl);
84+
const repoName = getRepoNameFromUrl(config.url);
85+
const repoPath = path.resolve(path.join(ctx.reposPath, `${repoId}.git`));
86+
const repo: GitRepository = {
87+
vcs: 'git',
88+
id: repoId,
89+
name: repoName,
90+
path: repoPath,
91+
isStale: false,
92+
cloneUrl: cloneUrl,
93+
branches: [],
94+
tags: [],
95+
}
96+
97+
if (config.revisions) {
98+
if (config.revisions.branches) {
99+
const branchGlobs = config.revisions.branches;
100+
const git = simpleGit();
101+
const branchList = await git.listRemote(['--heads', cloneUrl]);
102+
const branches = branchList
103+
.split('\n')
104+
.map(line => line.split('\t')[1])
105+
.filter(Boolean)
106+
.map(branch => branch.replace('refs/heads/', ''));
107+
108+
repo.branches = branches.filter(branch =>
109+
branchGlobs.some(glob => new RegExp(glob).test(branch))
110+
);
111+
}
112+
113+
if (config.revisions.tags) {
114+
const tagGlobs = config.revisions.tags;
115+
const git = simpleGit();
116+
const tagList = await git.listRemote(['--tags', cloneUrl]);
117+
const tags = tagList
118+
.split('\n')
119+
.map(line => line.split('\t')[1])
120+
.filter(Boolean)
121+
.map(tag => tag.replace('refs/tags/', ''));
122+
123+
repo.tags = tags.filter(tag =>
124+
tagGlobs.some(glob => new RegExp(glob).test(tag))
125+
);
126+
}
127+
}
128+
129+
return repo;
51130
}

packages/backend/src/main.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getGitLabReposFromConfig } from "./gitlab.js";
66
import { getGiteaReposFromConfig } from "./gitea.js";
77
import { getGerritReposFromConfig } from "./gerrit.js";
88
import { AppContext, LocalRepository, GitRepository, Repository, Settings } from "./types.js";
9-
import { cloneRepository, fetchRepository } from "./git.js";
9+
import { cloneRepository, fetchRepository, getGitRepoFromConfig } from "./git.js";
1010
import { createLogger } from "./logger.js";
1111
import { createRepository, Database, loadDB, updateRepository, updateSettings } from './db.js';
1212
import { arraysEqualShallow, isRemotePath, measure } from "./utils.js";
@@ -245,6 +245,11 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal,
245245
configRepos.push(repo);
246246
break;
247247
}
248+
case 'git': {
249+
const gitRepo = await getGitRepoFromConfig(repoConfig, ctx);
250+
gitRepo && configRepos.push(gitRepo);
251+
break;
252+
}
248253
}
249254
}
250255

packages/backend/src/schemas/v2.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
22

3-
export type Repos = GitHubConfig | GitLabConfig | GiteaConfig | GerritConfig | LocalConfig;
3+
export type Repos = GitHubConfig | GitLabConfig | GiteaConfig | GerritConfig | LocalConfig | GitConfig;
44

55
/**
66
* A Sourcebot configuration file outlines which repositories Sourcebot should sync and index.
@@ -268,3 +268,14 @@ export interface LocalConfig {
268268
paths?: string[];
269269
};
270270
}
271+
export interface GitConfig {
272+
/**
273+
* Git Configuration
274+
*/
275+
type: "git";
276+
/**
277+
* The URL to the git repository.
278+
*/
279+
url: string;
280+
revisions?: GitRevisions;
281+
}

schemas/v2/index.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,28 @@
516516
],
517517
"additionalProperties": false
518518
},
519+
"GitConfig": {
520+
"type": "object",
521+
"properties": {
522+
"type": {
523+
"const": "git",
524+
"description": "Git Configuration"
525+
},
526+
"url": {
527+
"type": "string",
528+
"format": "url",
529+
"description": "The URL to the git repository."
530+
},
531+
"revisions": {
532+
"$ref": "#/definitions/GitRevisions"
533+
}
534+
},
535+
"required": [
536+
"type",
537+
"url"
538+
],
539+
"additionalProperties": false
540+
},
519541
"Repos": {
520542
"anyOf": [
521543
{
@@ -532,6 +554,9 @@
532554
},
533555
{
534556
"$ref": "#/definitions/LocalConfig"
557+
},
558+
{
559+
"$ref": "#/definitions/GitConfig"
535560
}
536561
]
537562
},

0 commit comments

Comments
 (0)