@ -1,10 +1,8 @@
'use strict' ;
'use strict' ;
import { Strings } from '../system' ;
import { Strings } from '../system' ;
import { SpawnOptions } from 'child_process' ;
import { findGitPath , IGit } from './gitLocator' ;
import { findGitPath , IGit } from './gitLocator' ;
import { Logger } from '../logger' ;
import { Logger } from '../logger' ;
import { Observable } from 'rxjs' ;
import { spawnPromise } from 'spawn-rx' ;
import { CommandOptions , runCommand } from './shell' ;
import * as fs from 'fs' ;
import * as fs from 'fs' ;
import * as iconv from 'iconv-lite' ;
import * as iconv from 'iconv-lite' ;
import * as path from 'path' ;
import * as path from 'path' ;
@ -39,17 +37,7 @@ const GitWarnings = [
/ambiguous argument '.*?': unknown revision or path not in the working tree/
/ambiguous argument '.*?': unknown revision or path not in the working tree/
] ;
] ;
interface GitCommandOptions {
cwd : string ;
env? : any ;
encoding? : string ;
stdin? : Observable < string > | undefined ;
willHandleErrors? : boolean ;
}
async function gitCommand ( options : GitCommandOptions , . . . args : any [ ] ) : Promise < string > {
if ( options . willHandleErrors ) return gitCommandCore ( options , . . . args ) ;
async function gitCommand ( options : CommandOptions , . . . args : any [ ] ) : Promise < string > {
try {
try {
return await gitCommandCore ( options , . . . args ) ;
return await gitCommandCore ( options , . . . args ) ;
}
}
@ -61,27 +49,26 @@ async function gitCommand(options: GitCommandOptions, ...args: any[]): Promise
// A map of running git commands -- avoids running duplicate overlaping commands
// A map of running git commands -- avoids running duplicate overlaping commands
const pendingCommands : Map < string , Promise < string > > = new Map ( ) ;
const pendingCommands : Map < string , Promise < string > > = new Map ( ) ;
async function gitCommandCore ( options : Git CommandOptions, . . . args : any [ ] ) : Promise < string > {
async function gitCommandCore ( options : CommandOptions , . . . args : any [ ] ) : Promise < string > {
// Fixes https://github.com/eamodio/vscode-gitlens/issues/73 & https://github.com/eamodio/vscode-gitlens/issues/161
// Fixes https://github.com/eamodio/vscode-gitlens/issues/73 & https://github.com/eamodio/vscode-gitlens/issues/161
// See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x
// See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x
args . splice ( 0 , 0 , '-c' , 'core.quotepath=false' , '-c' , 'color.ui=false' ) ;
args . splice ( 0 , 0 , '-c' , 'core.quotepath=false' , '-c' , 'color.ui=false' ) ;
const opts = { encoding : 'utf8' , . . . options } ;
const encoding = options . encoding || 'utf8' ;
const opts = {
. . . options ,
encoding : encoding === 'utf8' ? 'utf8' : 'binary' ,
// Adds GCM environment variables to avoid any possible credential issues -- from https://github.com/Microsoft/vscode/issues/26573#issuecomment-338686581
// Shouldn't *really* be needed but better safe than sorry
env : { . . . ( options . env || process . env ) , GCM_INTERACTIVE : 'NEVER' , GCM_PRESERVE_CREDS : 'TRUE' }
} as CommandOptions ;
const command = ` ( ${ options . cwd } ): git ` + args . join ( ' ' ) ;
const command = ` ( ${ opts . cwd } ): git ${ args . join ( ' ' ) } ` ;
let promise = pendingCommands . get ( command ) ;
let promise = pendingCommands . get ( command ) ;
if ( promise === undefined ) {
if ( promise === undefined ) {
Logger . log ( ` Spawning ${ command } ` ) ;
promise = spawnPromise ( git . path , args , {
cwd : options.cwd ,
// Adds GCM environment variables to avoid any possible credential issues -- from https://github.com/Microsoft/vscode/issues/26573#issuecomment-338686581
// Shouldn't *really* be needed but better safe than sorry
env : { . . . ( options . env || process . env ) , GCM_INTERACTIVE : 'NEVER' , GCM_PRESERVE_CREDS : 'TRUE' } ,
encoding : ( opts . encoding === 'utf8' ) ? 'utf8' : 'binary' ,
stdin : opts.stdin
} as SpawnOptions ) ;
Logger . log ( ` Running ${ command } ` ) ;
promise = runCommand ( git . path , args , opts ) ;
pendingCommands . set ( command , promise ) ;
pendingCommands . set ( command , promise ) ;
}
}
@ -98,12 +85,12 @@ async function gitCommandCore(options: GitCommandOptions, ...args: any[]): Promi
Logger . log ( ` Completed ${ command } ` ) ;
Logger . log ( ` Completed ${ command } ` ) ;
}
}
if ( opts . encoding === 'utf8' || opts . encoding === 'binary' ) return data ;
if ( encoding === 'utf8' || encoding === 'binary' ) return data ;
return iconv . decode ( Buffer . from ( data , 'binary' ) , opts . encoding) ;
return iconv . decode ( Buffer . from ( data , 'binary' ) , encoding ) ;
}
}
function gitCommandDefaultErrorHandler ( ex : Error , options : Git CommandOptions, . . . args : any [ ] ) : string {
function gitCommandDefaultErrorHandler ( ex : Error , options : CommandOptions , . . . args : any [ ] ) : string {
const msg = ex && ex . toString ( ) ;
const msg = ex && ex . toString ( ) ;
if ( msg ) {
if ( msg ) {
for ( const warning of GitWarnings ) {
for ( const warning of GitWarnings ) {
@ -253,7 +240,8 @@ export class Git {
params . push ( ` -L ${ options . startLine } , ${ options . endLine } ` ) ;
params . push ( ` -L ${ options . startLine } , ${ options . endLine } ` ) ;
}
}
let stdin : Observable < string > | undefined ;
// let stdin: Observable<string> | undefined;
let stdin : string | undefined ;
if ( sha ) {
if ( sha ) {
if ( Git . isStagedUncommitted ( sha ) ) {
if ( Git . isStagedUncommitted ( sha ) ) {
// Pipe the blame contents to stdin
// Pipe the blame contents to stdin
@ -261,7 +249,7 @@ export class Git {
params . push ( '-' ) ;
params . push ( '-' ) ;
// Get the file contents for the staged version using `:`
// Get the file contents for the staged version using `:`
stdin = Observable . from < string > ( Git . show ( repoPath , fileName , ':' ) as any ) ;
stdin = await Git . show ( repoPath , fileName , ':' ) ;
}
}
else {
else {
params . push ( sha ) ;
params . push ( sha ) ;
@ -288,7 +276,7 @@ export class Git {
static async config_get ( key : string , repoPath? : string ) {
static async config_get ( key : string , repoPath? : string ) {
try {
try {
const data = await gitCommand ( { cwd : repoPath || '' , willHandleErrors : true } , ` config ` , ` --get ` , key ) ;
const data = await gitCommandCore ( { cwd : repoPath || '' } , ` config ` , ` --get ` , key ) ;
return data . trim ( ) ;
return data . trim ( ) ;
}
}
catch {
catch {
@ -305,7 +293,8 @@ export class Git {
params . push ( Git . isStagedUncommitted ( sha2 ) ? '--staged' : sha2 ) ;
params . push ( Git . isStagedUncommitted ( sha2 ) ? '--staged' : sha2 ) ;
}
}
return gitCommand ( { cwd : repoPath , encoding : options.encoding || 'utf8' } , . . . params , '--' , fileName ) ;
const encoding : BufferEncoding = options . encoding === 'utf8' ? 'utf8' : 'binary' ;
return gitCommand ( { cwd : repoPath , encoding : encoding } , . . . params , '--' , fileName ) ;
}
}
static diff_nameStatus ( repoPath : string , sha1? : string , sha2? : string , options : { filter? : string } = { } ) {
static diff_nameStatus ( repoPath : string , sha1? : string , sha2? : string , options : { filter? : string } = { } ) {
@ -408,7 +397,7 @@ export class Git {
static async log_resolve ( repoPath : string , fileName : string , ref : string ) {
static async log_resolve ( repoPath : string , fileName : string , ref : string ) {
try {
try {
const data = await gitCommand ( { cwd : repoPath , willHandleErrors : true } , ` log ` , ` --full-history ` , ` -M ` , ` -n1 ` , ` --no-merges ` , ` --format=%H ` , ref , ` -- ` , fileName ) ;
const data = await gitCommandCore ( { cwd : repoPath } , ` log ` , ` --full-history ` , ` -M ` , ` -n1 ` , ` --no-merges ` , ` --format=%H ` , ref , ` -- ` , fileName ) ;
return data . trim ( ) ;
return data . trim ( ) ;
}
}
catch {
catch {
@ -440,7 +429,7 @@ export class Git {
}
}
try {
try {
const data = await gitCommand ( { cwd : repoPath , willHandleErrors : true } , . . . params , fileName ) ;
const data = await gitCommandCore ( { cwd : repoPath } , . . . params , fileName ) ;
return data . trim ( ) ;
return data . trim ( ) ;
}
}
catch {
catch {
@ -458,10 +447,10 @@ export class Git {
static async revparse ( repoPath : string , ref : string ) : Promise < string | undefined > {
static async revparse ( repoPath : string , ref : string ) : Promise < string | undefined > {
try {
try {
const data = await gitCommand ( { cwd : repoPath , willHandleErrors : true } , ` rev-parse ` , ref ) ;
const data = await gitCommandCore ( { cwd : repoPath } , ` rev-parse ` , ref ) ;
return data . trim ( ) ;
return data . trim ( ) ;
}
}
catch ( ex ) {
catch {
return undefined ;
return undefined ;
}
}
}
}
@ -469,9 +458,9 @@ export class Git {
static async revparse_currentBranch ( repoPath : string ) : Promise < string | undefined > {
static async revparse_currentBranch ( repoPath : string ) : Promise < string | undefined > {
const params = [ ` rev-parse ` , ` --abbrev-ref ` , ` --symbolic-full-name ` , ` @ ` , ` @{u} ` ] ;
const params = [ ` rev-parse ` , ` --abbrev-ref ` , ` --symbolic-full-name ` , ` @ ` , ` @{u} ` ] ;
const opts = { cwd : repoPath , willHandleErrors : true } as Git CommandOptions;
const opts = { cwd : repoPath } as CommandOptions ;
try {
try {
const data = await gitCommand ( opts , . . . params ) ;
const data = await gitCommandCore ( opts , . . . params ) ;
return data ;
return data ;
}
}
catch ( ex ) {
catch ( ex ) {
@ -482,7 +471,7 @@ export class Git {
if ( /ambiguous argument '.*?': unknown revision or path not in the working tree/ . test ( msg ) ) {
if ( /ambiguous argument '.*?': unknown revision or path not in the working tree/ . test ( msg ) ) {
try {
try {
const params = [ ` symbolic-ref ` , ` -q ` , ` --short ` , ` HEAD ` ] ;
const params = [ ` symbolic-ref ` , ` -q ` , ` --short ` , ` HEAD ` ] ;
const data = await gitCommand ( opts , . . . params ) ;
const data = await gitCommandCore ( opts , . . . params ) ;
return data ;
return data ;
}
}
catch {
catch {
@ -496,7 +485,7 @@ export class Git {
static async revparse_toplevel ( cwd : string ) : Promise < string | undefined > {
static async revparse_toplevel ( cwd : string ) : Promise < string | undefined > {
try {
try {
const data = await gitCommand ( { cwd : cwd , willHandleErrors : true } , 'rev-parse' , '--show-toplevel' ) ;
const data = await gitCommandCore ( { cwd : cwd } , 'rev-parse' , '--show-toplevel' ) ;
return data . trim ( ) ;
return data . trim ( ) ;
}
}
catch {
catch {
@ -512,13 +501,13 @@ export class Git {
}
}
if ( Git . isUncommitted ( ref ) ) throw new Error ( ` sha= ${ ref } is uncommitted ` ) ;
if ( Git . isUncommitted ( ref ) ) throw new Error ( ` sha= ${ ref } is uncommitted ` ) ;
const opts = { cwd : root , encoding : options.encoding || 'utf8' , willHandleErrors : true } as Git CommandOptions;
const opts = { cwd : root , encoding : options.encoding || 'utf8' } as CommandOptions ;
const args = ref . endsWith ( ':' )
const args = ref . endsWith ( ':' )
? ` ${ ref } ./ ${ file } `
? ` ${ ref } ./ ${ file } `
: ` ${ ref } :./ ${ file } ` ;
: ` ${ ref } :./ ${ file } ` ;
try {
try {
const data = await gitCommand ( opts , 'show' , args ) ;
const data = await gitCommandCore ( opts , 'show' , args ) ;
return data ;
return data ;
}
}
catch ( ex ) {
catch ( ex ) {