//@tag foundation,core
//@define Ext.Number
//@require Ext.Array

/**
 * @class Ext.Number
 *
 * A collection of useful static methods to deal with numbers
 * @singleton
 */

(function() {

var isToFixedBroken = (0.9).toFixed() !== '1';

Ext.Number = {
    /**
     * Checks whether or not the passed number is within a desired range.  If the number is already within the
     * range it is returned, otherwise the min or max value is returned depending on which side of the range is
     * exceeded. Note that this method returns the constrained value but does not change the current number.
     * @param {Number} number The number to check
     * @param {Number} min The minimum number in the range
     * @param {Number} max The maximum number in the range
     * @return {Number} The constrained value if outside the range, otherwise the current value
     */
    constrain: function(number, min, max) {
        number = parseFloat(number);

        if (!isNaN(min)) {
            number = Math.max(number, min);
        }
        if (!isNaN(max)) {
            number = Math.min(number, max);
        }
        return number;
    },

    /**
     * Snaps the passed number between stopping points based upon a passed increment value.
     * @param {Number} value The unsnapped value.
     * @param {Number} increment The increment by which the value must move.
     * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
     * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
     * @return {Number} The value of the nearest snap target.
     */
    snap : function(value, increment, minValue, maxValue) {
        var newValue = value,
            m;

        if (!(increment && value)) {
            return value;
        }
        m = value % increment;
        if (m !== 0) {
            newValue -= m;
            if (m * 2 >= increment) {
                newValue += increment;
            } else if (m * 2 < -increment) {
                newValue -= increment;
            }
        }
        return Ext.Number.constrain(newValue, minValue,  maxValue);
    },

    /**
     * Formats a number using fixed-point notation
     * @param {Number} value The number to format
     * @param {Number} precision The number of digits to show after the decimal point
     */
    toFixed: function(value, precision) {
        if (isToFixedBroken) {
            precision = precision || 0;
            var pow = Math.pow(10, precision);
            return (Math.round(value * pow) / pow).toFixed(precision);
        }

        return value.toFixed(precision);
    },

    /**
     * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
     * it is not.

Ext.Number.from('1.23', 1); // returns 1.23
Ext.Number.from('abc', 1); // returns 1

     * @param {Object} value
     * @param {Number} defaultValue The value to return if the original value is non-numeric
     * @return {Number} value, if numeric, defaultValue otherwise
     */
    from: function(value, defaultValue) {
        if (isFinite(value)) {
            value = parseFloat(value);
        }

        return !isNaN(value) ? value : defaultValue;
    }
};

})();

/**
 * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
 *
 * @deprecated 2.0.0 Replaced by Ext.Number.from
 * @member Ext
 * @method num
 */
Ext.num = function() {
    return Ext.Number.from.apply(this, arguments);
};


/**
 * Extend object
 * @param {Object} target Target object
 * @param {Object} source Source object
 * @returns {Object}
 * @private
 */
function _extend(target, source) {
  var keys = Object.keys(source);

  for (var i = 0; i < keys.length; i++) {
    target[keys[i]] = source[keys[i]];
  }

  return target;
}

function Class() {
}

/**
 * Create new Class or extend exists
 * @static
 * @param  {Object} [_prototype]        Prototype object for new Class
 * @param  {Object} [_staticProperties] Object with static properties for new Class
 * @param  {Array}  [_mixins]           Array of mixins which need to inject in new Class prototype
 * @return {Object}                    Returns new Class
 *
 * @example
 * Class.create(prototype)
 * Class.create(prototype, staticProperties)
 * Class.create(prototype, staticProperties, [mixins])
 */
Class.create = function (_prototype, _staticProperties, _mixins) {
  var prototype = _prototype || {};
  var staticProperties = _staticProperties || {};
  var mixins = _mixins || [];

  function Constructor() {
    return this.init && this.init.apply(this, arguments);
  }

  Constructor.prototype = Object.create(this.prototype);

  Object.defineProperty(Constructor, 'constructor', {
    value: Constructor,
    enumerable: false,
    writable: false,
    configurable: true
  });

  Object.defineProperty(Constructor, 'extend', {
    value: Class.create,
    enumerable: false,
    writable: false,
    configurable: false
  });

  _extend(Constructor, staticProperties);
  _extend(Constructor.prototype, prototype);

  for (var i = mixins.length - 1; i >= 0; i--) {
    _extend(Constructor.prototype, mixins[i]);
  }

  return Constructor;
};

module.exports = Class;


/**
  * This library provides functionality to convert a JSON schema into a SQL
  * Query part, called dynamic columns. Currently this is only interesting
  * together with MariaDB.
  *
  * As MariaDB doesn't support array types, as of the 29th September 2015,
  * we don't handle arrays explicitly and just store them by their index, as the
  * specified key.
  * When extracting arrays back from a database, you going to receive an object
  * instead. You should be easily able to handle this, if you mind this little
  * fact.
  *
  * MariaDBs dynamic columns are created by `COLUMN_CREATE` and you can
  * manipulate the created column with the methods like `COLUMN_ADD`, to add,
  * update and delete columns. This library returns the JSON schema transposed
  * to the matching `COLUMN_CREATE(...)` SQL query string.
  *
  * This library gets useless, as soon as MariaDB supports parsing JSON, but
  * until we finally have this feature, this library will help you.
  */

module.exports = {

  /**
    * A non recursive way to resolve our json schema to the create query.
    *
    * We handle Numbers by default as double, booleans as unsigned integer
    * and everything else as string.
    *
    * If you need or want to specify the types, you can set the second parameter
    * to call the type defined variant instead.
    *
    * @return string
    */
  createQuery: function( json, typeSpecification, array ) {

      var keys,
          current = json,
          nested = [],
          nestedKeys = [],
          levels = [],
          query = 'COLUMN_CREATE(',
          root = true,
          curNest = '',
          curItem,
          item = 0,
          level = 0;

    while( current ) {

      keys = Object.keys( current );
      var len = keys.length;
      var _l;
      var deepestLevel = 1;

      for( var i = 0; i < len; ++i ) {

        if( ( _l = current[ keys[ i ] ] ) === null || _l === undefined ) {
          continue;
        }

        if( typeof( _l ) === 'object' ) {

          // skip empty objects, we do not store them,
          // this needs us to set NULL instead
          if( !Object.keys( _l ).length ) {

            _l = null;
            if( !typeSpecification ) {
              query += '\'' + keys[ i ].replace(/\\/g, '\\\\')
                .replace(/\u0008/g, '\\b')
                .replace(/'/g, '\\\'')
                .replace(/\u0000/g, '\\0') + '\', NULL, ';
            }
            else {
              query += '?, NULL, ';
              array.push( keys[ i ] );
            }

            continue;
          }

          nested.push( _l );
          nestedKeys.push( keys[ i ] );
          if ( curItem !== item ) {
              curItem = item;
              ++level;

              levels.push( { level: level - 1, nestSep: curNest + ')' } );
          }
          else {
            levels.push( { level: level - 1, nestSep: ')' } );
          }

          //save nesting level
        }
        else {

          var queryType = typeof( _l );
          if( !typeSpecification ) {
            query += '\'' + keys[ i ].replace(/\\/g, '\\\\')
              .replace(/\u0008/g, '\\b')
              .replace(/'/g, '\\\'')
              .replace(/\u0000/g, '\\0') + '\', ';
          }
          else {
            query += '?, ';
            array.push( keys[ i ] );
          }

          switch( queryType ) {

            case 'boolean':
              query += ( ( _l === true ) ? 1 : 0 ) + ' AS unsigned integer, ';
              break;

            case 'number':
              query += _l + ' AS double, ';
              break;

            default:

              if( !typeSpecification ) {

                query += '\'' + _l.replace(/\\/g, '\\\\')
                .replace(/\u0008/g, '\\b')
                .replace(/'/g, '\\\'')
                .replace(/\u0000/g, '\\0') + '\', ';
              }
              else {
                query += '?, ';
                array.push( _l );
              }
              break;
          }
        }
      }

      if( root ) {

        root = false;
      }
      else {

        if( level === 0 )
          query = query.substring( 0, query.length - 2 ) + curNest + ', ';
      }

      if( nested.length !== 0 ) {

        if( !typeSpecification ) {
          query += '\'' + nestedKeys.pop().replace(/\\/g, '\\\\')
            .replace(/\u0008/g, '\\b')
            .replace(/'/g, '\\\'')
            .replace(/\u0000/g, '\\0') + '\', COLUMN_CREATE(';
        }
        else {
          query += '?, COLUMN_CREATE(';
          array.push( nestedKeys.pop() );
        }
      }
      else {
        query = query.substring( 0, query.length - 2 );
      }

      current = nested.pop();
      ++item;

      //restore nesting level
      level = levels.pop() || 0;
      if ( level ) {

        curNest = level.nestSep;
        level = level.level;
      }

      deepestLevel = level + 1;
    }

    query += ')';

    return query;
  },

  /**
    * We create out of specified schema a update query for dynamic columns.
    * Queries can get quite complex on complex schemas.
    *
    * @return string
    */
  updateQuery: function( inputColumn, json, typeSpecification ) {

      var keys,
          current = json,
          nested = [],
          nestedKeys = [],
          nestedString = [],
          levels = [],
          query = 'COLUMN_ADD(`' + inputColumn + '`, ',
          root = true,
          accessString = 'COLUMN_GET(',
          baseString = 'COLUMN_GET(`' + inputColumn + '`, ',
          curString = baseString,
          curNest = '',
          item = 0,
          level = 0;

    while( current ) {

      keys = Object.keys( current );
      var len = keys.length;
      var _l;
      var curItem = undefined;

      for( var i = 0; i < len; ++i ) {

        if( ( _l = current[ keys[ i ] ] ) !== null &&
            _l !== undefined &&
            typeof( _l )  === 'object' ) {

          nested.push( _l );
          nestedKeys.push( keys[ i ] );
          if( root ) {

            nestedString.push( curString + '\'' + keys[ i ].replace(/\\/g, '\\\\')
              .replace(/\u0008/g, '\\b')
              .replace(/'/g, '\\\'')
              .replace(/\u0000/g, '\\0') +
              '\' AS CHAR), ');
          }
          else {

            nestedString.push( accessString + curString + '\'' + keys[ i ].replace(/\\/g, '\\\\')
              .replace(/\u0008/g, '\\b')
              .replace(/'/g, '\\\'')
              .replace(/\u0000/g, '\\0') +
              '\' AS CHAR), ');
          }

          if ( curItem !== item ) {
              curItem = item;
              ++level;
          }

          //save nesting level
          levels.push( { level: level - 1, nestSep: curNest + ')' } );
        }
        else {

          var queryType = typeof( _l );

          if( _l === null || _l === undefined )
            _l = 'NULL';

          query += '\'' + keys[ i ].replace(/\\/g, '\\\\')
            .replace(/\u0008/g, '\\b')
            .replace(/'/g, '\\\'')
            .replace(/\u0000/g, '\\0') + '\', ';

          switch( queryType ) {

            case 'boolean':
              query += ( ( _l === true ) ? 1 : 0 ) + ' AS unsigned integer, ';
              break;

            case 'number':
              query += _l + ' AS double, ';
              break;

            default:
              query += '\'' + _l.replace('\'', '\\\'')
                .replace(/\u0000/g, '\\0') + '\', ';
              break;
          }
        }
      }

      curString = nestedString.pop();

      if( root ) {

        root = false;
      }
      else {

        if( level === 0 )
          query = query.substring( 0, query.length - 2 ) + curNest + ', ';
      }

      if( nested.length !== 0 ) {
        query += '\'' + nestedKeys.pop().replace(/\\/g, '\\\\')
          .replace(/\u0008/g, '\\b')
          .replace(/'/g, '\\\'')
          .replace(/\u0000/g, '\\0') + '\', COLUMN_ADD(' + curString;
      }
      else {
        query = query.substring( 0, query.length - 2 );
      }

      current = nested.pop();
      ++item;

      //restore nesting level
      level = levels.pop() || 0;
      if ( level ) {

        curNest = level.nestSep;
        level = level.level;
      }
    }

    query += ')';

    return query;
  }
};


/**
 * @class Ext.data.NodeInterface
 * This class is meant to be used as a set of methods that are applied to the prototype of a
 * Record to decorate it with a Node API. This means that models used in conjunction with a tree
 * will have all of the tree related methods available on the model. In general this class will
 * not be used directly by the developer. This class also creates extra fields on the model if
 * they do not exist, to help maintain the tree state and UI. These fields are:
 *
 * - parentId
 * - index
 * - depth
 * - expanded
 * - expandable
 * - checked
 * - leaf
 * - cls
 * - iconCls
 * - root
 * - isLast
 * - isFirst
 * - allowDrop
 * - allowDrag
 * - loaded
 * - loading
 * - href
 * - hrefTarget
 * - qtip
 * - qtitle
 */
Ext.define('Ext.data.NodeInterface', {
    requires: ['Ext.data.Field', 'Ext.data.ModelManager'],

    alternateClassName: 'Ext.data.Node',

    /**
     * @property nextSibling
     * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
     */

    /**
     * @property previousSibling
     * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
     */

    /**
     * @property parentNode
     * A reference to this node's parent node. `null` if this node is the root node.
     */

    /**
     * @property lastChild
     * A reference to this node's last child node. `null` if this node has no children.
     */

    /**
     * @property firstChild
     * A reference to this node's first child node. `null` if this node has no children.
     */

    /**
     * @property childNodes
     * An array of this nodes children.  Array will be empty if this node has no children.
     */

    statics: {
        /**
         * This method allows you to decorate a Record's prototype to implement the NodeInterface.
         * This adds a set of methods, new events, new properties and new fields on every Record
         * with the same Model as the passed Record.
         * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
         * @static
         */
        decorate: function(record) {
            if (!record.isNode) {
                // Apply the methods and fields to the prototype
                var mgr = Ext.data.ModelManager,
                    modelName = record.modelName,
                    modelClass = mgr.getModel(modelName),
                    newFields = [],
                    i, newField, len;

                // Start by adding the NodeInterface methods to the Model's prototype
                modelClass.override(this.getPrototypeBody());

                newFields = this.applyFields(modelClass, [
                    {name: 'parentId',   type: 'string',  defaultValue: null},
                    {name: 'index',      type: 'int',     defaultValue: 0},
                    {name: 'depth',      type: 'int',     defaultValue: 0,     persist: false},
                    {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
                    {name: 'expandable', type: 'bool',    defaultValue: true,  persist: false},
                    {name: 'checked',    type: 'auto',    defaultValue: null},
                    {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
                    {name: 'cls',        type: 'string',  defaultValue: null,  persist: false},
                    {name: 'iconCls',    type: 'string',  defaultValue: null,  persist: false},
                    {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
                    {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
                    {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
                    {name: 'allowDrop',  type: 'boolean', defaultValue: true,  persist: false},
                    {name: 'allowDrag',  type: 'boolean', defaultValue: true,  persist: false},
                    {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
                    {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
                    {name: 'href',       type: 'string',  defaultValue: null,  persist: false},
                    {name: 'hrefTarget', type: 'string',  defaultValue: null,  persist: false},
                    {name: 'qtip',       type: 'string',  defaultValue: null,  persist: false},
                    {name: 'qtitle',     type: 'string',  defaultValue: null,  persist: false}
                ]);

                len = newFields.length;

                // We set a dirty flag on the fields collection of the model. Any reader that
                // will read in data for this model will update their extractor functions.
                modelClass.getFields().isDirty = true;

                // Set default values
                for (i = 0; i < len; ++i) {
                    newField = newFields[i];
                    if (record.get(newField.getName()) === undefined) {
                        record.data[newField.getName()] = newField.getDefaultValue();
                    }
                }
            }

            if (!record.isDecorated) {
                record.isDecorated = true;

                Ext.applyIf(record, {
                    firstChild: null,
                    lastChild: null,
                    parentNode: null,
                    previousSibling: null,
                    nextSibling: null,
                    childNodes: []
                });

                record.enableBubble([
                    /**
                     * @event append
                     * Fires when a new child node is appended.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} node The newly appended node.
                     * @param {Number} index The index of the newly appended node.
                     */
                    "append",

                    /**
                     * @event remove
                     * Fires when a child node is removed.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} node The removed node.
                     */
                    "remove",

                    /**
                     * @event move
                     * Fires when this node is moved to a new location in the tree.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} oldParent The old parent of this node.
                     * @param {Ext.data.NodeInterface} newParent The new parent of this node.
                     * @param {Number} index The index it was moved to.
                     */
                    "move",

                    /**
                     * @event insert
                     * Fires when a new child node is inserted.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} node The child node inserted.
                     * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before.
                     */
                    "insert",

                    /**
                     * @event beforeappend
                     * Fires before a new child is appended, return `false` to cancel the append.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} node The child node to be appended.
                     */
                    "beforeappend",

                    /**
                     * @event beforeremove
                     * Fires before a child is removed, return `false` to cancel the remove.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} node The child node to be removed.
                     */
                    "beforeremove",

                    /**
                     * @event beforemove
                     * Fires before this node is moved to a new location in the tree. Return `false` to cancel the move.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface} oldParent The parent of this node.
                     * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to.
                     * @param {Number} index The index it is being moved to.
                     */
                    "beforemove",

                     /**
                      * @event beforeinsert
                      * Fires before a new child is inserted, return false to cancel the insert.
                      * @param {Ext.data.NodeInterface} this This node
                      * @param {Ext.data.NodeInterface} node The child node to be inserted
                      * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
                      */
                    "beforeinsert",

                    /**
                     * @event expand
                     * Fires when this node is expanded.
                     * @param {Ext.data.NodeInterface} this The expanding node.
                     */
                    "expand",

                    /**
                     * @event collapse
                     * Fires when this node is collapsed.
                     * @param {Ext.data.NodeInterface} this The collapsing node.
                     */
                    "collapse",

                    /**
                     * @event beforeexpand
                     * Fires before this node is expanded.
                     * @param {Ext.data.NodeInterface} this The expanding node.
                     */
                    "beforeexpand",

                    /**
                     * @event beforecollapse
                     * Fires before this node is collapsed.
                     * @param {Ext.data.NodeInterface} this The collapsing node.
                     */
                    "beforecollapse",

                    /**
                     * @event sort
                     * Fires when this node's childNodes are sorted.
                     * @param {Ext.data.NodeInterface} this This node.
                     * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
                     */
                    "sort",

                    'load'
                ]);
            }

            return record;
        },

        applyFields: function(modelClass, addFields) {
            var modelPrototype = modelClass.prototype,
                fields = modelPrototype.fields,
                keys = fields.keys,
                ln = addFields.length,
                addField, i,
                newFields = [];

            for (i = 0; i < ln; i++) {
                addField = addFields[i];
                if (!Ext.Array.contains(keys, addField.name)) {
                    addField = Ext.create('Ext.data.Field', addField);

                    newFields.push(addField);
                    fields.add(addField);
                }
            }

            return newFields;
        },

        getPrototypeBody: function() {
            return {
                isNode: true,

                /**
                 * Ensures that the passed object is an instance of a Record with the NodeInterface applied
                 * @return {Boolean}
                 * @private
                 */
                createNode: function(node) {
                    if (Ext.isObject(node) && !node.isModel) {
                        node = Ext.data.ModelManager.create(node, this.modelName);
                    }
                    // Make sure the node implements the node interface
                    return Ext.data.NodeInterface.decorate(node);
                },

                /**
                 * Returns true if this node is a leaf
                 * @return {Boolean}
                 */
                isLeaf : function() {
                    return this.get('leaf') === true;
                },

                /**
                 * Sets the first child of this node
                 * @private
                 * @param {Ext.data.NodeInterface} node
                 */
                setFirstChild : function(node) {
                    this.firstChild = node;
                },

                /**
                 * Sets the last child of this node
                 * @private
                 * @param {Ext.data.NodeInterface} node
                 */
                setLastChild : function(node) {
                    this.lastChild = node;
                },

                /**
                 * Updates general data of this node like isFirst, isLast, depth. This
                 * method is internally called after a node is moved. This shouldn't
                 * have to be called by the developer unless they are creating custom
                 * Tree plugins.
                 * @return {Boolean}
                 */
                updateInfo: function(silent) {
                    var me = this,
                        parentNode = me.parentNode,
                        isFirst = (!parentNode ? true : parentNode.firstChild == me),
                        isLast = (!parentNode ? true : parentNode.lastChild == me),
                        depth = 0,
                        parent = me,
                        children = me.childNodes,
                        ln = children.length,
                        i;

                    while (parent.parentNode) {
                        ++depth;
                        parent = parent.parentNode;
                    }

                    me.beginEdit();
                    me.set({
                        isFirst: isFirst,
                        isLast: isLast,
                        depth: depth,
                        index: parentNode ? parentNode.indexOf(me) : 0,
                        parentId: parentNode ? parentNode.getId() : null
                    });
                    me.endEdit(silent);
                    if (silent) {
                        me.commit(silent);
                    }

                    for (i = 0; i < ln; i++) {
                        children[i].updateInfo(silent);
                    }
                },

                /**
                 * Returns `true` if this node is the last child of its parent.
                 * @return {Boolean}
                 */
                isLast : function() {
                   return this.get('isLast');
                },

                /**
                 * Returns `true` if this node is the first child of its parent.
                 * @return {Boolean}
                 */
                isFirst : function() {
                   return this.get('isFirst');
                },

                /**
                 * Returns `true` if this node has one or more child nodes, else `false`.
                 * @return {Boolean}
                 */
                hasChildNodes : function() {
                    return !this.isLeaf() && this.childNodes.length > 0;
                },

                /**
                 * Returns `true` if this node has one or more child nodes, or if the `expandable`
                 * node attribute is explicitly specified as `true`, otherwise returns `false`.
                 * @return {Boolean}
                 */
                isExpandable : function() {
                    var me = this;

                    if (me.get('expandable')) {
                        return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
                    }
                    return false;
                },

                /**
                 * Insert node(s) as the last child node of this node.
                 *
                 * If the node was previously a child node of another parent node, it will be removed from that node first.
                 *
                 * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append.
                 * @return {Ext.data.NodeInterface} The appended node if single append, or `null` if an array was passed.
                 */
                appendChild : function(node, suppressEvents, suppressNodeUpdate) {
                    var me = this,
                        i, ln,
                        index,
                        oldParent,
                        ps;

                    // if passed an array or multiple args do them one by one
                    if (Ext.isArray(node)) {
                        for (i = 0, ln = node.length; i < ln; i++) {
                            me.appendChild(node[i], suppressEvents, suppressNodeUpdate);
                        }
                    } else {
                        // Make sure it is a record
                        node = me.createNode(node);

                        if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
                            return false;
                        }

                        index = me.childNodes.length;
                        oldParent = node.parentNode;

                        // it's a move, make sure we move it cleanly
                        if (oldParent) {
                            if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
                                return false;
                            }
                            oldParent.removeChild(node, null, false, true);
                        }

                        index = me.childNodes.length;
                        if (index === 0) {
                            me.setFirstChild(node);
                        }

                        me.childNodes.push(node);
                        node.parentNode = me;
                        node.nextSibling = null;

                        me.setLastChild(node);

                        ps = me.childNodes[index - 1];
                        if (ps) {
                            node.previousSibling = ps;
                            ps.nextSibling = node;
                            ps.updateInfo(suppressNodeUpdate);
                        } else {
                            node.previousSibling = null;
                        }

                        node.updateInfo(suppressNodeUpdate);

                        // As soon as we append a child to this node, we are loaded
                        if (!me.isLoaded()) {
                            me.set('loaded', true);
                        }
                        // If this node didn't have any childnodes before, update myself
                        else if (me.childNodes.length === 1) {
                            me.set('loaded', me.isLoaded());
                        }

                        if (suppressEvents !== true) {
                            me.fireEvent("append", me, node, index);

                            if (oldParent) {
                                node.fireEvent("move", node, oldParent, me, index);
                            }
                        }

                        return node;
                    }
                },

                /**
                 * Returns the bubble target for this node.
                 * @private
                 * @return {Object} The bubble target.
                 */
                getBubbleTarget: function() {
                    return this.parentNode;
                },

                /**
                 * Removes a child node from this node.
                 * @param {Ext.data.NodeInterface} node The node to remove.
                 * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
                 * @return {Ext.data.NodeInterface} The removed node.
                 */
                removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
                    var me = this,
                        index = me.indexOf(node);

                    if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
                        return false;
                    }

                    // remove it from childNodes collection
                    Ext.Array.erase(me.childNodes, index, 1);

                    // update child refs
                    if (me.firstChild == node) {
                        me.setFirstChild(node.nextSibling);
                    }
                    if (me.lastChild == node) {
                        me.setLastChild(node.previousSibling);
                    }

                    if (suppressEvents !== true) {
                        me.fireEvent("remove", me, node);
                    }

                    // update siblings
                    if (node.previousSibling) {
                        node.previousSibling.nextSibling = node.nextSibling;
                        node.previousSibling.updateInfo(suppressNodeUpdate);
                    }
                    if (node.nextSibling) {
                        node.nextSibling.previousSibling = node.previousSibling;
                        node.nextSibling.updateInfo(suppressNodeUpdate);
                    }

                    // If this node suddenly doesn't have childnodes anymore, update myself
                    if (!me.childNodes.length) {
                        me.set('loaded', me.isLoaded());
                    }

                    if (destroy) {
                        node.destroy(true);
                    } else {
                        node.clear();
                    }

                    return node;
                },

                /**
                 * Creates a copy (clone) of this Node.
                 * @param {String} [newId] A new id, defaults to this Node's id.
                 * @param {Boolean} [deep] If passed as `true`, all child Nodes are recursively copied into the new Node.
                 * If omitted or `false`, the copy will have no child Nodes.
                 * @return {Ext.data.NodeInterface} A copy of this Node.
                 */
                copy: function(newId, deep) {
                    var me = this,
                        result = me.callOverridden(arguments),
                        len = me.childNodes ? me.childNodes.length : 0,
                        i;

                    // Move child nodes across to the copy if required
                    if (deep) {
                        for (i = 0; i < len; i++) {
                            result.appendChild(me.childNodes[i].copy(true));
                        }
                    }
                    return result;
                },

                /**
                 * Clear the node.
                 * @private
                 * @param {Boolean} destroy `true` to destroy the node.
                 */
                clear : function(destroy) {
                    var me = this;

                    // clear any references from the node
                    me.parentNode = me.previousSibling = me.nextSibling = null;
                    if (destroy) {
                        me.firstChild = me.lastChild = null;
                    }
                },

                /**
                 * Destroys the node.
                 */
                destroy : function(silent) {
                    /*
                     * Silent is to be used in a number of cases
                     * 1) When setRoot is called.
                     * 2) When destroy on the tree is called
                     * 3) For destroying child nodes on a node
                     */
                    var me = this,
                        options = me.destroyOptions;

                    if (silent === true) {
                        me.clear(true);
                        Ext.each(me.childNodes, function(n) {
                            n.destroy(true);
                        });
                        me.childNodes = null;
                        delete me.destroyOptions;
                        me.callOverridden([options]);
                    } else {
                        me.destroyOptions = silent;
                        // overridden method will be called, since remove will end up calling destroy(true);
                        me.remove(true);
                    }
                },

                /**
                 * Inserts the first node before the second node in this nodes `childNodes` collection.
                 * @param {Ext.data.NodeInterface} node The node to insert.
                 * @param {Ext.data.NodeInterface} refNode The node to insert before (if `null` the node is appended).
                 * @return {Ext.data.NodeInterface} The inserted node.
                 */
                insertBefore : function(node, refNode, suppressEvents) {
                    var me = this,
                        index     = me.indexOf(refNode),
                        oldParent = node.parentNode,
                        refIndex  = index,
                        ps;

                    if (!refNode) { // like standard Dom, refNode can be null for append
                        return me.appendChild(node);
                    }

                    // nothing to do
                    if (node == refNode) {
                        return false;
                    }

                    // Make sure it is a record with the NodeInterface
                    node = me.createNode(node);

                    if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
                        return false;
                    }

                    // when moving internally, indexes will change after remove
                    if (oldParent == me && me.indexOf(node) < index) {
                        refIndex--;
                    }

                    // it's a move, make sure we move it cleanly
                    if (oldParent) {
                        if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
                            return false;
                        }
                        oldParent.removeChild(node);
                    }

                    if (refIndex === 0) {
                        me.setFirstChild(node);
                    }

                    Ext.Array.splice(me.childNodes, refIndex, 0, node);
                    node.parentNode = me;

                    node.nextSibling = refNode;
                    refNode.previousSibling = node;

                    ps = me.childNodes[refIndex - 1];
                    if (ps) {
                        node.previousSibling = ps;
                        ps.nextSibling = node;
                        ps.updateInfo();
                    } else {
                        node.previousSibling = null;
                    }

                    node.updateInfo();

                    if (!me.isLoaded()) {
                        me.set('loaded', true);
                    }
                    // If this node didn't have any childnodes before, update myself
                    else if (me.childNodes.length === 1) {
                        me.set('loaded', me.isLoaded());
                    }

                    if (suppressEvents !== true) {
                        me.fireEvent("insert", me, node, refNode);

                        if (oldParent) {
                            node.fireEvent("move", node, oldParent, me, refIndex, refNode);
                        }
                    }

                    return node;
                },

                /**
                 * Insert a node into this node.
                 * @param {Number} index The zero-based index to insert the node at.
                 * @param {Ext.data.Model} node The node to insert.
                 * @return {Ext.data.Model} The record you just inserted.
                 */
                insertChild: function(index, node) {
                    var sibling = this.childNodes[index];
                    if (sibling) {
                        return this.insertBefore(node, sibling);
                    }
                    else {
                        return this.appendChild(node);
                    }
                },

                /**
                 * Removes this node from its parent.
                 * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
                 * @return {Ext.data.NodeInterface} this
                 */
                remove : function(destroy, suppressEvents) {
                    var parentNode = this.parentNode;

                    if (parentNode) {
                        parentNode.removeChild(this, destroy, suppressEvents, true);
                    }
                    return this;
                },

                /**
                 * Removes all child nodes from this node.
                 * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
                 * @return {Ext.data.NodeInterface} this
                 */
                removeAll : function(destroy, suppressEvents) {
                    var cn = this.childNodes,
                        n;

                    while ((n = cn[0])) {
                        this.removeChild(n, destroy, suppressEvents);
                    }
                    return this;
                },

                /**
                 * Returns the child node at the specified index.
                 * @param {Number} index
                 * @return {Ext.data.NodeInterface}
                 */
                getChildAt : function(index) {
                    return this.childNodes[index];
                },

                /**
                 * Replaces one child node in this node with another.
                 * @param {Ext.data.NodeInterface} newChild The replacement node.
                 * @param {Ext.data.NodeInterface} oldChild The node to replace.
                 * @return {Ext.data.NodeInterface} The replaced node.
                 */
                replaceChild : function(newChild, oldChild, suppressEvents) {
                    var s = oldChild ? oldChild.nextSibling : null;

                    this.removeChild(oldChild, suppressEvents);
                    this.insertBefore(newChild, s, suppressEvents);
                    return oldChild;
                },

                /**
                 * Returns the index of a child node.
                 * @param {Ext.data.NodeInterface} child
                 * @return {Number} The index of the node or -1 if it was not found.
                 */
                indexOf : function(child) {
                    return Ext.Array.indexOf(this.childNodes, child);
                },

                /**
                 * Gets the hierarchical path from the root of the current node.
                 * @param {String} field (optional) The field to construct the path from. Defaults to the model `idProperty`.
                 * @param {String} [separator=/] (optional) A separator to use.
                 * @return {String} The node path
                 */
                getPath: function(field, separator) {
                    field = field || this.idProperty;
                    separator = separator || '/';

                    var path = [this.get(field)],
                        parent = this.parentNode;

                    while (parent) {
                        path.unshift(parent.get(field));
                        parent = parent.parentNode;
                    }
                    return separator + path.join(separator);
                },

                /**
                 * Returns depth of this node (the root node has a depth of 0).
                 * @return {Number}
                 */
                getDepth : function() {
                    return this.get('depth');
                },

                /**
                 * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
                 * will be the args provided or the current node. If the function returns `false` at any point,
                 * the bubble is stopped.
                 * @param {Function} fn The function to call.
                 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
                 */
                bubble : function(fn, scope, args) {
                    var p = this;
                    while (p) {
                        if (fn.apply(scope || p, args || [p]) === false) {
                            break;
                        }
                        p = p.parentNode;
                    }
                },

                //<deprecated since=0.99>
                cascade: function() {
                    Ext.Logger.deprecate('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');

                    return this.cascadeBy.apply(this, arguments);
                },
                //</deprecated>

                /**
                 * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
                 * will be the args provided or the current node. If the function returns false at any point,
                 * the cascade is stopped on that branch.
                 * @param {Function} fn The function to call
                 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
                 */
                cascadeBy : function(fn, scope, args) {
                    if (fn.apply(scope || this, args || [this]) !== false) {
                        var childNodes = this.childNodes,
                            length     = childNodes.length,
                            i;

                        for (i = 0; i < length; i++) {
                            childNodes[i].cascadeBy(fn, scope, args);
                        }
                    }
                },

                /**
                 * Iterates the child nodes of this node, calling the specified function with each node. The arguments to the function
                 * will be the args provided or the current node. If the function returns false at any point,
                 * the iteration stops.
                 * @param {Function} fn The function to call.
                 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node in the iteration.
                 * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
                 */
                eachChild : function(fn, scope, args) {
                    var childNodes = this.childNodes,
                        length     = childNodes.length,
                        i;

                    for (i = 0; i < length; i++) {
                        if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
                            break;
                        }
                    }
                },

                /**
                 * Finds the first child that has the attribute with the specified value.
                 * @param {String} attribute The attribute name.
                 * @param {Object} value The value to search for.
                 * @param {Boolean} deep (Optional) `true` to search through nodes deeper than the immediate children.
                 * @return {Ext.data.NodeInterface} The found child or `null` if none was found.
                 */
                findChild : function(attribute, value, deep) {
                    return this.findChildBy(function() {
                        return this.get(attribute) == value;
                    }, null, deep);
                },

                /**
                 * Finds the first child by a custom function. The child matches if the function passed returns `true`.
                 * @param {Function} fn A function which must return `true` if the passed Node is the required Node.
                 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the Node being tested.
                 * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children.
                 * @return {Ext.data.NodeInterface} The found child or null if `none` was found.
                 */
                findChildBy : function(fn, scope, deep) {
                    var cs = this.childNodes,
                        len = cs.length,
                        i = 0, n, res;

                    for (; i < len; i++) {
                        n = cs[i];
                        if (fn.call(scope || n, n) === true) {
                            return n;
                        }
                        else if (deep) {
                            res = n.findChildBy(fn, scope, deep);
                            if (res !== null) {
                                return res;
                            }
                        }
                    }

                    return null;
                },

                /**
                 * Returns `true` if this node is an ancestor (at any point) of the passed node.
                 * @param {Ext.data.NodeInterface} node
                 * @return {Boolean}
                 */
                contains : function(node) {
                    return node.isAncestor(this);
                },

                /**
                 * Returns `true` if the passed node is an ancestor (at any point) of this node.
                 * @param {Ext.data.NodeInterface} node
                 * @return {Boolean}
                 */
                isAncestor : function(node) {
                    var p = this.parentNode;
                    while (p) {
                        if (p == node) {
                            return true;
                        }
                        p = p.parentNode;
                    }
                    return false;
                },

                /**
                 * Sorts this nodes children using the supplied sort function.
                 * @param {Function} sortFn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
                 * @param {Boolean} recursive Whether or not to apply this sort recursively.
                 * @param {Boolean} suppressEvent Set to true to not fire a sort event.
                 */
                sort: function(sortFn, recursive, suppressEvent) {
                    var cs  = this.childNodes,
                        ln = cs.length,
                        i, n;

                    if (ln > 0) {
                        Ext.Array.sort(cs, sortFn);
                        for (i = 0; i < ln; i++) {
                            n = cs[i];
                            n.previousSibling = cs[i-1];
                            n.nextSibling = cs[i+1];

                            if (i === 0) {
                                this.setFirstChild(n);
                            }
                            if (i == ln - 1) {
                                this.setLastChild(n);
                            }

                            n.updateInfo(suppressEvent);

                            if (recursive && !n.isLeaf()) {
                                n.sort(sortFn, true, true);
                            }
                        }

                        this.notifyStores('afterEdit', ['sorted'], {sorted: 'sorted'});

                        if (suppressEvent !== true) {
                            this.fireEvent('sort', this, cs);
                        }
                    }
                },

                /**
                 * Returns `true` if this node is expanded.
                 * @return {Boolean}
                 */
                isExpanded: function() {
                    return this.get('expanded');
                },

                /**
                 * Returns `true` if this node is loaded.
                 * @return {Boolean}
                 */
                isLoaded: function() {
                    return this.get('loaded');
                },

                /**
                 * Returns `true` if this node is loading.
                 * @return {Boolean}
                 */
                isLoading: function() {
                    return this.get('loading');
                },

                /**
                 * Returns `true` if this node is the root node.
                 * @return {Boolean}
                 */
                isRoot: function() {
                    return !this.parentNode;
                },

                /**
                 * Returns `true` if this node is visible.
                 * @return {Boolean}
                 */
                isVisible: function() {
                    var parent = this.parentNode;
                    while (parent) {
                        if (!parent.isExpanded()) {
                            return false;
                        }
                        parent = parent.parentNode;
                    }
                    return true;
                },

                /**
                 * Expand this node.
                 * @param {Function} recursive (Optional) `true` to recursively expand all the children.
                 * @param {Function} callback (Optional) The function to execute once the expand completes.
                 * @param {Object} scope (Optional) The scope to run the callback in.
                 */
                expand: function(recursive, callback, scope) {
                    var me = this;

                    if (!me.isLeaf()) {
                        if (me.isLoading()) {
                            me.on('expand', function() {
                                me.expand(recursive, callback, scope);
                            }, me, {single: true});
                        }
                        else {
                            if (!me.isExpanded()) {
                                // The TreeStore actually listens for the beforeexpand method and checks
                                // whether we have to asynchronously load the children from the server
                                // first. Thats why we pass a callback function to the event that the
                                // store can call once it has loaded and parsed all the children.
                                me.fireAction('expand', [this], function() {
                                    me.set('expanded', true);
                                    Ext.callback(callback, scope || me, [me.childNodes]);
                                });
                            }
                            else {
                                Ext.callback(callback, scope || me, [me.childNodes]);
                            }
                        }
                    } else {
                        Ext.callback(callback, scope || me);
                    }
                },

                /**
                 * Collapse this node.
                 * @param {Function} recursive (Optional) `true` to recursively collapse all the children.
                 * @param {Function} callback (Optional) The function to execute once the collapse completes.
                 * @param {Object} scope (Optional) The scope to run the callback in.
                 */
                collapse: function(recursive, callback, scope) {
                    var me = this;

                    // First we start by checking if this node is a parent
                    if (!me.isLeaf() && me.isExpanded()) {
                        this.fireAction('collapse', [me], function() {
                            me.set('expanded', false);
                            Ext.callback(callback, scope || me, [me.childNodes]);
                        });
                    } else {
                        Ext.callback(callback, scope || me, [me.childNodes]);
                    }
                }
            };
        }
    }
});


'use strict';

exports.start = function (firstFn, startFnOpt) {

  var errorCount = 0;
  var defaultOptions = {
    // default error handler will throw errors, to fail fast
    // (and encourage explicit error handling)
    onError: function (err) {
      throw err;
    },
    wrapWithTry: false
  };

  var allowedOptions = [
    'onError',
    'wrapWithTry'
  ];
  var makeOpts = function (fnOptsArg) {
    var fnOpts = fnOptsArg || {};
    var opts = {};
    var opt;
    for (var i = 0; i < allowedOptions.length; i += 1) {
      opt = allowedOptions[i];
      opts[opt] = typeof fnOpts[opt] === 'undefined' ? defaultOptions[opt] : fnOpts[opt];
    }
    return opts;
  };

  var head = {
    fns: [firstFn || function (callback) {
      return callback();
    }],
    opts: [startFnOpt]
  };
  var tail = head;

  // call the next target function(s), if possible, when appropriate
  var dispatch = function () {
    if (head.open === 0 && head.next) { // advance the head, if possible
      head = head.next;
    }
    head.open = head.open || 0;
    head.callbacks = head.callbacks || [false];

    var dispatchInternal = function (fnIndex) {
      head.open += 1;
      setImmediate(function () {
        var opts = makeOpts(head.opts[fnIndex]);
        if (errorCount > 0) {
          return;
        }
        var handleError = function (err) {
          errorCount += 1;
          opts.onError(err, errorCount);
        };
        var callback = function (err) {
          setImmediate(function () {
            if (head.callbacks[fnIndex]) {
              handleError(new Error('function called back twice!'));
            }
            head.callbacks[fnIndex] = true;
            if (err) {
              handleError(err);
            }
            head.open -= 1;
            setImmediate(dispatch);
          });
        };
        if (opts.wrapWithTry) {
          try {
            head.fns[fnIndex](callback);
          } catch (err) {
            head.open -= 1;
            handleError(err);
          }
        } else {
          head.fns[fnIndex](callback);
        }
      });
    };

    for (head.fnIndex = head.fnIndex || 0; head.fnIndex < head.fns.length; head.fnIndex += 1) {
      dispatchInternal(head.fnIndex);
    }
  };

  // the controller object closes over the necessary context
  // to allow more tasks to be added
  var controller = {
    // set the next fn, allowing the tail to advance
    thenStart: function (nextTargetFn, fnOpt) {
      tail.next = {
        fns: [nextTargetFn],
        opts: [fnOpt]
      };
      tail = tail.next;
      dispatch();
      return controller;
    },
    andStart: function (nextTargetFn, fnOpt) {
      tail.fns.push(nextTargetFn);
      tail.opts.push(fnOpt);
      dispatch();
      return controller;
    },
    onError: function (handler) {
      defaultOptions.onError = handler;
      return controller;
    },
    changeDefaults: function (newDefaults) {
      defaultOptions = makeOpts(newDefaults);
      return controller;
    }
  };

  // start the first task
  dispatch();

  return controller;
};


'use strict'

var path = require('path')
var _assign = require('lodash.assign')
var sh = require('shelljs')
var snakeCase = require('snake-case')

module.exports = Deployor

/**
 * @description
 * This is the deployor system
 *
 * @param {Object} options - The deployment options
 *
 * @constructor
 */
function Deployor (options) {
  this.options = _assign(
    {},
    Deployor.defaults,
    options
  )

  // TODO move this
  Object.keys(this.options).forEach(function (key) {
    process.env[snakeCase(key).toUpperCase()] = this.options[key]
  }.bind(this))
}

Deployor.defaults = {
  cwd: process.cwd(),
  branch: 'master'
}

Deployor.verbose = false
sh.config.silent = !Deployor.verbose

var e = Deployor.exec = function (cmd) {
  if (Deployor.verbose) {
    var realCmd = cmd.replace(/\$(\S+)/g, function (match, envKey) {
      return process.env[envKey] || '$' + envKey
    })
    console.log('$ ', realCmd)
  }
  return sh.exec(cmd)
}

Deployor.cloneRepoBranch = function cloneRepoBranch (options) {
  if (!options.cloneLocation) {
    options.cloneLocation = path.join('/', 'tmp', path.basename(process.cwd()))
  }
  options.cloneLocation = path.resolve(options.cloneLocation)

  var res

  // Get the remote.origin.url
  res = e('git config --get remote.origin.url')
  if (res.code > 0) throw new Error('Can\'t get no remote.origin.url !')

  options.repoUrl = options.repoUrl ||
    process.env.REPO ||
    String(res.output).split(/[\n\r]/).shift()

  if (!options.repoUrl) throw new Error('No repo link !')

  //

  // TODO move this
  Object.keys(options).forEach(function (key) {
    process.env[snakeCase(key).toUpperCase()] = options[key]
  })

  //

  // Remove tmp file
  e('rm -rf $CLONE_LOCATION')

  //

  // Clone the repo branch to a special location (clonedRepoLocation)
  res = e('git clone --branch=$BRANCH --single-branch $REPO_URL $CLONE_LOCATION')
  if (res.code > 0) {
    // try again without banch options
    res = e('git clone $REPO_URL $CLONE_LOCATION')
    if (res.code > 0) throw new Error('Can\'t clone !')
  }

  //

  // Go to the cloneLocation
  sh.cd(options.cloneLocation)

  if (sh.pwd() !== options.cloneLocation) {
    throw new Error('Can\'t access to the clone location : ' + options.cloneLocation + ' from ' + sh.pwd())
  }

  e('git clean -f -d')
  e('git fetch origin')

  // Checkout a branch (create an orphan if it doesn't exist on the remote).
  res = e('git ls-remote --exit-code . origin/$BRANCH')
  if (res.code > 0) {
    // branch doesn't exist, create an orphan
    res = e('git checkout --orphan $BRANCH')
    if (res.code > 0) throw new Error('Can\'t clone !')
  } else {
    // branch exists on remote, hard reset
    e('git checkout $BRANCH')
  }

  return new Deployor(options)
}

Deployor.prototype = {
  extraCleanUp: function () {
    // Empty the clone
    e('git rm --ignore-unmatch -rfq \'\\.[^\\.]*\' *')
  },
  copy: function (srcPath) {
    process.env.SRC_PATH = path.resolve(path.join(this.options.cwd, srcPath))

    var res
    // Copie the targeted files
    res = e('cp -rf $SRC_PATH/* ./')
    if (res && res.code > 0) throw new Error(res.output)
    res = e('cp -rf "$SRC_PATH/.[a-zA-Z0-9]*" ./')
  },
  commit: function (commitMessage) {
    process.env.COMMIT_MESSAGE = commitMessage
    e('git add .')
    e('git commit -m "$COMMIT_MESSAGE"')
  },
  tag: function (tagMessage) {
    process.env.TAG_MESSAGE = tagMessage
    var res = e('git tag $TAG_MESSAGE')
    if (res && res.code > 0) console.log('Can\'t tag failed, continuing !')
  },
  push: function () {
    var res = e('git push --tags origin $BRANCH')
    if (res && res.code > 0) throw new Error(res.output)
  }
}