use collections::{BTreeMap, HashMap, IndexSet};use editor::Editor;use git::{ BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote, parse_git_remote_url, repository::{ CommitDiff, CommitFile, InitialGraphCommitData, LogOrder, LogSource, RepoPath, SearchCommitArgs, }, status::{FileStatus, StatusCode, TrackedStatus},};use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView, git_status_icon};use gpui::{ Action, Anchor, AnyElement, App, Bounds, ClickEvent, ClipboardItem, DefiniteLength, DismissEvent, DragMoveEvent, ElementId, Empty, Entity, EventEmitter, FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, PathBuilder, Pixels, Point, ScrollStrategy, ScrollWheelEvent, SharedString, Subscription, Task, TextStyleRefinement, UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, point, prelude::*, px, uniform_list,};use language::line_diff;use menu::{Cancel, SelectFirst, SelectLast, SelectNext, SelectPrevious};use picker::{Picker, PickerDelegate};use project::{ GIT_COMMAND_TASK_TAG, ProjectPath, TaskSourceKind, git_store::{ CommitDataState, GitGraphEvent, GitStore, GitStoreEvent, GraphDataResponse, Repository, RepositoryEvent, RepositoryId, },};use project_panel::ProjectPanel;use search::{ SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, buffer_search,};use smallvec::{SmallVec, smallvec};use std::{ cell::Cell, ops::Range, rc::Rc, sync::{Arc, OnceLock}, time::{Duration, Instant},};use task::{ResolvedTask, TaskContext, TaskVariables, VariableName};use theme::AccentColors;use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem};use ui::{ ButtonLike, Chip, ColumnWidthConfig, CommonAnimationExt as _, ContextMenu, ContextMenuEntry, DiffStat, Divider, HeaderResizeInfo, HighlightedLabel, ListItem, ListItemSpacing, RedistributableColumnsState, ScrollableHandle, Table, TableInteractionState, TableRenderContext, TableResizeBehavior, Tooltip, WithScrollbar, bind_redistributable_columns, prelude::*, render_redistributable_columns_resize_handles, render_table_header, table_row::TableRow,};use workspace::{ ModalView, Workspace, item::{Item, ItemEvent, TabTooltipContent},}; const COMMIT_CIRCLE_RADIUS: Pixels = px(3.5);const COMMIT_CIRCLE_STROKE_WIDTH: Pixels = px(1.5);const LANE_WIDTH: Pixels = px(16.0);const LEFT_PADDING: Pixels = px(12.0);const LINE_WIDTH: Pixels = px(1.5);const RESIZE_HANDLE_WIDTH: f32 = 8.0;const COPIED_STATE_DURATION: Duration = Duration::from_secs(2);// Extra vertical breathing room added to the UI line height when computing// the git graph's row height, so commit dots and lines have space around them.const ROW_VERTICAL_PADDING: Pixels = px(4.0); struct CopiedState { copied_at: Option<Instant>,} impl CopiedState { fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self { Self { copied_at: None } } fn is_copied(&self) -> bool { self.copied_at .map(|t| t.elapsed() < COPIED_STATE_DURATION) .unwrap_or(false) } fn mark_copied(&mut self) { self.copied_at = Some(Instant::now()); }} struct DraggedSplitHandle; struct CommitTagPicker { picker: Entity<Picker<CommitTagPickerDelegate>>,} impl CommitTagPicker { fn new(tag_names: Vec<SharedString>, window: &mut Window, cx: &mut Context<Self>) -> Self { let delegate = CommitTagPickerDelegate { picker: cx.entity().downgrade(), tag_names, selected_index: 0, }; let picker = cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx)); Self { picker } }} impl EventEmitter<DismissEvent> for CommitTagPicker {}impl ModalView for CommitTagPicker {} impl Focusable for CommitTagPicker { fn focus_handle(&self, cx: &App) -> FocusHandle { self.picker.focus_handle(cx) }} impl Render for CommitTagPicker { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { v_flex().w(rems(18.)).child(self.picker.clone()) }} struct CommitTagPickerDelegate { picker: WeakEntity<CommitTagPicker>, tag_names: Vec<SharedString>, selected_index: usize,} impl PickerDelegate for CommitTagPickerDelegate { type ListItem = ListItem; fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Copy Tag".into() } fn match_count(&self) -> usize { self.tag_names.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn update_matches( &mut self, _query: String, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Task<()> { Task::ready(()) } fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) { if let Some(tag_name) = self.tag_names.get(self.selected_index) { cx.write_to_clipboard(ClipboardItem::new_string(tag_name.to_string())); } self.dismissed(window, cx); } fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) { self.picker .update(cx, |_this, cx| cx.emit(DismissEvent)) .ok(); } fn render_match( &self, ix: usize, selected: bool, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child(Label::new(self.tag_names.get(ix)?.clone())), ) }} #[derive(Clone)]struct ChangedFileEntry { status: FileStatus, file_name: SharedString, dir_path: SharedString, repo_path: RepoPath,} impl ChangedFileEntry { fn from_commit_file(file: &CommitFile, _cx: &App) -> Self { let file_name: SharedString = file .path .file_name() .map(|n| n.to_string()) .unwrap_or_default() .into(); let dir_path: SharedString = file .path .parent() .map(|p| p.as_unix_str().to_string()) .unwrap_or_default() .into(); let status_code = match (&file.old_text, &file.new_text) { (None, Some(_)) => StatusCode::Added, (Some(_), None) => StatusCode::Deleted, _ => StatusCode::Modified, }; let status = FileStatus::Tracked(TrackedStatus { index_status: status_code, worktree_status: StatusCode::Unmodified, }); Self { status, file_name, dir_path, repo_path: file.path.clone(), } } fn open_in_commit_view( &self, commit_sha: &SharedString, repository: &WeakEntity<Repository>, workspace: &WeakEntity<Workspace>, window: &mut Window, cx: &mut App, ) { CommitView::open( commit_sha.to_string(), repository.clone(), workspace.clone(), None, Some(self.repo_path.clone()), window, cx, ); } fn render( &self, ix: usize, commit_sha: SharedString, repository: WeakEntity<Repository>, workspace: WeakEntity<Workspace>, _cx: &App, ) -> AnyElement { let file_name = self.file_name.clone(); let dir_path = self.dir_path.clone(); div() .w_full() .child( ButtonLike::new(("changed-file", ix)) .child( h_flex() .min_w_0() .w_full() .gap_1() .overflow_hidden() .child(git_status_icon(self.status)) .child( Label::new(file_name.clone()) .size(LabelSize::Small) .truncate(), ) .when(!dir_path.is_empty(), |this| { this.child( Label::new(dir_path.clone()) .size(LabelSize::Small) .color(Color::Muted) .truncate_start(), ) }), ) .tooltip({ let meta = if dir_path.is_empty() { file_name } else { format!("{}/{}", dir_path, file_name).into() }; move |_, cx| Tooltip::with_meta("View Changes", None, meta.clone(), cx) }) .on_click({ let entry = self.clone(); move |_, window, cx| { entry.open_in_commit_view( &commit_sha, &repository, &workspace, window, cx, ); } }), ) .into_any_element() }} enum QueryState { Pending(SharedString), Confirmed((SharedString, Task<()>)), Empty,} impl QueryState { fn next_state(&mut self) { match self { Self::Confirmed((query, _)) => *self = Self::Pending(std::mem::take(query)), _ => {} }; }} struct SearchState { case_sensitive: bool, editor: Entity<Editor>, state: QueryState, pub matches: IndexSet<Oid>, pub selected_index: Option<usize>,} pub struct SplitState { left_ratio: f32, visible_left_ratio: f32,} impl SplitState { pub fn new() -> Self { Self { left_ratio: 1.0, visible_left_ratio: 1.0, } } pub fn right_ratio(&self) -> f32 { 1.0 - self.visible_left_ratio } fn on_drag_move( &mut self, drag_event: &DragMoveEvent<DraggedSplitHandle>, _window: &mut Window, _cx: &mut Context<Self>, ) { let drag_position = drag_event.event.position; let bounds = drag_event.bounds; let bounds_width = bounds.right() - bounds.left(); let min_ratio = 0.1; let max_ratio = 0.9; let new_ratio = (drag_position.x - bounds.left()) / bounds_width; self.visible_left_ratio = new_ratio.clamp(min_ratio, max_ratio); } fn commit_ratio(&mut self) { self.left_ratio = self.visible_left_ratio; } fn on_double_click(&mut self) { self.left_ratio = 1.0; self.visible_left_ratio = 1.0; }} actions!( git_graph, [ /// Copies the SHA of the selected commit to the clipboard. CopyCommitSha, /// Copies a tag from the selected commit to the clipboard. CopyCommitTag, /// Opens the commit view for the selected commit. OpenCommitView, /// Focuses the search field. FocusSearch, /// Focuses the next git graph tab stop. FocusNextTabStop, /// Focuses the previous git graph tab stop. FocusPreviousTabStop, /// Selects a commit half a page above the current selection. ScrollUp, /// Selects a commit half a page below the current selection. ScrollDown, ]); fn timestamp_format() -> &'static [BorrowedFormatItem<'static>] { static FORMAT: OnceLock<Vec<BorrowedFormatItem<'static>>> = OnceLock::new(); FORMAT.get_or_init(|| { time::format_description::parse("[day] [month repr:short] [year] [hour]:[minute]") .unwrap_or_default() })} fn format_timestamp(timestamp: i64) -> String { let Ok(datetime) = OffsetDateTime::from_unix_timestamp(timestamp) else { return "Unknown".to_string(); }; let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC); let local_datetime = datetime.to_offset(local_offset); local_datetime .format(timestamp_format()) .unwrap_or_default()} fn accent_colors_count(accents: &AccentColors) -> usize { accents.0.len()} #[derive(Copy, Clone, Debug)]struct BranchColor(u8); #[derive(Debug)]enum LaneState { Empty, Active { child: Oid, parent: Oid, color: Option<BranchColor>, starting_row: usize, starting_col: usize, destination_column: Option<usize>, segments: SmallVec<[CommitLineSegment; 1]>, },} impl LaneState { fn to_commit_lines( &mut self, ending_row: usize, lane_column: usize, parent_column: usize, parent_color: BranchColor, ) -> Option<CommitLine> { let state = std::mem::replace(self, LaneState::Empty); match state { LaneState::Active { #[cfg_attr(not(test), allow(unused_variables))] parent, #[cfg_attr(not(test), allow(unused_variables))] child, color, starting_row, starting_col, destination_column, mut segments, } => { let final_destination = destination_column.unwrap_or(parent_column); let final_color = color.unwrap_or(parent_color); Some(CommitLine { #[cfg(test)] child, #[cfg(test)] parent, child_column: starting_col, full_interval: starting_row..ending_row, color_idx: final_color.0 as usize, segments: { match segments.last_mut() { Some(CommitLineSegment::Straight { to_row }) if *to_row == usize::MAX => { if final_destination != lane_column { *to_row = ending_row - 1; let curved_line = CommitLineSegment::Curve { to_column: final_destination, on_row: ending_row, curve_kind: CurveKind::Checkout, }; if *to_row == starting_row { let last_index = segments.len() - 1; segments[last_index] = curved_line; } else { segments.push(curved_line); } } else { *to_row = ending_row;Lines 1–500 of 6220. Load more
use collections::{BTreeMap, HashMap, IndexSet}; use editor::Editor; use git::{ BuildCommitPermalinkParams, GitHostingProviderRegistry, GitRemote, Oid, ParsedGitRemote, parse_git_remote_url, repository::{ CommitDiff, CommitFile, InitialGraphCommitData, LogOrder, LogSource, RepoPath, SearchCommitArgs, }, status::{FileStatus, StatusCode, TrackedStatus}, }; use git_ui::{commit_tooltip::CommitAvatar, commit_view::CommitView, git_status_icon}; use gpui::{ Action, Anchor, AnyElement, App, Bounds, ClickEvent, ClipboardItem, DefiniteLength, DismissEvent, DragMoveEvent, ElementId, Empty, Entity, EventEmitter, FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, PathBuilder, Pixels, Point, ScrollStrategy, ScrollWheelEvent, SharedString, Subscription, Task, TextStyleRefinement, UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, point, prelude::*, px, uniform_list, }; use language::line_diff; use menu::{Cancel, SelectFirst, SelectLast, SelectNext, SelectPrevious}; use picker::{Picker, PickerDelegate}; use project::{ GIT_COMMAND_TASK_TAG, ProjectPath, TaskSourceKind, git_store::{ CommitDataState, GitGraphEvent, GitStore, GitStoreEvent, GraphDataResponse, Repository, RepositoryEvent, RepositoryId, }, }; use project_panel::ProjectPanel; use search::{ SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, buffer_search, }; use smallvec::{SmallVec, smallvec}; use std::{ cell::Cell, ops::Range, rc::Rc, sync::{Arc, OnceLock}, time::{Duration, Instant}, }; use task::{ResolvedTask, TaskContext, TaskVariables, VariableName}; use theme::AccentColors; use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem}; use ui::{ ButtonLike, Chip, ColumnWidthConfig, CommonAnimationExt as _, ContextMenu, ContextMenuEntry, DiffStat, Divider, HeaderResizeInfo, HighlightedLabel, ListItem, ListItemSpacing, RedistributableColumnsState, ScrollableHandle, Table, TableInteractionState, TableRenderContext, TableResizeBehavior, Tooltip, WithScrollbar, bind_redistributable_columns, prelude::*, render_redistributable_columns_resize_handles, render_table_header, table_row::TableRow, }; use workspace::{ ModalView, Workspace, item::{Item, ItemEvent, TabTooltipContent}, }; const COMMIT_CIRCLE_RADIUS: Pixels = px(3.5); const COMMIT_CIRCLE_STROKE_WIDTH: Pixels = px(1.5); const LANE_WIDTH: Pixels = px(16.0); const LEFT_PADDING: Pixels = px(12.0); const LINE_WIDTH: Pixels = px(1.5); const RESIZE_HANDLE_WIDTH: f32 = 8.0; const COPIED_STATE_DURATION: Duration = Duration::from_secs(2); // Extra vertical breathing room added to the UI line height when computing // the git graph's row height, so commit dots and lines have space around them. const ROW_VERTICAL_PADDING: Pixels = px(4.0); struct CopiedState { copied_at: Option<Instant>, } impl CopiedState { fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self { Self { copied_at: None } } fn is_copied(&self) -> bool { self.copied_at .map(|t| t.elapsed() < COPIED_STATE_DURATION) .unwrap_or(false) } fn mark_copied(&mut self) { self.copied_at = Some(Instant::now()); } } struct DraggedSplitHandle; struct CommitTagPicker { picker: Entity<Picker<CommitTagPickerDelegate>>, } impl CommitTagPicker { fn new(tag_names: Vec<SharedString>, window: &mut Window, cx: &mut Context<Self>) -> Self { let delegate = CommitTagPickerDelegate { picker: cx.entity().downgrade(), tag_names, selected_index: 0, }; let picker = cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx)); Self { picker } } } impl EventEmitter<DismissEvent> for CommitTagPicker {} impl ModalView for CommitTagPicker {} impl Focusable for CommitTagPicker { fn focus_handle(&self, cx: &App) -> FocusHandle { self.picker.focus_handle(cx) } } impl Render for CommitTagPicker { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { v_flex().w(rems(18.)).child(self.picker.clone()) } } struct CommitTagPickerDelegate { picker: WeakEntity<CommitTagPicker>, tag_names: Vec<SharedString>, selected_index: usize, } impl PickerDelegate for CommitTagPickerDelegate { type ListItem = ListItem; fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Copy Tag".into() } fn match_count(&self) -> usize { self.tag_names.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) { self.selected_index = ix; } fn update_matches( &mut self, _query: String, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Task<()> { Task::ready(()) } fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) { if let Some(tag_name) = self.tag_names.get(self.selected_index) { cx.write_to_clipboard(ClipboardItem::new_string(tag_name.to_string())); } self.dismissed(window, cx); } fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) { self.picker .update(cx, |_this, cx| cx.emit(DismissEvent)) .ok(); } fn render_match( &self, ix: usize, selected: bool, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child(Label::new(self.tag_names.get(ix)?.clone())), ) } } #[derive(Clone)] struct ChangedFileEntry { status: FileStatus, file_name: SharedString, dir_path: SharedString, repo_path: RepoPath, } impl ChangedFileEntry { fn from_commit_file(file: &CommitFile, _cx: &App) -> Self { let file_name: SharedString = file .path .file_name() .map(|n| n.to_string()) .unwrap_or_default() .into(); let dir_path: SharedString = file .path .parent() .map(|p| p.as_unix_str().to_string()) .unwrap_or_default() .into(); let status_code = match (&file.old_text, &file.new_text) { (None, Some(_)) => StatusCode::Added, (Some(_), None) => StatusCode::Deleted, _ => StatusCode::Modified, }; let status = FileStatus::Tracked(TrackedStatus { index_status: status_code, worktree_status: StatusCode::Unmodified, }); Self { status, file_name, dir_path, repo_path: file.path.clone(), } } fn open_in_commit_view( &self, commit_sha: &SharedString, repository: &WeakEntity<Repository>, workspace: &WeakEntity<Workspace>, window: &mut Window, cx: &mut App, ) { CommitView::open( commit_sha.to_string(), repository.clone(), workspace.clone(), None, Some(self.repo_path.clone()), window, cx, ); } fn render( &self, ix: usize, commit_sha: SharedString, repository: WeakEntity<Repository>, workspace: WeakEntity<Workspace>, _cx: &App, ) -> AnyElement { let file_name = self.file_name.clone(); let dir_path = self.dir_path.clone(); div() .w_full() .child( ButtonLike::new(("changed-file", ix)) .child( h_flex() .min_w_0() .w_full() .gap_1() .overflow_hidden() .child(git_status_icon(self.status)) .child( Label::new(file_name.clone()) .size(LabelSize::Small) .truncate(), ) .when(!dir_path.is_empty(), |this| { this.child( Label::new(dir_path.clone()) .size(LabelSize::Small) .color(Color::Muted) .truncate_start(), ) }), ) .tooltip({ let meta = if dir_path.is_empty() { file_name } else { format!("{}/{}", dir_path, file_name).into() }; move |_, cx| Tooltip::with_meta("View Changes", None, meta.clone(), cx) }) .on_click({ let entry = self.clone(); move |_, window, cx| { entry.open_in_commit_view( &commit_sha, &repository, &workspace, window, cx, ); } }), ) .into_any_element() } } enum QueryState { Pending(SharedString), Confirmed((SharedString, Task<()>)), Empty, } impl QueryState { fn next_state(&mut self) { match self { Self::Confirmed((query, _)) => *self = Self::Pending(std::mem::take(query)), _ => {} }; } } struct SearchState { case_sensitive: bool, editor: Entity<Editor>, state: QueryState, pub matches: IndexSet<Oid>, pub selected_index: Option<usize>, } pub struct SplitState { left_ratio: f32, visible_left_ratio: f32, } impl SplitState { pub fn new() -> Self { Self { left_ratio: 1.0, visible_left_ratio: 1.0, } } pub fn right_ratio(&self) -> f32 { 1.0 - self.visible_left_ratio } fn on_drag_move( &mut self, drag_event: &DragMoveEvent<DraggedSplitHandle>, _window: &mut Window, _cx: &mut Context<Self>, ) { let drag_position = drag_event.event.position; let bounds = drag_event.bounds; let bounds_width = bounds.right() - bounds.left(); let min_ratio = 0.1; let max_ratio = 0.9; let new_ratio = (drag_position.x - bounds.left()) / bounds_width; self.visible_left_ratio = new_ratio.clamp(min_ratio, max_ratio); } fn commit_ratio(&mut self) { self.left_ratio = self.visible_left_ratio; } fn on_double_click(&mut self) { self.left_ratio = 1.0; self.visible_left_ratio = 1.0; } } actions!( git_graph, [ /// Copies the SHA of the selected commit to the clipboard. CopyCommitSha, /// Copies a tag from the selected commit to the clipboard. CopyCommitTag, /// Opens the commit view for the selected commit. OpenCommitView, /// Focuses the search field. FocusSearch, /// Focuses the next git graph tab stop. FocusNextTabStop, /// Focuses the previous git graph tab stop. FocusPreviousTabStop, /// Selects a commit half a page above the current selection. ScrollUp, /// Selects a commit half a page below the current selection. ScrollDown, ] ); fn timestamp_format() -> &'static [BorrowedFormatItem<'static>] { static FORMAT: OnceLock<Vec<BorrowedFormatItem<'static>>> = OnceLock::new(); FORMAT.get_or_init(|| { time::format_description::parse("[day] [month repr:short] [year] [hour]:[minute]") .unwrap_or_default() }) } fn format_timestamp(timestamp: i64) -> String { let Ok(datetime) = OffsetDateTime::from_unix_timestamp(timestamp) else { return "Unknown".to_string(); }; let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC); let local_datetime = datetime.to_offset(local_offset); local_datetime .format(timestamp_format()) .unwrap_or_default() } fn accent_colors_count(accents: &AccentColors) -> usize { accents.0.len() } #[derive(Copy, Clone, Debug)] struct BranchColor(u8); #[derive(Debug)] enum LaneState { Empty, Active { child: Oid, parent: Oid, color: Option<BranchColor>, starting_row: usize, starting_col: usize, destination_column: Option<usize>, segments: SmallVec<[CommitLineSegment; 1]>, }, } impl LaneState { fn to_commit_lines( &mut self, ending_row: usize, lane_column: usize, parent_column: usize, parent_color: BranchColor, ) -> Option<CommitLine> { let state = std::mem::replace(self, LaneState::Empty); match state { LaneState::Active { #[cfg_attr(not(test), allow(unused_variables))] parent, #[cfg_attr(not(test), allow(unused_variables))] child, color, starting_row, starting_col, destination_column, mut segments, } => { let final_destination = destination_column.unwrap_or(parent_column); let final_color = color.unwrap_or(parent_color); Some(CommitLine { #[cfg(test)] child, #[cfg(test)] parent, child_column: starting_col, full_interval: starting_row..ending_row, color_idx: final_color.0 as usize, segments: { match segments.last_mut() { Some(CommitLineSegment::Straight { to_row }) if *to_row == usize::MAX => { if final_destination != lane_column { *to_row = ending_row - 1; let curved_line = CommitLineSegment::Curve { to_column: final_destination, on_row: ending_row, curve_kind: CurveKind::Checkout, }; if *to_row == starting_row { let last_index = segments.len() - 1; segments[last_index] = curved_line; } else { segments.push(curved_line); } } else { *to_row = ending_row;