Browse Source

Adds experimental "recent incoming changes" - #735

main
Eric Amodio 5 years ago
parent
commit
b5f98f12d8
10 changed files with 218 additions and 8 deletions
  1. +18
    -6
      src/git/git.ts
  2. +21
    -1
      src/git/gitService.ts
  3. +1
    -0
      src/git/models/models.ts
  4. +39
    -0
      src/git/models/reflog.ts
  5. +1
    -0
      src/git/parsers/parsers.ts
  6. +62
    -0
      src/git/parsers/reflogParser.ts
  7. +1
    -0
      src/views/nodes.ts
  8. +61
    -0
      src/views/nodes/recentIncomingChangesNode.ts
  9. +13
    -1
      src/views/nodes/repositoryNode.ts
  10. +1
    -0
      src/views/nodes/viewNode.ts

+ 18
- 6
src/git/git.ts View File

@ -8,7 +8,7 @@ import { Logger } from '../logger';
import { Objects, Strings } from '../system';
import { findGitPath, GitLocation } from './locator';
import { run, RunOptions } from './shell';
import { GitBranchParser, GitLogParser, GitStashParser } from './parsers/parsers';
import { GitBranchParser, GitLogParser, GitReflogParser, GitStashParser } from './parsers/parsers';
import { GitFileStatus } from './models/file';
export { GitLocation } from './locator';
@ -774,10 +774,10 @@ export class Git {
return data.length === 0 ? undefined : data.trim();
}
static async ls_tree(repoPath: string, ref: string, options: { fileName?: string } = {}) {
static async ls_tree(repoPath: string, ref: string, { fileName }: { fileName?: string } = {}) {
const params = ['ls-tree'];
if (options.fileName) {
params.push('-l', ref, '--', options.fileName);
if (fileName) {
params.push('-l', ref, '--', fileName);
}
else {
params.push('-lrt', ref, '--');
@ -786,15 +786,27 @@ export class Git {
return data.length === 0 ? undefined : data.trim();
}
static merge_base(repoPath: string, ref1: string, ref2: string, options: { forkPoint?: boolean } = {}) {
static merge_base(repoPath: string, ref1: string, ref2: string, { forkPoint }: { forkPoint?: boolean } = {}) {
const params = ['merge-base'];
if (options.forkPoint) {
if (forkPoint) {
params.push('--fork-point');
}
return git<string>({ cwd: repoPath }, ...params, ref1, ref2);
}
static reflog(repoPath: string, { branch, since }: { branch?: string; since?: string } = {}): Promise<string> {
const params = ['log', '-g', `--format=${GitReflogParser.defaultFormat}`, '--date=unix'];
if (branch) {
params.push(branch);
}
if (since) {
params.push(`--since=${since}`);
}
return git<string>({ cwd: repoPath }, ...params, '--');
}
static remote(repoPath: string): Promise<string> {
return git<string>({ cwd: repoPath }, 'remote', '-v');
}

+ 21
- 1
src/git/gitService.ts View File

@ -51,6 +51,7 @@ import {
GitLogCommit,
GitLogDiffFilter,
GitLogParser,
GitReflog,
GitRemote,
GitRemoteParser,
GitStash,
@ -67,7 +68,7 @@ import {
} from './git';
import { GitUri } from './gitUri';
import { RemoteProviderFactory, RemoteProviders } from './remotes/factory';
import { GitShortLogParser } from './parsers/parsers';
import { GitReflogParser, GitShortLogParser } from './parsers/parsers';
export * from './gitUri';
export * from './models/models';
@ -1953,6 +1954,25 @@ export class GitService implements Disposable {
}
@log()
async getRecentIncomingChanges(
repoPath: string,
options: { branch?: string; since?: string } = {}
): Promise<GitReflog | undefined> {
const cc = Logger.getCorrelationContext();
try {
const data = await Git.reflog(repoPath, options);
if (data === undefined) return undefined;
return GitReflogParser.parseRecentIncomingChanges(data, repoPath);
}
catch (ex) {
Logger.error(ex, cc);
return undefined;
}
}
@log()
async getRemotes(repoPath: string | undefined, options: { includeAll?: boolean } = {}): Promise<GitRemote[]> {
if (repoPath === undefined) return [];

+ 1
- 0
src/git/models/models.ts View File

@ -16,6 +16,7 @@ export * from './log';
export * from './logCommit';
export * from './remote';
export * from './repository';
export * from './reflog';
export * from './shortlog';
export * from './stash';
export * from './stashCommit';

+ 39
- 0
src/git/models/reflog.ts View File

@ -0,0 +1,39 @@
'use strict';
import { Dates, memoize } from '../../system';
import { CommitFormatting } from '../git';
import { DateStyle } from '../../config';
export class GitReflog {
previousRef: string | undefined;
constructor(
public readonly repoPath: string,
public readonly ref: string,
public readonly date: Date,
public readonly command: string
) {}
@memoize<GitReflog['formatDate']>(format => (format == null ? 'MMMM Do, YYYY h:mma' : format))
formatDate(format?: string | null) {
if (format == null) {
format = 'MMMM Do, YYYY h:mma';
}
return this.dateFormatter.format(format);
}
formatDateFromNow() {
return this.dateFormatter.fromNow();
}
get formattedDate(): string {
return CommitFormatting.dateStyle === DateStyle.Absolute
? this.formatDate(CommitFormatting.dateFormat)
: this.formatDateFromNow();
}
@memoize()
private get dateFormatter(): Dates.DateFormatter {
return Dates.getFormatter(this.date);
}
}

+ 1
- 0
src/git/parsers/parsers.ts View File

@ -4,6 +4,7 @@ export * from './blameParser';
export * from './branchParser';
export * from './diffParser';
export * from './logParser';
export * from './reflogParser';
export * from './remoteParser';
export * from './shortlogParser';
export * from './stashParser';

+ 62
- 0
src/git/parsers/reflogParser.ts View File

@ -0,0 +1,62 @@
'use strict';
import { debug } from '../../system';
import { GitReflog } from '../models/reflog';
const incomingCommands = ['merge', 'pull'];
const reflogRegex = /^<r>(.+)<d>(?:.+?)@{(.+)}<s>(\w*).*$/gm;
// Using %x00 codes because some shells seem to try to expand things if not
const lb = '%x3c'; // `%x${'<'.charCodeAt(0).toString(16)}`;
const rb = '%x3e'; // `%x${'>'.charCodeAt(0).toString(16)}`;
export class GitReflogParser {
static defaultFormat = [
`${lb}r${rb}%H`, // ref
`${lb}d${rb}%gD`, // reflog selector (with UNIX timestamp)
`${lb}s${rb}%gs` // reflog subject
].join('');
@debug({ args: false })
static parseRecentIncomingChanges(data: string, repoPath: string): GitReflog | undefined {
if (!data) return undefined;
let reflog: GitReflog | undefined;
let match: RegExpExecArray | null;
let date;
let ref;
let command;
do {
match = reflogRegex.exec(data);
if (match == null) break;
[, ref, date, command] = match;
// If we don't have a reflog, or are still at the same ref with a proper command, save it
if (
(reflog === undefined || (reflog !== undefined && ref === reflog.ref)) &&
incomingCommands.includes(command)
) {
reflog = new GitReflog(
repoPath,
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
` ${ref}`.substr(1),
new Date((date! as any) * 1000),
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
` ${command}`.substr(1)
);
}
else if (reflog !== undefined && ref !== reflog.ref) {
reflog.previousRef = ref;
break;
}
} while (match != null);
// Ensure the regex state is reset
reflogRegex.lastIndex = 0;
return reflog;
}
}

+ 1
- 0
src/views/nodes.ts View File

@ -13,6 +13,7 @@ export * from './nodes/fileHistoryTrackerNode';
export * from './nodes/folderNode';
export * from './nodes/lineHistoryNode';
export * from './nodes/lineHistoryTrackerNode';
export * from './nodes/recentIncomingChangesNode';
export * from './nodes/remoteNode';
export * from './nodes/remotesNode';
export * from './nodes/repositoriesNode';

+ 61
- 0
src/views/nodes/recentIncomingChangesNode.ts View File

@ -0,0 +1,61 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GlyphChars } from '../../constants';
import { Container } from '../../container';
import { GitReflog, GitUri } from '../../git/gitService';
import { Iterables } from '../../system';
import { ViewWithFiles } from '../viewBase';
import { CommitNode } from './commitNode';
import { MessageNode, ShowMoreNode } from './common';
import { insertDateMarkers } from './helpers';
import { PageableViewNode, ResourceType, ViewNode } from './viewNode';
export class RecentIncomingChangesNode extends ViewNode<ViewWithFiles> implements PageableViewNode {
readonly supportsPaging: boolean = true;
maxCount: number | undefined;
constructor(view: ViewWithFiles, parent: ViewNode, public readonly reflog: GitReflog) {
super(GitUri.fromRepoPath(reflog.repoPath), view, parent);
}
get id(): string {
return `${this._instanceId}:gitlens:repository(${this.uri.repoPath}):recent-incoming-changes`;
}
async getChildren(): Promise<ViewNode[]> {
const range = `${this.reflog.previousRef}..${this.reflog.ref}`;
const log = await Container.git.getLog(this.uri.repoPath!, {
maxCount: this.maxCount !== undefined ? this.maxCount : this.view.config.defaultItemLimit,
ref: range
});
if (log === undefined) return [new MessageNode(this.view, this, 'No changes')];
const children = [
...insertDateMarkers(Iterables.map(log.commits.values(), c => new CommitNode(this.view, this, c)), this, 1)
];
if (log.truncated) {
children.push(new ShowMoreNode(this.view, this, 'Commits', children[children.length - 1]));
}
return children;
}
getTreeItem(): TreeItem {
const item = new TreeItem('Recent incoming changes', TreeItemCollapsibleState.Collapsed);
item.id = this.id;
item.description = `via ${this.reflog.command} ${GlyphChars.Space}${GlyphChars.Dot}${GlyphChars.Space} ${
this.reflog.formattedDate
}`;
item.contextValue = ResourceType.RecentIncomingChanges;
item.tooltip = `Recent incoming changes via ${this.reflog.command}\n${this.reflog.formatDate()}`;
// const iconSuffix = ahead ? 'upload' : 'download';
// item.iconPath = {
// dark: Container.context.asAbsolutePath(`images/dark/icon-${iconSuffix}.svg`),
// light: Container.context.asAbsolutePath(`images/light/icon-${iconSuffix}.svg`)
// };
return item;
}
}

+ 13
- 1
src/views/nodes/repositoryNode.ts View File

@ -17,12 +17,13 @@ import { BranchesNode } from './branchesNode';
import { BranchNode } from './branchNode';
import { BranchTrackingStatusNode } from './branchTrackingStatusNode';
import { MessageNode } from './common';
import { ContributorsNode } from './contributorsNode';
import { RecentIncomingChangesNode } from './recentIncomingChangesNode';
import { RemotesNode } from './remotesNode';
import { StashesNode } from './stashesNode';
import { StatusFilesNode } from './statusFilesNode';
import { TagsNode } from './tagsNode';
import { ResourceType, SubscribeableViewNode, ViewNode } from './viewNode';
import { ContributorsNode } from './contributorsNode';
const hasTimeRegex = /[hHm]/;
@ -46,6 +47,17 @@ export class RepositoryNode extends SubscribeableViewNode {
const children = [];
const status = await this._status;
if (Container.config.insiders) {
const reflog = await Container.git.getRecentIncomingChanges(this.uri.repoPath!, {
// branch: status !== undefined ? status.branch : undefined,
since: '1 month ago'
});
if (reflog !== undefined) {
children.push(new RecentIncomingChangesNode(this.view, this, reflog));
}
}
if (status !== undefined) {
const branch = new GitBranch(
status.repoPath,

+ 1
- 0
src/views/nodes/viewNode.ts View File

@ -27,6 +27,7 @@ export enum ResourceType {
LineHistory = 'gitlens:history:line',
Message = 'gitlens:message',
Pager = 'gitlens:pager',
RecentIncomingChanges = 'gitlens:recent-incoming-changes',
Remote = 'gitlens:remote',
Remotes = 'gitlens:remotes',
Repositories = 'gitlens:repositories',

Loading…
Cancel
Save