72 Commits

Author SHA1 Message Date
9b4ed2837e Merge branch 'develop' 2022-12-12 17:41:41 +01:00
701e64df6f Release v0.7.12 2022-12-12 17:41:41 +01:00
23fc942db7 Warn on empty filters
Closes #29
2022-12-12 15:43:27 +01:00
38bba1472e Improve error messages during sync errors
Closes #46
2022-12-12 15:21:42 +01:00
7d131bbacf 'Enable deny_unknown_fields for all config structs 2022-12-12 15:10:00 +01:00
6e79dd318a Make clippy happy 2022-12-12 14:46:08 +01:00
5fc1d2148f Cargo.lock: Updating polling v2.3.0 -> v2.5.1 2022-12-12 14:41:07 +01:00
de184de5a0 Cargo.lock: Updating futures-io v0.3.24 -> v0.3.25 2022-12-12 14:41:07 +01:00
8a3b2ae1c5 Cargo.lock: Updating curl-sys v0.4.56+curl-7.83.1 -> v0.4.59+curl-7.86.0 2022-12-12 14:41:07 +01:00
93e38b0572 Cargo.lock: Updating cc v1.0.73 -> v1.0.77 2022-12-12 14:41:07 +01:00
43bbb8e143 Cargo.lock: Updating crossbeam-utils v0.8.12 -> v0.8.14 2022-12-12 14:41:07 +01:00
0fd9ce68b8 Cargo.lock: Updating async-channel v1.7.1 -> v1.8.0 2022-12-12 14:41:07 +01:00
68f2b81e3f dependencies: Update parse_link_header to 0.3.3 2022-12-12 14:41:07 +01:00
d7a39fa4e4 dependencies: Update serde_json to 1.0.89 2022-12-12 14:41:07 +01:00
1f646fd5f8 dependencies: Update serde_yaml to 0.9.14 2022-12-12 14:41:07 +01:00
96cbf8c568 dependencies: Update comfy-table to 6.1.3 2022-12-12 14:41:07 +01:00
bf199c1b17 dependencies: Update regex to 1.7.0 2022-12-12 14:41:07 +01:00
0f7a70c895 dependencies: Update clap to 4.0.29 2022-12-12 14:41:07 +01:00
3151b97bc0 dependencies: Update shellexpand to 3.0.0 2022-12-12 14:41:07 +01:00
8ce5cfecd4 dependencies: Update serde to 1.0.150 2022-12-12 13:55:05 +01:00
6da27c6444 Merge branch 'develop' 2022-12-12 13:53:02 +01:00
3026b3e6de Release v0.7.11 2022-12-12 13:53:02 +01:00
725414cc71 release-script: Fix missing newline 2022-12-12 13:43:34 +01:00
defb8fafca Merge branch 'develop' 2022-10-10 18:50:40 +02:00
f747c085c9 Release v0.7.10 2022-10-10 18:50:40 +02:00
85dd794b53 Cargo.lock: Updating tracing v0.1.36 -> v0.1.37 2022-10-10 18:06:27 +02:00
be8d85cb66 dependencies: Update serde_json to 1.0.86 2022-10-10 18:06:25 +02:00
0b7527fc7d dependencies: Update clap to 4.0.11 2022-10-10 18:06:25 +02:00
3a568a774a Remove init_worktree from sync config
It was currently unused and only confuses. The initialization of
worktrees can currently only be controlled via the `--init-worktree`
command line switch. This is unfortunate, but it's the only was to
handle it right now. Changing it would mean a restructure of the code,
mainly the `tree::sync_trees` function.
2022-10-06 12:59:56 +02:00
a6ecb66547 Merge branch 'develop' 2022-10-06 12:38:32 +02:00
8a04db8130 Release v0.7.9 2022-10-06 12:38:32 +02:00
d5bbbe6171 just: Use bash explicitly 2022-10-06 12:28:20 +02:00
c6a27525fd Remove unnecessary deref 2022-10-06 12:20:30 +02:00
5880066531 cli: Update code for clap v4 2022-10-06 12:17:26 +02:00
918b63047b Cargo.lock: Updating thiserror v1.0.35 -> v1.0.37 2022-10-06 11:36:53 +02:00
0fa2a65c81 Cargo.lock: Updating openssl-sys v0.9.75 -> v0.9.76 2022-10-06 11:36:52 +02:00
87d5b7ad85 Cargo.lock: Updating crossbeam-utils v0.8.11 -> v0.8.12 2022-10-06 11:36:50 +02:00
7db3596302 Cargo.lock: Updating smallvec v1.9.0 -> v1.10.0 2022-10-06 11:36:49 +02:00
e65c744f9c Cargo.lock: Updating jobserver v0.1.24 -> v0.1.25 2022-10-06 11:36:48 +02:00
bd79602d3a dependencies: Update console to 0.15.2 2022-10-06 11:36:47 +02:00
6e876aaefc dependencies: Update clap to 4.0.10 2022-10-06 11:36:47 +02:00
04753e8d9c Merge branch 'develop' 2022-09-23 07:19:03 +02:00
5811476e27 Release v0.7.8 2022-09-23 07:19:03 +02:00
5ac814b857 Cargo.lock: Updating idna v0.2.3 -> v0.3.0 2022-09-23 07:05:43 +02:00
3a87772606 Cargo.lock: Updating form_urlencoded v1.0.1 -> v1.1.0 2022-09-23 07:05:40 +02:00
001911bed9 Cargo.lock: Updating thiserror v1.0.32 -> v1.0.35 2022-09-23 07:05:37 +02:00
d1d729c33d Cargo.lock: Updating socket2 v0.4.6 -> v0.4.7 2022-09-23 07:05:35 +02:00
b4cabae4ec Cargo.lock: Updating lock_api v0.4.8 -> v0.4.9 2022-09-23 07:05:33 +02:00
196bbeb2c2 Cargo.lock: Updating aho-corasick v0.7.18 -> v0.7.19 2022-09-23 07:05:30 +02:00
4d60012f44 dependencies: Update serde_yaml to 0.9.13 2022-09-23 07:05:30 +02:00
b2f413b033 dependencies: Update clap to 3.2.22 2022-09-23 07:05:30 +02:00
c25bb8bf55 dependencies: Update serde to 1.0.145 2022-09-23 07:05:30 +02:00
3b923e3e13 Merge pull request #44 from vrischmann/name-in-error
catch the error returned by add_repo_status
2022-09-23 07:03:40 +02:00
Vincent Rischmann
061265bbd0 catch the error returned by add_repo_status
If add_repo_status returns an error it will eventually be printed to the
user but there won't be any repository information:

    [✘] Error getting status: No branch checked out

Catch the error earlier so we can print the repository name:

    [✘] Error: freebsd-src: Couldn't add repo status: No branch checked out
2022-09-22 21:10:16 +02:00
a08a8d2000 Merge branch 'develop' 2022-08-29 21:10:53 +02:00
fea0299c95 Release v0.7.7 2022-08-29 21:10:53 +02:00
444930199c Update README 2022-08-29 20:25:34 +02:00
95704b9a40 Cargo.lock: Updating pin-project v1.0.11 -> v1.0.12 2022-08-29 20:05:54 +02:00
8d300827d0 Cargo.lock: Updating futures-io v0.3.21 -> v0.3.24 2022-08-29 20:05:51 +02:00
9ab79b120a Cargo.lock: Updating fastrand v1.7.0 -> v1.8.0 2022-08-29 20:05:48 +02:00
8cc9470aca Cargo.lock: Updating curl v0.4.43 -> v0.4.44 2022-08-29 20:05:45 +02:00
9e95701a6e Cargo.lock: Updating crossbeam-utils v0.8.10 -> v0.8.11 2022-08-29 20:05:43 +02:00
fe90401688 Cargo.lock: Updating bytes v1.2.0 -> v1.2.1 2022-08-29 20:05:42 +02:00
aeaaee9915 Cargo.lock: Updating async-channel v1.6.1 -> v1.7.1 2022-08-29 20:05:41 +02:00
52b024c1ba dependencies: Update serde_json to 1.0.85 2022-08-29 20:05:40 +02:00
3a95613132 dependencies: Update serde_yaml to 0.9.10 2022-08-29 20:05:40 +02:00
2ea2c994d8 dependencies: Update comfy-table to 6.1.0 2022-08-29 20:05:40 +02:00
04686b6dfa dependencies: Update console to 0.15.1 2022-08-29 20:05:40 +02:00
78201d4759 dependencies: Update clap to 3.2.18 2022-08-29 20:05:40 +02:00
5fe6600dc3 dependencies: Update shellexpand to 2.1.2 2022-08-29 20:05:40 +02:00
2a65f78cd4 dependencies: Update git2 to 0.15.0 2022-08-29 20:05:40 +02:00
4852dad71e dependencies: Update serde to 1.0.144 2022-08-29 20:05:40 +02:00
17 changed files with 400 additions and 297 deletions

480
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "git-repo-manager" name = "git-repo-manager"
version = "0.7.6" version = "0.7.12"
edition = "2021" edition = "2021"
authors = [ authors = [
@@ -44,33 +44,33 @@ path = "src/grm/main.rs"
version = "=0.5.9" version = "=0.5.9"
[dependencies.serde] [dependencies.serde]
version = "=1.0.140" version = "=1.0.150"
features = ["derive"] features = ["derive"]
[dependencies.git2] [dependencies.git2]
version = "=0.14.4" version = "=0.15.0"
[dependencies.shellexpand] [dependencies.shellexpand]
version = "=2.1.0" version = "=3.0.0"
[dependencies.clap] [dependencies.clap]
version = "=3.2.14" version = "=4.0.29"
features = ["derive", "cargo"] features = ["derive", "cargo"]
[dependencies.console] [dependencies.console]
version = "=0.15.0" version = "=0.15.2"
[dependencies.regex] [dependencies.regex]
version = "=1.6.0" version = "=1.7.0"
[dependencies.comfy-table] [dependencies.comfy-table]
version = "=6.0.0" version = "=6.1.3"
[dependencies.serde_yaml] [dependencies.serde_yaml]
version = "=0.8.26" version = "=0.9.14"
[dependencies.serde_json] [dependencies.serde_json]
version = "=1.0.82" version = "=1.0.89"
[dependencies.isahc] [dependencies.isahc]
version = "=1.7.2" version = "=1.7.2"
@@ -78,7 +78,7 @@ default-features = false
features = ["json", "http2", "text-decoding"] features = ["json", "http2", "text-decoding"]
[dependencies.parse_link_header] [dependencies.parse_link_header]
version = "=0.3.2" version = "=0.3.3"
[dependencies.url-escape] [dependencies.url-escape]
version = "=0.1.1" version = "=0.1.1"

View File

@@ -1,5 +1,7 @@
set positional-arguments set positional-arguments
set shell := ["/bin/bash", "-c"]
static_target := "x86_64-unknown-linux-musl" static_target := "x86_64-unknown-linux-musl"
check: fmt-check lint test check: fmt-check lint test

View File

@@ -1,7 +1,10 @@
# GRM — Git Repository Manager # GRM — Git Repository Manager
GRM helps you manage git repositories in a declarative way. Configure your GRM helps you manage git repositories in a declarative way. Configure your
repositories in a [TOML](https://toml.io/) file, GRM does the rest. repositories in a [TOML](https://toml.io/) or YAML file, GRM does the rest.
Also, GRM can be used to work with git worktrees in an opinionated,
straightforward fashion.
**Take a look at the [official documentation](https://hakoerber.github.io/git-repo-manager/) **Take a look at the [official documentation](https://hakoerber.github.io/git-repo-manager/)
for installation & quickstart.** for installation & quickstart.**
@@ -34,23 +37,26 @@ like Terraform for your local git repositories. Write a config, run the tool, an
your repos are ready. The only thing that is tracked by git it the list of your repos are ready. The only thing that is tracked by git it the list of
repositories itself. repositories itself.
# Future & Ideas
* Operations over all repos (e.g. pull)
* Show status of managed repositories (dirty, compare to remotes, ...)
# Optional Features
* Support multiple file formats (YAML, JSON).
* Add systemd timer unit to run regular syncs
# Crates # Crates
* [`toml`](https://docs.rs/toml/) for the configuration file * [`toml`](https://docs.rs/toml/) for the configuration file.
* [`serde`](https://docs.rs/serde/) because we're using Rust, after all * [`serde`](https://docs.rs/serde/), together with
* [`git2`](https://docs.rs/git2/), a safe wrapper around `libgit2`, for all git operations [`serde_yaml`](https://docs.rs/serde_yaml/) and
* [`clap`](https://docs.rs/clap/), [`console`](https://docs.rs/console/) and [`shellexpand`](https://docs.rs/shellexpand) for good UX [`serde_json`](https://docs.rs/serde_json/). Because we're using Rust, after
all.
* [`git2`](https://docs.rs/git2/), a safe wrapper around `libgit2`, for all git operations.
* [`clap`](https://docs.rs/clap/), [`console`](https://docs.rs/console/), [`comfy_table`](https://docs.rs/comfy-table/) and [`shellexpand`](https://docs.rs/shellexpand) for good UX.
* [`isahc`](https://docs.rs/isahc/) as the HTTP client for forge integrations.
# Links # Links
* [crates.io](https://crates.io/crates/git-repo-manager) * [crates.io](https://crates.io/crates/git-repo-manager)
# Mirrors
This repository can be found on multiple forges:
* https://github.com/hakoerber/git-repo-manager
* https://code.hkoerber.de/hannes/git-repo-manager/
* https://codeberg.org/hakoerber/git-repo-manager
* https://git.sr.ht/~hkoerber/git-repo-manager

View File

@@ -28,7 +28,7 @@ def get_temporary_directory(dir=None):
def grm(args, cwd=None, is_invalid=False): def grm(args, cwd=None, is_invalid=False):
cmd = subprocess.run([binary] + args, cwd=cwd, capture_output=True, text=True) cmd = subprocess.run([binary] + args, cwd=cwd, capture_output=True, text=True)
if not is_invalid: if not is_invalid:
assert "USAGE" not in cmd.stderr assert "usage" not in cmd.stderr.lower()
print(f"grmcmd: {args}") print(f"grmcmd: {args}")
print(f"stdout:\n{cmd.stdout}") print(f"stdout:\n{cmd.stdout}")
print(f"stderr:\n{cmd.stderr}") print(f"stderr:\n{cmd.stderr}")

View File

@@ -5,9 +5,9 @@ from helpers import *
def test_invalid_command(): def test_invalid_command():
cmd = grm(["whatever"], is_invalid=True) cmd = grm(["whatever"], is_invalid=True)
assert "USAGE" in cmd.stderr assert "usage" in cmd.stderr.lower()
def test_help(): def test_help():
cmd = grm(["--help"]) cmd = grm(["--help"])
assert "USAGE" in cmd.stdout assert "usage" in cmd.stdout.lower()

View File

@@ -166,7 +166,7 @@ def test_repos_find_remote_no_filter(provider, configtype, default, use_config):
cmd = grm(args) cmd = grm(args)
assert cmd.returncode == 0 assert cmd.returncode == 0
assert len(cmd.stderr) == 0 assert "did not specify any filters" in cmd.stderr.lower()
if default or configtype == "toml": if default or configtype == "toml":
output = toml.loads(cmd.stdout) output = toml.loads(cmd.stdout)

View File

@@ -67,7 +67,7 @@ fi
for remote in $(git remote); do for remote in $(git remote); do
if git ls-remote --tags "${remote}" | grep -q "refs/tags/v${new_version}$"; then if git ls-remote --tags "${remote}" | grep -q "refs/tags/v${new_version}$"; then
printf 'Tag %s already exists on %s' "v${new_version}" "${remote}" >&2 printf 'Tag %s already exists on %s\n' "v${new_version}" "${remote}" >&2
exit 1 exit 1
fi fi
done done

View File

@@ -33,6 +33,7 @@ pub struct ConfigTrees {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigProviderFilter { pub struct ConfigProviderFilter {
pub access: Option<bool>, pub access: Option<bool>,
pub owner: Option<bool>, pub owner: Option<bool>,
@@ -41,6 +42,7 @@ pub struct ConfigProviderFilter {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigProvider { pub struct ConfigProvider {
pub provider: RemoteProvider, pub provider: RemoteProvider,
pub token_command: String, pub token_command: String,
@@ -52,7 +54,6 @@ pub struct ConfigProvider {
pub api_url: Option<String>, pub api_url: Option<String>,
pub worktree: Option<bool>, pub worktree: Option<bool>,
pub init_worktree: Option<bool>,
pub remote_name: Option<String>, pub remote_name: Option<String>,
} }
@@ -182,6 +183,12 @@ impl Config {
filters.access.unwrap_or(false), filters.access.unwrap_or(false),
); );
if filter.empty() {
print_warning(
"The configuration does not contain any filters, so no repos will match",
);
}
let repos = match config.provider { let repos = match config.provider {
RemoteProvider::Github => { RemoteProvider::Github => {
match provider::Github::new(filter, token, config.api_url) { match provider::Github::new(filter, token, config.api_url) {
@@ -241,7 +248,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 = path::env_home().display().to_string(); let home = path::env_home();
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`.
@@ -299,7 +306,7 @@ pub fn read_config<'a, T>(path: &str) -> Result<T, String>
where where
T: for<'de> serde::Deserialize<'de>, T: for<'de> serde::Deserialize<'de>,
{ {
let content = match std::fs::read_to_string(&path) { let content = match std::fs::read_to_string(path) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
return Err(format!( return Err(format!(

View File

@@ -1,4 +1,4 @@
use clap::{AppSettings, Parser}; use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[clap( #[clap(
@@ -7,7 +7,6 @@ use clap::{AppSettings, Parser};
author = clap::crate_authors!("\n"), author = clap::crate_authors!("\n"),
about = clap::crate_description!(), about = clap::crate_description!(),
long_version = clap::crate_version!(), long_version = clap::crate_version!(),
global_setting(AppSettings::DeriveDisplayOrder),
propagate_version = true, propagate_version = true,
)] )]
pub struct Opts { pub struct Opts {
@@ -65,7 +64,7 @@ pub struct FindLocalArgs {
pub path: String, pub path: String,
#[clap( #[clap(
arg_enum, value_enum,
short, short,
long, long,
help = "Format to produce", help = "Format to produce",
@@ -85,7 +84,7 @@ pub struct FindConfigArgs {
pub config: String, pub config: String,
#[clap( #[clap(
arg_enum, value_enum,
short, short,
long, long,
help = "Format to produce", help = "Format to produce",
@@ -100,14 +99,14 @@ pub struct FindRemoteArgs {
#[clap(short, long, help = "Path to the configuration file")] #[clap(short, long, help = "Path to the configuration file")]
pub config: Option<String>, pub config: Option<String>,
#[clap(arg_enum, short, long, help = "Remote provider to use")] #[clap(value_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider, pub provider: RemoteProvider,
#[clap(short, long, help = "Name of the remote to use")] #[clap(short, long, help = "Name of the remote to use")]
pub remote_name: Option<String>, pub remote_name: Option<String>,
#[clap( #[clap(
multiple_occurrences = true, action = clap::ArgAction::Append,
name = "user", name = "user",
long, long,
help = "Users to get repositories from" help = "Users to get repositories from"
@@ -115,7 +114,7 @@ pub struct FindRemoteArgs {
pub users: Vec<String>, pub users: Vec<String>,
#[clap( #[clap(
multiple_occurrences = true, action = clap::ArgAction::Append,
name = "group", name = "group",
long, long,
help = "Groups to get repositories from" help = "Groups to get repositories from"
@@ -138,7 +137,7 @@ pub struct FindRemoteArgs {
pub root: String, pub root: String,
#[clap( #[clap(
arg_enum, value_enum,
short, short,
long, long,
help = "Format to produce", help = "Format to produce",
@@ -149,11 +148,10 @@ pub struct FindRemoteArgs {
#[clap( #[clap(
long, long,
help = "Use worktree setup for repositories", help = "Use worktree setup for repositories",
possible_values = &["true", "false"], value_parser = ["true", "false"],
default_value = "false", default_value = "false",
default_missing_value = "true", default_missing_value = "true",
min_values = 0, num_args = 0..=1,
max_values = 1,
)] )]
pub worktree: String, pub worktree: String,
@@ -174,12 +172,11 @@ pub struct Config {
#[clap( #[clap(
long, long,
value_parser = ["true", "false"],
help = "Check out the default worktree after clone", help = "Check out the default worktree after clone",
possible_values = &["true", "false"],
default_value = "true", default_value = "true",
default_missing_value = "true", default_missing_value = "true",
min_values = 0, num_args = 0..=1,
max_values = 1,
)] )]
pub init_worktree: String, pub init_worktree: String,
} }
@@ -189,14 +186,14 @@ pub type RemoteProvider = super::provider::RemoteProvider;
#[derive(Parser)] #[derive(Parser)]
#[clap()] #[clap()]
pub struct SyncRemoteArgs { pub struct SyncRemoteArgs {
#[clap(arg_enum, short, long, help = "Remote provider to use")] #[clap(value_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider, pub provider: RemoteProvider,
#[clap(short, long, help = "Name of the remote to use")] #[clap(short, long, help = "Name of the remote to use")]
pub remote_name: Option<String>, pub remote_name: Option<String>,
#[clap( #[clap(
multiple_occurrences = true, action = clap::ArgAction::Append,
name = "user", name = "user",
long, long,
help = "Users to get repositories from" help = "Users to get repositories from"
@@ -204,7 +201,7 @@ pub struct SyncRemoteArgs {
pub users: Vec<String>, pub users: Vec<String>,
#[clap( #[clap(
multiple_occurrences = true, action = clap::ArgAction::Append,
name = "group", name = "group",
long, long,
help = "Groups to get repositories from" help = "Groups to get repositories from"
@@ -229,11 +226,10 @@ pub struct SyncRemoteArgs {
#[clap( #[clap(
long, long,
help = "Use worktree setup for repositories", help = "Use worktree setup for repositories",
possible_values = &["true", "false"], value_parser = ["true", "false"],
default_value = "false", default_value = "false",
default_missing_value = "true", default_missing_value = "true",
min_values = 0, num_args = 0..=1,
max_values = 1,
)] )]
pub worktree: String, pub worktree: String,
@@ -243,11 +239,10 @@ pub struct SyncRemoteArgs {
#[clap( #[clap(
long, long,
help = "Check out the default worktree after clone", help = "Check out the default worktree after clone",
possible_values = &["true", "false"], value_parser = ["true", "false"],
default_value = "true", default_value = "true",
default_missing_value = "true", default_missing_value = "true",
min_values = 0, num_args = 0..=1,
max_values = 1,
)] )]
pub init_worktree: String, pub init_worktree: String,
} }
@@ -259,7 +254,7 @@ pub struct OptionalConfig {
pub config: Option<String>, pub config: Option<String>,
} }
#[derive(clap::ArgEnum, Clone)] #[derive(clap::ValueEnum, Clone)]
pub enum ConfigFormat { pub enum ConfigFormat {
Yaml, Yaml,
Toml, Toml,
@@ -299,7 +294,7 @@ pub struct WorktreeAddArgs {
#[clap(short = 't', long = "track", help = "Remote branch to track")] #[clap(short = 't', long = "track", help = "Remote branch to track")]
pub track: Option<String>, pub track: Option<String>,
#[clap(long = "--no-track", help = "Disable tracking")] #[clap(long = "no-track", help = "Disable tracking")]
pub no_track: bool, pub no_track: bool,
} }
#[derive(Parser)] #[derive(Parser)]
@@ -328,22 +323,19 @@ pub struct WorktreeFetchArgs {}
#[derive(Parser)] #[derive(Parser)]
pub struct WorktreePullArgs { pub struct WorktreePullArgs {
#[clap(long = "--rebase", help = "Perform a rebase instead of a fast-forward")] #[clap(long = "rebase", help = "Perform a rebase instead of a fast-forward")]
pub rebase: bool, pub rebase: bool,
#[clap(long = "--stash", help = "Stash & unstash changes before & after pull")] #[clap(long = "stash", help = "Stash & unstash changes before & after pull")]
pub stash: bool, pub stash: bool,
} }
#[derive(Parser)] #[derive(Parser)]
pub struct WorktreeRebaseArgs { pub struct WorktreeRebaseArgs {
#[clap(long = "--pull", help = "Perform a pull before rebasing")] #[clap(long = "pull", help = "Perform a pull before rebasing")]
pub pull: bool, pub pull: bool,
#[clap(long = "--rebase", help = "Perform a rebase when doing a pull")] #[clap(long = "rebase", help = "Perform a rebase when doing a pull")]
pub rebase: bool, pub rebase: bool,
#[clap( #[clap(long = "stash", help = "Stash & unstash changes before & after rebase")]
long = "--stash",
help = "Stash & unstash changes before & after rebase"
)]
pub stash: bool, pub stash: bool,
} }

View File

@@ -38,7 +38,7 @@ fn main() {
} }
} }
Err(error) => { Err(error) => {
print_error(&format!("Error syncing trees: {}", error)); print_error(&format!("Sync error: {}", error));
process::exit(1); process::exit(1);
} }
} }
@@ -55,6 +55,10 @@ fn main() {
let filter = let filter =
provider::Filter::new(args.users, args.groups, args.owner, args.access); provider::Filter::new(args.users, args.groups, args.owner, args.access);
if filter.empty() {
print_warning("You did not specify any filters, so no repos will match");
}
let worktree = args.worktree == "true"; let worktree = args.worktree == "true";
let repos = match args.provider { let repos = match args.provider {
@@ -62,7 +66,7 @@ fn main() {
match 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!("Sync error: {}", error));
process::exit(1); process::exit(1);
} }
} }
@@ -76,7 +80,7 @@ fn main() {
match 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!("Sync error: {}", error));
process::exit(1); process::exit(1);
} }
} }
@@ -112,13 +116,13 @@ fn main() {
} }
} }
Err(error) => { Err(error) => {
print_error(&format!("Error syncing trees: {}", error)); print_error(&format!("Sync error: {}", error));
process::exit(1); process::exit(1);
} }
} }
} }
Err(error) => { Err(error) => {
print_error(&format!("Error: {}", error)); print_error(&format!("Sync error: {}", error));
process::exit(1); process::exit(1);
} }
} }
@@ -278,6 +282,10 @@ fn main() {
filters.access.unwrap_or(false), filters.access.unwrap_or(false),
); );
if filter.empty() {
print_warning("You did not specify any filters, so no repos will match");
}
let repos = match config.provider { let repos = match config.provider {
provider::RemoteProvider::Github => { provider::RemoteProvider::Github => {
match match provider::Github::new(filter, token, config.api_url) { match match provider::Github::new(filter, token, config.api_url) {
@@ -383,6 +391,10 @@ fn main() {
let filter = let filter =
provider::Filter::new(args.users, args.groups, args.owner, args.access); provider::Filter::new(args.users, args.groups, args.owner, args.access);
if filter.empty() {
print_warning("You did not specify any filters, so no repos will match");
}
let worktree = args.worktree == "true"; let worktree = args.worktree == "true";
let repos = match args.provider { let repos = match args.provider {

View File

@@ -106,7 +106,7 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)
}, },
) )
} else { } else {
let name = path.strip_prefix(&root).unwrap(); let name = path.strip_prefix(root).unwrap();
let namespace = name.parent().unwrap(); let namespace = name.parent().unwrap();
( (
if namespace != Path::new("") { if namespace != Path::new("") {

View File

@@ -47,9 +47,9 @@ pub fn path_as_string(path: &Path) -> String {
path.to_path_buf().into_os_string().into_string().unwrap() path.to_path_buf().into_os_string().into_string().unwrap()
} }
pub fn env_home() -> PathBuf { pub fn env_home() -> String {
match std::env::var("HOME") { match std::env::var("HOME") {
Ok(path) => Path::new(&path).to_path_buf(), Ok(path) => path,
Err(e) => { Err(e) => {
print_error(&format!("Unable to read HOME: {}", e)); print_error(&format!("Unable to read HOME: {}", e));
process::exit(1); process::exit(1);
@@ -58,16 +58,12 @@ pub fn env_home() -> PathBuf {
} }
pub fn expand_path(path: &Path) -> PathBuf { pub fn expand_path(path: &Path) -> PathBuf {
fn home_dir() -> Option<PathBuf> {
Some(env_home())
}
let expanded_path = match shellexpand::full_with_context( let expanded_path = match shellexpand::full_with_context(
&path_as_string(path), &path_as_string(path),
home_dir, || Some(env_home()),
|name| -> Result<Option<String>, &'static str> { |name| -> Result<Option<String>, &'static str> {
match name { match name {
"HOME" => Ok(Some(path_as_string(home_dir().unwrap().as_path()))), "HOME" => Ok(Some(env_home())),
_ => Ok(None), _ => Ok(None),
} }
}, },

View File

@@ -16,7 +16,7 @@ use std::collections::HashMap;
const DEFAULT_REMOTE_NAME: &str = "origin"; const DEFAULT_REMOTE_NAME: &str = "origin";
#[derive(Debug, Deserialize, Serialize, clap::ArgEnum, Clone)] #[derive(Debug, Deserialize, Serialize, clap::ValueEnum, Clone)]
pub enum RemoteProvider { pub enum RemoteProvider {
#[serde(alias = "github", alias = "GitHub")] #[serde(alias = "github", alias = "GitHub")]
Github, Github,
@@ -89,6 +89,10 @@ impl Filter {
access, access,
} }
} }
pub fn empty(&self) -> bool {
self.users.is_empty() && self.groups.is_empty() && !self.owner && !self.access
}
} }
pub enum ApiErrorResponse<T> pub enum ApiErrorResponse<T>
@@ -270,12 +274,16 @@ pub trait Provider {
} }
for group in &self.filter().groups { for group in &self.filter().groups {
let group_projects = self let group_projects = self.get_group_projects(group).map_err(|error| {
.get_group_projects(group) format!(
.map_err(|error| match error { "group \"{}\": {}",
ApiErrorResponse::Json(x) => x.to_string(), group,
ApiErrorResponse::String(s) => s, match error {
})?; ApiErrorResponse::Json(x) => x.to_string(),
ApiErrorResponse::String(s) => s,
}
)
})?;
for group_project in group_projects { for group_project in group_projects {
let mut already_present = false; let mut already_present = false;
for repo in &repos { for repo in &repos {

View File

@@ -785,7 +785,7 @@ impl RepoHandle {
)) ))
})?; })?;
for entry in match std::fs::read_dir(&root_dir) { for entry in match std::fs::read_dir(root_dir) {
Ok(iterator) => iterator, Ok(iterator) => iterator,
Err(error) => { Err(error) => {
return Err(WorktreeConversionFailureReason::Error(format!( return Err(WorktreeConversionFailureReason::Error(format!(
@@ -1343,7 +1343,7 @@ impl RepoHandle {
}, },
}) })
{ {
let repo_dir = &directory.join(&worktree.name()); let repo_dir = &directory.join(worktree.name());
if repo_dir.exists() { if repo_dir.exists() {
match self.remove_worktree( match self.remove_worktree(
directory, directory,
@@ -1387,12 +1387,12 @@ impl RepoHandle {
.map_err(|error| format!("Getting worktrees failed: {}", error))?; .map_err(|error| format!("Getting worktrees failed: {}", error))?;
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 = path::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()
.strip_prefix(&directory) .strip_prefix(directory)
// that unwrap() is safe as each entry is // that unwrap() is safe as each entry is
// guaranteed to be a subentry of &directory // guaranteed to be a subentry of &directory
.unwrap(), .unwrap(),

View File

@@ -111,7 +111,7 @@ pub fn get_worktree_status_table(
add_worktree_table_header(&mut table); add_worktree_table_header(&mut 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 repo::RepoHandle::open(worktree_dir, false) { let repo = match repo::RepoHandle::open(worktree_dir, false) {
Ok(repo) => repo, Ok(repo) => repo,
@@ -185,7 +185,11 @@ pub fn get_status_table(config: config::Config) -> Result<(Vec<Table>, Vec<Strin
} }
}; };
add_repo_status(&mut table, &repo.name, &repo_handle, repo.worktree_setup)?; if let Err(err) =
add_repo_status(&mut table, &repo.name, &repo_handle, repo.worktree_setup)
{
errors.push(format!("{}: Couldn't add repo status: {}", &repo.name, err));
}
} }
tables.push(table); tables.push(table);

View File

@@ -372,7 +372,7 @@ impl<'a> Worktree<'a, WithRemoteTrackingBranch<'a>> {
// TECHDEBT // TECHDEBT
// We must not call this with `Some()` without a valid target. // We must not call this with `Some()` without a valid target.
// I'm sure this can be improved, just not sure how. // I'm sure this can be improved, just not sure how.
&*self.extra.target_commit.unwrap(), &self.extra.target_commit.unwrap(),
)? )?
}; };