added code coverage script and badges
This commit is contained in:
parent
abe18e75e7
commit
5b7fbfd6ef
20 changed files with 467 additions and 85 deletions
59
.github/workflows/coverage.yml
vendored
Normal file
59
.github/workflows/coverage.yml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
name: Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: coverage-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
APP_FEATURES: --no-default-features --features gix
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Coverage (llvm-cov + Codecov)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
- name: Cache Rust artifacts
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- name: Run coverage
|
||||
run: |
|
||||
cargo llvm-cov --workspace \
|
||||
--exclude gitcomet-ui \
|
||||
--exclude gitcomet-ui-gpui \
|
||||
$APP_FEATURES \
|
||||
--lcov \
|
||||
--output-path target/llvm-cov/lcov.info
|
||||
- name: Upload coverage to Codecov (tokenless)
|
||||
if: ${{ secrets.CODECOV_TOKEN == '' }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: target/llvm-cov/lcov.info
|
||||
flags: unittests
|
||||
name: gitcomet-linux
|
||||
fail_ci_if_error: false
|
||||
- name: Upload coverage to Codecov (with token)
|
||||
if: ${{ secrets.CODECOV_TOKEN != '' }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: target/llvm-cov/lcov.info
|
||||
flags: unittests
|
||||
name: gitcomet-linux
|
||||
fail_ci_if_error: false
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
/target
|
||||
target/
|
||||
**/*.rs.bk
|
||||
.DS_Store
|
||||
*.swp
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ edition = "2024"
|
|||
license = "AGPL-3.0-only"
|
||||
authors = ["AutoExplore Oy <info@autoexplore.ai>"]
|
||||
repository = "https://github.com/Auto-Explore/GitComet"
|
||||
rust-version = "1.94.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Internal crates
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -1,5 +1,8 @@
|
|||
## GitComet
|
||||
|
||||
[](https://github.com/Auto-Explore/GitComet/actions/workflows/rust.yml)
|
||||
[](https://codecov.io/gh/Auto-Explore/GitComet)
|
||||
|
||||
Fast, resource-efficient, fully open source Git GUI written in Rust, targeting GitKraken/SourceTree/GitHub Desktop-class workflows using `gpui` for the UI.
|
||||
|
||||
### Goals
|
||||
|
|
@ -160,6 +163,19 @@ Clippy (CI mode):
|
|||
cargo clippy --workspace --no-default-features --features gix -- -D warnings
|
||||
```
|
||||
|
||||
Coverage (local + CI-compatible):
|
||||
|
||||
```bash
|
||||
rustup component add llvm-tools-preview
|
||||
cargo install --locked cargo-llvm-cov
|
||||
bash scripts/coverage.sh
|
||||
```
|
||||
|
||||
This writes:
|
||||
|
||||
- `target/llvm-cov/lcov.info` (used by CI upload)
|
||||
- `target/llvm-cov/html/index.html` (local detailed report)
|
||||
|
||||
The test suite covers:
|
||||
|
||||
- Core merge algorithm (ported from Git t6403/t6427)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -44,11 +44,8 @@ impl GixRepo {
|
|||
});
|
||||
}
|
||||
gix::status::index_worktree::Item::DirectoryContents { entry, .. } => {
|
||||
let kind = match entry.status {
|
||||
gix::dir::entry::Status::Untracked => FileStatusKind::Untracked,
|
||||
gix::dir::entry::Status::Ignored(_) => continue,
|
||||
gix::dir::entry::Status::Tracked => FileStatusKind::Modified,
|
||||
gix::dir::entry::Status::Pruned => continue,
|
||||
let Some(kind) = map_directory_entry_status(entry.status) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = path_buf_from_git_bytes(
|
||||
|
|
@ -307,10 +304,24 @@ fn map_entry_status<T, U>(
|
|||
}
|
||||
}
|
||||
|
||||
fn map_directory_entry_status(status: gix::dir::entry::Status) -> Option<FileStatusKind> {
|
||||
match status {
|
||||
// Directory-walk entries represent an unstaged change only when they are
|
||||
// genuinely untracked. `Tracked` entries are traversal metadata and must
|
||||
// not become synthetic "modified" files.
|
||||
gix::dir::entry::Status::Untracked => Some(FileStatusKind::Untracked),
|
||||
gix::dir::entry::Status::Ignored(_)
|
||||
| gix::dir::entry::Status::Tracked
|
||||
| gix::dir::entry::Status::Pruned => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{collect_unmerged_conflicts, conflict_kind_from_stage_mask};
|
||||
use gitcomet_core::domain::FileConflictKind;
|
||||
use super::{
|
||||
collect_unmerged_conflicts, conflict_kind_from_stage_mask, map_directory_entry_status,
|
||||
};
|
||||
use gitcomet_core::domain::{FileConflictKind, FileStatusKind};
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -414,4 +425,24 @@ mod tests {
|
|||
vec![(PathBuf::from("conflicted.txt"), FileConflictKind::BothAdded)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_directory_entry_status_only_reports_untracked_entries() {
|
||||
use gix::dir::entry::Status;
|
||||
|
||||
assert_eq!(
|
||||
map_directory_entry_status(Status::Untracked),
|
||||
Some(FileStatusKind::Untracked)
|
||||
);
|
||||
assert_eq!(map_directory_entry_status(Status::Tracked), None);
|
||||
assert_eq!(
|
||||
map_directory_entry_status(Status::Ignored(gix::ignore::Kind::Expendable)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
map_directory_entry_status(Status::Ignored(gix::ignore::Kind::Precious)),
|
||||
None
|
||||
);
|
||||
assert_eq!(map_directory_entry_status(Status::Pruned), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -573,6 +573,53 @@ fn status_lists_untracked_files_in_directories() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_ignores_nested_target_directories_with_target_slash_pattern() {
|
||||
if !require_git_shell_for_status_integration_tests() {
|
||||
return;
|
||||
}
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let repo = dir.path();
|
||||
|
||||
run_git(repo, &["init"]);
|
||||
run_git(repo, &["config", "user.email", "you@example.com"]);
|
||||
run_git(repo, &["config", "user.name", "You"]);
|
||||
run_git(repo, &["config", "commit.gpgsign", "false"]);
|
||||
|
||||
write(repo, ".gitignore", "target/\n");
|
||||
run_git(repo, &["add", ".gitignore"]);
|
||||
run_git(
|
||||
repo,
|
||||
&["-c", "commit.gpgsign=false", "commit", "-m", "init ignore"],
|
||||
);
|
||||
|
||||
write(
|
||||
repo,
|
||||
"crates/gitcomet-ui-gpui/target/criterion/report/index.html",
|
||||
"ignored\n",
|
||||
);
|
||||
write(repo, "visible.txt", "untracked\n");
|
||||
|
||||
let backend = GixBackend;
|
||||
let opened = backend.open(repo).unwrap();
|
||||
let status = opened.status().unwrap();
|
||||
|
||||
assert!(
|
||||
status.unstaged.iter().all(|entry| !entry
|
||||
.path
|
||||
.starts_with(Path::new("crates/gitcomet-ui-gpui/target"))),
|
||||
"expected nested target/ contents to be ignored, got {status:?}"
|
||||
);
|
||||
assert!(
|
||||
status
|
||||
.unstaged
|
||||
.iter()
|
||||
.any(|entry| entry.path == Path::new("visible.txt")
|
||||
&& entry.kind == FileStatusKind::Untracked),
|
||||
"expected visible.txt as untracked, got {status:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_unified_works_for_staged_and_unstaged() {
|
||||
if !require_git_shell_for_status_integration_tests() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -324,6 +324,11 @@ impl TextInput {
|
|||
self.selected_range.clone()
|
||||
}
|
||||
|
||||
pub fn select_all_text(&mut self, cx: &mut Context<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.content.len(), cx);
|
||||
}
|
||||
|
||||
pub fn set_soft_wrap(&mut self, soft_wrap: bool, cx: &mut Context<Self>) {
|
||||
if self.soft_wrap == soft_wrap {
|
||||
return;
|
||||
|
|
@ -452,8 +457,7 @@ impl TextInput {
|
|||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.content.len(), cx)
|
||||
self.select_all_text(cx);
|
||||
}
|
||||
|
||||
fn row_start(&self, offset: usize) -> usize {
|
||||
|
|
|
|||
|
|
@ -70,30 +70,6 @@ pub fn pill(theme: AppTheme, label: impl Into<SharedString>, bg: gpui::Rgba) ->
|
|||
.child(label.into())
|
||||
}
|
||||
|
||||
pub fn key_value_monospace_value(
|
||||
theme: AppTheme,
|
||||
key: impl Into<SharedString>,
|
||||
value: impl Into<SharedString>,
|
||||
) -> Div {
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted)
|
||||
.child(key.into()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_family(crate::view::UI_MONOSPACE_FONT_FAMILY)
|
||||
.child(value.into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn empty_state(
|
||||
theme: AppTheme,
|
||||
title: impl Into<SharedString>,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ mod toast;
|
|||
mod tokens;
|
||||
|
||||
pub use button::{Button, ButtonStyle};
|
||||
pub use containers::{empty_state, key_value_monospace_value, split_columns_header};
|
||||
pub use containers::{empty_state, split_columns_header};
|
||||
#[cfg(test)]
|
||||
pub use containers::{panel, pill};
|
||||
pub use context_menu::{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use gpui::Div;
|
||||
|
||||
fn merge_active(repo: Option<&RepoState>) -> bool {
|
||||
repo.is_some_and(|r| matches!(&r.merge_commit_message, Loadable::Ready(Some(_))))
|
||||
|
|
@ -8,7 +9,44 @@ fn commit_allowed(is_merge_active: bool, staged_count: usize) -> bool {
|
|||
staged_count > 0 || is_merge_active
|
||||
}
|
||||
|
||||
fn commit_details_selectable_row(
|
||||
theme: AppTheme,
|
||||
key: &'static str,
|
||||
input: Entity<components::TextInput>,
|
||||
) -> Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted)
|
||||
.child(key),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.min_w(px(0.0))
|
||||
.text_sm()
|
||||
.font_family(crate::view::UI_MONOSPACE_FONT_FAMILY)
|
||||
.child(input),
|
||||
)
|
||||
}
|
||||
|
||||
impl DetailsPaneView {
|
||||
fn sync_commit_details_input_value(
|
||||
input: &Entity<components::TextInput>,
|
||||
value: &str,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
if input.read(cx).text() != value {
|
||||
input.update(cx, |input, cx| {
|
||||
input.set_text(value.to_string(), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) fn commit_details_view(
|
||||
&mut self,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
|
|
@ -152,6 +190,21 @@ impl DetailsPaneView {
|
|||
input.set_text(details.message.clone(), cx);
|
||||
});
|
||||
}
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_sha_input,
|
||||
details.id.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_date_input,
|
||||
details.committed_at.as_str(),
|
||||
cx,
|
||||
);
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_parent_input,
|
||||
parent.as_str(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let message = div()
|
||||
.id(("commit_details_message_container", repo_id.0))
|
||||
|
|
@ -192,36 +245,21 @@ impl DetailsPaneView {
|
|||
.w_full()
|
||||
.min_w(px(0.0))
|
||||
.child(message)
|
||||
.child(components::key_value_monospace_value(
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Commit SHA",
|
||||
details.id.as_ref().to_string(),
|
||||
self.commit_details_sha_input.clone(),
|
||||
))
|
||||
.child(components::key_value_monospace_value(
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Commit date",
|
||||
details.committed_at.clone(),
|
||||
self.commit_details_date_input.clone(),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted)
|
||||
.child("Parent commit SHA"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_family("monospace")
|
||||
.whitespace_nowrap()
|
||||
.line_clamp(1)
|
||||
.child(parent),
|
||||
),
|
||||
),
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Parent commit SHA",
|
||||
self.commit_details_parent_input.clone(),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
|
@ -295,6 +333,21 @@ impl DetailsPaneView {
|
|||
input.set_text(details.message.clone(), cx);
|
||||
});
|
||||
}
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_sha_input,
|
||||
details.id.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_date_input,
|
||||
details.committed_at.as_str(),
|
||||
cx,
|
||||
);
|
||||
Self::sync_commit_details_input_value(
|
||||
&self.commit_details_parent_input,
|
||||
parent.as_str(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let message = div()
|
||||
.id(("commit_details_message_container", repo_id.0))
|
||||
|
|
@ -335,36 +388,21 @@ impl DetailsPaneView {
|
|||
.w_full()
|
||||
.min_w(px(0.0))
|
||||
.child(message)
|
||||
.child(components::key_value_monospace_value(
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Commit SHA",
|
||||
details.id.as_ref().to_string(),
|
||||
self.commit_details_sha_input.clone(),
|
||||
))
|
||||
.child(components::key_value_monospace_value(
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Commit date",
|
||||
details.committed_at.clone(),
|
||||
self.commit_details_date_input.clone(),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(theme.colors.text_muted)
|
||||
.child("Parent commit SHA"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_family("monospace")
|
||||
.whitespace_nowrap()
|
||||
.line_clamp(1)
|
||||
.child(parent),
|
||||
),
|
||||
),
|
||||
.child(commit_details_selectable_row(
|
||||
theme,
|
||||
"Parent commit SHA",
|
||||
self.commit_details_parent_input.clone(),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
|
|
|
|||
|
|
@ -465,8 +465,7 @@ fn added_file_preview_ctrl_a_ctrl_c_copies_all_content(cx: &mut gpui::TestAppCon
|
|||
std::process::id()
|
||||
));
|
||||
let file_rel = std::path::PathBuf::from("added.rs");
|
||||
let lines: Arc<Vec<String>> =
|
||||
Arc::new(vec!["alpha".into(), "beta".into(), "gamma".into()]);
|
||||
let lines: Arc<Vec<String>> = Arc::new(vec!["alpha".into(), "beta".into(), "gamma".into()]);
|
||||
assert_file_preview_ctrl_a_ctrl_c_copies_all(
|
||||
cx,
|
||||
repo_id,
|
||||
|
|
@ -496,6 +495,94 @@ fn deleted_file_preview_ctrl_a_ctrl_c_copies_all_content(cx: &mut gpui::TestAppC
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn commit_details_metadata_fields_are_selectable(cx: &mut gpui::TestAppContext) {
|
||||
let (store, events) = AppStore::new(Arc::new(TestBackend));
|
||||
let (view, cx) = cx.add_window_view(|window, cx| {
|
||||
super::super::GitCometView::new(store, events, None, window, cx)
|
||||
});
|
||||
|
||||
let repo_id = gitcomet_state::model::RepoId(33);
|
||||
let commit_sha = "0123456789abcdef0123456789abcdef01234567".to_string();
|
||||
let parent_sha = "89abcdef0123456789abcdef0123456789abcdef".to_string();
|
||||
let commit_date = "2026-03-08 12:34:56 +0200".to_string();
|
||||
|
||||
cx.update(|_window, app| {
|
||||
view.update(app, |this, cx| {
|
||||
let mut repo = gitcomet_state::model::RepoState::new_opening(
|
||||
repo_id,
|
||||
gitcomet_core::domain::RepoSpec {
|
||||
workdir: std::path::PathBuf::from("/tmp/repo-commit-metadata-copy"),
|
||||
},
|
||||
);
|
||||
repo.selected_commit = Some(gitcomet_core::domain::CommitId(commit_sha.clone()));
|
||||
repo.commit_details = gitcomet_state::model::Loadable::Ready(Arc::new(
|
||||
gitcomet_core::domain::CommitDetails {
|
||||
id: gitcomet_core::domain::CommitId(commit_sha.clone()),
|
||||
message: "subject".to_string(),
|
||||
committed_at: commit_date.clone(),
|
||||
parent_ids: vec![gitcomet_core::domain::CommitId(parent_sha.clone())],
|
||||
files: vec![],
|
||||
},
|
||||
));
|
||||
|
||||
let next_state = Arc::new(AppState {
|
||||
repos: vec![repo],
|
||||
active_repo: Some(repo_id),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
this._ui_model.update(cx, |model, cx| {
|
||||
model.set_state(next_state, cx);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
cx.update(|window, app| {
|
||||
let _ = window.draw(app);
|
||||
});
|
||||
|
||||
cx.update(|_window, app| {
|
||||
let details_pane = view.read(app).details_pane.clone();
|
||||
let pane = details_pane.read(app);
|
||||
assert_eq!(pane.commit_details_sha_input.read(app).text(), commit_sha);
|
||||
assert_eq!(pane.commit_details_date_input.read(app).text(), commit_date);
|
||||
assert_eq!(
|
||||
pane.commit_details_parent_input.read(app).text(),
|
||||
parent_sha
|
||||
);
|
||||
});
|
||||
|
||||
cx.update(|_window, app| {
|
||||
let details_pane = view.read(app).details_pane.clone();
|
||||
details_pane.update(app, |pane, cx| {
|
||||
pane.commit_details_sha_input
|
||||
.update(cx, |input, cx| input.select_all_text(cx));
|
||||
pane.commit_details_date_input
|
||||
.update(cx, |input, cx| input.select_all_text(cx));
|
||||
pane.commit_details_parent_input
|
||||
.update(cx, |input, cx| input.select_all_text(cx));
|
||||
});
|
||||
});
|
||||
|
||||
cx.update(|_window, app| {
|
||||
let details_pane = view.read(app).details_pane.clone();
|
||||
let pane = details_pane.read(app);
|
||||
assert_eq!(
|
||||
pane.commit_details_sha_input.read(app).selected_text(),
|
||||
Some(commit_sha)
|
||||
);
|
||||
assert_eq!(
|
||||
pane.commit_details_date_input.read(app).selected_text(),
|
||||
Some(commit_date)
|
||||
);
|
||||
assert_eq!(
|
||||
pane.commit_details_parent_input.read(app).selected_text(),
|
||||
Some(parent_sha)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn switching_active_repo_clears_commit_message_input(cx: &mut gpui::TestAppContext) {
|
||||
let (store, events) = AppStore::new(Arc::new(TestBackend));
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ pub(in super::super) struct DetailsPaneView {
|
|||
|
||||
pub(in super::super) commit_message_input: Entity<components::TextInput>,
|
||||
pub(in super::super) commit_details_message_input: Entity<components::TextInput>,
|
||||
pub(in super::super) commit_details_sha_input: Entity<components::TextInput>,
|
||||
pub(in super::super) commit_details_date_input: Entity<components::TextInput>,
|
||||
pub(in super::super) commit_details_parent_input: Entity<components::TextInput>,
|
||||
pub(in super::super) commit_message_user_edited: bool,
|
||||
pub(in super::super) commit_message_last_text: SharedString,
|
||||
pub(in super::super) commit_message_programmatic_change: bool,
|
||||
|
|
@ -103,6 +106,48 @@ impl DetailsPaneView {
|
|||
)
|
||||
});
|
||||
|
||||
let commit_details_sha_input = cx.new(|cx| {
|
||||
components::TextInput::new(
|
||||
components::TextInputOptions {
|
||||
placeholder: "".into(),
|
||||
multiline: false,
|
||||
read_only: true,
|
||||
chromeless: true,
|
||||
soft_wrap: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let commit_details_date_input = cx.new(|cx| {
|
||||
components::TextInput::new(
|
||||
components::TextInputOptions {
|
||||
placeholder: "".into(),
|
||||
multiline: false,
|
||||
read_only: true,
|
||||
chromeless: true,
|
||||
soft_wrap: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let commit_details_parent_input = cx.new(|cx| {
|
||||
components::TextInput::new(
|
||||
components::TextInputOptions {
|
||||
placeholder: "".into(),
|
||||
multiline: false,
|
||||
read_only: true,
|
||||
chromeless: true,
|
||||
soft_wrap: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let commit_message_subscription = cx.observe(&commit_message_input, |this, input, cx| {
|
||||
let next: SharedString = input.read(cx).text().to_string().into();
|
||||
if this.commit_message_programmatic_change {
|
||||
|
|
@ -133,6 +178,9 @@ impl DetailsPaneView {
|
|||
commit_scroll: ScrollHandle::new(),
|
||||
commit_message_input,
|
||||
commit_details_message_input,
|
||||
commit_details_sha_input,
|
||||
commit_details_date_input,
|
||||
commit_details_parent_input,
|
||||
commit_message_user_edited: false,
|
||||
commit_message_last_text: SharedString::default(),
|
||||
commit_message_programmatic_change: false,
|
||||
|
|
@ -152,6 +200,12 @@ impl DetailsPaneView {
|
|||
.update(cx, |input, cx| input.set_theme(theme, cx));
|
||||
self.commit_details_message_input
|
||||
.update(cx, |input, cx| input.set_theme(theme, cx));
|
||||
self.commit_details_sha_input
|
||||
.update(cx, |input, cx| input.set_theme(theme, cx));
|
||||
self.commit_details_date_input
|
||||
.update(cx, |input, cx| input.set_theme(theme, cx));
|
||||
self.commit_details_parent_input
|
||||
.update(cx, |input, cx| input.set_theme(theme, cx));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition.workspace = true
|
|||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
62
scripts/coverage.sh
Executable file
62
scripts/coverage.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
if ! command -v rustup >/dev/null 2>&1; then
|
||||
echo "rustup is required to manage Rust toolchain components." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "cargo is required to run coverage." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! rustup component list --installed | grep -Eq '^llvm-tools(-preview)?($|-)'; then
|
||||
echo "Missing rustup component: llvm-tools (aka llvm-tools-preview)." >&2
|
||||
echo "Install with: rustup component add llvm-tools-preview" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! cargo llvm-cov --version >/dev/null 2>&1; then
|
||||
echo "Missing cargo subcommand: cargo-llvm-cov" >&2
|
||||
echo "Install with: cargo install --locked cargo-llvm-cov" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
coverage_dir="target/llvm-cov"
|
||||
lcov_path="${coverage_dir}/lcov.info"
|
||||
html_dir="${coverage_dir}/html"
|
||||
|
||||
mkdir -p "$coverage_dir"
|
||||
|
||||
cargo llvm-cov \
|
||||
--workspace \
|
||||
--exclude gitcomet-ui \
|
||||
--exclude gitcomet-ui-gpui \
|
||||
--no-default-features \
|
||||
--features gix \
|
||||
--lcov \
|
||||
--output-path "$lcov_path" \
|
||||
"$@"
|
||||
|
||||
cargo llvm-cov \
|
||||
--workspace \
|
||||
--exclude gitcomet-ui \
|
||||
--exclude gitcomet-ui-gpui \
|
||||
--no-default-features \
|
||||
--features gix \
|
||||
--html \
|
||||
--output-dir "$html_dir" \
|
||||
--no-run \
|
||||
"$@"
|
||||
|
||||
if [[ -f "$lcov_path" && -f "${html_dir}/index.html" ]]; then
|
||||
echo "Coverage summary generated."
|
||||
echo "LCOV report: ${repo_root}/${lcov_path}"
|
||||
echo "HTML report: ${repo_root}/${html_dir}/index.html"
|
||||
else
|
||||
echo "cargo llvm-cov completed."
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue