Add subcommand that converts existing repository

Close #6
This commit is contained in:
2021-11-26 16:13:17 +01:00
parent b6c06e29a4
commit ff32759058
3 changed files with 131 additions and 15 deletions

View File

@@ -192,6 +192,23 @@ can also use the following:
$ grm wt clean
```
### Converting an existing repository
It is possible to convert an existing directory to a worktree setup, using `grm
wt convert`. This command has to be run in the root of the repository you want
to convert:
```
grm wt convert
[✔] Conversion successful
```
This command will refuse to run if you have any changes in your repository.
Commit them and try again!
Afterwards, the directory is empty, as there are no worktrees checked out yet.
Now you can use the usual commands to set up worktrees.
### Manual access
GRM isn't doing any magic, it's just git under the hood. If you need to have access

View File

@@ -83,6 +83,8 @@ pub enum WorktreeAction {
Delete(WorktreeDeleteArgs),
#[clap(about = "Show state of existing worktrees")]
Status(WorktreeStatusArgs),
#[clap(about = "Convert a normal repository to a worktree setup")]
Convert(WorktreeConvertArgs),
#[clap(about = "Clean all worktrees that do not contain uncommited/unpushed changes")]
Clean(WorktreeCleanArgs),
}
@@ -116,6 +118,9 @@ pub struct WorktreeDeleteArgs {
#[derive(Parser)]
pub struct WorktreeStatusArgs {}
#[derive(Parser)]
pub struct WorktreeConvertArgs {}
#[derive(Parser)]
pub struct WorktreeCleanArgs {}

View File

@@ -20,6 +20,8 @@ use repo::{
const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
const BRANCH_NAMESPACE_SEPARATOR: &str = "/";
const GIT_CONFIG_BARE_KEY: &str = "core.bare";
#[cfg(test)]
mod tests {
use super::*;
@@ -868,7 +870,8 @@ pub fn run() {
}
};
let repo = match open_repo(&dir, true) {
fn get_repo(dir: &Path) -> git2::Repository {
match open_repo(dir, true) {
Ok(r) => r,
Err(e) => {
match e.kind {
@@ -879,17 +882,21 @@ pub fn run() {
}
process::exit(1);
}
};
}
}
let worktrees = repo
.worktrees()
fn get_worktrees(repo: &git2::Repository) -> Vec<String> {
repo.worktrees()
.unwrap()
.iter()
.map(|e| e.unwrap().to_string())
.collect::<Vec<String>>();
.collect::<Vec<String>>()
}
match args.action {
cmd::WorktreeAction::Add(action_args) => {
let repo = get_repo(&dir);
let worktrees = get_worktrees(&repo);
if worktrees.contains(&action_args.name) {
print_error("Worktree already exists");
process::exit(1);
@@ -1014,6 +1021,7 @@ pub fn run() {
cmd::WorktreeAction::Delete(action_args) => {
let worktree_dir = dir.join(&action_args.name);
let repo = get_repo(&dir);
match remove_worktree(
&action_args.name,
@@ -1040,6 +1048,8 @@ pub fn run() {
}
}
cmd::WorktreeAction::Status(_args) => {
let repo = get_repo(&dir);
let worktrees = get_worktrees(&repo);
let mut table = Table::new();
add_worktree_table_header(&mut table);
for worktree in &worktrees {
@@ -1081,7 +1091,91 @@ pub fn run() {
}
println!("{}", table);
}
cmd::WorktreeAction::Convert(_args) => {
// Converting works like this:
// * Check whether there are uncommitted/unpushed changes
// * Move the contents of .git dir to the worktree directory
// * Remove all files
// * Set `core.bare` to `true`
let repo = open_repo(&dir, false).unwrap_or_else(|error| {
if error.kind == RepoErrorKind::NotFound {
print_error("Directory does not contain a git repository");
} else {
print_error(&format!("Opening repository failed: {}", error));
}
process::exit(1);
});
let status = get_repo_status(&repo, false);
if status.changes.unwrap().is_some() {
print_error("Changes found in repository, refusing to convert");
}
if let Err(error) = std::fs::rename(".git", GIT_MAIN_WORKTREE_DIRECTORY) {
print_error(&format!("Error moving .git directory: {}", error));
}
for entry in match std::fs::read_dir(&dir) {
Ok(iterator) => iterator,
Err(error) => {
print_error(&format!("Opening directory failed: {}", error));
process::exit(1);
}
} {
match entry {
Ok(entry) => {
let path = entry.path();
// The path will ALWAYS have a file component
if path.file_name().unwrap() == GIT_MAIN_WORKTREE_DIRECTORY {
continue;
}
if path.is_file() || path.is_symlink() {
if let Err(error) = std::fs::remove_file(&path) {
print_error(&format!("Failed removing {}", error));
process::exit(1);
}
} else if let Err(error) = std::fs::remove_dir_all(&path) {
print_error(&format!("Failed removing {}", error));
process::exit(1);
}
}
Err(error) => {
print_error(&format!("Error getting directory entry: {}", error));
process::exit(1);
}
}
}
let worktree_repo = open_repo(&dir, true).unwrap_or_else(|error| {
print_error(&format!(
"Opening newly converted repository failed: {}",
error
));
process::exit(1);
});
let mut config = worktree_repo.config().unwrap_or_else(|error| {
print_error(&format!(
"Opening getting repository configuration: {}",
error
));
process::exit(1);
});
config
.set_bool(GIT_CONFIG_BARE_KEY, true)
.unwrap_or_else(|error| {
print_error(&format!(
"Error setting {}: {}",
GIT_CONFIG_BARE_KEY, error
));
process::exit(1);
});
}
cmd::WorktreeAction::Clean(_args) => {
let repo = get_repo(&dir);
let worktrees = get_worktrees(&repo);
for worktree in &worktrees {
let repo_dir = &dir.join(&worktree);
if repo_dir.exists() {