mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 17:23:19 -05:00
Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)
* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 * disable strict null check
This commit is contained in:
2
extensions/git/src/api/git.d.ts
vendored
2
extensions/git/src/api/git.d.ts
vendored
@@ -235,4 +235,4 @@ export const enum GitErrorCodes {
|
||||
CantRebaseMultipleBranches = 'CantRebaseMultipleBranches',
|
||||
PatchDoesNotApply = 'PatchDoesNotApply',
|
||||
NoPathFound = 'NoPathFound'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,13 @@ class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.checkoutTracking(this.ref.name);
|
||||
const branches = await repository.findTrackingBranches(this.ref.name);
|
||||
|
||||
if (branches.length > 0) {
|
||||
await repository.checkout(branches[0].name!);
|
||||
} else {
|
||||
await repository.checkoutTracking(this.ref.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +202,7 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] {
|
||||
enum PushType {
|
||||
Push,
|
||||
PushTo,
|
||||
PushTags,
|
||||
PushFollowTags,
|
||||
}
|
||||
|
||||
interface PushOptions {
|
||||
@@ -481,10 +487,10 @@ export class CommandCenter {
|
||||
(_, token) => this.git.clone(url!, parentPath, token)
|
||||
);
|
||||
|
||||
const choices = [];
|
||||
let message = localize('proposeopen', "Would you like to open the cloned repository?");
|
||||
const open = localize('openrepo', "Open Repository");
|
||||
choices.push(open);
|
||||
const open = localize('openrepo', "Open");
|
||||
const openNewWindow = localize('openreponew', "Open in New Window");
|
||||
const choices = [open, openNewWindow];
|
||||
|
||||
const addToWorkspace = localize('add', "Add to Workspace");
|
||||
if (workspace.workspaceFolders) {
|
||||
@@ -509,6 +515,8 @@ export class CommandCenter {
|
||||
commands.executeCommand('vscode.openFolder', uri);
|
||||
} else if (result === addToWorkspace) {
|
||||
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
|
||||
} else if (result === openNewWindow) {
|
||||
commands.executeCommand('vscode.openFolder', uri, true);
|
||||
}
|
||||
} catch (err) {
|
||||
if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
|
||||
@@ -593,10 +601,10 @@ export class CommandCenter {
|
||||
|
||||
await this.git.init(repositoryPath);
|
||||
|
||||
const choices = [];
|
||||
let message = localize('proposeopen init', "Would you like to open the initialized repository?");
|
||||
const open = localize('openrepo', "Open Repository");
|
||||
choices.push(open);
|
||||
const open = localize('openrepo', "Open");
|
||||
const openNewWindow = localize('openreponew', "Open in New Window");
|
||||
const choices = [open, openNewWindow];
|
||||
|
||||
if (!askToOpen) {
|
||||
return;
|
||||
@@ -615,6 +623,8 @@ export class CommandCenter {
|
||||
commands.executeCommand('vscode.openFolder', uri);
|
||||
} else if (result === addToWorkspace) {
|
||||
workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
|
||||
} else if (result === openNewWindow) {
|
||||
commands.executeCommand('vscode.openFolder', uri, true);
|
||||
} else {
|
||||
await this.model.openRepository(repositoryPath);
|
||||
}
|
||||
@@ -663,7 +673,6 @@ export class CommandCenter {
|
||||
|
||||
if (!(resource instanceof Resource)) {
|
||||
// can happen when called from a keybinding
|
||||
console.log('WHAT');
|
||||
resource = this.getSCMResource();
|
||||
}
|
||||
|
||||
@@ -728,6 +737,8 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
const HEAD = await this.getLeftResource(resource);
|
||||
const basename = path.basename(resource.resourceUri.fsPath);
|
||||
const title = `${basename} (HEAD)`;
|
||||
|
||||
if (!HEAD) {
|
||||
window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
|
||||
@@ -738,7 +749,7 @@ export class CommandCenter {
|
||||
preview
|
||||
};
|
||||
|
||||
return await commands.executeCommand<void>('vscode.open', HEAD, opts);
|
||||
return await commands.executeCommand<void>('vscode.open', HEAD, opts, title);
|
||||
}
|
||||
|
||||
@command('git.openChange')
|
||||
@@ -1560,8 +1571,11 @@ export class CommandCenter {
|
||||
|
||||
@command('git.renameBranch', { repository: true })
|
||||
async renameBranch(repository: Repository): Promise<void> {
|
||||
const placeHolder = localize('provide branch name', "Please provide a branch name");
|
||||
const name = await window.showInputBox({ placeHolder });
|
||||
const name = await window.showInputBox({
|
||||
placeHolder: localize('branch name', "Branch name"),
|
||||
prompt: localize('provide branch name', "Please provide a branch name"),
|
||||
value: repository.HEAD && repository.HEAD.name
|
||||
});
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
return;
|
||||
@@ -1753,10 +1767,8 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (pushOptions.pushType === PushType.PushTags) {
|
||||
await repository.pushTags(undefined, forcePushMode);
|
||||
|
||||
window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
|
||||
if (pushOptions.pushType === PushType.PushFollowTags) {
|
||||
await repository.pushFollowTags(undefined, forcePushMode);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1813,13 +1825,13 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
@command('git.pushWithTags', { repository: true })
|
||||
async pushWithTags(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTags });
|
||||
async pushFollowTags(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushFollowTags });
|
||||
}
|
||||
|
||||
@command('git.pushWithTagsForce', { repository: true })
|
||||
async pushWithTagsForce(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
|
||||
async pushFollowTagsForce(repository: Repository): Promise<void> {
|
||||
await this._push(repository, { pushType: PushType.PushFollowTags, forcePush: true });
|
||||
}
|
||||
|
||||
@command('git.pushTo', { repository: true })
|
||||
|
||||
@@ -12,7 +12,8 @@ import { EventEmitter } from 'events';
|
||||
import iconv = require('iconv-lite');
|
||||
import * as filetype from 'file-type';
|
||||
import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util';
|
||||
import { CancellationToken, Uri, workspace } from 'vscode';
|
||||
import { CancellationToken } from 'vscode';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { detectEncoding } from './encoding';
|
||||
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
|
||||
|
||||
@@ -328,8 +329,8 @@ export class Git {
|
||||
this.env = options.env || {};
|
||||
}
|
||||
|
||||
open(repository: string): Repository {
|
||||
return new Repository(this, repository);
|
||||
open(repository: string, dotGit: string): Repository {
|
||||
return new Repository(this, repository, dotGit);
|
||||
}
|
||||
|
||||
async init(repository: string): Promise<void> {
|
||||
@@ -338,7 +339,7 @@ export class Git {
|
||||
}
|
||||
|
||||
async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise<string> {
|
||||
let baseFolderName = decodeURI(url).replace(/^.*\//, '').replace(/\.git$/, '') || 'repository';
|
||||
let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*\//, '').replace(/\.git$/, '') || 'repository';
|
||||
let folderName = baseFolderName;
|
||||
let folderPath = path.join(parentPath, folderName);
|
||||
let count = 1;
|
||||
@@ -369,6 +370,17 @@ export class Git {
|
||||
return path.normalize(result.stdout.trim());
|
||||
}
|
||||
|
||||
async getRepositoryDotGit(repositoryPath: string): Promise<string> {
|
||||
const result = await this.exec(repositoryPath, ['rev-parse', '--git-dir']);
|
||||
let dotGitPath = result.stdout.trim();
|
||||
|
||||
if (!path.isAbsolute(dotGitPath)) {
|
||||
dotGitPath = path.join(repositoryPath, dotGitPath);
|
||||
}
|
||||
|
||||
return path.normalize(dotGitPath);
|
||||
}
|
||||
|
||||
async exec(cwd: string, args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
options = assign({ cwd }, options || {});
|
||||
return await this._exec(args, options);
|
||||
@@ -557,7 +569,7 @@ export function parseGitmodules(raw: string): Submodule[] {
|
||||
return;
|
||||
}
|
||||
|
||||
const propertyMatch = /^\s*(\w+) = (.*)$/.exec(line);
|
||||
const propertyMatch = /^\s*(\w+)\s+=\s+(.*)$/.exec(line);
|
||||
|
||||
if (!propertyMatch) {
|
||||
return;
|
||||
@@ -636,6 +648,7 @@ export interface CommitOptions {
|
||||
|
||||
export interface PullOptions {
|
||||
unshallow?: boolean;
|
||||
tags?: boolean;
|
||||
}
|
||||
|
||||
export enum ForcePushMode {
|
||||
@@ -647,7 +660,8 @@ export class Repository {
|
||||
|
||||
constructor(
|
||||
private _git: Git,
|
||||
private repositoryRoot: string
|
||||
private repositoryRoot: string,
|
||||
readonly dotGit: string
|
||||
) { }
|
||||
|
||||
get git(): Git {
|
||||
@@ -995,7 +1009,7 @@ export class Repository {
|
||||
break;
|
||||
}
|
||||
|
||||
const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath));
|
||||
const originalUri = URI.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath));
|
||||
let status: Status = Status.UNTRACKED;
|
||||
|
||||
// Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status.
|
||||
@@ -1023,7 +1037,7 @@ export class Repository {
|
||||
break;
|
||||
}
|
||||
|
||||
const uri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath));
|
||||
const uri = URI.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath));
|
||||
result.push({
|
||||
uri,
|
||||
renameUri: uri,
|
||||
@@ -1363,9 +1377,8 @@ export class Repository {
|
||||
|
||||
async pull(rebase?: boolean, remote?: string, branch?: string, options: PullOptions = {}): Promise<void> {
|
||||
const args = ['pull'];
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
|
||||
if (config.get<boolean>('pullTags')) {
|
||||
if (options.tags) {
|
||||
args.push('--tags');
|
||||
}
|
||||
|
||||
@@ -1418,7 +1431,7 @@ export class Repository {
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
args.push('--tags');
|
||||
args.push('--follow-tags');
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
@@ -1578,6 +1591,14 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async findTrackingBranches(upstreamBranch: string): Promise<Branch[]> {
|
||||
const result = await this.run(['for-each-ref', '--format', '%(refname:short)%00%(upstream:short)', 'refs/heads']);
|
||||
return result.stdout.trim().split('\n')
|
||||
.map(line => line.trim().split('\0'))
|
||||
.filter(([_, upstream]) => upstream === upstreamBranch)
|
||||
.map(([ref]) => ({ name: ref, type: RefType.Head } as Branch));
|
||||
}
|
||||
|
||||
async getRefs(): Promise<Ref[]> {
|
||||
const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)', '--sort', '-committerdate']);
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export class Model {
|
||||
this.disposables.push(fsWatcher);
|
||||
|
||||
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
|
||||
const onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\/\.git\//.test(uri.path));
|
||||
const onGitRepositoryChange = filterEvent(onWorkspaceChange, uri => /\/\.git/.test(uri.path));
|
||||
const onPossibleGitRepositoryChange = filterEvent(onGitRepositoryChange, uri => !this.getRepository(uri));
|
||||
onPossibleGitRepositoryChange(this.onPossibleGitRepositoryChange, this, this.disposables);
|
||||
|
||||
@@ -232,7 +232,8 @@ export class Model {
|
||||
return;
|
||||
}
|
||||
|
||||
const repository = new Repository(this.git.open(repositoryRoot), this.globalState);
|
||||
const dotGit = await this.git.getRepositoryDotGit(repositoryRoot);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit), this.globalState, this.outputChannel);
|
||||
|
||||
this.open(repository);
|
||||
} catch (err) {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType } from 'vscode';
|
||||
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env } from 'vscode';
|
||||
import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git';
|
||||
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent } from './util';
|
||||
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable, watch, IFileWatcher } from './util';
|
||||
import { memoize, throttle, debounce } from './decorators';
|
||||
import { toGitUri } from './uri';
|
||||
import { AutoFetcher } from './autofetch';
|
||||
@@ -299,6 +299,7 @@ export const enum Operation {
|
||||
GetObjectDetails = 'GetObjectDetails',
|
||||
SubmoduleUpdate = 'SubmoduleUpdate',
|
||||
RebaseContinue = 'RebaseContinue',
|
||||
FindTrackingBranches = 'GetTracking',
|
||||
Apply = 'Apply',
|
||||
Blame = 'Blame',
|
||||
Log = 'Log',
|
||||
@@ -446,6 +447,91 @@ class ProgressManager {
|
||||
}
|
||||
}
|
||||
|
||||
class FileEventLogger {
|
||||
|
||||
private eventDisposable: IDisposable = EmptyDisposable;
|
||||
private logLevelDisposable: IDisposable = EmptyDisposable;
|
||||
|
||||
constructor(
|
||||
private onWorkspaceWorkingTreeFileChange: Event<Uri>,
|
||||
private onDotGitFileChange: Event<Uri>,
|
||||
private outputChannel: OutputChannel
|
||||
) {
|
||||
this.logLevelDisposable = env.onDidChangeLogLevel(this.onDidChangeLogLevel, this);
|
||||
this.onDidChangeLogLevel(env.logLevel);
|
||||
}
|
||||
|
||||
private onDidChangeLogLevel(level: LogLevel): void {
|
||||
this.eventDisposable.dispose();
|
||||
|
||||
if (level > LogLevel.Debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventDisposable = combinedDisposable([
|
||||
this.onWorkspaceWorkingTreeFileChange(uri => this.outputChannel.appendLine(`[debug] [wt] Change: ${uri.fsPath}`)),
|
||||
this.onDotGitFileChange(uri => this.outputChannel.appendLine(`[debug] [.git] Change: ${uri.fsPath}`))
|
||||
]);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.eventDisposable.dispose();
|
||||
this.logLevelDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class DotGitWatcher implements IFileWatcher {
|
||||
|
||||
readonly event: Event<Uri>;
|
||||
|
||||
private emitter = new EventEmitter<Uri>();
|
||||
private transientDisposables: IDisposable[] = [];
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private repository: Repository,
|
||||
private outputChannel: OutputChannel
|
||||
) {
|
||||
const rootWatcher = watch(repository.dotGit);
|
||||
this.disposables.push(rootWatcher);
|
||||
|
||||
const filteredRootWatcher = filterEvent(rootWatcher.event, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path));
|
||||
this.event = anyEvent(filteredRootWatcher, this.emitter.event);
|
||||
|
||||
repository.onDidRunGitStatus(this.updateTransientWatchers, this, this.disposables);
|
||||
this.updateTransientWatchers();
|
||||
}
|
||||
|
||||
private updateTransientWatchers() {
|
||||
this.transientDisposables = dispose(this.transientDisposables);
|
||||
|
||||
if (!this.repository.HEAD || !this.repository.HEAD.upstream) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transientDisposables = dispose(this.transientDisposables);
|
||||
|
||||
const { name, remote } = this.repository.HEAD.upstream;
|
||||
const upstreamPath = path.join(this.repository.dotGit, 'refs', 'remotes', remote, name);
|
||||
|
||||
try {
|
||||
const upstreamWatcher = watch(upstreamPath);
|
||||
this.transientDisposables.push(upstreamWatcher);
|
||||
upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables);
|
||||
} catch (err) {
|
||||
if (env.logLevel <= LogLevel.Info) {
|
||||
this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}'. Ref is most likely packed.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.emitter.dispose();
|
||||
this.transientDisposables = dispose(this.transientDisposables);
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class Repository implements Disposable {
|
||||
|
||||
private _onDidChangeRepository = new EventEmitter<Uri>();
|
||||
@@ -543,36 +629,41 @@ export class Repository implements Disposable {
|
||||
return this.repository.root;
|
||||
}
|
||||
|
||||
get dotGit(): string {
|
||||
return this.repository.dotGit;
|
||||
}
|
||||
|
||||
private isRepositoryHuge = false;
|
||||
private didWarnAboutLimit = false;
|
||||
private isFreshRepository: boolean | undefined = undefined;
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly repository: BaseRepository,
|
||||
globalState: Memento
|
||||
globalState: Memento,
|
||||
outputChannel: OutputChannel
|
||||
) {
|
||||
const fsWatcher = workspace.createFileSystemWatcher('**');
|
||||
this.disposables.push(fsWatcher);
|
||||
const workspaceWatcher = workspace.createFileSystemWatcher('**');
|
||||
this.disposables.push(workspaceWatcher);
|
||||
|
||||
const workspaceFilter = (uri: Uri) => isDescendant(repository.root, uri.fsPath);
|
||||
const onWorkspaceDelete = filterEvent(fsWatcher.onDidDelete, workspaceFilter);
|
||||
const onWorkspaceChange = filterEvent(anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate), workspaceFilter);
|
||||
const onRepositoryDotGitDelete = filterEvent(onWorkspaceDelete, uri => /\/\.git$/.test(uri.path));
|
||||
const onRepositoryChange = anyEvent(onWorkspaceDelete, onWorkspaceChange);
|
||||
const onWorkspaceFileChange = anyEvent(workspaceWatcher.onDidChange, workspaceWatcher.onDidCreate, workspaceWatcher.onDidDelete);
|
||||
const onWorkspaceRepositoryFileChange = filterEvent(onWorkspaceFileChange, uri => isDescendant(repository.root, uri.fsPath));
|
||||
const onWorkspaceWorkingTreeFileChange = filterEvent(onWorkspaceRepositoryFileChange, uri => !/\/\.git($|\/)/.test(uri.path));
|
||||
|
||||
// relevant repository changes are:
|
||||
// - DELETE .git folder
|
||||
// - ANY CHANGE within .git folder except .git itself and .git/index.lock
|
||||
const onRelevantRepositoryChange = anyEvent(
|
||||
onRepositoryDotGitDelete,
|
||||
filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path))
|
||||
);
|
||||
const dotGitFileWatcher = new DotGitWatcher(this, outputChannel);
|
||||
this.disposables.push(dotGitFileWatcher);
|
||||
|
||||
onRelevantRepositoryChange(this.onFSChange, this, this.disposables);
|
||||
// FS changes should trigger `git status`:
|
||||
// - any change inside the repository working tree
|
||||
// - any change whithin the first level of the `.git` folder, except the folder itself and `index.lock`
|
||||
const onFileChange = anyEvent(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event);
|
||||
onFileChange(this.onFileChange, this, this.disposables);
|
||||
|
||||
const onRelevantGitChange = filterEvent(onRelevantRepositoryChange, uri => /\/\.git\//.test(uri.path));
|
||||
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
|
||||
// Relevate repository changes should trigger virtual document change events
|
||||
dotGitFileWatcher.event(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
|
||||
|
||||
this.disposables.push(new FileEventLogger(onWorkspaceWorkingTreeFileChange, dotGitFileWatcher.event, outputChannel));
|
||||
|
||||
const root = Uri.file(repository.root);
|
||||
this._sourceControl = scm.createSourceControl('git', 'Git', root);
|
||||
@@ -582,9 +673,9 @@ export class Repository implements Disposable {
|
||||
this._sourceControl.inputBox.validateInput = this.validateInput.bind(this);
|
||||
this.disposables.push(this._sourceControl);
|
||||
|
||||
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes"));
|
||||
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
|
||||
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));
|
||||
this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES"));
|
||||
this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES"));
|
||||
this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES"));
|
||||
|
||||
const updateIndexGroupVisibility = () => {
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
@@ -909,6 +1000,10 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true }));
|
||||
}
|
||||
|
||||
async findTrackingBranches(upstreamRef: string): Promise<Branch[]> {
|
||||
return await this.run(Operation.FindTrackingBranches, () => this.repository.findTrackingBranches(upstreamRef));
|
||||
}
|
||||
|
||||
async getCommit(ref: string): Promise<Commit> {
|
||||
return await this.repository.getCommit(ref);
|
||||
}
|
||||
@@ -979,11 +1074,12 @@ export class Repository implements Disposable {
|
||||
await this.maybeAutoStash(async () => {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
const tags = config.get<boolean>('pullTags');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.repository.pull(rebase, undefined, undefined, { unshallow });
|
||||
await this.repository.pull(rebase, undefined, undefined, { unshallow, tags });
|
||||
} else {
|
||||
await this.repository.pull(rebase, remote, branch, { unshallow });
|
||||
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1006,7 +1102,7 @@ export class Repository implements Disposable {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream, undefined, forcePushMode));
|
||||
}
|
||||
|
||||
async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
async pushFollowTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
|
||||
await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
|
||||
}
|
||||
|
||||
@@ -1039,11 +1135,12 @@ export class Repository implements Disposable {
|
||||
await this.maybeAutoStash(async () => {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const fetchOnPull = config.get<boolean>('fetchOnPull');
|
||||
const tags = config.get<boolean>('pullTags');
|
||||
|
||||
if (fetchOnPull) {
|
||||
await this.repository.pull(rebase);
|
||||
await this.repository.pull(rebase, undefined, undefined, { tags });
|
||||
} else {
|
||||
await this.repository.pull(rebase, remoteName, pullBranch);
|
||||
await this.repository.pull(rebase, remoteName, pullBranch, { tags });
|
||||
}
|
||||
|
||||
const remote = this.remotes.find(r => r.name === remoteName);
|
||||
@@ -1156,7 +1253,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
// https://git-scm.com/docs/git-check-ignore#git-check-ignore--z
|
||||
const child = this.repository.stream(['check-ignore', '-z', '--stdin'], { stdio: [null, null, null] });
|
||||
const child = this.repository.stream(['check-ignore', '-v', '-z', '--stdin'], { stdio: [null, null, null] });
|
||||
child.stdin.end(filePaths.join('\0'), 'utf8');
|
||||
|
||||
const onExit = (exitCode: number) => {
|
||||
@@ -1164,8 +1261,7 @@ export class Repository implements Disposable {
|
||||
// nothing ignored
|
||||
resolve(new Set<string>());
|
||||
} else if (exitCode === 0) {
|
||||
// paths are separated by the null-character
|
||||
resolve(new Set<string>(data.split('\0')));
|
||||
resolve(new Set<string>(this.parseIgnoreCheck(data)));
|
||||
} else {
|
||||
if (/ is in submodule /.test(stderr)) {
|
||||
reject(new GitError({ stdout: data, stderr, exitCode, gitErrorCode: GitErrorCodes.IsInSubmodule }));
|
||||
@@ -1193,6 +1289,23 @@ export class Repository implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
// Parses output of `git check-ignore -v -z` and returns only those paths
|
||||
// that are actually ignored by git.
|
||||
// Matches to a negative pattern (starting with '!') are filtered out.
|
||||
// See also https://git-scm.com/docs/git-check-ignore#_output.
|
||||
private parseIgnoreCheck(raw: string): string[] {
|
||||
const ignored = [];
|
||||
const elements = raw.split('\0');
|
||||
for (let i = 0; i < elements.length; i += 4) {
|
||||
const pattern = elements[i + 2];
|
||||
const path = elements[i + 3];
|
||||
if (pattern && !pattern.startsWith('!')) {
|
||||
ignored.push(path);
|
||||
}
|
||||
}
|
||||
return ignored;
|
||||
}
|
||||
|
||||
private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
|
||||
if (this.state !== RepositoryState.Idle) {
|
||||
throw new Error('Repository not initialized');
|
||||
@@ -1430,7 +1543,7 @@ export class Repository implements Disposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
private onFSChange(_uri: Uri): void {
|
||||
private onFileChange(_uri: Uri): void {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const autorefresh = config.get<boolean>('autorefresh');
|
||||
|
||||
|
||||
@@ -172,6 +172,17 @@ suite('git', () => {
|
||||
{ name: 'deps/spdlog4', path: 'deps/spdlog4', url: 'https://github.com/gabime/spdlog4.git' }
|
||||
]);
|
||||
});
|
||||
|
||||
test('whitespace #74844', () => {
|
||||
const sample = `[submodule "deps/spdlog"]
|
||||
path = deps/spdlog
|
||||
url = https://github.com/gabime/spdlog.git
|
||||
`;
|
||||
|
||||
assert.deepEqual(parseGitmodules(sample), [
|
||||
{ name: 'deps/spdlog', path: 'deps/spdlog', url: 'https://github.com/gabime/spdlog.git' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('parseGitCommit', () => {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vscode';
|
||||
import { dirname, sep } from 'path';
|
||||
import { Event, EventEmitter, Uri } from 'vscode';
|
||||
import { dirname, sep, join } from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import * as fs from 'fs';
|
||||
import * as byline from 'byline';
|
||||
@@ -343,4 +343,20 @@ export function pathEquals(a: string, b: string): boolean {
|
||||
}
|
||||
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFileWatcher extends IDisposable {
|
||||
readonly event: Event<Uri>;
|
||||
}
|
||||
|
||||
export function watch(location: string): IFileWatcher {
|
||||
const dotGitWatcher = fs.watch(location);
|
||||
const onDotGitFileChangeEmitter = new EventEmitter<Uri>();
|
||||
dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string))));
|
||||
dotGitWatcher.on('error', err => console.error(err));
|
||||
|
||||
return new class implements IFileWatcher {
|
||||
event = onDotGitFileChangeEmitter.event;
|
||||
dispose() { dotGitWatcher.close(); }
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user