mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-19 07:19:41 +02:00
Added gitGraphAst as typescript added gitGraphTypes and gitGraphParser
This commit is contained in:
@@ -11,16 +11,20 @@ import {
|
|||||||
setDiagramTitle,
|
setDiagramTitle,
|
||||||
getDiagramTitle,
|
getDiagramTitle,
|
||||||
} from '../common/commonDb.js';
|
} from '../common/commonDb.js';
|
||||||
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
|
import type { DiagramOrientation, Commit } from './gitGraphTypes.js';
|
||||||
|
|
||||||
let { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
const mainBranchName = defaultConfig.gitGraph.mainBranchName;
|
||||||
let commits = new Map();
|
const mainBranchOrder = defaultConfig.gitGraph.mainBranchOrder;
|
||||||
let head = null;
|
|
||||||
let branchesConfig = new Map();
|
let commits = new Map<string, Commit>();
|
||||||
|
let head: Commit | null = null;
|
||||||
|
let branchesConfig = new Map<string, { name: string; order: number }>();
|
||||||
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
||||||
let branches = new Map();
|
let branches = new Map<string, string | null>();
|
||||||
branches.set(mainBranchName, head);
|
branches.set(mainBranchName, null);
|
||||||
let curBranch = mainBranchName;
|
let curBranch = mainBranchName;
|
||||||
let direction = 'LR';
|
let direction: DiagramOrientation = 'LR';
|
||||||
let seq = 0;
|
let seq = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,8 +61,8 @@ function getId() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param currentCommit
|
* @param currentCommit - current commit
|
||||||
* @param otherCommit
|
* @param otherCommit - other commit
|
||||||
*/
|
*/
|
||||||
// function isReachableFrom(currentCommit, otherCommit) {
|
// function isReachableFrom(currentCommit, otherCommit) {
|
||||||
// const currentSeq = currentCommit.seq;
|
// const currentSeq = currentCommit.seq;
|
||||||
@@ -68,10 +72,10 @@ function getId() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list
|
* @param list - list of items
|
||||||
* @param fn
|
* @param fn - function to get the key
|
||||||
*/
|
*/
|
||||||
function uniqBy(list, fn) {
|
function uniqBy(list: any[], fn: (item: any) => any) {
|
||||||
const recordMap = Object.create(null);
|
const recordMap = Object.create(null);
|
||||||
return list.reduce((out, item) => {
|
return list.reduce((out, item) => {
|
||||||
const key = fn(item);
|
const key = fn(item);
|
||||||
@@ -83,17 +87,18 @@ function uniqBy(list, fn) {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setDirection = function (dir) {
|
export const setDirection = function (dir: DiagramOrientation) {
|
||||||
direction = dir;
|
direction = dir;
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = {};
|
let options = {};
|
||||||
export const setOptions = function (rawOptString) {
|
export const setOptions = function (rawOptString: string) {
|
||||||
log.debug('options str', rawOptString);
|
log.debug('options str', rawOptString);
|
||||||
rawOptString = rawOptString?.trim();
|
rawOptString = rawOptString?.trim();
|
||||||
rawOptString = rawOptString || '{}';
|
rawOptString = rawOptString || '{}';
|
||||||
try {
|
try {
|
||||||
options = JSON.parse(rawOptString);
|
options = JSON.parse(rawOptString);
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log.error('error while parsing gitGraph options', e.message);
|
log.error('error while parsing gitGraph options', e.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -102,60 +107,65 @@ export const getOptions = function () {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commit = function (msg, id, type, tags) {
|
export const commit = function (msg: string, id: string, type: number, tag: string) {
|
||||||
log.debug('Entering commit:', msg, id, type, tags);
|
log.info('commit', msg, id, type, tag);
|
||||||
const config = getConfig();
|
log.debug('Entering commit:', msg, id, type, tag);
|
||||||
id = common.sanitizeText(id, config);
|
id = common.sanitizeText(id, getConfig());
|
||||||
msg = common.sanitizeText(msg, config);
|
msg = common.sanitizeText(msg, getConfig());
|
||||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
tag = common.sanitizeText(tag, getConfig());
|
||||||
const commit = {
|
const newCommit: Commit = {
|
||||||
id: id ? id : seq + '-' + getId(),
|
id: id ? id : seq + '-' + getId(),
|
||||||
message: msg,
|
message: msg,
|
||||||
seq: seq++,
|
seq: seq++,
|
||||||
type: type ? type : commitType.NORMAL,
|
type: type,
|
||||||
tags: tags ?? [],
|
tag: tag ? tag : '',
|
||||||
parents: head == null ? [] : [head.id],
|
parents: head == null ? [] : [head.id],
|
||||||
branch: curBranch,
|
branch: curBranch,
|
||||||
};
|
};
|
||||||
head = commit;
|
head = newCommit;
|
||||||
commits.set(commit.id, commit);
|
log.info('main branch', mainBranchName);
|
||||||
branches.set(curBranch, commit.id);
|
commits.set(newCommit.id, newCommit);
|
||||||
log.debug('in pushCommit ' + commit.id);
|
branches.set(curBranch, newCommit.id);
|
||||||
|
log.debug('in pushCommit ' + newCommit.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const branch = function (name, order) {
|
export const branch = function (name: string, order: number) {
|
||||||
name = common.sanitizeText(name, getConfig());
|
name = common.sanitizeText(name, getConfig());
|
||||||
if (!branches.has(name)) {
|
if (!branches.has(name)) {
|
||||||
branches.set(name, head != null ? head.id : null);
|
branches.set(name, head != null ? head.id : null);
|
||||||
branchesConfig.set(name, { name, order: order ? parseInt(order, 10) : null });
|
branchesConfig.set(name, { name, order });
|
||||||
checkout(name);
|
checkout(name);
|
||||||
log.debug('in createBranch');
|
log.debug('in createBranch');
|
||||||
} else {
|
} else {
|
||||||
let error = new Error(
|
throw new Error(
|
||||||
'Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ' +
|
`Trying to create an existing branch: ${name}. Use 'checkout ${name}' instead.`
|
||||||
name +
|
|
||||||
'")'
|
|
||||||
);
|
);
|
||||||
error.hash = {
|
|
||||||
text: 'branch ' + name,
|
|
||||||
token: 'branch ' + name,
|
|
||||||
line: '1',
|
|
||||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
|
||||||
expected: ['"checkout ' + name + '"'],
|
|
||||||
};
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const merge = function (otherBranch, custom_id, override_type, custom_tags) {
|
export const merge = (
|
||||||
const config = getConfig();
|
otherBranch: string,
|
||||||
otherBranch = common.sanitizeText(otherBranch, config);
|
custom_id?: string,
|
||||||
custom_id = common.sanitizeText(custom_id, config);
|
override_type?: number,
|
||||||
|
custom_tag?: string
|
||||||
const currentCommit = commits.get(branches.get(curBranch));
|
): void => {
|
||||||
const otherCommit = commits.get(branches.get(otherBranch));
|
otherBranch = common.sanitizeText(otherBranch, getConfig());
|
||||||
|
if (custom_id) {
|
||||||
|
custom_id = common.sanitizeText(custom_id, getConfig());
|
||||||
|
}
|
||||||
|
const currentBranchCheck: string | null | undefined = branches.get(curBranch);
|
||||||
|
const otherBranchCheck: string | null | undefined = branches.get(otherBranch);
|
||||||
|
const currentCommit: Commit | undefined = currentBranchCheck
|
||||||
|
? commits.get(currentBranchCheck)
|
||||||
|
: undefined;
|
||||||
|
const otherCommit: Commit | undefined = otherBranchCheck
|
||||||
|
? commits.get(otherBranchCheck)
|
||||||
|
: undefined;
|
||||||
|
if (currentCommit && otherCommit && currentCommit.branch === otherBranch) {
|
||||||
|
throw new Error(`Cannot merge branch '${otherBranch}' into itself.`);
|
||||||
|
}
|
||||||
if (curBranch === otherBranch) {
|
if (curBranch === otherBranch) {
|
||||||
let error = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
|
const error: any = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
|
||||||
error.hash = {
|
error.hash = {
|
||||||
text: 'merge ' + otherBranch,
|
text: 'merge ' + otherBranch,
|
||||||
token: 'merge ' + otherBranch,
|
token: 'merge ' + otherBranch,
|
||||||
@@ -165,7 +175,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
} else if (currentCommit === undefined || !currentCommit) {
|
} else if (currentCommit === undefined || !currentCommit) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "merge". Current branch (' + curBranch + ')has no commits'
|
'Incorrect usage of "merge". Current branch (' + curBranch + ')has no commits'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -177,7 +187,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
} else if (!branches.has(otherBranch)) {
|
} else if (!branches.has(otherBranch)) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
|
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -189,7 +199,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
} else if (otherCommit === undefined || !otherCommit) {
|
} else if (otherCommit === undefined || !otherCommit) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
|
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -201,7 +211,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
} else if (currentCommit === otherCommit) {
|
} else if (currentCommit === otherCommit) {
|
||||||
let error = new Error('Incorrect usage of "merge". Both branches have same head');
|
const error: any = new Error('Incorrect usage of "merge". Both branches have same head');
|
||||||
error.hash = {
|
error.hash = {
|
||||||
text: 'merge ' + otherBranch,
|
text: 'merge ' + otherBranch,
|
||||||
token: 'merge ' + otherBranch,
|
token: 'merge ' + otherBranch,
|
||||||
@@ -211,23 +221,24 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
} else if (custom_id && commits.has(custom_id)) {
|
} else if (custom_id && commits.has(custom_id)) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "merge". Commit with id:' +
|
'Incorrect usage of "merge". Commit with id:' +
|
||||||
custom_id +
|
custom_id +
|
||||||
' already exists, use different custom Id'
|
' already exists, use different custom Id'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
text: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
text: 'merge ' + otherBranch + custom_id + override_type + custom_tag,
|
||||||
token: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
token: 'merge ' + otherBranch + custom_id + override_type + custom_tag,
|
||||||
line: '1',
|
line: '1',
|
||||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||||
expected: [
|
expected: [
|
||||||
`merge ${otherBranch} ${custom_id}_UNIQUE ${override_type} ${custom_tags?.join(',')}`,
|
'merge ' + otherBranch + ' ' + custom_id + '_UNIQUE ' + override_type + ' ' + custom_tag,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (isReachableFrom(currentCommit, otherCommit)) {
|
// if (isReachableFrom(currentCommit, otherCommit)) {
|
||||||
// log.debug('Already merged');
|
// log.debug('Already merged');
|
||||||
// return;
|
// return;
|
||||||
@@ -237,16 +248,19 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
// head = commits.get(branches.get(curBranch));
|
// head = commits.get(branches.get(curBranch));
|
||||||
// } else {
|
// } else {
|
||||||
// create merge commit
|
// create merge commit
|
||||||
const commit = {
|
|
||||||
|
const verifiedBranch: string = otherBranchCheck ? otherBranchCheck : ''; //figure out a cleaner way to do this
|
||||||
|
|
||||||
|
const commit: Commit = {
|
||||||
id: custom_id ? custom_id : seq + '-' + getId(),
|
id: custom_id ? custom_id : seq + '-' + getId(),
|
||||||
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
|
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
|
||||||
seq: seq++,
|
seq: seq++,
|
||||||
parents: [head == null ? null : head.id, branches.get(otherBranch)],
|
parents: [head == null ? null : head.id, verifiedBranch],
|
||||||
branch: curBranch,
|
branch: curBranch,
|
||||||
type: commitType.MERGE,
|
type: commitType.MERGE,
|
||||||
customType: override_type,
|
customType: override_type, //TODO - need to make customType optional
|
||||||
customId: custom_id ? true : false,
|
customId: custom_id, //TODO - need to make customId optional as well as tag
|
||||||
tags: custom_tags ? custom_tags : [],
|
tag: custom_tag ? custom_tag : '',
|
||||||
};
|
};
|
||||||
head = commit;
|
head = commit;
|
||||||
commits.set(commit.id, commit);
|
commits.set(commit.id, commit);
|
||||||
@@ -256,16 +270,20 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
log.debug('in mergeBranch');
|
log.debug('in mergeBranch');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
export const cherryPick = function (
|
||||||
log.debug('Entering cherryPick:', sourceId, targetId, tags);
|
sourceId: string,
|
||||||
const config = getConfig();
|
targetId: string,
|
||||||
sourceId = common.sanitizeText(sourceId, config);
|
tag: string,
|
||||||
targetId = common.sanitizeText(targetId, config);
|
parentCommitId: string
|
||||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
) {
|
||||||
parentCommitId = common.sanitizeText(parentCommitId, config);
|
log.debug('Entering cherryPick:', sourceId, targetId, tag);
|
||||||
|
sourceId = common.sanitizeText(sourceId, getConfig());
|
||||||
|
targetId = common.sanitizeText(targetId, getConfig());
|
||||||
|
tag = common.sanitizeText(tag, getConfig());
|
||||||
|
parentCommitId = common.sanitizeText(parentCommitId, getConfig());
|
||||||
|
|
||||||
if (!sourceId || !commits.has(sourceId)) {
|
if (!sourceId || !commits.has(sourceId)) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
|
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -277,19 +295,22 @@ export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
let sourceCommit = commits.get(sourceId);
|
|
||||||
let sourceCommitBranch = sourceCommit.branch;
|
const sourceCommit = commits.get(sourceId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
parentCommitId &&
|
!sourceCommit ||
|
||||||
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
|
!parentCommitId ||
|
||||||
|
!Array.isArray(sourceCommit.parents) ||
|
||||||
|
!sourceCommit.parents.includes(parentCommitId)
|
||||||
) {
|
) {
|
||||||
let error = new Error(
|
throw new Error(
|
||||||
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
||||||
);
|
);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
const sourceCommitBranch = sourceCommit.branch;
|
||||||
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
|
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
|
||||||
let error = new Error(
|
const error = new Error(
|
||||||
'Incorrect usage of cherry-pick: 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.'
|
||||||
);
|
);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -298,7 +319,7 @@ export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
|||||||
// cherry-pick source commit to current branch
|
// cherry-pick source commit to current branch
|
||||||
|
|
||||||
if (sourceCommitBranch === curBranch) {
|
if (sourceCommitBranch === curBranch) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "cherryPick". Source commit is already on current branch'
|
'Incorrect usage of "cherryPick". Source commit is already on current branch'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -310,9 +331,24 @@ export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
|||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const currentCommit = commits.get(branches.get(curBranch));
|
const currentCommitId = branches.get(curBranch);
|
||||||
|
if (currentCommitId === undefined || !currentCommitId) {
|
||||||
|
const error: any = 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 currentCommit = commits.get(currentCommitId);
|
||||||
if (currentCommit === undefined || !currentCommit) {
|
if (currentCommit === undefined || !currentCommit) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits'
|
'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -326,18 +362,16 @@ export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
|||||||
}
|
}
|
||||||
const commit = {
|
const commit = {
|
||||||
id: seq + '-' + getId(),
|
id: seq + '-' + getId(),
|
||||||
message: 'cherry-picked ' + sourceCommit + ' into ' + curBranch,
|
message: 'cherry-picked ' + sourceCommit?.message + ' into ' + curBranch,
|
||||||
seq: seq++,
|
seq: seq++,
|
||||||
parents: [head == null ? null : head.id, sourceCommit.id],
|
parents: [head == null ? null : head.id, sourceCommit.id],
|
||||||
branch: curBranch,
|
branch: curBranch,
|
||||||
type: commitType.CHERRY_PICK,
|
type: commitType.CHERRY_PICK,
|
||||||
tags: tags
|
tag:
|
||||||
? tags.filter(Boolean)
|
tag ??
|
||||||
: [
|
|
||||||
`cherry-pick:${sourceCommit.id}${
|
`cherry-pick:${sourceCommit.id}${
|
||||||
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
||||||
}`,
|
}`,
|
||||||
],
|
|
||||||
};
|
};
|
||||||
head = commit;
|
head = commit;
|
||||||
commits.set(commit.id, commit);
|
commits.set(commit.id, commit);
|
||||||
@@ -346,10 +380,10 @@ export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
|||||||
log.debug('in cherryPick');
|
log.debug('in cherryPick');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const checkout = function (branch) {
|
export const checkout = function (branch: string) {
|
||||||
branch = common.sanitizeText(branch, getConfig());
|
branch = common.sanitizeText(branch, getConfig());
|
||||||
if (!branches.has(branch)) {
|
if (!branches.has(branch)) {
|
||||||
let error = new Error(
|
const error: any = new Error(
|
||||||
'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")'
|
'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
@@ -360,10 +394,19 @@ export const checkout = function (branch) {
|
|||||||
expected: ['"branch ' + branch + '"'],
|
expected: ['"branch ' + branch + '"'],
|
||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
|
//branches[branch] = head != null ? head.id : null;
|
||||||
|
//log.debug('in createBranch');
|
||||||
} else {
|
} else {
|
||||||
curBranch = branch;
|
curBranch = branch;
|
||||||
const id = branches.get(curBranch);
|
const id = branches.get(curBranch);
|
||||||
head = commits.get(id);
|
|
||||||
|
if (id === null || id === undefined) {
|
||||||
|
throw new Error('Branch ' + branch + ' has no commits');
|
||||||
|
}
|
||||||
|
if (commits.get(id) === undefined) {
|
||||||
|
throw new Error('Branch ' + branch + ' has no commits');
|
||||||
|
}
|
||||||
|
head = commits.get(id) ?? null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -387,11 +430,11 @@ export const checkout = function (branch) {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param arr
|
* @param arr - array
|
||||||
* @param key
|
* @param key - key
|
||||||
* @param newVal
|
* @param newVal - new value
|
||||||
*/
|
*/
|
||||||
function upsert(arr, key, newVal) {
|
function upsert(arr: any[], key: any, newVal: any) {
|
||||||
const index = arr.indexOf(key);
|
const index = arr.indexOf(key);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
arr.push(newVal);
|
arr.push(newVal);
|
||||||
@@ -400,8 +443,8 @@ function upsert(arr, key, newVal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param commitArr */
|
/** @param commitArr - array */
|
||||||
function prettyPrintCommitHistory(commitArr) {
|
function prettyPrintCommitHistory(commitArr: Commit[]) {
|
||||||
const commit = commitArr.reduce((out, commit) => {
|
const commit = commitArr.reduce((out, commit) => {
|
||||||
if (out.seq > commit.seq) {
|
if (out.seq > commit.seq) {
|
||||||
return out;
|
return out;
|
||||||
@@ -417,21 +460,25 @@ function prettyPrintCommitHistory(commitArr) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const label = [line, commit.id, commit.seq];
|
const label = [line, commit.id, commit.seq];
|
||||||
for (let branch in branches) {
|
for (const branch in branches) {
|
||||||
if (branches.get(branch) === commit.id) {
|
if (branches.get(branch) === commit.id) {
|
||||||
label.push(branch);
|
label.push(branch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug(label.join(' '));
|
log.debug(label.join(' '));
|
||||||
if (commit.parents && commit.parents.length == 2) {
|
if (commit.parents && commit.parents.length == 2 && commit.parents[0] && commit.parents[1]) {
|
||||||
const newCommit = commits.get(commit.parents[0]);
|
const newCommit = commits.get(commit.parents[0]);
|
||||||
upsert(commitArr, commit, newCommit);
|
upsert(commitArr, commit, newCommit);
|
||||||
commitArr.push(commits.get(commit.parents[1]));
|
if (commit.parents[1]) {
|
||||||
|
commitArr.push(commits.get(commit.parents[1])!);
|
||||||
|
}
|
||||||
} else if (commit.parents.length == 0) {
|
} else if (commit.parents.length == 0) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const nextCommit = commits.get(commit.parents);
|
if (commit.parents[0]) {
|
||||||
upsert(commitArr, commit, nextCommit);
|
const newCommit = commits.get(commit.parents[0]);
|
||||||
|
upsert(commitArr, commit, newCommit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
commitArr = uniqBy(commitArr, (c) => c.id);
|
commitArr = uniqBy(commitArr, (c) => c.id);
|
||||||
prettyPrintCommitHistory(commitArr);
|
prettyPrintCommitHistory(commitArr);
|
||||||
@@ -446,12 +493,13 @@ export const prettyPrint = function () {
|
|||||||
export const clear = function () {
|
export const clear = function () {
|
||||||
commits = new Map();
|
commits = new Map();
|
||||||
head = null;
|
head = null;
|
||||||
const { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
const mainBranch = defaultConfig.gitGraph.mainBranchName;
|
||||||
|
const mainBranchOrder = defaultConfig.gitGraph.mainBranchOrder;
|
||||||
branches = new Map();
|
branches = new Map();
|
||||||
branches.set(mainBranchName, null);
|
branches.set(mainBranch, null);
|
||||||
branchesConfig = new Map();
|
branchesConfig = new Map();
|
||||||
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
branchesConfig.set(mainBranch, { name: mainBranch, order: mainBranchOrder });
|
||||||
curBranch = mainBranchName;
|
curBranch = mainBranch;
|
||||||
seq = 0;
|
seq = 0;
|
||||||
commonClear();
|
commonClear();
|
||||||
};
|
};
|
||||||
@@ -464,7 +512,7 @@ export const getBranchesAsObjArray = function () {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...branchConfig,
|
...branchConfig,
|
||||||
order: parseFloat(`0.${i}`, 10),
|
order: parseFloat(`0.${i}`),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
@@ -531,5 +579,4 @@ export default {
|
|||||||
setAccDescription,
|
setAccDescription,
|
||||||
setDiagramTitle,
|
setDiagramTitle,
|
||||||
getDiagramTitle,
|
getDiagramTitle,
|
||||||
commitType,
|
|
||||||
};
|
};
|
72
packages/mermaid/src/diagrams/git/gitGraphParser.ts
Normal file
72
packages/mermaid/src/diagrams/git/gitGraphParser.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type { GitGraph } from '@mermaid-js/parser';
|
||||||
|
import { parse } from '@mermaid-js/parser';
|
||||||
|
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { populateCommonDb } from '../common/populateCommonDb.js';
|
||||||
|
import db from './gitGraphAst.js';
|
||||||
|
import type {
|
||||||
|
Statement,
|
||||||
|
CommitAst,
|
||||||
|
Branch,
|
||||||
|
Merge,
|
||||||
|
Checkout,
|
||||||
|
CherryPicking,
|
||||||
|
} from './gitGraphTypes.js';
|
||||||
|
|
||||||
|
const populate = (ast: any) => {
|
||||||
|
populateCommonDb(ast, db);
|
||||||
|
for (const statement of ast.statements) {
|
||||||
|
parseStatement(statement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseStatement = (statement: Statement) => {
|
||||||
|
switch (statement.$type) {
|
||||||
|
case 'Commit':
|
||||||
|
parseCommit(statement);
|
||||||
|
break;
|
||||||
|
case 'Branch':
|
||||||
|
parseBranch(statement);
|
||||||
|
break;
|
||||||
|
case 'Merge':
|
||||||
|
parseMerge(statement);
|
||||||
|
break;
|
||||||
|
case 'Checkout':
|
||||||
|
parseCheckout(statement);
|
||||||
|
break;
|
||||||
|
case 'CherryPicking':
|
||||||
|
parseCherryPicking(statement);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown statement type: ${(statement as any).$type}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseCommit(commit: CommitAst) {
|
||||||
|
const message = commit.message ?? '';
|
||||||
|
db.commit(message, commit.id, commit.tags, commit.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBranch(branch: Branch) {
|
||||||
|
db.branch(branch.name, branch.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMerge(merge: Merge) {
|
||||||
|
db.merge(merge.branch, merge.id, merge.tags, merge.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCheckout(checkout: Checkout) {
|
||||||
|
db.checkout(checkout.branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCherryPicking(cherryPicking: CherryPicking) {
|
||||||
|
db.cherryPick(cherryPicking.id, cherryPicking.tags, cherryPicking.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parser: ParserDefinition = {
|
||||||
|
parse: async (input: string): Promise<void> => {
|
||||||
|
const ast: GitGraph = await parse('gitGraph', input);
|
||||||
|
log.debug(ast);
|
||||||
|
populate(ast);
|
||||||
|
},
|
||||||
|
};
|
55
packages/mermaid/src/diagrams/git/gitGraphTypes.ts
Normal file
55
packages/mermaid/src/diagrams/git/gitGraphTypes.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
export type CommitType = 'NORMAL' | 'REVERSE' | 'HIGHLIGHT' | 'MERGE' | 'CHERRY_PICK';
|
||||||
|
|
||||||
|
export interface Commit {
|
||||||
|
id: string;
|
||||||
|
message: string;
|
||||||
|
seq: number;
|
||||||
|
type: number;
|
||||||
|
tag: string;
|
||||||
|
parents: (string | null)[];
|
||||||
|
branch: string;
|
||||||
|
customType?: number;
|
||||||
|
customId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitGraph {
|
||||||
|
statements: Statement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Statement = CommitAst | Branch | Merge | Checkout | CherryPicking;
|
||||||
|
|
||||||
|
export interface CommitAst {
|
||||||
|
$type: 'Commit';
|
||||||
|
id: string;
|
||||||
|
message?: string;
|
||||||
|
tags?: string[];
|
||||||
|
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Branch {
|
||||||
|
$type: 'Branch';
|
||||||
|
name: string;
|
||||||
|
order?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Merge {
|
||||||
|
$type: 'Merge';
|
||||||
|
branch: string;
|
||||||
|
id?: string;
|
||||||
|
tags?: string[];
|
||||||
|
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Checkout {
|
||||||
|
$type: 'Checkout';
|
||||||
|
branch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CherryPicking {
|
||||||
|
$type: 'CherryPicking';
|
||||||
|
id: string;
|
||||||
|
tags?: string[];
|
||||||
|
parent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DiagramOrientation = 'LR' | 'TB';
|
@@ -1,6 +1,7 @@
|
|||||||
grammar GitGraph
|
grammar GitGraph
|
||||||
|
|
||||||
import "../common/common";
|
import "../common/common";
|
||||||
|
|
||||||
entry GitGraph:
|
entry GitGraph:
|
||||||
NEWLINE*
|
NEWLINE*
|
||||||
'gitGraph' Direction? ':'?
|
'gitGraph' Direction? ':'?
|
||||||
@@ -23,60 +24,43 @@ Statement
|
|||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
Options:
|
|
||||||
'options' '{' rawOptions+=STRING* '}' EOL;
|
|
||||||
|
|
||||||
Direction:
|
Direction:
|
||||||
dir=('LR' | 'TB' | 'BT') EOL;
|
dir=('LR' | 'TB' | 'BT') EOL;
|
||||||
|
|
||||||
|
Options:
|
||||||
|
'options' '{' rawOptions+=STRING* '}' EOL;
|
||||||
|
|
||||||
Commit:
|
Commit:
|
||||||
'commit' properties+=CommitProperty* EOL;
|
'commit'
|
||||||
|
(
|
||||||
CommitProperty
|
'id:' id=STRING
|
||||||
: CommitId
|
|'msg:'? message=STRING
|
||||||
| CommitMessage
|
|'tag:' tags=STRING
|
||||||
| Tags
|
|'type:' name=('NORMAL' | 'REVERSE' | 'HIGHLIGHT')
|
||||||
| CommitType
|
)* EOL;
|
||||||
;
|
|
||||||
|
|
||||||
CommitId:
|
|
||||||
'id:' id=STRING;
|
|
||||||
|
|
||||||
CommitMessage:
|
|
||||||
'msg:'? message=STRING;
|
|
||||||
|
|
||||||
Tags:
|
|
||||||
'tag:' tags=STRING;
|
|
||||||
|
|
||||||
CommitType:
|
|
||||||
'type:' name=('NORMAL' | 'REVERSE' | 'HIGHLIGHT');
|
|
||||||
|
|
||||||
Branch:
|
Branch:
|
||||||
'branch' name=(ID|STRING) ('order:' order=INT)? EOL;
|
'branch' name=(ID|STRING)
|
||||||
|
('order:' order=INT)?
|
||||||
|
EOL;
|
||||||
|
|
||||||
Merge:
|
Merge:
|
||||||
'merge' name=(ID|STRING) properties+=MergeProperties* EOL;
|
'merge' branch=(ID|STRING)
|
||||||
|
(
|
||||||
MergeProperties
|
'id:' id=STRING
|
||||||
: CommitId
|
|'tag:' tags=STRING
|
||||||
| Tags
|
|'type:' name=('NORMAL' | 'REVERSE' | 'HIGHLIGHT')
|
||||||
| CommitType
|
)* EOL;
|
||||||
;
|
|
||||||
|
|
||||||
Checkout:
|
Checkout:
|
||||||
('checkout'|'switch') id=(ID|STRING) EOL;
|
('checkout'|'switch') branch=(ID|STRING) EOL;
|
||||||
|
|
||||||
CherryPicking:
|
CherryPicking:
|
||||||
'cherry-pick' properties+=CherryPickProperties* EOL;
|
'cherry-pick'
|
||||||
|
(
|
||||||
CherryPickProperties
|
'id:' id=STRING
|
||||||
: CommitId
|
|'tag:' tags=STRING
|
||||||
| Tags
|
|'parent:' id=STRING
|
||||||
| ParentCommit
|
)* EOL;
|
||||||
;
|
|
||||||
|
|
||||||
ParentCommit:
|
|
||||||
'parent:' id=STRING;
|
|
||||||
|
|
||||||
terminal INT returns number: /[0-9]+(?=\s)/;
|
terminal INT returns number: /[0-9]+(?=\s)/;
|
||||||
terminal ID returns string: /\w([-\./\w]*[-\w])?/;
|
terminal ID returns string: /\w([-\./\w]*[-\w])?/;
|
||||||
|
@@ -10,90 +10,9 @@ describe('gitGraph', () => {
|
|||||||
expect(result.value.statements).toHaveLength(0);
|
expect(result.value.statements).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle multiple commits', () => {
|
it('should handle gitGraph with one statement', () => {
|
||||||
const result = parse(`
|
const result = parse(`gitGraph\n A`);
|
||||||
gitGraph
|
|
||||||
commit
|
|
||||||
commit
|
|
||||||
`);
|
|
||||||
expect(result.value.$type).toBe(GitGraph);
|
expect(result.value.$type).toBe(GitGraph);
|
||||||
expect(result.value.statements).toHaveLength(2);
|
|
||||||
expect(
|
|
||||||
result.value.statements.every((s: { $type: string }) => s.$type === 'Commit')
|
|
||||||
).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle branches and checkouts', () => {
|
|
||||||
const result = parse(`
|
|
||||||
gitGraph
|
|
||||||
branch feature
|
|
||||||
branch release
|
|
||||||
checkout feature
|
|
||||||
`);
|
|
||||||
expect(result.value.statements).toHaveLength(3);
|
|
||||||
expect(result.value.statements[0].$type).toBe('Branch');
|
|
||||||
expect(result.value.statements[0].name).toBe('feature');
|
|
||||||
expect(result.value.statements[1].$type).toBe('Branch');
|
|
||||||
expect(result.value.statements[1].name).toBe('release');
|
|
||||||
expect(result.value.statements[2].$type).toBe('Checkout');
|
|
||||||
expect(result.value.statements[2].id).toBe('feature');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle merges', () => {
|
|
||||||
const result = parse(`
|
|
||||||
gitGraph
|
|
||||||
branch feature
|
|
||||||
commit id: "A"
|
|
||||||
merge feature id: "M"
|
|
||||||
`);
|
|
||||||
expect(result.value.statements).toHaveLength(3);
|
|
||||||
expect(result.value.statements[2].$type).toBe('Merge');
|
|
||||||
expect(result.value.statements[2].name).toBe('feature');
|
|
||||||
expect(result.value.statements[2].properties[0].id).toBe('M');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle cherry-picking with tags and parent', () => {
|
|
||||||
const result = parse(`
|
|
||||||
gitGraph
|
|
||||||
branch feature
|
|
||||||
commit id: "M"
|
|
||||||
checkout main
|
|
||||||
cherry-pick id: "M" tag: "v2.1:ZERO" parent:"ZERO"
|
|
||||||
`);
|
|
||||||
expect(result.value.statements).toHaveLength(4);
|
|
||||||
expect(result.value.statements[3].$type).toBe('CherryPicking');
|
|
||||||
expect(result.value.statements[3].properties.length).toBe(3);
|
|
||||||
expect(result.value.statements[3].properties[0].id).toBe('M');
|
|
||||||
expect(result.value.statements[3].properties[1].tags).toBe('v2.1:ZERO');
|
|
||||||
expect(result.value.statements[3].properties[2].id).toBe('ZERO');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse complex gitGraph interactions', () => {
|
|
||||||
const result = 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" tag: "v2.1:ZERO" parent:"ZERO"
|
|
||||||
commit id: "D"
|
|
||||||
`);
|
|
||||||
expect(result.value.statements).toHaveLength(12);
|
|
||||||
expect(result.value.statements[0].$type).toBe('Commit');
|
|
||||||
expect(result.value.statements[0].properties[0].id).toBe('ZERO');
|
|
||||||
expect(result.value.statements[1].$type).toBe('Branch');
|
|
||||||
expect(result.value.statements[6].$type).toBe('Merge');
|
|
||||||
expect(result.value.statements[10].$type).toBe('CherryPicking');
|
|
||||||
expect(result.value.statements[10].properties[0].id).toBe('M');
|
|
||||||
expect(result.value.statements[10].properties[2].id).toBe('ZERO');
|
|
||||||
expect(result.value.statements[11].$type).toBe('Commit');
|
|
||||||
expect(result.value.statements[11].properties[0].id).toBe('D');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user