mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
GitHub - leveral repository rules for branch protection (#180416)
* WIP - switching to use repository ruleset information * Handle includes * Refactor to add support for exclusion * Fix exclude condition * Fix request string * Add logging * Update setting description
This commit is contained in:
parent
3f6a54de51
commit
5ea57c3b48
7
extensions/git/src/api/git.d.ts
vendored
7
extensions/git/src/api/git.d.ts
vendored
|
@ -276,7 +276,12 @@ export interface PushErrorHandler {
|
||||||
|
|
||||||
export interface BranchProtection {
|
export interface BranchProtection {
|
||||||
readonly remote: string;
|
readonly remote: string;
|
||||||
readonly branches: string[];
|
readonly rules: BranchProtectionRule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BranchProtectionRule {
|
||||||
|
readonly include?: string[];
|
||||||
|
readonly exclude?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BranchProtectionProvider {
|
export interface BranchProtectionProvider {
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class GitBranchProtectionProvider implements BranchProtectionProvider {
|
||||||
.map(bp => typeof bp === 'string' ? bp.trim() : '')
|
.map(bp => typeof bp === 'string' ? bp.trim() : '')
|
||||||
.filter(bp => bp !== '');
|
.filter(bp => bp !== '');
|
||||||
|
|
||||||
this.branchProtection = { remote: '', branches };
|
this.branchProtection = { remote: '', rules: [{ include: branches }] };
|
||||||
this._onDidChangeBranchProtection.fire(this.repositoryRoot);
|
this._onDidChangeBranchProtection.fire(this.repositoryRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -605,6 +605,11 @@ class ResourceCommandResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BranchProtectionMatcher {
|
||||||
|
include?: picomatch.Matcher;
|
||||||
|
exclude?: picomatch.Matcher;
|
||||||
|
}
|
||||||
|
|
||||||
export class Repository implements Disposable {
|
export class Repository implements Disposable {
|
||||||
|
|
||||||
private _onDidChangeRepository = new EventEmitter<Uri>();
|
private _onDidChangeRepository = new EventEmitter<Uri>();
|
||||||
|
@ -744,7 +749,7 @@ export class Repository implements Disposable {
|
||||||
private isRepositoryHuge: false | { limit: number } = false;
|
private isRepositoryHuge: false | { limit: number } = false;
|
||||||
private didWarnAboutLimit = false;
|
private didWarnAboutLimit = false;
|
||||||
|
|
||||||
private branchProtection = new Map<string, picomatch.Matcher | undefined>();
|
private branchProtection = new Map<string, BranchProtectionMatcher[]>();
|
||||||
private commitCommandCenter: CommitCommandsCenter;
|
private commitCommandCenter: CommitCommandsCenter;
|
||||||
private resourceCommandResolver = new ResourceCommandResolver(this);
|
private resourceCommandResolver = new ResourceCommandResolver(this);
|
||||||
private updateModelStateCancellationTokenSource: CancellationTokenSource | undefined;
|
private updateModelStateCancellationTokenSource: CancellationTokenSource | undefined;
|
||||||
|
@ -2367,8 +2372,19 @@ export class Repository implements Disposable {
|
||||||
this.branchProtection.clear();
|
this.branchProtection.clear();
|
||||||
|
|
||||||
for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) {
|
for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) {
|
||||||
for (const { remote, branches } of provider.provideBranchProtection()) {
|
for (const { remote, rules } of provider.provideBranchProtection()) {
|
||||||
this.branchProtection.set(remote, branches.length !== 0 ? picomatch(branches) : undefined);
|
const matchers: BranchProtectionMatcher[] = [];
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
const include = rule.include && rule.include.length !== 0 ? picomatch(rule.include) : undefined;
|
||||||
|
const exclude = rule.exclude && rule.exclude.length !== 0 ? picomatch(rule.exclude) : undefined;
|
||||||
|
|
||||||
|
if (include || exclude) {
|
||||||
|
matchers.push({ include, exclude });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.branchProtection.set(remote, matchers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2416,14 +2432,23 @@ export class Repository implements Disposable {
|
||||||
if (branch?.name) {
|
if (branch?.name) {
|
||||||
// Default branch protection (settings)
|
// Default branch protection (settings)
|
||||||
const defaultBranchProtectionMatcher = this.branchProtection.get('');
|
const defaultBranchProtectionMatcher = this.branchProtection.get('');
|
||||||
if (defaultBranchProtectionMatcher && defaultBranchProtectionMatcher(branch.name)) {
|
if (defaultBranchProtectionMatcher?.length === 1 &&
|
||||||
|
defaultBranchProtectionMatcher[0].include &&
|
||||||
|
defaultBranchProtectionMatcher[0].include(branch.name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branch.upstream?.remote) {
|
if (branch.upstream?.remote) {
|
||||||
// Branch protection (contributed)
|
// Branch protection (contributed)
|
||||||
const remoteBranchProtectionMatcher = this.branchProtection.get(branch.upstream.remote);
|
const remoteBranchProtectionMatcher = this.branchProtection.get(branch.upstream.remote);
|
||||||
return remoteBranchProtectionMatcher ? remoteBranchProtectionMatcher(branch.name) : false;
|
if (remoteBranchProtectionMatcher && remoteBranchProtectionMatcher?.length !== 0) {
|
||||||
|
return remoteBranchProtectionMatcher.some(matcher => {
|
||||||
|
const include = matcher.include ? matcher.include(branch.name!) : true;
|
||||||
|
const exclude = matcher.exclude ? matcher.exclude(branch.name!) : false;
|
||||||
|
|
||||||
|
return include && !exclude;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"displayName": "GitHub",
|
"displayName": "GitHub",
|
||||||
"description": "GitHub features for VS Code",
|
"description": "GitHub features for VS Code",
|
||||||
"config.branchProtection": "Controls whether to query branch protection information for GitHub repositories",
|
"config.branchProtection": "Controls whether to query repository rules for GitHub repositories",
|
||||||
"config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.",
|
"config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.",
|
||||||
"config.gitProtocol": "Controls which protocol is used to clone a GitHub repository",
|
"config.gitProtocol": "Controls which protocol is used to clone a GitHub repository",
|
||||||
"welcome.publishFolder": {
|
"welcome.publishFolder": {
|
||||||
|
|
|
@ -3,11 +3,28 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { EventEmitter, Memento, Uri, workspace } from 'vscode';
|
import { EventEmitter, LogOutputChannel, Memento, Uri, workspace } from 'vscode';
|
||||||
import { getOctokit } from './auth';
|
import { getOctokit } from './auth';
|
||||||
import { API, BranchProtection, BranchProtectionProvider, Repository } from './typings/git';
|
import { API, BranchProtection, BranchProtectionProvider, BranchProtectionRule, Repository } from './typings/git';
|
||||||
import { DisposableStore, getRepositoryFromUrl } from './util';
|
import { DisposableStore, getRepositoryFromUrl } from './util';
|
||||||
|
|
||||||
|
interface RepositoryRuleset {
|
||||||
|
readonly id: number;
|
||||||
|
readonly conditions: {
|
||||||
|
ref_name: {
|
||||||
|
exclude: string[];
|
||||||
|
include: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
readonly enforcement: 'active' | 'disabled' | 'evaluate';
|
||||||
|
readonly rules: RepositoryRule[];
|
||||||
|
readonly target: 'branch' | 'tag';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepositoryRule {
|
||||||
|
readonly type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class GithubBranchProtectionProviderManager {
|
export class GithubBranchProtectionProviderManager {
|
||||||
|
|
||||||
private readonly disposables = new DisposableStore();
|
private readonly disposables = new DisposableStore();
|
||||||
|
@ -21,7 +38,7 @@ export class GithubBranchProtectionProviderManager {
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
for (const repository of this.gitAPI.repositories) {
|
for (const repository of this.gitAPI.repositories) {
|
||||||
this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState)));
|
this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState, this.logger)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.providerDisposables.dispose();
|
this.providerDisposables.dispose();
|
||||||
|
@ -30,10 +47,13 @@ export class GithubBranchProtectionProviderManager {
|
||||||
this._enabled = enabled;
|
this._enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly gitAPI: API, private readonly globalState: Memento) {
|
constructor(
|
||||||
|
private readonly gitAPI: API,
|
||||||
|
private readonly globalState: Memento,
|
||||||
|
private readonly logger: LogOutputChannel) {
|
||||||
this.disposables.add(this.gitAPI.onDidOpenRepository(repository => {
|
this.disposables.add(this.gitAPI.onDidOpenRepository(repository => {
|
||||||
if (this._enabled) {
|
if (this._enabled) {
|
||||||
this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState)));
|
this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState, this.logger)));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -65,7 +85,10 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider
|
||||||
private branchProtection: BranchProtection[];
|
private branchProtection: BranchProtection[];
|
||||||
private readonly globalStateKey = `branchProtection:${this.repository.rootUri.toString()}`;
|
private readonly globalStateKey = `branchProtection:${this.repository.rootUri.toString()}`;
|
||||||
|
|
||||||
constructor(private readonly repository: Repository, private readonly globalState: Memento) {
|
constructor(
|
||||||
|
private readonly repository: Repository,
|
||||||
|
private readonly globalState: Memento,
|
||||||
|
private readonly logger: LogOutputChannel) {
|
||||||
// Restore branch protection from global state
|
// Restore branch protection from global state
|
||||||
this.branchProtection = this.globalState.get<BranchProtection[]>(this.globalStateKey, []);
|
this.branchProtection = this.globalState.get<BranchProtection[]>(this.globalStateKey, []);
|
||||||
|
|
||||||
|
@ -82,21 +105,76 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider
|
||||||
await this.updateHEADBranchProtection();
|
await this.updateHEADBranchProtection();
|
||||||
|
|
||||||
// Branch protection (remotes)
|
// Branch protection (remotes)
|
||||||
await this.updateBranchProtection();
|
await this.updateRepositoryBranchProtection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkPushPermission(repository: { owner: string; repo: string }): Promise<boolean> {
|
private async hasPushPermission(repository: { owner: string; repo: string }): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const octokit = await getOctokit();
|
const octokit = await getOctokit();
|
||||||
const response = await octokit.repos.get({ ...repository });
|
const response = await octokit.repos.get({ ...repository });
|
||||||
|
|
||||||
return response.data.permissions?.push === true;
|
return response.data.permissions?.push === true;
|
||||||
} catch {
|
} catch (err) {
|
||||||
// todo@lszomoru - add logging
|
this.logger.warn(`Failed to get repository permissions for repository (${repository.owner}/${repository.repo}): ${err.message} (${err.status})`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getBranchRules(repository: { owner: string; repo: string }, branch: string): Promise<RepositoryRule[]> {
|
||||||
|
try {
|
||||||
|
const octokit = await getOctokit();
|
||||||
|
const response = await octokit.request('GET /repos/{owner}/{repo}/rules/branches/{branch}', {
|
||||||
|
...repository,
|
||||||
|
branch,
|
||||||
|
headers: {
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data as RepositoryRule[];
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.warn(`Failed to get branch rules for repository (${repository.owner}/${repository.repo}), branch (${branch}): ${err.message} (${err.status})`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRepositoryRulesets(repository: { owner: string; repo: string }): Promise<RepositoryRuleset[]> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rulesets: RepositoryRuleset[] = [];
|
||||||
|
const octokit = await getOctokit();
|
||||||
|
for await (const response of octokit.paginate.iterator('GET /repos/{owner}/{repo}/rulesets', { ...repository, includes_parents: true })) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ruleset of response.data as RepositoryRuleset[]) {
|
||||||
|
if (ruleset.target !== 'branch' || ruleset.enforcement !== 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await octokit.request('GET /repos/{owner}/{repo}/rulesets/{id}', {
|
||||||
|
...repository,
|
||||||
|
id: ruleset.id,
|
||||||
|
headers: {
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rulesetWithDetails = response.data as RepositoryRuleset;
|
||||||
|
if (rulesetWithDetails?.rules.find(r => r.type === 'pull_request')) {
|
||||||
|
rulesets.push(rulesetWithDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rulesets;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.logger.warn(`Failed to get repository rulesets for repository (${repository.owner}/${repository.repo}): ${err.message} (${err.status})`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async updateHEADBranchProtection(): Promise<void> {
|
private async updateHEADBranchProtection(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const HEAD = this.repository.state.HEAD;
|
const HEAD = this.repository.state.HEAD;
|
||||||
|
@ -118,25 +196,24 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.checkPushPermission(repository))) {
|
if (!(await this.hasPushPermission(repository))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const octokit = await getOctokit();
|
const rules = await this.getBranchRules(repository, HEAD.name);
|
||||||
const response = await octokit.repos.getBranch({ ...repository, branch: HEAD.name });
|
if (!rules.find(r => r.type === 'pull_request')) {
|
||||||
|
|
||||||
if (!response.data.protected) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.branchProtection = [{ remote: remote.name, branches: [HEAD.name] }];
|
this.branchProtection = [{ remote: remote.name, rules: [{ include: [HEAD.name] }] }];
|
||||||
this._onDidChangeBranchProtection.fire(this.repository.rootUri);
|
this._onDidChangeBranchProtection.fire(this.repository.rootUri);
|
||||||
} catch {
|
} catch (err) {
|
||||||
// todo@lszomoru - add logging
|
// noop
|
||||||
|
this.logger.warn(`Failed to update HEAD branch protection: ${err.message} (${err.status})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateBranchProtection(): Promise<void> {
|
private async updateRepositoryBranchProtection(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const branchProtection: BranchProtection[] = [];
|
const branchProtection: BranchProtection[] = [];
|
||||||
|
|
||||||
|
@ -147,27 +224,38 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.checkPushPermission(repository))) {
|
if (!(await this.hasPushPermission(repository))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repository details
|
||||||
const octokit = await getOctokit();
|
const octokit = await getOctokit();
|
||||||
|
const response = await octokit.repos.get({ ...repository });
|
||||||
|
|
||||||
let page = 1;
|
// Repository rulesets
|
||||||
const protectedBranches: string[] = [];
|
const rulesets = await this.getRepositoryRulesets(repository);
|
||||||
|
|
||||||
while (true) {
|
const parseRef = (ref: string): string => {
|
||||||
const response = await octokit.repos.listBranches({ ...repository, protected: true, per_page: 100, page });
|
if (ref.startsWith('refs/heads/')) {
|
||||||
|
return ref.substring(11);
|
||||||
if (response.data.length === 0) {
|
} else if (ref === '~DEFAULT_BRANCH') {
|
||||||
break;
|
return response.data.default_branch;
|
||||||
|
} else if (ref === '~ALL') {
|
||||||
|
return '**/*';
|
||||||
}
|
}
|
||||||
|
|
||||||
protectedBranches.push(...response.data.map(b => b.name));
|
return ref;
|
||||||
page++;
|
};
|
||||||
|
|
||||||
|
const rules: BranchProtectionRule[] = [];
|
||||||
|
for (const ruleset of rulesets) {
|
||||||
|
rules.push({
|
||||||
|
include: ruleset.conditions.ref_name.include.map(r => parseRef(r)),
|
||||||
|
exclude: ruleset.conditions.ref_name.exclude.map(r => parseRef(r))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
branchProtection.push({ remote: remote.name, branches: protectedBranches });
|
branchProtection.push({ remote: remote.name, rules });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.branchProtection = branchProtection;
|
this.branchProtection = branchProtection;
|
||||||
|
@ -175,8 +263,9 @@ export class GithubBranchProtectionProvider implements BranchProtectionProvider
|
||||||
|
|
||||||
// Save branch protection to global state
|
// Save branch protection to global state
|
||||||
await this.globalState.update(this.globalStateKey, branchProtection);
|
await this.globalState.update(this.globalStateKey, branchProtection);
|
||||||
} catch {
|
} catch (err) {
|
||||||
// todo@lszomoru - add logging
|
// noop
|
||||||
|
this.logger.warn(`Failed to update repository branch protection: ${err.message} (${err.status})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { commands, Disposable, ExtensionContext, extensions } from 'vscode';
|
import { commands, Disposable, ExtensionContext, extensions, l10n, LogLevel, LogOutputChannel, window } from 'vscode';
|
||||||
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
|
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
|
||||||
import { API, GitExtension } from './typings/git';
|
import { API, GitExtension } from './typings/git';
|
||||||
import { registerCommands } from './commands';
|
import { registerCommands } from './commands';
|
||||||
|
@ -15,8 +15,20 @@ import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
||||||
import { GithubBranchProtectionProviderManager } from './branchProtection';
|
import { GithubBranchProtectionProviderManager } from './branchProtection';
|
||||||
|
|
||||||
export function activate(context: ExtensionContext): void {
|
export function activate(context: ExtensionContext): void {
|
||||||
context.subscriptions.push(initializeGitBaseExtension());
|
const disposables: Disposable[] = [];
|
||||||
context.subscriptions.push(initializeGitExtension(context));
|
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
|
||||||
|
|
||||||
|
const logger = window.createOutputChannel('GitHub', { log: true });
|
||||||
|
disposables.push(logger);
|
||||||
|
|
||||||
|
const onDidChangeLogLevel = (logLevel: LogLevel) => {
|
||||||
|
logger.appendLine(l10n.t('Log level: {0}', LogLevel[logLevel]));
|
||||||
|
};
|
||||||
|
disposables.push(logger.onDidChangeLogLevel(onDidChangeLogLevel));
|
||||||
|
onDidChangeLogLevel(logger.logLevel);
|
||||||
|
|
||||||
|
disposables.push(initializeGitBaseExtension());
|
||||||
|
disposables.push(initializeGitExtension(context, logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeGitBaseExtension(): Disposable {
|
function initializeGitBaseExtension(): Disposable {
|
||||||
|
@ -64,7 +76,7 @@ function setGitHubContext(gitAPI: API, disposables: DisposableStore) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeGitExtension(context: ExtensionContext): Disposable {
|
function initializeGitExtension(context: ExtensionContext, logger: LogOutputChannel): Disposable {
|
||||||
const disposables = new DisposableStore();
|
const disposables = new DisposableStore();
|
||||||
|
|
||||||
let gitExtension = extensions.getExtension<GitExtension>('vscode.git');
|
let gitExtension = extensions.getExtension<GitExtension>('vscode.git');
|
||||||
|
@ -78,7 +90,7 @@ function initializeGitExtension(context: ExtensionContext): Disposable {
|
||||||
|
|
||||||
disposables.add(registerCommands(gitAPI));
|
disposables.add(registerCommands(gitAPI));
|
||||||
disposables.add(new GithubCredentialProviderManager(gitAPI));
|
disposables.add(new GithubCredentialProviderManager(gitAPI));
|
||||||
disposables.add(new GithubBranchProtectionProviderManager(gitAPI, context.globalState));
|
disposables.add(new GithubBranchProtectionProviderManager(gitAPI, context.globalState, logger));
|
||||||
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
|
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
|
||||||
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
||||||
setGitHubContext(gitAPI, disposables);
|
setGitHubContext(gitAPI, disposables);
|
||||||
|
|
7
extensions/github/src/typings/git.d.ts
vendored
7
extensions/github/src/typings/git.d.ts
vendored
|
@ -270,7 +270,12 @@ export interface PushErrorHandler {
|
||||||
|
|
||||||
export interface BranchProtection {
|
export interface BranchProtection {
|
||||||
readonly remote: string;
|
readonly remote: string;
|
||||||
readonly branches: string[];
|
readonly rules: BranchProtectionRule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BranchProtectionRule {
|
||||||
|
readonly include?: string[];
|
||||||
|
readonly exclude?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BranchProtectionProvider {
|
export interface BranchProtectionProvider {
|
||||||
|
|
Loading…
Reference in a new issue