86 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
0746be904a Merge branch 'develop' 2022-07-21 20:05:57 +02:00
53c2ee404c Release v0.7.6 2022-07-21 20:05:57 +02:00
bd694c3f7d just: Add pushall target for easier releases 2022-07-21 20:05:22 +02:00
95e9fcbffe Cargo.lock: Updating pin-project v1.0.10 -> v1.0.11 2022-07-21 19:49:53 +02:00
98665a3231 Cargo.lock: Updating openssl-sys v0.9.74 -> v0.9.75 2022-07-21 19:49:48 +02:00
7a51ad135f Cargo.lock: Updating rustversion v1.0.7 -> v1.0.8 2022-07-21 19:49:46 +02:00
e386935bc7 Cargo.lock: Updating bytes v1.1.0 -> v1.2.0 2022-07-21 19:49:44 +02:00
c62562e6f0 dependencies: Update serde_yaml to 0.8.26 2022-07-21 19:49:43 +02:00
00e37996b7 dependencies: Update regex to 1.6.0 2022-07-21 19:49:43 +02:00
a7e2c61984 dependencies: Update clap to 3.2.14 2022-07-21 19:49:43 +02:00
58919b2d58 dependencies: Update serde to 1.0.140 2022-07-21 19:49:42 +02:00
dd36eb886f Merge pull request #42 from hakoerber/dependabot/cargo/openssl-src-111.22.01.1.1q
build(deps): bump openssl-src from 111.21.0+1.1.1p to 111.22.0+1.1.1q
2022-07-07 08:01:05 +02:00
dependabot[bot]
d2e01db0ae build(deps): bump openssl-src from 111.21.0+1.1.1p to 111.22.0+1.1.1q
Bumps [openssl-src](https://github.com/alexcrichton/openssl-src-rs) from 111.21.0+1.1.1p to 111.22.0+1.1.1q.
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

---
updated-dependencies:
- dependency-name: openssl-src
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-07 05:55:19 +00:00
bfd7b01ea4 Use en_US for spelling 2022-06-30 20:33:51 +02:00
21 changed files with 427 additions and 314 deletions

491
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.5" 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.137" 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.8" 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.5.6" 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.24" 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
@@ -32,6 +34,13 @@ build-release:
build-release-static: build-release-static:
cargo build --release --target {{static_target}} --features=static-build cargo build --release --target {{static_target}} --features=static-build
pushall:
for r in $(git remote) ; do \
for branch in develop master ; do \
git push $r $branch ; \
done ; \
done
release-patch: release-patch:
./release.sh patch ./release.sh patch

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

@@ -10,7 +10,7 @@
- [Git Worktrees](./worktrees.md) - [Git Worktrees](./worktrees.md)
- [Working with Worktrees](./worktree_working.md) - [Working with Worktrees](./worktree_working.md)
- [Worktrees and Remotes](./worktree_remotes.md) - [Worktrees and Remotes](./worktree_remotes.md)
- [Behaviour Details](./worktree_behaviour.md) - [Behavior Details](./worktree_behavior.md)
- [FAQ](./faq.md) - [FAQ](./faq.md)
- [Developer Documentation](./developing.md) - [Developer Documentation](./developing.md)
- [Testing](./testing.md) - [Testing](./testing.md)

View File

@@ -16,7 +16,7 @@ functions (like validation of certain input).
## E2E tests ## E2E tests
The main focus of the testing setup lays on the e2e tests. Each user-facing The main focus of the testing setup lays on the e2e tests. Each user-facing
behaviour *should* have a corresponding e2e test. These are the most important behavior *should* have a corresponding e2e test. These are the most important
tests, as they test functionality the user will use in the end. tests, as they test functionality the user will use in the end.
The test suite is written in python and uses The test suite is written in python and uses
@@ -28,7 +28,7 @@ Effectively, each tests works like this:
* Set up some prerequisites (e.g. different git repositories or configuration * Set up some prerequisites (e.g. different git repositories or configuration
files) files)
* Run `grm` * Run `grm`
* Check that everything is according to expected behaviour (e.g. that `grm` had * Check that everything is according to expected behavior (e.g. that `grm` had
certain output and exit code, that the target repositories have certain certain output and exit code, that the target repositories have certain
branches, heads and remotes, ...) branches, heads and remotes, ...)
@@ -39,8 +39,8 @@ configuration exists, what a config value is set to, how the repository looks
like, ...) like, ...)
Whenever you write a new test, think about the different circumstances that can Whenever you write a new test, think about the different circumstances that can
happen. What are the failure modes? What affects the behaviour? Parametrize each happen. What are the failure modes? What affects the behavior? Parametrize each
of these behaviours. of these behaviors.
### Optimization ### Optimization

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

@@ -70,7 +70,7 @@ def test_worktree_add(
explicit_track_branch_name = f"{default_remote}/{worktree_name}" explicit_track_branch_name = f"{default_remote}/{worktree_name}"
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat() timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
# GitPython has some weird behaviour here. It is not possible to use kwargs # GitPython has some weird behavior here. It is not possible to use kwargs
# to set the commit and author date. # to set the commit and author date.
# #
# `committer_date=x` (which is documented) does not work, as `git commit` # `committer_date=x` (which is documented) does not work, as `git commit`
@@ -79,7 +79,7 @@ def test_worktree_add(
# `author_date=x` does not work, as it's now called --date in `git commit` # `author_date=x` does not work, as it's now called --date in `git commit`
# #
# `date=x` should work, but is refused by GitPython, as it does not know # `date=x` should work, but is refused by GitPython, as it does not know
# about the new behaviour in `git commit` # about the new behavior in `git commit`
# #
# Fortunately, there are env variables that control those timestamps. # Fortunately, there are env variables that control those timestamps.
os.environ["GIT_COMMITTER_DATE"] = str(timestamp) os.environ["GIT_COMMITTER_DATE"] = str(timestamp)

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

@@ -233,7 +233,7 @@ impl Worktree {
let operation = operation.map_err(convert_libgit2_error)?; let operation = operation.map_err(convert_libgit2_error)?;
// This is required to preserve the commiter of the rebased // This is required to preserve the commiter of the rebased
// commits, which is the expected behaviour. // commits, which is the expected behavior.
let rebased_commit = repo let rebased_commit = repo
.0 .0
.find_commit(operation.id()) .find_commit(operation.id())
@@ -357,7 +357,7 @@ impl Worktree {
let operation = operation.map_err(convert_libgit2_error)?; let operation = operation.map_err(convert_libgit2_error)?;
// This is required to preserve the commiter of the rebased // This is required to preserve the commiter of the rebased
// commits, which is the expected behaviour. // commits, which is the expected behavior.
let rebased_commit = repo let rebased_commit = repo
.0 .0
.find_commit(operation.id()) .find_commit(operation.id())
@@ -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

@@ -81,9 +81,9 @@
//! * Instead of just picking `origin/prefix/foobar`, grm will complain because //! * Instead of just picking `origin/prefix/foobar`, grm will complain because
//! it also selected `remote2/foobar`. //! it also selected `remote2/foobar`.
//! //!
//! This is just emergent behaviour of the logic above. Fixing it would require //! This is just emergent behavior of the logic above. Fixing it would require
//! additional logic for that edge case. I assume that it's just so rare to get //! additional logic for that edge case. I assume that it's just so rare to get
//! that behaviour that it's acceptable for now. //! that behavior that it's acceptable for now.
//! //!
//! Now we either have a commit, we aborted, or we do not have commit. In the //! Now we either have a commit, we aborted, or we do not have commit. In the
//! last case, as stated above, we check out the "default" branch. //! last case, as stated above, we check out the "default" branch.
@@ -137,7 +137,7 @@
//! `foobar`. As both `remote1/foobar` and `remote2/foobar` as the same, the new //! `foobar`. As both `remote1/foobar` and `remote2/foobar` as the same, the new
//! worktree will use that as the state of the new branch. But as `grm` cannot //! worktree will use that as the state of the new branch. But as `grm` cannot
//! tell which remote branch to track, it will not set up remote tracking. This //! tell which remote branch to track, it will not set up remote tracking. This
//! behaviour may be a bit confusing, but first, there is no good way to resolve //! behavior may be a bit confusing, but first, there is no good way to resolve
//! this, and second, the situation should be really rare (when having multiple //! this, and second, the situation should be really rare (when having multiple
//! remotes, you would generally have a `default_remote` configured). //! remotes, you would generally have a `default_remote` configured).
//! //!
@@ -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(),
)? )?
}; };
@@ -463,7 +463,7 @@ impl<'a> Worktree<'a, WithRemoteTrackingBranch<'a>> {
// //
// > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test // > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test
// //
// This is a discrepancy between the behaviour of libgit2 and the // This is a discrepancy between the behavior of libgit2 and the
// git CLI when creating worktrees with slashes: // git CLI when creating worktrees with slashes:
// //
// The git CLI will create the worktree's configuration directory // The git CLI will create the worktree's configuration directory