feat: complete JjLib describe implementation

This commit is contained in:
2026-03-07 00:37:43 +01:00
parent bd20747bae
commit 1b66d7f86c
5 changed files with 124 additions and 23 deletions

View File

@@ -1,9 +1,9 @@
use std::path::Path;
use jj_lib::config::StackedConfig;
use jj_lib::repo::{Repo, StoreFactories};
use jj_lib::settings::UserSettings;
use jj_lib::workspace::{Workspace, default_working_copy_factories};
use jj_lib::repo::{Repo, StoreFactories};
use crate::error::Error;
use crate::jj::JjExecutor;
@@ -46,15 +46,82 @@ impl JjExecutor for JjLib {
let settings = UserSettings::from_config(config)?;
let store_factories = StoreFactories::default();
let wc_factories = default_working_copy_factories();
match Workspace::load(&settings, &std::env::current_dir()?, &store_factories, &wc_factories) {
Ok(_) => Ok(true),
Err(_) => Ok(false)
// Check if the directory exists first
if !self.working_dir.exists() {
return Ok(false);
}
// Try to load workspace from the working directory
// Walk up the directory tree until we find a repository or reach the root
let mut current_dir = self.working_dir.clone();
loop {
match Workspace::load(&settings, &current_dir, &store_factories, &wc_factories) {
Ok(_) => return Ok(true),
Err(_) => {
// Move up to parent directory
if !current_dir.pop() {
// Reached root directory
break;
}
}
}
}
Ok(false)
}
async fn describe(&self, _message: &str) -> Result<(), Error> {
// TODO: T018/T019 - Implement using jj-lib transactions
todo!("T018/T019: Implement describe() using jj-lib")
async fn describe(&self, message: &str) -> Result<(), Error> {
// Load the repository
let config = StackedConfig::with_defaults();
let settings = UserSettings::from_config(config)?;
let store_factories = StoreFactories::default();
let wc_factories = default_working_copy_factories();
let workspace = Workspace::load(
&settings,
&self.working_dir,
&store_factories,
&wc_factories,
)
.map_err(|_| Error::NotARepository)?;
let repo = workspace.repo_loader().load_at_head()?;
// Start a transaction
let mut tx = repo.start_transaction();
tx.set_tag("args".to_string(), "jj-cz describe".to_string());
// Get the current working copy commit (equivalent to @ revset)
let view = tx.repo().view();
let wc_commit_ids = view.wc_commit_ids();
if wc_commit_ids.is_empty() {
return Err(Error::JjOperation {
context: "No working copy commit found".to_string(),
});
}
// Get the first working copy commit (usually there's only one)
let wc_commit_id = wc_commit_ids.values().next().unwrap();
let wc_commit = tx.repo().store().get_commit(wc_commit_id)?;
// Rewrite the working copy commit with the new description
let commit_builder = tx
.repo_mut()
.rewrite_commit(&wc_commit)
.set_description(message);
// Write the modified commit
let _new_commit = commit_builder.write()?;
// Rebase descendants after the rewrite
tx.repo_mut().rebase_descendants()?;
// Finish the transaction
tx.commit("jj-cz: update commit description")?;
Ok(())
}
}
@@ -72,8 +139,7 @@ mod tests {
.output()?;
if !output.status.success() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
return Err(std::io::Error::other(
format!(
"jj git init failed: {}",
String::from_utf8_lossy(&output.stderr)
@@ -91,12 +157,8 @@ mod tests {
.output()?;
if !output.status.success() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"jj log failed: {}",
String::from_utf8_lossy(&output.stderr)
),
return Err(std::io::Error::other(
format!("jj log failed: {}", String::from_utf8_lossy(&output.stderr)),
));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
@@ -219,7 +281,11 @@ mod tests {
let test_message = "feat(scope): add new feature";
let result = jj_lib.describe(test_message).await;
assert!(result.is_ok(), "describe() should succeed, got {:?}", result);
assert!(
result.is_ok(),
"describe() should succeed, got {:?}",
result
);
// Verify the commit description was updated
let actual_description =
@@ -239,10 +305,14 @@ mod tests {
let jj_lib = JjLib::with_working_dir(temp_dir.path());
let multiline_message = "feat: add feature\n\nThis is the body of the commit.\nIt spans multiple lines.";
let multiline_message =
"feat: add feature\n\nThis is the body of the commit.\nIt spans multiple lines.";
let result = jj_lib.describe(multiline_message).await;
assert!(result.is_ok(), "describe() should succeed with multiline message");
assert!(
result.is_ok(),
"describe() should succeed with multiline message"
);
let actual_description = get_commit_description(temp_dir.path()).unwrap();
assert_eq!(actual_description, multiline_message);
@@ -264,7 +334,10 @@ mod tests {
// Then clear it with empty message
let result = jj_lib.describe("").await;
assert!(result.is_ok(), "describe() should succeed with empty message");
assert!(
result.is_ok(),
"describe() should succeed with empty message"
);
let actual_description = get_commit_description(temp_dir.path()).unwrap();
assert_eq!(actual_description, "", "Description should be cleared");
@@ -281,7 +354,10 @@ mod tests {
let special_message = "fix: handle \"quotes\" and 'apostrophes' & <brackets>";
let result = jj_lib.describe(special_message).await;
assert!(result.is_ok(), "describe() should handle special characters");
assert!(
result.is_ok(),
"describe() should handle special characters"
);
let actual_description = get_commit_description(temp_dir.path()).unwrap();
assert_eq!(actual_description, special_message);

View File

@@ -58,7 +58,8 @@ mod tests {
/// Check if is_repository() was called
fn was_is_repo_called(&self) -> bool {
self.is_repo_called.load(std::sync::atomic::Ordering::SeqCst)
self.is_repo_called
.load(std::sync::atomic::Ordering::SeqCst)
}
/// Get all messages passed to describe()