Add an isMerge attribute to commits

f0856f6c06cb76569ce3961f775ce4daf431ab82

Tucker McKnight <tucker@pangolin.lan> | Sun Feb 01 2026

Add an isMerge attribute to commits

Also fix bug where the commit message wasn't being snipped correctly
on merge commits. This was because they have the extra metadata line
at the top, "Merge," which means the commit message is now five lines
down instead of four.
js_templates/commits.ts:51
Before
50
51
52

53
54
        class: "bezel-gray p-2 my-2",
        style: "max-width: 40rem;"
      }, [
⁣
        m('a', {
          class: "fs-5",
          href: `${data.reposPath}/${slugify(data.patchPage.repoName)}/branches/${slugify(data.patchPage.branchName)}/commits/${commit.hash}`,
After
50
51
52
53
54
55
        class: "bezel-gray p-2 my-2",
        style: "max-width: 40rem;"
      }, [
        commit.isMerge ? m('span', {class: 'badge rounded-pill bg-primary me-1'}, 'merge') : null,
        m('a', {
          class: "fs-5",
          href: `${data.reposPath}/${slugify(data.patchPage.repoName)}/branches/${slugify(data.patchPage.branchName)}/commits/${commit.hash}`,
src/dataTypes.ts:20
Before
19
20
21

22
23
  commits?: Map<string, {
    hash: string,
    message: string,
⁣
    author: string,
    date: Date,
    parent: string | null,
After
19
20
21
22
23
24
  commits?: Map<string, {
    hash: string,
    message: string,
    isMerge: boolean,
    author: string,
    date: Date,
    parent: string | null,
src/repos.ts:126
Before
125
126
127
128
129
130
131
132
133
134
135
136
137
      const compareTo = branchDescription.compareTo || reposConfig.repos[repoName].defaultBranch
      const compareToBranch = branches.find((test) => test.name === compareTo)

      const compareToBranchCommits = new Set()
      let currentCommit = commits.get(compareToBranch.head)
      while (currentCommit !== undefined) {
        compareToBranchCommits.add(currentCommit.hash)
        currentCommit = commits.get(currentCommit.parent)
      }

      const thisBranchCommits = new Set()
      currentCommit = commits.get(branch.head)
      while (currentCommit !== undefined) {
        thisBranchCommits.add(currentCommit.hash)
After
125
126
127
128
129
130
131
132
133
134
135
136
137
      const compareTo = branchDescription.compareTo || reposConfig.repos[repoName].defaultBranch
      const compareToBranch = branches.find((test) => test.name === compareTo)

      const compareToBranchCommits = new Set<string>()
      let currentCommit = commits.get(compareToBranch.head)
      while (currentCommit !== undefined) {
        compareToBranchCommits.add(currentCommit.hash)
        currentCommit = commits.get(currentCommit.parent)
      }

      const thisBranchCommits = new Set<string>()
      currentCommit = commits.get(branch.head)
      while (currentCommit !== undefined) {
        thisBranchCommits.add(currentCommit.hash)
src/repos.ts:142
Before
141
142
143


144


145
146
147

      // At this point, we have all commits in the compareTo branch in one set, and
      // all commits from this branch in another set.
⁣
⁣
      const onlyInThisBranch = Array.from(thisBranchCommits).filter(thisBranchCommit => !compareToBranchCommits.has(thisBranchCommit)).length
⁣
⁣
      const onlyInCompareToBranch = Array.from(compareToBranchCommits).filter(compareToBranchCommit => !thisBranchCommits.has(compareToBranchCommit)).length

      const compareToInfo = {
        ahead: onlyInThisBranch,
After
141
142
143
144
145
146
147
148
149
150
151

      // At this point, we have all commits in the compareTo branch in one set, and
      // all commits from this branch in another set.
      const onlyInThisBranch = Array.from(thisBranchCommits).filter((thisBranchCommit) => {
        return !commits.get(thisBranchCommit).isMerge && !compareToBranchCommits.has(thisBranchCommit)
      }).length
      const onlyInCompareToBranch = Array.from(compareToBranchCommits).filter((compareToBranchCommit) => {
        return !commits.get(compareToBranchCommit).isMerge && !thisBranchCommits.has(compareToBranchCommit)
      }).length

      const compareToInfo = {
        ahead: onlyInThisBranch,
src/vcses/git/operations.ts:66
Before
65
66
67
68
69
70
        return
      }

      let author: string, date: string
      [1, 2, 3].forEach((lineNumber) => {
        if (currentPatch[lineNumber].startsWith("Author")) {
          author = currentPatch[lineNumber].replace("Author: ", "").trim()
After
65
66
67
68
69
70
        return
      }

      let author: string, date: string, isMerge: boolean
      [1, 2, 3].forEach((lineNumber) => {
        if (currentPatch[lineNumber].startsWith("Author")) {
          author = currentPatch[lineNumber].replace("Author: ", "").trim()
src/vcses/git/operations.ts:74
Before
73
74
75



76
77
78
79









80
81
82
83
        else if (currentPatch[lineNumber].startsWith("Date")) {
          date = currentPatch[lineNumber].replace("Date: ", "").trim()
        }
⁣
⁣
⁣
      })
      const diffStart = currentPatch.findIndex((line) => {
        return line.startsWith("diff ")
      })
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
      // Git log is indent four spaces by default -- remove those.
      const commitMessage = currentPatch.slice(4, diffStart).map(str => str.replace("    ", "")).filter((line) => {
        // git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
        return !line.startsWith('Ignore-this: ')
      }).join("\n").trim()
After
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        else if (currentPatch[lineNumber].startsWith("Date")) {
          date = currentPatch[lineNumber].replace("Date: ", "").trim()
        }
        else if (currentPatch[lineNumber].startsWith("Merge")) {
          isMerge = true
        }
      })
      let diffStart = currentPatch.findIndex((line) => {
        return line.startsWith("diff ")
      })
      // If no line starts with "diff", this
      // is probably a mege commit. Use the last
      // line of the patch + 1, in that case, to just get the full
      // text of the commit
      let messageStart = 4
      if (diffStart === -1) {
        messageStart = 5
        diffStart = currentPatch.length
      }
      // Git log is indent four spaces by default -- remove those.
      const commitMessage = currentPatch.slice(messageStart, diffStart).map(str => str.replace("    ", "")).filter((line) => {
        // git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
        return !line.startsWith('Ignore-this: ')
      }).join("\n").trim()
src/vcses/git/operations.ts:88
Before
87
88
89

90
91
      commits.set(hash, {
        hash,
        message: commitMessage,
⁣
        author,
        date: new Date(date),
        diffs,
After
87
88
89
90
91
92
      commits.set(hash, {
        hash,
        message: commitMessage,
        isMerge: isMerge || false,
        author,
        date: new Date(date),
        diffs,