Parcourir la source

Adds rebase status (wip)

main
Eric Amodio il y a 3 ans
Parent
révision
14d45f402a
19 fichiers modifiés avec 850 ajouts et 352 suppressions
  1. +5
    -4
      src/git/formatters/commitFormatter.ts
  2. +123
    -26
      src/git/gitService.ts
  3. +2
    -1
      src/git/models/branch.ts
  4. +5
    -4
      src/git/models/merge.ts
  5. +1
    -0
      src/git/models/models.ts
  6. +15
    -0
      src/git/models/rebase.ts
  7. +1
    -0
      src/git/models/status.ts
  8. +2
    -2
      src/views/commitsView.ts
  9. +4
    -0
      src/views/nodes.ts
  10. +84
    -53
      src/views/nodes/branchNode.ts
  11. +9
    -5
      src/views/nodes/fileRevisionAsCommitNode.ts
  12. +100
    -0
      src/views/nodes/mergeConflictCurrentChangesNode.ts
  13. +126
    -0
      src/views/nodes/mergeConflictFileNode.ts
  14. +113
    -0
      src/views/nodes/mergeConflictIncomingChangesNode.ts
  15. +32
    -247
      src/views/nodes/mergeStatusNode.ts
  16. +210
    -0
      src/views/nodes/rebaseStatusNode.ts
  17. +14
    -7
      src/views/nodes/repositoryNode.ts
  18. +3
    -2
      src/views/nodes/viewNode.ts
  19. +1
    -1
      src/views/viewCommands.ts

+ 5
- 4
src/git/formatters/commitFormatter.ts Voir le fichier

@ -33,6 +33,7 @@ const emptyStr = '';
export interface CommitFormatOptions extends FormatOptions {
autolinkedIssuesOrPullRequests?: Map<string, IssueOrPullRequest | Promises.CancellationError | undefined>;
avatarSize?: number;
dateStyle?: DateStyle;
footnotes?: Map<number, string>;
getBranchAndTagTips?: (sha: string) => string | undefined;
@ -210,7 +211,7 @@ export class CommitFormatter extends Formatter {
presence.status === 'dnd' ? 'in ' : emptyStr
}${presence.statusText.toLocaleLowerCase()}`;
const avatarMarkdownPromise = this._getAvatarMarkdown(title);
const avatarMarkdownPromise = this._getAvatarMarkdown(title, this._options.avatarSize);
return avatarMarkdownPromise.then(md =>
this._padOrTruncate(
`${md}${this._getPresenceMarkdown(presence, title)}`,
@ -219,11 +220,11 @@ export class CommitFormatter extends Formatter {
);
}
return this._getAvatarMarkdown(this._item.author);
return this._getAvatarMarkdown(this._item.author, this._options.avatarSize);
}
private async _getAvatarMarkdown(title: string) {
const size = Container.config.hovers.avatarSize;
private async _getAvatarMarkdown(title: string, size?: number) {
size = size ?="o">? Container.config.hovers.avatarSize;
const avatarPromise = this._item.getAvatarUri({
defaultStyle: Container.config.defaultGravatarsStyle,
size: size,

+ 123
- 26
src/git/gitService.ts Voir le fichier

@ -1,6 +1,7 @@
'use strict';
import * as fs from 'fs';
import * as paths from 'path';
import { TextDecoder } from 'util';
import {
ConfigurationChangeEvent,
Disposable,
@ -51,6 +52,7 @@ import {
GitLogCommit,
GitLogParser,
GitMergeStatus,
GitRebaseStatus,
GitReference,
GitReflog,
GitRemote,
@ -119,6 +121,8 @@ const weightedDefaultBranches = new Map([
['development', 1],
]);
const textDecoder = new TextDecoder('utf8');
export class GitService implements Disposable {
private _onDidChangeRepositories = new EventEmitter<void>();
get onDidChangeRepositories(): Event<void> {
@ -1139,16 +1143,23 @@ export class GitService implements Disposable {
const [name, tracking] = data[0].split('\n');
if (GitBranch.isDetached(name)) {
const committerDate = await Git.log__recent_committerdate(repoPath);
const [rebaseStatus, committerDate] = await Promise.all([
this.getRebaseStatus(repoPath),
Git.log__recent_committerdate(repoPath),
]);
branch = new GitBranch(
repoPath,
name,
rebaseStatus?.incoming.name ?? name,
false,
true,
committerDate == null ? undefined : new Date(Number(committerDate) * 1000),
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
data[1],
tracking,
undefined,
undefined,
undefined,
rebaseStatus != null,
);
}
@ -1214,17 +1225,24 @@ export class GitService implements Disposable {
const data = await Git.rev_parse__currentBranch(repoPath);
if (data != null) {
const committerDate = await Git.log__recent_committerdate(repoPath);
const [name, tracking] = data[0].split('\n');
const [rebaseStatus, committerDate] = await Promise.all([
GitBranch.isDetached(name) ? this.getRebaseStatus(repoPath) : undefined,
Git.log__recent_committerdate(repoPath),
]);
current = new GitBranch(
repoPath,
name,
rebaseStatus?.incoming.name ?? name,
false,
true,
committerDate == null ? undefined : new Date(Number(committerDate) * 1000),
committerDate != null ? new Date(Number(committerDate) * 1000) : undefined,
data[1],
tracking,
undefined,
undefined,
undefined,
rebaseStatus != null,
);
}
@ -1426,7 +1444,7 @@ export class GitService implements Disposable {
const shortlog = GitShortLogParser.parse(data, repoPath);
if (shortlog != null) {
// Mark the current user
const currentUser = await Container.git.getCurrentUser(repoPath);
const currentUser = await this.getCurrentUser(repoPath);
if (currentUser != null) {
const index = shortlog.contributors.findIndex(
c => currentUser.email === c.email && currentUser.name === c.name,
@ -2379,28 +2397,92 @@ export class GitService implements Disposable {
}
}
async getMergeStatus(repoPath: string): Promise<GitMergeStatus | undefined>;
async getMergeStatus(status: GitStatus): Promise<GitMergeStatus | undefined>;
@log({ args: false })
async getMergeStatus(repoPathOrStatus: string | GitStatus): Promise<GitMergeStatus | undefined> {
let status;
if (typeof repoPathOrStatus === 'string') {
status = await this.getStatusForRepo(repoPathOrStatus);
} else {
status = repoPathOrStatus;
}
if (status?.hasConflicts !== true) return undefined;
@log()
async getMergeStatus(repoPath: string): Promise<GitMergeStatus | undefined> {
const merge = await Git.rev_parse__verify(repoPath, 'MERGE_HEAD');
if (merge == null) return undefined;
const [mergeBase, possibleSourceBranches] = await Promise.all([
Container.git.getMergeBase(status.repoPath, 'MERGE_HEAD', 'HEAD'),
Container.git.getCommitBranches(status.repoPath, 'MERGE_HEAD', { mode: 'pointsAt' }),
const [branch, mergeBase, possibleSourceBranches] = await Promise.all([
this.getBranch(repoPath),
this.getMergeBase(repoPath, 'MERGE_HEAD', 'HEAD'),
this.getCommitBranches(repoPath, 'MERGE_HEAD', { mode: 'pointsAt' }),
]);
return {
repoPath: status.repoPath,
type: 'merge',
repoPath: repoPath,
mergeBase: mergeBase,
conflicts: status.files.filter(f => f.conflicted),
into: status.branch,
incoming: possibleSourceBranches?.length === 1 ? possibleSourceBranches[0] : undefined,
HEAD: GitReference.create(merge, repoPath, { refType: 'revision' }),
current: GitReference.fromBranch(branch!),
incoming:
possibleSourceBranches?.length === 1
? GitReference.create(possibleSourceBranches[0], repoPath, {
refType: 'branch',
name: possibleSourceBranches[0],
remote: false,
})
: undefined,
};
}
@log()
async getRebaseStatus(repoPath: string): Promise<GitRebaseStatus | undefined> {
const rebase = await Git.rev_parse__verify(repoPath, 'REBASE_HEAD');
if (rebase == null) return undefined;
const [mergeBase, headNameBytes, ontoBytes, stepBytes, stepMessageBytes, stepsBytes] = await Promise.all([
this.getMergeBase(repoPath, 'REBASE_HEAD', 'HEAD'),
workspace.fs.readFile(Uri.file(paths.join(repoPath, '.git', 'rebase-merge', 'head-name'))),
workspace.fs.readFile(Uri.file(paths.join(repoPath, '.git', 'rebase-merge', 'onto'))),
workspace.fs.readFile(Uri.file(paths.join(repoPath, '.git', 'rebase-merge', 'msgnum'))),
workspace.fs.readFile(Uri.file(paths.join(repoPath, '.git', 'rebase-merge', 'message'))),
workspace.fs.readFile(Uri.file(paths.join(repoPath, '.git', 'rebase-merge', 'end'))),
]);
let branch = textDecoder.decode(headNameBytes);
if (branch.startsWith('refs/heads/')) {
branch = branch.substr(11).trim();
}
const onto = textDecoder.decode(ontoBytes).trim();
const step = Number.parseInt(textDecoder.decode(stepBytes).trim(), 10);
const steps = Number.parseInt(textDecoder.decode(stepsBytes).trim(), 10);
const possibleSourceBranches = await this.getCommitBranches(repoPath, onto, { mode: 'pointsAt' });
let possibleSourceBranch: string | undefined;
for (const b of possibleSourceBranches) {
if (b.startsWith('(no branch, rebasing')) continue;
possibleSourceBranch = b;
break;
}
return {
type: 'rebase',
repoPath: repoPath,
mergeBase: mergeBase,
HEAD: GitReference.create(rebase, repoPath, { refType: 'revision' }),
current:
possibleSourceBranch != null
? GitReference.create(possibleSourceBranch, repoPath, {
refType: 'branch',
name: possibleSourceBranch,
remote: false,
})
: undefined,
incoming: GitReference.create(branch, repoPath, {
refType: 'branch',
name: branch,
remote: false,
}),
step: step,
stepCurrent: GitReference.create(rebase, repoPath, {
refType: 'revision',
message: textDecoder.decode(stepMessageBytes).trim(),
}),
steps: steps,
};
}
@ -3338,6 +3420,21 @@ export class GitService implements Disposable {
similarityThreshold: Container.config.advanced.similarityThreshold,
});
const status = GitStatusParser.parse(data, repoPath, porcelainVersion);
if (status?.detached) {
const rebaseStatus = await this.getRebaseStatus(repoPath);
if (rebaseStatus != null) {
return new GitStatus(
repoPath,
rebaseStatus.incoming.name,
status.sha,
status.files,
status.state,
status.upstream,
true,
);
}
}
return status;
}

+ 2
- 1
src/git/models/branch.ts Voir le fichier

@ -8,7 +8,7 @@ import { GitStatus } from './status';
import { Dates, debug, memoize } from '../../system';
const whitespaceRegex = /\s/;
const detachedHEADRegex = /^(?=.*\bHEAD\b)(?=.*\bdetached\b).*$/;
const detachedHEADRegex = /^(?=.*\bHEAD\b)?(?=.*\bdetached\b).*$/;
export const BranchDateFormatting = {
dateFormat: undefined! as string | null,
@ -96,6 +96,7 @@ export class GitBranch implements GitBranchReference {
ahead: number = 0,
behind: number = 0,
detached: boolean = false,
public readonly rebasing: boolean = false,
) {
this.id = `${repoPath}|${remote ? 'remotes/' : 'heads/'}${name}`;

+ 5
- 4
src/git/models/merge.ts Voir le fichier

@ -1,10 +1,11 @@
'use strict';
import { GitStatusFile } from './status';
import { GitBranchReference, GitRevisionReference } from './models';
export interface GitMergeStatus {
type: 'merge';
repoPath: string;
into: string;
HEAD: GitRevisionReference;
mergeBase: string | undefined;
incoming: string | undefined;
conflicts: GitStatusFile[];
current: GitBranchReference;
incoming: GitBranchReference | undefined;
}

+ 1
- 0
src/git/models/models.ts Voir le fichier

@ -351,6 +351,7 @@ export * from './log';
export * from './logCommit';
export * from './merge';
export * from './pullRequest';
export * from './rebase';
export * from './reflog';
export * from './remote';
export * from './repository';

+ 15
- 0
src/git/models/rebase.ts Voir le fichier

@ -0,0 +1,15 @@
'use strict';
import { GitBranchReference, GitRevisionReference } from './models';
export interface GitRebaseStatus {
type: 'rebase';
repoPath: string;
HEAD: GitRevisionReference;
mergeBase: string | undefined;
current: GitBranchReference | undefined;
incoming: GitBranchReference;
step: number;
stepCurrent: GitRevisionReference;
steps: number;
}

+ 1
- 0
src/git/models/status.ts Voir le fichier

@ -32,6 +32,7 @@ export class GitStatus {
public readonly files: GitStatusFile[],
public readonly state: GitTrackingState,
public readonly upstream?: string,
public readonly rebasing: boolean = false,
) {
this.detached = GitBranch.isDetached(branch);
if (this.detached) {

+ 2
- 2
src/views/commitsView.ts Voir le fichier

@ -217,8 +217,8 @@ export class CommitsViewNode extends ViewNode {
const status = branch.getTrackingStatus();
this.view.description = `${status ? `${status} ${GlyphChars.Dot} ` : ''}${branch.name}${
lastFetched ? ` ${GlyphChars.Dot} Last fetched ${Repository.formatLastFetched(lastFetched)}` : ''
}`;
branch.rebasing ? ' (Rebasing)' : ''
}${lastFetched ? ` ${GlyphChars.Dot} Last fetched ${Repository.formatLastFetched(lastFetched)}` : ''}`;
} else {
this.view.description = undefined;
}

+ 4
- 0
src/views/nodes.ts Voir le fichier

@ -21,8 +21,12 @@ export * from './nodes/fileRevisionAsCommitNode';
export * from './nodes/folderNode';
export * from './nodes/lineHistoryNode';
export * from './nodes/lineHistoryTrackerNode';
export * from './nodes/mergeConflictFileNode';
export * from './nodes/mergeConflictCurrentChangesNode';
export * from './nodes/mergeConflictIncomingChangesNode';
export * from './nodes/mergeStatusNode';
export * from './nodes/pullRequestNode';
export * from './nodes/rebaseStatusNode';
export * from './nodes/reflogNode';
export * from './nodes/reflogRecordNode';
export * from './nodes/remoteNode';

+ 84
- 53
src/views/nodes/branchNode.ts Voir le fichier

@ -22,6 +22,7 @@ import { GitUri } from '../../git/gitUri';
import { insertDateMarkers } from './helpers';
import { MergeStatusNode } from './mergeStatusNode';
import { PullRequestNode } from './pullRequestNode';
import { RebaseStatusNode } from './rebaseStatusNode';
import { RemotesView } from '../remotesView';
import { RepositoriesView } from '../repositoriesView';
import { RepositoryNode } from './repositoryNode';
@ -42,6 +43,7 @@ export class BranchNode
showAsCommits: boolean;
showComparison: false | ViewShowBranchComparison;
showCurrent: boolean;
showStatus: boolean;
showTracking: boolean;
authors?: string[];
};
@ -60,6 +62,7 @@ export class BranchNode
showAsCommits?: boolean;
showComparison?: false | ViewShowBranchComparison;
showCurrent?: boolean;
showStatus?: boolean;
showTracking?: boolean;
authors?: string[];
},
@ -70,9 +73,11 @@ export class BranchNode
expanded: false,
showAsCommits: false,
showComparison: false,
// Hide the current branch checkmark when the node is displayed as a root under the repository node
// Hide the current branch checkmark when the node is displayed as a root
showCurrent: !this.root,
// Don't show tracking info the node is displayed as a root under the repository node
// Don't show merge/rebase status info the node is displayed as a root
showStatus: true, //!this.root,
// Don't show tracking info the node is displayed as a root
showTracking: !this.root,
...options,
};
@ -96,14 +101,16 @@ export class BranchNode
if (this.options.showAsCommits) return 'Commits';
const branchName = this.branch.getNameWithoutRemote();
return this.view.config.branches?.layout !== ViewBranchesLayout.Tree ||
return `${
this.view.config.branches?.layout !== ViewBranchesLayout.Tree ||
this.compacted ||
this.root ||
this.current ||
this.branch.detached ||
this.branch.starred
? branchName
: this.branch.getBasename();
? branchName
: this.branch.getBasename()
}${this.branch.rebasing ? ' (Rebasing)' : ''}`;
}
get ref(): GitBranchReference {
@ -121,12 +128,24 @@ export class BranchNode
const children = [];
const range = await Container.git.getBranchAheadRange(this.branch);
const [log, getBranchAndTagTips, mergeStatus, pr, unpublishedCommits] = await Promise.all([
const [
log,
getBranchAndTagTips,
status,
mergeStatus,
rebaseStatus,
pr,
unpublishedCommits,
] = await Promise.all([
this.getLog(),
Container.git.getBranchesAndTagsTipsFn(this.uri.repoPath, this.branch.name),
this.options.showTracking && this.branch.current
this.options.showStatus && this.branch.current
? Container.git.getStatusForRepo(this.uri.repoPath)
: undefined,
this.options.showStatus && this.branch.current
? Container.git.getMergeStatus(this.uri.repoPath!)
: undefined,
this.options.showStatus ? Container.git.getRebaseStatus(this.uri.repoPath!) : undefined,
this.view.config.pullRequests.enabled &&
this.view.config.pullRequests.showForBranches &&
(this.branch.tracking || this.branch.remote)
@ -158,54 +177,62 @@ export class BranchNode
children.push(new PullRequestNode(this.view, this, pr, this.branch));
}
if (this.options.showTracking) {
if (mergeStatus != null) {
children.push(new MergeStatusNode(this.view, this, this.branch, mergeStatus));
} else {
const status = {
ref: this.branch.ref,
repoPath: this.branch.repoPath,
state: this.branch.state,
upstream: this.branch.tracking,
};
if (this.branch.tracking) {
if (this.root && !status.state.behind && !status.state.ahead) {
if (this.options.showStatus && mergeStatus != null) {
children.push(
new MergeStatusNode(
this.view,
this,
this.branch,
mergeStatus,
status ?? (await Container.git.getStatusForRepo(this.uri.repoPath)),
this.root,
),
);
} else if (
this.options.showStatus &&
rebaseStatus != null &&
(this.branch.current || this.branch.name === rebaseStatus.incoming.name)
) {
children.push(
new RebaseStatusNode(
this.view,
this,
this.branch,
rebaseStatus,
status ?? (await Container.git.getStatusForRepo(this.uri.repoPath)),
this.root,
),
);
} else if (this.options.showTracking) {
const status = {
ref: this.branch.ref,
repoPath: this.branch.repoPath,
state: this.branch.state,
upstream: this.branch.tracking,
};
if (this.branch.tracking) {
if (this.root && !status.state.behind && !status.state.ahead) {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'same', this.root),
);
} else {
if (status.state.behind) {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'same', this.root),
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'behind', this.root),
);
}
if (status.state.ahead) {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'ahead', this.root),
);
} else {
if (status.state.behind) {
children.push(
new BranchTrackingStatusNode(
this.view,
this,
this.branch,
status,
'behind',
this.root,
),
);
}
if (status.state.ahead) {
children.push(
new BranchTrackingStatusNode(
this.view,
this,
this.branch,
status,
'ahead',
this.root,
),
);
}
}
} else {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'none', this.root),
);
}
} else {
children.push(
new BranchTrackingStatusNode(this.view, this, this.branch, status, 'none', this.root),
);
}
}
@ -249,7 +276,7 @@ export class BranchNode
let tooltip: string | MarkdownString = `${
this.current ? 'Current branch' : 'Branch'
} $(git-branch) ${this.branch.getNameWithoutRemote()}`;
} $(git-branch) ${this.branch.getNameWithoutRemote()}${this.branch.rebasing ? ' (Rebasing)' : ''}`;
let contextValue: string = ContextValues.Branch;
if (this.current) {
@ -303,7 +330,11 @@ export class BranchNode
description = this.options.showAsCommits
? `${this.branch.getTrackingStatus({
suffix: Strings.pad(GlyphChars.Dot, 1, 1),
})}${this.branch.getNameWithoutRemote()}${Strings.pad(arrows, 2, 2)}${this.branch.tracking}`
})}${this.branch.getNameWithoutRemote()}${this.branch.rebasing ? ' (Rebasing)' : ''}${Strings.pad(
arrows,
2,
2,
)}${this.branch.tracking}`
: `${this.branch.getTrackingStatus({ suffix: `${GlyphChars.Space} ` })}${arrows}${
GlyphChars.Space
} ${this.branch.tracking}`;

+ 9
- 5
src/views/nodes/fileRevisionAsCommitNode.ts Voir le fichier

@ -15,7 +15,8 @@ import {
} from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { LineHistoryView } from '../lineHistoryView';
import { MergeConflictCurrentChangesNode, MergeConflictIncomingChangesNode } from './mergeStatusNode';
import { MergeConflictCurrentChangesNode } from './mergeConflictCurrentChangesNode';
import { MergeConflictIncomingChangesNode } from './mergeConflictIncomingChangesNode';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode, ViewRefFileNode } from './viewNode';
@ -55,12 +56,15 @@ export class FileRevisionAsCommitNode extends ViewRefFileNode
async getChildren(): Promise<ViewNode[]> {
if (!this.commit.hasConflicts) return [];
const mergeStatus = await Container.git.getMergeStatus(this.commit.repoPath);
if (mergeStatus == null) return [];
const [mergeStatus, rebaseStatus] = await Promise.all([
Container.git.getMergeStatus(this.commit.repoPath),
Container.git.getRebaseStatus(this.commit.repoPath),
]);
if (mergeStatus == null && rebaseStatus == null) return [];
return [
new MergeConflictCurrentChangesNode(this.view, this, mergeStatus, this.file),
new MergeConflictIncomingChangesNode(this.view, this, mergeStatus, this.file),
new MergeConflictCurrentChangesNode(this.view, this, (mergeStatus ?? rebaseStatus)!, this.file),
new MergeConflictIncomingChangesNode(this.view, this, (mergeStatus ?? rebaseStatus)!, this.file),
];
}

+ 100
- 0
src/views/nodes/mergeConflictCurrentChangesNode.ts Voir le fichier

@ -0,0 +1,100 @@
'use strict';
import { Command, MarkdownString, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Commands, DiffWithCommandArgs } from '../../commands';
import { BuiltInCommands, GlyphChars } from '../../constants';
import { Container } from '../../container';
import { FileHistoryView } from '../fileHistoryView';
import { CommitFormatter, GitFile, GitMergeStatus, GitRebaseStatus, GitReference } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { LineHistoryView } from '../lineHistoryView';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode } from './viewNode';
export class MergeConflictCurrentChangesNode extends ViewNode<ViewsWithCommits | FileHistoryView | LineHistoryView> {
constructor(
view: ViewsWithCommits | FileHistoryView | LineHistoryView,
parent: ViewNode,
private readonly status: GitMergeStatus | GitRebaseStatus,
private readonly file: GitFile,
) {
super(GitUri.fromFile(file, status.repoPath, 'HEAD'), view, parent);
}
getChildren(): ViewNode[] {
return [];
}
async getTreeItem(): Promise<TreeItem> {
const commit = await Container.git.getCommit(this.status.repoPath, 'HEAD');
const item = new TreeItem('Current changes', TreeItemCollapsibleState.None);
item.contextValue = ContextValues.MergeConflictCurrentChanges;
item.description = `${GitReference.toString(this.status.current, { expand: false, icon: false })}${
commit != null ? ` (${GitReference.toString(commit, { expand: false, icon: false })})` : ' (HEAD)'
}`;
item.iconPath = this.view.config.avatars
? (await commit?.getAvatarUri({ defaultStyle: Container.config.defaultGravatarsStyle })) ??
new ThemeIcon('diff')
: new ThemeIcon('diff');
item.tooltip = new MarkdownString(
`Current changes to $(file)${GlyphChars.Space}${this.file.fileName} on ${GitReference.toString(
this.status.current,
)}${
commit != null
? `\n\n${await CommitFormatter.fromTemplateAsync(
`$(git-commit)&nbsp;\${id} ${GlyphChars.Dash} \${avatar}&nbsp;__\${author}__, \${ago}\${' via 'pullRequest} &nbsp; _(\${date})_ \n\n\${message}`,
commit,
{
avatarSize: 16,
dateFormat: Container.config.defaultDateFormat,
markdown: true,
// messageAutolinks: true,
messageIndent: 4,
},
)}`
: ''
}`,
true,
);
item.command = this.getCommand();
return item;
}
getCommand(): Command | undefined {
if (this.status.mergeBase == null) {
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri('HEAD', this.file.fileName, this.status.repoPath)],
};
}
const commandArgs: DiffWithCommandArgs = {
lhs: {
sha: this.status.mergeBase,
uri: GitUri.fromFile(this.file, this.status.repoPath, undefined, true),
title: `${this.file.fileName} (merge-base)`,
},
rhs: {
sha: 'HEAD',
uri: GitUri.fromFile(this.file, this.status.repoPath),
title: `${this.file.fileName} (${GitReference.toString(this.status.current, {
expand: false,
icon: false,
})})`,
},
repoPath: this.status.repoPath,
line: 0,
showOptions: {
preserveFocus: true,
preview: true,
},
};
return {
title: 'Open Changes',
command: Commands.DiffWith,
arguments: [commandArgs],
};
}
}

+ 126
- 0
src/views/nodes/mergeConflictFileNode.ts Voir le fichier

@ -0,0 +1,126 @@
'use strict';
import * as paths from 'path';
import { Command, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { BuiltInCommands } from '../../constants';
import { FileNode } from './folderNode';
import { GitFile, GitMergeStatus, GitRebaseStatus, StatusFileFormatter } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { MergeConflictCurrentChangesNode } from './mergeConflictCurrentChangesNode';
import { MergeConflictIncomingChangesNode } from './mergeConflictIncomingChangesNode';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode } from './viewNode';
export class MergeConflictFileNode extends ViewNode<ViewsWithCommits> implements FileNode {
constructor(
view: ViewsWithCommits,
parent: ViewNode,
public readonly status: GitMergeStatus | GitRebaseStatus,
public readonly file: GitFile,
) {
super(GitUri.fromFile(file, status.repoPath, status.HEAD.ref), view, parent);
}
toClipboard(): string {
return this.fileName;
}
get baseUri(): Uri {
return GitUri.fromFile(this.file, this.status.repoPath, this.status.mergeBase ?? 'HEAD');
}
get fileName(): string {
return this.file.fileName;
}
get repoPath(): string {
return this.status.repoPath;
}
getChildren(): ViewNode[] {
return [
new MergeConflictCurrentChangesNode(this.view, this, this.status, this.file),
new MergeConflictIncomingChangesNode(this.view, this, this.status, this.file),
];
}
getTreeItem(): TreeItem {
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.description = this.description;
item.contextValue = `${ContextValues.File}+conflicted`;
item.tooltip = StatusFileFormatter.fromTemplate(
// eslint-disable-next-line no-template-curly-in-string
'${file}\n${directory}/\n\n${status}${ (originalPath)} in Index (staged)',
this.file,
);
// Use the file icon and decorations
item.resourceUri = GitUri.resolveToUri(this.file.fileName, this.repoPath);
item.iconPath = ThemeIcon.File;
item.command = this.getCommand();
// Only cache the label/description for a single refresh
this._label = undefined;
this._description = undefined;
return item;
}
private _description: string | undefined;
get description() {
if (this._description == null) {
this._description = StatusFileFormatter.fromTemplate(
this.view.config.formats.files.description,
this.file,
{
relativePath: this.relativePath,
},
);
}
return this._description;
}
private _folderName: string | undefined;
get folderName() {
if (this._folderName == null) {
this._folderName = paths.dirname(this.uri.relativePath);
}
return this._folderName;
}
private _label: string | undefined;
get label() {
if (this._label == null) {
this._label = StatusFileFormatter.fromTemplate(this.view.config.formats.files.label, this.file, {
relativePath: this.relativePath,
});
}
return this._label;
}
get priority(): number {
return 0;
}
private _relativePath: string | undefined;
get relativePath(): string | undefined {
return this._relativePath;
}
set relativePath(value: string | undefined) {
this._relativePath = value;
this._label = undefined;
this._description = undefined;
}
getCommand(): Command | undefined {
return {
title: 'Open File',
command: BuiltInCommands.Open,
arguments: [
GitUri.resolveToUri(this.file.fileName, this.repoPath),
{
preserveFocus: true,
preview: true,
},
],
};
}
}

+ 113
- 0
src/views/nodes/mergeConflictIncomingChangesNode.ts Voir le fichier

@ -0,0 +1,113 @@
'use strict';
import { Command, MarkdownString, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Commands, DiffWithCommandArgs } from '../../commands';
import { BuiltInCommands, GlyphChars } from '../../constants';
import { Container } from '../../container';
import { FileHistoryView } from '../fileHistoryView';
import { CommitFormatter, GitFile, GitMergeStatus, GitRebaseStatus, GitReference } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { LineHistoryView } from '../lineHistoryView';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode } from './viewNode';
export class MergeConflictIncomingChangesNode extends ViewNode<ViewsWithCommits | FileHistoryView | LineHistoryView> {
constructor(
view: ViewsWithCommits | FileHistoryView | LineHistoryView,
parent: ViewNode,
private readonly status: GitMergeStatus | GitRebaseStatus,
private readonly file: GitFile,
) {
super(GitUri.fromFile(file, status.repoPath, status.HEAD.ref), view, parent);
}
getChildren(): ViewNode[] {
return [];
}
async getTreeItem(): Promise<TreeItem> {
const commit = await Container.git.getCommit(
this.status.repoPath,
this.status.type === 'rebase' ? this.status.stepCurrent.ref : this.status.HEAD.ref,
);
const item = new TreeItem('Incoming changes', TreeItemCollapsibleState.None);
item.contextValue = ContextValues.MergeConflictIncomingChanges;
item.description = `${GitReference.toString(this.status.incoming, { expand: false, icon: false })}${
this.status.type === 'rebase'
? ` (${GitReference.toString(this.status.stepCurrent, { expand: false, icon: false })})`
: ` (${GitReference.toString(this.status.HEAD, { expand: false, icon: false })})`
}`;
item.iconPath = this.view.config.avatars
? (await commit?.getAvatarUri({ defaultStyle: Container.config.defaultGravatarsStyle })) ??
new ThemeIcon('diff')
: new ThemeIcon('diff');
item.tooltip = new MarkdownString(
`Incoming changes to $(file)${GlyphChars.Space}${this.file.fileName}${
this.status.incoming != null
? ` from ${GitReference.toString(this.status.incoming)}${
commit != null
? `\n\n${await CommitFormatter.fromTemplateAsync(
`$(git-commit)&nbsp;\${id} ${GlyphChars.Dash} \${avatar}&nbsp;__\${author}__, \${ago}\${' via 'pullRequest} &nbsp; _(\${date})_ \n\n\${message}`,
commit,
{
avatarSize: 16,
dateFormat: Container.config.defaultDateFormat,
markdown: true,
// messageAutolinks: true,
messageIndent: 4,
},
)}`
: this.status.type === 'rebase'
? `\n\n${GitReference.toString(this.status.stepCurrent, {
capitalize: true,
label: false,
})}`
: `\n\n${GitReference.toString(this.status.HEAD, { capitalize: true, label: false })}`
}`
: ''
}`,
true,
);
item.command = this.getCommand();
return item;
}
getCommand(): Command | undefined {
if (this.status.mergeBase == null) {
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri(this.status.HEAD.ref, this.file.fileName, this.status.repoPath)],
};
}
const commandArgs: DiffWithCommandArgs = {
lhs: {
sha: this.status.mergeBase,
uri: GitUri.fromFile(this.file, this.status.repoPath, undefined, true),
title: `${this.file.fileName} (merge-base)`,
},
rhs: {
sha: this.status.HEAD.ref,
uri: GitUri.fromFile(this.file, this.status.repoPath),
title: `${this.file.fileName} (${
this.status.incoming != null
? GitReference.toString(this.status.incoming, { expand: false, icon: false })
: 'incoming'
})`,
},
repoPath: this.status.repoPath,
line: 0,
showOptions: {
preserveFocus: true,
preview: true,
},
};
return {
title: 'Open Changes',
command: Commands.DiffWith,
arguments: [commandArgs],
};
}
}

+ 32
- 247
src/views/nodes/mergeStatusNode.ts Voir le fichier

@ -1,34 +1,36 @@
'use strict';
import * as paths from 'path';
import { Command, MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { BranchNode } from './branchNode';
import { Commands, DiffWithCommandArgs } from '../../commands';
import { ViewFilesLayout } from '../../configuration';
import { BuiltInCommands } from '../../constants';
import { FileNode, FolderNode } from './folderNode';
import { GitBranch, GitFile, GitMergeStatus, StatusFileFormatter } from '../../git/git';
import { GitBranch, GitMergeStatus, GitReference, GitStatus } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { MergeConflictFileNode } from './mergeConflictFileNode';
import { Arrays, Strings } from '../../system';
import { View, ViewsWithCommits } from '../viewBase';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode } from './viewNode';
export class MergeStatusNode extends ViewNode<ViewsWithCommits> {
static key = ':merge';
static getId(repoPath: string, name: string): string {
return `${BranchNode.getId(repoPath, name, true)}${this.key}`;
static getId(repoPath: string, name: string, root: boolean): string {
return `${BranchNode.getId(repoPath, name, root)}${this.key}`;
}
constructor(
view: ViewsWithCommits,
parent: ViewNode,
public readonly branch: GitBranch,
public readonly status: GitMergeStatus,
public readonly mergeStatus: GitMergeStatus,
public readonly status: GitStatus | undefined,
// Specifies that the node is shown as a root
public readonly root: boolean,
) {
super(GitUri.fromRepoPath(status.repoPath), view, parent);
super(GitUri.fromRepoPath(mergeStatus.repoPath), view, parent);
}
get id(): string {
return MergeStatusNode.getId(this.status.repoPath, this.status.into);
return MergeStatusNode.getId(this.mergeStatus.repoPath, this.mergeStatus.current.name, this.root);
}
get repoPath(): string {
@ -36,8 +38,10 @@ export class MergeStatusNode extends ViewNode {
}
getChildren(): ViewNode[] {
if (this.status?.hasConflicts !== true) return [];
let children: FileNode[] = this.status.conflicts.map(
f => new MergeConflictFileNode(this.view, this, this.status, f),
f => new MergeConflictFileNode(this.view, this, this.mergeStatus, f),
);
if (this.view.config.files.layout !== ViewFilesLayout.List) {
@ -61,251 +65,32 @@ export class MergeStatusNode extends ViewNode {
getTreeItem(): TreeItem {
const item = new TreeItem(
`Resolve conflicts before merging ${this.status.incoming ? `${this.status.incoming} ` : ''}into ${
this.status.into
}`,
`${this.status?.hasConflicts ? 'Resolve conflicts before merging' : 'Merging'} ${
this.mergeStatus.incoming != null
? `${GitReference.toString(this.mergeStatus.incoming, { expand: false, icon: false })} `
: ''
}into ${GitReference.toString(this.mergeStatus.current, { expand: false, icon: false })}`,
TreeItemCollapsibleState.Expanded,
);
item.id = this.id;
item.contextValue = ContextValues.Merge;
item.description = Strings.pluralize('conflict', this.status.conflicts.length);
item.iconPath = new ThemeIcon('warning', new ThemeColor('list.warningForeground'));
item.description = this.status?.hasConflicts
? Strings.pluralize('conflict', this.status.conflicts.length)
: undefined;
item.iconPath = this.status?.hasConflicts
? new ThemeIcon('warning', new ThemeColor('list.warningForeground'))
: new ThemeIcon('debug-pause', new ThemeColor('list.foreground'));
item.tooltip = new MarkdownString(
`Merging ${this.status.incoming ? `$(git-branch) ${this.status.incoming} ` : ''} into $(git-branch) ${
this.status.into
}\n\n${Strings.pluralize('conflicted file', this.status.conflicts.length)}
`,
true,
);
return item;
}
}
export class MergeConflictFileNode extends ViewNode implements FileNode {
constructor(view: View, parent: ViewNode, private readonly status: GitMergeStatus, public readonly file: GitFile) {
super(GitUri.fromFile(file, status.repoPath, 'MERGE_HEAD'), view, parent);
}
toClipboard(): string {
return this.fileName;
}
get baseUri(): Uri {
return GitUri.fromFile(this.file, this.status.repoPath, this.status.mergeBase ?? 'HEAD');
}
get fileName(): string {
return this.file.fileName;
}
get repoPath(): string {
return this.status.repoPath;
}
getChildren(): ViewNode[] {
return [
new MergeConflictCurrentChangesNode(this.view, this, this.status, this.file),
new MergeConflictIncomingChangesNode(this.view, this, this.status, this.file),
];
}
getTreeItem(): TreeItem {
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.description = this.description;
item.contextValue = `${ContextValues.File}+conflicted`;
item.tooltip = StatusFileFormatter.fromTemplate(
// eslint-disable-next-line no-template-curly-in-string
'${file}\n${directory}/\n\n${status}${ (originalPath)} in Index (staged)',
this.file,
);
// Use the file icon and decorations
item.resourceUri = GitUri.resolveToUri(this.file.fileName, this.repoPath);
item.iconPath = ThemeIcon.File;
item.command = this.getCommand();
// Only cache the label/description for a single refresh
this._label = undefined;
this._description = undefined;
return item;
}
private _description: string | undefined;
get description() {
if (this._description == null) {
this._description = StatusFileFormatter.fromTemplate(
this.view.config.formats.files.description,
this.file,
{
relativePath: this.relativePath,
},
);
}
return this._description;
}
private _folderName: string | undefined;
get folderName() {
if (this._folderName == null) {
this._folderName = paths.dirname(this.uri.relativePath);
}
return this._folderName;
}
private _label: string | undefined;
get label() {
if (this._label == null) {
this._label = StatusFileFormatter.fromTemplate(this.view.config.formats.files.label, this.file, {
relativePath: this.relativePath,
});
}
return this._label;
}
get priority(): number {
return 0;
}
private _relativePath: string | undefined;
get relativePath(): string | undefined {
return this._relativePath;
}
set relativePath(value: string | undefined) {
this._relativePath = value;
this._label = undefined;
this._description = undefined;
}
getCommand(): Command | undefined {
return {
title: 'Open File',
command: BuiltInCommands.Open,
arguments: [
GitUri.resolveToUri(this.file.fileName, this.repoPath),
{
preserveFocus: true,
preview: true,
},
],
};
}
}
export class MergeConflictCurrentChangesNode extends ViewNode {
constructor(view: View, parent: ViewNode, private readonly status: GitMergeStatus, private readonly file: GitFile) {
super(GitUri.fromFile(file, status.repoPath, 'HEAD'), view, parent);
}
getChildren(): ViewNode[] {
return [];
}
getTreeItem(): TreeItem {
const item = new TreeItem('Current changes', TreeItemCollapsibleState.None);
item.contextValue = ContextValues.MergeCurrentChanges;
item.description = this.status.into;
item.iconPath = new ThemeIcon('diff');
item.tooltip = new MarkdownString(
`Current changes to $(file) ${this.file.fileName} on $(git-branch) ${this.status.into}`,
true,
);
item.command = this.getCommand();
return item;
}
getCommand(): Command | undefined {
if (this.status.mergeBase == null) {
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri('HEAD', this.file.fileName, this.status.repoPath)],
};
}
const commandArgs: DiffWithCommandArgs = {
lhs: {
sha: this.status.mergeBase,
uri: GitUri.fromFile(this.file, this.status.repoPath, undefined, true),
title: `${this.file.fileName} (merge-base)`,
},
rhs: {
sha: 'HEAD',
uri: GitUri.fromFile(this.file, this.status.repoPath),
title: `${this.file.fileName} (${this.status.into})`,
},
repoPath: this.status.repoPath,
line: 0,
showOptions: {
preserveFocus: true,
preview: true,
},
};
return {
title: 'Open Changes',
command: Commands.DiffWith,
arguments: [commandArgs],
};
}
}
export class MergeConflictIncomingChangesNode extends ViewNode {
constructor(view: View, parent: ViewNode, private readonly status: GitMergeStatus, private readonly file: GitFile) {
super(GitUri.fromFile(file, status.repoPath, 'MERGE_HEAD'), view, parent);
}
getChildren(): ViewNode[] {
return [];
}
getTreeItem(): TreeItem {
const item = new TreeItem('Incoming changes', TreeItemCollapsibleState.None);
item.contextValue = ContextValues.MergeIncomingChanges;
item.description = this.status.incoming;
item.iconPath = new ThemeIcon('diff');
item.tooltip = new MarkdownString(
`Incoming changes to $(file) ${this.file.fileName}${
this.status.incoming ? ` from $(git-branch) ${this.status.incoming}` : ''
`${`Merging ${
this.mergeStatus.incoming != null ? GitReference.toString(this.mergeStatus.incoming) : ''
}into ${GitReference.toString(this.mergeStatus.current)}`}${
this.status?.hasConflicts
? `\n\n${Strings.pluralize('conflicted file', this.status.conflicts.length)}`
: ''
}`,
true,
);
item.command = this.getCommand();
return item;
}
getCommand(): Command | undefined {
if (this.status.mergeBase == null) {
return {
title: 'Open Revision',
command: BuiltInCommands.Open,
arguments: [GitUri.toRevisionUri('MERGE_HEAD', this.file.fileName, this.status.repoPath)],
};
}
const commandArgs: DiffWithCommandArgs = {
lhs: {
sha: this.status.mergeBase,
uri: GitUri.fromFile(this.file, this.status.repoPath, undefined, true),
title: `${this.file.fileName} (merge-base)`,
},
rhs: {
sha: 'MERGE_HEAD',
uri: GitUri.fromFile(this.file, this.status.repoPath),
title: `${this.file.fileName} (${this.status.incoming ? this.status.incoming : 'incoming'})`,
},
repoPath: this.status.repoPath,
line: 0,
showOptions: {
preserveFocus: true,
preview: true,
},
};
return {
title: 'Open Changes',
command: Commands.DiffWith,
arguments: [commandArgs],
};
}
}

+ 210
- 0
src/views/nodes/rebaseStatusNode.ts Voir le fichier

@ -0,0 +1,210 @@
'use strict';
import * as paths from 'path';
import { Command, MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { BranchNode } from './branchNode';
import { ViewFilesLayout } from '../../configuration';
import { FileNode, FolderNode } from './folderNode';
import {
CommitFormatter,
GitBranch,
GitLogCommit,
GitRebaseStatus,
GitReference,
GitRevisionReference,
GitStatus,
} from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { MergeConflictFileNode } from './mergeConflictFileNode';
import { Arrays, Strings } from '../../system';
import { ViewsWithCommits } from '../viewBase';
import { ContextValues, ViewNode, ViewRefNode } from './viewNode';
import { Container } from '../../container';
import { GlyphChars } from '../../constants';
import { CommitFileNode } from './commitFileNode';
import { Commands, DiffWithPreviousCommandArgs } from '../../commands';
export class RebaseStatusNode extends ViewNode<ViewsWithCommits> {
static key = ':rebase';
static getId(repoPath: string, name: string, root: boolean): string {
return `${BranchNode.getId(repoPath, name, root)}${this.key}`;
}
constructor(
view: ViewsWithCommits,
parent: ViewNode,
public readonly branch: GitBranch,
public readonly rebaseStatus: GitRebaseStatus,
public readonly status: GitStatus | undefined,
// Specifies that the node is shown as a root
public readonly root: boolean,
) {
super(GitUri.fromRepoPath(rebaseStatus.repoPath), view, parent);
}
get id(): string {
return RebaseStatusNode.getId(this.rebaseStatus.repoPath, this.rebaseStatus.incoming.name, this.root);
}
get repoPath(): string {
return this.uri.repoPath!;
}
async getChildren(): Promise<ViewNode[]> {
if (this.status?.hasConflicts !== true) return [];
let children: FileNode[] = this.status.conflicts.map(
f => new MergeConflictFileNode(this.view, this, this.rebaseStatus, f),
);
if (this.view.config.files.layout !== ViewFilesLayout.List) {
const hierarchy = Arrays.makeHierarchical(
children,
n => n.uri.relativePath.split('/'),
(...parts: string[]) => Strings.normalizePath(paths.join(...parts)),
this.view.config.files.compact,
);
const root = new FolderNode(this.view, this, this.repoPath, '', hierarchy);
children = root.getChildren() as FileNode[];
} else {
children.sort((a, b) =>
a.label!.localeCompare(b.label!, undefined, { numeric: true, sensitivity: 'base' }),
);
}
const commit = await Container.git.getCommit(this.rebaseStatus.repoPath, this.rebaseStatus.stepCurrent.ref);
if (commit != null) {
children.splice(0, 0, new RebaseCommitNode(this.view, this, commit) as any);
}
return children;
}
getTreeItem(): TreeItem {
const item = new TreeItem(
`${this.status?.hasConflicts ? 'Resolve conflicts to continue rebasing' : 'Rebasing'} ${
this.rebaseStatus.incoming != null
? `${GitReference.toString(this.rebaseStatus.incoming, { expand: false, icon: false })} `
: ''
}(${this.rebaseStatus.step}/${this.rebaseStatus.steps})`,
TreeItemCollapsibleState.Expanded,
);
item.id = this.id;
item.contextValue = ContextValues.Rebase;
item.description = this.status?.hasConflicts
? Strings.pluralize('conflict', this.status.conflicts.length)
: undefined;
item.iconPath = this.status?.hasConflicts
? new ThemeIcon('warning', new ThemeColor('list.warningForeground'))
: new ThemeIcon('debug-pause', new ThemeColor('list.foreground'));
item.tooltip = new MarkdownString(
`${`Rebasing ${
this.rebaseStatus.incoming != null ? GitReference.toString(this.rebaseStatus.incoming) : ''
}onto ${GitReference.toString(this.rebaseStatus.current)}`}\n\nStep ${this.rebaseStatus.step} of ${
this.rebaseStatus.steps
}\\\nStopped at ${GitReference.toString(this.rebaseStatus.stepCurrent, { icon: true })}${
this.status?.hasConflicts
? `\n\n${Strings.pluralize('conflicted file', this.status.conflicts.length)}`
: ''
}`,
true,
);
return item;
}
}
export class RebaseCommitNode extends ViewRefNode<ViewsWithCommits, GitRevisionReference> {
constructor(view: ViewsWithCommits, parent: ViewNode, public readonly commit: GitLogCommit) {
super(commit.toGitUri(), view, parent);
}
toClipboard(): string {
let message = this.commit.message;
const index = message.indexOf('\n');
if (index !== -1) {
message = `${message.substring(0, index)}${GlyphChars.Space}${GlyphChars.Ellipsis}`;
}
return `${this.commit.shortSha}: ${message}`;
}
get ref(): GitRevisionReference {
return this.commit;
}
private get tooltip() {
return CommitFormatter.fromTemplate(
`\${author}\${ (email)} ${
GlyphChars.Dash
} \${id}\${ (tips)}\n\${ago} (\${date})\${\n\nmessage}${this.commit.getFormattedDiffStatus({
expand: true,
prefix: '\n\n',
separator: '\n',
})}\${\n\n${GlyphChars.Dash.repeat(2)}\nfootnotes}`,
this.commit,
{
dateFormat: Container.config.defaultDateFormat,
messageIndent: 4,
},
);
}
getChildren(): ViewNode[] {
const commit = this.commit;
let children: FileNode[] = commit.files.map(
s => new CommitFileNode(this.view, this, s, commit.toFileCommit(s)!),
);
if (this.view.config.files.layout !== ViewFilesLayout.List) {
const hierarchy = Arrays.makeHierarchical(
children,
n => n.uri.relativePath.split('/'),
(...parts: string[]) => Strings.normalizePath(paths.join(...parts)),
this.view.config.files.compact,
);
const root = new FolderNode(this.view, this, this.repoPath, '', hierarchy);
children = root.getChildren() as FileNode[];
} else {
children.sort((a, b) =>
a.label!.localeCompare(b.label!, undefined, { numeric: true, sensitivity: 'base' }),
);
}
return children;
}
getTreeItem(): TreeItem {
const item = new TreeItem(`Stopped at commit ${this.commit.shortSha}`, TreeItemCollapsibleState.Collapsed);
// item.contextValue = ContextValues.RebaseCommit;
// eslint-disable-next-line no-template-curly-in-string
item.description = CommitFormatter.fromTemplate('${message}', this.commit, {
messageTruncateAtNewLine: true,
});
item.iconPath = new ThemeIcon('git-commit');
item.tooltip = this.tooltip;
return item;
}
getCommand(): Command | undefined {
const commandArgs: DiffWithPreviousCommandArgs = {
commit: this.commit,
uri: this.uri,
line: 0,
showOptions: {
preserveFocus: true,
preview: true,
},
};
return {
title: 'Open Changes with Previous Revision',
command: Commands.DiffWithPrevious,
arguments: [undefined, commandArgs],
};
}
}

+ 14
- 7
src/views/nodes/repositoryNode.ts Voir le fichier

@ -21,6 +21,7 @@ import { BranchTrackingStatusNode } from './branchTrackingStatusNode';
import { MessageNode } from './common';
import { ContributorsNode } from './contributorsNode';
import { MergeStatusNode } from './mergeStatusNode';
import { RebaseStatusNode } from './rebaseStatusNode';
import { ReflogNode } from './reflogNode';
import { RemotesNode } from './remotesNode';
import { StashesNode } from './stashesNode';
@ -68,6 +69,7 @@ export class RepositoryNode extends SubscribeableViewNode {
status.state.ahead,
status.state.behind,
status.detached,
status.rebasing,
);
if (this.view.config.showBranchComparison !== false) {
@ -83,11 +85,15 @@ export class RepositoryNode extends SubscribeableViewNode {
);
}
if (status.hasConflicts) {
const mergeStatus = await Container.git.getMergeStatus(status);
if (mergeStatus != null) {
children.push(new MergeStatusNode(this.view, this, branch, mergeStatus));
}
const [mergeStatus, rebaseStatus] = await Promise.all([
Container.git.getMergeStatus(status.repoPath),
Container.git.getRebaseStatus(status.repoPath),
]);
if (mergeStatus != null) {
children.push(new MergeStatusNode(this.view, this, branch, mergeStatus, status, true));
} else if (rebaseStatus != null) {
children.push(new RebaseStatusNode(this.view, this, branch, rebaseStatus, status, true));
} else if (this.view.config.showUpstreamStatus) {
if (status.upstream) {
if (!status.state.behind && !status.state.ahead) {
@ -127,6 +133,7 @@ export class RepositoryNode extends SubscribeableViewNode {
showAsCommits: true,
showComparison: false,
showCurrent: false,
showStatus: false,
showTracking: false,
}),
);
@ -186,7 +193,7 @@ export class RepositoryNode extends SubscribeableViewNode {
const status = await this._status;
if (status != null) {
tooltip += `\n\nCurrent branch $(git-branch) ${status.branch}`;
tooltip += `\n\nCurrent branch $(git-branch) ${status.branch}${status.rebasing ? ' (Rebasing)' : ''}`;
if (this.view.config.includeWorkingTree && status.files.length !== 0) {
workingStatus = status.getFormattedDiffStatus({
@ -199,7 +206,7 @@ export class RepositoryNode extends SubscribeableViewNode {
suffix: Strings.pad(GlyphChars.Dot, 1, 1),
});
description = `${upstreamStatus}${status.branch}${workingStatus}`;
description = `${upstreamStatus}${status.branch}${status.rebasing ? ' (Rebasing)' : ''}${workingStatus}`;
let providerName;
if (status.upstream != null) {

+ 3
- 2
src/views/nodes/viewNode.ts Voir le fichier

@ -40,11 +40,12 @@ export enum ContextValues {
Folder = 'gitlens:folder',
LineHistory = 'gitlens:history:line',
Merge = 'gitlens:merge',
MergeCurrentChanges = 'gitlens:merge:current',
MergeIncomingChanges = 'gitlens:merge:incoming',
MergeConflictCurrentChanges = 'gitlens:merge-conflict:current',
MergeConflictIncomingChanges = 'gitlens:merge-conflict:incoming',
Message = 'gitlens:message',
Pager = 'gitlens:pager',
PullRequest = 'gitlens:pullrequest',
Rebase = 'gitlens:rebase',
Reflog = 'gitlens:reflog',
ReflogRecord = 'gitlens:reflog-record',
Remote = 'gitlens:remote',

+ 1
- 1
src/views/viewCommands.ts Voir le fichier

@ -777,7 +777,7 @@ export class ViewCommands {
if (node instanceof MergeConflictFileNode) {
void executeCommand<DiffWithCommandArgs>(Commands.DiffWith, {
lhs: {
sha: 'MERGE_HEAD',
sha: node.status.HEAD.ref,
uri: GitUri.fromFile(node.file, node.repoPath, undefined, true),
},
rhs: {

Chargement…
Annuler
Enregistrer