1use crate::Result;
2use crate::model::{
3 Bounds, GitGraphArrowLayout, GitGraphBranchLayout, GitGraphCommitLayout, GitGraphDiagramLayout,
4};
5use crate::text::{TextMeasurer, TextStyle};
6use serde::Deserialize;
7use std::collections::HashMap;
8
9const LAYOUT_OFFSET: f64 = 10.0;
10const COMMIT_STEP: f64 = 40.0;
11const DEFAULT_POS: f64 = 30.0;
12const THEME_COLOR_LIMIT: usize = 8;
13
14const COMMIT_TYPE_MERGE: i64 = 3;
15
16#[derive(Debug, Clone, Deserialize)]
17struct GitGraphBranch {
18 name: String,
19}
20
21#[derive(Debug, Clone, Deserialize)]
22struct GitGraphCommit {
23 id: String,
24 #[serde(default)]
25 message: String,
26 #[serde(default)]
27 parents: Vec<String>,
28 seq: i64,
29 #[serde(default)]
30 tags: Vec<String>,
31 #[serde(rename = "type")]
32 commit_type: i64,
33 branch: String,
34 #[serde(default, rename = "customType")]
35 custom_type: Option<i64>,
36 #[serde(default, rename = "customId")]
37 custom_id: Option<bool>,
38}
39
40#[derive(Debug, Clone, Deserialize)]
41struct GitGraphModel {
42 #[serde(default)]
43 branches: Vec<GitGraphBranch>,
44 #[serde(default)]
45 commits: Vec<GitGraphCommit>,
46 #[serde(default)]
47 direction: String,
48 #[serde(rename = "type")]
49 diagram_type: String,
50}
51
52fn cfg_f64(cfg: &serde_json::Value, path: &[&str]) -> Option<f64> {
53 let mut cur = cfg;
54 for k in path {
55 cur = cur.get(*k)?;
56 }
57 cur.as_f64()
58}
59
60fn cfg_bool(cfg: &serde_json::Value, path: &[&str]) -> Option<bool> {
61 let mut cur = cfg;
62 for k in path {
63 cur = cur.get(*k)?;
64 }
65 cur.as_bool()
66}
67
68fn commit_symbol_type(commit: &GitGraphCommit) -> i64 {
69 commit.custom_type.unwrap_or(commit.commit_type)
70}
71
72#[derive(Debug, Clone, Copy)]
73struct CommitPosition {
74 x: f64,
75 y: f64,
76}
77
78fn find_closest_parent(
79 parents: &[String],
80 dir: &str,
81 commit_pos: &HashMap<String, CommitPosition>,
82) -> Option<String> {
83 let mut target: f64 = if dir == "BT" { f64::INFINITY } else { 0.0 };
84 let mut closest: Option<String> = None;
85 for parent in parents {
86 let Some(pos) = commit_pos.get(parent) else {
87 continue;
88 };
89 let parent_position = if dir == "TB" || dir == "BT" {
90 pos.y
91 } else {
92 pos.x
93 };
94 if dir == "BT" {
95 if parent_position <= target {
96 closest = Some(parent.clone());
97 target = parent_position;
98 }
99 } else if parent_position >= target {
100 closest = Some(parent.clone());
101 target = parent_position;
102 }
103 }
104 closest
105}
106
107fn should_reroute_arrow(
108 commit_a: &GitGraphCommit,
109 commit_b: &GitGraphCommit,
110 p1: CommitPosition,
111 p2: CommitPosition,
112 all_commits: &HashMap<String, GitGraphCommit>,
113 dir: &str,
114) -> bool {
115 let commit_b_is_furthest = if dir == "TB" || dir == "BT" {
116 p1.x < p2.x
117 } else {
118 p1.y < p2.y
119 };
120 let branch_to_get_curve = if commit_b_is_furthest {
121 commit_b.branch.as_str()
122 } else {
123 commit_a.branch.as_str()
124 };
125
126 all_commits.values().any(|commit_x| {
127 commit_x.branch == branch_to_get_curve
128 && commit_x.seq > commit_a.seq
129 && commit_x.seq < commit_b.seq
130 })
131}
132
133fn find_lane(y1: f64, y2: f64, lanes: &mut Vec<f64>, depth: usize) -> f64 {
134 let candidate = y1 + (y1 - y2).abs() / 2.0;
135 if depth > 5 {
136 return candidate;
137 }
138
139 let ok = lanes.iter().all(|lane| (lane - candidate).abs() >= 10.0);
140 if ok {
141 lanes.push(candidate);
142 return candidate;
143 }
144
145 let diff = (y1 - y2).abs();
146 find_lane(y1, y2 - diff / 5.0, lanes, depth + 1)
147}
148
149fn draw_arrow(
150 commit_a: &GitGraphCommit,
151 commit_b: &GitGraphCommit,
152 all_commits: &HashMap<String, GitGraphCommit>,
153 commit_pos: &HashMap<String, CommitPosition>,
154 branch_index: &HashMap<String, usize>,
155 lanes: &mut Vec<f64>,
156 dir: &str,
157) -> Option<GitGraphArrowLayout> {
158 let p1 = *commit_pos.get(&commit_a.id)?;
159 let p2 = *commit_pos.get(&commit_b.id)?;
160 let arrow_needs_rerouting = should_reroute_arrow(commit_a, commit_b, p1, p2, all_commits, dir);
161
162 let mut color_class_num = branch_index.get(&commit_b.branch).copied().unwrap_or(0);
163 if commit_b.commit_type == COMMIT_TYPE_MERGE
164 && commit_a
165 .id
166 .as_str()
167 .ne(commit_b.parents.first().map(|s| s.as_str()).unwrap_or(""))
168 {
169 color_class_num = branch_index
170 .get(&commit_a.branch)
171 .copied()
172 .unwrap_or(color_class_num);
173 }
174
175 let mut line_def: Option<String> = None;
176 if arrow_needs_rerouting {
177 let arc = "A 10 10, 0, 0, 0,";
178 let arc2 = "A 10 10, 0, 0, 1,";
179 let radius = 10.0;
180 let offset = 10.0;
181
182 let line_y = if p1.y < p2.y {
183 find_lane(p1.y, p2.y, lanes, 0)
184 } else {
185 find_lane(p2.y, p1.y, lanes, 0)
186 };
187 let line_x = if p1.x < p2.x {
188 find_lane(p1.x, p2.x, lanes, 0)
189 } else {
190 find_lane(p2.x, p1.x, lanes, 0)
191 };
192
193 if dir == "TB" {
194 if p1.x < p2.x {
195 line_def = Some(format!(
196 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
197 p1.x,
198 p1.y,
199 line_x - radius,
200 p1.y,
201 arc2,
202 line_x,
203 p1.y + offset,
204 line_x,
205 p2.y - radius,
206 arc,
207 line_x + offset,
208 p2.y,
209 p2.x,
210 p2.y
211 ));
212 } else {
213 color_class_num = branch_index.get(&commit_a.branch).copied().unwrap_or(0);
214 line_def = Some(format!(
215 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
216 p1.x,
217 p1.y,
218 line_x + radius,
219 p1.y,
220 arc,
221 line_x,
222 p1.y + offset,
223 line_x,
224 p2.y - radius,
225 arc2,
226 line_x - offset,
227 p2.y,
228 p2.x,
229 p2.y
230 ));
231 }
232 } else if dir == "BT" {
233 if p1.x < p2.x {
234 line_def = Some(format!(
235 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
236 p1.x,
237 p1.y,
238 line_x - radius,
239 p1.y,
240 arc,
241 line_x,
242 p1.y - offset,
243 line_x,
244 p2.y + radius,
245 arc2,
246 line_x + offset,
247 p2.y,
248 p2.x,
249 p2.y
250 ));
251 } else {
252 color_class_num = branch_index.get(&commit_a.branch).copied().unwrap_or(0);
253 line_def = Some(format!(
254 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
255 p1.x,
256 p1.y,
257 line_x + radius,
258 p1.y,
259 arc2,
260 line_x,
261 p1.y - offset,
262 line_x,
263 p2.y + radius,
264 arc,
265 line_x - offset,
266 p2.y,
267 p2.x,
268 p2.y
269 ));
270 }
271 } else if p1.y < p2.y {
272 line_def = Some(format!(
273 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
274 p1.x,
275 p1.y,
276 p1.x,
277 line_y - radius,
278 arc,
279 p1.x + offset,
280 line_y,
281 p2.x - radius,
282 line_y,
283 arc2,
284 p2.x,
285 line_y + offset,
286 p2.x,
287 p2.y
288 ));
289 } else {
290 color_class_num = branch_index.get(&commit_a.branch).copied().unwrap_or(0);
291 line_def = Some(format!(
292 "M {} {} L {} {} {} {} {} L {} {} {} {} {} L {} {}",
293 p1.x,
294 p1.y,
295 p1.x,
296 line_y + radius,
297 arc2,
298 p1.x + offset,
299 line_y,
300 p2.x - radius,
301 line_y,
302 arc,
303 p2.x,
304 line_y - offset,
305 p2.x,
306 p2.y
307 ));
308 }
309 } else {
310 let arc = "A 20 20, 0, 0, 0,";
311 let arc2 = "A 20 20, 0, 0, 1,";
312 let radius = 20.0;
313 let offset = 20.0;
314
315 if dir == "TB" {
316 if p1.x < p2.x {
317 if commit_b.commit_type == COMMIT_TYPE_MERGE
318 && commit_a.id.as_str().ne(commit_b
319 .parents
320 .first()
321 .map(|s| s.as_str())
322 .unwrap_or(""))
323 {
324 line_def = Some(format!(
325 "M {} {} L {} {} {} {} {} L {} {}",
326 p1.x,
327 p1.y,
328 p1.x,
329 p2.y - radius,
330 arc,
331 p1.x + offset,
332 p2.y,
333 p2.x,
334 p2.y
335 ));
336 } else {
337 line_def = Some(format!(
338 "M {} {} L {} {} {} {} {} L {} {}",
339 p1.x,
340 p1.y,
341 p2.x - radius,
342 p1.y,
343 arc2,
344 p2.x,
345 p1.y + offset,
346 p2.x,
347 p2.y
348 ));
349 }
350 }
351
352 if p1.x > p2.x {
353 if commit_b.commit_type == COMMIT_TYPE_MERGE
354 && commit_a.id.as_str().ne(commit_b
355 .parents
356 .first()
357 .map(|s| s.as_str())
358 .unwrap_or(""))
359 {
360 line_def = Some(format!(
361 "M {} {} L {} {} {} {} {} L {} {}",
362 p1.x,
363 p1.y,
364 p1.x,
365 p2.y - radius,
366 arc2,
367 p1.x - offset,
368 p2.y,
369 p2.x,
370 p2.y
371 ));
372 } else {
373 line_def = Some(format!(
374 "M {} {} L {} {} {} {} {} L {} {}",
375 p1.x,
376 p1.y,
377 p2.x + radius,
378 p1.y,
379 arc,
380 p2.x,
381 p1.y + offset,
382 p2.x,
383 p2.y
384 ));
385 }
386 }
387
388 if p1.x == p2.x {
389 line_def = Some(format!("M {} {} L {} {}", p1.x, p1.y, p2.x, p2.y));
390 }
391 } else if dir == "BT" {
392 if p1.x < p2.x {
393 if commit_b.commit_type == COMMIT_TYPE_MERGE
394 && commit_a.id.as_str().ne(commit_b
395 .parents
396 .first()
397 .map(|s| s.as_str())
398 .unwrap_or(""))
399 {
400 line_def = Some(format!(
401 "M {} {} L {} {} {} {} {} L {} {}",
402 p1.x,
403 p1.y,
404 p1.x,
405 p2.y + radius,
406 arc2,
407 p1.x + offset,
408 p2.y,
409 p2.x,
410 p2.y
411 ));
412 } else {
413 line_def = Some(format!(
414 "M {} {} L {} {} {} {} {} L {} {}",
415 p1.x,
416 p1.y,
417 p2.x - radius,
418 p1.y,
419 arc,
420 p2.x,
421 p1.y - offset,
422 p2.x,
423 p2.y
424 ));
425 }
426 }
427
428 if p1.x > p2.x {
429 if commit_b.commit_type == COMMIT_TYPE_MERGE
430 && commit_a.id.as_str().ne(commit_b
431 .parents
432 .first()
433 .map(|s| s.as_str())
434 .unwrap_or(""))
435 {
436 line_def = Some(format!(
437 "M {} {} L {} {} {} {} {} L {} {}",
438 p1.x,
439 p1.y,
440 p1.x,
441 p2.y + radius,
442 arc,
443 p1.x - offset,
444 p2.y,
445 p2.x,
446 p2.y
447 ));
448 } else {
449 line_def = Some(format!(
450 "M {} {} L {} {} {} {} {} L {} {}",
451 p1.x,
452 p1.y,
453 p2.x - radius,
454 p1.y,
455 arc,
456 p2.x,
457 p1.y - offset,
458 p2.x,
459 p2.y
460 ));
461 }
462 }
463
464 if p1.x == p2.x {
465 line_def = Some(format!("M {} {} L {} {}", p1.x, p1.y, p2.x, p2.y));
466 }
467 } else {
468 if p1.y < p2.y {
469 if commit_b.commit_type == COMMIT_TYPE_MERGE
470 && commit_a.id.as_str().ne(commit_b
471 .parents
472 .first()
473 .map(|s| s.as_str())
474 .unwrap_or(""))
475 {
476 line_def = Some(format!(
477 "M {} {} L {} {} {} {} {} L {} {}",
478 p1.x,
479 p1.y,
480 p2.x - radius,
481 p1.y,
482 arc2,
483 p2.x,
484 p1.y + offset,
485 p2.x,
486 p2.y
487 ));
488 } else {
489 line_def = Some(format!(
490 "M {} {} L {} {} {} {} {} L {} {}",
491 p1.x,
492 p1.y,
493 p1.x,
494 p2.y - radius,
495 arc,
496 p1.x + offset,
497 p2.y,
498 p2.x,
499 p2.y
500 ));
501 }
502 }
503
504 if p1.y > p2.y {
505 if commit_b.commit_type == COMMIT_TYPE_MERGE
506 && commit_a.id.as_str().ne(commit_b
507 .parents
508 .first()
509 .map(|s| s.as_str())
510 .unwrap_or(""))
511 {
512 line_def = Some(format!(
513 "M {} {} L {} {} {} {} {} L {} {}",
514 p1.x,
515 p1.y,
516 p2.x - radius,
517 p1.y,
518 arc,
519 p2.x,
520 p1.y - offset,
521 p2.x,
522 p2.y
523 ));
524 } else {
525 line_def = Some(format!(
526 "M {} {} L {} {} {} {} {} L {} {}",
527 p1.x,
528 p1.y,
529 p1.x,
530 p2.y + radius,
531 arc2,
532 p1.x + offset,
533 p2.y,
534 p2.x,
535 p2.y
536 ));
537 }
538 }
539
540 if p1.y == p2.y {
541 line_def = Some(format!("M {} {} L {} {}", p1.x, p1.y, p2.x, p2.y));
542 }
543 }
544 }
545
546 let d = line_def?;
547 Some(GitGraphArrowLayout {
548 from: commit_a.id.clone(),
549 to: commit_b.id.clone(),
550 class_index: (color_class_num % THEME_COLOR_LIMIT) as i64,
551 d,
552 })
553}
554
555pub fn layout_gitgraph_diagram(
556 semantic: &serde_json::Value,
557 effective_config: &serde_json::Value,
558 measurer: &dyn TextMeasurer,
559) -> Result<GitGraphDiagramLayout> {
560 let model: GitGraphModel = crate::json::from_value_ref(semantic)?;
561 let _ = model.diagram_type.as_str();
562
563 let direction = if model.direction.trim().is_empty() {
564 "LR".to_string()
565 } else {
566 model.direction.trim().to_string()
567 };
568
569 let rotate_commit_label =
570 cfg_bool(effective_config, &["gitGraph", "rotateCommitLabel"]).unwrap_or(true);
571 let show_commit_label =
572 cfg_bool(effective_config, &["gitGraph", "showCommitLabel"]).unwrap_or(true);
573 let show_branches = cfg_bool(effective_config, &["gitGraph", "showBranches"]).unwrap_or(true);
574 let diagram_padding = cfg_f64(effective_config, &["gitGraph", "diagramPadding"])
575 .unwrap_or(8.0)
576 .max(0.0);
577 let parallel_commits =
578 cfg_bool(effective_config, &["gitGraph", "parallelCommits"]).unwrap_or(false);
579
580 let label_style = TextStyle {
583 font_family: Some("\"trebuchet ms\", verdana, arial, sans-serif".to_string()),
584 font_size: 16.0,
585 font_weight: None,
586 };
587
588 fn corr_px(num_over_2048: i32) -> f64 {
589 num_over_2048 as f64 / 2048.0
592 }
593
594 fn gitgraph_branch_label_bbox_width_correction_px(text: &str) -> f64 {
595 match text {
601 "develop" => corr_px(16), "feature" => corr_px(-48), "newbranch" => corr_px(-32), "testBranch" => corr_px(-32), "testBranch2" => corr_px(-32), "__proto__" => corr_px(-16), "branch/example-branch" => corr_px(-64), _ => 0.0,
616 }
617 }
618
619 fn gitgraph_branch_label_bbox_width_px(
620 measurer: &dyn TextMeasurer,
621 text: &str,
622 style: &TextStyle,
623 ) -> f64 {
624 let base = crate::text::round_to_1_64_px(
627 measurer
628 .measure_svg_simple_text_bbox_width_px(text, style)
629 .max(0.0),
630 );
631 (base + gitgraph_branch_label_bbox_width_correction_px(text)).max(0.0)
632 }
633
634 let mut branches: Vec<GitGraphBranchLayout> = Vec::new();
635 let mut branch_pos: HashMap<String, f64> = HashMap::new();
636 let mut branch_index: HashMap<String, usize> = HashMap::new();
637 let mut pos = 0.0;
638 for (i, b) in model.branches.iter().enumerate() {
639 let metrics = measurer.measure(&b.name, &label_style);
641 let bbox_w = gitgraph_branch_label_bbox_width_px(measurer, &b.name, &label_style);
642 branch_pos.insert(b.name.clone(), pos);
643 branch_index.insert(b.name.clone(), i);
644
645 branches.push(GitGraphBranchLayout {
646 name: b.name.clone(),
647 index: i as i64,
648 pos,
649 bbox_width: bbox_w.max(0.0),
650 bbox_height: metrics.height.max(0.0),
651 });
652
653 pos += 50.0
654 + if rotate_commit_label { 40.0 } else { 0.0 }
655 + if direction == "TB" || direction == "BT" {
656 bbox_w.max(0.0) / 2.0
657 } else {
658 0.0
659 };
660 }
661
662 let mut commits_by_id: HashMap<String, GitGraphCommit> = HashMap::new();
663 for c in &model.commits {
664 commits_by_id.insert(c.id.clone(), c.clone());
665 }
666
667 let mut commit_order: Vec<GitGraphCommit> = model.commits.clone();
668 commit_order.sort_by_key(|c| c.seq);
669
670 let mut sorted_keys: Vec<String> = commit_order.iter().map(|c| c.id.clone()).collect();
671 if direction == "BT" {
672 sorted_keys.reverse();
673 }
674
675 let mut commit_pos: HashMap<String, CommitPosition> = HashMap::new();
676 let mut commits: Vec<GitGraphCommitLayout> = Vec::new();
677 let mut max_pos: f64 = 0.0;
678 let mut cur_pos = if direction == "TB" || direction == "BT" {
679 DEFAULT_POS
680 } else {
681 0.0
682 };
683
684 for id in &sorted_keys {
685 let Some(commit) = commits_by_id.get(id) else {
686 continue;
687 };
688
689 if parallel_commits {
690 if !commit.parents.is_empty() {
691 if let Some(closest_parent) =
692 find_closest_parent(&commit.parents, &direction, &commit_pos)
693 {
694 if let Some(parent_position) = commit_pos.get(&closest_parent) {
695 if direction == "TB" {
696 cur_pos = parent_position.y + COMMIT_STEP;
697 } else if direction == "BT" {
698 let current_position = commit_pos
699 .get(&commit.id)
700 .copied()
701 .unwrap_or(CommitPosition { x: 0.0, y: 0.0 });
702 cur_pos = current_position.y - COMMIT_STEP;
703 } else {
704 cur_pos = parent_position.x + COMMIT_STEP;
705 }
706 }
707 }
708 } else if direction == "TB" {
709 cur_pos = DEFAULT_POS;
710 }
711 }
712
713 let pos_with_offset = if direction == "BT" && parallel_commits {
714 cur_pos
715 } else {
716 cur_pos + LAYOUT_OFFSET
717 };
718 let Some(branch_lane) = branch_pos.get(&commit.branch).copied() else {
719 return Err(crate::Error::InvalidModel {
720 message: format!("unknown branch for commit {}: {}", commit.id, commit.branch),
721 });
722 };
723
724 let (x, y) = if direction == "TB" || direction == "BT" {
725 (branch_lane, pos_with_offset)
726 } else {
727 (pos_with_offset, branch_lane)
728 };
729 commit_pos.insert(commit.id.clone(), CommitPosition { x, y });
730
731 commits.push(GitGraphCommitLayout {
732 id: commit.id.clone(),
733 message: commit.message.clone(),
734 seq: commit.seq,
735 commit_type: commit.commit_type,
736 custom_type: commit.custom_type,
737 custom_id: commit.custom_id,
738 tags: commit.tags.clone(),
739 parents: commit.parents.clone(),
740 branch: commit.branch.clone(),
741 pos: cur_pos,
742 pos_with_offset,
743 x,
744 y,
745 });
746
747 cur_pos = if direction == "BT" && parallel_commits {
748 cur_pos + COMMIT_STEP
749 } else {
750 cur_pos + COMMIT_STEP + LAYOUT_OFFSET
751 };
752 max_pos = max_pos.max(cur_pos);
753 }
754
755 let mut lanes: Vec<f64> = if show_branches {
756 branches.iter().map(|b| b.pos).collect()
757 } else {
758 Vec::new()
759 };
760
761 let mut arrows: Vec<GitGraphArrowLayout> = Vec::new();
762 let mut commits_for_arrows = model.commits.clone();
765 commits_for_arrows.sort_by_key(|c| c.seq);
766 for commit_b in &commits_for_arrows {
767 for parent in &commit_b.parents {
768 let Some(commit_a) = commits_by_id.get(parent) else {
769 continue;
770 };
771 if let Some(a) = draw_arrow(
772 commit_a,
773 commit_b,
774 &commits_by_id,
775 &commit_pos,
776 &branch_index,
777 &mut lanes,
778 &direction,
779 ) {
780 arrows.push(a);
781 }
782 }
783 }
784
785 let mut min_x = f64::INFINITY;
786 let mut min_y = f64::INFINITY;
787 let mut max_x = f64::NEG_INFINITY;
788 let mut max_y = f64::NEG_INFINITY;
789
790 for b in &branches {
791 if direction == "TB" || direction == "BT" {
792 min_x = min_x.min(b.pos);
793 max_x = max_x.max(b.pos);
794 min_y = min_y.min(DEFAULT_POS.min(max_pos));
795 max_y = max_y.max(DEFAULT_POS.max(max_pos));
796 } else {
797 min_y = min_y.min(b.pos);
798 max_y = max_y.max(b.pos);
799 min_x = min_x.min(0.0);
800 max_x = max_x.max(max_pos);
801 let label_left =
802 -b.bbox_width - 4.0 - if rotate_commit_label { 30.0 } else { 0.0 } - 19.0;
803 min_x = min_x.min(label_left);
804 }
805 }
806
807 for c in &commits {
808 let r = if commit_symbol_type(&commits_by_id[&c.id]) == COMMIT_TYPE_MERGE {
809 9.0
810 } else {
811 10.0
812 };
813 min_x = min_x.min(c.x - r);
814 min_y = min_y.min(c.y - r);
815 max_x = max_x.max(c.x + r);
816 max_y = max_y.max(c.y + r);
817 }
818
819 let bounds = if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite()
820 {
821 Some(Bounds {
822 min_x: min_x - diagram_padding,
823 min_y: min_y - diagram_padding,
824 max_x: max_x + diagram_padding,
825 max_y: max_y + diagram_padding,
826 })
827 } else {
828 None
829 };
830
831 Ok(GitGraphDiagramLayout {
832 bounds,
833 direction,
834 rotate_commit_label,
835 show_branches,
836 show_commit_label,
837 parallel_commits,
838 diagram_padding,
839 max_pos,
840 branches,
841 commits,
842 arrows,
843 })
844}