From c147404d1ca39a9b1cbbe88f57904769c8314665 Mon Sep 17 00:00:00 2001 From: ashishj Date: Tue, 7 Jun 2022 20:32:43 +0200 Subject: [PATCH 1/4] #3080 Added support for cherry pick commits --- .../integration/rendering/gitGraph.spec.js | 24 +++++- src/diagrams/git/gitGraphAst.js | 81 +++++++++++++++++++ src/diagrams/git/gitGraphRenderer.js | 48 ++++++++++- src/diagrams/git/parser/gitGraph.jison | 6 ++ 4 files changed, 155 insertions(+), 4 deletions(-) diff --git a/cypress/integration/rendering/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js index 7242c05dd1..c1846ede5c 100644 --- a/cypress/integration/rendering/gitGraph.spec.js +++ b/cypress/integration/rendering/gitGraph.spec.js @@ -126,12 +126,11 @@ describe('Git Graph diagram', () => { branch branch8 branch branch9 checkout branch1 - commit + commit id: "1" `, {} ); }); - it('9: should render a simple gitgraph with rotated labels', () => { imgSnapshotTest( `%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'gitGraph': { @@ -160,4 +159,25 @@ describe('Git Graph diagram', () => { {} ); }); + it('11: should render a simple gitgraph with cherry pick commit', () => { + imgSnapshotTest( + ` + gitGraph + commit id: "ZERO" + branch develop + commit id:"A" + checkout main + commit id:"ONE" + checkout develop + commit id:"B" + checkout main + commit id:"TWO" + cherry-pick id:"A" + commit id:"THREE" + checkout develop + commit id:"C" + `, + {} + ); + }); }); diff --git a/src/diagrams/git/gitGraphAst.js b/src/diagrams/git/gitGraphAst.js index bf0d1c2ab9..2e7dc601a5 100644 --- a/src/diagrams/git/gitGraphAst.js +++ b/src/diagrams/git/gitGraphAst.js @@ -235,6 +235,85 @@ export const merge = function (otherBranch, tag) { log.debug('in mergeBranch'); }; +export const cherryPick = function (sourceId, targetId) { + sourceId = common.sanitizeText(sourceId, configApi.getConfig()); + targetId = common.sanitizeText(targetId, configApi.getConfig()); + + if (!sourceId || typeof commits[sourceId] === 'undefined') { + let error = new Error( + 'Incorrect usage of "cherryPick". Source commit id should exist and provided' + ); + error.hash = { + text: 'cherryPick ' + sourceId + ' ' + targetId, + token: 'cherryPick ' + sourceId + ' ' + targetId, + line: '1', + loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, + expected: ['cherry-pick abc'], + }; + throw error; + } + + let sourceCommit = commits[sourceId]; + let sourceCommitBranch = sourceCommit.branch; + if (sourceCommit.type === commitType.MERGE) { + let error = new Error( + 'Incorrect usage of "cherryPick". Source commit should not be a merge commit' + ); + error.hash = { + text: 'cherryPick ' + sourceId + ' ' + targetId, + token: 'cherryPick ' + sourceId + ' ' + targetId, + line: '1', + loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, + expected: ['cherry-pick abc'], + }; + throw error; + } + if (!targetId || typeof commits[targetId] === 'undefined') { + // cherry-pick source commit to current branch + + if (sourceCommitBranch === curBranch) { + let error = new Error( + 'Incorrect usage of "cherryPick". Source commit is already on current branch' + ); + error.hash = { + text: 'cherryPick ' + sourceId + ' ' + targetId, + token: 'cherryPick ' + sourceId + ' ' + targetId, + line: '1', + loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, + expected: ['cherry-pick abc'], + }; + throw error; + } + const currentCommit = commits[branches[curBranch]]; + if (typeof currentCommit === 'undefined' || !currentCommit) { + let error = new Error( + 'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits' + ); + error.hash = { + text: 'cherryPick ' + sourceId + ' ' + targetId, + token: 'cherryPick ' + sourceId + ' ' + targetId, + line: '1', + loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, + expected: ['cherry-pick abc'], + }; + throw error; + } + const commit = { + id: seq + '-' + getId(), + message: 'cherry-picked ' + sourceCommit + ' into ' + curBranch, + seq: seq++, + parents: [head == null ? null : head.id, sourceCommit.id], + branch: curBranch, + type: commitType.CHERRY_PICK, + tag: 'cherry-pick:' + sourceCommit.id, + }; + head = commit; + commits[commit.id] = commit; + branches[curBranch] = commit.id; + log.debug(branches); + log.debug('in cheeryPick'); + } +}; export const checkout = function (branch) { branch = common.sanitizeText(branch, configApi.getConfig()); if (typeof branches[branch] === 'undefined') { @@ -390,6 +469,7 @@ export const commitType = { REVERSE: 1, HIGHLIGHT: 2, MERGE: 3, + CHERRY_PICK: 4, }; export default { @@ -401,6 +481,7 @@ export default { commit, branch, merge, + cherryPick, checkout, //reset, prettyPrint, diff --git a/src/diagrams/git/gitGraphRenderer.js b/src/diagrams/git/gitGraphRenderer.js index 7a7c8f25f0..2735ea83d4 100644 --- a/src/diagrams/git/gitGraphRenderer.js +++ b/src/diagrams/git/gitGraphRenderer.js @@ -14,6 +14,7 @@ const commitType = { REVERSE: 1, HIGHLIGHT: 2, MERGE: 3, + CHERRY_PICK: 4, }; let branchPos = {}; @@ -103,6 +104,9 @@ const drawCommits = (svg, commits, modifyGraph) => { case commitType.MERGE: typeClass = 'commit-merge'; break; + case commitType.CHERRY_PICK: + typeClass = 'commit-cherry-pick'; + break; default: typeClass = 'commit-normal'; } @@ -139,6 +143,43 @@ const drawCommits = (svg, commits, modifyGraph) => { typeClass + '-inner' ); + } else if (commit.type === commitType.CHERRY_PICK) { + gBullets + .append('circle') + .attr('cx', x) + .attr('cy', y) + .attr('r', 10) + .attr('class', 'commit ' + commit.id + ' ' + typeClass); + gBullets + .append('circle') + .attr('cx', x - 3) + .attr('cy', y + 2) + .attr('r', 2.75) + .attr('fill', '#fff') + .attr('class', 'commit ' + commit.id + ' ' + typeClass); + gBullets + .append('circle') + .attr('cx', x + 3) + .attr('cy', y + 2) + .attr('r', 2.75) + .attr('fill', '#fff') + .attr('class', 'commit ' + commit.id + ' ' + typeClass); + gBullets + .append('line') + .attr('x1', x + 3) + .attr('y1', y + 1) + .attr('x2', x) + .attr('y2', y - 5) + .attr('stroke', '#fff') + .attr('class', 'commit ' + commit.id + ' ' + typeClass); + gBullets + .append('line') + .attr('x1', x - 3) + .attr('y1', y + 1) + .attr('x2', x) + .attr('y2', y - 5) + .attr('stroke', '#fff') + .attr('class', 'commit ' + commit.id + ' ' + typeClass); } else { const circle = gBullets.append('circle'); circle.attr('cx', x); @@ -175,7 +216,11 @@ const drawCommits = (svg, commits, modifyGraph) => { const px = 4; const py = 2; // Draw the commit label - if (commit.type !== commitType.MERGE && gitGraphConfig.showCommitLabel) { + if ( + commit.type !== commitType.CHERRY_PICK && + commit.type !== commitType.MERGE && + gitGraphConfig.showCommitLabel + ) { const wrapper = gLabels.append('g'); const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg'); @@ -197,7 +242,6 @@ const drawCommits = (svg, commits, modifyGraph) => { if (gitGraphConfig.rotateCommitLabel) { let r_x = -7.5 - ((bbox.width + 10) / 25) * 9.5; let r_y = 10 + (bbox.width / 25) * 8.5; - //wrapper.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + bbox.width / 2 + ') '); wrapper.attr( 'transform', 'translate(' + r_x + ', ' + r_y + ') rotate(' + -45 + ', ' + pos + ', ' + y + ')' diff --git a/src/diagrams/git/parser/gitGraph.jison b/src/diagrams/git/parser/gitGraph.jison index 8d1ad61f76..04b2082493 100644 --- a/src/diagrams/git/parser/gitGraph.jison +++ b/src/diagrams/git/parser/gitGraph.jison @@ -48,6 +48,7 @@ accDescr\s*"{"\s* { this.begin("ac "branch" return 'BRANCH'; "order:" return 'ORDER'; "merge" return 'MERGE'; +"cherry-pick" return 'CHERRY_PICK'; // "reset" return 'RESET'; "checkout" return 'CHECKOUT'; "LR" return 'DIR'; @@ -102,6 +103,7 @@ line statement : commitStatement | mergeStatement + | cherryPickStatement | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} @@ -114,6 +116,10 @@ branchStatement | BRANCH ID ORDER NUM {yy.branch($2, $4)} ; +cherryPickStatement + : CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3)} + ; + mergeStatement : MERGE ID {yy.merge($2)} | MERGE ID COMMIT_TAG STR {yy.merge($2, $4)} From 44b2fae0dbfe2cffa84d3af0779dcfcb08f0eb1d Mon Sep 17 00:00:00 2001 From: ashishj Date: Tue, 7 Jun 2022 20:48:40 +0200 Subject: [PATCH 2/4] #3080 Added docs for cherry pick functionality --- docs/gitgraph.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/gitgraph.md b/docs/gitgraph.md index 9f43cc873b..582da417f2 100644 --- a/docs/gitgraph.md +++ b/docs/gitgraph.md @@ -182,6 +182,33 @@ After this we made use of the `checkout` keyword to set the current branch as `m After this we merge the `develop` branch onto the current branch `main`, resulting in a merge commit. Since the current branch at this point is still `main`, the last two commits are registered against that. +### Cherry Pick commit from another branch +Similar to how 'git' allows you to cherry pick a commit from **another branch** onto the **current** branch, Mermaid also suports this functionality. You can also cherry pick a commit from another branch using the `cherry-pick` keyword. + +Here, a new commt representing the cherry pick is created on the current branch, and is visually highlighted in the diagram with a **cherry** and a tag depicting the commit id from which it is cherry picked from. + +Few Important rules to note here are: +1. You need to provide the `id` for an existing commit to be cherry picked. If given commit id does not exist it will result in an error. For this make use of the `commit id:$value` format of declaring commits. See the examples from above. +2. The given commit must not exist on the current branch. Cherry picked commit must always be a different branch than the current branch. +3. Current branch must have atleast one commit, before you can cherry pick a commit, otherwise it will case an error is throw. + +Let see an example: +```mermaid-example + gitGraph + commit id: "ZERO" + branch develop + commit id:"A" + checkout main + commit id:"ONE" + checkout develop + commit id:"B" + checkout main + commit id:"TWO" + cherry-pick id:"A" + commit id:"THREE" + checkout develop + commit id:"C" +``` ## Gitgraph specific configuration options In Mermaid, you have the option to configure the gitgraph diagram. You can configure the following options: - `showBranches` : Boolean, default is `true`. If set to `false`, the branches are not shown in the diagram. From e3df38e078d09e0da700f26216298422c0b1292b Mon Sep 17 00:00:00 2001 From: ashishj Date: Tue, 7 Jun 2022 20:52:52 +0200 Subject: [PATCH 3/4] #3080 Added more rendering test for cherry pick functionality --- .../integration/rendering/gitGraph.spec.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cypress/integration/rendering/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js index c1846ede5c..4dda2c16ed 100644 --- a/cypress/integration/rendering/gitGraph.spec.js +++ b/cypress/integration/rendering/gitGraph.spec.js @@ -180,4 +180,31 @@ describe('Git Graph diagram', () => { {} ); }); + + it('11: should render a simple gitgraph with two cherry pick commit', () => { + imgSnapshotTest( + ` + gitGraph + commit id: "ZERO" + branch develop + commit id:"A" + checkout main + commit id:"ONE" + checkout develop + commit id:"B" + branch featureA + commit id:"FIX" + commit id: "FIX-2" + checkout main + commit id:"TWO" + cherry-pick id:"A" + commit id:"THREE" + cherry-pick id:"FIX" + checkout develop + commit id:"C" + merge featureA + `, + {} + ); + }); }); From 84ae430806a40aafcf07f35a0f40932b9f0f225f Mon Sep 17 00:00:00 2001 From: ashishj Date: Tue, 7 Jun 2022 21:00:26 +0200 Subject: [PATCH 4/4] #3080 Updated docs for cherry pick functionality --- docs/gitgraph.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/gitgraph.md b/docs/gitgraph.md index 582da417f2..fe8f9b05a0 100644 --- a/docs/gitgraph.md +++ b/docs/gitgraph.md @@ -185,6 +185,10 @@ Since the current branch at this point is still `main`, the last two commits are ### Cherry Pick commit from another branch Similar to how 'git' allows you to cherry pick a commit from **another branch** onto the **current** branch, Mermaid also suports this functionality. You can also cherry pick a commit from another branch using the `cherry-pick` keyword. +To use the `cherry-pick` keyword, you must specify the id using the `id` attribute, followed by `:` and your desired commit id within `""` quote. For example: + + `cherry-pick id: "your_custom_id"` + Here, a new commt representing the cherry pick is created on the current branch, and is visually highlighted in the diagram with a **cherry** and a tag depicting the commit id from which it is cherry picked from. Few Important rules to note here are: