Kaynağa Gözat

Separates branch comparison into ahead/behind

main
Eric Amodio 4 yıl önce
ebeveyn
işleme
143d55b8f5
11 değiştirilmiş dosya ile 205 ekleme ve 145 silme
  1. +4
    -7
      package.json
  2. +4
    -4
      src/commands/git/merge.ts
  3. +4
    -4
      src/commands/git/rebase.ts
  4. +22
    -9
      src/git/git.ts
  5. +5
    -2
      src/git/gitService.ts
  6. +103
    -91
      src/views/nodes/compareBranchNode.ts
  7. +4
    -4
      src/views/nodes/compareResultsNode.ts
  8. +37
    -11
      src/views/nodes/resultsCommitsNode.ts
  9. +16
    -7
      src/views/nodes/resultsFilesNode.ts
  10. +1
    -1
      src/views/nodes/searchResultsCommitsNode.ts
  11. +5
    -5
      src/views/viewCommands.ts

+ 4
- 7
package.json Dosyayı Görüntüle

@ -3604,18 +3604,15 @@
},
{
"command": "gitlens.views.setBranchComparisonToWorking",
"title": "Toggle Comparison Source (Working Tree)",
"title": "Toggle Source (Branch)",
"category": "GitLens",
"icon": {
"dark": "images/dark/icon-compare-ref-working.svg",
"light": "images/light/icon-compare-ref-working.svg"
}
"icon": "$(git-branch)"
},
{
"command": "gitlens.views.setBranchComparisonToBranch",
"title": "Toggle Comparison Source (Branch)",
"title": "Toggle Source (Working Tree)",
"category": "GitLens",
"icon": "$(compare-changes)"
"icon": "$(list-tree)"
},
{
"command": "gitlens.views.clearNode",

+ 4
- 4
src/commands/git/merge.ts Dosyayı Görüntüle

@ -193,10 +193,10 @@ export class MergeGitCommand extends QuickCommand {
}
private async *confirmStep(state: MergeStepState, context: Context): StepResultGenerator<Flags[]> {
const count =
(await Container.git.getCommitCount(state.repo.path, [
GitRevision.createRange(context.destination.name, state.reference.name),
])) ?? 0;
const aheadBehind class="o">= await Container.git.getAheadBehindCommitCount class="p">(state.repo .="nx">path, [
GitRevision.createRange(context.destination.name, state.reference.name),
]);
const count = aheadBehind != null ? aheadBehind.ahead + aheadBehind.behind : 0;
if (count === 0) {
const step: QuickPickStep<DirectiveQuickPickItem> = this.createConfirmStep(
appendReposToTitle(`Confirm ${context.title}`, state, context),

+ 4
- 4
src/commands/git/rebase.ts Dosyayı Görüntüle

@ -194,10 +194,10 @@ export class RebaseGitCommand extends QuickCommand {
}
private async *confirmStep(state: RebaseStepState, context: Context): StepResultGenerator<Flags[]> {
const count =
(await Container.git.getCommitCount(state.repo.path, [
GitRevision.createRange(state.reference.ref, context.destination.ref),
])) ?? 0;
const aheadBehind class="o">= await Container.git.getAheadBehindCommitCount class="p">(state.repo .="nx">path, [
GitRevision.createRange(context.destination.name, state.reference.name),
]);
const count = aheadBehind != null ? aheadBehind.ahead + aheadBehind.behind : 0;
if (count === 0) {
const step: QuickPickStep<DirectiveQuickPickItem> = this.createConfirmStep(
appendReposToTitle(`Confirm ${context.title}`, state, context),

+ 22
- 9
src/git/git.ts Dosyayı Görüntüle

@ -1017,16 +1017,29 @@ export namespace Git {
export async function rev_list(
repoPath: string,
refs: string[],
options: { count?: boolean } = {},
): Promise<number | undefined> {
const params = [];
if (options.count) {
params.push('--count');
}
params.push(...refs);
): Promise<{ ahead: number; behind: number } | undefined> {
const data = await git<string>(
{ cwd: repoPath, errors: GitErrorHandling.Ignore },
'rev-list',
'--left-right',
'--count',
...refs,
'--',
);
if (data.length === 0) return undefined;
const parts = data.split('\t');
if (parts.length !== 2) return undefined;
const [ahead, behind] = parts;
const result = {
ahead: parseInt(ahead, 10),
behind: parseInt(behind, 10),
};
if (isNaN(result.ahead) || isNaN(result.behind)) return undefined;
const data = await git<string>({ cwd: repoPath, errors: GitErrorHandling.Ignore }, 'rev-list', ...params, '--');
return data.length === 0 ? undefined : Number(data.trim()) || undefined;
return result;
}
export async function rev_parse__currentBranch(

+ 5
- 2
src/git/gitService.ts Dosyayı Görüntüle

@ -1260,8 +1260,11 @@ export class GitService implements Disposable {
}
@log()
getCommitCount(repoPath: string, refs: string[]) {
return Git.rev_list(repoPath, refs, { count: true });
getAheadBehindCommitCount(
repoPath: string,
refs: string[],
): Promise<{ ahead: number; behind: number } | undefined> {
return Git.rev_list(repoPath, refs);
}
@log()

+ 103
- 91
src/views/nodes/compareBranchNode.ts Dosyayı Görüntüle

@ -11,7 +11,7 @@ import { CommandQuickPickItem, ReferencePicker } from '../../quickpicks';
import { RepositoriesView } from '../repositoriesView';
import { RepositoryNode } from './repositoryNode';
import { CommitsQueryResults, ResultsCommitsNode } from './resultsCommitsNode';
import { FilesQueryResults, ResultsFilesNode } from './resultsFilesNode';
import { FilesQueryResults } from './resultsFilesNode';
import { debug, gate, log, Strings } from '../../system';
import { ContextValues, ViewNode } from './viewNode';
@ -37,7 +37,7 @@ export class CompareBranchNode extends ViewNode
if (compareWith !== undefined && typeof compareWith === 'string') {
this._compareWith = {
ref: compareWith,
notation: Container.config.advanced.useSymmetricDifferenceNotation ? '...' : '..',
notation: undefined,
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
type: this.view.config.showBranchComparison || ViewShowBranchComparison.Working,
};
@ -51,33 +51,57 @@ export class CompareBranchNode extends ViewNode
}
async getChildren(): Promise<ViewNode[]> {
if (this._compareWith === undefined) return [];
if (this._compareWith == null) return [];
if (this._children === undefined) {
let ref1 = this._compareWith.ref || 'HEAD';
if (this.comparisonNotation === '..') {
ref1 = (await Container.git.getMergeBase(this.branch.repoPath, ref1, this.branch.ref)) ?? ref1;
}
if (this._children == null) {
const aheadBehind = await Container.git.getAheadBehindCommitCount(this.branch.repoPath, [
GitRevision.createRange(this.branch.ref || 'HEAD', this._compareWith.ref || 'HEAD', '...'),
]);
this._children = [
new ResultsCommitsNode(
this.view,
this,
this.uri.repoPath!,
'commits',
this.getCommitsQuery.bind(this),
'Behind', //`Behind (${aheadBehind?.behind})`,
this.getCommitsQuery(
GitRevision.createRange(this.branch.ref, this._compareWith?.ref ?? 'HEAD', '..'),
),
{
id: 'behind',
description: Strings.pluralize('commit', aheadBehind?.behind ?? 0),
expand: false,
includeDescription: false,
includeRepoName: true,
files: {
ref1: this.branch.ref,
ref2: this._compareWith.ref || 'HEAD',
query: this.getBehindFilesQuery.bind(this),
},
},
),
new ResultsFilesNode(
new ResultsCommitsNode(
this.view,
this,
this.uri.repoPath!,
ref1,
this.compareWithWorkingTree ? '' : this.branch.ref,
this.getFilesQuery.bind(this),
'Ahead', //`Ahead (${aheadBehind?.ahead})`,
this.getCommitsQuery(
GitRevision.createRange(
this._compareWith?.ref ?? 'HEAD',
this.compareWithWorkingTree ? '' : this.branch.ref,
'..',
),
),
{
id: 'ahead',
description: Strings.pluralize('commit', aheadBehind?.ahead ?? 0),
expand: false,
includeRepoName: true,
files: {
ref1: this._compareWith.ref || 'HEAD',
ref2: this.compareWithWorkingTree ? '' : this.branch.ref,
query: this.getAheadFilesQuery.bind(this),
},
},
),
];
}
@ -109,18 +133,11 @@ export class CompareBranchNode extends ViewNode
command: 'gitlens.views.executeNodeCallback',
arguments: [() => this.compareWith()],
};
item.contextValue = `${ContextValues.CompareBranch}${this.branch.current ? '+current' : ''}${
this._compareWith === undefined ? '' : '+comparing'
}+${this.comparisonNotation === '..' ? 'twodot' : 'threedot'}+${this.comparisonType}`;
item.contextValue = `${ContextValues.CompareBranch}${this.branch.current ? '+current' : ''}+${
this.comparisonType
}${this._compareWith == null ? '' : '+comparing'}`;
item.description = description;
if (this.compareWithWorkingTree) {
item.iconPath = {
dark: Container.context.asAbsolutePath('images/dark/icon-compare-ref-working.svg'),
light: Container.context.asAbsolutePath('images/light/icon-compare-ref-working.svg'),
};
} else {
item.iconPath = new ThemeIcon('git-compare');
}
item.iconPath = new ThemeIcon('git-compare');
item.id = this.id;
item.tooltip = `Click to compare ${this.branch.name}${this.compareWithWorkingTree ? ' (working)' : ''} with${
GlyphChars.Ellipsis
@ -140,14 +157,10 @@ export class CompareBranchNode extends ViewNode
this.view.triggerNodeChange(this);
}
@log()
async setComparisonNotation(comparisonNotation: '...' | '..') {
if (this._compareWith !== undefined) {
await this.updateCompareWith({ ...this._compareWith, notation: comparisonNotation });
}
@gate()
@debug()
refresh() {
this._children = undefined;
this.view.triggerNodeChange(this);
}
@log()
@ -160,17 +173,6 @@ export class CompareBranchNode extends ViewNode
this.view.triggerNodeChange(this);
}
private get comparisonNotation(): '..' | '...' {
return this._compareWith?.notation ?? (Container.config.advanced.useSymmetricDifferenceNotation ? '...' : '..');
}
private get diffComparisonNotation(): '..' | '...' {
// In git diff the range syntax doesn't mean the same thing as with git log -- since git diff is about comparing endpoints not ranges
// see https://git-scm.com/docs/git-diff#Documentation/git-diff.txt-emgitdiffemltoptionsgtltcommitgtltcommitgt--ltpathgt82308203
// So inverting the range syntax should be about equivalent for the behavior we want
return this.comparisonNotation === '...' ? '..' : '...';
}
private get comparisonType() {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
return this._compareWith?.type ?? (this.view.config.showBranchComparison || ViewShowBranchComparison.Working);
@ -199,7 +201,7 @@ export class CompareBranchNode extends ViewNode
await this.updateCompareWith({
ref: pick.ref,
notation: this.comparisonNotation,
notation: undefined,
type: this.comparisonType,
});
@ -207,60 +209,68 @@ export class CompareBranchNode extends ViewNode
this.view.triggerNodeChange(this);
}
private async getCommitsQuery(limit: number | undefined): Promise<CommitsQueryResults> {
const log = await Container.git.getLog(this.uri.repoPath!, {
limit: limit,
ref: GitRevision.createRange(
this._compareWith?.ref ?? 'HEAD',
this.compareWithWorkingTree ? '' : this.branch.ref,
this.comparisonNotation,
),
});
const count = log?.count ?? 0;
const results: Mutable<Partial<CommitsQueryResults>> = {
label: Strings.pluralize('commit', count, {
number: log?.hasMore ?? false ? `${count}+` : undefined,
zero: 'No',
}),
log: log,
hasMore: log?.hasMore ?? true,
};
if (results.hasMore) {
results.more = async (limit: number | undefined) => {
results.log = (await results.log?.more?.(limit)) ?? results.log;
const count = results.log?.count ?? 0;
results.label = Strings.pluralize('commit', count, {
number: results.log?.hasMore ?? false ? `${count}+` : undefined,
zero: 'No',
});
results.hasMore = results.log?.hasMore ?? true;
private getCommitsQuery(range: string): (limit: number | undefined) => Promise<CommitsQueryResults> {
const repoPath = this.uri.repoPath!;
return async (limit: number | undefined) => {
const log = await Container.git.getLog(repoPath, {
limit: limit,
ref: range,
});
const results: Mutable<Partial<CommitsQueryResults>> = {
log: log,
hasMore: log?.hasMore ?? true,
};
}
return results as CommitsQueryResults;
}
if (results.hasMore) {
results.more = async (limit: number | undefined) => {
results.log = (await results.log?.more?.(limit)) ?? results.log;
results.hasMore = results.log?.hasMore ?? true;
};
}
@gate()
@debug()
refresh() {
this._children = undefined;
return results as CommitsQueryResults;
};
}
private async getFilesQuery(): Promise<FilesQueryResults> {
private async getBehindFilesQuery(): Promise<FilesQueryResults> {
const diff = await Container.git.getDiffStatus(
this.uri.repoPath!,
GitRevision.createRange(
this._compareWith?.ref ?? 'HEAD',
this.compareWithWorkingTree ? '' : this.branch.ref,
this.diffComparisonNotation,
),
GitRevision.createRange(this.branch.ref, this._compareWith?.ref ?? 'HEAD', '...'),
);
return {
label: `${Strings.pluralize('file', diff !== undefined ? diff.length : 0, { zero: 'No' })} changed`,
diff: diff,
files: diff,
};
}
private async getAheadFilesQuery(): Promise<FilesQueryResults> {
let files = await Container.git.getDiffStatus(
this.uri.repoPath!,
GitRevision.createRange(this._compareWith?.ref ?? 'HEAD', this.branch.ref, '...'),
);
if (this.compareWithWorkingTree) {
const workingFiles = await Container.git.getDiffStatus(this.uri.repoPath!, 'HEAD');
if (workingFiles != null) {
if (files != null) {
for (const wf of workingFiles) {
const index = files.findIndex(f => f.fileName === wf.fileName);
if (index !== -1) {
files.splice(index, 1, wf);
} else {
files.push(wf);
}
}
} else {
files = workingFiles;
}
}
}
return {
label: `${Strings.pluralize('file', files?.length ?? 0, { zero: 'No' })} changed`,
files: files,
};
}
@ -272,10 +282,12 @@ export class CompareBranchNode extends ViewNode
comparisons = Object.create(null) as BranchComparisons;
}
const id = `${this.branch.id}${this.branch.current ? '+current' : ''}`;
if (compareWith != null) {
comparisons[this.branch.id] = { ...compareWith };
comparisons[id] = { ...compareWith };
} else {
const { [this.branch.id]: _, ...rest } = comparisons;
const { [id]: _, ...rest } = comparisons;
comparisons = rest;
}
await Container.context.workspaceState.update(WorkspaceState.BranchComparisons, comparisons);

+ 4
- 4
src/views/nodes/compareResultsNode.ts Dosyayı Görüntüle

@ -70,7 +70,7 @@ export class CompareResultsNode extends ViewNode implements Disposa
this.getCommitsQuery.bind(this),
{
expand: false,
includeDescription: true,
includeRepoName: true,
},
),
new ResultsFilesNode(this.view, this, this.uri.repoPath!, ref1, ref2, this.getFilesQuery.bind(this)),
@ -257,11 +257,11 @@ export class CompareResultsNode extends ViewNode implements Disposa
comparison = `${this._compareWith.ref}${this.diffComparisonNotation}${this._ref.ref}`;
}
const diff = await Container.git.getDiffStatus(this.uri.repoPath!, comparison);
const files = await Container.git.getDiffStatus(this.uri.repoPath!, comparison);
return {
label: `${Strings.pluralize('file', diff !== undefined ? diff.length : 0, { zero: 'No' })} changed`,
diff: diff,
label: `${Strings.pluralize('file', files !== undefined ? files.length : 0, { zero: 'No' })} changed`,
files: files,
};
}

+ 37
- 11
src/views/nodes/resultsCommitsNode.ts Dosyayı Görüntüle

@ -2,10 +2,12 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { CommitNode } from './commitNode';
import { LoadMoreNode } from './common';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { GitLog } from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { insertDateMarkers } from './helpers';
import { FilesQueryResults, ResultsFilesNode } from './resultsFilesNode';
import { debug, gate, Iterables, Promises } from '../../system';
import { ViewsWithFiles } from '../viewBase';
import { ContextValues, PageableViewNode, ViewNode } from './viewNode';
@ -24,15 +26,25 @@ export class ResultsCommitsNode extends ViewNode implements Page
public readonly repoPath: string,
private _label: string,
private readonly _commitsQuery: (limit: number | undefined) => Promise<CommitsQueryResults>,
private readonly _options: { expand?: boolean; includeDescription?: boolean } = {},
private readonly _options: {
id?: string;
description?: string;
expand?: boolean;
includeRepoName?: boolean;
files?: {
ref1: string;
ref2: string;
query: () => Promise<FilesQueryResults>;
};
} = {},
) {
super(GitUri.fromRepoPath(repoPath), view, parent);
this._options = { expand: true, includeDescription: true, ..._options };
this._options = { expand: true, includeRepoName: true, ..._options };
}
get id(): string {
return `${this.parent!.id}:results:commits`;
return `${this.parent!.id}:results:commits${this._options.id ? `:${this._options.id}` : ''}`;
}
get type(): ContextValues {
@ -41,12 +53,23 @@ export class ResultsCommitsNode extends ViewNode implements Page
async getChildren(): Promise<ViewNode[]> {
const { log } = await this.getCommitsQueryResults();
if (log === undefined) return [];
if (log == null) return [];
const options = { expand: this._options.expand && log.count === 1 };
const getBranchAndTagTips = await Container.git.getBranchesAndTagsTipsFn(this.uri.repoPath);
const children = [
const children = [];
const { files } = this._options;
if (files != null) {
children.push(
new ResultsFilesNode(this.view, this, this.uri.repoPath!, files.ref1, files.ref2, files.query, {
expand: false,
}),
);
}
children.push(
...insertDateMarkers(
Iterables.map(
log.commits.values(),
@ -56,7 +79,7 @@ export class ResultsCommitsNode extends ViewNode implements Page
undefined,
{ show: log.count > 1 },
),
];
);
if (log.hasMore) {
children.push(new LoadMoreNode(this.view, this, children[children.length - 1]));
@ -88,10 +111,12 @@ export class ResultsCommitsNode extends ViewNode implements Page
state = TreeItemCollapsibleState.Collapsed;
}
let description;
if (this._options.includeDescription && (await Container.git.getRepositoryCount()) > 1) {
let description = this._options.description;
if (this._options.includeRepoName && (await Container.git.getRepositoryCount()) > 1) {
const repo = await Container.git.getRepository(this.repoPath);
description = repo?.formattedName ?? this.repoPath;
description = `${description ? `${description} ${GlyphChars.Dot} ` : ''}${
repo?.formattedName ?? this.repoPath
}`;
}
const item = new TreeItem(label ?? this._label, state);
@ -113,7 +138,7 @@ export class ResultsCommitsNode extends ViewNode implements Page
private _commitsQueryResults: Promise<CommitsQueryResults> | undefined;
private async getCommitsQueryResults() {
if (this._commitsQueryResults === undefined) {
if (this._commitsQueryResults == null) {
this._commitsQueryResults = this._commitsQuery(this.limit ?? Container.config.advanced.maxSearchItems);
const results = await this._commitsQueryResults;
this._hasMore = results.hasMore;
@ -130,11 +155,12 @@ export class ResultsCommitsNode extends ViewNode implements Page
limit: number | undefined = this.view.getNodeLastKnownLimit(this);
async loadMore(limit?: number) {
const results = await this.getCommitsQueryResults();
if (results === undefined || !results.hasMore) return;
if (results == null || !results.hasMore) return;
await results.more?.(limit ?? this.view.config.pageItemLimit);
this.limit = results.log?.count;
void this.triggerChange(false);
}
}

+ 16
- 7
src/views/nodes/resultsFilesNode.ts Dosyayı Görüntüle

@ -12,7 +12,7 @@ import { ContextValues, ViewNode } from './viewNode';
export interface FilesQueryResults {
label: string;
diff: GitFile[] | undefined;
files: GitFile[] | undefined;
}
export class ResultsFilesNode extends ViewNode<ViewsWithFiles> {
@ -23,8 +23,13 @@ export class ResultsFilesNode extends ViewNode {
public readonly ref1: string,
public readonly ref2: string,
private readonly _filesQuery: () => Promise<FilesQueryResults>,
private readonly _options: {
expand?: boolean;
} = {},
) {
super(GitUri.fromRepoPath(repoPath), view, parent);
this._options = { expand: true, ..._options };
}
get id(): string {
@ -32,11 +37,11 @@ export class ResultsFilesNode extends ViewNode {
}
async getChildren(): Promise<ViewNode[]> {
const { diff } = await this.getFilesQueryResults();
if (diff === undefined) return [];
const { files } = await this.getFilesQueryResults();
if (files == null) return [];
let children: FileNode[] = [
...Iterables.map(diff, s => new ResultsFileNode(this.view, this, this.repoPath, s, this.ref1, this.ref2)),
...Iterables.map(files, s => new ResultsFileNode(this.view, this, this.repoPath, s, this.ref1, this.ref2)),
];
if (this.view.config.files.layout !== ViewFilesLayout.List) {
@ -62,13 +67,17 @@ export class ResultsFilesNode extends ViewNode {
async getTreeItem(): Promise<TreeItem> {
let label;
let diff;
let files;
let state;
try {
({ label, diff } = await Promises.cancellable(this.getFilesQueryResults(), 100));
({ label, files } = await Promises.cancellable(this.getFilesQueryResults(), 100));
state =
diff == null || diff.length === 0 ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Expanded;
files == null || files.length === 0
? TreeItemCollapsibleState.None
: this._options.expand
? TreeItemCollapsibleState.Expanded
: TreeItemCollapsibleState.Collapsed;
} catch (ex) {
if (ex instanceof Promises.CancellationError) {
ex.promise.then(() => this.triggerChange(false));

+ 1
- 1
src/views/nodes/searchResultsCommitsNode.ts Dosyayı Görüntüle

@ -29,7 +29,7 @@ export class SearchResultsCommitsNode extends ResultsCommitsNode {
) {
super(view, parent, repoPath, label, commitsQuery, {
expand: true,
includeDescription: true,
includeRepoName: true,
});
this._instanceId = instanceId++;

+ 5
- 5
src/views/viewCommands.ts Dosyayı Görüntüle

@ -484,7 +484,7 @@ export class ViewCommands {
@debug()
private setComparisonNotation(node: ViewNode, comparisonNotation: '...' | '..') {
if (!(node instanceof CompareResultsNode) && !(node instanceof CompareBranchNode)) return Promise.resolve();
if (!(node instanceof CompareResultsNode)) return Promise.resolve();
return node.setComparisonNotation(comparisonNotation);
}
@ -674,7 +674,7 @@ export class ViewCommands {
}
if (node instanceof ResultsFilesNode) {
const { diff } = await node.getFilesQueryResults();
const { files: diff } = await node.getFilesQueryResults();
if (diff == null || diff.length === 0) return undefined;
return GitActions.Commit.openAllChanges(
@ -732,7 +732,7 @@ export class ViewCommands {
}
if (node instanceof ResultsFilesNode) {
const { diff } = await node.getFilesQueryResults();
const { files: diff } = await node.getFilesQueryResults();
if (diff == null || diff.length === 0) return undefined;
return GitActions.Commit.openAllChangesWithWorking(
@ -828,7 +828,7 @@ export class ViewCommands {
}
if (node instanceof ResultsFilesNode) {
const { diff } = await node.getFilesQueryResults();
const { files: diff } = await node.getFilesQueryResults();
if (diff == null || diff.length === 0) return undefined;
return GitActions.Commit.openFiles(diff, node.repoPath, node.ref1 || node.ref2);
@ -882,7 +882,7 @@ export class ViewCommands {
}
if (node instanceof ResultsFilesNode) {
const { diff } = await node.getFilesQueryResults();
const { files: diff } = await node.getFilesQueryResults();
if (diff == null || diff.length === 0) return undefined;
return GitActions.Commit.openFilesAtRevision(diff, node.repoPath, node.ref1, node.ref2);

Yükleniyor…
İptal
Kaydet