/[osn-commons]/trunk/git/genchangelog
ViewVC logotype

Diff of /trunk/git/genchangelog

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 14 by rakinar2, Sat Aug 3 15:50:38 2024 UTC revision 15 by rakinar2, Sat Aug 3 16:04:38 2024 UTC
# Line 37  function perror(...args) { Line 37  function perror(...args) {
37  }  }
38    
39  function findInPath(executable) {  function findInPath(executable) {
40      for (const segment of process.env.PATH?.split(process.platform === "win32" ? ";" : ":") ?? []) {      for (const segment of process.env.PATH?.split(
41          const executablePath = path.join(segment, executable + (process.platform === "win32" ? ".exe" : ""));          process.platform === "win32" ? ";" : ":"
42                ) ?? []) {
43            const executablePath = path.join(
44                segment,
45                executable + (process.platform === "win32" ? ".exe" : "")
46            );
47    
48          if (existsSync(executablePath)) {          if (existsSync(executablePath)) {
49              return executablePath;              return executablePath;
50          }          }
# Line 63  function checkForGit() { Line 68  function checkForGit() {
68  function getGitLog(gitPath) {  function getGitLog(gitPath) {
69      try {      try {
70          return execSync(gitPath + " --no-pager log", { encoding: "utf8" });          return execSync(gitPath + " --no-pager log", { encoding: "utf8" });
71      }      } catch {
     catch {  
72          perror("command `git --no-pager log' failed");          perror("command `git --no-pager log' failed");
73          exit(1);          exit(1);
74      }      }
# Line 80  function parseGitLog(gitLog) { Line 84  function parseGitLog(gitLog) {
84              continue;              continue;
85          }          }
86    
87          const [, hash] = lines[i++].split(' ');          const [, hash] = lines[i++].split(" ");
88          const headerProps = {};          const headerProps = {};
89    
90          while (i < lines.length && lines[i].trim() !== "" && !/^\s/.test(lines[i])) {          while (
91                i < lines.length &&
92                lines[i].trim() !== "" &&
93                !/^\s/.test(lines[i])
94            ) {
95              const colonIndex = lines[i].indexOf(":");              const colonIndex = lines[i].indexOf(":");
96              const name = lines[i].slice(0, colonIndex).toLowerCase();              const name = lines[i].slice(0, colonIndex).toLowerCase();
97              const value = lines[i].slice(colonIndex + 1).trim();              const value = lines[i].slice(colonIndex + 1).trim();
# Line 99  function parseGitLog(gitLog) { Line 107  function parseGitLog(gitLog) {
107              if (!lineToPush) {              if (!lineToPush) {
108                  continue;                  continue;
109              }              }
110                
111              messageLines.push(lineToPush);              messageLines.push(lineToPush);
112          }          }
113    
114          let mindex = messageLines.length - 1;          let mindex = messageLines.length - 1;
115          const footerProps = {};          const footerProps = {};
116          const validFooterProps = ["signed-off-by", "co-authored-by", "on-behalf-of"];          const validFooterProps = [
117                        "signed-off-by",
118          while (mindex >= 1 && /^[A-Za-z0-9-]+: /.test(messageLines.at(mindex))) {              "co-authored-by",
119                "on-behalf-of",
120            ];
121    
122            while (
123                mindex >= 1 &&
124                /^[A-Za-z0-9-]+: /.test(messageLines.at(mindex))
125            ) {
126              const messageLine = messageLines[mindex--];              const messageLine = messageLines[mindex--];
127              const colonIndex = messageLine.indexOf(":");              const colonIndex = messageLine.indexOf(":");
128              const name = messageLine.slice(0, colonIndex).toLowerCase();              const name = messageLine.slice(0, colonIndex).toLowerCase();
# Line 115  function parseGitLog(gitLog) { Line 130  function parseGitLog(gitLog) {
130              if (!validFooterProps.includes(name)) {              if (!validFooterProps.includes(name)) {
131                  continue;                  continue;
132              }              }
133                
134              const value = messageLine.slice(colonIndex + 1).trim();              const value = messageLine.slice(colonIndex + 1).trim();
135    
136              if (name in footerProps && !Array.isArray(footerProps[name])) {              if (name in footerProps && !Array.isArray(footerProps[name])) {
# Line 124  function parseGitLog(gitLog) { Line 139  function parseGitLog(gitLog) {
139    
140              if (Array.isArray(footerProps[name])) {              if (Array.isArray(footerProps[name])) {
141                  footerProps[name].push(value);                  footerProps[name].push(value);
142              }              } else {
             else {  
143                  footerProps[name] = value;                  footerProps[name] = value;
144              }              }
145    
# Line 133  function parseGitLog(gitLog) { Line 147  function parseGitLog(gitLog) {
147          }          }
148    
149          const message = messageLines.join("\n");          const message = messageLines.join("\n");
150            
151          commits.push({          commits.push({
152              hash,              hash,
153              message,              message,
# Line 142  function parseGitLog(gitLog) { Line 156  function parseGitLog(gitLog) {
156              signedOffBy: footerProps["signed-off-by"],              signedOffBy: footerProps["signed-off-by"],
157              onBehalfOf: footerProps["on-behalf-of"],              onBehalfOf: footerProps["on-behalf-of"],
158              author: headerProps["author"],              author: headerProps["author"],
159              createdAt: new Date(headerProps["date"])              createdAt: new Date(headerProps["date"]),
160          });          });
161      }      }
162    
# Line 151  function parseGitLog(gitLog) { Line 165  function parseGitLog(gitLog) {
165    
166  function generateChangelog(commits) {  function generateChangelog(commits) {
167      let output = "";      let output = "";
168    
169      const grouppedCommitsByDate = {};      const grouppedCommitsByDate = {};
170        
171      for (const commit of commits) {      for (const commit of commits) {
172          const key = `${commit.createdAt.getUTCDate().toString().padStart(2, 0)}-${commit.createdAt.getUTCMonth().toString().padStart(2, '0')}-${commit.createdAt.getUTCFullYear()}::${Array.isArray(commit.author) ? commit.author.join(':') : commit.author}`;          const key = `${commit.createdAt
173                .getUTCDate()
174                .toString()
175                .padStart(2, 0)}-${commit.createdAt
176                .getUTCMonth()
177                .toString()
178                .padStart(2, "0")}-${commit.createdAt.getUTCFullYear()}::${
179                Array.isArray(commit.author)
180                    ? commit.author.join(":")
181                    : commit.author
182            }`;
183          grouppedCommitsByDate[key] ??= [];          grouppedCommitsByDate[key] ??= [];
184          grouppedCommitsByDate[key].push(commit);          grouppedCommitsByDate[key].push(commit);
185      }      }
# Line 165  function generateChangelog(commits) { Line 189  function generateChangelog(commits) {
189          const date = key.slice(0, separatorPosition);          const date = key.slice(0, separatorPosition);
190          const commits = grouppedCommitsByDate[key];          const commits = grouppedCommitsByDate[key];
191    
192          output += `${date}  ${Array.isArray(commits[0].author) ? commits[0].author.join(", ") : commits[0].author}\n\n`;          output += `${date}  ${
193                Array.isArray(commits[0].author)
194                    ? commits[0].author.join(", ")
195                    : commits[0].author
196            }\n\n`;
197    
198          for (const commit of commits) {          for (const commit of commits) {
199              output += `        ${commit.message.replaceAll("\n", "\n        ")}\n\n`;              output += `        ${commit.message.replaceAll(
200                    "\n",
201                    "\n        "
202                )}\n\n`;
203          }          }
204      }      }
205        
206      return output.trim();      return output.trim();
207  }  }
208    
# Line 194  function printHelp() { Line 225  function printHelp() {
225    
226  function printVersion() {  function printVersion() {
227      console.log("Copyright (C) 2024 OSN, Inc.");      console.log("Copyright (C) 2024 OSN, Inc.");
228      console.log("License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.");      console.log(
229      console.log("This is free software: you are free to change and redistribute it.");          "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>."
230        );
231        console.log(
232            "This is free software: you are free to change and redistribute it."
233        );
234      console.log("There is NO WARRANTY, to the extent permitted by law.");      console.log("There is NO WARRANTY, to the extent permitted by law.");
235      console.log();      console.log();
236      console.log("Written by Ar Rakin.");      console.log("Written by Ar Rakin.");
# Line 212  async function main() { Line 247  async function main() {
247              options: {              options: {
248                  help: {                  help: {
249                      type: "boolean",                      type: "boolean",
250                      alias: 'h'                      alias: "h",
251                  },                  },
252                  version: {                  version: {
253                      type: "boolean",                      type: "boolean",
254                      alias: "v"                      alias: "v",
255                  },                  },
256                  output: {                  output: {
257                      type: "string",                      type: "string",
258                      short: "o"                      short: "o",
259                  },                  },
260                  "no-overwrite": {                  "no-overwrite": {
261                      type: "boolean"                      type: "boolean",
262                  }                  },
263              }              },
264          }).values;          }).values;
265      }      } catch (error) {
     catch (error) {  
266          perror(`${error?.message ?? error}`);          perror(`${error?.message ?? error}`);
267          exit(1);          exit(1);
268      }      }
# Line 244  async function main() { Line 278  async function main() {
278      }      }
279    
280      if (!options.output && options["no-overwrite"]) {      if (!options.output && options["no-overwrite"]) {
281          perror("option `--no-overwrite' without `--output` does not make sense");          perror(
282                "option `--no-overwrite' without `--output` does not make sense"
283            );
284          exit(1);          exit(1);
285      }      }
286        
287      if (options.output && options["no-overwrite"] && existsSync(options.output)) {      if (
288            options.output &&
289            options["no-overwrite"] &&
290            existsSync(options.output)
291        ) {
292          perror(`${options.output}: cannot write changelog: File exists`);          perror(`${options.output}: cannot write changelog: File exists`);
293          exit(1);          exit(1);
294      }      }
295        
296      const gitPath = checkForGit();      const gitPath = checkForGit();
297      const gitLog = getGitLog(gitPath);      const gitLog = getGitLog(gitPath);
298      const commits = parseGitLog(gitLog);      const commits = parseGitLog(gitLog);
299      const changelog = generateChangelog(commits);      const filteredCommits = commits.filter(
300            (commit) =>
301                !/Merge pull request #\d+ from|Merge branch '\S+' of/.test(
302                    commit.message
303                )
304        );
305        const changelog = generateChangelog(filteredCommits);
306    
307      if (options.output) {              if (options.output) {
308          try {          try {
309              await writeFile(options.output, changelog);              await writeFile(options.output, changelog);
310          }          } catch (error) {
311          catch (error) {              perror(
312              perror(`${options.output}: failed to write changelog: ${error?.message ?? error}`);                  `${options.output}: failed to write changelog: ${
313                        error?.message ?? error
314                    }`
315                );
316              exit(1);              exit(1);
317          }          }
318    
319          print(`wrote generated changelog to ${options.output}`);          print(`wrote generated changelog to ${options.output}`);
320      }      } else {
     else {  
321          console.log(changelog);          console.log(changelog);
322      }      }
323  }  }

Legend:
Removed from v.14  
changed lines
  Added in v.15

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26