Restructure into smaller modules
This commit is contained in:
0
BRANCH_NAMESPACE_SEPARATOR
Normal file
0
BRANCH_NAMESPACE_SEPARATOR
Normal file
36
src/auth.rs
Normal file
36
src/auth.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use std::process;
|
||||||
|
|
||||||
|
pub fn get_token_from_command(command: &str) -> Result<String, String> {
|
||||||
|
let output = process::Command::new("/usr/bin/env")
|
||||||
|
.arg("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(command)
|
||||||
|
.output()
|
||||||
|
.map_err(|error| format!("Failed to run token-command: {}", error))?;
|
||||||
|
|
||||||
|
let stderr = String::from_utf8(output.stderr).map_err(|error| error.to_string())?;
|
||||||
|
let stdout = String::from_utf8(output.stdout).map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
if !stderr.is_empty() {
|
||||||
|
return Err(format!("Token command failed: {}", stderr));
|
||||||
|
} else {
|
||||||
|
return Err(String::from("Token command failed."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stderr.is_empty() {
|
||||||
|
return Err(format!("Token command produced stderr: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if stdout.is_empty() {
|
||||||
|
return Err(String::from("Token command did not produce output"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = stdout
|
||||||
|
.split('\n')
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| String::from("Output did not contain any newline"))?;
|
||||||
|
|
||||||
|
Ok(token.to_string())
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use crate::output::*;
|
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{get_token_from_command, path_as_string, Remote, Repo, Tree};
|
use super::auth;
|
||||||
|
use super::output::*;
|
||||||
|
use super::path;
|
||||||
|
use super::provider;
|
||||||
|
use super::provider::Filter;
|
||||||
|
use super::provider::Provider;
|
||||||
|
use super::repo;
|
||||||
|
use super::tree;
|
||||||
|
|
||||||
use crate::provider;
|
pub type RemoteProvider = provider::RemoteProvider;
|
||||||
use crate::provider::Filter;
|
pub type RemoteType = repo::RemoteType;
|
||||||
use crate::provider::Provider;
|
|
||||||
|
|
||||||
pub type RemoteProvider = crate::provider::RemoteProvider;
|
|
||||||
pub type RemoteType = crate::repo::RemoteType;
|
|
||||||
|
|
||||||
fn worktree_setup_default() -> bool {
|
fn worktree_setup_default() -> bool {
|
||||||
false
|
false
|
||||||
@@ -64,7 +65,7 @@ pub struct RemoteConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteConfig {
|
impl RemoteConfig {
|
||||||
pub fn from_remote(remote: Remote) -> Self {
|
pub fn from_remote(remote: repo::Remote) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: remote.name,
|
name: remote.name,
|
||||||
url: remote.url,
|
url: remote.url,
|
||||||
@@ -72,8 +73,8 @@ impl RemoteConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_remote(self) -> Remote {
|
pub fn into_remote(self) -> repo::Remote {
|
||||||
Remote {
|
repo::Remote {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
url: self.url,
|
url: self.url,
|
||||||
remote_type: self.remote_type,
|
remote_type: self.remote_type,
|
||||||
@@ -93,7 +94,7 @@ pub struct RepoConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RepoConfig {
|
impl RepoConfig {
|
||||||
pub fn from_repo(repo: Repo) -> Self {
|
pub fn from_repo(repo: repo::Repo) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: repo.name,
|
name: repo.name,
|
||||||
worktree_setup: repo.worktree_setup,
|
worktree_setup: repo.worktree_setup,
|
||||||
@@ -103,14 +104,14 @@ impl RepoConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_repo(self) -> Repo {
|
pub fn into_repo(self) -> repo::Repo {
|
||||||
let (namespace, name) = if let Some((namespace, name)) = self.name.rsplit_once('/') {
|
let (namespace, name) = if let Some((namespace, name)) = self.name.rsplit_once('/') {
|
||||||
(Some(namespace.to_string()), name.to_string())
|
(Some(namespace.to_string()), name.to_string())
|
||||||
} else {
|
} else {
|
||||||
(None, self.name)
|
(None, self.name)
|
||||||
};
|
};
|
||||||
|
|
||||||
Repo {
|
repo::Repo {
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
worktree_setup: self.worktree_setup,
|
worktree_setup: self.worktree_setup,
|
||||||
@@ -133,7 +134,7 @@ impl ConfigTrees {
|
|||||||
ConfigTrees { trees: vec }
|
ConfigTrees { trees: vec }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_trees(vec: Vec<Tree>) -> Self {
|
pub fn from_trees(vec: Vec<tree::Tree>) -> Self {
|
||||||
ConfigTrees {
|
ConfigTrees {
|
||||||
trees: vec.into_iter().map(ConfigTree::from_tree).collect(),
|
trees: vec.into_iter().map(ConfigTree::from_tree).collect(),
|
||||||
}
|
}
|
||||||
@@ -157,7 +158,7 @@ impl Config {
|
|||||||
match self {
|
match self {
|
||||||
Config::ConfigTrees(config) => Ok(config.trees),
|
Config::ConfigTrees(config) => Ok(config.trees),
|
||||||
Config::ConfigProvider(config) => {
|
Config::ConfigProvider(config) => {
|
||||||
let token = match get_token_from_command(&config.token_command) {
|
let token = match auth::get_token_from_command(&config.token_command) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Getting token from command failed: {}", error));
|
print_error(&format!("Getting token from command failed: {}", error));
|
||||||
@@ -217,9 +218,9 @@ impl Config {
|
|||||||
.collect();
|
.collect();
|
||||||
let tree = ConfigTree {
|
let tree = ConfigTree {
|
||||||
root: if let Some(namespace) = namespace {
|
root: if let Some(namespace) = namespace {
|
||||||
path_as_string(&Path::new(&config.root).join(namespace))
|
path::path_as_string(&Path::new(&config.root).join(namespace))
|
||||||
} else {
|
} else {
|
||||||
path_as_string(Path::new(&config.root))
|
path::path_as_string(Path::new(&config.root))
|
||||||
},
|
},
|
||||||
repos: Some(repos),
|
repos: Some(repos),
|
||||||
};
|
};
|
||||||
@@ -236,7 +237,7 @@ impl Config {
|
|||||||
|
|
||||||
pub fn normalize(&mut self) {
|
pub fn normalize(&mut self) {
|
||||||
if let Config::ConfigTrees(config) = self {
|
if let Config::ConfigTrees(config) = self {
|
||||||
let home = super::env_home().display().to_string();
|
let home = path::env_home().display().to_string();
|
||||||
for tree in &mut config.trees_mut().iter_mut() {
|
for tree in &mut config.trees_mut().iter_mut() {
|
||||||
if tree.root.starts_with(&home) {
|
if tree.root.starts_with(&home) {
|
||||||
// The tilde is not handled differently, it's just a normal path component for `Path`.
|
// The tilde is not handled differently, it's just a normal path component for `Path`.
|
||||||
@@ -275,14 +276,14 @@ pub struct ConfigTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigTree {
|
impl ConfigTree {
|
||||||
pub fn from_repos(root: String, repos: Vec<Repo>) -> Self {
|
pub fn from_repos(root: String, repos: Vec<repo::Repo>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root,
|
root,
|
||||||
repos: Some(repos.into_iter().map(RepoConfig::from_repo).collect()),
|
repos: Some(repos.into_iter().map(RepoConfig::from_repo).collect()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_tree(tree: Tree) -> Self {
|
pub fn from_tree(tree: tree::Tree) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: tree.root,
|
root: tree.root,
|
||||||
repos: Some(tree.repos.into_iter().map(RepoConfig::from_repo).collect()),
|
repos: Some(tree.repos.into_iter().map(RepoConfig::from_repo).collect()),
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ pub struct Config {
|
|||||||
pub init_worktree: String,
|
pub init_worktree: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RemoteProvider = grm::provider::RemoteProvider;
|
pub type RemoteProvider = super::provider::RemoteProvider;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap()]
|
#[clap()]
|
||||||
|
|||||||
118
src/grm/main.rs
118
src/grm/main.rs
@@ -3,12 +3,17 @@ use std::process;
|
|||||||
|
|
||||||
mod cmd;
|
mod cmd;
|
||||||
|
|
||||||
|
use grm::auth;
|
||||||
use grm::config;
|
use grm::config;
|
||||||
|
use grm::find_in_tree;
|
||||||
use grm::output::*;
|
use grm::output::*;
|
||||||
use grm::path_as_string;
|
use grm::path;
|
||||||
use grm::provider;
|
use grm::provider;
|
||||||
use grm::provider::Provider;
|
use grm::provider::Provider;
|
||||||
use grm::repo;
|
use grm::repo;
|
||||||
|
use grm::table;
|
||||||
|
use grm::tree;
|
||||||
|
use grm::worktree;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let opts = cmd::parse();
|
let opts = cmd::parse();
|
||||||
@@ -24,7 +29,7 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match grm::sync_trees(config, args.init_worktree == "true") {
|
match tree::sync_trees(config, args.init_worktree == "true") {
|
||||||
Ok(success) => {
|
Ok(success) => {
|
||||||
if !success {
|
if !success {
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
@@ -37,7 +42,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::SyncAction::Remote(args) => {
|
cmd::SyncAction::Remote(args) => {
|
||||||
let token = match grm::get_token_from_command(&args.token_command) {
|
let token = match auth::get_token_from_command(&args.token_command) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Getting token from command failed: {}", error));
|
print_error(&format!("Getting token from command failed: {}", error));
|
||||||
@@ -45,18 +50,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = grm::provider::Filter::new(
|
let filter =
|
||||||
args.users,
|
provider::Filter::new(args.users, args.groups, args.owner, args.access);
|
||||||
args.groups,
|
|
||||||
args.owner,
|
|
||||||
args.access,
|
|
||||||
);
|
|
||||||
|
|
||||||
let worktree = args.worktree == "true";
|
let worktree = args.worktree == "true";
|
||||||
|
|
||||||
let repos = match args.provider {
|
let repos = match args.provider {
|
||||||
cmd::RemoteProvider::Github => {
|
cmd::RemoteProvider::Github => {
|
||||||
match grm::provider::Github::new(filter, token, args.api_url) {
|
match provider::Github::new(filter, token, args.api_url) {
|
||||||
Ok(provider) => provider,
|
Ok(provider) => provider,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error: {}", error));
|
print_error(&format!("Error: {}", error));
|
||||||
@@ -66,7 +67,7 @@ fn main() {
|
|||||||
.get_repos(worktree, args.force_ssh)
|
.get_repos(worktree, args.force_ssh)
|
||||||
}
|
}
|
||||||
cmd::RemoteProvider::Gitlab => {
|
cmd::RemoteProvider::Gitlab => {
|
||||||
match grm::provider::Gitlab::new(filter, token, args.api_url) {
|
match provider::Gitlab::new(filter, token, args.api_url) {
|
||||||
Ok(provider) => provider,
|
Ok(provider) => provider,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error: {}", error));
|
print_error(&format!("Error: {}", error));
|
||||||
@@ -83,9 +84,9 @@ fn main() {
|
|||||||
|
|
||||||
for (namespace, repolist) in repos {
|
for (namespace, repolist) in repos {
|
||||||
let root = if let Some(namespace) = namespace {
|
let root = if let Some(namespace) = namespace {
|
||||||
path_as_string(&Path::new(&args.root).join(namespace))
|
path::path_as_string(&Path::new(&args.root).join(namespace))
|
||||||
} else {
|
} else {
|
||||||
path_as_string(Path::new(&args.root))
|
path::path_as_string(Path::new(&args.root))
|
||||||
};
|
};
|
||||||
|
|
||||||
let tree = config::ConfigTree::from_repos(root, repolist);
|
let tree = config::ConfigTree::from_repos(root, repolist);
|
||||||
@@ -94,7 +95,7 @@ fn main() {
|
|||||||
|
|
||||||
let config = config::Config::from_trees(trees);
|
let config = config::Config::from_trees(trees);
|
||||||
|
|
||||||
match grm::sync_trees(config, args.init_worktree == "true") {
|
match tree::sync_trees(config, args.init_worktree == "true") {
|
||||||
Ok(success) => {
|
Ok(success) => {
|
||||||
if !success {
|
if !success {
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
@@ -122,7 +123,7 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match grm::table::get_status_table(config) {
|
match table::get_status_table(config) {
|
||||||
Ok((tables, errors)) => {
|
Ok((tables, errors)) => {
|
||||||
for table in tables {
|
for table in tables {
|
||||||
println!("{}", table);
|
println!("{}", table);
|
||||||
@@ -146,7 +147,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match grm::table::show_single_repo_status(&dir) {
|
match table::show_single_repo_status(&dir) {
|
||||||
Ok((table, warnings)) => {
|
Ok((table, warnings)) => {
|
||||||
println!("{}", table);
|
println!("{}", table);
|
||||||
for warning in warnings {
|
for warning in warnings {
|
||||||
@@ -184,7 +185,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (found_repos, warnings) = match grm::find_in_tree(&path) {
|
let (found_repos, warnings) = match find_in_tree(&path) {
|
||||||
Ok((repos, warnings)) => (repos, warnings),
|
Ok((repos, warnings)) => (repos, warnings),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&error);
|
print_error(&error);
|
||||||
@@ -192,7 +193,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let trees = grm::config::ConfigTrees::from_trees(vec![found_repos]);
|
let trees = config::ConfigTrees::from_trees(vec![found_repos]);
|
||||||
if trees.trees_ref().iter().all(|t| match &t.repos {
|
if trees.trees_ref().iter().all(|t| match &t.repos {
|
||||||
None => false,
|
None => false,
|
||||||
Some(r) => r.is_empty(),
|
Some(r) => r.is_empty(),
|
||||||
@@ -237,8 +238,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::FindAction::Config(args) => {
|
cmd::FindAction::Config(args) => {
|
||||||
let config: crate::config::ConfigProvider =
|
let config: config::ConfigProvider = match config::read_config(&args.config) {
|
||||||
match config::read_config(&args.config) {
|
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&error);
|
print_error(&error);
|
||||||
@@ -246,7 +246,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = match grm::get_token_from_command(&config.token_command) {
|
let token = match auth::get_token_from_command(&config.token_command) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Getting token from command failed: {}", error));
|
print_error(&format!("Getting token from command failed: {}", error));
|
||||||
@@ -254,7 +254,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let filters = config.filters.unwrap_or(grm::config::ConfigProviderFilter {
|
let filters = config.filters.unwrap_or(config::ConfigProviderFilter {
|
||||||
access: Some(false),
|
access: Some(false),
|
||||||
owner: Some(false),
|
owner: Some(false),
|
||||||
users: Some(vec![]),
|
users: Some(vec![]),
|
||||||
@@ -314,14 +314,14 @@ fn main() {
|
|||||||
for (namespace, namespace_repos) in repos {
|
for (namespace, namespace_repos) in repos {
|
||||||
let tree = config::ConfigTree {
|
let tree = config::ConfigTree {
|
||||||
root: if let Some(namespace) = namespace {
|
root: if let Some(namespace) = namespace {
|
||||||
path_as_string(&Path::new(&config.root).join(namespace))
|
path::path_as_string(&Path::new(&config.root).join(namespace))
|
||||||
} else {
|
} else {
|
||||||
path_as_string(Path::new(&config.root))
|
path::path_as_string(Path::new(&config.root))
|
||||||
},
|
},
|
||||||
repos: Some(
|
repos: Some(
|
||||||
namespace_repos
|
namespace_repos
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(grm::config::RepoConfig::from_repo)
|
.map(config::RepoConfig::from_repo)
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -360,7 +360,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::FindAction::Remote(args) => {
|
cmd::FindAction::Remote(args) => {
|
||||||
let token = match grm::get_token_from_command(&args.token_command) {
|
let token = match auth::get_token_from_command(&args.token_command) {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Getting token from command failed: {}", error));
|
print_error(&format!("Getting token from command failed: {}", error));
|
||||||
@@ -368,18 +368,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = grm::provider::Filter::new(
|
let filter =
|
||||||
args.users,
|
provider::Filter::new(args.users, args.groups, args.owner, args.access);
|
||||||
args.groups,
|
|
||||||
args.owner,
|
|
||||||
args.access,
|
|
||||||
);
|
|
||||||
|
|
||||||
let worktree = args.worktree == "true";
|
let worktree = args.worktree == "true";
|
||||||
|
|
||||||
let repos = match args.provider {
|
let repos = match args.provider {
|
||||||
cmd::RemoteProvider::Github => {
|
cmd::RemoteProvider::Github => {
|
||||||
match grm::provider::Github::new(filter, token, args.api_url) {
|
match provider::Github::new(filter, token, args.api_url) {
|
||||||
Ok(provider) => provider,
|
Ok(provider) => provider,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error: {}", error));
|
print_error(&format!("Error: {}", error));
|
||||||
@@ -389,7 +385,7 @@ fn main() {
|
|||||||
.get_repos(worktree, args.force_ssh)
|
.get_repos(worktree, args.force_ssh)
|
||||||
}
|
}
|
||||||
cmd::RemoteProvider::Gitlab => {
|
cmd::RemoteProvider::Gitlab => {
|
||||||
match grm::provider::Gitlab::new(filter, token, args.api_url) {
|
match provider::Gitlab::new(filter, token, args.api_url) {
|
||||||
Ok(provider) => provider,
|
Ok(provider) => provider,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error: {}", error));
|
print_error(&format!("Error: {}", error));
|
||||||
@@ -410,14 +406,14 @@ fn main() {
|
|||||||
for (namespace, repolist) in repos {
|
for (namespace, repolist) in repos {
|
||||||
let tree = config::ConfigTree {
|
let tree = config::ConfigTree {
|
||||||
root: if let Some(namespace) = namespace {
|
root: if let Some(namespace) = namespace {
|
||||||
path_as_string(&Path::new(&args.root).join(namespace))
|
path::path_as_string(&Path::new(&args.root).join(namespace))
|
||||||
} else {
|
} else {
|
||||||
path_as_string(Path::new(&args.root))
|
path::path_as_string(Path::new(&args.root))
|
||||||
},
|
},
|
||||||
repos: Some(
|
repos: Some(
|
||||||
repolist
|
repolist
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(grm::config::RepoConfig::from_repo)
|
.map(config::RepoConfig::from_repo)
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -503,7 +499,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match grm::add_worktree(&cwd, name, subdirectory, track, action_args.no_track) {
|
match worktree::add_worktree(
|
||||||
|
&cwd,
|
||||||
|
name,
|
||||||
|
subdirectory,
|
||||||
|
track,
|
||||||
|
action_args.no_track,
|
||||||
|
) {
|
||||||
Ok(_) => print_success(&format!("Worktree {} created", &action_args.name)),
|
Ok(_) => print_success(&format!("Worktree {} created", &action_args.name)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error creating worktree: {}", error));
|
print_error(&format!("Error creating worktree: {}", error));
|
||||||
@@ -525,7 +527,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
print_error(&format!("Error opening repository: {}", error));
|
print_error(&format!("Error opening repository: {}", error));
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
@@ -539,17 +541,17 @@ fn main() {
|
|||||||
Ok(_) => print_success(&format!("Worktree {} deleted", &action_args.name)),
|
Ok(_) => print_success(&format!("Worktree {} deleted", &action_args.name)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
match error {
|
match error {
|
||||||
grm::WorktreeRemoveFailureReason::Error(msg) => {
|
repo::WorktreeRemoveFailureReason::Error(msg) => {
|
||||||
print_error(&msg);
|
print_error(&msg);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
grm::WorktreeRemoveFailureReason::Changes(changes) => {
|
repo::WorktreeRemoveFailureReason::Changes(changes) => {
|
||||||
print_warning(&format!(
|
print_warning(&format!(
|
||||||
"Changes in worktree: {}. Refusing to delete",
|
"Changes in worktree: {}. Refusing to delete",
|
||||||
changes
|
changes
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
grm::WorktreeRemoveFailureReason::NotMerged(message) => {
|
repo::WorktreeRemoveFailureReason::NotMerged(message) => {
|
||||||
print_warning(&message);
|
print_warning(&message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,12 +560,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::WorktreeAction::Status(_args) => {
|
cmd::WorktreeAction::Status(_args) => {
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
print_error(&format!("Error opening repository: {}", error));
|
print_error(&format!("Error opening repository: {}", error));
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
match grm::table::get_worktree_status_table(&repo, &cwd) {
|
match table::get_worktree_status_table(&repo, &cwd) {
|
||||||
Ok((table, errors)) => {
|
Ok((table, errors)) => {
|
||||||
println!("{}", table);
|
println!("{}", table);
|
||||||
for error in errors {
|
for error in errors {
|
||||||
@@ -583,8 +585,8 @@ fn main() {
|
|||||||
// * Remove all files
|
// * Remove all files
|
||||||
// * Set `core.bare` to `true`
|
// * Set `core.bare` to `true`
|
||||||
|
|
||||||
let repo = grm::RepoHandle::open(&cwd, false).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, false).unwrap_or_else(|error| {
|
||||||
if error.kind == grm::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
print_error("Directory does not contain a git repository");
|
print_error("Directory does not contain a git repository");
|
||||||
} else {
|
} else {
|
||||||
print_error(&format!("Opening repository failed: {}", error));
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
@@ -611,8 +613,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::WorktreeAction::Clean(_args) => {
|
cmd::WorktreeAction::Clean(_args) => {
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
if error.kind == grm::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
print_error("Directory does not contain a git repository");
|
print_error("Directory does not contain a git repository");
|
||||||
} else {
|
} else {
|
||||||
print_error(&format!("Opening repository failed: {}", error));
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
@@ -645,8 +647,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd::WorktreeAction::Fetch(_args) => {
|
cmd::WorktreeAction::Fetch(_args) => {
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
if error.kind == grm::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
print_error("Directory does not contain a git repository");
|
print_error("Directory does not contain a git repository");
|
||||||
} else {
|
} else {
|
||||||
print_error(&format!("Opening repository failed: {}", error));
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
@@ -661,8 +663,8 @@ fn main() {
|
|||||||
print_success("Fetched from all remotes");
|
print_success("Fetched from all remotes");
|
||||||
}
|
}
|
||||||
cmd::WorktreeAction::Pull(args) => {
|
cmd::WorktreeAction::Pull(args) => {
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
if error.kind == grm::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
print_error("Directory does not contain a git repository");
|
print_error("Directory does not contain a git repository");
|
||||||
} else {
|
} else {
|
||||||
print_error(&format!("Opening repository failed: {}", error));
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
@@ -702,8 +704,8 @@ fn main() {
|
|||||||
print_error("There is no point in using --rebase without --pull");
|
print_error("There is no point in using --rebase without --pull");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
let repo = grm::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
let repo = repo::RepoHandle::open(&cwd, true).unwrap_or_else(|error| {
|
||||||
if error.kind == grm::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
print_error("Directory does not contain a git repository");
|
print_error("Directory does not contain a git repository");
|
||||||
} else {
|
} else {
|
||||||
print_error(&format!("Opening repository failed: {}", error));
|
print_error(&format!("Opening repository failed: {}", error));
|
||||||
@@ -718,12 +720,8 @@ fn main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let config =
|
let config = repo::read_worktree_root_config(&cwd).unwrap_or_else(|error| {
|
||||||
grm::repo::read_worktree_root_config(&cwd).unwrap_or_else(|error| {
|
print_error(&format!("Failed to read worktree configuration: {}", error));
|
||||||
print_error(&format!(
|
|
||||||
"Failed to read worktree configuration: {}",
|
|
||||||
error
|
|
||||||
));
|
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
595
src/lib.rs
595
src/lib.rs
@@ -1,423 +1,37 @@
|
|||||||
#![feature(io_error_more)]
|
#![feature(io_error_more)]
|
||||||
#![feature(const_option_ext)]
|
#![feature(const_option_ext)]
|
||||||
|
|
||||||
use std::fs;
|
use std::path::Path;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
|
pub mod path;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
pub mod table;
|
pub mod table;
|
||||||
|
pub mod tree;
|
||||||
|
pub mod worktree;
|
||||||
|
|
||||||
use config::Config;
|
|
||||||
use output::*;
|
|
||||||
|
|
||||||
use repo::{clone_repo, detect_remote_type, Remote, RemoteType};
|
|
||||||
|
|
||||||
pub use repo::{
|
|
||||||
RemoteTrackingStatus, Repo, RepoErrorKind, RepoHandle, WorktreeRemoveFailureReason,
|
|
||||||
};
|
|
||||||
|
|
||||||
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";
|
|
||||||
const GIT_CONFIG_PUSH_DEFAULT: &str = "push.default";
|
|
||||||
|
|
||||||
pub struct Tree {
|
|
||||||
root: String,
|
|
||||||
repos: Vec<Repo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn setup() {
|
|
||||||
std::env::set_var("HOME", "/home/test");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_expand_tilde() {
|
|
||||||
setup();
|
|
||||||
assert_eq!(
|
|
||||||
expand_path(Path::new("~/file")),
|
|
||||||
Path::new("/home/test/file")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_expand_invalid_tilde() {
|
|
||||||
setup();
|
|
||||||
assert_eq!(
|
|
||||||
expand_path(Path::new("/home/~/file")),
|
|
||||||
Path::new("/home/~/file")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_expand_home() {
|
|
||||||
setup();
|
|
||||||
assert_eq!(
|
|
||||||
expand_path(Path::new("$HOME/file")),
|
|
||||||
Path::new("/home/test/file")
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
expand_path(Path::new("${HOME}/file")),
|
|
||||||
Path::new("/home/test/file")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path_as_string(path: &Path) -> String {
|
|
||||||
path.to_path_buf().into_os_string().into_string().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn env_home() -> PathBuf {
|
|
||||||
match std::env::var("HOME") {
|
|
||||||
Ok(path) => Path::new(&path).to_path_buf(),
|
|
||||||
Err(e) => {
|
|
||||||
print_error(&format!("Unable to read HOME: {}", e));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_path(path: &Path) -> PathBuf {
|
|
||||||
fn home_dir() -> Option<PathBuf> {
|
|
||||||
Some(env_home())
|
|
||||||
}
|
|
||||||
|
|
||||||
let expanded_path = match shellexpand::full_with_context(
|
|
||||||
&path_as_string(path),
|
|
||||||
home_dir,
|
|
||||||
|name| -> Result<Option<String>, &'static str> {
|
|
||||||
match name {
|
|
||||||
"HOME" => Ok(Some(path_as_string(home_dir().unwrap().as_path()))),
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Ok(std::borrow::Cow::Borrowed(path)) => path.to_owned(),
|
|
||||||
Ok(std::borrow::Cow::Owned(path)) => path,
|
|
||||||
Err(e) => {
|
|
||||||
print_error(&format!("Unable to expand root: {}", e));
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Path::new(&expanded_path).to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_token_from_command(command: &str) -> Result<String, String> {
|
|
||||||
let output = std::process::Command::new("/usr/bin/env")
|
|
||||||
.arg("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(command)
|
|
||||||
.output()
|
|
||||||
.map_err(|error| format!("Failed to run token-command: {}", error))?;
|
|
||||||
|
|
||||||
let stderr = String::from_utf8(output.stderr).map_err(|error| error.to_string())?;
|
|
||||||
let stdout = String::from_utf8(output.stdout).map_err(|error| error.to_string())?;
|
|
||||||
|
|
||||||
if !output.status.success() {
|
|
||||||
if !stderr.is_empty() {
|
|
||||||
return Err(format!("Token command failed: {}", stderr));
|
|
||||||
} else {
|
|
||||||
return Err(String::from("Token command failed."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stderr.is_empty() {
|
|
||||||
return Err(format!("Token command produced stderr: {}", stderr));
|
|
||||||
}
|
|
||||||
|
|
||||||
if stdout.is_empty() {
|
|
||||||
return Err(String::from("Token command did not produce output"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = stdout
|
|
||||||
.split('\n')
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| String::from("Output did not contain any newline"))?;
|
|
||||||
|
|
||||||
Ok(token.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_repo(root_path: &Path, repo: &Repo, init_worktree: bool) -> Result<(), String> {
|
|
||||||
let repo_path = root_path.join(&repo.fullname());
|
|
||||||
let actual_git_directory = get_actual_git_directory(&repo_path, repo.worktree_setup);
|
|
||||||
|
|
||||||
let mut newly_created = false;
|
|
||||||
|
|
||||||
if repo_path.exists() {
|
|
||||||
if repo.worktree_setup && !actual_git_directory.exists() {
|
|
||||||
return Err(String::from(
|
|
||||||
"Repo already exists, but is not using a worktree setup",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
} else if matches!(&repo.remotes, None) || repo.remotes.as_ref().unwrap().is_empty() {
|
|
||||||
print_repo_action(
|
|
||||||
&repo.name,
|
|
||||||
"Repository does not have remotes configured, initializing new",
|
|
||||||
);
|
|
||||||
match RepoHandle::init(&repo_path, repo.worktree_setup) {
|
|
||||||
Ok(r) => {
|
|
||||||
print_repo_success(&repo.name, "Repository created");
|
|
||||||
Some(r)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Repository failed during init: {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let first = repo.remotes.as_ref().unwrap().first().unwrap();
|
|
||||||
|
|
||||||
match clone_repo(first, &repo_path, repo.worktree_setup) {
|
|
||||||
Ok(_) => {
|
|
||||||
print_repo_success(&repo.name, "Repository successfully cloned");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Repository failed during clone: {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
newly_created = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let repo_handle = match RepoHandle::open(&repo_path, repo.worktree_setup) {
|
|
||||||
Ok(repo) => repo,
|
|
||||||
Err(error) => {
|
|
||||||
if !repo.worktree_setup && RepoHandle::open(&repo_path, true).is_ok() {
|
|
||||||
return Err(String::from(
|
|
||||||
"Repo already exists, but is using a worktree setup",
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return Err(format!("Opening repository failed: {}", error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if newly_created && repo.worktree_setup && init_worktree {
|
|
||||||
match repo_handle.default_branch() {
|
|
||||||
Ok(branch) => {
|
|
||||||
add_worktree(&repo_path, &branch.name()?, None, None, false)?;
|
|
||||||
}
|
|
||||||
Err(_error) => print_repo_error(
|
|
||||||
&repo.name,
|
|
||||||
"Could not determine default branch, skipping worktree initializtion",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(remotes) = &repo.remotes {
|
|
||||||
let current_remotes: Vec<String> = repo_handle
|
|
||||||
.remotes()
|
|
||||||
.map_err(|error| format!("Repository failed during getting the remotes: {}", error))?;
|
|
||||||
|
|
||||||
for remote in remotes {
|
|
||||||
let current_remote = repo_handle.find_remote(&remote.name)?;
|
|
||||||
|
|
||||||
match current_remote {
|
|
||||||
Some(current_remote) => {
|
|
||||||
let current_url = current_remote.url();
|
|
||||||
|
|
||||||
if remote.url != current_url {
|
|
||||||
print_repo_action(
|
|
||||||
&repo.name,
|
|
||||||
&format!("Updating remote {} to \"{}\"", &remote.name, &remote.url),
|
|
||||||
);
|
|
||||||
if let Err(e) = repo_handle.remote_set_url(&remote.name, &remote.url) {
|
|
||||||
return Err(format!("Repository failed during setting of the remote URL for remote \"{}\": {}", &remote.name, e));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
print_repo_action(
|
|
||||||
&repo.name,
|
|
||||||
&format!(
|
|
||||||
"Setting up new remote \"{}\" to \"{}\"",
|
|
||||||
&remote.name, &remote.url
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if let Err(e) = repo_handle.new_remote(&remote.name, &remote.url) {
|
|
||||||
return Err(format!(
|
|
||||||
"Repository failed during setting the remotes: {}",
|
|
||||||
e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for current_remote in ¤t_remotes {
|
|
||||||
if !remotes.iter().any(|r| &r.name == current_remote) {
|
|
||||||
print_repo_action(
|
|
||||||
&repo.name,
|
|
||||||
&format!("Deleting remote \"{}\"", ¤t_remote,),
|
|
||||||
);
|
|
||||||
if let Err(e) = repo_handle.remote_delete(current_remote) {
|
|
||||||
return Err(format!(
|
|
||||||
"Repository failed during deleting remote \"{}\": {}",
|
|
||||||
¤t_remote, e
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_unmanaged_repos(
|
|
||||||
root_path: &Path,
|
|
||||||
managed_repos: &[Repo],
|
|
||||||
) -> Result<Vec<PathBuf>, String> {
|
|
||||||
let mut unmanaged_repos = Vec::new();
|
|
||||||
|
|
||||||
for repo_path in find_repo_paths(root_path)? {
|
|
||||||
if !managed_repos
|
|
||||||
.iter()
|
|
||||||
.any(|r| Path::new(root_path).join(r.fullname()) == repo_path)
|
|
||||||
{
|
|
||||||
unmanaged_repos.push(repo_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(unmanaged_repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_trees(config: Config, init_worktree: bool) -> Result<bool, String> {
|
|
||||||
let mut failures = false;
|
|
||||||
|
|
||||||
let mut unmanaged_repos_absolute_paths = vec![];
|
|
||||||
let mut managed_repos_absolute_paths = vec![];
|
|
||||||
|
|
||||||
let trees = config.trees()?;
|
|
||||||
|
|
||||||
for tree in trees {
|
|
||||||
let repos: Vec<Repo> = tree
|
|
||||||
.repos
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.map(|repo| repo.into_repo())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let root_path = expand_path(Path::new(&tree.root));
|
|
||||||
|
|
||||||
for repo in &repos {
|
|
||||||
managed_repos_absolute_paths.push(root_path.join(repo.fullname()));
|
|
||||||
match sync_repo(&root_path, repo, init_worktree) {
|
|
||||||
Ok(_) => print_repo_success(&repo.name, "OK"),
|
|
||||||
Err(error) => {
|
|
||||||
print_repo_error(&repo.name, &error);
|
|
||||||
failures = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match find_unmanaged_repos(&root_path, &repos) {
|
|
||||||
Ok(repos) => {
|
|
||||||
unmanaged_repos_absolute_paths.extend(repos);
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
print_error(&format!("Error getting unmanaged repos: {}", error));
|
|
||||||
failures = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for unmanaged_repo_absolute_path in &unmanaged_repos_absolute_paths {
|
|
||||||
if managed_repos_absolute_paths
|
|
||||||
.iter()
|
|
||||||
.any(|managed_repo_absolute_path| {
|
|
||||||
managed_repo_absolute_path == unmanaged_repo_absolute_path
|
|
||||||
})
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
print_warning(&format!(
|
|
||||||
"Found unmanaged repository: \"{}\"",
|
|
||||||
path_as_string(unmanaged_repo_absolute_path)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(!failures)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds repositories recursively, returning their path
|
|
||||||
fn find_repo_paths(path: &Path) -> Result<Vec<PathBuf>, String> {
|
|
||||||
let mut repos = Vec::new();
|
|
||||||
|
|
||||||
let git_dir = path.join(".git");
|
|
||||||
let git_worktree = path.join(GIT_MAIN_WORKTREE_DIRECTORY);
|
|
||||||
|
|
||||||
if git_dir.exists() || git_worktree.exists() {
|
|
||||||
repos.push(path.to_path_buf());
|
|
||||||
} else {
|
|
||||||
match fs::read_dir(path) {
|
|
||||||
Ok(contents) => {
|
|
||||||
for content in contents {
|
|
||||||
match content {
|
|
||||||
Ok(entry) => {
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_symlink() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if path.is_dir() {
|
|
||||||
match find_repo_paths(&path) {
|
|
||||||
Ok(ref mut r) => repos.append(r),
|
|
||||||
Err(error) => return Err(error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Error accessing directory: {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!(
|
|
||||||
"Failed to open \"{}\": {}",
|
|
||||||
&path.display(),
|
|
||||||
match e.kind() {
|
|
||||||
std::io::ErrorKind::NotADirectory =>
|
|
||||||
String::from("directory expected, but path is not a directory"),
|
|
||||||
std::io::ErrorKind::NotFound => String::from("not found"),
|
|
||||||
_ => format!("{:?}", e.kind()),
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_actual_git_directory(path: &Path, is_worktree: bool) -> PathBuf {
|
|
||||||
match is_worktree {
|
|
||||||
false => path.to_path_buf(),
|
|
||||||
true => path.join(GIT_MAIN_WORKTREE_DIRECTORY),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find all git repositories under root, recursively
|
/// Find all git repositories under root, recursively
|
||||||
///
|
///
|
||||||
/// The bool in the return value specifies whether there is a repository
|
/// The bool in the return value specifies whether there is a repository
|
||||||
/// in root itself.
|
/// in root itself.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, String> {
|
fn find_repos(root: &Path) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)>, String> {
|
||||||
let mut repos: Vec<Repo> = Vec::new();
|
let mut repos: Vec<repo::Repo> = Vec::new();
|
||||||
let mut repo_in_root = false;
|
let mut repo_in_root = false;
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
for path in find_repo_paths(root)? {
|
for path in tree::find_repo_paths(root)? {
|
||||||
let is_worktree = RepoHandle::detect_worktree(&path);
|
let is_worktree = repo::RepoHandle::detect_worktree(&path);
|
||||||
if path == root {
|
if path == root {
|
||||||
repo_in_root = true;
|
repo_in_root = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
match RepoHandle::open(&path, is_worktree) {
|
match repo::RepoHandle::open(&path, is_worktree) {
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"Error opening repo {}{}: {}",
|
"Error opening repo {}{}: {}",
|
||||||
@@ -436,32 +50,32 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, Str
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"{}: Error getting remotes: {}",
|
"{}: Error getting remotes: {}",
|
||||||
&path_as_string(&path),
|
&path::path_as_string(&path),
|
||||||
error
|
error
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut results: Vec<Remote> = Vec::new();
|
let mut results: Vec<repo::Remote> = Vec::new();
|
||||||
for remote_name in remotes.iter() {
|
for remote_name in remotes.iter() {
|
||||||
match repo.find_remote(remote_name)? {
|
match repo.find_remote(remote_name)? {
|
||||||
Some(remote) => {
|
Some(remote) => {
|
||||||
let name = remote.name();
|
let name = remote.name();
|
||||||
let url = remote.url();
|
let url = remote.url();
|
||||||
let remote_type = match detect_remote_type(&url) {
|
let remote_type = match repo::detect_remote_type(&url) {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"{}: Could not detect remote type of \"{}\"",
|
"{}: Could not detect remote type of \"{}\"",
|
||||||
&path_as_string(&path),
|
&path::path_as_string(&path),
|
||||||
&url
|
&url
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
results.push(Remote {
|
results.push(repo::Remote {
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
remote_type,
|
remote_type,
|
||||||
@@ -470,7 +84,7 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, Str
|
|||||||
None => {
|
None => {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"{}: Remote {} not found",
|
"{}: Remote {} not found",
|
||||||
&path_as_string(&path),
|
&path::path_as_string(&path),
|
||||||
remote_name
|
remote_name
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@@ -483,7 +97,9 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, Str
|
|||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
match &root.parent() {
|
match &root.parent() {
|
||||||
Some(parent) => path_as_string(path.strip_prefix(parent).unwrap()),
|
Some(parent) => {
|
||||||
|
path::path_as_string(path.strip_prefix(parent).unwrap())
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
warnings.push(String::from("Getting name of the search root failed. Do you have a git repository in \"/\"?"));
|
warnings.push(String::from("Getting name of the search root failed. Do you have a git repository in \"/\"?"));
|
||||||
continue;
|
continue;
|
||||||
@@ -495,15 +111,15 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, Str
|
|||||||
let namespace = name.parent().unwrap();
|
let namespace = name.parent().unwrap();
|
||||||
(
|
(
|
||||||
if namespace != Path::new("") {
|
if namespace != Path::new("") {
|
||||||
Some(path_as_string(namespace).to_string())
|
Some(path::path_as_string(namespace).to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
path_as_string(name),
|
path::path_as_string(name),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
repos.push(Repo {
|
repos.push(repo::Repo {
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
remotes: Some(remotes),
|
remotes: Some(remotes),
|
||||||
@@ -515,10 +131,10 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<Repo>, Vec<String>, bool)>, Str
|
|||||||
Ok(Some((repos, warnings, repo_in_root)))
|
Ok(Some((repos, warnings, repo_in_root)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_in_tree(path: &Path) -> Result<(Tree, Vec<String>), String> {
|
pub fn find_in_tree(path: &Path) -> Result<(tree::Tree, Vec<String>), String> {
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
let (repos, repo_in_root): (Vec<Repo>, bool) = match find_repos(path)? {
|
let (repos, repo_in_root): (Vec<repo::Repo>, bool) = match find_repos(path)? {
|
||||||
Some((vec, mut repo_warnings, repo_in_root)) => {
|
Some((vec, mut repo_warnings, repo_in_root)) => {
|
||||||
warnings.append(&mut repo_warnings);
|
warnings.append(&mut repo_warnings);
|
||||||
(vec, repo_in_root)
|
(vec, repo_in_root)
|
||||||
@@ -539,171 +155,10 @@ pub fn find_in_tree(path: &Path) -> Result<(Tree, Vec<String>), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Tree {
|
tree::Tree {
|
||||||
root: root.into_os_string().into_string().unwrap(),
|
root: root.into_os_string().into_string().unwrap(),
|
||||||
repos,
|
repos,
|
||||||
},
|
},
|
||||||
warnings,
|
warnings,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_worktree(
|
|
||||||
directory: &Path,
|
|
||||||
name: &str,
|
|
||||||
subdirectory: Option<&Path>,
|
|
||||||
track: Option<(&str, &str)>,
|
|
||||||
no_track: bool,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let repo = RepoHandle::open(directory, true).map_err(|error| match error.kind {
|
|
||||||
RepoErrorKind::NotFound => {
|
|
||||||
String::from("Current directory does not contain a worktree setup")
|
|
||||||
}
|
|
||||||
_ => format!("Error opening repo: {}", error),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let config = repo::read_worktree_root_config(directory)?;
|
|
||||||
|
|
||||||
if repo.find_worktree(name).is_ok() {
|
|
||||||
return Err(format!("Worktree {} already exists", &name));
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = match subdirectory {
|
|
||||||
Some(dir) => directory.join(dir).join(name),
|
|
||||||
None => directory.join(Path::new(name)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut remote_branch_exists = false;
|
|
||||||
|
|
||||||
let default_checkout = || repo.default_branch()?.to_commit();
|
|
||||||
|
|
||||||
let checkout_commit;
|
|
||||||
if no_track {
|
|
||||||
checkout_commit = default_checkout()?;
|
|
||||||
} else {
|
|
||||||
match track {
|
|
||||||
Some((remote_name, remote_branch_name)) => {
|
|
||||||
let remote_branch = repo.find_remote_branch(remote_name, remote_branch_name);
|
|
||||||
match remote_branch {
|
|
||||||
Ok(branch) => {
|
|
||||||
remote_branch_exists = true;
|
|
||||||
checkout_commit = branch.to_commit()?;
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
remote_branch_exists = false;
|
|
||||||
checkout_commit = default_checkout()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => match &config {
|
|
||||||
None => checkout_commit = default_checkout()?,
|
|
||||||
Some(config) => match &config.track {
|
|
||||||
None => checkout_commit = default_checkout()?,
|
|
||||||
Some(track_config) => {
|
|
||||||
if track_config.default {
|
|
||||||
let remote_branch =
|
|
||||||
repo.find_remote_branch(&track_config.default_remote, name);
|
|
||||||
match remote_branch {
|
|
||||||
Ok(branch) => {
|
|
||||||
remote_branch_exists = true;
|
|
||||||
checkout_commit = branch.to_commit()?;
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
checkout_commit = default_checkout()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
checkout_commit = default_checkout()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut target_branch = match repo.find_local_branch(name) {
|
|
||||||
Ok(branchref) => branchref,
|
|
||||||
Err(_) => repo.create_branch(name, &checkout_commit)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn push(
|
|
||||||
remote: &mut repo::RemoteHandle,
|
|
||||||
branch_name: &str,
|
|
||||||
remote_branch_name: &str,
|
|
||||||
repo: &repo::RepoHandle,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
if !remote.is_pushable()? {
|
|
||||||
return Err(format!(
|
|
||||||
"Cannot push to non-pushable remote {}",
|
|
||||||
remote.url()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
remote.push(branch_name, remote_branch_name, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !no_track {
|
|
||||||
if let Some((remote_name, remote_branch_name)) = track {
|
|
||||||
if remote_branch_exists {
|
|
||||||
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
|
||||||
} else {
|
|
||||||
let mut remote = repo
|
|
||||||
.find_remote(remote_name)
|
|
||||||
.map_err(|error| format!("Error getting remote {}: {}", remote_name, error))?
|
|
||||||
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
|
||||||
|
|
||||||
push(
|
|
||||||
&mut remote,
|
|
||||||
&target_branch.name()?,
|
|
||||||
remote_branch_name,
|
|
||||||
&repo,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
|
||||||
}
|
|
||||||
} else if let Some(config) = config {
|
|
||||||
if let Some(track_config) = config.track {
|
|
||||||
if track_config.default {
|
|
||||||
let remote_name = track_config.default_remote;
|
|
||||||
if remote_branch_exists {
|
|
||||||
target_branch.set_upstream(&remote_name, name)?;
|
|
||||||
} else {
|
|
||||||
let remote_branch_name = match track_config.default_remote_prefix {
|
|
||||||
Some(prefix) => {
|
|
||||||
format!("{}{}{}", &prefix, BRANCH_NAMESPACE_SEPARATOR, &name)
|
|
||||||
}
|
|
||||||
None => name.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut remote = repo
|
|
||||||
.find_remote(&remote_name)
|
|
||||||
.map_err(|error| {
|
|
||||||
format!("Error getting remote {}: {}", remote_name, error)
|
|
||||||
})?
|
|
||||||
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
|
||||||
|
|
||||||
if !remote.is_pushable()? {
|
|
||||||
return Err(format!(
|
|
||||||
"Cannot push to non-pushable remote {}",
|
|
||||||
remote.url()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
push(
|
|
||||||
&mut remote,
|
|
||||||
&target_branch.name()?,
|
|
||||||
&remote_branch_name,
|
|
||||||
&repo,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
target_branch.set_upstream(&remote_name, &remote_branch_name)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(subdirectory) = subdirectory {
|
|
||||||
std::fs::create_dir_all(subdirectory).map_err(|error| error.to_string())?;
|
|
||||||
}
|
|
||||||
repo.new_worktree(name, &path, &target_branch)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
84
src/path.rs
Normal file
84
src/path.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use super::output::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn setup() {
|
||||||
|
std::env::set_var("HOME", "/home/test");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_expand_tilde() {
|
||||||
|
setup();
|
||||||
|
assert_eq!(
|
||||||
|
expand_path(Path::new("~/file")),
|
||||||
|
Path::new("/home/test/file")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_expand_invalid_tilde() {
|
||||||
|
setup();
|
||||||
|
assert_eq!(
|
||||||
|
expand_path(Path::new("/home/~/file")),
|
||||||
|
Path::new("/home/~/file")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_expand_home() {
|
||||||
|
setup();
|
||||||
|
assert_eq!(
|
||||||
|
expand_path(Path::new("$HOME/file")),
|
||||||
|
Path::new("/home/test/file")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
expand_path(Path::new("${HOME}/file")),
|
||||||
|
Path::new("/home/test/file")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_as_string(path: &Path) -> String {
|
||||||
|
path.to_path_buf().into_os_string().into_string().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env_home() -> PathBuf {
|
||||||
|
match std::env::var("HOME") {
|
||||||
|
Ok(path) => Path::new(&path).to_path_buf(),
|
||||||
|
Err(e) => {
|
||||||
|
print_error(&format!("Unable to read HOME: {}", e));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_path(path: &Path) -> PathBuf {
|
||||||
|
fn home_dir() -> Option<PathBuf> {
|
||||||
|
Some(env_home())
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded_path = match shellexpand::full_with_context(
|
||||||
|
&path_as_string(path),
|
||||||
|
home_dir,
|
||||||
|
|name| -> Result<Option<String>, &'static str> {
|
||||||
|
match name {
|
||||||
|
"HOME" => Ok(Some(path_as_string(home_dir().unwrap().as_path()))),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(std::borrow::Cow::Borrowed(path)) => path.to_owned(),
|
||||||
|
Ok(std::borrow::Cow::Owned(path)) => path,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(&format!("Unable to expand root: {}", e));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Path::new(&expanded_path).to_path_buf()
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ pub mod gitlab;
|
|||||||
pub use github::Github;
|
pub use github::Github;
|
||||||
pub use gitlab::Gitlab;
|
pub use gitlab::Gitlab;
|
||||||
|
|
||||||
use crate::{Remote, RemoteType, Repo};
|
use super::repo;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -29,15 +29,20 @@ enum ProjectResponse<T, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Project {
|
pub trait Project {
|
||||||
fn into_repo_config(self, provider_name: &str, worktree_setup: bool, force_ssh: bool) -> Repo
|
fn into_repo_config(
|
||||||
|
self,
|
||||||
|
provider_name: &str,
|
||||||
|
worktree_setup: bool,
|
||||||
|
force_ssh: bool,
|
||||||
|
) -> repo::Repo
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
Repo {
|
repo::Repo {
|
||||||
name: self.name(),
|
name: self.name(),
|
||||||
namespace: self.namespace(),
|
namespace: self.namespace(),
|
||||||
worktree_setup,
|
worktree_setup,
|
||||||
remotes: Some(vec![Remote {
|
remotes: Some(vec![repo::Remote {
|
||||||
name: String::from(provider_name),
|
name: String::from(provider_name),
|
||||||
url: if force_ssh || self.private() {
|
url: if force_ssh || self.private() {
|
||||||
self.ssh_url()
|
self.ssh_url()
|
||||||
@@ -45,9 +50,9 @@ pub trait Project {
|
|||||||
self.http_url()
|
self.http_url()
|
||||||
},
|
},
|
||||||
remote_type: if force_ssh || self.private() {
|
remote_type: if force_ssh || self.private() {
|
||||||
RemoteType::Ssh
|
repo::RemoteType::Ssh
|
||||||
} else {
|
} else {
|
||||||
RemoteType::Https
|
repo::RemoteType::Https
|
||||||
},
|
},
|
||||||
}]),
|
}]),
|
||||||
}
|
}
|
||||||
@@ -201,7 +206,7 @@ pub trait Provider {
|
|||||||
&self,
|
&self,
|
||||||
worktree_setup: bool,
|
worktree_setup: bool,
|
||||||
force_ssh: bool,
|
force_ssh: bool,
|
||||||
) -> Result<HashMap<Option<String>, Vec<Repo>>, String> {
|
) -> Result<HashMap<Option<String>, Vec<repo::Repo>>, String> {
|
||||||
let mut repos = vec![];
|
let mut repos = vec![];
|
||||||
|
|
||||||
if self.filter().owner {
|
if self.filter().owner {
|
||||||
@@ -278,7 +283,7 @@ pub trait Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ret: HashMap<Option<String>, Vec<Repo>> = HashMap::new();
|
let mut ret: HashMap<Option<String>, Vec<repo::Repo>> = HashMap::new();
|
||||||
|
|
||||||
for repo in repos {
|
for repo in repos {
|
||||||
let namespace = repo.namespace();
|
let namespace = repo.namespace();
|
||||||
|
|||||||
38
src/repo.rs
38
src/repo.rs
@@ -3,9 +3,13 @@ use std::path::Path;
|
|||||||
|
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
|
|
||||||
use crate::output::*;
|
use super::output::*;
|
||||||
|
use super::path;
|
||||||
|
use super::worktree;
|
||||||
|
|
||||||
const WORKTREE_CONFIG_FILE_NAME: &str = "grm.toml";
|
const WORKTREE_CONFIG_FILE_NAME: &str = "grm.toml";
|
||||||
|
const GIT_CONFIG_BARE_KEY: &str = "core.bare";
|
||||||
|
const GIT_CONFIG_PUSH_DEFAULT: &str = "push.default";
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
@@ -506,7 +510,7 @@ impl RepoHandle {
|
|||||||
false => Repository::open,
|
false => Repository::open,
|
||||||
};
|
};
|
||||||
let path = match is_worktree {
|
let path = match is_worktree {
|
||||||
true => path.join(crate::GIT_MAIN_WORKTREE_DIRECTORY),
|
true => path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY),
|
||||||
false => path.to_path_buf(),
|
false => path.to_path_buf(),
|
||||||
};
|
};
|
||||||
match open_func(path) {
|
match open_func(path) {
|
||||||
@@ -679,7 +683,7 @@ impl RepoHandle {
|
|||||||
pub fn init(path: &Path, is_worktree: bool) -> Result<Self, String> {
|
pub fn init(path: &Path, is_worktree: bool) -> Result<Self, String> {
|
||||||
let repo = match is_worktree {
|
let repo = match is_worktree {
|
||||||
false => Repository::init(path).map_err(convert_libgit2_error)?,
|
false => Repository::init(path).map_err(convert_libgit2_error)?,
|
||||||
true => Repository::init_bare(path.join(crate::GIT_MAIN_WORKTREE_DIRECTORY))
|
true => Repository::init_bare(path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY))
|
||||||
.map_err(convert_libgit2_error)?,
|
.map_err(convert_libgit2_error)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -742,8 +746,8 @@ impl RepoHandle {
|
|||||||
let mut config = self.config()?;
|
let mut config = self.config()?;
|
||||||
|
|
||||||
config
|
config
|
||||||
.set_bool(crate::GIT_CONFIG_BARE_KEY, value)
|
.set_bool(GIT_CONFIG_BARE_KEY, value)
|
||||||
.map_err(|error| format!("Could not set {}: {}", crate::GIT_CONFIG_BARE_KEY, error))
|
.map_err(|error| format!("Could not set {}: {}", GIT_CONFIG_BARE_KEY, error))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_to_worktree(
|
pub fn convert_to_worktree(
|
||||||
@@ -766,7 +770,7 @@ impl RepoHandle {
|
|||||||
return Err(WorktreeConversionFailureReason::Ignored);
|
return Err(WorktreeConversionFailureReason::Ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::fs::rename(".git", crate::GIT_MAIN_WORKTREE_DIRECTORY).map_err(|error| {
|
std::fs::rename(".git", worktree::GIT_MAIN_WORKTREE_DIRECTORY).map_err(|error| {
|
||||||
WorktreeConversionFailureReason::Error(format!(
|
WorktreeConversionFailureReason::Error(format!(
|
||||||
"Error moving .git directory: {}",
|
"Error moving .git directory: {}",
|
||||||
error
|
error
|
||||||
@@ -786,7 +790,7 @@ impl RepoHandle {
|
|||||||
Ok(entry) => {
|
Ok(entry) => {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
// unwrap is safe here, the path will ALWAYS have a file component
|
// unwrap is safe here, the path will ALWAYS have a file component
|
||||||
if path.file_name().unwrap() == crate::GIT_MAIN_WORKTREE_DIRECTORY {
|
if path.file_name().unwrap() == worktree::GIT_MAIN_WORKTREE_DIRECTORY {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if path.is_file() || path.is_symlink() {
|
if path.is_file() || path.is_symlink() {
|
||||||
@@ -835,18 +839,12 @@ impl RepoHandle {
|
|||||||
|
|
||||||
config
|
config
|
||||||
.set_str(
|
.set_str(
|
||||||
crate::GIT_CONFIG_PUSH_DEFAULT,
|
GIT_CONFIG_PUSH_DEFAULT,
|
||||||
match value {
|
match value {
|
||||||
GitPushDefaultSetting::Upstream => "upstream",
|
GitPushDefaultSetting::Upstream => "upstream",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|error| {
|
.map_err(|error| format!("Could not set {}: {}", GIT_CONFIG_PUSH_DEFAULT, error))
|
||||||
format!(
|
|
||||||
"Could not set {}: {}",
|
|
||||||
crate::GIT_CONFIG_PUSH_DEFAULT,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_untracked_files(&self, is_worktree: bool) -> Result<bool, String> {
|
pub fn has_untracked_files(&self, is_worktree: bool) -> Result<bool, String> {
|
||||||
@@ -1105,7 +1103,7 @@ impl RepoHandle {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
if branch_name != name
|
if branch_name != name
|
||||||
&& !branch_name.ends_with(&format!("{}{}", crate::BRANCH_NAMESPACE_SEPARATOR, name))
|
&& !branch_name.ends_with(&format!("{}{}", super::BRANCH_NAMESPACE_SEPARATOR, name))
|
||||||
{
|
{
|
||||||
return Err(WorktreeRemoveFailureReason::Error(format!(
|
return Err(WorktreeRemoveFailureReason::Error(format!(
|
||||||
"Branch {} is checked out in worktree, this does not look correct",
|
"Branch {} is checked out in worktree, this does not look correct",
|
||||||
@@ -1275,7 +1273,7 @@ impl RepoHandle {
|
|||||||
|
|
||||||
let mut unmanaged_worktrees = Vec::new();
|
let mut unmanaged_worktrees = Vec::new();
|
||||||
for entry in std::fs::read_dir(&directory).map_err(|error| error.to_string())? {
|
for entry in std::fs::read_dir(&directory).map_err(|error| error.to_string())? {
|
||||||
let dirname = crate::path_as_string(
|
let dirname = path::path_as_string(
|
||||||
entry
|
entry
|
||||||
.map_err(|error| error.to_string())?
|
.map_err(|error| error.to_string())?
|
||||||
.path()
|
.path()
|
||||||
@@ -1308,7 +1306,7 @@ impl RepoHandle {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if dirname == crate::GIT_MAIN_WORKTREE_DIRECTORY {
|
if dirname == worktree::GIT_MAIN_WORKTREE_DIRECTORY {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if dirname == WORKTREE_CONFIG_FILE_NAME {
|
if dirname == WORKTREE_CONFIG_FILE_NAME {
|
||||||
@@ -1327,7 +1325,7 @@ impl RepoHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect_worktree(path: &Path) -> bool {
|
pub fn detect_worktree(path: &Path) -> bool {
|
||||||
path.join(crate::GIT_MAIN_WORKTREE_DIRECTORY).exists()
|
path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY).exists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1486,7 +1484,7 @@ pub fn clone_repo(
|
|||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let clone_target = match is_worktree {
|
let clone_target = match is_worktree {
|
||||||
false => path.to_path_buf(),
|
false => path.to_path_buf(),
|
||||||
true => path.join(crate::GIT_MAIN_WORKTREE_DIRECTORY),
|
true => path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY),
|
||||||
};
|
};
|
||||||
|
|
||||||
print_action(&format!(
|
print_action(&format!(
|
||||||
|
|||||||
38
src/table.rs
38
src/table.rs
@@ -1,4 +1,6 @@
|
|||||||
use crate::RepoHandle;
|
use super::config;
|
||||||
|
use super::path;
|
||||||
|
use super::repo;
|
||||||
|
|
||||||
use comfy_table::{Cell, Table};
|
use comfy_table::{Cell, Table};
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ fn add_table_header(table: &mut Table) {
|
|||||||
fn add_repo_status(
|
fn add_repo_status(
|
||||||
table: &mut Table,
|
table: &mut Table,
|
||||||
repo_name: &str,
|
repo_name: &str,
|
||||||
repo_handle: &crate::RepoHandle,
|
repo_handle: &repo::RepoHandle,
|
||||||
is_worktree: bool,
|
is_worktree: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let repo_status = repo_handle.status(is_worktree)?;
|
let repo_status = repo_handle.status(is_worktree)?;
|
||||||
@@ -65,11 +67,11 @@ fn add_repo_status(
|
|||||||
" <{}>{}",
|
" <{}>{}",
|
||||||
remote_branch_name,
|
remote_branch_name,
|
||||||
&match remote_tracking_status {
|
&match remote_tracking_status {
|
||||||
crate::RemoteTrackingStatus::UpToDate =>
|
repo::RemoteTrackingStatus::UpToDate =>
|
||||||
String::from(" \u{2714}"),
|
String::from(" \u{2714}"),
|
||||||
crate::RemoteTrackingStatus::Ahead(d) => format!(" [+{}]", &d),
|
repo::RemoteTrackingStatus::Ahead(d) => format!(" [+{}]", &d),
|
||||||
crate::RemoteTrackingStatus::Behind(d) => format!(" [-{}]", &d),
|
repo::RemoteTrackingStatus::Behind(d) => format!(" [-{}]", &d),
|
||||||
crate::RemoteTrackingStatus::Diverged(d1, d2) =>
|
repo::RemoteTrackingStatus::Diverged(d1, d2) =>
|
||||||
format!(" [+{}/-{}]", &d1, &d2),
|
format!(" [+{}/-{}]", &d1, &d2),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -99,7 +101,7 @@ fn add_repo_status(
|
|||||||
|
|
||||||
// Don't return table, return a type that implements Display(?)
|
// Don't return table, return a type that implements Display(?)
|
||||||
pub fn get_worktree_status_table(
|
pub fn get_worktree_status_table(
|
||||||
repo: &crate::RepoHandle,
|
repo: &repo::RepoHandle,
|
||||||
directory: &Path,
|
directory: &Path,
|
||||||
) -> Result<(impl std::fmt::Display, Vec<String>), String> {
|
) -> Result<(impl std::fmt::Display, Vec<String>), String> {
|
||||||
let worktrees = repo.get_worktrees()?;
|
let worktrees = repo.get_worktrees()?;
|
||||||
@@ -111,7 +113,7 @@ pub fn get_worktree_status_table(
|
|||||||
for worktree in &worktrees {
|
for worktree in &worktrees {
|
||||||
let worktree_dir = &directory.join(&worktree.name());
|
let worktree_dir = &directory.join(&worktree.name());
|
||||||
if worktree_dir.exists() {
|
if worktree_dir.exists() {
|
||||||
let repo = match crate::RepoHandle::open(worktree_dir, false) {
|
let repo = match repo::RepoHandle::open(worktree_dir, false) {
|
||||||
Ok(repo) => repo,
|
Ok(repo) => repo,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
errors.push(format!(
|
errors.push(format!(
|
||||||
@@ -132,7 +134,7 @@ pub fn get_worktree_status_table(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for worktree in RepoHandle::find_unmanaged_worktrees(repo, directory)? {
|
for worktree in repo::RepoHandle::find_unmanaged_worktrees(repo, directory)? {
|
||||||
errors.push(format!(
|
errors.push(format!(
|
||||||
"Found {}, which is not a valid worktree directory!",
|
"Found {}, which is not a valid worktree directory!",
|
||||||
&worktree
|
&worktree
|
||||||
@@ -141,13 +143,13 @@ pub fn get_worktree_status_table(
|
|||||||
Ok((table, errors))
|
Ok((table, errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status_table(config: crate::Config) -> Result<(Vec<Table>, Vec<String>), String> {
|
pub fn get_status_table(config: config::Config) -> Result<(Vec<Table>, Vec<String>), String> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let mut tables = Vec::new();
|
let mut tables = Vec::new();
|
||||||
for tree in config.trees()? {
|
for tree in config.trees()? {
|
||||||
let repos = tree.repos.unwrap_or_default();
|
let repos = tree.repos.unwrap_or_default();
|
||||||
|
|
||||||
let root_path = crate::expand_path(Path::new(&tree.root));
|
let root_path = path::expand_path(Path::new(&tree.root));
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
add_table_header(&mut table);
|
add_table_header(&mut table);
|
||||||
@@ -163,12 +165,12 @@ pub fn get_status_table(config: crate::Config) -> Result<(Vec<Table>, Vec<String
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let repo_handle = crate::RepoHandle::open(&repo_path, repo.worktree_setup);
|
let repo_handle = repo::RepoHandle::open(&repo_path, repo.worktree_setup);
|
||||||
|
|
||||||
let repo_handle = match repo_handle {
|
let repo_handle = match repo_handle {
|
||||||
Ok(repo) => repo,
|
Ok(repo) => repo,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if error.kind == crate::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
errors.push(format!(
|
errors.push(format!(
|
||||||
"{}: No git repository found. Run sync?",
|
"{}: No git repository found. Run sync?",
|
||||||
&repo.name
|
&repo.name
|
||||||
@@ -206,8 +208,8 @@ fn add_worktree_table_header(table: &mut Table) {
|
|||||||
|
|
||||||
fn add_worktree_status(
|
fn add_worktree_status(
|
||||||
table: &mut Table,
|
table: &mut Table,
|
||||||
worktree: &crate::repo::Worktree,
|
worktree: &repo::Worktree,
|
||||||
repo: &crate::RepoHandle,
|
repo: &repo::RepoHandle,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let repo_status = repo.status(false)?;
|
let repo_status = repo.status(false)?;
|
||||||
|
|
||||||
@@ -272,13 +274,13 @@ pub fn show_single_repo_status(
|
|||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
let is_worktree = crate::RepoHandle::detect_worktree(path);
|
let is_worktree = repo::RepoHandle::detect_worktree(path);
|
||||||
add_table_header(&mut table);
|
add_table_header(&mut table);
|
||||||
|
|
||||||
let repo_handle = crate::RepoHandle::open(path, is_worktree);
|
let repo_handle = repo::RepoHandle::open(path, is_worktree);
|
||||||
|
|
||||||
if let Err(error) = repo_handle {
|
if let Err(error) = repo_handle {
|
||||||
if error.kind == crate::RepoErrorKind::NotFound {
|
if error.kind == repo::RepoErrorKind::NotFound {
|
||||||
return Err(String::from("Directory is not a git directory"));
|
return Err(String::from("Directory is not a git directory"));
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Opening repository failed: {}", error));
|
return Err(format!("Opening repository failed: {}", error));
|
||||||
|
|||||||
268
src/tree.rs
Normal file
268
src/tree.rs
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use super::config;
|
||||||
|
use super::output::*;
|
||||||
|
use super::path;
|
||||||
|
use super::repo;
|
||||||
|
use super::worktree;
|
||||||
|
|
||||||
|
pub struct Tree {
|
||||||
|
pub root: String,
|
||||||
|
pub repos: Vec<repo::Repo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_unmanaged_repos(
|
||||||
|
root_path: &Path,
|
||||||
|
managed_repos: &[repo::Repo],
|
||||||
|
) -> Result<Vec<PathBuf>, String> {
|
||||||
|
let mut unmanaged_repos = Vec::new();
|
||||||
|
|
||||||
|
for repo_path in find_repo_paths(root_path)? {
|
||||||
|
if !managed_repos
|
||||||
|
.iter()
|
||||||
|
.any(|r| Path::new(root_path).join(r.fullname()) == repo_path)
|
||||||
|
{
|
||||||
|
unmanaged_repos.push(repo_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(unmanaged_repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_trees(config: config::Config, init_worktree: bool) -> Result<bool, String> {
|
||||||
|
let mut failures = false;
|
||||||
|
|
||||||
|
let mut unmanaged_repos_absolute_paths = vec![];
|
||||||
|
let mut managed_repos_absolute_paths = vec![];
|
||||||
|
|
||||||
|
let trees = config.trees()?;
|
||||||
|
|
||||||
|
for tree in trees {
|
||||||
|
let repos: Vec<repo::Repo> = tree
|
||||||
|
.repos
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|repo| repo.into_repo())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let root_path = path::expand_path(Path::new(&tree.root));
|
||||||
|
|
||||||
|
for repo in &repos {
|
||||||
|
managed_repos_absolute_paths.push(root_path.join(repo.fullname()));
|
||||||
|
match sync_repo(&root_path, repo, init_worktree) {
|
||||||
|
Ok(_) => print_repo_success(&repo.name, "OK"),
|
||||||
|
Err(error) => {
|
||||||
|
print_repo_error(&repo.name, &error);
|
||||||
|
failures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match find_unmanaged_repos(&root_path, &repos) {
|
||||||
|
Ok(repos) => {
|
||||||
|
unmanaged_repos_absolute_paths.extend(repos);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
print_error(&format!("Error getting unmanaged repos: {}", error));
|
||||||
|
failures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for unmanaged_repo_absolute_path in &unmanaged_repos_absolute_paths {
|
||||||
|
if managed_repos_absolute_paths
|
||||||
|
.iter()
|
||||||
|
.any(|managed_repo_absolute_path| {
|
||||||
|
managed_repo_absolute_path == unmanaged_repo_absolute_path
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
print_warning(&format!(
|
||||||
|
"Found unmanaged repository: \"{}\"",
|
||||||
|
path::path_as_string(unmanaged_repo_absolute_path)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(!failures)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds repositories recursively, returning their path
|
||||||
|
pub fn find_repo_paths(path: &Path) -> Result<Vec<PathBuf>, String> {
|
||||||
|
let mut repos = Vec::new();
|
||||||
|
|
||||||
|
let git_dir = path.join(".git");
|
||||||
|
let git_worktree = path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY);
|
||||||
|
|
||||||
|
if git_dir.exists() || git_worktree.exists() {
|
||||||
|
repos.push(path.to_path_buf());
|
||||||
|
} else {
|
||||||
|
match fs::read_dir(path) {
|
||||||
|
Ok(contents) => {
|
||||||
|
for content in contents {
|
||||||
|
match content {
|
||||||
|
Ok(entry) => {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_symlink() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if path.is_dir() {
|
||||||
|
match find_repo_paths(&path) {
|
||||||
|
Ok(ref mut r) => repos.append(r),
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("Error accessing directory: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Failed to open \"{}\": {}",
|
||||||
|
&path.display(),
|
||||||
|
match e.kind() {
|
||||||
|
std::io::ErrorKind::NotADirectory =>
|
||||||
|
String::from("directory expected, but path is not a directory"),
|
||||||
|
std::io::ErrorKind::NotFound => String::from("not found"),
|
||||||
|
_ => format!("{:?}", e.kind()),
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_repo(root_path: &Path, repo: &repo::Repo, init_worktree: bool) -> Result<(), String> {
|
||||||
|
let repo_path = root_path.join(&repo.fullname());
|
||||||
|
let actual_git_directory = get_actual_git_directory(&repo_path, repo.worktree_setup);
|
||||||
|
|
||||||
|
let mut newly_created = false;
|
||||||
|
|
||||||
|
if repo_path.exists() {
|
||||||
|
if repo.worktree_setup && !actual_git_directory.exists() {
|
||||||
|
return Err(String::from(
|
||||||
|
"Repo already exists, but is not using a worktree setup",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
} else if matches!(&repo.remotes, None) || repo.remotes.as_ref().unwrap().is_empty() {
|
||||||
|
print_repo_action(
|
||||||
|
&repo.name,
|
||||||
|
"Repository does not have remotes configured, initializing new",
|
||||||
|
);
|
||||||
|
match repo::RepoHandle::init(&repo_path, repo.worktree_setup) {
|
||||||
|
Ok(r) => {
|
||||||
|
print_repo_success(&repo.name, "Repository created");
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("Repository failed during init: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
let first = repo.remotes.as_ref().unwrap().first().unwrap();
|
||||||
|
|
||||||
|
match repo::clone_repo(first, &repo_path, repo.worktree_setup) {
|
||||||
|
Ok(_) => {
|
||||||
|
print_repo_success(&repo.name, "Repository successfully cloned");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("Repository failed during clone: {}", e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
newly_created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let repo_handle = match repo::RepoHandle::open(&repo_path, repo.worktree_setup) {
|
||||||
|
Ok(repo) => repo,
|
||||||
|
Err(error) => {
|
||||||
|
if !repo.worktree_setup && repo::RepoHandle::open(&repo_path, true).is_ok() {
|
||||||
|
return Err(String::from(
|
||||||
|
"Repo already exists, but is using a worktree setup",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(format!("Opening repository failed: {}", error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if newly_created && repo.worktree_setup && init_worktree {
|
||||||
|
match repo_handle.default_branch() {
|
||||||
|
Ok(branch) => {
|
||||||
|
worktree::add_worktree(&repo_path, &branch.name()?, None, None, false)?;
|
||||||
|
}
|
||||||
|
Err(_error) => print_repo_error(
|
||||||
|
&repo.name,
|
||||||
|
"Could not determine default branch, skipping worktree initializtion",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(remotes) = &repo.remotes {
|
||||||
|
let current_remotes: Vec<String> = repo_handle
|
||||||
|
.remotes()
|
||||||
|
.map_err(|error| format!("Repository failed during getting the remotes: {}", error))?;
|
||||||
|
|
||||||
|
for remote in remotes {
|
||||||
|
let current_remote = repo_handle.find_remote(&remote.name)?;
|
||||||
|
|
||||||
|
match current_remote {
|
||||||
|
Some(current_remote) => {
|
||||||
|
let current_url = current_remote.url();
|
||||||
|
|
||||||
|
if remote.url != current_url {
|
||||||
|
print_repo_action(
|
||||||
|
&repo.name,
|
||||||
|
&format!("Updating remote {} to \"{}\"", &remote.name, &remote.url),
|
||||||
|
);
|
||||||
|
if let Err(e) = repo_handle.remote_set_url(&remote.name, &remote.url) {
|
||||||
|
return Err(format!("Repository failed during setting of the remote URL for remote \"{}\": {}", &remote.name, e));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
print_repo_action(
|
||||||
|
&repo.name,
|
||||||
|
&format!(
|
||||||
|
"Setting up new remote \"{}\" to \"{}\"",
|
||||||
|
&remote.name, &remote.url
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if let Err(e) = repo_handle.new_remote(&remote.name, &remote.url) {
|
||||||
|
return Err(format!(
|
||||||
|
"Repository failed during setting the remotes: {}",
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for current_remote in ¤t_remotes {
|
||||||
|
if !remotes.iter().any(|r| &r.name == current_remote) {
|
||||||
|
print_repo_action(
|
||||||
|
&repo.name,
|
||||||
|
&format!("Deleting remote \"{}\"", ¤t_remote,),
|
||||||
|
);
|
||||||
|
if let Err(e) = repo_handle.remote_delete(current_remote) {
|
||||||
|
return Err(format!(
|
||||||
|
"Repository failed during deleting remote \"{}\": {}",
|
||||||
|
¤t_remote, e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_actual_git_directory(path: &Path, is_worktree: bool) -> PathBuf {
|
||||||
|
match is_worktree {
|
||||||
|
false => path.to_path_buf(),
|
||||||
|
true => path.join(worktree::GIT_MAIN_WORKTREE_DIRECTORY),
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/worktree.rs
Normal file
166
src/worktree.rs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use super::repo;
|
||||||
|
|
||||||
|
pub const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
|
||||||
|
|
||||||
|
pub fn add_worktree(
|
||||||
|
directory: &Path,
|
||||||
|
name: &str,
|
||||||
|
subdirectory: Option<&Path>,
|
||||||
|
track: Option<(&str, &str)>,
|
||||||
|
no_track: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let repo = repo::RepoHandle::open(directory, true).map_err(|error| match error.kind {
|
||||||
|
repo::RepoErrorKind::NotFound => {
|
||||||
|
String::from("Current directory does not contain a worktree setup")
|
||||||
|
}
|
||||||
|
_ => format!("Error opening repo: {}", error),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let config = repo::read_worktree_root_config(directory)?;
|
||||||
|
|
||||||
|
if repo.find_worktree(name).is_ok() {
|
||||||
|
return Err(format!("Worktree {} already exists", &name));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = match subdirectory {
|
||||||
|
Some(dir) => directory.join(dir).join(name),
|
||||||
|
None => directory.join(Path::new(name)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remote_branch_exists = false;
|
||||||
|
|
||||||
|
let default_checkout = || repo.default_branch()?.to_commit();
|
||||||
|
|
||||||
|
let checkout_commit;
|
||||||
|
if no_track {
|
||||||
|
checkout_commit = default_checkout()?;
|
||||||
|
} else {
|
||||||
|
match track {
|
||||||
|
Some((remote_name, remote_branch_name)) => {
|
||||||
|
let remote_branch = repo.find_remote_branch(remote_name, remote_branch_name);
|
||||||
|
match remote_branch {
|
||||||
|
Ok(branch) => {
|
||||||
|
remote_branch_exists = true;
|
||||||
|
checkout_commit = branch.to_commit()?;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
remote_branch_exists = false;
|
||||||
|
checkout_commit = default_checkout()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => match &config {
|
||||||
|
None => checkout_commit = default_checkout()?,
|
||||||
|
Some(config) => match &config.track {
|
||||||
|
None => checkout_commit = default_checkout()?,
|
||||||
|
Some(track_config) => {
|
||||||
|
if track_config.default {
|
||||||
|
let remote_branch =
|
||||||
|
repo.find_remote_branch(&track_config.default_remote, name);
|
||||||
|
match remote_branch {
|
||||||
|
Ok(branch) => {
|
||||||
|
remote_branch_exists = true;
|
||||||
|
checkout_commit = branch.to_commit()?;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
checkout_commit = default_checkout()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
checkout_commit = default_checkout()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut target_branch = match repo.find_local_branch(name) {
|
||||||
|
Ok(branchref) => branchref,
|
||||||
|
Err(_) => repo.create_branch(name, &checkout_commit)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn push(
|
||||||
|
remote: &mut repo::RemoteHandle,
|
||||||
|
branch_name: &str,
|
||||||
|
remote_branch_name: &str,
|
||||||
|
repo: &repo::RepoHandle,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if !remote.is_pushable()? {
|
||||||
|
return Err(format!(
|
||||||
|
"Cannot push to non-pushable remote {}",
|
||||||
|
remote.url()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
remote.push(branch_name, remote_branch_name, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !no_track {
|
||||||
|
if let Some((remote_name, remote_branch_name)) = track {
|
||||||
|
if remote_branch_exists {
|
||||||
|
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
||||||
|
} else {
|
||||||
|
let mut remote = repo
|
||||||
|
.find_remote(remote_name)
|
||||||
|
.map_err(|error| format!("Error getting remote {}: {}", remote_name, error))?
|
||||||
|
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
||||||
|
|
||||||
|
push(
|
||||||
|
&mut remote,
|
||||||
|
&target_branch.name()?,
|
||||||
|
remote_branch_name,
|
||||||
|
&repo,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
||||||
|
}
|
||||||
|
} else if let Some(config) = config {
|
||||||
|
if let Some(track_config) = config.track {
|
||||||
|
if track_config.default {
|
||||||
|
let remote_name = track_config.default_remote;
|
||||||
|
if remote_branch_exists {
|
||||||
|
target_branch.set_upstream(&remote_name, name)?;
|
||||||
|
} else {
|
||||||
|
let remote_branch_name = match track_config.default_remote_prefix {
|
||||||
|
Some(prefix) => {
|
||||||
|
format!("{}{}{}", &prefix, super::BRANCH_NAMESPACE_SEPARATOR, &name)
|
||||||
|
}
|
||||||
|
None => name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remote = repo
|
||||||
|
.find_remote(&remote_name)
|
||||||
|
.map_err(|error| {
|
||||||
|
format!("Error getting remote {}: {}", remote_name, error)
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
||||||
|
|
||||||
|
if !remote.is_pushable()? {
|
||||||
|
return Err(format!(
|
||||||
|
"Cannot push to non-pushable remote {}",
|
||||||
|
remote.url()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
push(
|
||||||
|
&mut remote,
|
||||||
|
&target_branch.name()?,
|
||||||
|
&remote_branch_name,
|
||||||
|
&repo,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
target_branch.set_upstream(&remote_name, &remote_branch_name)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(subdirectory) = subdirectory {
|
||||||
|
std::fs::create_dir_all(subdirectory).map_err(|error| error.to_string())?;
|
||||||
|
}
|
||||||
|
repo.new_worktree(name, &path, &target_branch)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user