.
This commit is contained in:
@@ -309,6 +309,23 @@ pub fn worktree_all_commits(workdir: &Path) -> Result<Vec<FileCommit>> {
|
|||||||
all_commits(&gix::open(workdir)?)
|
all_commits(&gix::open(workdir)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The commit HEAD points to, like `git log -1`. Returns `Ok(None)` for a
|
||||||
|
/// repository without commits yet (unborn HEAD).
|
||||||
|
pub fn head_commit(repo: &gix::Repository) -> Result<Option<FileCommit>> {
|
||||||
|
let Some(head) = repo.head_id().ok() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let commit = head.object()?.into_commit();
|
||||||
|
let author = commit.author()?;
|
||||||
|
let message = commit.message()?;
|
||||||
|
Ok(Some(FileCommit {
|
||||||
|
id: commit.id().shorten_or_id().to_string(),
|
||||||
|
summary: String::from_utf8_lossy(message.title).trim().to_string(),
|
||||||
|
author: String::from_utf8_lossy(author.name).trim().to_string(),
|
||||||
|
time: author.time()?.seconds,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// Short names of local branches (`refs/heads/*`), sorted alphabetically.
|
/// Short names of local branches (`refs/heads/*`), sorted alphabetically.
|
||||||
pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
|
pub fn worktree_branches(workdir: &Path) -> Result<Vec<String>> {
|
||||||
let repo = gix::open(workdir)?;
|
let repo = gix::open(workdir)?;
|
||||||
@@ -353,10 +370,12 @@ pub struct WorktreeSnapshot {
|
|||||||
pub readme: Option<Vec<u8>>,
|
pub readme: Option<Vec<u8>>,
|
||||||
/// Branch HEAD points to (`None` when detached, e.g. on a tag).
|
/// Branch HEAD points to (`None` when detached, e.g. on a tag).
|
||||||
pub current_branch: Option<String>,
|
pub current_branch: Option<String>,
|
||||||
|
/// Commit HEAD points to, if any (see [`head_commit`]).
|
||||||
|
pub head_commit: Option<FileCommit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot the worktree after a branch/tag switch: entries, README and the
|
/// Snapshot the worktree after a branch/tag switch: entries, README, the
|
||||||
/// branch HEAD points to, opening the repository once.
|
/// branch HEAD points to and its commit, opening the repository once.
|
||||||
pub fn worktree_snapshot(workdir: &Path) -> Result<WorktreeSnapshot> {
|
pub fn worktree_snapshot(workdir: &Path) -> Result<WorktreeSnapshot> {
|
||||||
let repo = gix::open(workdir)?;
|
let repo = gix::open(workdir)?;
|
||||||
let readme_path = find_readme(&repo)?;
|
let readme_path = find_readme(&repo)?;
|
||||||
@@ -369,6 +388,7 @@ pub fn worktree_snapshot(workdir: &Path) -> Result<WorktreeSnapshot> {
|
|||||||
readme_path,
|
readme_path,
|
||||||
readme,
|
readme,
|
||||||
current_branch: current_branch(&repo)?,
|
current_branch: current_branch(&repo)?,
|
||||||
|
head_commit: head_commit(&repo)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,6 +692,23 @@ mod tests {
|
|||||||
assert!(find_readme(&repo).expect("find").is_none());
|
assert!(find_readme(&repo).expect("find").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn head_commit_reports_head() {
|
||||||
|
let (_dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||||
|
|
||||||
|
// Unborn HEAD: no commit yet.
|
||||||
|
assert!(head_commit(&repo).expect("head").is_none());
|
||||||
|
|
||||||
|
commit_all(&repo, "initial");
|
||||||
|
let head = head_commit(&repo).expect("head").expect("commit");
|
||||||
|
assert_eq!(
|
||||||
|
head.id,
|
||||||
|
repo.head_id().expect("head id").shorten_or_id().to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(head.summary, "initial");
|
||||||
|
assert_eq!(head.author, "Test Author");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn worktree_branches_and_tags_list_short_names() {
|
fn worktree_branches_and_tags_list_short_names() {
|
||||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||||
@@ -743,6 +780,10 @@ mod tests {
|
|||||||
|
|
||||||
let snapshot = worktree_snapshot(dir).expect("snapshot");
|
let snapshot = worktree_snapshot(dir).expect("snapshot");
|
||||||
assert_eq!(snapshot.current_branch.as_deref(), Some("feature"));
|
assert_eq!(snapshot.current_branch.as_deref(), Some("feature"));
|
||||||
|
assert_eq!(
|
||||||
|
snapshot.head_commit.as_ref().expect("head commit").summary,
|
||||||
|
"feature work"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
String::from_utf8(snapshot.readme.expect("readme")).expect("utf8"),
|
String::from_utf8(snapshot.readme.expect("readme")).expect("utf8"),
|
||||||
"# feature"
|
"# feature"
|
||||||
@@ -763,6 +804,10 @@ mod tests {
|
|||||||
|
|
||||||
let snapshot = worktree_snapshot(dir).expect("snapshot");
|
let snapshot = worktree_snapshot(dir).expect("snapshot");
|
||||||
assert_eq!(snapshot.current_branch.as_deref(), Some(default.as_str()));
|
assert_eq!(snapshot.current_branch.as_deref(), Some(default.as_str()));
|
||||||
|
assert_eq!(
|
||||||
|
snapshot.head_commit.as_ref().expect("head commit").summary,
|
||||||
|
"initial"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
String::from_utf8(snapshot.readme.expect("readme")).expect("utf8"),
|
String::from_utf8(snapshot.readme.expect("readme")).expect("utf8"),
|
||||||
"# main"
|
"# main"
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ pub struct RepoDetailView {
|
|||||||
/// A clone/fetch is in flight.
|
/// A clone/fetch is in flight.
|
||||||
loading: bool,
|
loading: bool,
|
||||||
error: Option<SharedString>,
|
error: Option<SharedString>,
|
||||||
|
/// Commit HEAD currently points to, shown in the header button.
|
||||||
|
head_commit: Option<FileCommit>,
|
||||||
/// Branch selector (header): local branches, searchable.
|
/// Branch selector (header): local branches, searchable.
|
||||||
branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
|
branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
|
||||||
/// Tag selector (header): tags, searchable.
|
/// Tag selector (header): tags, searchable.
|
||||||
@@ -164,6 +166,7 @@ impl RepoDetailView {
|
|||||||
item_sizes: Rc::new(Vec::new()),
|
item_sizes: Rc::new(Vec::new()),
|
||||||
loading: true,
|
loading: true,
|
||||||
error: None,
|
error: None,
|
||||||
|
head_commit: None,
|
||||||
branch_select,
|
branch_select,
|
||||||
tag_select,
|
tag_select,
|
||||||
switching_ref: false,
|
switching_ref: false,
|
||||||
@@ -203,6 +206,7 @@ impl RepoDetailView {
|
|||||||
),
|
),
|
||||||
None => (Vec::new(), Vec::new(), None),
|
None => (Vec::new(), Vec::new(), None),
|
||||||
};
|
};
|
||||||
|
let head_commit = signed_git::head_commit(&repo).unwrap_or(None);
|
||||||
|
|
||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
entries,
|
entries,
|
||||||
@@ -212,6 +216,7 @@ impl RepoDetailView {
|
|||||||
branches,
|
branches,
|
||||||
tags,
|
tags,
|
||||||
current_branch,
|
current_branch,
|
||||||
|
head_commit,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,8 +233,10 @@ impl RepoDetailView {
|
|||||||
branches,
|
branches,
|
||||||
tags,
|
tags,
|
||||||
current_branch,
|
current_branch,
|
||||||
|
head_commit,
|
||||||
)) => {
|
)) => {
|
||||||
this.worktree = Some(worktree);
|
this.worktree = Some(worktree);
|
||||||
|
this.head_commit = head_commit;
|
||||||
this.tree_state.update(cx, |state, cx| {
|
this.tree_state.update(cx, |state, cx| {
|
||||||
state.set_items(build_tree_items(&entries), cx);
|
state.set_items(build_tree_items(&entries), cx);
|
||||||
});
|
});
|
||||||
@@ -259,7 +266,7 @@ impl RepoDetailView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((_, _, _, None, _, _, _)) => {
|
Ok((_, _, _, None, _, _, _, _)) => {
|
||||||
this.error = Some("Repository has no worktree".into());
|
this.error = Some("Repository has no worktree".into());
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@@ -589,6 +596,7 @@ impl RepoDetailView {
|
|||||||
this.switching_ref = false;
|
this.switching_ref = false;
|
||||||
match result {
|
match result {
|
||||||
Ok(snapshot) => {
|
Ok(snapshot) => {
|
||||||
|
this.head_commit = snapshot.head_commit;
|
||||||
// Rebuild the tree from scratch: entries of the
|
// Rebuild the tree from scratch: entries of the
|
||||||
// previous branch are gone, and with them the
|
// previous branch are gone, and with them the
|
||||||
// expansion state.
|
// expansion state.
|
||||||
@@ -619,6 +627,7 @@ impl RepoDetailView {
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
this.error = Some(error.to_string().into());
|
this.error = Some(error.to_string().into());
|
||||||
|
this.head_commit = None;
|
||||||
// The tree may show files that no longer exist.
|
// The tree may show files that no longer exist.
|
||||||
this.tree_state.update(cx, |state, cx| {
|
this.tree_state.update(cx, |state, cx| {
|
||||||
state.set_items(Vec::new(), cx);
|
state.set_items(Vec::new(), cx);
|
||||||
@@ -870,6 +879,34 @@ impl Render for RepoDetailView {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("enc")
|
||||||
|
.secondary()
|
||||||
|
.when_some(self.head_commit.as_ref(), |this, commit| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.child(SharedString::from(&commit.id)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.max_w(px(200.))
|
||||||
|
.overflow_hidden()
|
||||||
|
.text_ellipsis()
|
||||||
|
.whitespace_nowrap()
|
||||||
|
.text_xs()
|
||||||
|
.child(SharedString::from(&commit.summary)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.tooltip(
|
||||||
|
self.head_commit
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(SharedString::default, |commit| {
|
||||||
|
commit.summary.clone().into()
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user