Skip to content

Commit

Permalink
Merge branch 'master' into t/73
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamil Piechaczek committed Nov 23, 2018
2 parents 5c11361 + ee61afb commit 8ec819f
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 17 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## [0.9.0](https://github.com/cksource/mgit2/compare/v0.8.1...v0.9.0) (2018-11-22)

### Features

* The `mgit bootstrap` and `mgit update` commands will try pulling changes twice in case of a network hang-up. Closes [#87](https://github.com/cksource/mgit2/issues/87). ([47e6840](https://github.com/cksource/mgit2/commit/47e6840))


## [0.8.1](https://github.com/cksource/mgit2/compare/v0.8.0...v0.8.1) (2018-11-19)

### Bug fixes
Expand Down
49 changes: 44 additions & 5 deletions lib/commands/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ module.exports = {

// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
log.info( `Package "${ data.packageName }" was not found. Cloning...` );

return this._clonePackage( {
path: destinationPath,
name: data.packageName,
url: data.repository.url,
branch: data.repository.branch
}, data.mgitOptions );
}, data.mgitOptions, { log } );
}

return execCommand.execute( getExecData( 'git status -s' ) )
Expand Down Expand Up @@ -178,12 +180,13 @@ module.exports = {
* @param {String} packageDetails.path An absolute path where the package should be cloned.
* @param {String} packageDetails.branch A branch on which the repository will be checked out after cloning.
* @param {Options} mgitOptions Options resolved by mgit.
* @param {Object} options Additional options which aren't related to mgit.
* @param {Logger} options.log Logger
* @param {Boolean} [options.doNotTryAgain=false] If set to `true`, bootstrap command won't be executed again.
* @returns {Promise}
*/
_clonePackage( packageDetails, mgitOptions ) {
const log = require( '../utils/log' )();

log.info( `Package "${ packageDetails.name }" was not found. Cloning...` );
_clonePackage( packageDetails, mgitOptions, options ) {
const log = options.log;

const command = [
`git clone --progress "${ packageDetails.url }" "${ packageDetails.path }"`,
Expand Down Expand Up @@ -215,6 +218,42 @@ module.exports = {
}

return commandOutput;
} )
.catch( error => {
if ( isRemoteHungUpError( error ) && !options.doNotTryAgain ) {
return delay( 5000 ).then( () => {
return this._clonePackage( packageDetails, mgitOptions, { log, doNotTryAgain: true } );
} );
}

log.error( error );

return Promise.reject( { logs: log.all() } );
} );
}
};

// See: https://github.com/cksource/mgit2/issues/87
function isRemoteHungUpError( error ) {
if ( typeof error != 'string' ) {
error = error.toString();
}

const fatalErrors = error.split( '\n' )
.filter( message => message.startsWith( 'fatal:' ) )
.map( message => message.trim() );

if ( fatalErrors.length !== 3 ) {
return false;
}

return fatalErrors[ 0 ] == 'fatal: The remote end hung up unexpectedly' &&
fatalErrors[ 1 ] == 'fatal: early EOF' &&
fatalErrors[ 2 ] == 'fatal: index-pack failed';
}

function delay( ms ) {
return new Promise( resolve => {
setTimeout( resolve, ms );
} );
}
8 changes: 8 additions & 0 deletions lib/utils/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ module.exports = function log() {
return logger;
};

/**
* @typedef {Object} Logger
*
* @property {Function} info A function that informs about process.
*
* @property {Function} error A function that informs about errors.
*/

/**
* @typedef {Object} Logs
*
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mgit2",
"version": "0.8.1",
"version": "0.9.0",
"description": "A tool for managing projects build using multiple repositories.",
"keywords": [
"git",
Expand Down
69 changes: 58 additions & 11 deletions tests/commands/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ describe( 'commands/sync', () => {
};

mgitOptions = {
cwd: __dirname,
packages: __dirname + '/packages',
cwd: '/tmp',
packages: '/tmp/packages',
resolverPath: 'PATH_TO_RESOLVER'
};

Expand Down Expand Up @@ -92,10 +92,10 @@ describe( 'commands/sync', () => {

// Clone the repository.
expect( cloneCommand[ 0 ] ).to.equal(
`git clone --progress "git@github.com/organization/test-package.git" "${ __dirname }/packages/test-package"`
'git clone --progress "git@github.com/organization/test-package.git" "/tmp/packages/test-package"'
);
// Change the directory to cloned package.
expect( cloneCommand[ 1 ] ).to.equal( `cd "${ __dirname }/packages/test-package"` );
expect( cloneCommand[ 1 ] ).to.equal( 'cd "/tmp/packages/test-package"' );
// And check out to proper branch.
expect( cloneCommand[ 2 ] ).to.equal( 'git checkout --quiet master' );

Expand Down Expand Up @@ -135,6 +135,55 @@ describe( 'commands/sync', () => {
expect( response.packages ).to.deep.equal( [ 'test-bar' ] );
} );
} );

it( 'tries to install missing packages once again if git ends with unexpected error', function() {
this.timeout( 5500 );

stubs.fs.existsSync.returns( false );

stubs.shell.onFirstCall().returns( Promise.reject( [
'exec: Cloning into \'/some/path\'...',
'remote: Enumerating objects: 6, done.',
'remote: Counting objects: 100% (6/6), done.',
'remote: Compressing objects: 100% (6/6), done.',
'packet_write_wait: Connection to 000.00.000.000 port 22: Broken pipe',
'fatal: The remote end hung up unexpectedly',
'fatal: early EOF',
'fatal: index-pack failed'
].join( '\n' ) ) );

stubs.shell.onSecondCall().returns( Promise.resolve( 'Git clone log.' ) );

return syncCommand.execute( commandData )
.then( response => {
expect( stubs.shell.calledTwice ).to.equal( true );

const firstCommand = stubs.shell.firstCall.args[ 0 ].split( ' && ' );

// Clone the repository for the first time. It failed.
expect( firstCommand[ 0 ] )
.to.equal( 'git clone --progress "git@github.com/organization/test-package.git" "/tmp/packages/test-package"' );
// Change the directory to cloned package.
expect( firstCommand[ 1 ] ).to.equal( 'cd "/tmp/packages/test-package"' );
// And check out to proper branch.
expect( firstCommand[ 2 ] ).to.equal( 'git checkout --quiet master' );

const secondCommand = stubs.shell.secondCall.args[ 0 ].split( ' && ' );

// Clone the repository for the second time. It succeed.
expect( secondCommand[ 0 ] )
.to.equal( 'git clone --progress "git@github.com/organization/test-package.git" "/tmp/packages/test-package"' );
// Change the directory to cloned package.
expect( secondCommand[ 1 ] ).to.equal( 'cd "/tmp/packages/test-package"' );
// And check out to proper branch.
expect( secondCommand[ 2 ] ).to.equal( 'git checkout --quiet master' );

expect( response.logs.info ).to.deep.equal( [
'Package "test-package" was not found. Cloning...',
'Git clone log.'
] );
} );
} );
} );

describe( 'the package is already installed', () => {
Expand Down Expand Up @@ -359,15 +408,14 @@ describe( 'commands/sync', () => {
} );

syncCommand.afterExecute( processedPackages, null, mgitOptions );
consoleLog.restore();

expect( consoleLog.callCount ).to.equal( 3 );
expect( consoleLog.callCount ).to.equal( 4 );
expect( consoleLog.firstCall.args[ 0 ] ).to.match( /2 packages have been processed\./ );
expect( consoleLog.secondCall.args[ 0 ] ).to.match(
/Paths to directories listed below are skipped by mgit because they are not defined in "mgit\.json":/
);
expect( consoleLog.thirdCall.args[ 0 ] ).to.match( / {2}- .*\/packages\/package-3/ );

consoleLog.restore();
} );

it( 'informs about differences between packages in directory and defined in mgit.json for scopes packages', () => {
Expand Down Expand Up @@ -418,15 +466,14 @@ describe( 'commands/sync', () => {
} );

syncCommand.afterExecute( processedPackages, null, mgitOptions );
consoleLog.restore();

expect( consoleLog.callCount ).to.equal( 3 );
expect( consoleLog.callCount ).to.equal( 5 );
expect( consoleLog.firstCall.args[ 0 ] ).to.match( /2 packages have been processed\./ );
expect( consoleLog.secondCall.args[ 0 ] ).to.match(
/Paths to directories listed below are skipped by mgit because they are not defined in "mgit\.json":/
);
expect( consoleLog.thirdCall.args[ 0 ] ).to.match( / {2}- .*\/packages\/@foo\/package-3/ );

consoleLog.restore();
expect( consoleLog.getCall( 3 ).args[ 0 ] ).to.match( / {2}- .*\/packages\/@foo\/package-3/ );
} );

it( 'does not inform about differences between packages in directory and defined in mgit.json if everything seems to be ok', () => {
Expand Down

0 comments on commit 8ec819f

Please sign in to comment.