feat: complete JjLib describe implementation
This commit is contained in:
@@ -8,4 +8,4 @@ mod description;
|
||||
pub use description::{Description, DescriptionError};
|
||||
|
||||
mod message;
|
||||
pub use message::{CommitMessageError, ConventionalCommit};
|
||||
pub use message::CommitMessageError;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use lazy_regex::regex_find;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
|
||||
24
src/error.rs
24
src/error.rs
@@ -56,3 +56,27 @@ impl From<jj_lib::config::ConfigGetError> for Error {
|
||||
Self::FailedReadingConfig
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jj_lib::repo::RepoLoaderError> for Error {
|
||||
fn from(error: jj_lib::repo::RepoLoaderError) -> Self {
|
||||
Self::JjOperation {
|
||||
context: format!("Failed to load repository: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jj_lib::backend::BackendError> for Error {
|
||||
fn from(error: jj_lib::backend::BackendError) -> Self {
|
||||
Self::JjOperation {
|
||||
context: format!("Backend operation failed: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jj_lib::transaction::TransactionCommitError> for Error {
|
||||
fn from(error: jj_lib::transaction::TransactionCommitError) -> Self {
|
||||
Self::JjOperation {
|
||||
context: format!("Transaction commit failed: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¤t_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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user