diff --git a/src/vs/workbench/parts/git/browser/gitActions.ts b/src/vs/workbench/parts/git/browser/gitActions.ts index 8917069d08640b8b8c7c18751b1888c3a0e3a93a..e9fd08a7a390710a4977466d1742c63ea135d143 100644 --- a/src/vs/workbench/parts/git/browser/gitActions.ts +++ b/src/vs/workbench/parts/git/browser/gitActions.ts @@ -29,6 +29,7 @@ import { IGitService, IFileStatus, Status, StatusType, ServiceState, IModel, IBr import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService'; import paths = require('vs/base/common/paths'); import URI from 'vs/base/common/uri'; +import { IStorageService } from 'vs/platform/storage/common/storage'; function flatten(context?: any, preferFocus = false): IFileStatus[] { if (!context) { @@ -1113,13 +1114,16 @@ export class UndoLastCommitAction extends GitAction { static ID = 'workbench.action.git.undoLastCommit'; static LABEL = nls.localize('undoLastCommit', "Undo Last Commit"); + private storageService: IStorageService; constructor( id = UndoLastCommitAction.ID, label = UndoLastCommitAction.LABEL, - @IGitService gitService: IGitService + @IGitService gitService: IGitService, + @IStorageService storageService: IStorageService ) { super(UndoLastCommitAction.ID, UndoLastCommitAction.LABEL, 'git-action undo-last-commit', gitService); + this.storageService = storageService; } public run():Promise { diff --git a/src/vs/workbench/parts/git/browser/gitServices.ts b/src/vs/workbench/parts/git/browser/gitServices.ts index c79eaa8dfc19a7dc4d9d01b8f9a1ffa4f43daadf..dc1e1fe6428abc4a4776cfe5edabbb035438b07b 100644 --- a/src/vs/workbench/parts/git/browser/gitServices.ts +++ b/src/vs/workbench/parts/git/browser/gitServices.ts @@ -691,6 +691,10 @@ export class GitService extends ee.EventEmitter return this.run(git.ServiceOperations.COMMIT, () => this.raw.commit(message, amend, stage)); } + public getCommitTemplate(): winjs.Promise { + return this.raw.getCommitTemplate(); + } + public detectMimetypes(path: string, treeish: string = '~'): winjs.Promise { return this.raw.detectMimetypes(path, treeish); } diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts index a4f82b205a241f28573989f5fd8fb485f4c12dd6..2876e4ebf8e45f2e649e7d0e8beb8ac4efe9edbd 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts @@ -41,6 +41,7 @@ import {IEventService} from 'vs/platform/event/common/event'; import {CommonKeybindings} from 'vs/base/common/keyCodes'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {IStorageService} from 'vs/platform/storage/common/storage'; import IGitService = git.IGitService; @@ -89,7 +90,8 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV @IGitService gitService: IGitService, @IOutputService outputService: IOutputService, @IEventService eventService: IEventService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @IStorageService private storageService: IStorageService ) { super(); @@ -236,14 +238,24 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV if (visible) { this.tree.onVisible(); - return this.onEditorsChanged(this.editorService.getActiveEditorInput()); - + return this.onCommitInputShown().then((_) => + this.onEditorsChanged(this.editorService.getActiveEditorInput())); } else { this.tree.onHidden(); return WinJS.TPromise.as(null); } } + public onCommitInputShown(): WinJS.TPromise { + if (!this.commitInputBox.value) { + return this.gitService.getCommitTemplate().then((template) => { + if (template) { this.commitInputBox.value = template; } + }); + } else { + return WinJS.TPromise.as(null); + } + } + public getControl(): Tree.ITree { return this.tree; } @@ -395,12 +407,13 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV } private onGitOperationEnd(e: { operation: git.IGitOperation; error: any; }): void { - if (e.operation.id === git.ServiceOperations.COMMIT) { + if (e.operation.id === git.ServiceOperations.COMMIT || e.operation.id === git.ServiceOperations.RESET) { if (this.commitInputBox) { this.commitInputBox.enable(); if (!e.error) { this.commitInputBox.value = ''; + this.onCommitInputShown(); } } } diff --git a/src/vs/workbench/parts/git/common/git.ts b/src/vs/workbench/parts/git/common/git.ts index 001d3f1e75717b26eeafd2c8cd84fe89804f1184..6348e34b06a5fd747ca1bbc6f5a1e73cda947685 100644 --- a/src/vs/workbench/parts/git/common/git.ts +++ b/src/vs/workbench/parts/git/common/git.ts @@ -222,7 +222,7 @@ export var ServiceOperations = { BACKGROUND_FETCH: 'backgroundfetch', PULL: 'pull', PUSH: 'push', - SYNC: 'sync' + SYNC: 'sync', }; // Service config @@ -264,6 +264,23 @@ export interface IPushOptions { setUpstream?: boolean; } +/** + * These are `git log` options. + * The use case driving this is getting the previous commit message, message only. + * @example prevCount: 1, format: '%B' executes `git log -1 --format=%B` + * */ +export interface ILogOptions { + /** + * @example `git log -1 --format=%B` to get the last commit log, message only + */ + prevCount?: number; + + /** + * @example format: "%B" translates to `git log --format=%B` to get the message only + */ + format?: string; +} + export interface IRawGitService { onOutput: Event; getVersion(): TPromise; @@ -286,6 +303,7 @@ export interface IRawGitService { commit(message:string, amend?: boolean, stage?: boolean): TPromise; detectMimetypes(path: string, treeish?: string): TPromise; show(path: string, treeish?: string): TPromise; + getCommitTemplate(): TPromise; } export var GIT_SERVICE_ID = 'gitService'; @@ -322,6 +340,7 @@ export interface IGitService extends IEventEmitter { isIdle(): boolean; getRunningOperations(): IGitOperation[]; getAutoFetcher(): IAutoFetcher; + getCommitTemplate(): TPromise; } export interface IAskpassService { diff --git a/src/vs/workbench/parts/git/common/gitIpc.ts b/src/vs/workbench/parts/git/common/gitIpc.ts index e4bf45ad1ddaa606f6721d2c5ba2019801e6f224..b7e8caf9c564b25bd3065781f81c3c9d6178f08e 100644 --- a/src/vs/workbench/parts/git/common/gitIpc.ts +++ b/src/vs/workbench/parts/git/common/gitIpc.ts @@ -87,6 +87,7 @@ export interface IGitChannel extends IChannel { call(command: 'detectMimetypes', args: [string, string]): TPromise; call(command: 'show', args: [string, string]): TPromise; call(command: 'onOutput'): TPromise; + call(command: 'getCommitTemplate'): TPromise; call(command: string, args: any): TPromise; } @@ -117,6 +118,7 @@ export class GitChannel implements IGitChannel { case 'detectMimetypes': return this.service.then(s => s.detectMimetypes(args[0], args[1])); case 'show': return this.service.then(s => s.show(args[0], args[1])); case 'onOutput': return this.service.then(s => eventToCall(s.onOutput)); + case 'getCommitTemplate': return this.service.then(s => s.getCommitTemplate()); } } } @@ -217,6 +219,10 @@ export class GitChannelClient implements IRawGitService { show(path: string, treeish?: string): TPromise { return this.channel.call('show', [path, treeish]); } + + getCommitTemplate(): TPromise { + return this.channel.call('getCommitTemplate'); + } } export interface IAskpassChannel extends IChannel { diff --git a/src/vs/workbench/parts/git/common/noopGitService.ts b/src/vs/workbench/parts/git/common/noopGitService.ts index ddfd63bbab6f9dce755e11423740092247beb764..3fce07ecfef0b8ac447583ec2fffcb56a53754f9 100644 --- a/src/vs/workbench/parts/git/common/noopGitService.ts +++ b/src/vs/workbench/parts/git/common/noopGitService.ts @@ -101,4 +101,12 @@ export class NoOpGitService implements IRawGitService { show(path: string, treeish?: string): TPromise { return TPromise.as(null); } + + getLog(): TPromise { + return TPromise.as(null); + } + + getCommitTemplate(): TPromise { + return TPromise.as(null); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/git/node/git.lib.ts b/src/vs/workbench/parts/git/node/git.lib.ts index 08d9df169ab5ba225aaf320300e0c7eff7611af5..10c68f06eaa452231d83c2d16c5f3f68223ec664 100644 --- a/src/vs/workbench/parts/git/node/git.lib.ts +++ b/src/vs/workbench/parts/git/node/git.lib.ts @@ -11,7 +11,7 @@ import { assign } from 'vs/base/common/objects'; import { v4 as UUIDv4 } from 'vs/base/common/uuid'; import { localize } from 'vs/nls'; import { uniqueFilter } from 'vs/base/common/arrays'; -import { IRawFileStatus, RefType, IRef, IBranch, IRemote, GitErrorCodes, IPushOptions } from 'vs/workbench/parts/git/common/git'; +import { IRawFileStatus, RefType, IRef, IBranch, IRemote, GitErrorCodes, IPushOptions, ILogOptions } from 'vs/workbench/parts/git/common/git'; import { detectMimesFromStream } from 'vs/base/node/mime'; import { IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files'; import { spawn, ChildProcess } from 'child_process'; @@ -557,6 +557,18 @@ export class Repository { return this.run(['rev-parse', '--show-toplevel'], { log: false }).then(result => result.stdout.trim()); } + /** Only implemented for use case `git log` and `git log -N`. */ + getLog(options?: ILogOptions): TPromise { + const args = ['log']; + + if (options) { + if (options.prevCount) { args.push(`-${options.prevCount}`); } + if (options.format) { args.push(`--format=${options.format}`); } + } + + return this.run(args, { log: false }).then(result => result.stdout.trim()); + } + getStatus(): TPromise { return this.run(['status', '-z', '-u'], { log: false }).then((executionResult) => { const status = executionResult.stdout; diff --git a/src/vs/workbench/parts/git/node/rawGitService.ts b/src/vs/workbench/parts/git/node/rawGitService.ts index 3f3541ec61f4c8e7bc0338c9e2b153f7a57b369c..07397cca410b2418c0a63fc54dd9c6f0d2b1a680 100644 --- a/src/vs/workbench/parts/git/node/rawGitService.ts +++ b/src/vs/workbench/parts/git/node/rawGitService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as fs from 'fs'; import { join } from 'path'; import { TPromise, Promise } from 'vs/base/common/winjs.base'; import { detectMimesFromFile, detectMimesFromStream } from 'vs/base/node/mime'; @@ -195,6 +196,49 @@ export class RawGitService implements IRawGitService { return TPromise.wrapError(e); }); } + + /** + * Reads the commit.template git config setting. If exists, then tries to load the contents of the file specified by that setting and returns these contents. + */ + getCommitTemplate(): TPromise { + return this.repo.run(['config', '--get', 'commit.template']).then(execResult => execResult, err => '').then(execResult => { + return execResult ? this.readCommitTemplateFile(execResult.stdout.trim()) : ''; + }); + } + + /** + * Reads the given file, if exists and is valid. + * @returns commit template file contents if exists and valid, else "" + */ + private readCommitTemplateFile(file: string): string { + try { + // // This is resolving to [repo]\.build\electron\~\.gitmessage + // let fullPath = resolve(file) + // console.log(`file: ${file}, fullPath: ${fullPath}`) + // return fs.existsSync(fullPath) ? fs.readFileSync(file, 'utf8') : ''; + // Check the file itself + if (fs.existsSync(file)) { + return fs.readFileSync(file, 'utf8'); + } else { + // File doesn't exist. Try converting ~/path to absolute path + + // Try checking in local repo git folder (This is wrong interpretation oy) + let repo_file = file.replace('~', `${this.repo.path}\\.git`).replace('/', '\\'); + if (fs.existsSync(repo_file)) { + return fs.readFileSync(repo_file, 'utf8'); + } else { + // Check global (e.g. Windows user folder, Linux: home git config) + // not implemented + + console.warn(`file doesnt exist in repo local git config. global git config template not implemented. (commit template file: ${file})`); + return ''; + } + } + } catch (error) { + console.error(`Error reading file. file: ${file}, error: ${error.message})`); + return ''; + } + } } export class DelayedRawGitService implements IRawGitService {