diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js index 0997f65b1..ca9bbb010 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphAst.js +++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js @@ -113,7 +113,7 @@ export const commit = function (msg, id, type, tag) { message: msg, seq: seq++, type: type ? type : commitType.NORMAL, - tag: tag ? tag : '', + tags: tag ? [tag] : [], parents: head == null ? [] : [head.id], branch: curBranch, }; @@ -147,7 +147,7 @@ export const branch = function (name, order) { } }; -export const merge = function (otherBranch, custom_id, override_type, custom_tag) { +export const merge = function (otherBranch, custom_id, override_type, custom_tags) { otherBranch = common.sanitizeText(otherBranch, getConfig()); custom_id = common.sanitizeText(custom_id, getConfig()); @@ -216,12 +216,19 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag ' already exists, use different custom Id' ); error.hash = { - text: 'merge ' + otherBranch + custom_id + override_type + custom_tag, - token: 'merge ' + otherBranch + custom_id + override_type + custom_tag, + text: 'merge ' + otherBranch + custom_id + override_type + custom_tags.join(','), + token: 'merge ' + otherBranch + custom_id + override_type + custom_tags.join(','), line: '1', loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, expected: [ - 'merge ' + otherBranch + ' ' + custom_id + '_UNIQUE ' + override_type + ' ' + custom_tag, + 'merge ' + + otherBranch + + ' ' + + custom_id + + '_UNIQUE ' + + override_type + + ' ' + + custom_tags.join(','), ], }; @@ -245,7 +252,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag type: commitType.MERGE, customType: override_type, customId: custom_id ? true : false, - tag: custom_tag ? custom_tag : '', + tags: custom_tags ? custom_tags : [], }; head = commit; commits[commit.id] = commit; @@ -329,11 +336,11 @@ export const cherryPick = function (sourceId, targetId, tag, parentCommitId) { parents: [head == null ? null : head.id, sourceCommit.id], branch: curBranch, type: commitType.CHERRY_PICK, - tag: - tag ?? + tags: tag ? [tag] : [ `cherry-pick:${sourceCommit.id}${ sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : '' }`, + ], }; head = commit; commits[commit.id] = commit; diff --git a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js index ac85712b2..53e69c620 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js +++ b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js @@ -20,7 +20,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe(''); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(0); }); @@ -37,7 +37,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe(''); expect(commits[key].id).toBe('1111'); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(0); }); @@ -73,7 +73,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe(''); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(2); }); @@ -91,7 +91,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe(''); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(1); }); @@ -109,7 +109,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe(''); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(0); }); @@ -127,7 +127,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe('test commit'); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(0); }); @@ -145,7 +145,7 @@ describe('when parsing a gitGraph', function () { const key = Object.keys(commits)[0]; expect(commits[key].message).toBe('test commit'); expect(commits[key].id).not.toBeNull(); - expect(commits[key].tag).toBe(''); + expect(commits[key].tag).toBe([]); expect(commits[key].type).toBe(0); }); @@ -741,7 +741,7 @@ describe('when parsing a gitGraph', function () { parser.parse(str); const commits = parser.yy.getCommits(); const cherryPickCommitID = Object.keys(commits)[2]; - expect(commits[cherryPickCommitID].tag).toBe(''); + expect(commits[cherryPickCommitID].tag).toBe([]); expect(commits[cherryPickCommitID].branch).toBe('main'); }); @@ -831,8 +831,8 @@ describe('when parsing a gitGraph', function () { const commits = parser.yy.getCommits(); const cherryPickCommitID = Object.keys(commits)[5]; const cherryPickCommitID2 = Object.keys(commits)[7]; - expect(commits[cherryPickCommitID].tag).toBe(''); - expect(commits[cherryPickCommitID2].tag).toBe(''); + expect(commits[cherryPickCommitID].tag).toBe([]); + expect(commits[cherryPickCommitID2].tag).toBe([]); expect(commits[cherryPickCommitID].branch).toBe('release'); }); diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 7ccc60cd0..9f8e27a0b 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -408,59 +408,83 @@ const drawCommits = (svg, commits, modifyGraph) => { } } } - if (commit.tag) { - const rect = gLabels.insert('polygon'); - const hole = gLabels.append('circle'); - const tag = gLabels - .append('text') - // Note that we are delaying setting the x position until we know the width of the text - .attr('y', y - 16) - .attr('class', 'tag-label') - .text(commit.tag); - let tagBbox = tag.node().getBBox(); - tag.attr('x', posWithOffset - tagBbox.width / 2); + if (commit.tags.length > 0) { + let yOffset = 0; + let maxTagBboxWidth = 0; + let maxTagBboxHeight = 0; + const tagElements = []; - const h2 = tagBbox.height / 2; - const ly = y - 19.2; - rect.attr('class', 'tag-label-bkg').attr( - 'points', - ` - ${pos - tagBbox.width / 2 - px / 2},${ly + py} - ${pos - tagBbox.width / 2 - px / 2},${ly - py} - ${posWithOffset - tagBbox.width / 2 - px},${ly - h2 - py} - ${posWithOffset + tagBbox.width / 2 + px},${ly - h2 - py} - ${posWithOffset + tagBbox.width / 2 + px},${ly + h2 + py} - ${posWithOffset - tagBbox.width / 2 - px},${ly + h2 + py}` - ); + for (const tagValue of commit.tags.reverse()) { + const rect = gLabels.insert('polygon'); + const hole = gLabels.append('circle'); + const tag = gLabels + .append('text') + // Note that we are delaying setting the x position until we know the width of the text + .attr('y', y - 16 - yOffset) + .attr('class', 'tag-label') + .text(tagValue); + let tagBbox = tag.node().getBBox(); + maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width); + maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height); - hole - .attr('cx', pos - tagBbox.width / 2 + px / 2) - .attr('cy', ly) - .attr('r', 1.5) - .attr('class', 'tag-hole'); + // We don't use the max over here to center the text within the tags + tag.attr('x', posWithOffset - tagBbox.width / 2); + + tagElements.push({ + tag, + hole, + rect, + yOffset, + }); + + yOffset += 20; + } + + for (const { tag, hole, rect, yOffset } of tagElements) { + const h2 = maxTagBboxHeight / 2; + const ly = y - 19.2 - yOffset; + rect.attr('class', 'tag-label-bkg').attr( + 'points', + ` + ${pos - maxTagBboxWidth / 2 - px / 2},${ly + py} + ${pos - maxTagBboxWidth / 2 - px / 2},${ly - py} + ${posWithOffset - maxTagBboxWidth / 2 - px},${ly - h2 - py} + ${posWithOffset + maxTagBboxWidth / 2 + px},${ly - h2 - py} + ${posWithOffset + maxTagBboxWidth / 2 + px},${ly + h2 + py} + ${posWithOffset - maxTagBboxWidth / 2 - px},${ly + h2 + py}` + ); - if (dir === 'TB' || dir === 'BT') { - rect - .attr('class', 'tag-label-bkg') - .attr( - 'points', - ` - ${x},${pos + py} - ${x},${pos - py} - ${x + layoutOffset},${pos - h2 - py} - ${x + layoutOffset + tagBbox.width + px},${pos - h2 - py} - ${x + layoutOffset + tagBbox.width + px},${pos + h2 + py} - ${x + layoutOffset},${pos + h2 + py}` - ) - .attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')'); hole - .attr('cx', x + px / 2) - .attr('cy', pos) - .attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')'); - tag - .attr('x', x + 5) - .attr('y', pos + 3) - .attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')'); + .attr('cy', ly) + .attr('cx', pos - maxTagBboxWidth / 2 + px / 2) + .attr('r', 1.5) + .attr('class', 'tag-hole'); + + if (dir === 'TB' || dir === 'BT') { + const yOrigin = pos + yOffset; + + rect + .attr('class', 'tag-label-bkg') + .attr( + 'points', + ` + ${x},${yOrigin + py} + ${x},${yOrigin - py} + ${x + layoutOffset},${yOrigin - h2 - py} + ${x + layoutOffset + maxTagBboxWidth + px},${yOrigin - h2 - py} + ${x + layoutOffset + maxTagBboxWidth + px},${yOrigin + h2 + py} + ${x + layoutOffset},${yOrigin + h2 + py}` + ) + .attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')'); + hole + .attr('cx', x + px / 2) + .attr('cy', yOrigin) + .attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')'); + tag + .attr('x', x + 5) + .attr('y', yOrigin + 3) + .attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')'); + } } } } diff --git a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison index b4670ca0b..2e5293999 100644 --- a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison +++ b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison @@ -128,19 +128,19 @@ mergeStatement : MERGE ref {yy.merge($2,'','','')} | MERGE ref COMMIT_ID STR {yy.merge($2, $4,'','')} | MERGE ref COMMIT_TYPE commitType {yy.merge($2,'', $4,'')} - | MERGE ref COMMIT_TAG STR {yy.merge($2, '','',$4)} - | MERGE ref COMMIT_TAG STR COMMIT_ID STR {yy.merge($2, $6,'', $4)} - | MERGE ref COMMIT_TAG STR COMMIT_TYPE commitType {yy.merge($2, '',$6, $4)} - | MERGE ref COMMIT_TYPE commitType COMMIT_TAG STR {yy.merge($2, '',$4, $6)} + | MERGE ref commitTags {yy.merge($2, '','',$3)} + | MERGE ref commitTags COMMIT_ID STR {yy.merge($2, $5,'', $3)} + | MERGE ref commitTags COMMIT_TYPE commitType {yy.merge($2, '',$5, $3)} + | MERGE ref COMMIT_TYPE commitType commitTags {yy.merge($2, '',$4, $5)} | MERGE ref COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $4, $6, '')} - | MERGE ref COMMIT_ID STR COMMIT_TAG STR {yy.merge($2, $4, '', $6)} + | MERGE ref COMMIT_ID STR commitTags {yy.merge($2, $4, '', $5)} | MERGE ref COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $6,$4, '')} - | MERGE ref COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.merge($2, $4, $6, $8)} - | MERGE ref COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_ID STR {yy.merge($2, $8, $4, $6)} - | MERGE ref COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.merge($2, $4, $8, $6)} - | MERGE ref COMMIT_TYPE commitType COMMIT_ID STR COMMIT_TAG STR {yy.merge($2, $6, $4, $8)} - | MERGE ref COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $8, $6, $4)} - | MERGE ref COMMIT_TAG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $6, $8, $4)} + | MERGE ref COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.merge($2, $4, $6, $7)} + | MERGE ref COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.merge($2, $7, $4, $5)} + | MERGE ref COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.merge($2, $4, $7, $5)} + | MERGE ref COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.merge($2, $6, $4, $7)} + | MERGE ref commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $7, $5, $3)} + | MERGE ref commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $5, $7, $3)} ; commitStatement @@ -238,6 +238,10 @@ commitType | REVERSE { $$=yy.commitType.REVERSE;} | HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;} ; +commitTags + : COMMIT_TAG STR {$$=[$2]} + | commitTags COMMIT_TAG STR {$commitTags.push($3); $$=$commitTags;} + ; ref : ID