Add checkout command
git checkout
simply instantiates a commit in the worktree. We’re going to oversimplify the actual git command to make our implementation clear and understandable. We’re also going to add a few safeguards. Here’s how our version of checkout will work:
It will take two arguments: a commit, and a directory. Git checkout only needs a commit.
It will then instantiate the tree in the directory, if and only if the directory is empty.
Update cli module
Checkout {
commit: Sha1,
path: String,
},
}
.subcommand(
ClapCommand::new("checkout")
.about("Checkout a commit inside a directory")
.arg(
Arg::new("commit")
.value_name("COMMIT")
.help("The commit or tree to checkout"),
)
.arg(
Arg::new("path")
.value_name("PATH")
.help("An EMPTY directory to checkout on"),
),
)
} else if let Some(subcommand) = matches.subcommand_matches("checkout") {
let commit: String = subcommand.get_one::<String>("commit").unwrap().clone();
let path = subcommand.get_one::<String>("path").unwrap().clone();
Ok(Command::Checkout { commit, path })
} else {
Update main
Command::Checkout { commit, path } => cmd_checkout(commit, PathBuf::from(path))?,
Add cmd_checkout
fn cmd_checkout(commit: String, path: PathBuf) -> Result<()> {
let current_directory = std::env::current_dir()?;
let repo = GitRepository::find(¤t_directory)?;
// Read the object
let object = {
let object = repo.read_object(&commit)?;
if let git_object::GitObject::Commit(commit) = object {
repo.read_object(
commit
.get_value("tree")
.ok_or(anyhow::anyhow!("No tree entry found in the object"))?,
)?
} else {
object
}
};
// Create the path if doesn't exist
if path.exists() {
if path.is_file() {
return Err(anyhow::anyhow!("{} not a directory!", path.display()));
} else if path.is_dir() && path.read_dir()?.next().is_some() {
return Err(anyhow::anyhow!("{} not a empty!", path.display()));
}
} else {
std::fs::create_dir_all(&path)?;
}
// checkout the tree
if let git_object::GitObject::Tree(tree) = object {
tree_checkout(&repo, tree, path)?;
}
Ok(())
}
Add tree_checkout function
fn tree_checkout(repo: &GitRepository, tree: git_object::Tree, base_path: PathBuf) -> Result<()> {
for leaf in tree.iter() {
match leaf.get_type() {
git_object::mode::Type::Tree => {
// If it's a tree, create the directory, and read the tree recursively.
std::fs::create_dir(base_path.join(&leaf.path))?;
let object = repo.read_object(&leaf.hash)?;
if let git_object::GitObject::Tree(tree) = object {
tree_checkout(&repo, tree, base_path.join(&leaf.path))?;
} else {
return Err(anyhow::anyhow!("Invalid tree object"));
}
}
// Otherwise, write the file
git_object::mode::Type::RegularFile => {
let object = repo.read_object(&leaf.hash)?;
if let git_object::GitObject::Blob(blob) = object {
std::fs::write(base_path.join(&leaf.path), blob.serialize())?;
} else {
return Err(anyhow::anyhow!("Invalid blob object"));
};
}
git_object::mode::Type::SymbolicLink => todo!(),
git_object::mode::Type::Submodule => todo!(),
};
}
Ok(())
}
Last updated