123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- var logger = process.logging || require('./log');
- var fs = require('fs');
- var path = require('path');
- var events = require('events');
- var diff = require('diff');
- var prompt = require('../actions/prompt');
- var log = logger('conflicter');
- var async = require('async');
- var isBinaryFile = require('isbinaryfile');
- var chalk = require('chalk');
- var conflicter = module.exports = Object.create(events.EventEmitter.prototype);
- conflicter.conflicts = [];
- conflicter.add = function add(conflict) {
- if (typeof conflict === 'string') {
- conflict = {
- file: conflict,
- content: fs.readFileSync(conflict, 'utf8')
- };
- }
- if (!conflict.file) {
- throw new Error('Missing conflict.file option');
- }
- if (conflict.content === undefined) {
- throw new Error('Missing conflict.content option');
- }
- this.conflicts.push(conflict);
- return this;
- };
- conflicter.reset = function reset() {
- this.conflicts = [];
- return this;
- };
- conflicter.pop = function pop() {
- return this.conflicts.pop();
- };
- conflicter.shift = function shift() {
- return this.conflicts.shift();
- };
- conflicter.resolve = function resolve(cb) {
- var resolveConflicts = function (conflict) {
- return function (next) {
- if (!conflict) {
- return next();
- }
- conflicter.collision(conflict.file, conflict.content, function (status) {
- conflicter.emit('resolved:' + conflict.file, {
- status: status,
- callback: next
- });
- });
- };
- };
- async.series(this.conflicts.map(resolveConflicts), function (err) {
- if (err) {
- cb();
- return self.emit('error', err);
- }
- conflicter.reset();
- cb();
- }.bind(this));
- };
- conflicter._ask = function (filepath, content, cb) {
- // for this particular use case, might use prompt module directly to avoid
- // the additional "Are you sure?" prompt
- var self = this;
- var config = [{
- type: 'expand',
- message: 'Overwrite ' + filepath + '?',
- choices: [{
- key: 'y',
- name: 'overwrite',
- value: function (cb) {
- log.force(filepath);
- return cb('force');
- }
- }, {
- key: 'n',
- name: 'do not overwrite',
- value: function (cb) {
- log.skip(filepath);
- return cb('skip');
- }
- }, {
- key: 'a',
- name: 'overwrite this and all others',
- value: function (cb) {
- log.force(filepath);
- self.force = true;
- return cb('force');
- }
- }, {
- key: 'x',
- name: 'abort',
- value: function (cb) {
- log.writeln('Aborting ...');
- return process.exit(0);
- }
- }, {
- key: 'd',
- name: 'show the differences between the old and the new',
- value: function (cb) {
- console.log(conflicter.diff(fs.readFileSync(filepath, 'utf8'), content));
- return self._ask(filepath, content, cb);
- }
- }],
- name: 'overwrite'
- }];
- process.nextTick(function () {
- this.emit('prompt', config);
- this.emit('conflict', filepath);
- }.bind(this));
- prompt(config, function (result) {
- result.overwrite(function (action) {
- cb(action);
- });
- });
- };
- conflicter.collision = function collision(filepath, content, cb) {
- var self = this;
- if (!fs.existsSync(filepath)) {
- log.create(filepath);
- return cb('create');
- }
- var encoding = null;
- if (!isBinaryFile(path.resolve(filepath))) {
- encoding = 'utf8';
- }
- var actual = fs.readFileSync(path.resolve(filepath), encoding);
- // In case of binary content, `actual` and `content` are `Buffer` objects,
- // we just can't compare those 2 objects with standard `===`,
- // so we convert each binary content to an hexadecimal string first, and then compare them with standard `===`
- //
- // For not binary content, we can directly compare the 2 strings this way
- if ((!encoding && (actual.toString('hex') === content.toString('hex'))) ||
- (actual === content)) {
- log.identical(filepath);
- return cb('identical');
- }
- if (self.force) {
- log.force(filepath);
- return cb('force');
- }
- log.conflict(filepath);
- conflicter._ask(filepath, content, cb);
- };
- conflicter.colorDiffAdded = chalk.bgGreen;
- conflicter.colorDiffRemoved = chalk.bgRed;
- // below is borrowed code from visionmedia's excellent mocha (and its reporter)
- conflicter.diff = function _diff(actual, expected) {
- var msg = diff.diffLines(actual, expected).map(function (str) {
- if (str.added) {
- return conflicter.colorLines('Added', str.value);
- }
- if (str.removed) {
- return conflicter.colorLines('Removed', str.value);
- }
- return str.value;
- }).join('');
- // legend
- msg = '\n' +
- conflicter.colorDiffRemoved('removed') +
- ' ' +
- conflicter.colorDiffAdded('added') +
- '\n\n' +
- msg +
- '\n';
- return msg;
- };
- conflicter.colorLines = function colorLines(name, str) {
- return str.split('\n').map(function (str) {
- return conflicter['colorDiff' + name](str);
- }).join('\n');
- };
|