Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLadislau Szomoru <3372902+lszomoru@users.noreply.github.com>2022-11-10 16:35:43 +0300
committerGitHub <noreply@github.com>2022-11-10 16:35:43 +0300
commitb4eaea6bc63f492156480907237b577acc04b403 (patch)
treef44ede9adff7847905f859bcfa3550ef02911f9b /extensions
parent35bdaa800326b5fd47b009dfa47ed90b522ee084 (diff)
Git - get remotes from the config file (#165909)
Diffstat (limited to 'extensions')
-rw-r--r--extensions/git/src/git.ts140
-rw-r--r--extensions/git/src/test/git.test.ts85
2 files changed, 181 insertions, 44 deletions
diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts
index 86ff30ec8f9..8a897fcd2d5 100644
--- a/extensions/git/src/git.ts
+++ b/extensions/git/src/git.ts
@@ -688,6 +688,61 @@ export interface Commit {
refNames: string[];
}
+interface GitConfigSection {
+ name: string;
+ subSectionName?: string;
+ properties: { [key: string]: string };
+}
+
+class GitConfigParser {
+ private static readonly _lineSeparator = /\r?\n/;
+
+ private static readonly _commentRegex = /^\s*[#;].*/;
+ private static readonly _emptyLineRegex = /^\s*$/;
+ private static readonly _propertyRegex = /^\s*(\w+)\s*=\s*(.*)$/;
+ private static readonly _sectionRegex = /^\s*\[\s*([^\]]+?)\s*(\"[^"]+\")*\]\s*$/;
+
+ static parse(raw: string, sectionName: string): GitConfigSection[] {
+ let section: GitConfigSection | undefined;
+ const config: { sections: GitConfigSection[] } = { sections: [] };
+
+ const addSection = (section?: GitConfigSection) => {
+ if (!section) { return; }
+ config.sections.push(section);
+ };
+
+ for (const configFileLine of raw.split(GitConfigParser._lineSeparator)) {
+ // Ignore empty lines and comments
+ if (GitConfigParser._emptyLineRegex.test(configFileLine) ||
+ GitConfigParser._commentRegex.test(configFileLine)) {
+ continue;
+ }
+
+ // Section
+ const sectionMatch = configFileLine.match(GitConfigParser._sectionRegex);
+ if (sectionMatch?.length === 3) {
+ addSection(section);
+ section = sectionMatch[1] === sectionName ?
+ { name: sectionMatch[1], subSectionName: sectionMatch[2]?.replaceAll('"', ''), properties: {} } : undefined;
+
+ continue;
+ }
+
+ // Properties
+ if (section) {
+ const propertyMatch = configFileLine.match(GitConfigParser._propertyRegex);
+ if (propertyMatch?.length === 3 && !Object.keys(section.properties).includes(propertyMatch[1])) {
+ section.properties[propertyMatch[1]] = propertyMatch[2];
+ }
+ }
+ }
+
+ addSection(section);
+
+ return config.sections;
+ }
+}
+
export class GitStatusParser {
private lastRaw = '';
@@ -761,59 +816,39 @@ export interface Submodule {
}
export function parseGitmodules(raw: string): Submodule[] {
- const regex = /\r?\n/g;
- let position = 0;
- let match: RegExpExecArray | null = null;
-
const result: Submodule[] = [];
- let submodule: Partial<Submodule> = {};
-
- function parseLine(line: string): void {
- const sectionMatch = /^\s*\[submodule "([^"]+)"\]\s*$/.exec(line);
-
- if (sectionMatch) {
- if (submodule.name && submodule.path && submodule.url) {
- result.push(submodule as Submodule);
- }
-
- const name = sectionMatch[1];
-
- if (name) {
- submodule = { name };
- return;
- }
- }
- if (!submodule) {
- return;
+ for (const submoduleSection of GitConfigParser.parse(raw, 'submodule')) {
+ if (submoduleSection.subSectionName && submoduleSection.properties['path'] && submoduleSection.properties['url']) {
+ result.push({
+ name: submoduleSection.subSectionName,
+ path: submoduleSection.properties['path'],
+ url: submoduleSection.properties['url']
+ });
}
+ }
- const propertyMatch = /^\s*(\w+)\s*=\s*(.*)$/.exec(line);
-
- if (!propertyMatch) {
- return;
- }
+ return result;
+}
- const [, key, value] = propertyMatch;
+export function parseGitRemotes(raw: string): Remote[] {
+ const remotes: Remote[] = [];
- switch (key) {
- case 'path': submodule.path = value; break;
- case 'url': submodule.url = value; break;
+ for (const remoteSection of GitConfigParser.parse(raw, 'remote')) {
+ if (!remoteSection.subSectionName) {
+ continue;
}
- }
-
- while (match = regex.exec(raw)) {
- parseLine(raw.substring(position, match.index));
- position = match.index + match[0].length;
- }
- parseLine(raw.substring(position));
-
- if (submodule.name && submodule.path && submodule.url) {
- result.push(submodule as Submodule);
+ remotes.push({
+ name: remoteSection.subSectionName,
+ fetchUrl: remoteSection.properties['url'],
+ pushUrl: remoteSection.properties['pushurl'] ?? remoteSection.properties['url'],
+ // https://github.com/microsoft/vscode/issues/45271
+ isReadOnly: remoteSection.properties['pushurl'] === 'no_push'
+ });
}
- return result;
+ return remotes;
}
const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm;
@@ -2148,6 +2183,20 @@ export class Repository {
}
async getRemotes(): Promise<Remote[]> {
+ try {
+ // Attempt to parse the config file
+ const remotes = await this.getRemotesFS();
+ if (remotes.length === 0) {
+ throw new Error('No remotes found in the git config file.');
+ }
+
+ return remotes;
+ }
+ catch (err) {
+ this.logger.warn(err.message);
+ }
+
+ // Fallback to using git to determine remotes
const result = await this.exec(['remote', '--verbose']);
const lines = result.stdout.trim().split('\n').filter(l => !!l);
const remotes: MutableRemote[] = [];
@@ -2179,6 +2228,11 @@ export class Repository {
return remotes;
}
+ private async getRemotesFS(): Promise<Remote[]> {
+ const raw = await fs.readFile(path.join(this.dotGit.commonPath ?? this.dotGit.path, 'config'), 'utf8');
+ return parseGitRemotes(raw);
+ }
+
async getBranch(name: string): Promise<Branch> {
if (name === 'HEAD') {
return this.getHEAD();
diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts
index 3b225157c3b..27ad7d93d5f 100644
--- a/extensions/git/src/test/git.test.ts
+++ b/extensions/git/src/test/git.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'mocha';
-import { GitStatusParser, parseGitCommits, parseGitmodules, parseLsTree, parseLsFiles } from '../git';
+import { GitStatusParser, parseGitCommits, parseGitmodules, parseLsTree, parseLsFiles, parseGitRemotes } from '../git';
import * as assert from 'assert';
import { splitInChunks } from '../util';
@@ -197,6 +197,89 @@ suite('git', () => {
});
});
+ suite('parseGitRemotes', () => {
+ test('empty', () => {
+ assert.deepStrictEqual(parseGitRemotes(''), []);
+ });
+
+ test('single remote', () => {
+ const sample = `[remote "origin"]
+ url = https://github.com/microsoft/vscode.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), [
+ { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode.git', isReadOnly: false }
+ ]);
+ });
+
+ test('single remote (read-only)', () => {
+ const sample = `[remote "origin"]
+ url = https://github.com/microsoft/vscode.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ pushurl = no_push
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), [
+ { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'no_push', isReadOnly: true }
+ ]);
+ });
+
+ test('single remote (multiple urls)', () => {
+ const sample = `[remote "origin"]
+ url = https://github.com/microsoft/vscode.git
+ url = https://github.com/microsoft/vscode2.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), [
+ { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode.git', isReadOnly: false }
+ ]);
+ });
+
+ test('multiple remotes', () => {
+ const sample = `[remote "origin"]
+ url = https://github.com/microsoft/vscode.git
+ pushurl = https://github.com/microsoft/vscode1.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[remote "remote2"]
+ url = https://github.com/microsoft/vscode2.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), [
+ { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode1.git', isReadOnly: false },
+ { name: 'remote2', fetchUrl: 'https://github.com/microsoft/vscode2.git', pushUrl: 'https://github.com/microsoft/vscode2.git', isReadOnly: false }
+ ]);
+ });
+
+ test('remotes (white space)', () => {
+ const sample = ` [remote "origin"]
+ url = https://github.com/microsoft/vscode.git
+ pushurl=https://github.com/microsoft/vscode1.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[ remote"remote2"]
+ url = https://github.com/microsoft/vscode2.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), [
+ { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode1.git', isReadOnly: false },
+ { name: 'remote2', fetchUrl: 'https://github.com/microsoft/vscode2.git', pushUrl: 'https://github.com/microsoft/vscode2.git', isReadOnly: false }
+ ]);
+ });
+
+ test('remotes (invalid section)', () => {
+ const sample = `[remote "origin"
+ url = https://github.com/microsoft/vscode.git
+ pushurl = https://github.com/microsoft/vscode1.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+`;
+
+ assert.deepStrictEqual(parseGitRemotes(sample), []);
+ });
+ });
+
suite('parseGitCommit', () => {
test('single parent commit', function () {
const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1