Skip to content

Commit

Permalink
Merge pull request mrcrowl#96 from incidentist/feature/shelve
Browse files Browse the repository at this point in the history
Add shelve, unshelve, unshelve with keep, abort unshelve, continue unshelve commands.
  • Loading branch information
incidentist authored Jun 25, 2020
2 parents 73ee608 + b02ecf1 commit 07c363f
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 8 deletions.
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,31 @@
"command": "hg.update",
"title": "%command.update%",
"category": "Hg"
},
{
"command": "hg.shelve",
"title": "%command.shelve%",
"category": "Hg"
},
{
"command": "hg.unshelve",
"title": "%command.unshelve%",
"category": "Hg"
},
{
"command": "hg.unshelveKeep",
"title": "%command.unshelveKeep%",
"category": "Hg"
},
{
"command": "hg.unshelveAbort",
"title": "%command.unshelveAbort%",
"category": "Hg"
},
{
"command": "hg.unshelveContinue",
"title": "%command.unshelveContinue%",
"category": "Hg"
}
],
"menus": {
Expand Down
5 changes: 5 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
"command.logRepo": "Log – entire repository",
"command.mergeHeads": "Merge heads",
"command.mergeWithLocal": "Merge into working directory...",
"command.shelve": "Shelve",
"command.unshelve": "Unshelve",
"command.unshelveKeep": "Unshelve and Keep",
"command.unshelveAbort": "Abort unshelve",
"command.unshelveContinue": "Continue unshelve",
"config.enabled": "Whether Hg is enabled",
"config.path": "Path to the 'hg' executable (only required if auto-detection fails)",
"config.autoInOut": "Whether auto-incoming/outgoing counts are enabled",
Expand Down
47 changes: 46 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/

import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, SourceControlResourceState, SourceControl, SourceControlResourceGroup, TextDocumentShowOptions, ViewColumn } from "vscode";
import { Ref, RefType, Hg, Commit, HgError, HgErrorCodes, PushOptions, IMergeResult, LogEntryOptions, IFileStatus, CommitDetails, Revision, SyncOptions, Bookmark } from "./hg";
import { Ref, RefType, ShelveOptions, Hg, Commit, HgError, HgErrorCodes, PushOptions, IMergeResult, LogEntryOptions, IFileStatus, CommitDetails, Revision, SyncOptions, Bookmark } from "./hg";
import { Model } from "./model";
import { Resource, Status, CommitOptions, CommitScope, MergeStatus, LogEntriesOptions, Repository } from "./repository"
import * as path from 'path';
Expand Down Expand Up @@ -872,6 +872,51 @@ export class CommandCenter {
}
}

@command('hg.shelve', { repository: true })
public async shelve(repository: Repository) {
const options: ShelveOptions = {}
const shelveName = await interaction.inputShelveName();
if (shelveName) {
options.name = shelveName;
}
return repository.shelve(options);
}

@command('hg.unshelve', { repository: true })
public async unshelve(repository: Repository) {
const shelves = await repository.getShelves();
const shelve = await interaction.pickShelve(shelves);

if (!shelve) {
return;
}

const opts = { name: shelve.name };
await repository.unshelve(opts);
}

@command('hg.unshelveKeep', { repository: true })
public async unshelveKeep(repository: Repository) {
const shelves = await repository.getShelves();
const shelve = await interaction.pickShelve(shelves);

if (!shelve) {
return;
}
const opts = { name: shelve.name, keep: true };
await repository.unshelve(opts);
}

@command('hg.unshelveAbort', { repository: true })
public async unshelveAbort(repository: Repository) {
await repository.unshelveAbort();
}

@command('hg.unshelveContinue', { repository: true })
public async unshelveContinue(repository: Repository) {
await repository.unshelveContinue();
}

private async doMerge(repository: Repository, otherRevision: string, otherBranchName?: string) {
try {
const result = await repository.merge(otherRevision);
Expand Down
62 changes: 61 additions & 1 deletion src/hg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export interface SyncOptions {
revs?: string[];
}

export interface ShelveOptions {
name?: string
}

export interface UnshelveOptions {
name: string;
keep?: boolean;
}

export interface IMergeResult {
unresolvedCount: number;
}
Expand Down Expand Up @@ -88,6 +97,10 @@ export interface Bookmark extends Ref {
active: boolean;
}

export interface Shelve {
name: string;
}

export interface Path {
name: string;
url: string;
Expand Down Expand Up @@ -342,7 +355,9 @@ export const HgErrorCodes = {
BranchAlreadyExists: 'BranchAlreadyExists',
NoRollbackInformationAvailable: 'NoRollbackInformationAvailable',
UntrackedFilesDiffer: 'UntrackedFilesDiffer',
DefaultRepositoryNotConfigured: 'DefaultRepositoryNotConfigured'
DefaultRepositoryNotConfigured: 'DefaultRepositoryNotConfigured',
UnshelveInProgress: 'UnshelveInProgress',
ShelveConflict: 'ShelveConflict'
};

export class Hg {
Expand Down Expand Up @@ -831,6 +846,51 @@ export class Repository {
}
}

async shelve(opts: ShelveOptions) {
const args = ['shelve']
if (opts.name) {
args.push('--name', opts.name);
}

const result = this.run(args);
}

async getShelves(): Promise<Shelve[]> {
const result = await this.run(['shelve', '--list', '--quiet']);
const shelves = result.stdout.trim().split('\n')
.filter(l => !!l)
.map(line => ({ name: line }));
return shelves;
}

async unshelve(opts: UnshelveOptions) {
const args = ['unshelve', '--name', opts.name];
if (opts.keep) {
args.push('--keep');
}

try {
const result = await this.run(args);
} catch (err) {
if (/unresolved conflicts/.test(err.stderr || '')) {
err.hgErrorCode = HgErrorCodes.ShelveConflict;
}
else if (/abort: unshelve already in progress/.test(err.stderr || '')) {
err.hgErrorCode = HgErrorCodes.UnshelveInProgress;
}

throw err;
}
}

async unshelveAbort(): Promise<void> {
await this.run(['unshelve', '--abort']);
}

async unshelveContinue(): Promise<void> {
await this.run(['unshelve', '--continue']);
}

async tryGetLastCommitDetails(): Promise<ICommitDetails> {
try {
return {
Expand Down
48 changes: 43 additions & 5 deletions src/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

import * as nls from "vscode-nls";
import * as path from "path";
import { commands, window, QuickPickItem, workspace, Uri, WorkspaceFolder } from "vscode";
import { commands, window, QuickPickItem, workspace, Uri, MessageOptions, OutputChannel, WorkspaceFolder } from "vscode";
import { ChildProcess } from "child_process";
import { Model } from "./model";
import { HgRollbackDetails, Path, Ref, RefType, Commit, HgError, LogEntryOptions, CommitDetails, IFileStatus, Bookmark } from "./hg";
import { HgRollbackDetails, Path, Ref, RefType, Commit, Shelve, LogEntryOptions, CommitDetails, IFileStatus, Bookmark, HgErrorCodes } from "./hg";
import { humanise } from "./humanise";
import * as fs from 'fs';
import * as os from "os";
Expand Down Expand Up @@ -193,12 +193,29 @@ export namespace interaction {
}

export async function errorPromptOpenLog(err: any): Promise<boolean> {
const options: MessageOptions = {
modal: true
};

let message: string;
let type: 'error' | 'warning' = 'error';

const openOutputChannelChoice = localize('open hg log', "Open Hg Log");

switch (err.hgErrorCode) {
case 'DirtyWorkingDirectory':
case HgErrorCodes.DirtyWorkingDirectory:
message = localize('clean repo', "Please clean your repository working directory before updating.");
break;
case HgErrorCodes.ShelveConflict:
// TODO: Show "Abort" button
message = localize('shelve merge conflicts', "There were merge conflicts while unshelving.");
type = 'warning';
options.modal = false;
break;
case HgErrorCodes.UnshelveInProgress:
message = localize('unshelve in progress', "There is already an unshelve operation in progress.");
options.modal = false;
break;

default:
const hint = (err.stderr || err.message || String(err))
Expand All @@ -220,8 +237,10 @@ export namespace interaction {
return false;
}

const openOutputChannelChoice = localize('open hg log', "Open Hg Log");
const choice = await window.showErrorMessage(message, openOutputChannelChoice);
const choice = type === 'error'
? await window.showErrorMessage(message, options, openOutputChannelChoice)
: await window.showWarningMessage(message, options, openOutputChannelChoice);

return choice === openOutputChannelChoice;
}

Expand Down Expand Up @@ -257,6 +276,25 @@ export namespace interaction {
return bookmark;
}

export async function inputShelveName(): Promise<string | undefined> {
return await window.showInputBox({
prompt: localize('shelve name', "Optionally provide a shelve name."),
ignoreFocusOut: true,
})
}

export async function pickShelve(shelves: Shelve[]): Promise<Shelve | undefined> {
if (shelves.length === 0) {
window.showInformationMessage(localize('no shelves', "There are no shelves in the repository."));
return;
}

const placeHolder = localize('pick shelve to apply', "Pick a shelve to apply");
const picks = shelves.map(shelve => ({ label: `${shelve.name}`, description: '', details: '', shelve }));
const result = await window.showQuickPick(picks, { placeHolder });
return result && result.shelve;
}

export async function warnNotUsingBookmarks(): Promise<boolean> {
const message = localize('offer bookmarks', "Bookmarks requires the hg.useBookmarks setting to be enabled.")
const useBookmarks = localize("use bookmarks", "Use Bookmarks (workspace)");
Expand Down
25 changes: 24 additions & 1 deletion src/repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, commands } from 'vscode';
import { Repository as BaseRepository, Ref, Commit, RefType, HgError, Bookmark, IRepoStatus, SyncOptions, PullOptions, PushOptions, HgErrorCodes, IMergeResult, CommitDetails, LogEntryRepositoryOptions, HgRollbackDetails } from './hg';
import { Repository as BaseRepository, Ref, Commit, Shelve, HgError, Bookmark, IRepoStatus, SyncOptions, PullOptions, PushOptions, HgErrorCodes, IMergeResult, CommitDetails, LogEntryRepositoryOptions, HgRollbackDetails, ShelveOptions, UnshelveOptions, RefType } from './hg';
import { anyEvent, filterEvent, eventToPromise, dispose, IDisposable, delay, groupBy, partition } from './util';
import { memoize, throttle, debounce } from './decorators';
import { StatusBarCommands } from './statusbar';
Expand Down Expand Up @@ -200,6 +200,9 @@ export const enum Operation {
AddRemove = 1 << 22,
SetBookmark = 1 << 23,
RemoveBookmark = 1 << 24,
Shelve = 1 << 25,
UnshelveContinue = 1 << 26,
UnshelveAbort = 1 << 27,
}

function isReadOnly(operation: Operation): boolean {
Expand Down Expand Up @@ -958,6 +961,26 @@ export class Repository implements IDisposable {
return true;
}

async shelve(options: ShelveOptions) {
return await this.run(Operation.Shelve, () => this.repository.shelve(options));
}

async unshelve(options: UnshelveOptions) {
return await this.run(Operation.Shelve, () => this.repository.unshelve(options));
}

async unshelveAbort(): Promise<void> {
await this.run(Operation.UnshelveAbort, async () => await this.repository.unshelveAbort());
}

async unshelveContinue(): Promise<void> {
await this.run(Operation.UnshelveContinue, async () => await this.repository.unshelveContinue());
}

async getShelves() {
return await this.repository.getShelves();
}

async show(ref: string, filePath: string): Promise<string> {
// TODO@Joao: should we make this a general concept?
await this.whenIdleAndFocused();
Expand Down

0 comments on commit 07c363f

Please sign in to comment.