Quellcode durchsuchen

Adds ahead commit status to file/line history

Adds ahead commit status for branches without an upstream
main
Eric Amodio vor 4 Jahren
Ursprung
Commit
37a9793bc5
11 geänderte Dateien mit 197 neuen und 44 gelöschten Zeilen
  1. +5
    -0
      package.json
  2. +12
    -6
      src/git/git.ts
  3. +76
    -0
      src/git/gitService.ts
  4. +9
    -9
      src/git/parsers/logParser.ts
  5. +6
    -5
      src/views/nodes/branchNode.ts
  6. +28
    -7
      src/views/nodes/commitFileNode.ts
  7. +16
    -4
      src/views/nodes/fileHistoryNode.ts
  8. +10
    -3
      src/views/nodes/fileHistoryTrackerNode.ts
  9. +21
    -5
      src/views/nodes/lineHistoryNode.ts
  10. +12
    -3
      src/views/nodes/lineHistoryTrackerNode.ts
  11. +2
    -2
      src/views/viewCommands.ts

+ 5
- 0
package.json Datei anzeigen

@ -6754,6 +6754,11 @@
"group": "inline@1"
},
{
"command": "gitlens.views.undoCommit",
"when": "!gitlens:readonly && viewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)(?=.*?\\b\\+HEAD\\b)(?=.*?\\b\\+unpublished\\b)/",
"group": "inline@-1"
},
{
"command": "gitlens.views.openFile",
"when": "viewItem =~ /gitlens:file\\b(?!.*?\\b\\+history\\b)/",
"group": "inline@1",

+ 12
- 6
src/git/git.ts Datei anzeigen

@ -702,6 +702,7 @@ export namespace Git {
ref: string | undefined,
{
authors,
format = 'default',
limit,
merges,
reverse,
@ -709,6 +710,7 @@ export namespace Git {
since,
}: {
authors?: string[];
format?: 'refs' | 'default';
limit?: number;
merges?: boolean;
reverse?: boolean;
@ -718,12 +720,16 @@ export namespace Git {
) {
const params = [
'log',
'--name-status',
`--format=${GitLogParser.defaultFormat}`,
`--format=${format === 'refs' ? GitLogParser.simpleRefs : GitLogParser.defaultFormat}`,
'--full-history',
`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`,
'-m',
];
if (format !== 'refs') {
params.push('--name-status');
}
if (limit && !reverse) {
params.push(`-n${limit + 1}`);
}
@ -763,25 +769,25 @@ export namespace Git {
{
all,
filters,
limit,
firstParent = false,
format = 'default',
limit,
renames = true,
reverse = false,
since,
skip,
format = 'default',
startLine,
endLine,
}: {
all?: boolean;
filters?: GitDiffFilter[];
limit?: number;
firstParent?: boolean;
format?: 'refs' | 'simple' | 'default';
limit?: number;
renames?: boolean;
reverse?: boolean;
since?: string;
skip?: number;
format?: 'refs' | 'simple' | 'default';
startLine?: number;
endLine?: number;
} = {},

+ 76
- 0
src/git/gitService.ts Datei anzeigen

@ -106,6 +106,15 @@ const mappedAuthorRegex = /(.+)\s<(.+)>/;
const emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
const reflogCommands = ['merge', 'pull'];
const maxDefaultBranchWeight = 100;
const weightedDefaultBranches = new Map<string, number>([
['master', maxDefaultBranchWeight],
['main', 15],
['default', 10],
['develop', 5],
['development', 1],
]);
export class GitService implements Disposable {
private _onDidChangeRepositories = new EventEmitter<void>();
get onDidChangeRepositories(): Event<void> {
@ -1101,6 +1110,39 @@ export class GitService implements Disposable {
return branch;
}
@log({
args: {
0: b => b.name,
},
})
async getBranchAheadRange(branch: GitBranch) {
if (branch.state.ahead > 0) {
return GitRevision.createRange(branch.tracking, branch.ref);
}
if (!branch.tracking) {
// If we have no tracking branch, try to find a best guess branch to use as the "base"
const branches = await this.getBranches(branch.repoPath, {
filter: b => weightedDefaultBranches.has(b.name),
});
if (branches.length > 0) {
let weightedBranch: { weight: number; branch: GitBranch } | undefined;
for (const branch of branches) {
const weight = weightedDefaultBranches.get(branch.name)!;
if (weightedBranch == null || weightedBranch.weight < weight) {
weightedBranch = { weight: weight, branch: branch };
}
if (weightedBranch.weight === maxDefaultBranchWeight) break;
}
return GitRevision.createRange(weightedBranch!.branch.ref, branch.ref);
}
}
return undefined;
}
@log()
async getBranches(
repoPath: string | undefined,
@ -1680,6 +1722,40 @@ export class GitService implements Disposable {
}
}
@log()
async getLogRefsOnly(
repoPath: string,
{
ref,
...options
}: {
authors?: string[];
limit?: number;
merges?: boolean;
ref?: string;
reverse?: boolean;
since?: string;
} = {},
): Promise<Set<string> | undefined> {
const limit = options.limit ?? Container.config.advanced.maxListItems ?? 0;
try {
const data = await Git.log(repoPath, ref, {
authors: options.authors,
format: 'refs',
limit: limit,
merges: options.merges == null ? true : options.merges,
reverse: options.reverse,
similarityThreshold: Container.config.advanced.similarityThreshold,
since: options.since,
});
const commits = GitLogParser.parseRefsOnly(data);
return new Set(commits);
} catch (ex) {
return undefined;
}
}
private getLogMoreFn(
log: GitLog,
options: { authors?: string[]; limit?: number; merges?: boolean; ref?: string; reverse?: boolean },

+ 9
- 9
src/git/parsers/logParser.ts Datei anzeigen

@ -25,7 +25,7 @@ const fileStatusAndSummaryRegex = /^(\d+?|-)\s+?(\d+?|-)\s+?(.*)(?:\n\s(delete|r
const fileStatusAndSummaryRenamedFileRegex = /(.+)\s=>\s(.+)/;
const fileStatusAndSummaryRenamedFilePathRegex = /(.*?){(.+?)\s=>\s(.*?)}(.*)/;
const logFileRefsRegex = /^<r> (.*)/gm;
const logRefsRegex = /^<r> (.*)/gm;
const logFileSimpleRegex = /^<r> (.*)\s*(?:(?:diff --git a\/(.*) b\/(.*))|(?:(\S)\S*\t([^\t\n]+)(?:\t(.+))?))/gm;
const logFileSimpleRenamedRegex = /^<r> (\S+)\s*(.*)$/s;
const logFileSimpleRenamedFilesRegex = /^(\S)\S*\t([^\t\n]+)(?:\t(.+)?)?$/gm;
@ -475,14 +475,14 @@ export class GitLogParser {
let ref;
let match;
do {
match = logFileRefsRegex.exec(data);
match = logRefsRegex.exec(data);
if (match == null) break;
[, ref] = match;
} while (true);
// Ensure the regex state is reset
logFileRefsRegex.lastIndex = 0;
logRefsRegex.lastIndex = 0;
return ref;
}
@ -494,19 +494,19 @@ export class GitLogParser {
let ref;
let match;
do {
match = logFileRefsRegex.exec(data);
match = logRefsRegex.exec(data);
if (match == null) break;
[, ref] = match;
if (ref == null || ref.length === 0) {
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
refs.push(` ${ref}`.substr(1));
}
if (ref == null || ref.length === 0) continue;
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
refs.push(` ${ref}`.substr(1));
} while (true);
// Ensure the regex state is reset
logFileRefsRegex.lastIndex = 0;
logRefsRegex.lastIndex = 0;
return refs;
}

+ 6
- 5
src/views/nodes/branchNode.ts Datei anzeigen

@ -114,7 +114,8 @@ export class BranchNode
if (this._children == null) {
const children = [];
const [log, getBranchAndTagTips, pr, unpublished] = await Promise.all([
const range = await Container.git.getBranchAheadRange(this.branch);
const [log, getBranchAndTagTips, pr, unpublishedCommits] = await Promise.all([
this.getLog(),
Container.git.getBranchesAndTagsTipsFn(this.uri.repoPath, this.branch.name),
this.view.config.pullRequests.enabled &&
@ -122,10 +123,10 @@ export class BranchNode
(this.branch.tracking || this.branch.remote)
? this.branch.getAssociatedPullRequest(this.root ? { include: [PullRequestState.Open] } : undefined)
: undefined,
this.branch.state.ahead > 0
? Container.git.getLog(this.uri.repoPath!, {
range
? Container.git.getLogRefsOnly(this.uri.repoPath!, {
limit: 0,
ref: GitRevision.createRange(this.branch.tracking, this.branch.ref),
ref: range,
})
: undefined,
]);
@ -188,7 +189,7 @@ export class BranchNode
this.view,
this,
c,
unpublished?.commits.has(c.ref),
unpublishedCommits?.has(c.ref),
this.branch,
getBranchAndTagTips,
),

+ 28
- 7
src/views/nodes/commitFileNode.ts Datei anzeigen

@ -1,10 +1,17 @@
'use strict';
import * as paths from 'path';
import { Command, Selection, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Command, Selection, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Commands, DiffWithPreviousCommandArgs } from '../../commands';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { CommitFormatter, GitFile, GitLogCommit, GitRevisionReference, StatusFileFormatter } from '../../git/git';
import {
CommitFormatter,
GitBranch,
GitFile,
GitLogCommit,
GitRevisionReference,
StatusFileFormatter,
} from '../../git/git';
import { GitUri } from '../../git/gitUri';
import { StashesView } from '../stashesView';
import { View } from '../viewBase';
@ -16,7 +23,13 @@ export class CommitFileNode extends ViewRefFileNode {
parent: ViewNode,
public readonly file: GitFile,
public commit: GitLogCommit,
private readonly _options: { displayAsCommit?: boolean; inFileHistory?: boolean; selection?: Selection } = {},
private readonly _options: {
branch?: GitBranch;
displayAsCommit?: boolean;
inFileHistory?: boolean;
selection?: Selection;
unpublished?: boolean;
} = {},
) {
super(GitUri.fromFile(file, commit.repoPath, commit.sha), view, parent);
}
@ -73,9 +86,15 @@ export class CommitFileNode extends ViewRefFileNode {
item.description = this.description;
item.tooltip = this.tooltip;
if (this._options.displayAsCommit && !(this.view instanceof StashesView) && this.view.config.avatars) {
item.iconPath = this.commit.getAvatarUri(Container.config.defaultGravatarsStyle);
} else {
if (this._options.displayAsCommit) {
if (!this.commit.isUncommitted && !(this.view instanceof StashesView) && this.view.config.avatars) {
item.iconPath = this._options.unpublished
? new ThemeIcon('arrow-up')
: this.commit.getAvatarUri(Container.config.defaultGravatarsStyle);
}
}
if (item.iconPath == null) {
const icon = GitFile.getStatusIcon(this.file.status);
item.iconPath = {
dark: Container.context.asAbsolutePath(paths.join('images', 'dark', icon)),
@ -93,7 +112,9 @@ export class CommitFileNode extends ViewRefFileNode {
protected get contextValue(): string {
if (!this.commit.isUncommitted) {
return `${ContextValues.File}+committed${this._options.inFileHistory ? '+history' : ''}`;
return `${ContextValues.File}+committed${
this._options.branch?.current && this._options.branch.sha === this.commit.ref ? '+HEAD' : ''
}${this._options.unpublished ? '+unpublished' : ''}${this._options.inFileHistory ? '+history' : ''}`;
}
return this.commit.isUncommittedStaged ? `${ContextValues.File}+staged` : `${ContextValues.File}+unstaged`;

+ 16
- 4
src/views/nodes/fileHistoryNode.ts Datei anzeigen

@ -5,6 +5,7 @@ import { LoadMoreNode, MessageNode } from './common';
import { Container } from '../../container';
import { FileHistoryTrackerNode } from './fileHistoryTrackerNode';
import {
GitBranch,
GitLog,
GitRevision,
RepositoryChange,
@ -27,7 +28,7 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
protected splatted = true;
constructor(uri: GitUri, view: View, parent: ViewNode) {
constructor(uri: GitUri, view: View, parent: ViewNode, private readonly branch: GitBranch | undefined) {
super(uri, view, parent);
}
@ -46,9 +47,19 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
const children: ViewNode[] = [];
if (this.uri.sha == null) {
const status = await Container.git.getStatusForFile(this.uri.repoPath!, this.uri.fsPath);
const range = this.branch != null ? await Container.git.getBranchAheadRange(this.branch) : undefined;
const [log, status, unpublishedCommits] = await Promise.all([
this.getLog(),
this.uri.sha == null ? Container.git.getStatusForFile(this.uri.repoPath!, this.uri.fsPath) : undefined,
range
? Container.git.getLogRefsOnly(this.uri.repoPath!, {
limit: 0,
ref: range,
})
: undefined,
]);
if (this.uri.sha == null) {
const commits = await status?.toPsuedoCommits();
if (commits?.length) {
children.push(
@ -63,7 +74,6 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
}
}
const log = await this.getLog();
if (log != null) {
children.push(
...insertDateMarkers(
@ -71,8 +81,10 @@ export class FileHistoryNode extends SubscribeableViewNode implements PageableVi
log.commits.values(),
c =>
new CommitFileNode(this.view, this, c.files[0], c, {
branch: this.branch,
displayAsCommit: true,
inFileHistory: true,
unpublished: unpublishedCommits?.has(c.ref),
}),
),
this,

+ 10
- 3
src/views/nodes/fileHistoryTrackerNode.ts Datei anzeigen

@ -6,7 +6,7 @@ import { BranchSorting, TagSorting } from '../../configuration';
import { Container } from '../../container';
import { FileHistoryView } from '../fileHistoryView';
import { FileHistoryNode } from './fileHistoryNode';
import { GitReference } from '../../git/git';
import { GitReference, GitRevision } from '../../git/git';
import { GitCommitish, GitUri } from '../../git/gitUri';
import { Logger } from '../../logger';
import { ReferencePicker } from '../../quickpicks';
@ -37,7 +37,7 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode
this._child = undefined;
}
getChildren(): ViewNode[] | Promise<ViewNode[]> {
async getChildren(): Promise<ViewNode[]> {
if (this._child == null) {
if (this._fileUri == null && this.uri === unknownGitUri) {
this.view.description = undefined;
@ -54,7 +54,14 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode
const uri = this._fileUri ?? this.uri;
const commitish: GitCommitish = { ...uri, repoPath: uri.repoPath!, sha: this._base ?? uri.sha };
const fileUri = new GitUri(uri, commitish);
this._child = new FileHistoryNode(fileUri, this.view, this);
let branch;
if (!commitish.sha || commitish.sha === 'HEAD') {
branch = await Container.git.getBranch(uri.repoPath);
} else if (!GitRevision.isSha(commitish.sha)) {
[branch] = await Container.git.getBranches(uri.repoPath, { filter: b => b.name === commitish.sha });
}
this._child = new FileHistoryNode(fileUri, this.view, this, branch);
}
return this._child.getChildren();

+ 21
- 5
src/views/nodes/lineHistoryNode.ts Datei anzeigen

@ -4,6 +4,7 @@ import { CommitFileNode } from './commitFileNode';
import { LoadMoreNode, MessageNode } from './common';
import { Container } from '../../container';
import {
GitBranch,
GitCommitType,
GitFile,
GitLog,
@ -36,8 +37,9 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
uri: GitUri,
view: View,
parent: ViewNode,
private readonly branch: GitBranch | undefined,
public readonly selection: Selection,
private readonly _editorContents: string | undefined,
private readonly editorContents: string | undefined,
) {
super(uri, view, parent);
}
@ -59,11 +61,24 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
let selection = this.selection;
const range = this.branch != null ? await Container.git.getBranchAheadRange(this.branch) : undefined;
const [log, blame, unpublishedCommits] = await Promise.all([
this.getLog(selection),
this.uri.sha == null
? this.editorContents
? await Container.git.getBlameForRangeContents(this.uri, selection, this.editorContents)
: await Container.git.getBlameForRange(this.uri, selection)
: undefined,
range
? Container.git.getLogRefsOnly(this.uri.repoPath!, {
limit: 0,
ref: range,
})
: undefined,
]);
if (this.uri.sha == null) {
// Check for any uncommitted changes in the range
const blame = this._editorContents
? await Container.git.getBlameForRangeContents(this.uri, selection, this._editorContents)
: await Container.git.getBlameForRange(this.uri, selection);
if (blame != null) {
for (const commit of blame.commits.values()) {
if (!commit.isUncommitted) continue;
@ -183,7 +198,6 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
}
}
const log = await this.getLog(selection);
if (log != null) {
children.push(
...insertDateMarkers(
@ -191,9 +205,11 @@ export class LineHistoryNode extends SubscribeableViewNode implements PageableVi
log.commits.values(),
c =>
new CommitFileNode(this.view, this, c.files[0], c, {
branch: this.branch,
displayAsCommit: true,
inFileHistory: true,
selection: selection,
unpublished: unpublishedCommits?.has(c.ref),
}),
),
this,

+ 12
- 3
src/views/nodes/lineHistoryTrackerNode.ts Datei anzeigen

@ -5,7 +5,7 @@ import { UriComparer } from '../../comparers';
import { BranchSorting, TagSorting } from '../../configuration';
import { Container } from '../../container';
import { FileHistoryView } from '../fileHistoryView';
import { GitReference } from '../../git/git';
import { GitReference, GitRevision } from '../../git/git';
import { GitCommitish, GitUri } from '../../git/gitUri';
import { LineHistoryView } from '../lineHistoryView';
import { LineHistoryNode } from './lineHistoryNode';
@ -40,7 +40,7 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode
this._child = undefined;
}
getChildren(): ViewNode[] | Promise<ViewNode[]> {
async getChildren(): Promise<ViewNode[]> {
if (this._child == null) {
if (this.uri === unknownGitUri) {
this.view.description = undefined;
@ -60,7 +60,16 @@ export class LineHistoryTrackerNode extends SubscribeableViewNode
sha: this.uri.sha ?? this._base,
};
const fileUri = new GitUri(this.uri, commitish);
this._child = new LineHistoryNode(fileUri, this.view, this, this._selection!, this._editorContents);
let branch;
if (!commitish.sha || commitish.sha === 'HEAD') {
branch = await Container.git.getBranch(this.uri.repoPath);
} else if (!GitRevision.isSha(commitish.sha)) {
[branch] = await Container.git.getBranches(this.uri.repoPath, {
filter: b => b.name === commitish.sha,
});
}
this._child = new LineHistoryNode(fileUri, this.view, this, branch, this._selection!, this._editorContents);
}
return this._child.getChildren();

+ 2
- 2
src/views/viewCommands.ts Datei anzeigen

@ -522,8 +522,8 @@ export class ViewCommands {
}
@debug()
private undoCommit(node: CommitNode) {
if (!(node instanceof CommitNode)) return Promise.resolve();
private undoCommit(node: CommitNode | CommitFileNode) {
if (!(node instanceof CommitNode) && !(node instanceof CommitFileNode)) return Promise.resolve();
return GitActions.reset(
node.repoPath,

Laden…
Abbrechen
Speichern