diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js index 15e8a3e38..2efb263a9 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphAst.js +++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js @@ -256,11 +256,12 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag log.debug('in mergeBranch'); }; -export const cherryPick = function (sourceId, targetId, tag, parentCommit) { +export const cherryPick = function (sourceId, targetId, tag, parentCommitId) { log.debug('Entering cherryPick:', sourceId, targetId, tag); sourceId = common.sanitizeText(sourceId, configApi.getConfig()); targetId = common.sanitizeText(targetId, configApi.getConfig()); tag = common.sanitizeText(tag, configApi.getConfig()); + parentCommitId = common.sanitizeText(parentCommitId, configApi.getConfig()); if (!sourceId || commits[sourceId] === undefined) { let error = new Error( @@ -278,9 +279,9 @@ export const cherryPick = function (sourceId, targetId, tag, parentCommit) { let sourceCommit = commits[sourceId]; let sourceCommitBranch = sourceCommit.branch; if (sourceCommit.type === commitType.MERGE) { - if (!parentCommit) { + if (!parentCommitId) { let error = new Error( - 'Incorrect usage of cherryPick: If the source commit is a merge commit, an immediate parent commit must be specified.' + 'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.' ); error.hash = { text: 'cherryPick ' + sourceId + ' ' + targetId, @@ -291,7 +292,7 @@ export const cherryPick = function (sourceId, targetId, tag, parentCommit) { }; throw error; } - if (!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommit))) { + if (!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))) { let error = new Error( 'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.' ); @@ -342,11 +343,11 @@ export const cherryPick = function (sourceId, targetId, tag, parentCommit) { parents: [head == null ? null : head.id, sourceCommit.id], branch: curBranch, type: commitType.CHERRY_PICK, - tag: tag - ? `${tag}${sourceCommit.type === commitType.MERGE ? ` | parent: ${parentCommit}` : ''}` - : `cherry-pick: ${sourceCommit.id}${ - sourceCommit.type === commitType.MERGE ? ` | parent: ${parentCommit}` : '' - }`, + 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 df20a5eb5..f9abec4b2 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js +++ b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js @@ -673,6 +673,145 @@ describe('when parsing a gitGraph', function () { expect(commits[cherryPickCommitID].branch).toBe('main'); }); + it('should support cherry-picking of merge commits', function () { + const str = `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + cherry-pick id: "M" parent:"B" + `; + + parser.parse(str); + const commits = parser.yy.getCommits(); + const cherryPickCommitID = Object.keys(commits)[4]; + expect(commits[cherryPickCommitID].tag).toBe('cherry-pick: M | parent: B'); + expect(commits[cherryPickCommitID].branch).toBe('release'); + }); + + it('should support cherry-picking of merge commits with tag', function () { + const str = `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + cherry-pick id: "M" parent:"ZERO" tag: "v1.0" + `; + + parser.parse(str); + const commits = parser.yy.getCommits(); + const cherryPickCommitID = Object.keys(commits)[4]; + expect(commits[cherryPickCommitID].tag).toBe('v1.0'); + expect(commits[cherryPickCommitID].branch).toBe('release'); + }); + + it('should support cherry-picking of merge commits with additional commit', function () { + const str = `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + commit id: "C" + cherry-pick id: "M" tag: "v2.1:ZERO" parent:"ZERO" + commit id: "D" + `; + + parser.parse(str); + const commits = parser.yy.getCommits(); + const cherryPickCommitID = Object.keys(commits)[5]; + expect(commits[cherryPickCommitID].tag).toBe('v2.1:ZERO'); + expect(commits[cherryPickCommitID].branch).toBe('release'); + }); + + it('should support cherry-picking of merge commits with empty tag', function () { + const str = `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + commit id: "C" + cherry-pick id:"M" parent: "ZERO" tag:"" + commit id: "D" + cherry-pick id:"M" tag:"" parent: "B" + `; + + parser.parse(str); + 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].branch).toBe('release'); + }); + + it('should fail cherry-picking of merge commits if the parent of merge commits is not specified', function () { + expect(() => + parser + .parse( + `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + commit id: "C" + cherry-pick id:"M" + ` + ) + .toThrow( + 'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.' + ) + ); + }); + + it('should fail cherry-picking of merge commits when the parent provided is not an immediate parent of cherry picked commit', function () { + expect(() => + parser + .parse( + `gitGraph + commit id: "ZERO" + branch feature + branch release + checkout feature + commit id: "A" + commit id: "B" + checkout main + merge feature id: "M" + checkout release + commit id: "C" + cherry-pick id:"M" parent: "A" + ` + ) + .toThrow( + 'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.' + ) + ); + }); + it('should throw error when try to branch existing branch: main', function () { const str = `gitGraph commit