list-branches.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /* eslint-disable no-console */
  2. /*
  3. * USAGE:
  4. * node list-branches [OPTION]
  5. *
  6. * OPTIONS:
  7. * --inactive : Return inactive branches (default)
  8. * --illegal : Return illegal named branches
  9. */
  10. const { execSync } = require('child_process');
  11. const url = require('url');
  12. const EXCLUDE_TERM_DAYS = 14;
  13. const EXCLUDE_PATTERNS = [
  14. /^feat\/custom-sidebar-2$/,
  15. // https://regex101.com/r/Lnx7Pz/3
  16. /^dev\/[\d.x]*$/,
  17. ];
  18. const LEGAL_PATTERNS = [
  19. /^master$/,
  20. // https://regex101.com/r/p9xswM/4
  21. /^(dev|feat|imprv|support|fix|rc|release|tmp)\/.+$/,
  22. ];
  23. const GITHUB_REPOS_URI = 'https://github.com/weseek/growi/';
  24. class BranchSummary {
  25. constructor(line) {
  26. const splitted = line.split('\t'); // split with '%09'
  27. this.authorDate = new Date(splitted[0].trim());
  28. this.authorName = splitted[1].trim();
  29. this.branchName = splitted[2].trim().replace(/^origin\//, '');
  30. this.subject = splitted[3].trim();
  31. }
  32. }
  33. function getExcludeTermDate() {
  34. const date = new Date();
  35. date.setDate(date.getDate() - EXCLUDE_TERM_DAYS);
  36. return date;
  37. }
  38. function getBranchSummaries() {
  39. // exec git for-each-ref
  40. const out = execSync(`\
  41. git for-each-ref refs/remotes \
  42. --sort=-committerdate \
  43. --format='%(authordate:iso) %09 %(authorname) %09 %(refname:short) %09 %(subject)'
  44. `).toString();
  45. // parse
  46. const summaries = out
  47. .split('\n')
  48. .filter(v => v !== '') // trim empty string
  49. .map(line => new BranchSummary(line))
  50. .filter((summary) => { // exclude branches that matches to patterns
  51. return !EXCLUDE_PATTERNS.some(pattern => pattern.test(summary.branchName));
  52. });
  53. return summaries;
  54. }
  55. function getGitHubCommitsUrl(branchName) {
  56. return url.resolve(GITHUB_REPOS_URI, `commits/${branchName}`);
  57. }
  58. function getGitHubComparingLink(branchName) {
  59. const label = `master <- ${branchName}`;
  60. const link = url.resolve(GITHUB_REPOS_URI, `compare/${branchName}`);
  61. return `<${link}|${label}>`;
  62. }
  63. /**
  64. * @see https://api.slack.com/messaging/composing/layouts#building-attachments
  65. * @see https://github.com/marketplace/actions/slack-incoming-webhook
  66. *
  67. * @param {string} mode
  68. * @param {BranchSummary} summaries
  69. */
  70. function printSlackAttachments(mode, summaries) {
  71. const color = (mode === 'illegal') ? 'warning' : '#999999';
  72. const attachments = summaries.map((summary) => {
  73. const {
  74. authorName, authorDate, branchName, subject,
  75. } = summary;
  76. return {
  77. color,
  78. title: branchName,
  79. title_link: getGitHubCommitsUrl(branchName),
  80. fields: [
  81. {
  82. title: 'Author Date',
  83. value: authorDate,
  84. short: true,
  85. },
  86. {
  87. title: 'Author',
  88. value: authorName,
  89. short: true,
  90. },
  91. {
  92. title: 'Last Commit Subject',
  93. value: subject,
  94. },
  95. {
  96. title: 'Comparing Link',
  97. value: getGitHubComparingLink(branchName),
  98. },
  99. ],
  100. };
  101. });
  102. console.log(JSON.stringify(attachments));
  103. }
  104. async function main(mode) {
  105. const summaries = getBranchSummaries();
  106. let filteredSummaries;
  107. switch (mode) {
  108. case 'illegal':
  109. filteredSummaries = summaries
  110. .filter((summary) => { // exclude branches that matches to patterns
  111. return !LEGAL_PATTERNS.some(pattern => pattern.test(summary.branchName));
  112. });
  113. break;
  114. default: {
  115. const excludeTermDate = getExcludeTermDate();
  116. filteredSummaries = summaries
  117. .filter((summary) => {
  118. return summary.authorDate < excludeTermDate;
  119. });
  120. break;
  121. }
  122. }
  123. printSlackAttachments(mode, filteredSummaries);
  124. }
  125. const args = process.argv.slice(2);
  126. let mode = 'inactive';
  127. if (args.length > 0 && args[0] === '--illegal') {
  128. mode = 'illegal';
  129. }
  130. main(mode);