# Code coverage report for lib/git/branch.js

## Statements: 100% (94 / 94) Branches: 100% (22 / 22) Functions: 100% (21 / 21) Lines: 100% (91 / 91) Ignored: 6 statements, 4 branches

All files » lib/git/ » branch.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    1871
    1
    1
    1

    1

    1
    1

    1

    47
    47
    47
    47
    47
    47

    47
    47
    47
    47

    1
    47
    47

    47

    47
    1
    1

    46

    1
    1
    1
    1

    1
    1

    1
    38
    38
    38

    38
    1
    1

    38

    1
    28

    1
    46

    1
    86

    1

    40

    40
    3
    3
    3

    37
    37

    37

    37

    37
    37

    37
    3
    3
    3

    3

    37

    37

    1

    49

    49

    49

    49
    49

    2
    2
    1

    1
    1

    1
    1

    1
    1
    1

    47

    47
    1
    1

    47
    47

    47
    46
    46

    1

     var fs = require('fs');
    var path = require('path');
    var mkdirp = require('mkdirp');
    var rimraf = require('rimraf');

    var logger = require('../logging.js');

    var consul_broker = require('../consul');
    var git_commands = require('./commands.js');

    function Branch(repo_config, name) {
      // Immutable properties
      Object.defineProperty(this, 'url', {value: repo_config.url});
      Object.defineProperty(this, 'repo_name', {value: repo_config.name });
      Object.defineProperty(this, 'name', {value: name });
      Object.defineProperty(this, 'branch_parent', {value: repo_config.local_store + path.sep + repo_config.name});
      Object.defineProperty(this, 'branch_directory', {value: this.branch_parent + path.sep + name});
      Object.defineProperty(this, 'include_branch_name', {
        // If include_branch_name is not set, assume true.  Otherwise, identity check the value against true.
        value: repo_config['include_branch_name'] == undefined || repo_config['include_branch_name'] === true
      });

      // Writable properties
      Object.defineProperty(this, 'busy', {value: false, writable: true});
      Object.defineProperty(this, 'update_in_progress', {value: false, writable: true});
      Object.defineProperty(this, 'pending_ref_change', {value: undefined, writable: true});
      Object.defineProperty(this, 'pending_callbacks', {value: [], writable: true});
    }

    Branch.prototype.clone = function(cb) {
      var this_obj = this;
      this_obj.busy = true;

      git_commands.clone(this_obj.url, this_obj.name, this_obj.branch_parent, function(err, output) {
        /* istanbul ignore next */
        if (err) {
          this_obj.busy = false;
          return cb(err);
        }
        consul_broker.handleRefChange(this_obj, cb);
      });
    };

    /**
     * Delete the local copy of this branch.  If the branch has been corrupted, this is the cleanest way
     * to reset things.
     */
    Branch.prototype.purgeBranchCache = function(cb) {
      var this_obj = this;
      logger.warn("Purging branch cache %s for branch %s in repo %s", this.branch_directory, this.name, this.repo_name);
      rimraf(this.branch_directory, function(err) {
        /* istanbul ignore if */
        Iif (err) logger.error("Failed to purge branch cache %s", this_obj.branch_directory);
        cb();
      });
    };

    /**
     * Fetch the branch up to a given commit.  After this operation, the local copy should be up to date
     * with the branch at that commit.
     */
    Branch.prototype.pull = function(cb) {
      var this_obj = this;
      this_obj.busy = true;
      git_commands.pull(this_obj.branch_directory, function(err, output) {
        /* istanbul ignore if */
        Iif (err) {
          this_obj.busy = false;
          return cb(err);
        }
        consul_broker.handleRefChange(this_obj, cb);
      });
    };

    Branch.prototype.listChangedFiles = function(from_ref, to_ref, cb) {
      git_commands.listChangedFiles(from_ref, to_ref, this.branch_directory, cb);
    };

    Branch.prototype.listAllFiles = function(cb) {
      git_commands.listAllFiles(this.branch_directory, cb);
    };

    Branch.prototype.getCurrentRef = function(cb) {
      git_commands.getCurrentRef(this.branch_directory, cb);
    };

    Branch.prototype.handleRefChange = function(target_ref, cb) {

      var this_obj = this;

      // If an update is pending
      if (this.update_in_progress) {
        this.pending_ref_change = target_ref;
        this.pending_callbacks.push(cb);
        return;
      }

      // Once we wrap this callback, we are signalling that an update is in progress.
      this_obj.update_in_progress = true;
      this_obj.busy = true;

      // We want to lock handleRefChange so that only one is happening per branch_manager at a time.  This
      // function allows us to manage that locking behavior and will re-run handleRefChange if one or more are
      // queued up during execution of handleRefChange.
      this.pull(function(err, output) {
        // Callback seen.  Update no longer in progress.
        this_obj.update_in_progress = false;

        // Grab a local copy of pending callbacks and reset the array.
        var my_pending_callbacks = this_obj.pending_callbacks;
        this_obj.pending_callbacks = [];

        // If a request to handle a ref_change came in while we were processing a prior ref_change, run
        // handleRefChange again.
        if (this_obj.pending_ref_change) {
          this_obj.handleRefChange(this_obj.pending_ref_change, function(err) {
            my_pending_callbacks.forEach(function(pending_callback) {
              pending_callback(err);
            });
          });

          // Reset the pending_refchange because it's been copied onto the callstack of handleRefChange.
          this_obj.pending_ref_change = undefined;
        }

        this_obj.busy = false;

        // Pass values to the original callback
        cb(err, output);
      });
    };

    Branch.prototype.init = function(cb) {

      var this_obj = this;

      logger.info("Initting branch %s %s", this_obj.branch_parent, this_obj.branch_directory);

      this_obj.busy = true;

      // Check to see if the branch directory is already present.
      fs.stat(this_obj.branch_directory, function(err, stat) {
        if (stat) {
          // This branch was already cloned.  Do a pull to make sure it's up to date.
          return this_obj.getCurrentRef(function(err, ref) {
            if (err) {
              this_obj.busy = false;
              // If we have a failure on getCurrentRef, we suspect that the local copy is hosed.  Purge it
              // so that it gets reloaded the next time git2consul starts.
              return this_obj.purgeBranchCache(function() {
                cb(err);
              });
            }

            this_obj.pull(function(err) {
              this_obj.busy = false;
              /* istanbul ignore if */
              Iif (err) return cb(err);
              logger.info("Initialized branch %s from %s", this_obj.name, this_obj.repo_name);
              cb();
            });
          });
        }

        // Create the branch_parent_directory (if necessary) and clone the branch from there.
        mkdirp(this_obj.branch_parent, function(err) {
          /* istanbul ignore next */
          Iif (err) {
            this_obj.busy = false;
            return cb(err);
          }

          // Clone the branch
          this_obj.clone(function(err) {
            this_obj.busy = false;
            /* istanbul ignore if */
            if (err) return cb(err);
            logger.info("Initialized branch %s from %s", this_obj.name, this_obj.repo_name);
            cb();
          });
        });
      });
    };

    module.exports = Branch;