use anyhow::Result;
use objects::object::State;
use refs::{Head, RefExpectation, RefUpdate};
use repo::Repository;
use serde::Serialize;
use super::snapshot::{ensure_current_state, resolve_attribution};
use crate::{
cli::{Cli, should_output_json},
config::UserConfig,
};
#[derive(Serialize)]
struct ForkOutput {
change_id: String,
content_hash: String,
thread: Option<String>,
from_state: String,
message: String,
}
pub fn cmd_fork(cli: &Cli, name: Option<String>, from: Option<String>) -> Result<()> {
let repo = Repository::open(cli.repo.as_ref().unwrap_or(&std::env::current_dir()?))?;
let source_state = if let Some(ref state_spec) = from {
let change_id = repo
.resolve_state(state_spec)?
.ok_or_else(|| anyhow::anyhow!("State not found: {}", state_spec))?;
repo.store()
.get_state(&change_id)?
.ok_or_else(|| anyhow::anyhow!("State not found: {}", state_spec))?
} else {
let change_id = ensure_current_state(
&repo,
&UserConfig::load_default().unwrap_or_default(),
Some("Bootstrap git-overlay before forking".to_string()),
)?;
repo.store()
.get_state(&change_id)?
.ok_or_else(|| anyhow::anyhow!("Current state not found"))?
};
let user_config = UserConfig::load_default()?;
let attribution = resolve_attribution(&repo, &user_config)?;
let mut new_state =
State::new_fork_of(source_state.tree, vec![source_state.change_id], attribution);
if let Some(ref intent) = source_state.intent {
new_state = new_state.with_intent(format!("Fork: {}", intent));
} else {
new_state = new_state.with_intent(format!("Fork from {}", source_state.change_id.short()));
}
repo.store().put_state(&new_state)?;
if let Some(ref track_name) = name {
let updates = vec![
RefUpdate::Thread {
name: track_name.clone(),
expected: RefExpectation::Missing,
new: Some(new_state.change_id),
},
RefUpdate::Head {
expected: RefExpectation::Any,
new: Head::Attached {
thread: track_name.clone(),
},
},
];
repo.refs().update_refs(&updates)?;
} else {
repo.refs().write_head(&Head::Detached {
state: new_state.change_id,
})?;
}
repo.oplog()
.record_fork(&new_state.change_id, &source_state.change_id)?;
let output = ForkOutput {
change_id: new_state.change_id.short(),
content_hash: new_state.compute_hash().short(),
thread: name.clone(),
from_state: source_state.change_id.short(),
message: if let Some(ref track_name) = name {
format!(
"Created fork {} on thread '{}' from {}",
new_state.change_id.short(),
track_name,
source_state.change_id.short()
)
} else {
format!(
"Created fork {} from {}",
new_state.change_id.short(),
source_state.change_id.short()
)
},
};
render_fork(&output, should_output_json(cli, Some(repo.config())))
}
fn render_fork(output: &ForkOutput, json: bool) -> Result<()> {
if json {
println!("{}", serde_json::to_string(output)?);
} else {
println!("{}", output.message);
}
Ok(())
}