use std::path::Path;
use crate::ledger::dolt::{self, Dolt, BRANCH, REMOTE};
use crate::ledger::fetch::fetch_tip;
use crate::ledger::git::Git;
use crate::ledger::location::{dolt_db_path, state_root_from_env, Repo};
use crate::ledger::report_refresh;
use crate::ledger::store::dolt::{remote_tip, DoltStore};
use crate::ledger::tips::Tips;
use crate::ledger::transport::{self, Published, Transport};
const COMMIT_MESSAGE: &str = "Move the ledger to a Dolt file remote (transport file-remote)";
const TEMP_REMOTE: &str = "migrate-transport";
pub fn run(cwd: &Path) -> Result<(), String> {
Dolt::new(".").run(["version"])?;
dolt::ensure_identity()?;
let repo = Repo::discover(cwd)?;
let state_root = state_root_from_env()?;
let location = repo.location(&state_root)?;
let store = DoltStore::open(cwd)?;
if remote_tip(&repo.git, &location)?.is_none() {
return Err(format!(
"error: {} on {} holds no ledger, so there is nothing to migrate. A new ledger already uses a file remote",
location.ref_name(),
location.label()
));
}
let (outcome, version) = store.refresh_data(false)?;
let outcome = report_refresh(outcome);
let db = Dolt::new(dolt_db_path(&state_root, &repo.ensure_clone_id()?));
let dir = transport::remote_dir_beside(db.dir());
let on_file_remote = transport::store_url(db.dir())
.is_some_and(|url| Transport::of_url(&url) == Transport::FileRemote);
if on_file_remote {
eprintln!("note: this ledger already uses a file remote; nothing to migrate");
println!("migrated-transport:already");
return Ok(());
}
if version == dolt::LEGACY_LAYOUT_VERSION {
return Err("error: this ledger stores its documents in Dolt (version 1). Run tsk ledger migrate-docs first".to_string());
}
if !outcome.pending.is_empty() {
return Err(format!(
"error: {} holds {} commits that are not on {}'s {}.\n \
Run tsk ledger push first, then tsk ledger migrate-transport",
outcome.path.display(),
outcome.pending.len(),
location.label(),
location.ref_name()
));
}
let old_tip = fetch_tip(&repo.git, &location)?.ok_or_else(|| {
format!(
"error: {} on {} has no commit",
location.ref_name(),
location.label()
)
})?;
transport::sync_checkout(&repo, &dir, &old_tip)?;
let wt = Git::new(&dir);
wt.run(["rm", "-r", "-q", "--ignore-unmatch", "--", "."])?;
wt.run(["clean", "-fdxq"])?;
let _ = db.output(["remote", "remove", TEMP_REMOTE])?;
db.run([
"remote",
"add",
TEMP_REMOTE,
transport::file_url(&dir).as_str(),
])?;
let pushed = db.run(["push", TEMP_REMOTE, BRANCH]);
let _ = db.output(["remote", "remove", TEMP_REMOTE])?;
pushed?;
std::fs::write(
dir.join(transport::MARKER_FILE),
format!("{}\n", Transport::FileRemote.as_str()),
)
.and_then(|()| {
std::fs::write(
dir.join(transport::GITIGNORE_FILE),
transport::GITIGNORE_TEXT,
)
})
.map_err(|e| format!("error: could not write in {}: {}", dir.display(), e))?;
let commit = match transport::publish(&location, &dir, COMMIT_MESSAGE, Some(&old_tip))? {
Published::Pushed(commit) | Published::Unchanged(commit) => commit,
Published::Rejected(summary) => {
return Err(format!(
"error: {}'s {} moved while the ledger was converted: {}.\n \
Nothing changed on origin. Run tsk ledger fetch, then tsk ledger migrate-transport again",
location.label(),
location.ref_name(),
summary
));
}
};
transport::point_store_at_file_remote(&db, &dir, location.ref_name())?;
db.run(["fetch", REMOTE])?;
let remote_head = db.hash_of(&format!("{}/{}", REMOTE, BRANCH))?;
if remote_head != db.head_hash()? {
return Err(format!(
"error: the file remote holds {} but the ledger store is at {}. The ref now holds the file remote ({}); run tsk ledger status",
remote_head,
db.head_hash()?,
commit
));
}
if let Some(clone_dir) = db.dir().parent() {
Tips::new(clone_dir).set(location.remote(), location.ref_name(), Some(&commit));
}
eprintln!(
"note: {}'s {} now holds a Dolt file remote; other clones move to it on their next fetch",
location.label(),
location.ref_name()
);
println!("migrated-transport:file-remote");
println!("{}", commit);
Ok(())
}