describe-ref.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. "use strict";
  2. const log = require("npmlog");
  3. const childProcess = require("@lerna/child-process");
  4. module.exports.describeRef = describeRef;
  5. module.exports.describeRefSync = describeRefSync;
  6. /**
  7. * @typedef {object} DescribeRefOptions
  8. * @property {string} [cwd] Defaults to `process.cwd()`
  9. * @property {string} [match] Glob passed to `--match` flag
  10. */
  11. /**
  12. * @typedef {object} DescribeRefFallbackResult When annotated release tags are missing
  13. * @property {boolean} isDirty
  14. * @property {string} refCount
  15. * @property {string} sha
  16. */
  17. /**
  18. * @typedef {object} DescribeRefDetailedResult When annotated release tags are present
  19. * @property {string} lastTagName
  20. * @property {string} lastVersion
  21. * @property {boolean} isDirty
  22. * @property {string} refCount
  23. * @property {string} sha
  24. */
  25. /**
  26. * Build `git describe` args.
  27. * @param {DescribeRefOptions} options
  28. * @param {boolean} [includeMergedTags]
  29. */
  30. function getArgs(options, includeMergedTags) {
  31. let args = [
  32. "describe",
  33. // fallback to short sha if no tags located
  34. "--always",
  35. // always return full result, helps identify existing release
  36. "--long",
  37. // annotate if uncommitted changes present
  38. "--dirty",
  39. // prefer tags originating on upstream branch
  40. "--first-parent",
  41. ];
  42. if (options.match) {
  43. args.push("--match", options.match);
  44. }
  45. if (includeMergedTags) {
  46. // we want to consider all tags, also from merged branches
  47. args = args.filter((arg) => arg !== "--first-parent");
  48. }
  49. return args;
  50. }
  51. /**
  52. * @param {DescribeRefOptions} [options]
  53. * @param {boolean} [includeMergedTags]
  54. * @returns {Promise<DescribeRefFallbackResult|DescribeRefDetailedResult>}
  55. */
  56. function describeRef(options = {}, includeMergedTags) {
  57. const promise = childProcess.exec("git", getArgs(options, includeMergedTags), options);
  58. return promise.then(({ stdout }) => {
  59. const result = parse(stdout, options.cwd);
  60. log.verbose("git-describe", "%j => %j", options && options.match, stdout);
  61. log.silly("git-describe", "parsed => %j", result);
  62. return result;
  63. });
  64. }
  65. /**
  66. * @param {DescribeRefOptions} [options]
  67. * @param {boolean} [includeMergedTags]
  68. */
  69. function describeRefSync(options = {}, includeMergedTags) {
  70. const stdout = childProcess.execSync("git", getArgs(options, includeMergedTags), options);
  71. const result = parse(stdout, options.cwd);
  72. // only called by collect-updates with no matcher
  73. log.silly("git-describe.sync", "%j => %j", stdout, result);
  74. return result;
  75. }
  76. /**
  77. * Parse git output and return relevant metadata.
  78. * @param {string} stdout Result of `git describe`
  79. * @param {string} [cwd] Defaults to `process.cwd()`
  80. * @returns {DescribeRefFallbackResult|DescribeRefDetailedResult}
  81. */
  82. function parse(stdout, cwd) {
  83. const minimalShaRegex = /^([0-9a-f]{7,40})(-dirty)?$/;
  84. // when git describe fails to locate tags, it returns only the minimal sha
  85. if (minimalShaRegex.test(stdout)) {
  86. // repo might still be dirty
  87. const [, sha, isDirty] = minimalShaRegex.exec(stdout);
  88. // count number of commits since beginning of time
  89. const refCount = childProcess.execSync("git", ["rev-list", "--count", sha], { cwd });
  90. return { refCount, sha, isDirty: Boolean(isDirty) };
  91. }
  92. const [, lastTagName, lastVersion, refCount, sha, isDirty] =
  93. /^((?:.*@)?(.*))-(\d+)-g([0-9a-f]+)(-dirty)?$/.exec(stdout) || [];
  94. return { lastTagName, lastVersion, refCount, sha, isDirty: Boolean(isDirty) };
  95. }