<commit_before>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var path = require('path');

/**
  TODOs
  - Add our headers and marker text
  - Handle the context functions
  - Handle the other bundles
  - Create the custom loader
  - Create a manifest with each module and its dependencies
  - Handle source maps - preserve the relative original lines of text so we
    can easily generate a source map for the start of each line
*/

function JupyterLabPlugin(options) {
  options = this.options = options || {};
  this.name = options.name || 'jupyter';
}

// Notes
// We can't replace __webpack_require__ during compilation because
//  it is hard-coded in several places
// We have to set the module id directly because it is used verbatim
// to add the requires and we can't necessarily parse them in context
// dependencies.

// We can't replace the module ids directly, as it messes with compilation
// We can replace them in regular modules (ones with a userRequest)
// Context modules we'll have to assemble ourselves or use a better lookup
// since they are not clearly delimited

// During the emit phase we create our own mangled stuff

JupyterLabPlugin.prototype.apply = function(compiler) {
  var pluginName = this.name;
  var publicPath = compiler.options.output.publicPath;
  if (!publicPath.endsWith('/')) {
    publicPath += '/';
  }

  compiler.plugin('emit', function(compilation, callback) {

    // Explore each chunk (build output):
    compilation.chunks.forEach(function(chunk) {

      var sources = [];
      var manifest = {};

      // Explore each module within the chunk (built inputs):
      chunk.modules.forEach(function(module) {

        // We don't allow externals.
        if (module.external) {
          throw Error('Cannot use externals:', module.userRequest);
        }

        // Parse each module.
        var source = parseModule(compilation, module, pluginName, publicPath);
        sources.push(source);

        // Add dependencies to the manifest.
        var deps = [];
        for (var i = 0; i < module.dependencies.length; i++) {
          var dep = module.dependencies[i];
          if (dep.id && dep.id !== module.id) {
            deps.push(getRequireName(dep));
          }
        }
        manifest[getDefineName(module)] = deps;
      });

      var code = sources.join(',\n\n');

      // Insert this list into the Webpack build as a new file asset:
      // Use the first file name, because the mangling of the chunk
      // file names are private to WebPack.
      var fileName = chunk.files[0];
      compilation.assets[fileName] = {
        source: function() {
          return code;
        },
        size: function() {
          return code.length;
        }
      };

      compilation.assets[fileName + '.manifest'] = {
        source: function() {
          return JSON.stringify(manifest);
        },
        size: function() {
          return JSON.stringify(manifest).length;
        }
      }
    });

    callback();
  });
};

// From a request - find its package root.
function findRoot(request) {
  var orig = request;
  if (path.extname(request)) {
    request = path.dirname(request);
  }
  while (true) {
    try {
      var pkgPath = require.resolve(path.join(request, 'package.json'));
      var pkg = require(pkgPath);
      // Use public packages except for the local package.
      if (!pkg.private || request === process.cwd()) {
        return request;
      }
    } catch (err) {
      // no-op
    }
    var prev = request;
    request = path.dirname(request);
    if (request === prev) {
      throw Error('Could not find package for ' + orig);
    }
  }
}

// Get the package.json associated with a file.
function getPackage(request) {
  var rootPath = findRoot(request);
  return require(path.join(rootPath, 'package.json'));
}

// From a Webpack module object - find a version-mangled define name.
function getDefineName(module) {
  if (!module.context) {
    return '__ignored__';
  }
  var request = module.userRequest || module.context;
  var rootPath = findRoot(request);
  var package = getPackage(rootPath);
  var modPath = request.slice(rootPath.length + 1);
  var name = package.name + '@' + package.version;
  if (modPath) {
    name += '/' + modPath;
  }
  return name;
}

// From a WebPack module object - find its semver-mangled require name.
function getRequireName(module) {
  if (!module.context) {
    return '__ignored__';
  }
  var issuer = module.issuer || module.userRequest;
  var request = module.userRequest || module.context;
  var rootPath = findRoot(request);
  var rootPackage = getPackage(rootPath);
  var issuerPath = findRoot(issuer);
  var issuerPackage = getPackage(issuer);
  var modPath = request.slice(rootPath.length + 1);
  var name = rootPackage.name;
  var semver = issuerPackage.dependencies[name] || rootPackage.version;
  if (issuerPackage.name === rootPackage.name) {
    // Allow patch version increments of itself.
    semver = '~' + rootPackage.version;
  } else if (semver.indexOf('file:') === 0) {
    var sourcePath = path.resolve(issuerPath, semver.slice('file:'.length));
    var sourcePackage = getPackage(sourcePath);
    // Allow patch version increments of local packages.
    semver = '~' + sourcePackage.version;
  }
  var id = name + '@' + semver;
  if (modPath) {
    id += '/' + modPath;
  }
  return id;
}

// Create our own mangled context module source.
function createContextModule(module) {
  // Modeled after Webpack's ContextModule.js.
  var str;
  if (module.dependencies && module.dependencies.length > 0) {
    var map = {};
    module.dependencies.slice().sort(function(a, b) {
      if (a.userRequest === b.userRequest) {
        return 0;
      }
      return a.userRequest < b.userRequest ? -1 : 1;
    }).forEach(function(dep) {
      if (dep.module) {
        map[dep.userRequest] = getRequireName(dep.module);
      }
    });
    str = [
      "\tvar map = ", JSON.stringify(map, null, "\t"), ";\n",
      "function webpackContext(req) {\n",
      "\treturn __webpack_require__(webpackContextResolve(req));\n",
      "};\n",
      "function webpackContextResolve(req) {\n",
      "\treturn map[req] || (function() { throw new Error(\"Cannot find module '\" + req + \"'.\") }());\n",
      "};\n",
      "webpackContext.keys = function webpackContextKeys() {\n",
      "\treturn Object.keys(map);\n",
      "};\n",
      "webpackContext.resolve = webpackContextResolve;\n",
      "module.exports = webpackContext;\n",
      "webpackContext.id = \"" + getDefineName(module) + "\";\n"
    ];
  } else {
    str = [
      "\tfunction webpackContext(req) {\n",
      "\tthrow new Error(\"Cannot find module '\" + req + \"'.\");\n",
      "}\n",
      "webpackContext.keys = function() { return []; };\n",
      "webpackContext.resolve = webpackContext;\n",
      "module.exports = webpackContext;\n",
      "webpackContext.id = \"" + getDefineName(module) + "\";\n"
    ];
  }
  return str.join('\t');
}

// Parse a module.
function parseModule(compilation, module, pluginName, publicPath) {
  var requireName = '__' + pluginName + '_require__';
  var defineName = pluginName + 'Define';
  var source = module.source().source();

  // Regular modules.
  if (module.userRequest) {
    // Handle ensure blocks with and without inline comments.
    // From WebPack dependencies/DepBlockHelpers
    source = handleEnsure(
      compilation, source, publicPath, /__webpack_require__.e\/\*.*?\*\/\((\d+)/
    );
    source = handleEnsure(
      compilation, source, publicPath, /__webpack_require__.e\((\d+)/
    );

    // Replace the require statements with the semver-mangled name.
    var deps = module.getAllModuleDependencies();
    for (var i = 0; i < deps.length; i++) {
      var dep = deps[i];
      var target = '__webpack_require__(' + dep.id + ')';
      var name = getRequireName(dep);
      var replacer = '__webpack_require__("' + name + '")';
      source = source.split(target).join(replacer);
    }
  // Context modules.
  } else if (module.context) {
    source = createContextModule(module);
    source = source.split('webpackContext').join(pluginName + 'Context');
  }

  // Handle public requires.
  var requireP = '__webpack_require__.p';
  var newRequireP = '"' + publicPath + '"';
  source = source.split(requireP).join(newRequireP);

  // Replace the require name with the custom one.
  source = source.split('__webpack_require__').join(requireName);

  // Create our header with a version-mangled defined name.
  var header = pluginName + '.define("' + getDefineName(module);
  header += '", function (module, exports, ' + requireName + ') {\n';

  // Combine code indent.
  return header + source.split('\n').join('\n\t') + '\n})';
}

// Handle an ensure block.
function handleEnsure(compilation, source, publicPath, regex) {
  while (regex.test(source)) {
    var match = source.match(regex);
    var chunkId = match[1];
    var fileName = '';
    // Use the first file name, because the mangling of the chunk
    // file name is private to WebPack.
    compilation.chunks.forEach(function(chunk) {
      if (String(chunk.id) === match[1]) {
        fileName = chunk.files[0];
      }
    });
    var replacement = ('__webpack_require__.e("' + publicPath +
            fileName + '"');
    source = source.replace(regex, replacement);
  }
  return source;
}

module.exports = JupyterLabPlugin;
<commit_msg>Document the plugin and add the define markers<commit_after>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

var path = require('path');

/**
 * A WebPack plugin that generates custom bundles that use version and
 * semver-mangled require semantics.
 */
function JupyterLabPlugin(options) {
  options = this.options = options || {};
  this.name = options.name || 'jupyter';
}

/**
 * Plugin installation, called by WebPack.
 *
 * @param compiler - The WebPack compiler object.
 */
JupyterLabPlugin.prototype.apply = function(compiler) {
  var pluginName = this.name;
  var publicPath = compiler.options.output.publicPath;
  if (!publicPath.endsWith('/')) {
    publicPath += '/';
  }

  // Notes
  // We use the emit phase because it allows other plugins to act on the
  // output first.
  // We can't replace the module ids during compilation, because there are
  // places in the compilation that assume a numeric id.
  compiler.plugin('emit', function(compilation, callback) {

    // Explore each chunk (build output):
    compilation.chunks.forEach(function(chunk) {

      var sources = [];

      // A manifest for each module and its dependencies.
      var manifest = {};

      // Explore each module within the chunk (built inputs):
      chunk.modules.forEach(function(module) {

        // We don't allow externals.
        if (module.external) {
          throw Error('Cannot use externals:', module.userRequest);
        }

        // Parse each module.
        var source = parseModule(compilation, module, pluginName, publicPath);
        sources.push(source);

        // Add dependencies to the manifest.
        var deps = [];
        for (var i = 0; i < module.dependencies.length; i++) {
          var dep = module.dependencies[i];
          if (dep.id && dep.id !== module.id) {
            deps.push(getRequireName(dep));
          }
        }
        manifest[getDefineName(module)] = deps;
      });

      var code = sources.join('\n\n');

      // Replace the original chunk file.
      // Use the first file name, because the mangling of the chunk
      // file names are private to WebPack.
      var fileName = chunk.files[0];
      compilation.assets[fileName] = {
        source: function() {
          return code;
        },
        size: function() {
          return code.length;
        }
      };

      // Create the manifest.
      compilation.assets[fileName + '.manifest'] = {
        source: function() {
          return JSON.stringify(manifest);
        },
        size: function() {
          return JSON.stringify(manifest).length;
        }
      }
    });

    callback();
  });
};

/**
 * Find a package root path from a request.
 *
 * @param request - The request path.
 *
 * @returns The path to the package root.
 */
function findRoot(request) {
  var orig = request;
  if (path.extname(request)) {
    request = path.dirname(request);
  }
  while (true) {
    try {
      var pkgPath = require.resolve(path.join(request, 'package.json'));
      var pkg = require(pkgPath);
      // Use public packages except for the local package.
      if (!pkg.private || request === process.cwd()) {
        return request;
      }
    } catch (err) {
      // no-op
    }
    var prev = request;
    request = path.dirname(request);
    if (request === prev) {
      throw Error('Could not find package for ' + orig);
    }
  }
}

/**
 * Get the package.json associated with a file.
 *
 * @param request - The request path.
 *
 * @returns The package.json object for the package.
 */
function getPackage(request) {
  var rootPath = findRoot(request);
  return require(path.join(rootPath, 'package.json'));
}

/**
 * Get the define name for a WebPack module.
 *
 * @param module - A parsed WebPack module object.
 *
 * @returns A version-mangled define name for the module.
 *    For example, "foo@1.0.1/lib/bar/baz.js".
 */
function getDefineName(module) {
  if (!module.context) {
    return '__ignored__';
  }
  var request = module.userRequest || module.context;
  var rootPath = findRoot(request);
  var package = getPackage(rootPath);
  var modPath = request.slice(rootPath.length + 1);
  var name = package.name + '@' + package.version;
  if (modPath) {
    name += '/' + modPath;
  }
  return name;
}

/**
 * Get the require name for a WebPack module.
 *
 * @param module - A parsed WebPack module object.
 *
 * @returns A semver-mangled define name for the module.
 *    For example, "foo@^1.0.0/lib/bar/baz.js".
 */
function getRequireName(module) {
  if (!module.context) {
    return '__ignored__';
  }
  var issuer = module.issuer || module.userRequest;
  var request = module.userRequest || module.context;
  var rootPath = findRoot(request);
  var rootPackage = getPackage(rootPath);
  var issuerPath = findRoot(issuer);
  var issuerPackage = getPackage(issuer);
  var modPath = request.slice(rootPath.length + 1);
  var name = rootPackage.name;
  var semver = issuerPackage.dependencies[name] || rootPackage.version;
  if (issuerPackage.name === rootPackage.name) {
    // Allow patch version increments of itself.
    semver = '~' + rootPackage.version;
  } else if (semver.indexOf('file:') === 0) {
    var sourcePath = path.resolve(issuerPath, semver.slice('file:'.length));
    var sourcePackage = getPackage(sourcePath);
    // Allow patch version increments of local packages.
    semver = '~' + sourcePackage.version;
  }
  var id = name + '@' + semver;
  if (modPath) {
    id += '/' + modPath;
  }
  return id;
}

/**
 * Create custom context module source.
 *
 * @param module - A parsed WebPack module object.
 *
 * @returns The new contents of the context module output.
 */
function createContextModule(module) {
  // Modeled after Webpack's ContextModule.js.
  var str;
  if (module.dependencies && module.dependencies.length > 0) {
    var map = {};
    module.dependencies.slice().sort(function(a, b) {
      if (a.userRequest === b.userRequest) {
        return 0;
      }
      return a.userRequest < b.userRequest ? -1 : 1;
    }).forEach(function(dep) {
      if (dep.module) {
        map[dep.userRequest] = getRequireName(dep.module);
      }
    });
    str = [
      "\tvar map = ", JSON.stringify(map, null, "\t"), ";\n",
      "function webpackContext(req) {\n",
      "\treturn __webpack_require__(webpackContextResolve(req));\n",
      "};\n",
      "function webpackContextResolve(req) {\n",
      "\treturn map[req] || (function() { throw new Error(\"Cannot find module '\" + req + \"'.\") }());\n",
      "};\n",
      "webpackContext.keys = function webpackContextKeys() {\n",
      "\treturn Object.keys(map);\n",
      "};\n",
      "webpackContext.resolve = webpackContextResolve;\n",
      "module.exports = webpackContext;\n",
      "webpackContext.id = \"" + getDefineName(module) + "\";\n"
    ];
  } else {
    str = [
      "\tfunction webpackContext(req) {\n",
      "\tthrow new Error(\"Cannot find module '\" + req + \"'.\");\n",
      "}\n",
      "webpackContext.keys = function() { return []; };\n",
      "webpackContext.resolve = webpackContext;\n",
      "module.exports = webpackContext;\n",
      "webpackContext.id = \"" + getDefineName(module) + "\";\n"
    ];
  }
  return str.join('\t');
}

/**
 * Parse a WebPack module to generate a custom version.
 *
 * @param compilation - The Webpack compilation object.
 *
 * @param module - A parsed WebPack module object.
 *
 * @param pluginName - The name of the plugin.
 *
 * @param publicPath - The public path of the plugin.
 *
 * @returns The new module contents.
 */
function parseModule(compilation, module, pluginName, publicPath) {
  var requireName = '__' + pluginName + '_require__';
  var defineName = pluginName + 'Define';
  var source = module.source().source();

  // Regular modules.
  if (module.userRequest) {
    // Handle ensure blocks with and without inline comments.
    // From WebPack dependencies/DepBlockHelpers
    source = handleEnsure(
      compilation, source, publicPath, /__webpack_require__.e\/\*.*?\*\/\((\d+)/
    );
    source = handleEnsure(
      compilation, source, publicPath, /__webpack_require__.e\((\d+)/
    );

    // Replace the require statements with the semver-mangled name.
    var deps = module.getAllModuleDependencies();
    for (var i = 0; i < deps.length; i++) {
      var dep = deps[i];
      var target = '__webpack_require__(' + dep.id + ')';
      var name = getRequireName(dep);
      var replacer = '__webpack_require__("' + name + '")';
      source = source.split(target).join(replacer);
    }
  // Context modules.
  } else if (module.context) {
    // Context modules have to be assembled ourselves
    // because they are not clearly delimited in the text.
    source = createContextModule(module);
    source = source.split('webpackContext').join(pluginName + 'Context');
  }

  // Handle public requires.
  var requireP = '__webpack_require__.p';
  var newRequireP = '"' + publicPath + '"';
  source = source.split(requireP).join(newRequireP);

  // Replace the require name with the custom one.
  source = source.split('__webpack_require__').join(requireName);

  // Create our header with a version-mangled defined name.
  var defineName = getDefineName(module);
  var header = '/** START DEFINE BLOCK for ' + defineName + ' **/\n';
  header += pluginName + '.define("' + getDefineName(module);
  header += '", function (module, exports, ' + requireName + ') {\n';
  var footer = '\n})\n/** END DEFINE BLOCK for ' + defineName + '**/';

  // Combine code indent.
  return header + source.split('\n').join('\n\t') + footer;
}

/**
 * Handle an ensure block.
 *
 * @param compilation - The Webpack compilation object.
 *
 * @param source - The raw module source.
 *
 * @param publicPath - The public path of the plugin.
 *
 * @param regex - The ensure block regex.
 *
 * @returns The new ensure block contents.
 */
function handleEnsure(compilation, source, publicPath, regex) {
  while (regex.test(source)) {
    var match = source.match(regex);
    var chunkId = match[1];
    var fileName = '';
    // Use the first file name, because the mangling of the chunk
    // file name is private to WebPack.
    compilation.chunks.forEach(function(chunk) {
      if (String(chunk.id) === match[1]) {
        fileName = chunk.files[0];
      }
    });
    var replacement = ('__webpack_require__.e("' + publicPath +
            fileName + '"');
    source = source.replace(regex, replacement);
  }
  return source;
}

module.exports = JupyterLabPlugin;
<|endoftext|>


<commit_before>
function AllocationTable(doc) {
  this._doc = doc;
}

AllocationTable.SecIdFree       = -1;
AllocationTable.SecIdEndOfChain = -2;
AllocationTable.SecIdSAT        = -3;
AllocationTable.SecIdMSAT       = -4;

AllocationTable.prototype.load = function(secIds, callback) {
  var self = this;
  var doc = self._doc;
  var header = doc._header;

  self._table = new Array( secIds.length * ( header.secSize / 4 ) );

  doc._readSectors( secIds, function(buffer) {
    var i;
    for ( i = 0; i < buffer.length / 4; i++ ) {
      self._table[i] = buffer.readInt32LE( i * 4 );
    }
    callback();
  });
};

AllocationTable.prototype.getSecIdChain = function(startSecId) {
  var secId = startSecId;
  var secIds = [];
  while ( secId > AllocationTable.SecIdFree ) {
    secIds.push( secId );
    var secIdPrior = secId;
    secId = this._table[secId];
    if (secId === secIdPrior) { // this will cause a deadlock and a out of memory error
      break;
    }
  }

  return secIds;
};

module.exports = AllocationTable;
<commit_msg>wip: converted AllocationTable to class<commit_after>
const ALLOCATION_TABLE_SEC_ID_FREE               = -1;
const ALLOCATION_TABLE_SEC_ID_END_OF_CHAIN       = -2;   // eslint-disable-line no-unused-vars
const ALLOCATION_TABLE_SEC_ID_SAT                = -3;   // eslint-disable-line no-unused-vars
const ALLOCATION_TABLE_SEC_ID_MSAT               = -4;   // eslint-disable-line no-unused-vars

class AllocationTable {

  constructor(doc) {
    this._doc = doc;
  }

  load(secIds, callback) {
    var self = this;
    var doc = self._doc;
    var header = doc._header;

    self._table = new Array( secIds.length * ( header.secSize / 4 ) );

    doc._readSectors( secIds, function(buffer) {
      var i;
      for ( i = 0; i < buffer.length / 4; i++ ) {
        self._table[i] = buffer.readInt32LE( i * 4 );
      }
      callback();
    });
  }

  getSecIdChain(startSecId) {
    var secId = startSecId;
    var secIds = [];
    while ( secId > ALLOCATION_TABLE_SEC_ID_FREE ) {
      secIds.push( secId );
      var secIdPrior = secId;
      secId = this._table[secId];
      if (secId === secIdPrior) { // this will cause a deadlock and a out of memory error
        break;
      }
    }

    return secIds;
  }

}

module.exports = AllocationTable;
<|endoftext|>


<commit_before>var utils = require('../utils'),
	env = require('../env'),
	openTags = require('../parsers/open-tags'),
	strictTags = /span|script|meta/,
	Context = require('../context'),
	Template = require('../template');

// String Output Descriptor
function SOD() {
	this.attributes = '';
	this.classes = '';
	this.children = '';
	this.style = '';
}

function tagOutput(descriptor, innerDescriptor, name) {
	var out = '<' + name + innerDescriptor.attributes;
	if (innerDescriptor.style)
		out += ' style="' + innerDescriptor.style + '"';
	if (innerDescriptor.classes)
		out += ' class="' + innerDescriptor.classes + '"';
	if (innerDescriptor.children)
		descriptor.children += out + '>' + innerDescriptor.children + '</' + name + '>';
	else if (openTags.test(name))
		descriptor.children += out + '>';
	else if (strictTags.test(name))
		descriptor.children += out + '></' + name + '>';
	else
		descriptor.children += out + '/>';
}
utils.tagOutput = tagOutput;

var methods = {
	SOD: SOD,
	//_________________________________ local context management
	context: function(context, descriptor, args) {
		var value = args[0],
			parentPath = args[1];
		descriptor.context = new Context(parentPath ? null : value, context, parentPath ? parentPath : null)
	},
	//_________________________________________ EACH
	each: function(context, descriptor, args) {
		var path = args[0],
			values = (typeof path === 'string') ? context.get(path) : path;
		if (values && values.length) {
			var template = args[1];
			for (var i = 0, len = values.length; i < len; ++i)
				template.toHTMLString(new Context(values[i], context), descriptor);
		} else if (args[2])
			args[2].toHTMLString(context, descriptor);
	},
	// ____________________________________ WITH
	with: function(context, descriptor, args) {
		var path = args[0],
			template = args[1];
		var ctx = new Context(typeof path === 'string' ? context.get(path) : path, context, path);
		template.toHTMLString(ctx, descriptor, container);
	},
	//______________________________________________
	if: function(context, descriptor, args) {
		var ok, condition = args[0],
			successTempl = args[1],
			failTempl = args[2];
		if (condition && condition.__interpolable__)
			ok = condition.output(context);
		else if (type === 'function')
			ok = condition.call(this, context);
		if (ok)
			successTempl.toHTMLString(context, descriptor);
		else if (failTempl)
			failTempl.toHTMLString(context, descriptor);
	},
	//________________________________ TAGS
	tag: function(context, descriptor, originalArgs) {
		var name = originalArgs[0],
			template = originalArgs[1];
		var newDescriptor = new SOD();
		template.toHTMLString(context, newDescriptor);
		tagOutput(descriptor, newDescriptor, name);
	},
	text: function(context, descriptor, args) {
		var value = args[0];
		descriptor.children += value.__interpolable__ ? value.output(context) : value;
	},
	br: function(context, descriptor) {
		descriptor.children += '
';
	},
	//______________________________________________ ATTRIBUTES
	attr: function(context, descriptor, args) {
		var name = args[0],
			value = args[1];
		descriptor.attributes += ' ' + name;
		if (value)
			descriptor.attributes += '="' + (value.__interpolable__ ? value.output(context) : value) + '"';
	},
	disabled: function(context, descriptor, args) {
		var value = args[0];
		if (value === undefined || context.get(value))
			descriptor.attributes += ' disabled';
	},
	val: function(context, descriptor, args) {
		var path = args[0],
			value = args[1];
		descriptor.attributes += ' value="' + (value.__interpolable__ ? value.output(context) : value) + '"';
	},
	setClass: function(context, descriptor, args) {
		var name = args[0],
			flag = args[1];
		if ((flag.__interpolable__ && flag.output(context)) || flag)
			descriptor.classes += ' ' + (name.__interpolable__ ? name.output(context) : name);
	},
	css: function(context, descriptor, args) {
		var prop = args[0],
			value = args[1];
		descriptor.style += prop + ':' + (value.__interpolable__ ? value.output(context) : value);
	},
	visible: function(context, descriptor, args) {
		var flag = args[0],
			val = flag.__interpolable__ ? flag.output(context) : flag;
		if (!val)
			descriptor.style += 'display:none;';
	},
	//_________________________________ EVENTS
	on: function() {},
	off: function() {},
	//_________________________________ CLIENT/SERVER
	client: function(context, descriptor, args) {
		if (env.isServer)
			return;
		args[0].toHTMLString(context, descriptor);
	},
	server: function(context, descriptor, args) {
		if (!env.isServer)
			return;
		args[0].toHTMLString(context, descriptor);
	}
};

Template.prototype.toHTMLString = function(context, descriptor) {
	context = context || new Context();
	descriptor = descriptor ||  new SOD();
	var handler = this._queue[0],
		nextIndex = 0,
		f;
	while (handler) {
		if (handler.engineBlock)
			f = handler.engineBlock.string;
		else
			f = handler.func || methods[handler.name];
		if (!f)
			throw new Error('string output : no template output method found with ' + JSON.stringify(handler));
		f(descriptor.context || context, descriptor, handler.args);
		handler = this._queue[++nextIndex];
	}
	return descriptor.children;
};

module.exports = methods;
<commit_msg>remove issing handler guard in toHTMLString<commit_after>var utils = require('../utils'),
	env = require('../env'),
	openTags = require('../parsers/open-tags'),
	strictTags = /span|script|meta/,
	Context = require('../context'),
	Template = require('../template');

// String Output Descriptor
function SOD() {
	this.attributes = '';
	this.classes = '';
	this.children = '';
	this.style = '';
}

function tagOutput(descriptor, innerDescriptor, name) {
	var out = '<' + name + innerDescriptor.attributes;
	if (innerDescriptor.style)
		out += ' style="' + innerDescriptor.style + '"';
	if (innerDescriptor.classes)
		out += ' class="' + innerDescriptor.classes + '"';
	if (innerDescriptor.children)
		descriptor.children += out + '>' + innerDescriptor.children + '</' + name + '>';
	else if (openTags.test(name))
		descriptor.children += out + '>';
	else if (strictTags.test(name))
		descriptor.children += out + '></' + name + '>';
	else
		descriptor.children += out + '/>';
}
utils.tagOutput = tagOutput;

var methods = {
	SOD: SOD,
	//_________________________________ local context management
	context: function(context, descriptor, args) {
		var value = args[0],
			parentPath = args[1];
		descriptor.context = new Context(parentPath ? null : value, context, parentPath ? parentPath : null)
	},
	//_________________________________________ EACH
	each: function(context, descriptor, args) {
		var path = args[0],
			values = (typeof path === 'string') ? context.get(path) : path;
		if (values && values.length) {
			var template = args[1];
			for (var i = 0, len = values.length; i < len; ++i)
				template.toHTMLString(new Context(values[i], context), descriptor);
		} else if (args[2])
			args[2].toHTMLString(context, descriptor);
	},
	// ____________________________________ WITH
	with: function(context, descriptor, args) {
		var path = args[0],
			template = args[1];
		var ctx = new Context(typeof path === 'string' ? context.get(path) : path, context, path);
		template.toHTMLString(ctx, descriptor, container);
	},
	//______________________________________________
	if: function(context, descriptor, args) {
		var ok, condition = args[0],
			successTempl = args[1],
			failTempl = args[2];
		if (condition && condition.__interpolable__)
			ok = condition.output(context);
		else if (type === 'function')
			ok = condition.call(this, context);
		if (ok)
			successTempl.toHTMLString(context, descriptor);
		else if (failTempl)
			failTempl.toHTMLString(context, descriptor);
	},
	//________________________________ TAGS
	tag: function(context, descriptor, originalArgs) {
		var name = originalArgs[0],
			template = originalArgs[1];
		var newDescriptor = new SOD();
		template.toHTMLString(context, newDescriptor);
		tagOutput(descriptor, newDescriptor, name);
	},
	text: function(context, descriptor, args) {
		var value = args[0];
		descriptor.children += value.__interpolable__ ? value.output(context) : value;
	},
	br: function(context, descriptor) {
		descriptor.children += '
';
	},
	//______________________________________________ ATTRIBUTES
	attr: function(context, descriptor, args) {
		var name = args[0],
			value = args[1];
		descriptor.attributes += ' ' + name;
		if (value)
			descriptor.attributes += '="' + (value.__interpolable__ ? value.output(context) : value) + '"';
	},
	disabled: function(context, descriptor, args) {
		var value = args[0];
		if (value === undefined || context.get(value))
			descriptor.attributes += ' disabled';
	},
	val: function(context, descriptor, args) {
		var path = args[0],
			value = args[1];
		descriptor.attributes += ' value="' + (value.__interpolable__ ? value.output(context) : value) + '"';
	},
	setClass: function(context, descriptor, args) {
		var name = args[0],
			flag = args[1];
		if ((flag.__interpolable__ && flag.output(context)) || flag)
			descriptor.classes += ' ' + (name.__interpolable__ ? name.output(context) : name);
	},
	css: function(context, descriptor, args) {
		var prop = args[0],
			value = args[1];
		descriptor.style += prop + ':' + (value.__interpolable__ ? value.output(context) : value);
	},
	visible: function(context, descriptor, args) {
		var flag = args[0],
			val = flag.__interpolable__ ? flag.output(context) : flag;
		if (!val)
			descriptor.style += 'display:none;';
	},
	//_________________________________ EVENTS
	on: function() {},
	off: function() {},
	//_________________________________ CLIENT/SERVER
	client: function(context, descriptor, args) {
		if (env.isServer)
			return;
		args[0].toHTMLString(context, descriptor);
	},
	server: function(context, descriptor, args) {
		if (!env.isServer)
			return;
		args[0].toHTMLString(context, descriptor);
	}
};

Template.prototype.toHTMLString = function(context, descriptor) {
	context = context || new Context();
	descriptor = descriptor ||  new SOD();
	var handler = this._queue[0],
		nextIndex = 0,
		f;
	while (handler) {
		if (handler.engineBlock)
			f = handler.engineBlock.string;
		else
			f = handler.func || methods[handler.name];
		f(descriptor.context || context, descriptor, handler.args);
		handler = this._queue[++nextIndex];
	}
	return descriptor.children;
};

module.exports = methods;
<|endoftext|>


<commit_before>(function(){

function merge(o1, o2) {
  for (var p in o2) {
      o1[p] = (
          o1[p] != null
          && o2[p] != null
          && !Array.isArray(o1[p])
          && !Array.isArray(o2[p])
          && typeof o1[p] == 'object'
          && typeof o2[p] == 'object'
      ) ? merge(o1[p], o2[p]) : o2[p];
  }
  return o1;
}

exports.State = {

	register: function(server){
		server.$state = {};
		server.$locks = {};
		server.$timeouts = {};
	},

	send: function(server, event, data){
		server.$socket.sockets.emit(event, data);
		return this;
	},

	broadcast: function(conn, event, data){
		conn.broadcast.emit(event, data);
		return this;
	},

	reply: function(conn, event, data){
		conn.emit(event, data);
		return this;
	},

	error: function(conn, name, data){
		conn.emit('error', name, data);
		return this;
	},

	onClientConnect: function(server, conn){
		conn.on('update', this.onUpdate.bind(this, server, conn));
		conn.on('put', this.onPut.bind(this, server, conn));
		conn.on('delete', this.onDelete.bind(this, server, conn));
		conn.on('acquire lock', this.onAcquireLock.bind(this, server, conn));
		conn.on('release lock', this.onReleaseLock.bind(this, server, conn));
		this.reply(conn, 'initial state', server.$state);
	},

	onUpdate: function(server, conn, data){
		if (data == null || data.key == null || data.value == null){
			return this.error(conn, 'update error', data);
		}
		if (server.$locks[data.key] && server.$locks[data.key] !== conn.id){
			return this.error(conn, 'lock error', data.key);
		}
		server.$state[data.key] = data.value;
		this.send(server, 'state update', data);
	},

	onPut: function(server, conn, data){
		merge(server.$state, data);
		this.send(server, 'put', data);
	},

	onDelete: function(server, conn, key){
		if (server.$state[key] == null){
			return this.error(conn, 'delete error', key);
		}
		delete server.$state[key];
		this.releaseLock(server, conn, key);
		this.send(server, 'delete', key);
	},

	onAcquireLock: function(server, conn, key){
		var locks = server.$locks,
			timeouts = server.$timeouts;

		if (locks[key] && locks[key] != conn.id){
			return this.error(conn, 'acquire lock error', key);
		}
		if (timeouts[key]) clearTimeout(timeouts[key]);
		this.acquireLock(server, conn, key);
		timeouts[key] = setTimeout(this.releaseLock.bind(this, server, conn, key), 20000);
	},

	acquireLock: function(server, conn, key){
		server.$locks[key] = conn.id;
		this.reply(conn, 'lock acquired', key);
		this.broadcast(conn, 'lock key', key);
		server.emit('keyLocked', server, key, conn.id);
		return this;
	},

	onReleaseLock: function(server, conn, key){
		if (server.$locks[key] != conn.id){
			return this.error(conn, 'release lock error', key);
		}
		this.releaseLock(server, conn, key);
	},

	releaseLock: function(server, conn, key){
		var locks = server.$locks,
			timeouts = server.$timeouts;

		if (locks[key] == conn.id){
			if (timeouts[key]) clearTimeout(timeouts[key]);
			delete locks[key];
			delete timeouts[key];
			this.reply(conn, 'lock released', key);
			this.send(server, 'release key', key);
			server.emit('keyReleased', server, key, conn.id);
		}
		return this;
	},

	onClientDisconnect: function(server, conn){
		this.clearLocks(server, conn);
	},

	clearLocks: function(server, conn){
		for (var key in server.$locks){
			this.releaseLock(server, conn, key);
		}
		return this;
	}

};

})();
<commit_msg>put should now JSON.merge/JSON.extend correctly<commit_after>(function(){

function extend(o1, o2) {
	// http://jsfiddle.net/k5Ywe/29/
	for (var p in o2) {
		o1[p] = (
			o1[p] != null
			&& o2[p] != null
			&& !Array.isArray(o1[p])
			&& typeof o1[p] == 'object'
			&& typeof o2[p] == 'object'
		) ? extend(o1[p], o2[p]) : o2[p];
	}
	return o1;
}

exports.State = {

	register: function(server){
		server.$state = {};
		server.$locks = {};
		server.$timeouts = {};
	},

	send: function(server, event, data){
		server.$socket.sockets.emit(event, data);
		return this;
	},

	broadcast: function(conn, event, data){
		conn.broadcast.emit(event, data);
		return this;
	},

	reply: function(conn, event, data){
		conn.emit(event, data);
		return this;
	},

	error: function(conn, name, data){
		conn.emit('error', name, data);
		return this;
	},

	onClientConnect: function(server, conn){
		conn.on('update', this.onUpdate.bind(this, server, conn));
		conn.on('put', this.onPut.bind(this, server, conn));
		conn.on('delete', this.onDelete.bind(this, server, conn));
		conn.on('acquire lock', this.onAcquireLock.bind(this, server, conn));
		conn.on('release lock', this.onReleaseLock.bind(this, server, conn));
		this.reply(conn, 'initial state', server.$state);
	},

	onUpdate: function(server, conn, data){
		if (data == null || data.key == null || data.value == null){
			return this.error(conn, 'update error', data);
		}
		if (server.$locks[data.key] && server.$locks[data.key] !== conn.id){
			return this.error(conn, 'lock error', data.key);
		}
		server.$state[data.key] = data.value;
		this.send(server, 'state update', data);
	},

	onPut: function(server, conn, data){
		extend(server.$state, data);
		this.send(server, 'put', data);
	},

	onDelete: function(server, conn, key){
		if (server.$state[key] == null){
			return this.error(conn, 'delete error', key);
		}
		delete server.$state[key];
		this.releaseLock(server, conn, key);
		this.send(server, 'delete', key);
	},

	onAcquireLock: function(server, conn, key){
		var locks = server.$locks,
			timeouts = server.$timeouts;

		if (locks[key] && locks[key] != conn.id){
			return this.error(conn, 'acquire lock error', key);
		}
		if (timeouts[key]) clearTimeout(timeouts[key]);
		this.acquireLock(server, conn, key);
		timeouts[key] = setTimeout(this.releaseLock.bind(this, server, conn, key), 20000);
	},

	acquireLock: function(server, conn, key){
		server.$locks[key] = conn.id;
		this.reply(conn, 'lock acquired', key);
		this.broadcast(conn, 'lock key', key);
		server.emit('keyLocked', server, key, conn.id);
		return this;
	},

	onReleaseLock: function(server, conn, key){
		if (server.$locks[key] != conn.id){
			return this.error(conn, 'release lock error', key);
		}
		this.releaseLock(server, conn, key);
	},

	releaseLock: function(server, conn, key){
		var locks = server.$locks,
			timeouts = server.$timeouts;

		if (locks[key] == conn.id){
			if (timeouts[key]) clearTimeout(timeouts[key]);
			delete locks[key];
			delete timeouts[key];
			this.reply(conn, 'lock released', key);
			this.send(server, 'release key', key);
			server.emit('keyReleased', server, key, conn.id);
		}
		return this;
	},

	onClientDisconnect: function(server, conn){
		this.clearLocks(server, conn);
	},

	clearLocks: function(server, conn){
		for (var key in server.$locks){
			this.releaseLock(server, conn, key);
		}
		return this;
	}

};

})();
<|endoftext|>


<commit_before>// @ts-nocheck //because of logger, we can't ts-check
const azureHelper = require('./azure-helper');
const azureApi = require('./azure-got-wrapper');
const hostRules = require('../../util/host-rules');
const { appSlug } = require('../../config/app-strings');
const GitStorage = require('../git/storage');

let config = {};

module.exports = {
  // Initialization
  getRepos,
  cleanRepo,
  initRepo,
  getRepoStatus,
  getRepoForceRebase,
  setBaseBranch,
  // Search
  getFileList,
  // Branch
  branchExists,
  getAllRenovateBranches,
  isBranchStale,
  getBranchPr,
  getBranchStatus,
  getBranchStatusCheck,
  setBranchStatus,
  deleteBranch,
  mergeBranch,
  getBranchLastCommitTime,
  // issue
  findIssue,
  ensureIssue,
  ensureIssueClosing,
  addAssignees,
  addReviewers,
  deleteLabel,
  // Comments
  ensureComment,
  ensureCommentRemoval,
  // PR
  getPrList,
  findPr,
  createPr,
  getPr,
  getPrFiles,
  updatePr,
  mergePr,
  getPrBody,
  // file
  commitFilesToBranch,
  getFile,
  // Commits
  getCommitMessages,
  // vulnerability alerts
  getVulnerabilityAlerts,
};

async function getRepos(token, endpoint) {
  logger.info('Autodiscovering Azure DevOps repositories');
  logger.debug('getRepos(token, endpoint)');
  const opts = hostRules.find({ platform: 'azure' }, { token, endpoint });
  hostRules.update({ ...opts, platform: 'azure', default: true });
  const azureApiGit = await azureApi.gitApi();
  const repos = await azureApiGit.getRepositories();
  return repos.map(repo => `${repo.project.name}/${repo.name}`);
}

async function initRepo({ repository, endpoint, localDir }) {
  logger.debug(`initRepo("${repository}")`);
  const opts = hostRules.find({ platform: 'azure' }, { endpoint });
  // istanbul ignore next
  if (!opts.token) {
    throw new Error('No token found for getRepos');
  }
  hostRules.update({ ...opts, platform: 'azure', default: true });
  config.repository = repository;
  config.fileList = null;
  config.prList = null;
  const azureApiGit = await azureApi.gitApi();
  const repos = await azureApiGit.getRepositories();
  const names = azureHelper.getProjectAndRepo(repository);
  const repo = repos.filter(
    c =>
      c.name.toLowerCase() === names.repo.toLowerCase() &&
      c.project.name.toLowerCase() === names.project.toLowerCase()
  )[0];
  logger.debug({ repositoryDetails: repo }, 'Repository details');
  config.repoId = repo.id;
  config.owner = '?owner?';
  logger.debug(`${repository} owner = ${config.owner}`);
  // Use default branch as PR target unless later overridden
  config.defaultBranch = repo.defaultBranch.replace('refs/heads/', '');
  config.baseBranch = config.defaultBranch;
  logger.debug(`${repository} default branch = ${config.defaultBranch}`);
  config.baseCommitSHA = await getBranchCommit(config.baseBranch);

  // Todo Azure: Get Merge method
  config.mergeMethod = 'merge';
  // if (res.body.allow_rebase_merge) {
  //   config.mergeMethod = 'rebase';
  // } else if (res.body.allow_squash_merge) {
  //   config.mergeMethod = 'squash';
  // } else if (res.body.allow_merge_commit) {
  //   config.mergeMethod = 'merge';
  // } else {
  //   logger.debug('Could not find allowed merge methods for repo');
  // }

  // Todo Azure: Get getBranchProtection
  config.repoForceRebase = false;
  // try {
  //   const branchProtection = await getBranchProtection(config.baseBranch);
  //   if (branchProtection.strict) {
  //     logger.debug('Repo has branch protection and needs PRs up-to-date');
  //     config.repoForceRebase = true;
  //   } else {
  //     logger.debug(
  //       'Repo has branch protection but does not require up-to-date'
  //     );
  //   }
  // } catch (err) {
  //   if (err.statusCode === 404) {
  //     logger.debug('Repo has no branch protection');
  //   } else if (err.statusCode === 403) {
  //     logger.debug('Do not have permissions to detect branch protection');
  //   } else {
  //     throw err;
  //   }
  // }
  // Always gitFs
  config.storage = new GitStorage();
  const [projectName, repoName] = repository.split('/');
  const url =
    endpoint.replace('https://', `https://token:${opts.token}@`) +
    `${projectName}/_git/${repoName}`;
  await config.storage.initRepo({
    ...config,
    localDir,
    url,
  });
  const platformConfig = {
    privateRepo: true,
    isFork: false,
  };
  return platformConfig;
}

function getRepoForceRebase() {
  return false;
}

// istanbul ignore next
async function setBaseBranch(branchName) {
  if (branchName) {
    logger.debug(`Setting baseBranch to ${branchName}`);
    config.baseBranch = branchName;
    delete config.baseCommitSHA;
    delete config.fileList;
    await config.storage.setBaseBranch(branchName);
    await getFileList(branchName);
  }
}

// Search

// istanbul ignore next
function getFileList(branchName) {
  return config.storage.getFileList(branchName);
}

// Branch

// istanbul ignore next
function branchExists(branchName) {
  return config.storage.branchExists(branchName);
}

// istanbul ignore next
function getAllRenovateBranches(branchPrefix) {
  return config.storage.getAllRenovateBranches(branchPrefix);
}

// istanbul ignore next
function isBranchStale(branchName) {
  return config.storage.isBranchStale(branchName);
}

// istanbul ignore next
function getFile(filePath, branchName) {
  return config.storage.getFile(filePath, branchName);
}

// istanbul ignore next
async function deleteBranch(branchName, abandonAssociatedPr = false) {
  await config.storage.deleteBranch(branchName);
  // istanbul ignore if
  if (abandonAssociatedPr) {
    const pr = await getBranchPr(branchName);
    await abandonPr(pr.number);
  }
}

// istanbul ignore next
function getBranchLastCommitTime(branchName) {
  return config.storage.getBranchLastCommitTime(branchName);
}

// istanbul ignore next
function getRepoStatus() {
  return config.storage.getRepoStatus();
}

// istanbul ignore next
function mergeBranch(branchName) {
  return config.storage.mergeBranch(branchName);
}

// istanbul ignore next
function commitFilesToBranch(
  branchName,
  files,
  message,
  parentBranch = config.baseBranch
) {
  return config.storage.commitFilesToBranch(
    branchName,
    files,
    message,
    parentBranch
  );
}

// istanbul ignore next
function getCommitMessages() {
  return config.storage.getCommitMessages();
}

async function getBranchCommit(fullBranchName) {
  const azureApiGit = await azureApi.gitApi();
  const commit = await azureApiGit.getBranch(
    config.repoId,
    azureHelper.getBranchNameWithoutRefsheadsPrefix(fullBranchName)
  );
  return commit.commit.commitId;
}

function getPrList() {
  return [];
}

async function findPr(branchName, prTitle, state = 'all') {
  logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
  let prsFiltered = [];
  try {
    const azureApiGit = await azureApi.gitApi();
    const prs = await azureApiGit.getPullRequests(config.repoId, null);

    prsFiltered = prs.filter(
      item => item.sourceRefName === azureHelper.getNewBranchName(branchName)
    );

    if (prTitle) {
      prsFiltered = prsFiltered.filter(item => item.title === prTitle);
    }

    // update format
    prsFiltered = prsFiltered.map(item =>
      azureHelper.getRenovatePRFormat(item)
    );

    switch (state) {
      case 'all':
        // no more filter needed, we can go further...
        break;
      case '!open':
        prsFiltered = prsFiltered.filter(item => item.state !== 'open');
        break;
      default:
        prsFiltered = prsFiltered.filter(item => item.state === state);
        break;
    }
  } catch (error) {
    logger.error('findPr ' + error);
  }
  if (prsFiltered.length === 0) {
    return null;
  }
  return prsFiltered[0];
}

async function getBranchPr(branchName) {
  logger.debug(`getBranchPr(${branchName})`);
  const existingPr = await findPr(branchName, null, 'open');
  return existingPr ? getPr(existingPr.pullRequestId) : null;
}

async function getBranchStatus(branchName, requiredStatusChecks) {
  logger.debug(`getBranchStatus(${branchName})`);
  if (!requiredStatusChecks) {
    // null means disable status checks, so it always succeeds
    return 'success';
  }
  if (requiredStatusChecks.length) {
    // This is Unsupported
    logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
    return 'failed';
  }
  const branchStatusCheck = await getBranchStatusCheck(branchName);
  return branchStatusCheck;
}

async function getBranchStatusCheck(branchName, context) {
  logger.trace(`getBranchStatusCheck(${branchName}, ${context})`);
  const azureApiGit = await azureApi.gitApi();
  const branch = await azureApiGit.getBranch(
    config.repoId,
    azureHelper.getBranchNameWithoutRefsheadsPrefix(branchName)
  );
  if (branch.aheadCount === 0) {
    return 'success';
  }
  return 'pending';
}

async function getPr(pullRequestId) {
  logger.debug(`getPr(${pullRequestId})`);
  if (!pullRequestId) {
    return null;
  }
  const azureApiGit = await azureApi.gitApi();
  const prs = await azureApiGit.getPullRequests(config.repoId, null);
  const azurePr = prs.filter(item => item.pullRequestId === pullRequestId);
  if (azurePr.length === 0) {
    return null;
  }
  logger.debug(`pr: (${azurePr[0]})`);
  const pr = azureHelper.getRenovatePRFormat(azurePr[0]);
  return pr;
}

async function createPr(branchName, title, body, labels, useDefaultBranch) {
  const sourceRefName = azureHelper.getNewBranchName(branchName);
  const targetRefName = azureHelper.getNewBranchName(
    useDefaultBranch ? config.defaultBranch : config.baseBranch
  );
  const description = azureHelper.max4000Chars(body);
  const azureApiGit = await azureApi.gitApi();
  const pr = await azureApiGit.createPullRequest(
    {
      sourceRefName,
      targetRefName,
      title,
      description,
    },
    config.repoId
  );
  pr.branchName = branchName;
  return azureHelper.getRenovatePRFormat(pr);
}

async function updatePr(prNo, title, body) {
  logger.debug(`updatePr(${prNo}, ${title}, body)`);
  const azureApiGit = await azureApi.gitApi();
  const objToUpdate = {
    title,
  };
  if (body) {
    objToUpdate.description = azureHelper.max4000Chars(body);
  }
  await azureApiGit.updatePullRequest(objToUpdate, config.repoId, prNo);
}

async function ensureComment(issueNo, topic, content) {
  logger.debug(`ensureComment(${issueNo}, ${topic}, content)`);
  const body = `### ${topic}\n\n${content}`;
  const azureApiGit = await azureApi.gitApi();
  await azureApiGit.createThread(
    {
      comments: [{ content: body, commentType: 1, parentCommentId: 0 }],
      status: 1,
    },
    config.repoId,
    issueNo
  );
}

async function ensureCommentRemoval(issueNo, topic) {
  logger.debug(`ensureCommentRemoval(issueNo, topic)(${issueNo}, ${topic})`);
  if (issueNo) {
    const azureApiGit = await azureApi.gitApi();
    const threads = await azureApiGit.getThreads(config.repoId, issueNo);
    let threadIdFound = null;

    threads.forEach(thread => {
      if (thread.comments[0].content.startsWith(`### ${topic}\n\n`)) {
        threadIdFound = thread.id;
      }
    });

    if (threadIdFound) {
      await azureApiGit.updateThread(
        {
          status: 4, // close
        },
        config.repoId,
        issueNo,
        threadIdFound
      );
    }
  }
}

// istanbul ignore next
async function abandonPr(prNo) {
  logger.debug(`abandonPr(prNo)(${prNo})`);
  const azureApiGit = await azureApi.gitApi();
  await azureApiGit.updatePullRequest(
    {
      status: 2,
    },
    config.repoId,
    prNo
  );
}

function setBranchStatus(branchName, context, description, state, targetUrl) {
  logger.debug(
    `setBranchStatus(${branchName}, ${context}, ${description}, ${state}, ${targetUrl}) - Not supported by Azure DevOps (yet!)`
  );
}

async function mergePr(pr) {
  logger.info(`mergePr(pr)(${pr}) - Not supported by Azure DevOps (yet!)`);
  await null;
}

function getPrBody(input) {
  // Remove any HTML we use
  return input
    .replace(new RegExp(`\n---\n\n.*?<!-- ${appSlug}-rebase -->.*?\n`), '')
    .replace('', '**')
    .replace('', '**')
    .replace('', '')
    .replace('', '');
}

// istanbul ignore next
function findIssue() {
  logger.warn(`findIssue() is not implemented`);
}

// istanbul ignore next
function ensureIssue() {
  logger.warn(`ensureIssue() is not implemented`);
}

// istanbul ignore next
function ensureIssueClosing() {}

/**
 *
 * @param {number} issueNo
 * @param {string[]} assignees
 */
async function addAssignees(issueNo, assignees) {
  logger.trace(`addAssignees(${issueNo}, ${assignees})`);
  await ensureComment(
    issueNo,
    'Add Assignees',
    assignees.map(a => `@<${a}>`).join(', ')
  );
}

/**
 *
 * @param {number} prNo
 * @param {string[]} reviewers
 */
async function addReviewers(prNo, reviewers) {
  logger.trace(`addReviewers(${prNo}, ${reviewers})`);
  const azureApiGit = await azureApi.gitApi();
  const azureApiCore = await azureApi.getCoreApi();
  const repos = await azureApiGit.getRepositories();
  const repo = repos.filter(c => c.id === config.repoId)[0];
  const teams = await azureApiCore.getTeams(repo.project.id);
  const members = await Promise.all(
    teams.map(
      async t =>
        /* eslint-disable no-return-await */
        await azureApiCore.getTeamMembers(repo.project.id, t.id)
    )
  );

  const ids = [];
  members.forEach(listMembers => {
    listMembers.forEach(m => {
      reviewers.forEach(r => {
        if (
          r.toLowerCase() === m.displayName.toLowerCase() ||
          r.toLowerCase() === m.uniqueName.toLowerCase()
        ) {
          if (ids.filter(c => c.id === m.id).length === 0) {
            ids.push({ id: m.id, name: r });
          }
        }
      });
    });
  });

  await Promise.all(
    ids.map(async obj => {
      await azureApiGit.createPullRequestReviewer(
        {},
        config.repoId,
        prNo,
        obj.id
      );
      logger.info(`Reviewer added: ${obj.name}`);
    })
  );
}

// istanbul ignore next
function deleteLabel() {
  throw new Error('deleteLabel not implemented');
}

// to become async?
function getPrFiles(prNo) {
  logger.info(
    `getPrFiles(prNo)(${prNo}) - Not supported by Azure DevOps (yet!)`
  );
  return [];
}

function getVulnerabilityAlerts() {
  return [];
}

function cleanRepo() {
  // istanbul ignore if
  if (config.storage && config.storage.cleanRepo) {
    config.storage.cleanRepo();
  }
  config = {};
}
<commit_msg>fix(azure-devops): findPr (#3366)<commit_after>// @ts-nocheck //because of logger, we can't ts-check
const azureHelper = require('./azure-helper');
const azureApi = require('./azure-got-wrapper');
const hostRules = require('../../util/host-rules');
const { appSlug } = require('../../config/app-strings');
const GitStorage = require('../git/storage');

let config = {};

module.exports = {
  // Initialization
  getRepos,
  cleanRepo,
  initRepo,
  getRepoStatus,
  getRepoForceRebase,
  setBaseBranch,
  // Search
  getFileList,
  // Branch
  branchExists,
  getAllRenovateBranches,
  isBranchStale,
  getBranchPr,
  getBranchStatus,
  getBranchStatusCheck,
  setBranchStatus,
  deleteBranch,
  mergeBranch,
  getBranchLastCommitTime,
  // issue
  findIssue,
  ensureIssue,
  ensureIssueClosing,
  addAssignees,
  addReviewers,
  deleteLabel,
  // Comments
  ensureComment,
  ensureCommentRemoval,
  // PR
  getPrList,
  findPr,
  createPr,
  getPr,
  getPrFiles,
  updatePr,
  mergePr,
  getPrBody,
  // file
  commitFilesToBranch,
  getFile,
  // Commits
  getCommitMessages,
  // vulnerability alerts
  getVulnerabilityAlerts,
};

async function getRepos(token, endpoint) {
  logger.info('Autodiscovering Azure DevOps repositories');
  logger.debug('getRepos(token, endpoint)');
  const opts = hostRules.find({ platform: 'azure' }, { token, endpoint });
  hostRules.update({ ...opts, platform: 'azure', default: true });
  const azureApiGit = await azureApi.gitApi();
  const repos = await azureApiGit.getRepositories();
  return repos.map(repo => `${repo.project.name}/${repo.name}`);
}

async function initRepo({ repository, endpoint, localDir }) {
  logger.debug(`initRepo("${repository}")`);
  const opts = hostRules.find({ platform: 'azure' }, { endpoint });
  // istanbul ignore next
  if (!opts.token) {
    throw new Error('No token found for getRepos');
  }
  hostRules.update({ ...opts, platform: 'azure', default: true });
  config.repository = repository;
  config.fileList = null;
  config.prList = null;
  const azureApiGit = await azureApi.gitApi();
  const repos = await azureApiGit.getRepositories();
  const names = azureHelper.getProjectAndRepo(repository);
  const repo = repos.filter(
    c =>
      c.name.toLowerCase() === names.repo.toLowerCase() &&
      c.project.name.toLowerCase() === names.project.toLowerCase()
  )[0];
  logger.debug({ repositoryDetails: repo }, 'Repository details');
  config.repoId = repo.id;
  config.owner = '?owner?';
  logger.debug(`${repository} owner = ${config.owner}`);
  // Use default branch as PR target unless later overridden
  config.defaultBranch = repo.defaultBranch.replace('refs/heads/', '');
  config.baseBranch = config.defaultBranch;
  logger.debug(`${repository} default branch = ${config.defaultBranch}`);
  config.baseCommitSHA = await getBranchCommit(config.baseBranch);

  // Todo Azure: Get Merge method
  config.mergeMethod = 'merge';
  // if (res.body.allow_rebase_merge) {
  //   config.mergeMethod = 'rebase';
  // } else if (res.body.allow_squash_merge) {
  //   config.mergeMethod = 'squash';
  // } else if (res.body.allow_merge_commit) {
  //   config.mergeMethod = 'merge';
  // } else {
  //   logger.debug('Could not find allowed merge methods for repo');
  // }

  // Todo Azure: Get getBranchProtection
  config.repoForceRebase = false;
  // try {
  //   const branchProtection = await getBranchProtection(config.baseBranch);
  //   if (branchProtection.strict) {
  //     logger.debug('Repo has branch protection and needs PRs up-to-date');
  //     config.repoForceRebase = true;
  //   } else {
  //     logger.debug(
  //       'Repo has branch protection but does not require up-to-date'
  //     );
  //   }
  // } catch (err) {
  //   if (err.statusCode === 404) {
  //     logger.debug('Repo has no branch protection');
  //   } else if (err.statusCode === 403) {
  //     logger.debug('Do not have permissions to detect branch protection');
  //   } else {
  //     throw err;
  //   }
  // }
  // Always gitFs
  config.storage = new GitStorage();
  const [projectName, repoName] = repository.split('/');
  const url =
    endpoint.replace('https://', `https://token:${opts.token}@`) +
    `${projectName}/_git/${repoName}`;
  await config.storage.initRepo({
    ...config,
    localDir,
    url,
  });
  const platformConfig = {
    privateRepo: true,
    isFork: false,
  };
  return platformConfig;
}

function getRepoForceRebase() {
  return false;
}

// istanbul ignore next
async function setBaseBranch(branchName) {
  if (branchName) {
    logger.debug(`Setting baseBranch to ${branchName}`);
    config.baseBranch = branchName;
    delete config.baseCommitSHA;
    delete config.fileList;
    await config.storage.setBaseBranch(branchName);
    await getFileList(branchName);
  }
}

// Search

// istanbul ignore next
function getFileList(branchName) {
  return config.storage.getFileList(branchName);
}

// Branch

// istanbul ignore next
function branchExists(branchName) {
  return config.storage.branchExists(branchName);
}

// istanbul ignore next
function getAllRenovateBranches(branchPrefix) {
  return config.storage.getAllRenovateBranches(branchPrefix);
}

// istanbul ignore next
function isBranchStale(branchName) {
  return config.storage.isBranchStale(branchName);
}

// istanbul ignore next
function getFile(filePath, branchName) {
  return config.storage.getFile(filePath, branchName);
}

// istanbul ignore next
async function deleteBranch(branchName, abandonAssociatedPr = false) {
  await config.storage.deleteBranch(branchName);
  // istanbul ignore if
  if (abandonAssociatedPr) {
    const pr = await getBranchPr(branchName);
    await abandonPr(pr.number);
  }
}

// istanbul ignore next
function getBranchLastCommitTime(branchName) {
  return config.storage.getBranchLastCommitTime(branchName);
}

// istanbul ignore next
function getRepoStatus() {
  return config.storage.getRepoStatus();
}

// istanbul ignore next
function mergeBranch(branchName) {
  return config.storage.mergeBranch(branchName);
}

// istanbul ignore next
function commitFilesToBranch(
  branchName,
  files,
  message,
  parentBranch = config.baseBranch
) {
  return config.storage.commitFilesToBranch(
    branchName,
    files,
    message,
    parentBranch
  );
}

// istanbul ignore next
function getCommitMessages() {
  return config.storage.getCommitMessages();
}

async function getBranchCommit(fullBranchName) {
  const azureApiGit = await azureApi.gitApi();
  const commit = await azureApiGit.getBranch(
    config.repoId,
    azureHelper.getBranchNameWithoutRefsheadsPrefix(fullBranchName)
  );
  return commit.commit.commitId;
}

function getPrList() {
  return [];
}

async function findPr(branchName, prTitle, state = 'all') {
  logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
  let prsFiltered = [];
  try {
    const azureApiGit = await azureApi.gitApi();
    const prs = await azureApiGit.getPullRequests(config.repoId, {});

    prsFiltered = prs.filter(
      item => item.sourceRefName === azureHelper.getNewBranchName(branchName)
    );

    if (prTitle) {
      prsFiltered = prsFiltered.filter(item => item.title === prTitle);
    }

    // update format
    prsFiltered = prsFiltered.map(item =>
      azureHelper.getRenovatePRFormat(item)
    );

    switch (state) {
      case 'all':
        // no more filter needed, we can go further...
        break;
      case '!open':
        prsFiltered = prsFiltered.filter(item => item.state !== 'open');
        break;
      default:
        prsFiltered = prsFiltered.filter(item => item.state === state);
        break;
    }
  } catch (error) {
    logger.error('findPr ' + error);
  }
  if (prsFiltered.length === 0) {
    return null;
  }
  return prsFiltered[0];
}

async function getBranchPr(branchName) {
  logger.debug(`getBranchPr(${branchName})`);
  const existingPr = await findPr(branchName, null, 'open');
  return existingPr ? getPr(existingPr.pullRequestId) : null;
}

async function getBranchStatus(branchName, requiredStatusChecks) {
  logger.debug(`getBranchStatus(${branchName})`);
  if (!requiredStatusChecks) {
    // null means disable status checks, so it always succeeds
    return 'success';
  }
  if (requiredStatusChecks.length) {
    // This is Unsupported
    logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
    return 'failed';
  }
  const branchStatusCheck = await getBranchStatusCheck(branchName);
  return branchStatusCheck;
}

async function getBranchStatusCheck(branchName, context) {
  logger.trace(`getBranchStatusCheck(${branchName}, ${context})`);
  const azureApiGit = await azureApi.gitApi();
  const branch = await azureApiGit.getBranch(
    config.repoId,
    azureHelper.getBranchNameWithoutRefsheadsPrefix(branchName)
  );
  if (branch.aheadCount === 0) {
    return 'success';
  }
  return 'pending';
}

async function getPr(pullRequestId) {
  logger.debug(`getPr(${pullRequestId})`);
  if (!pullRequestId) {
    return null;
  }
  const azureApiGit = await azureApi.gitApi();
  const prs = await azureApiGit.getPullRequests(config.repoId, {});
  const azurePr = prs.filter(item => item.pullRequestId === pullRequestId);
  if (azurePr.length === 0) {
    return null;
  }
  logger.debug(`pr: (${azurePr[0]})`);
  const pr = azureHelper.getRenovatePRFormat(azurePr[0]);
  return pr;
}

async function createPr(branchName, title, body, labels, useDefaultBranch) {
  const sourceRefName = azureHelper.getNewBranchName(branchName);
  const targetRefName = azureHelper.getNewBranchName(
    useDefaultBranch ? config.defaultBranch : config.baseBranch
  );
  const description = azureHelper.max4000Chars(body);
  const azureApiGit = await azureApi.gitApi();
  const pr = await azureApiGit.createPullRequest(
    {
      sourceRefName,
      targetRefName,
      title,
      description,
    },
    config.repoId
  );
  pr.branchName = branchName;
  return azureHelper.getRenovatePRFormat(pr);
}

async function updatePr(prNo, title, body) {
  logger.debug(`updatePr(${prNo}, ${title}, body)`);
  const azureApiGit = await azureApi.gitApi();
  const objToUpdate = {
    title,
  };
  if (body) {
    objToUpdate.description = azureHelper.max4000Chars(body);
  }
  await azureApiGit.updatePullRequest(objToUpdate, config.repoId, prNo);
}

async function ensureComment(issueNo, topic, content) {
  logger.debug(`ensureComment(${issueNo}, ${topic}, content)`);
  const body = `### ${topic}\n\n${content}`;
  const azureApiGit = await azureApi.gitApi();
  await azureApiGit.createThread(
    {
      comments: [{ content: body, commentType: 1, parentCommentId: 0 }],
      status: 1,
    },
    config.repoId,
    issueNo
  );
}

async function ensureCommentRemoval(issueNo, topic) {
  logger.debug(`ensureCommentRemoval(issueNo, topic)(${issueNo}, ${topic})`);
  if (issueNo) {
    const azureApiGit = await azureApi.gitApi();
    const threads = await azureApiGit.getThreads(config.repoId, issueNo);
    let threadIdFound = null;

    threads.forEach(thread => {
      if (thread.comments[0].content.startsWith(`### ${topic}\n\n`)) {
        threadIdFound = thread.id;
      }
    });

    if (threadIdFound) {
      await azureApiGit.updateThread(
        {
          status: 4, // close
        },
        config.repoId,
        issueNo,
        threadIdFound
      );
    }
  }
}

// istanbul ignore next
async function abandonPr(prNo) {
  logger.debug(`abandonPr(prNo)(${prNo})`);
  const azureApiGit = await azureApi.gitApi();
  await azureApiGit.updatePullRequest(
    {
      status: 2,
    },
    config.repoId,
    prNo
  );
}

function setBranchStatus(branchName, context, description, state, targetUrl) {
  logger.debug(
    `setBranchStatus(${branchName}, ${context}, ${description}, ${state}, ${targetUrl}) - Not supported by Azure DevOps (yet!)`
  );
}

async function mergePr(pr) {
  logger.info(`mergePr(pr)(${pr}) - Not supported by Azure DevOps (yet!)`);
  await null;
}

function getPrBody(input) {
  // Remove any HTML we use
  return input
    .replace(new RegExp(`\n---\n\n.*?<!-- ${appSlug}-rebase -->.*?\n`), '')
    .replace('', '**')
    .replace('', '**')
    .replace('', '')
    .replace('', '');
}

// istanbul ignore next
function findIssue() {
  logger.warn(`findIssue() is not implemented`);
}

// istanbul ignore next
function ensureIssue() {
  logger.warn(`ensureIssue() is not implemented`);
}

// istanbul ignore next
function ensureIssueClosing() {}

/**
 *
 * @param {number} issueNo
 * @param {string[]} assignees
 */
async function addAssignees(issueNo, assignees) {
  logger.trace(`addAssignees(${issueNo}, ${assignees})`);
  await ensureComment(
    issueNo,
    'Add Assignees',
    assignees.map(a => `@<${a}>`).join(', ')
  );
}

/**
 *
 * @param {number} prNo
 * @param {string[]} reviewers
 */
async function addReviewers(prNo, reviewers) {
  logger.trace(`addReviewers(${prNo}, ${reviewers})`);
  const azureApiGit = await azureApi.gitApi();
  const azureApiCore = await azureApi.getCoreApi();
  const repos = await azureApiGit.getRepositories();
  const repo = repos.filter(c => c.id === config.repoId)[0];
  const teams = await azureApiCore.getTeams(repo.project.id);
  const members = await Promise.all(
    teams.map(
      async t =>
        /* eslint-disable no-return-await */
        await azureApiCore.getTeamMembers(repo.project.id, t.id)
    )
  );

  const ids = [];
  members.forEach(listMembers => {
    listMembers.forEach(m => {
      reviewers.forEach(r => {
        if (
          r.toLowerCase() === m.displayName.toLowerCase() ||
          r.toLowerCase() === m.uniqueName.toLowerCase()
        ) {
          if (ids.filter(c => c.id === m.id).length === 0) {
            ids.push({ id: m.id, name: r });
          }
        }
      });
    });
  });

  await Promise.all(
    ids.map(async obj => {
      await azureApiGit.createPullRequestReviewer(
        {},
        config.repoId,
        prNo,
        obj.id
      );
      logger.info(`Reviewer added: ${obj.name}`);
    })
  );
}

// istanbul ignore next
function deleteLabel() {
  throw new Error('deleteLabel not implemented');
}

// to become async?
function getPrFiles(prNo) {
  logger.info(
    `getPrFiles(prNo)(${prNo}) - Not supported by Azure DevOps (yet!)`
  );
  return [];
}

function getVulnerabilityAlerts() {
  return [];
}

function cleanRepo() {
  // istanbul ignore if
  if (config.storage && config.storage.cleanRepo) {
    config.storage.cleanRepo();
  }
  config = {};
}
<|endoftext|>


<commit_before>const fs = require('fs-extra');
const { join } = require('path');
const path = require('path');
const URL = require('url');
const Git = require('simple-git/promise');
const convertHrtime = require('convert-hrtime');

class Storage {
  constructor() {
    let config = {};
    let git = null;
    let cwd = null;

    Object.assign(this, {
      initRepo,
      cleanRepo,
      getRepoStatus,
      setBaseBranch,
      branchExists,
      commitFilesToBranch,
      createBranch,
      deleteBranch,
      getAllRenovateBranches,
      getBranchCommit,
      getBranchLastCommitTime,
      getCommitMessages,
      getFile,
      getFileList,
      isBranchStale,
      mergeBranch,
    });

    // istanbul ignore next
    async function resetToBranch(branchName) {
      logger.debug(`resetToBranch(${branchName})`);
      await git.raw(['reset', '--hard']);
      await git.checkout(branchName);
      await git.raw(['reset', '--hard', 'origin/' + branchName]);
      await git.raw(['clean', '-fd']);
    }

    // istanbul ignore next
    async function cleanLocalBranches() {
      const existingBranches = (await git.raw(['branch']))
        .split('\n')
        .map(branch => branch.trim())
        .filter(branch => branch.length)
        .filter(branch => !branch.startsWith('* '));
      logger.debug({ existingBranches });
      for (const branchName of existingBranches) {
        await deleteLocalBranch(branchName);
      }
    }

    async function initRepo(args) {
      cleanRepo();
      config = { ...args };
      cwd = config.localDir;
      logger.info('Initialising git repository into ' + cwd);
      const gitHead = path.join(cwd, '.git/HEAD');
      let clone = true;
      async function determineBaseBranch() {
        // see https://stackoverflow.com/a/44750379/1438522
        try {
          config.baseBranch =
            config.baseBranch ||
            (await git.raw(['symbolic-ref', 'refs/remotes/origin/HEAD']))
              .replace('refs/remotes/origin/', '')
              .trim();
        } catch (err) /* istanbul ignore next */ {
          if (
            err.message.startsWith(
              'fatal: ref refs/remotes/origin/HEAD is not a symbolic ref'
            )
          ) {
            throw new Error('empty');
          }
          throw err;
        }
      }
      // istanbul ignore if
      if (process.env.NODE_ENV !== 'test' && (await fs.exists(gitHead))) {
        try {
          git = Git(cwd).silent(true);
          await git.raw(['remote', 'set-url', 'origin', config.url]);
          const fetchStart = process.hrtime();
          await git.fetch(config.url, ['--depth=2', '--no-single-branch']);
          await determineBaseBranch();
          await resetToBranch(config.baseBranch);
          await cleanLocalBranches();
          await git.raw(['remote', 'prune', 'origin']);
          const fetchSeconds =
            Math.round(
              1 + 10 * convertHrtime(process.hrtime(fetchStart)).seconds
            ) / 10;
          logger.info({ fetchSeconds }, 'git fetch completed');
          clone = false;
        } catch (err) {
          logger.error({ err }, 'git fetch error');
        }
      }
      if (clone) {
        await fs.emptyDir(cwd);
        git = Git(cwd).silent(true);
        const cloneStart = process.hrtime();
        try {
          await git.clone(config.url, '.', ['--depth=2', '--no-single-branch']);
        } catch (err) /* istanbul ignore next */ {
          logger.debug({ err }, 'git clone error');
          throw new Error('platform-failure');
        }
        const cloneSeconds =
          Math.round(
            1 + 10 * convertHrtime(process.hrtime(cloneStart)).seconds
          ) / 10;
        logger.info({ cloneSeconds }, 'git clone completed');
      }
      try {
        const latestCommitDate = (await git.log({ n: 1 })).latest.date;
        logger.debug({ latestCommitDate }, 'latest commit');
      } catch (err) /* istanbul ignore next */ {
        if (err.message.includes('does not have any commits yet')) {
          throw new Error('empty');
        }
        logger.warn({ err }, 'Cannot retrieve latest commit date');
      }
      // istanbul ignore if
      if (config.gitPrivateKey) {
        logger.debug('Git private key configured, but not being set');
      } else {
        logger.debug('No git private key present - commits will be unsigned');
        await git.raw(['config', 'commit.gpgsign', 'false']);
      }

      if (global.gitAuthor) {
        logger.info({ gitAuthor: global.gitAuthor }, 'Setting git author');
        try {
          await git.raw(['config', 'user.name', global.gitAuthor.name]);
          await git.raw(['config', 'user.email', global.gitAuthor.email]);
        } catch (err) /* istanbul ignore next */ {
          logger.debug({ err }, 'Error setting git config');
          throw new Error('temporary-error');
        }
      }

      await determineBaseBranch();
    }

    // istanbul ignore next
    function getRepoStatus() {
      return git.status();
    }

    async function createBranch(branchName, sha) {
      logger.debug(`createBranch(${branchName})`);
      await git.reset('hard');
      await git.raw(['clean', '-fd']);
      await git.checkout(['-B', branchName, sha]);
      await git.push(['origin', branchName, '--force']);
    }

    // Return the commit SHA for a branch
    async function getBranchCommit(branchName) {
      const res = await git.revparse(['origin/' + branchName]);
      return res.trim();
    }

    async function getCommitMessages() {
      logger.debug('getCommitMessages');
      const res = await git.log({
        n: 10,
        format: { message: '%s' },
      });
      return res.all.map(commit => commit.message);
    }

    async function setBaseBranch(branchName) {
      if (branchName) {
        logger.debug(`Setting baseBranch to ${branchName}`);
        config.baseBranch = branchName;
        if (branchName !== 'master') {
          config.baseBranchSha = (await git.raw([
            'rev-parse',
            'origin/' + branchName,
          ])).trim();
        }
      }
    }

    async function getFileList(branchName) {
      const branch = branchName || config.baseBranch;
      const exists = await branchExists(branch);
      if (!exists) {
        return [];
      }
      const files = await git.raw([
        'ls-tree',
        '-r',
        '--name-only',
        'origin/' + branch,
      ]);
      // istanbul ignore if
      if (!files) {
        return [];
      }
      return files.split('\n').filter(Boolean);
    }

    async function branchExists(branchName) {
      try {
        await git.raw(['show-branch', 'origin/' + branchName]);
        return true;
      } catch (ex) {
        return false;
      }
    }

    async function getAllRenovateBranches(branchPrefix) {
      const branches = await git.branch(['--remotes', '--verbose']);
      return branches.all
        .map(localName)
        .filter(branchName => branchName.startsWith(branchPrefix));
    }

    async function isBranchStale(branchName) {
      const branches = await git.branch([
        '--remotes',
        '--verbose',
        '--contains',
        config.baseBranchSha || `origin/${config.baseBranch}`,
      ]);
      return !branches.all.map(localName).includes(branchName);
    }

    async function deleteLocalBranch(branchName) {
      await git.branch(['-D', branchName]);
    }

    async function deleteBranch(branchName) {
      try {
        await git.raw(['push', '--delete', 'origin', branchName]);
        logger.debug({ branchName }, 'Deleted remote branch');
      } catch (err) /* istanbul ignore next */ {
        logger.info({ branchName, err }, 'Error deleting remote branch');
        if (err.message.includes('.github/main.workflow')) {
          logger.warn(
            'A GitHub bug prevents gitFs + GitHub Actions. Please disable gitFs'
          );
        } else {
          throw new Error('repository-changed');
        }
      }
      try {
        await deleteLocalBranch(branchName);
        // istanbul ignore next
        logger.debug({ branchName }, 'Deleted local branch');
      } catch (err) {
        logger.debug({ branchName }, 'No local branch to delete');
      }
    }

    async function mergeBranch(branchName) {
      await git.reset('hard');
      await git.checkout(['-B', branchName, 'origin/' + branchName]);
      await git.checkout(config.baseBranch);
      await git.merge([branchName]);
      await git.push('origin', config.baseBranch);
    }

    async function getBranchLastCommitTime(branchName) {
      try {
        const time = await git.show([
          '-s',
          '--format=%ai',
          'origin/' + branchName,
        ]);
        return new Date(Date.parse(time));
      } catch (ex) {
        return new Date();
      }
    }

    async function getFile(filePath, branchName) {
      if (branchName) {
        const exists = await branchExists(branchName);
        if (!exists) {
          logger.info({ branchName }, 'branch no longer exists - aborting');
          throw new Error('repository-changed');
        }
      }
      try {
        const content = await git.show([
          'origin/' + (branchName || config.baseBranch) + ':' + filePath,
        ]);
        return content;
      } catch (ex) {
        return null;
      }
    }

    async function commitFilesToBranch(
      branchName,
      files,
      message,
      parentBranch = config.baseBranch
    ) {
      logger.debug(`Committing files to branch ${branchName}`);
      try {
        await git.reset('hard');
        await git.raw(['clean', '-fd']);
        await git.checkout(['-B', branchName, 'origin/' + parentBranch]);
        const fileNames = [];
        const deleted = [];
        for (const file of files) {
          // istanbul ignore if
          if (file.name === '|delete|') {
            deleted.push(file.contents);
          } else {
            fileNames.push(file.name);
            await fs.outputFile(
              join(cwd, file.name),
              Buffer.from(file.contents)
            );
          }
        }
        // istanbul ignore if
        if (fileNames.length === 1 && fileNames[0] === 'renovate.json') {
          fileNames.unshift('-f');
        }
        if (fileNames.length) await git.add(fileNames);
        if (deleted.length) {
          for (const f of deleted) {
            try {
              await git.rm([f]);
            } catch (err) {
              logger.debug({ err }, 'Cannot delete ' + f);
            }
          }
        }
        await git.commit(message);
        await git.push([
          'origin',
          `${branchName}:${branchName}`,
          '--force',
          '-u',
        ]);
      } catch (err) /* istanbul ignore next */ {
        logger.debug({ err }, 'Error commiting files');
        if (err.message.includes('.github/main.workflow')) {
          logger.warn(
            'A GitHub bug prevents gitFs + GitHub Actions. Please disable gitFs'
          );
          throw new Error('disable-gitfs');
        } else if (err.message.includes('[remote rejected]')) {
          throw new Error('repository-changed');
        }
        throw err;
      }
    }

    function cleanRepo() {}
  }
}

function localName(branchName) {
  return branchName.replace(/^origin\//, '');
}

Storage.getUrl = ({ gitFs, auth, hostname, host, repository }) => {
  let protocol = gitFs || 'https';
  // istanbul ignore if
  if (protocol.toString() === 'true') {
    protocol = 'https';
  }
  if (protocol === 'ssh') {
    return `git@${hostname}:${repository}.git`;
  }
  return URL.format({
    protocol,
    auth,
    hostname,
    host,
    pathname: repository + '.git',
  });
};

module.exports = Storage;
<commit_msg>tests: fix coverage<commit_after>const fs = require('fs-extra');
const { join } = require('path');
const path = require('path');
const URL = require('url');
const Git = require('simple-git/promise');
const convertHrtime = require('convert-hrtime');

class Storage {
  constructor() {
    let config = {};
    let git = null;
    let cwd = null;

    Object.assign(this, {
      initRepo,
      cleanRepo,
      getRepoStatus,
      setBaseBranch,
      branchExists,
      commitFilesToBranch,
      createBranch,
      deleteBranch,
      getAllRenovateBranches,
      getBranchCommit,
      getBranchLastCommitTime,
      getCommitMessages,
      getFile,
      getFileList,
      isBranchStale,
      mergeBranch,
    });

    // istanbul ignore next
    async function resetToBranch(branchName) {
      logger.debug(`resetToBranch(${branchName})`);
      await git.raw(['reset', '--hard']);
      await git.checkout(branchName);
      await git.raw(['reset', '--hard', 'origin/' + branchName]);
      await git.raw(['clean', '-fd']);
    }

    // istanbul ignore next
    async function cleanLocalBranches() {
      const existingBranches = (await git.raw(['branch']))
        .split('\n')
        .map(branch => branch.trim())
        .filter(branch => branch.length)
        .filter(branch => !branch.startsWith('* '));
      logger.debug({ existingBranches });
      for (const branchName of existingBranches) {
        await deleteLocalBranch(branchName);
      }
    }

    async function initRepo(args) {
      cleanRepo();
      config = { ...args };
      cwd = config.localDir;
      logger.info('Initialising git repository into ' + cwd);
      const gitHead = path.join(cwd, '.git/HEAD');
      let clone = true;
      async function determineBaseBranch() {
        // see https://stackoverflow.com/a/44750379/1438522
        try {
          config.baseBranch =
            config.baseBranch ||
            (await git.raw(['symbolic-ref', 'refs/remotes/origin/HEAD']))
              .replace('refs/remotes/origin/', '')
              .trim();
        } catch (err) /* istanbul ignore next */ {
          if (
            err.message.startsWith(
              'fatal: ref refs/remotes/origin/HEAD is not a symbolic ref'
            )
          ) {
            throw new Error('empty');
          }
          throw err;
        }
      }
      // istanbul ignore if
      if (process.env.NODE_ENV !== 'test' && (await fs.exists(gitHead))) {
        try {
          git = Git(cwd).silent(true);
          await git.raw(['remote', 'set-url', 'origin', config.url]);
          const fetchStart = process.hrtime();
          await git.fetch(config.url, ['--depth=2', '--no-single-branch']);
          await determineBaseBranch();
          await resetToBranch(config.baseBranch);
          await cleanLocalBranches();
          await git.raw(['remote', 'prune', 'origin']);
          const fetchSeconds =
            Math.round(
              1 + 10 * convertHrtime(process.hrtime(fetchStart)).seconds
            ) / 10;
          logger.info({ fetchSeconds }, 'git fetch completed');
          clone = false;
        } catch (err) {
          logger.error({ err }, 'git fetch error');
        }
      }
      if (clone) {
        await fs.emptyDir(cwd);
        git = Git(cwd).silent(true);
        const cloneStart = process.hrtime();
        try {
          await git.clone(config.url, '.', ['--depth=2', '--no-single-branch']);
        } catch (err) /* istanbul ignore next */ {
          logger.debug({ err }, 'git clone error');
          throw new Error('platform-failure');
        }
        const cloneSeconds =
          Math.round(
            1 + 10 * convertHrtime(process.hrtime(cloneStart)).seconds
          ) / 10;
        logger.info({ cloneSeconds }, 'git clone completed');
      }
      try {
        const latestCommitDate = (await git.log({ n: 1 })).latest.date;
        logger.debug({ latestCommitDate }, 'latest commit');
      } catch (err) /* istanbul ignore next */ {
        if (err.message.includes('does not have any commits yet')) {
          throw new Error('empty');
        }
        logger.warn({ err }, 'Cannot retrieve latest commit date');
      }
      // istanbul ignore if
      if (config.gitPrivateKey) {
        logger.debug('Git private key configured, but not being set');
      } else {
        logger.debug('No git private key present - commits will be unsigned');
        await git.raw(['config', 'commit.gpgsign', 'false']);
      }

      if (global.gitAuthor) {
        logger.info({ gitAuthor: global.gitAuthor }, 'Setting git author');
        try {
          await git.raw(['config', 'user.name', global.gitAuthor.name]);
          await git.raw(['config', 'user.email', global.gitAuthor.email]);
        } catch (err) /* istanbul ignore next */ {
          logger.debug({ err }, 'Error setting git config');
          throw new Error('temporary-error');
        }
      }

      await determineBaseBranch();
    }

    // istanbul ignore next
    function getRepoStatus() {
      return git.status();
    }

    async function createBranch(branchName, sha) {
      logger.debug(`createBranch(${branchName})`);
      await git.reset('hard');
      await git.raw(['clean', '-fd']);
      await git.checkout(['-B', branchName, sha]);
      await git.push(['origin', branchName, '--force']);
    }

    // Return the commit SHA for a branch
    async function getBranchCommit(branchName) {
      const res = await git.revparse(['origin/' + branchName]);
      return res.trim();
    }

    async function getCommitMessages() {
      logger.debug('getCommitMessages');
      const res = await git.log({
        n: 10,
        format: { message: '%s' },
      });
      return res.all.map(commit => commit.message);
    }

    async function setBaseBranch(branchName) {
      if (branchName) {
        logger.debug(`Setting baseBranch to ${branchName}`);
        config.baseBranch = branchName;
        if (branchName !== 'master') {
          config.baseBranchSha = (await git.raw([
            'rev-parse',
            'origin/' + branchName,
          ])).trim();
        }
      }
    }

    async function getFileList(branchName) {
      const branch = branchName || config.baseBranch;
      const exists = await branchExists(branch);
      if (!exists) {
        return [];
      }
      const files = await git.raw([
        'ls-tree',
        '-r',
        '--name-only',
        'origin/' + branch,
      ]);
      // istanbul ignore if
      if (!files) {
        return [];
      }
      return files.split('\n').filter(Boolean);
    }

    async function branchExists(branchName) {
      try {
        await git.raw(['show-branch', 'origin/' + branchName]);
        return true;
      } catch (ex) {
        return false;
      }
    }

    async function getAllRenovateBranches(branchPrefix) {
      const branches = await git.branch(['--remotes', '--verbose']);
      return branches.all
        .map(localName)
        .filter(branchName => branchName.startsWith(branchPrefix));
    }

    async function isBranchStale(branchName) {
      const branches = await git.branch([
        '--remotes',
        '--verbose',
        '--contains',
        config.baseBranchSha || `origin/${config.baseBranch}`,
      ]);
      return !branches.all.map(localName).includes(branchName);
    }

    async function deleteLocalBranch(branchName) {
      await git.branch(['-D', branchName]);
    }

    async function deleteBranch(branchName) {
      try {
        await git.raw(['push', '--delete', 'origin', branchName]);
        logger.debug({ branchName }, 'Deleted remote branch');
      } catch (err) /* istanbul ignore next */ {
        logger.info({ branchName, err }, 'Error deleting remote branch');
        if (err.message.includes('.github/main.workflow')) {
          logger.warn(
            'A GitHub bug prevents gitFs + GitHub Actions. Please disable gitFs'
          );
        } else {
          throw new Error('repository-changed');
        }
      }
      try {
        await deleteLocalBranch(branchName);
        // istanbul ignore next
        logger.debug({ branchName }, 'Deleted local branch');
      } catch (err) {
        logger.debug({ branchName }, 'No local branch to delete');
      }
    }

    async function mergeBranch(branchName) {
      await git.reset('hard');
      await git.checkout(['-B', branchName, 'origin/' + branchName]);
      await git.checkout(config.baseBranch);
      await git.merge([branchName]);
      await git.push('origin', config.baseBranch);
    }

    async function getBranchLastCommitTime(branchName) {
      try {
        const time = await git.show([
          '-s',
          '--format=%ai',
          'origin/' + branchName,
        ]);
        return new Date(Date.parse(time));
      } catch (ex) {
        return new Date();
      }
    }

    async function getFile(filePath, branchName) {
      if (branchName) {
        const exists = await branchExists(branchName);
        if (!exists) {
          logger.info({ branchName }, 'branch no longer exists - aborting');
          throw new Error('repository-changed');
        }
      }
      try {
        const content = await git.show([
          'origin/' + (branchName || config.baseBranch) + ':' + filePath,
        ]);
        return content;
      } catch (ex) {
        return null;
      }
    }

    async function commitFilesToBranch(
      branchName,
      files,
      message,
      parentBranch = config.baseBranch
    ) {
      logger.debug(`Committing files to branch ${branchName}`);
      try {
        await git.reset('hard');
        await git.raw(['clean', '-fd']);
        await git.checkout(['-B', branchName, 'origin/' + parentBranch]);
        const fileNames = [];
        const deleted = [];
        for (const file of files) {
          // istanbul ignore if
          if (file.name === '|delete|') {
            deleted.push(file.contents);
          } else {
            fileNames.push(file.name);
            await fs.outputFile(
              join(cwd, file.name),
              Buffer.from(file.contents)
            );
          }
        }
        // istanbul ignore if
        if (fileNames.length === 1 && fileNames[0] === 'renovate.json') {
          fileNames.unshift('-f');
        }
        if (fileNames.length) await git.add(fileNames);
        if (deleted.length) {
          for (const f of deleted) {
            try {
              await git.rm([f]);
            } catch (err) /* istanbul ignore next */ {
              logger.debug({ err }, 'Cannot delete ' + f);
            }
          }
        }
        await git.commit(message);
        await git.push([
          'origin',
          `${branchName}:${branchName}`,
          '--force',
          '-u',
        ]);
      } catch (err) /* istanbul ignore next */ {
        logger.debug({ err }, 'Error commiting files');
        if (err.message.includes('.github/main.workflow')) {
          logger.warn(
            'A GitHub bug prevents gitFs + GitHub Actions. Please disable gitFs'
          );
          throw new Error('disable-gitfs');
        } else if (err.message.includes('[remote rejected]')) {
          throw new Error('repository-changed');
        }
        throw err;
      }
    }

    function cleanRepo() {}
  }
}

function localName(branchName) {
  return branchName.replace(/^origin\//, '');
}

Storage.getUrl = ({ gitFs, auth, hostname, host, repository }) => {
  let protocol = gitFs || 'https';
  // istanbul ignore if
  if (protocol.toString() === 'true') {
    protocol = 'https';
  }
  if (protocol === 'ssh') {
    return `git@${hostname}:${repository}.git`;
  }
  return URL.format({
    protocol,
    auth,
    hostname,
    host,
    pathname: repository + '.git',
  });
};

module.exports = Storage;
<|endoftext|>