Преглед на файлове

Adds new git commands features

Adds arbitrary ref support to cherry-pick git command
Enhances the feedback when choosing an arbitrary ref
main
Eric Amodio преди 5 години
родител
ревизия
8a090bd54e
променени са 12 файла, в които са добавени 179 реда и са изтрити 103 реда
  1. +6
    -2
      src/annotations/annotations.ts
  2. +1
    -1
      src/commands/diffWith.ts
  3. +56
    -25
      src/commands/gitCommands.ts
  4. +17
    -12
      src/commands/quick/checkout.ts
  5. +49
    -35
      src/commands/quick/cherry-pick.ts
  6. +3
    -0
      src/commands/quick/quickCommand.ts
  7. +14
    -10
      src/git/git.ts
  8. +10
    -6
      src/git/gitService.ts
  9. +4
    -5
      src/quickpicks/gitQuickPicks.ts
  10. +3
    -1
      src/views/nodes/compareBranchNode.ts
  11. +3
    -2
      src/views/nodes/compareResultsNode.ts
  12. +13
    -4
      src/vsls/vsls.ts

+ 6
- 2
src/annotations/annotations.ts Целия файл

@ -143,7 +143,9 @@ export class Annotations {
previous =
diffUris.previous.sha === undefined || diffUris.previous.isUncommitted
? `_${GitService.shortenSha(diffUris.previous.sha, {
working: 'Working Tree'
strings: {
working: 'Working Tree'
}
})}_`
: `[\`${GitService.shortenSha(
diffUris.previous.sha || ''
@ -154,7 +156,9 @@ export class Annotations {
current =
diffUris.current.sha === undefined || diffUris.current.isUncommitted
? `_${GitService.shortenSha(diffUris.current.sha, {
working: 'Working Tree'
strings: {
working: 'Working Tree'
}
})}_`
: `[\`${GitService.shortenSha(
diffUris.current.sha || ''

+ 1
- 1
src/commands/diffWith.ts Целия файл

@ -117,7 +117,7 @@ export class DiffWithCommand extends ActiveEditorCommand {
Container.git.getVersionedUri(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha)
]);
let rhsSuffix = GitService.shortenSha(rhsSha, { uncommitted: 'Working Tree' }) || '';
let rhsSuffix = GitService.shortenSha(rhsSha, { strings: { uncommitted: 'Working Tree' } }) || '';
if (rhs === undefined) {
if (GitService.isUncommitted(args.rhs.sha)) {
rhsSuffix = 'deleted';

+ 56
- 25
src/commands/gitCommands.ts Целия файл

@ -167,6 +167,8 @@ export class GitCommandsCommand extends Command {
try {
return await new Promise<QuickPickStep | QuickInputStep | undefined>(resolve => {
let overrideItems = false;
disposables.push(
quickpick.onDidHide(() => resolve()),
quickpick.onDidTriggerButton(async e => {
@ -186,40 +188,67 @@ export class GitCommandsCommand extends Command {
step.onDidClickButton(quickpick, e);
}),
quickpick.onDidChangeValue(async e => {
if (quickpick.canSelectMany && e === ' ') {
quickpick.value = '';
quickpick.selectedItems =
quickpick.selectedItems.length === quickpick.items.length ? [] : quickpick.items;
return;
}
if (!overrideItems) {
if (quickpick.canSelectMany && e === ' ') {
quickpick.value = '';
quickpick.selectedItems =
quickpick.selectedItems.length === quickpick.items.length ? [] : quickpick.items;
if (e.endsWith(' ')) {
if (quickpick.canSelectMany && quickpick.selectedItems.length !== 0) {
return;
}
let items;
if (commandsStep.command === undefined) {
const command = commandsStep.find(quickpick.value.trim());
if (command === undefined) return;
if (e.endsWith(' ')) {
if (quickpick.canSelectMany && quickpick.selectedItems.length !== 0) {
return;
}
commandsStep.command = command;
}
else {
const step = commandsStep.command.value;
if (step === undefined || !isQuickPickStep(step)) return;
let items;
if (commandsStep.command === undefined) {
const command = commandsStep.find(quickpick.value.trim());
if (command === undefined) return;
const cmd = quickpick.value.trim().toLowerCase();
const item = step.items.find(
i => i.label.replace(sanitizeLabel, '').toLowerCase() === cmd
);
if (item === undefined) return;
commandsStep.command = command;
}
else {
const step = commandsStep.command.value;
if (step === undefined || !isQuickPickStep(step)) return;
items = [item];
const cmd = quickpick.value.trim().toLowerCase();
const item = step.items.find(
i => i.label.replace(sanitizeLabel, '').toLowerCase() === cmd
);
if (item === undefined) return;
items = [item];
}
resolve(await this.nextStep(quickpick, commandsStep.command, items));
return;
}
}
// Assume there is no matches (since there is no activeItems)
if (
!quickpick.canSelectMany &&
commandsStep.command !== undefined &&
e.trim().length !== 0 &&
(overrideItems || quickpick.activeItems.length === 0)
) {
const step = commandsStep.command.value;
if (step === undefined || !isQuickPickStep(step) || step.onValidateValue === undefined) {
return;
}
resolve(await this.nextStep(quickpick, commandsStep.command, items));
overrideItems = await step.onValidateValue(quickpick, e.trim(), step.items);
}
else {
overrideItems = false;
}
// If we are no longer overriding the items, put them back (only if we need to)
if (!overrideItems && quickpick.items.length !== step.items.length) {
quickpick.items = step.items;
}
}),
quickpick.onDidAccept(async () => {
@ -260,6 +289,8 @@ export class GitCommandsCommand extends Command {
quickpick.buttons = step.buttons || [QuickInputButtons.Back];
quickpick.title = step.title;
quickpick.placeholder = step.placeholder;
quickpick.matchOnDescription = Boolean(step.matchOnDescription);
quickpick.matchOnDetail = Boolean(step.matchOnDetail);
quickpick.canSelectMany = Boolean(step.multiselect);
quickpick.items = step.items;

+ 17
- 12
src/commands/quick/checkout.ts Целия файл

@ -2,7 +2,7 @@
/* eslint-disable no-loop-func */
import { ProgressLocation, QuickInputButtons, window } from 'vscode';
import { Container } from '../../container';
import { GitBranch, GitReference, GitService, GitTag, Repository } from '../../git/gitService';
import { GitBranch, GitReference, GitTag, Repository } from '../../git/gitService';
import { GlyphChars } from '../../constants';
import {
CommandAbortError,
@ -12,7 +12,7 @@ import {
QuickPickStep,
StepState
} from './quickCommand';
import { ReferencesQuickPickItem, RepositoryQuickPickItem } from '../../quickpicks';
import { ReferencesQuickPickItem, RefQuickPickItem, RepositoryQuickPickItem } from '../../quickpicks';
import { Strings } from '../../system';
interface State {
@ -131,6 +131,7 @@ export class CheckoutQuickCommand extends QuickCommandBase {
placeholder: `Choose a branch${
includeTags ? ' or tag' : ''
} to checkout to${GlyphChars.Space.repeat(3)}(select or enter a reference)`,
matchOnDescription: true,
items: items,
selectedItems: state.branchOrTagOrRef
? items.filter(ref => ref.label === state.branchOrTagOrRef!.ref)
@ -165,11 +166,18 @@ export class CheckoutQuickCommand extends QuickCommandBase {
quickpick.busy = false;
quickpick.enabled = true;
},
onDidAccept: (quickpick): Promise<boolean> => {
const ref = quickpick.value.trim();
if (ref.length === 0 || state.repos!.length !== 1) return Promise.resolve(false);
return Container.git.validateReference(state.repos![0].path, ref);
// onDidAccept: (quickpick): Promise<boolean> => {
// const ref = quickpick.value.trim();
// if (ref.length === 0 || state.repos!.length !== 1) return Promise.resolve(false);
// return Container.git.validateReference(state.repos![0].path, ref);
// },
onValidateValue: async (quickpick, value) => {
if (state.repos!.length !== 1) return false;
if (!(await Container.git.validateReference(state.repos![0].path, value))) return false;
quickpick.items = [RefQuickPickItem.create(value, true, { ref: true })];
return true;
}
});
const selection = yield step;
@ -182,13 +190,10 @@ export class CheckoutQuickCommand extends QuickCommandBase {
continue;
}
state.branchOrTagOrRef =
typeof selection === 'string'
? { name: GitService.shortenSha(selection), ref: selection }
: selection[0].item;
state.branchOrTagOrRef = selection[0].item;
}
if (state.branchOrTagOrRef instanceof GitBranch && state.branchOrTagOrRef.remote) {
if (GitBranch.is(state.branchOrTagOrRef) && state.branchOrTagOrRef.remote) {
const branches = await Container.git.getBranches(state.branchOrTagOrRef.repoPath, {
filter: b => {
return b.tracking === state.branchOrTagOrRef!.name;

+ 49
- 35
src/commands/quick/cherry-pick.ts Целия файл

@ -1,7 +1,7 @@
'use strict';
/* eslint-disable no-loop-func */
import { Container } from '../../container';
import { GitBranch, GitLogCommit, Repository } from '../../git/gitService';
import { GitBranch, GitLogCommit, GitReference, Repository } from '../../git/gitService';
import { GlyphChars } from '../../constants';
import { Iterables, Strings } from '../../system';
import {
@ -14,12 +14,13 @@ import {
} from './quickCommand';
import { BranchQuickPickItem, CommitQuickPickItem, RepositoryQuickPickItem } from '../../quickpicks';
import { runGitCommandInTerminal } from '../../terminal';
import { RefQuickPickItem } from '../../quickpicks/gitQuickPicks';
interface State {
repo: Repository;
destination: GitBranch;
source: GitBranch;
commits: GitLogCommit[];
source: GitBranch | GitReference;
commits?: GitLogCommit[];
}
export class CherryPickQuickCommand extends QuickCommandBase<State> {
@ -28,9 +29,13 @@ export class CherryPickQuickCommand extends QuickCommandBase {
}
execute(state: State) {
// Ensure the commits are ordered with the oldest first
state.commits.sort((a, b) => a.date.getTime() - b.date.getTime());
runGitCommandInTerminal('cherry-pick', state.commits.map(c => c.sha).join(' '), state.repo.path, true);
if (state.commits !== undefined) {
// Ensure the commits are ordered with the oldest first
state.commits.sort((a, b) => a.date.getTime() - b.date.getTime());
runGitCommandInTerminal('cherry-pick', state.commits.map(c => c.sha).join(' '), state.repo.path, true);
}
runGitCommandInTerminal('cherry-pick', state.source.ref, state.repo.path, true);
}
protected async *steps(): AsyncIterableIterator<QuickPickStep | QuickInputStep> {
@ -79,20 +84,23 @@ export class CherryPickQuickCommand extends QuickCommandBase {
if (state.source === undefined || state.counter < 2) {
const destId = state.destination.id;
const step = this.createPickStep<BranchQuickPickItem>({
const step = this.createPickStep<BranchQuickPickItem | RefQuickPickItem>({
title: `${this.title} into ${state.destination.name}${Strings.pad(GlyphChars.Dot, 2, 2)}${
state.repo.name
}`,
placeholder: 'Choose a branch or tag to cherry-pick from',
placeholder: `Choose a branch or tag to cherry-pick from${GlyphChars.Space.repeat(
3
)}(select or enter a reference)`,
matchOnDescription: true,
items: await getBranchesAndOrTags(state.repo, true, {
filterBranches: b => b.id !== destId
})
// onDidAccept: (quickpick): Promise<boolean> => {
// const ref = quickpick.value.trim();
// if (ref.length === 0) return Promise.resolve(false);
}),
onValidateValue: async (quickpick, value) => {
if (!(await Container.git.validateReference(state.repo!.path, value))) return false;
// return Container.git.validateReference(state.repo!.path, ref);
// }
quickpick.items = [RefQuickPickItem.create(value, true, { ref: true })];
return true;
}
});
const selection = yield step;
@ -104,16 +112,16 @@ export class CherryPickQuickCommand extends QuickCommandBase {
continue;
}
// TODO: Allow pasting in commit id
// if (typeof selection === 'string') {
// }
// else {
state.source = selection[0].item;
// }
if (GitBranch.is(state.source)) {
state.source = selection[0].item;
}
else {
state.source = selection[0].item;
state.counter++;
}
}
if (state.commits === undefined || state.counter < 3) {
if (GitBranch.is(state.source) && (state.commits === undefined || state.counter < 3)) {
const log = await Container.git.getLog(state.repo.path, {
ref: `${state.destination.ref}..${state.source.ref}`,
merges: false
@ -153,19 +161,25 @@ export class CherryPickQuickCommand extends QuickCommandBase {
const step = this.createConfirmStep(
`Confirm ${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${state.repo.name}`,
[
{
label: this.title,
description: `${
state.commits.length === 1
? state.commits[0].shortSha
: `${state.commits.length} commits`
} onto ${state.destination.name}`,
detail: `Will apply ${
state.commits.length === 1
? `commit ${state.commits[0].shortSha}`
: `${state.commits.length} commits`
} onto ${state.destination.name}`
}
state.commits !== undefined
? {
label: this.title,
description: `${
state.commits.length === 1
? state.commits[0].shortSha
: `${state.commits.length} commits`
} onto ${state.destination.name}`,
detail: `Will apply ${
state.commits.length === 1
? `commit ${state.commits[0].shortSha}`
: `${state.commits.length} commits`
} onto ${state.destination.name}`
}
: {
label: this.title,
description: `${state.source.name} onto ${state.destination.name}`,
detail: `Will apply commit ${state.source.name} onto ${state.destination.name}`
}
]
);
const selection = yield step;

+ 3
- 0
src/commands/quick/quickCommand.ts Целия файл

@ -22,6 +22,8 @@ export interface QuickPickStep {
buttons?: QuickInputButton[];
selectedItems?: QuickPickItem[];
items: QuickPickItem[];
matchOnDescription?: boolean;
matchOnDetail?: boolean;
multiselect?: boolean;
placeholder?: string;
title?: string;
@ -29,6 +31,7 @@ export interface QuickPickStep {
onDidAccept?(quickpick: QuickPick<T>): Promise<boolean>;
onDidClickButton?(quickpick: QuickPick<T>, button: QuickInputButton): void;
onValidateValue?(quickpick: QuickPick<T>, value: string, items: T[]): Promise<boolean>;
validate?(selection: T[]): boolean | Promise<boolean>;
}

+ 14
- 10
src/git/git.ts Целия файл

@ -259,17 +259,21 @@ export class Git {
static shortenSha(
ref: string | undefined,
{
uncommitted = 'Working Tree',
uncommittedStaged: uncommittedStaged = 'Index',
working = emptyStr
}: { uncommittedStaged?: string; uncommitted?: string; working?: string } = {}
force,
strings = {}
}: {
force?: boolean;
strings?: { uncommitted?: string; uncommittedStaged?: string; working?: string };
} = {}
) {
if (ref == null || ref.length === 0) return working;
if (ref == null || ref.length === 0) return strings.working || emptyStr;
if (Git.isUncommitted(ref)) {
return Git.isUncommittedStaged(ref) ? uncommittedStaged : uncommitted;
return Git.isUncommittedStaged(ref)
? strings.uncommittedStaged || 'Index'
: strings.uncommitted || 'Working Tree';
}
if (!Git.isShaLike(ref)) return ref;
if (!force && !Git.isShaLike(ref)) return ref;
// Don't allow shas to be shortened to less than 5 characters
const len = Math.max(5, Container.config.advanced.abbreviatedShaLength);
@ -415,12 +419,12 @@ export class Git {
if (Git.isUncommitted(ref)) return ref;
try {
await git<string>(
void (await git<string>(
{ cwd: repoPath, errors: GitErrorHandling.Throw },
'cat-file',
'-e',
`${ref}:./${fileName}`
);
));
return ref;
}
catch (ex) {
@ -437,7 +441,7 @@ export class Git {
if (Git.isUncommitted(ref)) return true;
try {
await git<string>({ cwd: repoPath, errors: GitErrorHandling.Throw }, 'cat-file', '-t', ref);
void (await git<string>({ cwd: repoPath, errors: GitErrorHandling.Throw }, 'cat-file', '-t', ref));
return true;
}
catch (ex) {

+ 10
- 6
src/git/gitService.ts Целия файл

@ -150,6 +150,10 @@ export class GitService implements Disposable {
await Git.setOrFindGitPath(gitPath || configuration.getAny<string>('git.path'));
}
get readonly() {
return Container.vsls.readonly;
}
get useCaching() {
return Container.config.advanced.caching.enabled;
}
@ -2800,13 +2804,13 @@ export class GitService implements Disposable {
static shortenSha(
ref: string | undefined,
{
deletedOrMissing = '(deleted)',
...strings
}: { deletedOrMissing?: string; uncommitted?: string; uncommittedStaged?: string; working?: string } = {}
options: {
force?: boolean;
strings?: { uncommitted?: string; uncommittedStaged?: string; working?: string };
} = {}
) {
if (ref === GitService.deletedOrMissingSha) return deletedOrMissing;
if (ref === GitService.deletedOrMissingSha) return '(deleted)';
return Git.shortenSha(ref, strings);
return Git.shortenSha(ref, options);
}
}

+ 4
- 5
src/quickpicks/gitQuickPicks.ts Целия файл

@ -170,13 +170,12 @@ export namespace CommitQuickPickItem {
}
export namespace RefQuickPickItem {
export function create(ref: string, picked?: boolean, options: { checked?: boolean } = {}) {
export function create(ref: string, picked?: boolean, options: { ref?: boolean } = {}) {
const item: RefQuickPickItem = {
label: `${
options.checked ? `$(check)${GlyphChars.Space}` : GlyphChars.Space.repeat(4)
} ${GitService.shortenSha(ref)}`,
label: `Commit ${GitService.shortenSha(ref, { force: true })}`,
description: options.ref ? `$(git-commit) ${ref}` : '',
picked: picked,
item: { name: ref, ref: ref },
item: { name: GitService.shortenSha(ref, { force: true }), ref: ref },
current: false,
ref: ref,
remote: false

+ 3
- 1
src/views/nodes/compareBranchNode.ts Целия файл

@ -85,7 +85,9 @@ export class CompareBranchNode extends ViewNode {
description = `${GlyphChars.ArrowLeftRightLong}${GlyphChars.Space} ${GitService.shortenSha(
this._compareWith.ref,
{
working: 'Working Tree'
strings: {
working: 'Working Tree'
}
}
)}`;
state = TreeItemCollapsibleState.Collapsed;

+ 3
- 2
src/views/nodes/compareResultsNode.ts Целия файл

@ -65,8 +65,9 @@ export class CompareResultsNode extends SubscribeableViewNode {
}
const item = new TreeItem(
`Comparing ${this._ref.label || GitService.shortenSha(this._ref.ref, { working: 'Working Tree' })} to ${this
._compareWith.label || GitService.shortenSha(this._compareWith.ref, { working: 'Working Tree' })}`,
`Comparing ${this._ref.label ||
GitService.shortenSha(this._ref.ref, { strings: { working: 'Working Tree' } })} to ${this._compareWith
.label || GitService.shortenSha(this._compareWith.ref, { strings: { working: 'Working Tree' } })}`,
this._state || TreeItemCollapsibleState.Collapsed
);
item.contextValue = `${ResourceType.CompareResults}+${

+ 13
- 4
src/vsls/vsls.ts Целия файл

@ -48,7 +48,7 @@ export class VslsController implements Disposable {
workspace.workspaceFolders !== undefined &&
workspace.workspaceFolders.some(f => f.uri.scheme === DocumentSchemes.Vsls)
) {
setCommandContext(CommandContext.Readonly, true);
this.setReadonly(true);
this._waitForReady = new Promise(resolve => (this._onReady = resolve));
}
@ -80,6 +80,15 @@ export class VslsController implements Disposable {
return this._guest !== undefined || this._waitForReady !== undefined;
}
private _readonly: boolean = false;
get readonly() {
return this._readonly;
}
private setReadonly(value: boolean) {
this._readonly = value;
setCommandContext(CommandContext.Readonly, value ? true : undefined);
}
async getContact(email: string | undefined) {
if (email === undefined) return undefined;
@ -148,20 +157,20 @@ export class VslsController implements Disposable {
switch (e.session.role) {
case Role.Host:
setCommandContext(CommandContext.Readonly, undefined);
this.setReadonly(false);
setCommandContext(CommandContext.Vsls, 'host');
if (Container.config.liveshare.allowGuestAccess) {
this._host = await VslsHostService.share(api);
}
break;
case Role.Guest:
setCommandContext(CommandContext.Readonly, true);
this.setReadonly(true);
setCommandContext(CommandContext.Vsls, 'guest');
this._guest = await VslsGuestService.connect(api);
break;
default:
setCommandContext(CommandContext.Readonly, undefined);
this.setReadonly(false);
setCommandContext(CommandContext.Vsls, true);
break;
}

Зареждане…
Отказ
Запис