@@ -192,6 +192,23 @@ can also use the following:
|
|||||||
$ grm wt clean
|
$ 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
|
### Manual access
|
||||||
|
|
||||||
GRM isn't doing any magic, it's just git under the hood. If you need to have access
|
GRM isn't doing any magic, it's just git under the hood. If you need to have access
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ pub enum WorktreeAction {
|
|||||||
Delete(WorktreeDeleteArgs),
|
Delete(WorktreeDeleteArgs),
|
||||||
#[clap(about = "Show state of existing worktrees")]
|
#[clap(about = "Show state of existing worktrees")]
|
||||||
Status(WorktreeStatusArgs),
|
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")]
|
#[clap(about = "Clean all worktrees that do not contain uncommited/unpushed changes")]
|
||||||
Clean(WorktreeCleanArgs),
|
Clean(WorktreeCleanArgs),
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,9 @@ pub struct WorktreeDeleteArgs {
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct WorktreeStatusArgs {}
|
pub struct WorktreeStatusArgs {}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct WorktreeConvertArgs {}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct WorktreeCleanArgs {}
|
pub struct WorktreeCleanArgs {}
|
||||||
|
|
||||||
|
|||||||
124
src/lib.rs
124
src/lib.rs
@@ -20,6 +20,8 @@ use repo::{
|
|||||||
const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
|
const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
|
||||||
const BRANCH_NAMESPACE_SEPARATOR: &str = "/";
|
const BRANCH_NAMESPACE_SEPARATOR: &str = "/";
|
||||||
|
|
||||||
|
const GIT_CONFIG_BARE_KEY: &str = "core.bare";
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -868,28 +870,33 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let repo = match open_repo(&dir, true) {
|
fn get_repo(dir: &Path) -> git2::Repository {
|
||||||
Ok(r) => r,
|
match open_repo(dir, true) {
|
||||||
Err(e) => {
|
Ok(r) => r,
|
||||||
match e.kind {
|
Err(e) => {
|
||||||
RepoErrorKind::NotFound => {
|
match e.kind {
|
||||||
print_error("Current directory does not contain a worktree setup")
|
RepoErrorKind::NotFound => {
|
||||||
|
print_error("Current directory does not contain a worktree setup")
|
||||||
|
}
|
||||||
|
_ => print_error(&format!("Error opening repo: {}", e)),
|
||||||
}
|
}
|
||||||
_ => print_error(&format!("Error opening repo: {}", e)),
|
process::exit(1);
|
||||||
}
|
}
|
||||||
process::exit(1);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let worktrees = repo
|
fn get_worktrees(repo: &git2::Repository) -> Vec<String> {
|
||||||
.worktrees()
|
repo.worktrees()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.unwrap().to_string())
|
.map(|e| e.unwrap().to_string())
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
cmd::WorktreeAction::Add(action_args) => {
|
cmd::WorktreeAction::Add(action_args) => {
|
||||||
|
let repo = get_repo(&dir);
|
||||||
|
let worktrees = get_worktrees(&repo);
|
||||||
if worktrees.contains(&action_args.name) {
|
if worktrees.contains(&action_args.name) {
|
||||||
print_error("Worktree already exists");
|
print_error("Worktree already exists");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
@@ -1014,6 +1021,7 @@ pub fn run() {
|
|||||||
|
|
||||||
cmd::WorktreeAction::Delete(action_args) => {
|
cmd::WorktreeAction::Delete(action_args) => {
|
||||||
let worktree_dir = dir.join(&action_args.name);
|
let worktree_dir = dir.join(&action_args.name);
|
||||||
|
let repo = get_repo(&dir);
|
||||||
|
|
||||||
match remove_worktree(
|
match remove_worktree(
|
||||||
&action_args.name,
|
&action_args.name,
|
||||||
@@ -1040,6 +1048,8 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::WorktreeAction::Status(_args) => {
|
cmd::WorktreeAction::Status(_args) => {
|
||||||
|
let repo = get_repo(&dir);
|
||||||
|
let worktrees = get_worktrees(&repo);
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
add_worktree_table_header(&mut table);
|
add_worktree_table_header(&mut table);
|
||||||
for worktree in &worktrees {
|
for worktree in &worktrees {
|
||||||
@@ -1081,7 +1091,91 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
println!("{}", table);
|
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) => {
|
cmd::WorktreeAction::Clean(_args) => {
|
||||||
|
let repo = get_repo(&dir);
|
||||||
|
let worktrees = get_worktrees(&repo);
|
||||||
for worktree in &worktrees {
|
for worktree in &worktrees {
|
||||||
let repo_dir = &dir.join(&worktree);
|
let repo_dir = &dir.join(&worktree);
|
||||||
if repo_dir.exists() {
|
if repo_dir.exists() {
|
||||||
|
|||||||
Reference in New Issue
Block a user