ソースを参照

Adds native Pull and Push implementations to experimental Native Git flag (#2832)

* Adds a native push implementation behind experimental flag

* Adds more git error cases

* Adds a native pull implementation behind experimental flag

* Fixes git error case

* Throws errors at git level instead

* Uses custom error objects

* Avoids reaching out to interface with Repository
Avoids extra lookups and just uses branch ref

* Fixes provider service calling wrong command

* Looks also at other error properties in decoding stage

* Catches at git provider and outputs message

* Adds 'remote ahead' case

* Always logs errors at provider level

---------

Co-authored-by: Eric Amodio <eamodio@gmail.com>
main
Ramin Tadayon 1年前
committed by GitHub
コミット
8c0671643b
この署名に対応する既知のキーがデータベースに存在しません GPGキーID: 4AEE18F83AFDEB23
10個のファイルの変更565行の追加67行の削除
  1. +1
    -0
      src/constants.ts
  2. +150
    -19
      src/env/node/git/git.ts
  3. +100
    -14
      src/env/node/git/localGitProvider.ts
  4. +205
    -0
      src/git/errors.ts
  5. +16
    -0
      src/git/gitProvider.ts
  6. +17
    -0
      src/git/gitProviderService.ts
  7. +3
    -3
      src/git/models/branch.ts
  8. +10
    -1
      src/git/models/reference.ts
  9. +51
    -30
      src/git/models/repository.ts
  10. +12
    -0
      src/plus/github/githubGitProvider.ts

+ 1
- 0
src/constants.ts ファイルの表示

@ -600,6 +600,7 @@ export type CoreGitConfiguration =
| 'git.enabled'
| 'git.fetchOnPull'
| 'git.path'
| 'git.pullTags'
| 'git.repositoryScanIgnoredFolders'
| 'git.repositoryScanMaxDepth'
| 'git.useForcePushWithLease';

+ 150
- 19
src/env/node/git/git.ts ファイルの表示

@ -9,7 +9,17 @@ import { hrtime } from '@env/hrtime';
import { GlyphChars } from '../../../constants';
import type { GitCommandOptions, GitSpawnOptions } from '../../../git/commandOptions';
import { GitErrorHandling } from '../../../git/commandOptions';
import { StashPushError, StashPushErrorReason, WorkspaceUntrustedError } from '../../../git/errors';
import {
FetchError,
FetchErrorReason,
PullError,
PullErrorReason,
PushError,
PushErrorReason,
StashPushError,
StashPushErrorReason,
WorkspaceUntrustedError,
} from '../../../git/errors';
import type { GitDiffFilter } from '../../../git/models/diff';
import { isUncommitted, isUncommittedStaged, shortenRevision } from '../../../git/models/reference';
import type { GitUser } from '../../../git/models/user';
@ -58,14 +68,29 @@ const rootSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
export const GitErrors = {
badRevision: /bad revision '(.*?)'/i,
cantLockRef: /cannot lock ref|unable to update local ref/i,
changesWouldBeOverwritten: /Your local changes to the following files would be overwritten/i,
commitChangesFirst: /Please, commit your changes before you can/i,
conflict: /^CONFLICT \([^)]+\): \b/m,
noFastForward: /\(non-fast-forward\)/i,
noMergeBase: /no merge base/i,
noRemoteRepositorySpecified: /No remote repository specified\./i,
notAValidObjectName: /Not a valid object name/i,
noUserNameConfigured: /Please tell me who you are\./i,
invalidLineCount: /file .+? has only \d+ lines/i,
uncommittedChanges: /contains modified or untracked files/i,
alreadyExists: /already exists/i,
alreadyCheckedOut: /already checked out/i,
mainWorkingTree: /is a main working tree/i,
noUpstream: /^fatal: The current branch .* has no upstream branch/i,
permissionDenied: /Permission.*denied/i,
pushRejected: /^error: failed to push some refs to\b/m,
rebaseMultipleBranches: /cannot rebase onto multiple branches/i,
remoteAhead: /rejected because the remote contains work/i,
remoteConnection: /Could not read from remote repository/i,
tagConflict: /! \[rejected\].*\(would clobber existing tag\)/m,
unmergedFiles: /is not possible because you have unmerged files/i,
unstagedChanges: /You have unstaged changes/i,
};
const GitWarnings = {
@ -84,6 +109,7 @@ const GitWarnings = {
noRemoteRepositorySpecified: /No remote repository specified\./i,
remoteConnectionError: /Could not read from remote repository/i,
notAGitCommand: /'.+' is not a git command/i,
tipBehind: /tip of your current branch is behind/i,
};
function defaultExceptionHandler(ex: Error, cwd: string | undefined, start?: [number, number]): string {
@ -824,7 +850,7 @@ export class Git {
async fetch(
repoPath: string,
options:
| { all?: boolean; branch?: undefined; prune?: boolean; remote?: string }
| { all?: boolean; branch?: undefined; prune?: boolean; pull?: boolean; remote?: string }
| {
all?: undefined;
branch: string;
@ -843,22 +869,6 @@ export class Git {
if (options.branch && options.remote) {
if (options.upstream && options.pull) {
params.push('-u', options.remote, `${options.upstream}:${options.branch}`);
try {
void (await this.git<string>({ cwd: repoPath }, ...params));
return;
} catch (ex) {
const msg: string = ex?.toString() ?? '';
if (GitErrors.noFastForward.test(msg)) {
void window.showErrorMessage(
`Unable to pull the '${options.branch}' branch, as it can't be fast-forwarded.`,
);
return;
}
throw ex;
}
} else {
params.push(
options.remote,
@ -873,7 +883,128 @@ export class Git {
params.push('--all');
}
void (await this.git<string>({ cwd: repoPath }, ...params));
try {
void (await this.git<string>({ cwd: repoPath }, ...params));
} catch (ex) {
const msg: string = ex?.toString() ?? '';
let reason: FetchErrorReason = FetchErrorReason.Other;
if (GitErrors.noFastForward.test(msg) || GitErrors.noFastForward.test(ex.stderr ?? '')) {
reason = FetchErrorReason.NoFastForward;
} else if (
GitErrors.noRemoteRepositorySpecified.test(msg) ||
GitErrors.noRemoteRepositorySpecified.test(ex.stderr ?? '')
) {
reason = FetchErrorReason.NoRemote;
} else if (GitErrors.remoteConnection.test(msg) || GitErrors.remoteConnection.test(ex.stderr ?? '')) {
reason = FetchErrorReason.RemoteConnection;
}
throw new FetchError(reason, ex, options?.branch, options?.remote);
}
}
async push(
repoPath: string,
options: { branch?: string; force?: boolean; publish?: boolean; remote?: string; upstream?: string },
): Promise<void> {
const params = ['push'];
if (options.force) {
params.push('--force');
}
if (options.branch && options.remote) {
if (options.upstream) {
params.push('-u', options.remote, `${options.upstream}:${options.branch}`);
} else if (options.publish) {
params.push('--set-upstream', options.remote, options.branch);
} else {
params.push(options.remote, options.branch);
}
} else if (options.remote) {
params.push(options.remote);
}
try {
void (await this.git<string>({ cwd: repoPath }, ...params));
} catch (ex) {
const msg: string = ex?.toString() ?? '';
let reason: PushErrorReason = PushErrorReason.Other;
if (GitErrors.remoteAhead.test(msg) || GitErrors.remoteAhead.test(ex.stderr ?? '')) {
reason = PushErrorReason.RemoteAhead;
} else if (GitWarnings.tipBehind.test(msg) || GitWarnings.tipBehind.test(ex.stderr ?? '')) {
reason = PushErrorReason.TipBehind;
} else if (GitErrors.pushRejected.test(msg) || GitErrors.pushRejected.test(ex.stderr ?? '')) {
reason = PushErrorReason.PushRejected;
} else if (GitErrors.permissionDenied.test(msg) || GitErrors.permissionDenied.test(ex.stderr ?? '')) {
reason = PushErrorReason.PermissionDenied;
} else if (GitErrors.remoteConnection.test(msg) || GitErrors.remoteConnection.test(ex.stderr ?? '')) {
reason = PushErrorReason.RemoteConnection;
} else if (GitErrors.noUpstream.test(msg) || GitErrors.noUpstream.test(ex.stderr ?? '')) {
reason = PushErrorReason.NoUpstream;
}
throw new PushError(reason, ex, options?.branch, options?.remote);
}
}
async pull(
repoPath: string,
options: { branch?: string; remote?: string; rebase?: boolean; tags?: boolean },
): Promise<void> {
const params = ['pull'];
if (options.tags) {
params.push('--tags');
}
if (options.rebase) {
params.push('-r');
}
if (options.remote && options.branch) {
params.push(options.remote);
params.push(options.branch);
}
try {
void (await this.git<string>({ cwd: repoPath }, ...params));
} catch (ex) {
const msg: string = ex?.toString() ?? '';
let reason: PullErrorReason = PullErrorReason.Other;
if (GitErrors.conflict.test(msg) || GitErrors.conflict.test(ex.stdout ?? '')) {
reason = PullErrorReason.Conflict;
} else if (
GitErrors.noUserNameConfigured.test(msg) ||
GitErrors.noUserNameConfigured.test(ex.stderr ?? '')
) {
reason = PullErrorReason.GitIdentity;
} else if (GitErrors.remoteConnection.test(msg) || GitErrors.remoteConnection.test(ex.stderr ?? '')) {
reason = PullErrorReason.RemoteConnection;
} else if (GitErrors.unstagedChanges.test(msg) || GitErrors.unstagedChanges.test(ex.stderr ?? '')) {
reason = PullErrorReason.UnstagedChanges;
} else if (GitErrors.unmergedFiles.test(msg) || GitErrors.unmergedFiles.test(ex.stderr ?? '')) {
reason = PullErrorReason.UnmergedFiles;
} else if (GitErrors.commitChangesFirst.test(msg) || GitErrors.commitChangesFirst.test(ex.stderr ?? '')) {
reason = PullErrorReason.UncommittedChanges;
} else if (
GitErrors.changesWouldBeOverwritten.test(msg) ||
GitErrors.changesWouldBeOverwritten.test(ex.stderr ?? '')
) {
reason = PullErrorReason.OverwrittenChanges;
} else if (GitErrors.cantLockRef.test(msg) || GitErrors.cantLockRef.test(ex.stderr ?? '')) {
reason = PullErrorReason.RefLocked;
} else if (
GitErrors.rebaseMultipleBranches.test(msg) ||
GitErrors.rebaseMultipleBranches.test(ex.stderr ?? '')
) {
reason = PullErrorReason.RebaseMultipleBranches;
} else if (GitErrors.tagConflict.test(msg) || GitErrors.tagConflict.test(ex.stderr ?? '')) {
reason = PullErrorReason.TagConflict;
}
throw new PullError(reason, ex, options?.branch, options?.remote);
}
}
for_each_ref__branch(repoPath: string, options: { all: boolean } = { all: false }) {

+ 100
- 14
src/env/node/git/localGitProvider.ts ファイルの表示

@ -21,7 +21,10 @@ import { emojify } from '../../../emojis';
import { Features } from '../../../features';
import { GitErrorHandling } from '../../../git/commandOptions';
import {
FetchError,
GitSearchError,
PullError,
PushError,
StashApplyError,
StashApplyErrorReason,
WorktreeCreateError,
@ -80,6 +83,7 @@ import type { GitRebaseStatus } from '../../../git/models/rebase';
import type { GitBranchReference } from '../../../git/models/reference';
import {
createReference,
getBranchTrackingWithoutRemote,
getReferenceFromBranch,
isBranchReference,
isRevisionRange,
@ -88,6 +92,7 @@ import {
isUncommitted,
isUncommittedStaged,
shortenRevision,
splitRefNameAndRemote,
} from '../../../git/models/reference';
import type { GitReflog } from '../../../git/models/reflog';
import { getRemoteIconUri, getVisibilityCacheKey, GitRemote } from '../../../git/models/remote';
@ -1157,22 +1162,103 @@ export class LocalGitProvider implements GitProvider, Disposable {
repoPath: string,
options?: { all?: boolean; branch?: GitBranchReference; prune?: boolean; pull?: boolean; remote?: string },
): Promise<void> {
const { branch: branchRef, ...opts } = options ?? {};
if (isBranchReference(branchRef)) {
const repo = this.container.git.getRepository(repoPath);
const branch = await repo?.getBranch(branchRef?.name);
if (!branch?.remote && branch?.upstream == null) return undefined;
await this.git.fetch(repoPath, {
branch: branch.getNameWithoutRemote(),
remote: branch.getRemoteName()!,
upstream: branch.getTrackingWithoutRemote()!,
pull: options?.pull,
const { branch, ...opts } = options ?? {};
try {
if (isBranchReference(branch)) {
if (!branch?.remote && branch?.upstream == null) return undefined;
const [branchName, remoteName] = splitRefNameAndRemote(branch);
await this.git.fetch(repoPath, {
branch: branchName,
remote: remoteName!,
upstream: getBranchTrackingWithoutRemote(branch)!,
pull: options?.pull,
});
} else {
await this.git.fetch(repoPath, opts);
}
this.container.events.fire('git:cache:reset', { repoPath: repoPath });
} catch (ex) {
Logger.error(ex, 'LocalGitProvider.fetch');
if (FetchError.is(ex)) {
void window.showErrorMessage(ex.message);
} else {
throw ex;
}
}
}
@gate()
@log()
async push(
repoPath: string,
options?: { branch?: GitBranchReference; force?: boolean; publish?: { remote: string } },
): Promise<void> {
let branch = options?.branch;
if (!isBranchReference(branch)) {
branch = await this.getBranch(repoPath);
if (branch == null) return undefined;
}
const [branchName, remoteName] = splitRefNameAndRemote(branch);
if (options?.publish == null && remoteName == null && branch.upstream == null) {
return undefined;
}
try {
await this.git.push(repoPath, {
branch: branchName,
remote: options?.publish ? options.publish.remote : remoteName,
upstream: getBranchTrackingWithoutRemote(branch),
force: options?.force,
publish: options?.publish != null,
});
} else {
await this.git.fetch(repoPath, opts);
this.container.events.fire('git:cache:reset', { repoPath: repoPath });
} catch (ex) {
Logger.error(ex, 'LocalGitProvider.push');
if (PushError.is(ex)) {
void window.showErrorMessage(ex.message);
} else {
throw ex;
}
}
}
@gate()
@log()
async pull(
repoPath: string,
options?: { branch?: GitBranchReference; rebase?: boolean; tags?: boolean },
): Promise<void> {
let branch = options?.branch;
if (!isBranchReference(branch)) {
branch = await this.getBranch(repoPath);
if (branch == null) return undefined;
}
const [branchName, remoteName] = splitRefNameAndRemote(branch);
if (remoteName == null && branch.upstream == null) return undefined;
try {
await this.git.pull(repoPath, {
branch: branchName,
remote: remoteName,
rebase: options?.rebase,
tags: options?.tags,
});
this.container.events.fire('git:cache:reset', { repoPath: repoPath });
} catch (ex) {
Logger.error(ex, 'LocalGitProvider.pull');
if (PullError.is(ex)) {
void window.showErrorMessage(ex.message);
} else {
throw ex;
}
}
this.container.events.fire('git:cache:reset', { repoPath: repoPath });
}
private readonly toCanonicalMap = new Map<string, Uri>();

+ 205
- 0
src/git/errors.ts ファイルの表示

@ -82,6 +82,211 @@ export class StashPushError extends Error {
}
}
export const enum PushErrorReason {
RemoteAhead = 1,
TipBehind = 2,
PushRejected = 3,
PermissionDenied = 4,
RemoteConnection = 5,
NoUpstream = 6,
Other = 7,
}
export class PushError extends Error {
static is(ex: any, reason?: PushErrorReason): ex is PushError {
return ex instanceof PushError && (reason == null || ex.reason === reason);
}
readonly original?: Error;
readonly reason: PushErrorReason | undefined;
constructor(reason?: PushErrorReason, original?: Error, branch?: string, remote?: string);
constructor(message?: string, original?: Error);
constructor(
messageOrReason: string | PushErrorReason | undefined,
original?: Error,
branch?: string,
remote?: string,
) {
let message;
const baseMessage = `Unable to push${branch ? ` branch '${branch}'` : ''}${remote ? ` to ${remote}` : ''}`;
let reason: PushErrorReason | undefined;
if (messageOrReason == null) {
message = baseMessage;
} else if (typeof messageOrReason === 'string') {
message = messageOrReason;
reason = undefined;
} else {
reason = messageOrReason;
switch (reason) {
case PushErrorReason.RemoteAhead:
message = `${baseMessage} because the remote contains work that you do not have locally. Try doing a fetch first.`;
break;
case PushErrorReason.TipBehind:
message = `${baseMessage} as it is behind its remote counterpart. Try doing a pull first.`;
break;
case PushErrorReason.PushRejected:
message = `${baseMessage} because some refs failed to push or the push was rejected.`;
break;
case PushErrorReason.PermissionDenied:
message = `${baseMessage} because you don't have permission to push to this remote repository.`;
break;
case PushErrorReason.RemoteConnection:
message = `${baseMessage} because the remote repository could not be reached.`;
break;
case PushErrorReason.NoUpstream:
message = `${baseMessage} because it has no upstream branch.`;
break;
default:
message = baseMessage;
}
}
super(message);
this.original = original;
this.reason = reason;
Error.captureStackTrace?.(this, PushError);
}
}
export const enum PullErrorReason {
Conflict = 1,
GitIdentity = 2,
RemoteConnection = 3,
UnstagedChanges = 4,
UnmergedFiles = 5,
UncommittedChanges = 6,
OverwrittenChanges = 7,
RefLocked = 8,
RebaseMultipleBranches = 9,
TagConflict = 10,
Other = 11,
}
export class PullError extends Error {
static is(ex: any, reason?: PullErrorReason): ex is PullError {
return ex instanceof PullError && (reason == null || ex.reason === reason);
}
readonly original?: Error;
readonly reason: PullErrorReason | undefined;
constructor(reason?: PullErrorReason, original?: Error, branch?: string, remote?: string);
constructor(message?: string, original?: Error);
constructor(
messageOrReason: string | PullErrorReason | undefined,
original?: Error,
branch?: string,
remote?: string,
) {
let message;
let reason: PullErrorReason | undefined;
const baseMessage = `Unable to pull${branch ? ` branch '${branch}'` : ''}${remote ? ` from ${remote}` : ''}`;
if (messageOrReason == null) {
message = 'Unable to pull';
} else if (typeof messageOrReason === 'string') {
message = messageOrReason;
reason = undefined;
} else {
reason = messageOrReason;
switch (reason) {
case PullErrorReason.Conflict:
message = `${baseMessage} due to conflicts.`;
break;
case PullErrorReason.GitIdentity:
message = `${baseMessage} because you have not yet set up your Git identity.`;
break;
case PullErrorReason.RemoteConnection:
message = `${baseMessage} because the remote repository could not be reached.`;
break;
case PullErrorReason.UnstagedChanges:
message = `${baseMessage} because you have unstaged changes.`;
break;
case PullErrorReason.UnmergedFiles:
message = `${baseMessage} because you have unmerged files.`;
break;
case PullErrorReason.UncommittedChanges:
message = `${baseMessage} because you have uncommitted changes.`;
break;
case PullErrorReason.OverwrittenChanges:
message = `${baseMessage} because local changes to some files would be overwritten.`;
break;
case PullErrorReason.RefLocked:
message = `${baseMessage} because a local ref could not be updated.`;
break;
case PullErrorReason.RebaseMultipleBranches:
message = `${baseMessage} because you are trying to rebase onto multiple branches.`;
break;
case PullErrorReason.TagConflict:
message = `${baseMessage} because a local tag would be overwritten.`;
break;
default:
message = baseMessage;
}
}
super(message);
this.original = original;
this.reason = reason;
Error.captureStackTrace?.(this, PullError);
}
}
export const enum FetchErrorReason {
NoFastForward = 1,
NoRemote = 2,
RemoteConnection = 3,
Other = 4,
}
export class FetchError extends Error {
static is(ex: any, reason?: FetchErrorReason): ex is FetchError {
return ex instanceof FetchError && (reason == null || ex.reason === reason);
}
readonly original?: Error;
readonly reason: FetchErrorReason | undefined;
constructor(reason?: FetchErrorReason, original?: Error, branch?: string, remote?: string);
constructor(message?: string, original?: Error);
constructor(
messageOrReason: string | FetchErrorReason | undefined,
original?: Error,
branch?: string,
remote?: string,
) {
let message;
const baseMessage = `Unable to fetch${branch ? ` branch '${branch}'` : ''}${remote ? ` from ${remote}` : ''}`;
let reason: FetchErrorReason | undefined;
if (messageOrReason == null) {
message = baseMessage;
} else if (typeof messageOrReason === 'string') {
message = messageOrReason;
reason = undefined;
} else {
reason = messageOrReason;
switch (reason) {
case FetchErrorReason.NoFastForward:
message = `${baseMessage} as it cannot be fast-forwarded`;
break;
case FetchErrorReason.NoRemote:
message = `${baseMessage} without a remote repository specified.`;
break;
case FetchErrorReason.RemoteConnection:
message = `${baseMessage}. Could not connect to the remote repository.`;
break;
default:
message = baseMessage;
}
}
super(message);
this.original = original;
this.reason = reason;
Error.captureStackTrace?.(this, FetchError);
}
}
export class WorkspaceUntrustedError extends Error {
constructor() {
super('Unable to perform Git operations because the current workspace is untrusted');

+ 16
- 0
src/git/gitProvider.ts ファイルの表示

@ -163,6 +163,22 @@ export interface GitProvider extends Disposable {
remote?: string | undefined;
},
): Promise<void>;
pull(
repoPath: string,
options?: {
branch?: GitBranchReference | undefined;
rebase?: boolean | undefined;
tags?: boolean | undefined;
},
): Promise<void>;
push(
repoPath: string,
options?: {
branch?: GitBranchReference | undefined;
force?: boolean | undefined;
publish?: { remote: string };
},
): Promise<void>;
findRepositoryUri(uri: Uri, isDirectory?: boolean): Promise<Uri | undefined>;
getAheadBehindCommitCount(repoPath: string, refs: string[]): Promise<{ ahead: number; behind: number } | undefined>;
/**

+ 17
- 0
src/git/gitProviderService.ts ファイルの表示

@ -1319,6 +1319,13 @@ export class GitProviderService implements Disposable {
);
}
@gate()
@log()
pull(repoPath: string, options?: { branch?: GitBranchReference; rebase?: boolean; tags?: boolean }): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
return provider.pull(path, options);
}
@gate<GitProviderService['pullAll']>(
(repos, opts) => `${repos == null ? '' : repos.map(r => r.id).join(',')}|${JSON.stringify(opts)}`,
)
@ -1344,6 +1351,16 @@ export class GitProviderService implements Disposable {
);
}
@gate()
@log()
push(
repoPath: string,
options?: { branch?: GitBranchReference; force?: boolean; publish?: { remote: string } },
): Promise<void> {
const { provider, path } = this.getProvider(repoPath);
return provider.push(path, options);
}
@gate<GitProviderService['pushAll']>(repos => `${repos == null ? '' : repos.map(r => r.id).join(',')}`)
@log<GitProviderService['pushAll']>({ args: { 0: repos => repos?.map(r => r.name).join(', ') } })
async pushAll(

+ 3
- 3
src/git/models/branch.ts ファイルの表示

@ -11,7 +11,7 @@ import type { RemoteProvider } from '../remotes/remoteProvider';
import type { RichRemoteProvider } from '../remotes/richRemoteProvider';
import type { PullRequest, PullRequestState } from './pullRequest';
import type { GitBranchReference, GitReference } from './reference';
import { shortenRevision } from './reference';
import { getBranchTrackingWithoutRemote, shortenRevision } from './reference';
import type { GitRemote } from './remote';
import type { Repository } from './repository';
import { getUpstreamStatus } from './status';
@ -139,7 +139,7 @@ export class GitBranch implements GitBranchReference {
@memoize()
getTrackingWithoutRemote(): string | undefined {
return this.upstream?.name.substring(getRemoteNameSlashIndex(this.upstream.name) + 1);
return getBranchTrackingWithoutRemote(this);
}
@memoize()
@ -220,7 +220,7 @@ export function formatDetachedHeadName(sha: string): string {
return `(${shortenRevision(sha)}...)`;
}
function getRemoteNameSlashIndex(name: string): number {
export function getRemoteNameSlashIndex(name: string): number {
return name.startsWith('remotes/') ? name.indexOf('/', 8) : name.indexOf('/');
}

+ 10
- 1
src/git/models/reference.ts ファイルの表示

@ -1,6 +1,11 @@
import { GlyphChars } from '../../constants';
import { configuration } from '../../system/configuration';
import { getBranchNameWithoutRemote, getRemoteNameFromBranchName, splitBranchNameAndRemote } from './branch';
import {
getBranchNameWithoutRemote,
getRemoteNameFromBranchName,
getRemoteNameSlashIndex,
splitBranchNameAndRemote,
} from './branch';
import { deletedOrMissing, uncommitted, uncommittedStaged } from './constants';
const rangeRegex = /^(\S*?)(\.\.\.?)(\S*)\s*$/;
@ -264,6 +269,10 @@ export function getNameWithoutRemote(ref: GitReference) {
return ref.name;
}
export function getBranchTrackingWithoutRemote(ref: GitBranchReference) {
return ref.upstream?.name.substring(getRemoteNameSlashIndex(ref.upstream.name) + 1);
}
export function isGitReference(ref: unknown): ref is GitReference {
if (ref == null || typeof ref !== 'object') return false;

+ 51
- 30
src/git/models/repository.ts ファイルの表示

@ -793,11 +793,25 @@ export class Repository implements Disposable {
private async pullCore(options?: { rebase?: boolean }) {
try {
const upstream = await this.hasUpstreamBranch();
if (upstream) {
void (await executeCoreGitCommand(options?.rebase ? 'git.pullRebase' : 'git.pull', this.path));
} else if (configuration.getAny<CoreGitConfiguration, boolean>('git.fetchOnPull', Uri.file(this.path))) {
await this.container.git.fetch(this.path);
if (configuration.getAny('gitlens.experimental.nativeGit') === true) {
const withTags = configuration.getAny<CoreGitConfiguration, boolean>(
'git.pullTags',
Uri.file(this.path),
);
if (configuration.getAny<CoreGitConfiguration, boolean>('git.fetchOnPull', Uri.file(this.path))) {
await this.container.git.fetch(this.path);
}
await this.container.git.pull(this.path, { ...options, tags: withTags });
} else {
const upstream = await this.hasUpstreamBranch();
if (upstream) {
void (await executeCoreGitCommand(options?.rebase ? 'git.pullRebase' : 'git.pull', this.path));
} else if (
configuration.getAny<CoreGitConfiguration, boolean>('git.fetchOnPull', Uri.file(this.path))
) {
await this.container.git.fetch(this.path);
}
}
this.fireChange(RepositoryChange.Unknown);
@ -807,30 +821,6 @@ export class Repository implements Disposable {
}
}
@gate()
@log()
async push(options?: {
force?: boolean;
progress?: boolean;
reference?: GitReference;
publish?: {
remote: string;
};
}) {
const { progress, ...opts } = { progress: true, ...options };
if (!progress) return this.pushCore(opts);
return window.withProgress(
{
location: ProgressLocation.Notification,
title: isBranchReference(opts.reference)
? `${opts.publish != null ? 'Publishing ' : 'Pushing '}${opts.reference.name}...`
: `Pushing ${this.formattedName}...`,
},
() => this.pushCore(opts),
);
}
private async showCreatePullRequestPrompt(remoteName: string, branch: GitBranchReference) {
if (!this.container.actionRunners.count('createPullRequest')) return;
if (!(await showCreatePullRequestPrompt(branch.name))) return;
@ -862,6 +852,30 @@ export class Repository implements Disposable {
});
}
@gate()
@log()
async push(options?: {
force?: boolean;
progress?: boolean;
reference?: GitReference;
publish?: {
remote: string;
};
}) {
const { progress, ...opts } = { progress: true, ...options };
if (!progress) return this.pushCore(opts);
return window.withProgress(
{
location: ProgressLocation.Notification,
title: isBranchReference(opts.reference)
? `${opts.publish != null ? 'Publishing ' : 'Pushing '}${opts.reference.name}...`
: `Pushing ${this.formattedName}...`,
},
() => this.pushCore(opts),
);
}
private async pushCore(options?: {
force?: boolean;
reference?: GitReference;
@ -870,7 +884,14 @@ export class Repository implements Disposable {
};
}) {
try {
if (isBranchReference(options?.reference)) {
if (configuration.getAny('gitlens.experimental.nativeGit') === true) {
const branch = await this.getBranch(options?.reference?.name);
await this.container.git.push(this.path, {
force: options?.force,
branch: isBranchReference(options?.reference) ? options?.reference : branch,
...(options?.publish && { publish: options.publish }),
});
} else if (isBranchReference(options?.reference)) {
const repo = await this.container.git.getOrOpenScmRepository(this.path);
if (repo == null) return;

+ 12
- 0
src/plus/github/githubGitProvider.ts ファイルの表示

@ -486,6 +486,18 @@ export class GitHubGitProvider implements GitProvider, Disposable {
_options?: { all?: boolean; branch?: GitBranchReference; prune?: boolean; pull?: boolean; remote?: string },
): Promise<void> {}
@log()
async pull(
_repoPath: string,
_options?: { branch?: GitBranchReference; rebase?: boolean; tags?: boolean },
): Promise<void> {}
@log()
async push(
_repoPath: string,
_options?: { branch?: GitBranchReference; force?: boolean; publish?: { remote: string } },
): Promise<void> {}
@gate()
@debug()
async findRepositoryUri(uri: Uri, _isDirectory?: boolean): Promise<Uri | undefined> {

読み込み中…
キャンセル
保存