Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented root repository feature #162

Merged
merged 9 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ CLI options:
Default: null

--preset Uses an alternative set of dependencies defined in the config file.

--skip-root Allows skipping root repository when executing command,
if "$rootRepository" is defined in the config file.
```

All these options can also be specified in `mrgit.json` (options passed through CLI takes precedence):
Expand Down Expand Up @@ -187,6 +190,41 @@ Example:
}
```

### The `$rootRepository` option

`mrgit` allows executing git commands in the root repository as well. If such behavior is desired, configure the `$rootRepository` option in the config. When configured, the requested command (e.g. `pull`) will be executed in the root repository and in all defined dependencies. Additionally, the `$rootRepository` key can be included in any preset, and will work accordingly to presets logic.

If the `$rootRepository` option is configured you can still disable this feature with the `--skip-root` CLI option.

Not all commands support execution in the root repository. If a command does not support this feature, it is executed normally, without affecting the root repository. Currently supported commands are:

- `checkout`
- `commit`
- `diff`
- `exec`
- `fetch`
- `pull`
- `push`
- `status`
- `sync`

Example config:

```json
{
"packages": "/workspace/modules",
"$rootRepository": "cksource/root-repository",
"dependencies": {
"foo": "bar"
},
"presets": {
"dev": {
"$rootRepository": "cksource/root-repository#dev"
}
}
}
```

### Recursive cloning

When the `--recursive` option is used `mrgit` will clone repositories recursively. First, it will clone the `dependencies` specified in `mrgit.json` and, then, their `dependencies` and `devDependencies` specified in `package.json` files located in cloned repositories.
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ function handleCli() {

${ y( '--preset' ) } Uses an alternative set of dependencies defined in the config file.

${ y( '--skip-root' ) } Allows skipping root repository when executing command,
if "${ u( '$rootRepository' ) }" is defined in the config file.

${ u( 'Git Options:' ) }
Git options are supported by the following commands: commit, diff, fetch, push.
Type "mrgit [command] -h" in order to see which options are supported.
Expand Down
2 changes: 2 additions & 0 deletions lib/commands/close.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const buildOptions = require( 'minimist-options' );
const minimist = require( 'minimist' );

module.exports = {
name: 'close',

get helpMessage() {
const {
italic: i,
Expand Down
2 changes: 2 additions & 0 deletions lib/commands/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
const chalk = require( 'chalk' );

module.exports = {
name: 'diff',

skipCounter: true,

get helpMessage() {
Expand Down
18 changes: 11 additions & 7 deletions lib/commands/exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const chalk = require( 'chalk' );
const shell = require( '../utils/shell' );

module.exports = {
name: 'exec',

get helpMessage() {
const {
italic: i,
Expand Down Expand Up @@ -43,16 +45,18 @@ module.exports = {
const log = require( '../utils/log' )();

return new Promise( ( resolve, reject ) => {
const newCwd = path.join( data.toolOptions.packages, data.repository.directory );
if ( !data.isRootRepository ) {
const newCwd = path.join( data.toolOptions.packages, data.repository.directory );

// Package does not exist.
if ( !fs.existsSync( newCwd ) ) {
log.error( `Package "${ data.packageName }" is not available. Run "mrgit sync" in order to download the package.` );
// Package does not exist.
if ( !fs.existsSync( newCwd ) ) {
log.error( `Package "${ data.packageName }" is not available. Run "mrgit sync" in order to download the package.` );

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

process.chdir( newCwd );
process.chdir( newCwd );
}

shell( data.arguments[ 0 ] )
.then( stdout => {
Expand Down
12 changes: 8 additions & 4 deletions lib/commands/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const buildOptions = require( 'minimist-options' );
const minimist = require( 'minimist' );

module.exports = {
name: 'fetch',

skipCounter: true,

get helpMessage() {
Expand Down Expand Up @@ -42,11 +44,13 @@ module.exports = {
const log = require( '../utils/log' )();
const execCommand = require( './exec' );

const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );
if ( !data.isRootRepository ) {
const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );

// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
}
}

const options = this._parseArguments( data.arguments );
Expand Down
12 changes: 8 additions & 4 deletions lib/commands/pull.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const path = require( 'upath' );
const chalk = require( 'chalk' );

module.exports = {
name: 'pull',

skipCounter: true,

get helpMessage() {
Expand All @@ -32,11 +34,13 @@ module.exports = {
async execute( data ) {
const execCommand = require( './exec' );

const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );
if ( !data.isRootRepository ) {
const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );

// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
}
}

const commandResponse = await execCommand.execute( getExecData( 'git branch --show-current' ) );
Expand Down
12 changes: 8 additions & 4 deletions lib/commands/push.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const path = require( 'upath' );
const chalk = require( 'chalk' );

module.exports = {
name: 'push',

skipCounter: true,

get helpMessage() {
Expand Down Expand Up @@ -39,11 +41,13 @@ module.exports = {
async execute( data ) {
const execCommand = require( './exec' );

const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );
if ( !data.isRootRepository ) {
const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );

// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
// Package is not cloned.
if ( !fs.existsSync( destinationPath ) ) {
return Promise.resolve( {} );
}
}

const commandResponse = await execCommand.execute( getExecData( 'git branch --show-current' ) );
Expand Down
2 changes: 2 additions & 0 deletions lib/commands/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const updateJsonFile = require( '../utils/updatejsonfile' );
const gitStatusParser = require( '../utils/gitstatusparser' );

module.exports = {
name: 'save',

get helpMessage() {
const {
gray: g,
Expand Down
20 changes: 9 additions & 11 deletions lib/commands/status.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const chalk = require( 'chalk' );
const Table = require( 'cli-table' );
const gitStatusParser = require( '../utils/gitstatusparser' );
const { addRootRepositorySuffix } = require( '../utils/rootrepositoryutils' );

module.exports = {
name: 'status',
Expand Down Expand Up @@ -59,8 +60,13 @@ module.exports = {
packageName = packageName.replace( new RegExp( '^' + packagePrefix ), '' );
}

if ( data.isRootRepository ) {
packageName = addRootRepositorySuffix( packageName, { bold: true } );
}

const commandResponse = {
packageName,
isRootRepository: data.isRootRepository,
status: gitStatusParser( currentBranchStatusResponse.logs.info[ 0 ], currentTag ),
commit: hashResponse.logs.info[ 0 ].slice( 0, 7 ), // Short version of the commit hash.
mrgitBranch: data.repository.branch,
Expand Down Expand Up @@ -99,17 +105,9 @@ module.exports = {
} );

const packagesResponses = Array.from( commandResponses.values() )
.sort( ( a, b ) => {
/* istanbul ignore else */
if ( a.packageName < b.packageName ) {
return -1;
} else if ( a.packageName > b.packageName ) {
return 1;
}

/* istanbul ignore next */
return 0;
} );
.sort( ( a, b ) => a.packageName.localeCompare( b.packageName ) )
// The root package should be at the top.
.sort( ( a, b ) => b.isRootRepository - a.isRootRepository );

for ( const singleResponse of packagesResponses ) {
table.push( createSingleRow( singleResponse ) );
Expand Down
30 changes: 17 additions & 13 deletions lib/commands/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const chalk = require( 'chalk' );
const shell = require( '../utils/shell' );

module.exports = {
name: 'sync',

get helpMessage() {
const {
gray: g,
Expand Down Expand Up @@ -46,19 +48,21 @@ module.exports = {
const log = require( '../utils/log' )();
const execCommand = require( './exec' );

const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );

// 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,
tag: data.repository.tag
}, data.toolOptions, { log } );
if ( !data.isRootRepository ) {
const destinationPath = path.join( data.toolOptions.packages, data.repository.directory );

// 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,
tag: data.repository.tag
}, data.toolOptions, { log } );
}
}

return execCommand.execute( getExecData( 'git status -s' ) )
Expand Down
7 changes: 5 additions & 2 deletions lib/default-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ const parseRepositoryUrl = require( './utils/parserepositoryurl' );
*
* @param {String} packageName Package name.
* @param {Options} options The options object.
* @param {Boolean} isRootRepository
* @returns {Repository|null}
*/
module.exports = function resolver( packageName, options ) {
const repositoryUrl = options.dependencies[ packageName ];
module.exports = function resolver( packageName, options, isRootRepository ) {
const repositoryUrl = isRootRepository ?
options.$rootRepository :
options.dependencies[ packageName ];

if ( !repositoryUrl ) {
return null;
Expand Down
28 changes: 23 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

'use strict';

const fs = require( 'fs' );
const path = require( 'upath' );
const chalk = require( 'chalk' );
const createForkPool = require( './utils/createforkpool' );
Expand All @@ -13,6 +14,7 @@ const getOptions = require( './utils/getoptions' );
const getPackageNames = require( './utils/getpackagenames' );
const getCommandInstance = require( './utils/getcommandinstance' );
const getCwd = require( './utils/getcwd' );
const { addRootRepositorySuffix } = require( './utils/rootrepositoryutils' );

const CHILD_PROCESS_PATH = require.resolve( './utils/child-process' );

Expand All @@ -28,27 +30,35 @@ module.exports = function( args, options ) {
}

const cwd = getCwd();
const mainPackageName = require( path.resolve( cwd, 'package.json' ) ).name;
const startTime = process.hrtime();
const toolOptions = getOptions( options, cwd );
const repositoryResolver = require( toolOptions.resolverPath );
const forkPool = createForkPool( CHILD_PROCESS_PATH );

const mainPkgJsonPath = path.resolve( cwd, 'package.json' );

if ( !fs.existsSync( mainPkgJsonPath ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The occurrence of the package.json file does not say "you are in the .git" repo. It can be true in 99% of cases, but I would check if the .git/ directory is available. The program should finish if it does not exist if the $rootRepository is specified in the configuration.

console.log( chalk.red( '"mrgit.json" file has to be located inside git repository.' ) );

process.exit( 1 );
}

const mainPackageName = require( mainPkgJsonPath ).name;

if ( command.beforeExecute ) {
try {
command.beforeExecute( args, toolOptions );
} catch ( error ) {
console.log( chalk.red( error.message ) );
process.exit( 1 );

return;
process.exit( 1 );
}
}

const processedPackages = new Set();
const commandResponses = new Set();
const packagesWithError = new Set();
const packageNames = getPackageNames( toolOptions );
const packageNames = getPackageNames( toolOptions, command );

let allPackagesNumber = packageNames.length;
let donePackagesNumber = 0;
Expand All @@ -73,14 +83,18 @@ module.exports = function( args, options ) {
return;
}

const isRootRepository = packageName.startsWith( '$' );
packageName = packageName.replace( /^\$/, '' );

processedPackages.add( packageName );

const data = {
packageName,
isRootRepository,
toolOptions,
commandPath: command.path,
arguments: args.slice( 1 ),
repository: repositoryResolver( packageName, toolOptions )
repository: repositoryResolver( packageName, toolOptions, isRootRepository )
};

forkPool.enqueue( data )
Expand All @@ -103,6 +117,10 @@ module.exports = function( args, options ) {
}

if ( returnedData.logs ) {
if ( data.isRootRepository ) {
packageName = addRootRepositorySuffix( packageName );
}

if ( returnedData.logs.error.length ) {
packagesWithError.add( packageName );
}
Expand Down
Loading