Compare commits
65 Commits
23fc942db7
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| b9051d5afb | |||
| 6c6295651f | |||
| 8c418ff846 | |||
| 29b3bd3581 | |||
|
|
012c6efb03 | ||
| 241bf473a7 | |||
| 8fd663462e | |||
| 4beacbf65d | |||
| 102f5561a8 | |||
| e04f065d42 | |||
| 941dd50868 | |||
| d20dabc91e | |||
| 0e63a1c6bf | |||
| 9792c09850 | |||
| a1519a6bc5 | |||
| 36535dcaec | |||
| 32f94b1ef5 | |||
| 913df16f28 | |||
| f66a512a83 | |||
| de15e799ac | |||
| a8736ed37f | |||
| 1a45887fb6 | |||
| 9403156edf | |||
| 21e3a9b9bb | |||
| ca0c9c28fd | |||
| 1edc61d6e6 | |||
| b20bba529a | |||
| fb0948787a | |||
| 625457e474 | |||
| d4b7cabcf2 | |||
| b2727c7a96 | |||
| ff3cbfbdba | |||
| 44602e7bc2 | |||
| 1706df7236 | |||
| 80fc28c44a | |||
| 7335c0fc62 | |||
| a536e688c9 | |||
| 0d22b43ed0 | |||
| 9d7f566209 | |||
| 1e6f965f7a | |||
| 6183a58204 | |||
| 2a4934b01a | |||
| fc4261b7ac | |||
| 7d248c5ea3 | |||
| 8d4af73364 | |||
|
|
4c738d027a | ||
|
|
f2fa3411d8 | ||
|
|
19443bc4ca | ||
| 60a777276f | |||
|
|
1262ec5a33 | ||
|
|
4c6b69e125 | ||
|
|
28881a23a9 | ||
|
|
e796362e6b | ||
|
|
37094a3295 | ||
|
|
100bac8f87 | ||
|
|
fdafa3aa81 | ||
|
|
d267564bca | ||
|
|
2cc477e551 | ||
|
|
8cbdd9f408 | ||
|
|
21be3e40dd | ||
|
|
a3824c2671 | ||
|
|
8eeb010c3a | ||
|
|
956b172426 | ||
| 9b4ed2837e | |||
| 701e64df6f |
@@ -1,2 +1,3 @@
|
|||||||
nonnominandus
|
nonnominandus
|
||||||
Maximilian Volk
|
Maximilian Volk
|
||||||
|
Baptiste (@BapRx)
|
||||||
|
|||||||
840
Cargo.lock
generated
840
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "git-repo-manager"
|
name = "git-repo-manager"
|
||||||
version = "0.7.11"
|
version = "0.7.15"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
@@ -23,7 +23,7 @@ repository = "https://github.com/hakoerber/git-repo-manager"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
# Required for `std::path::Path::is_symlink()`. Will be released with 1.57.
|
# Required for `std::path::Path::is_symlink()`. Will be released with 1.57.
|
||||||
rust-version = "1.57"
|
rust-version = "1.58"
|
||||||
|
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
@@ -41,36 +41,36 @@ path = "src/grm/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
[dependencies.toml]
|
[dependencies.toml]
|
||||||
version = "=0.5.9"
|
version = "=0.8.6"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "=1.0.150"
|
version = "=1.0.190"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
[dependencies.git2]
|
[dependencies.git2]
|
||||||
version = "=0.15.0"
|
version = "=0.18.1"
|
||||||
|
|
||||||
[dependencies.shellexpand]
|
[dependencies.shellexpand]
|
||||||
version = "=3.0.0"
|
version = "=3.1.0"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "=4.0.29"
|
version = "=4.4.7"
|
||||||
features = ["derive", "cargo"]
|
features = ["derive", "cargo"]
|
||||||
|
|
||||||
[dependencies.console]
|
[dependencies.console]
|
||||||
version = "=0.15.2"
|
version = "=0.15.7"
|
||||||
|
|
||||||
[dependencies.regex]
|
[dependencies.regex]
|
||||||
version = "=1.7.0"
|
version = "=1.10.2"
|
||||||
|
|
||||||
[dependencies.comfy-table]
|
[dependencies.comfy-table]
|
||||||
version = "=6.1.3"
|
version = "=7.1.0"
|
||||||
|
|
||||||
[dependencies.serde_yaml]
|
[dependencies.serde_yaml]
|
||||||
version = "=0.9.14"
|
version = "=0.9.27"
|
||||||
|
|
||||||
[dependencies.serde_json]
|
[dependencies.serde_json]
|
||||||
version = "=1.0.89"
|
version = "=1.0.108"
|
||||||
|
|
||||||
[dependencies.isahc]
|
[dependencies.isahc]
|
||||||
version = "=1.7.2"
|
version = "=1.7.2"
|
||||||
|
|||||||
42
Justfile
42
Justfile
@@ -4,35 +4,39 @@ set shell := ["/bin/bash", "-c"]
|
|||||||
|
|
||||||
static_target := "x86_64-unknown-linux-musl"
|
static_target := "x86_64-unknown-linux-musl"
|
||||||
|
|
||||||
|
cargo := "cargo"
|
||||||
|
|
||||||
check: fmt-check lint test
|
check: fmt-check lint test
|
||||||
cargo check
|
{{cargo}} check
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cargo clean
|
{{cargo}} clean
|
||||||
git clean -f -d -X
|
git clean -f -d -X
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
cargo fmt
|
{{cargo}} fmt
|
||||||
|
git ls-files | grep '\.py$' | xargs isort
|
||||||
git ls-files | grep '\.py$' | xargs black
|
git ls-files | grep '\.py$' | xargs black
|
||||||
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --write
|
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --write
|
||||||
|
|
||||||
fmt-check:
|
fmt-check:
|
||||||
cargo fmt --check
|
{{cargo}} fmt --check
|
||||||
git ls-files | grep '\.py$' | xargs black --check
|
git ls-files | grep '\.py$' | xargs black --check
|
||||||
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --diff
|
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --diff
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
cargo clippy --no-deps -- -Dwarnings
|
{{cargo}} clippy --no-deps -- -Dwarnings
|
||||||
|
git ls-files | grep '\.py$' | xargs ruff --ignore E501
|
||||||
git ls-files | grep '\.sh$' | xargs -L 1 shellcheck --norc
|
git ls-files | grep '\.sh$' | xargs -L 1 shellcheck --norc
|
||||||
|
|
||||||
lint-fix:
|
lint-fix:
|
||||||
cargo clippy --no-deps --fix
|
{{cargo}} clippy --no-deps --fix
|
||||||
|
|
||||||
build-release:
|
build-release:
|
||||||
cargo build --release
|
{{cargo}} 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:
|
pushall:
|
||||||
for r in $(git remote) ; do \
|
for r in $(git remote) ; do \
|
||||||
@@ -48,38 +52,38 @@ test-binary:
|
|||||||
env \
|
env \
|
||||||
GITHUB_API_BASEURL=http://rest:5000/github \
|
GITHUB_API_BASEURL=http://rest:5000/github \
|
||||||
GITLAB_API_BASEURL=http://rest:5000/gitlab \
|
GITLAB_API_BASEURL=http://rest:5000/gitlab \
|
||||||
cargo build --profile e2e-tests --target {{static_target}} --features=static-build
|
{{cargo}} build --profile e2e-tests --target {{static_target}} --features=static-build
|
||||||
|
|
||||||
install:
|
install:
|
||||||
cargo install --path .
|
{{cargo}} install --path .
|
||||||
|
|
||||||
install-static:
|
install-static:
|
||||||
cargo install --target {{static_target}} --features=static-build --path .
|
{{cargo}} install --target {{static_target}} --features=static-build --path .
|
||||||
|
|
||||||
build:
|
build:
|
||||||
cargo build
|
{{cargo}} build
|
||||||
|
|
||||||
build-static:
|
build-static:
|
||||||
cargo build --target {{static_target}} --features=static-build
|
{{cargo}} build --target {{static_target}} --features=static-build
|
||||||
|
|
||||||
test: test-unit test-integration test-e2e
|
test: test-unit test-integration test-e2e
|
||||||
|
|
||||||
test-unit +tests="":
|
test-unit +tests="":
|
||||||
cargo test --lib --bins -- --show-output {{tests}}
|
{{cargo}} test --lib --bins -- --show-output {{tests}}
|
||||||
|
|
||||||
test-integration:
|
test-integration:
|
||||||
cargo test --test "*"
|
{{cargo}} test --test "*"
|
||||||
|
|
||||||
test-e2e +tests=".": test-binary
|
test-e2e +tests=".": test-binary
|
||||||
cd ./e2e_tests \
|
cd ./e2e_tests \
|
||||||
&& docker-compose rm --stop -f \
|
&& docker compose rm --stop -f \
|
||||||
&& docker-compose build \
|
&& docker compose build \
|
||||||
&& docker-compose run \
|
&& docker compose run \
|
||||||
--rm \
|
--rm \
|
||||||
-v $PWD/../target/x86_64-unknown-linux-musl/e2e-tests/grm:/grm \
|
-v $PWD/../target/x86_64-unknown-linux-musl/e2e-tests/grm:/grm \
|
||||||
pytest \
|
pytest \
|
||||||
"GRM_BINARY=/grm ALTERNATE_DOMAIN=alternate-rest python3 -m pytest --exitfirst -p no:cacheprovider --color=yes "$@"" \
|
"GRM_BINARY=/grm ALTERNATE_DOMAIN=alternate-rest python3 -m pytest --exitfirst -p no:cacheprovider --color=yes "$@"" \
|
||||||
&& docker-compose rm --stop -f
|
&& docker compose rm --stop -f
|
||||||
|
|
||||||
update-dependencies: update-cargo-dependencies
|
update-dependencies: update-cargo-dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ in once place?
|
|||||||
|
|
||||||
This is how GRM came to be. I'm a fan of infrastructure-as-code, and GRM is a bit
|
This is how GRM came to be. I'm a fan of infrastructure-as-code, and GRM is a bit
|
||||||
like Terraform for your local git repositories. Write a config, run the tool, and
|
like Terraform for your local git repositories. Write a config, run the tool, and
|
||||||
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 is the list of
|
||||||
repositories itself.
|
repositories itself.
|
||||||
|
|
||||||
# Crates
|
# Crates
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import semver
|
import semver
|
||||||
import tomlkit
|
import tomlkit
|
||||||
@@ -94,15 +93,7 @@ for tier in ["dependencies", "dev-dependencies"]:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
cmd = subprocess.run(
|
cmd = subprocess.run(
|
||||||
[
|
["cargo", "update", "--offline", "--aggressive", "--package", name],
|
||||||
"cargo",
|
|
||||||
"update",
|
|
||||||
"-Z",
|
|
||||||
"no-index-update",
|
|
||||||
"--aggressive",
|
|
||||||
"--package",
|
|
||||||
name,
|
|
||||||
],
|
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -136,15 +127,7 @@ while True:
|
|||||||
spec = f"{package['name']}:{package['version']}"
|
spec = f"{package['name']}:{package['version']}"
|
||||||
try:
|
try:
|
||||||
cmd = subprocess.run(
|
cmd = subprocess.run(
|
||||||
[
|
["cargo", "update", "--offline", "--aggressive", "--package", spec],
|
||||||
"cargo",
|
|
||||||
"update",
|
|
||||||
"-Z",
|
|
||||||
"no-index-update",
|
|
||||||
"--aggressive",
|
|
||||||
"--package",
|
|
||||||
spec,
|
|
||||||
],
|
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ Feature branches are not required, there are also changes happening directly on
|
|||||||
|
|
||||||
You will need the following tools:
|
You will need the following tools:
|
||||||
|
|
||||||
* Rust (obviously) (easiest via `rustup`), with the nightly toolchain
|
* Rust (obviously) (easiest via `rustup`)
|
||||||
* Python3
|
* Python3
|
||||||
* [`just`](https://github.com/casey/just), a command runner like `make`. See
|
* [`just`](https://github.com/casey/just), a command runner like `make`. See
|
||||||
[here](https://github.com/casey/just#installation) for installation
|
[here](https://github.com/casey/just#installation) for installation
|
||||||
instructions (it's most likely just a simple `cargo install just`).
|
instructions (it's most likely just a simple `cargo install just`).
|
||||||
* Docker & docker-compose for the e2e tests
|
* Docker & docker-compose for the e2e tests
|
||||||
* `black` and `shfmt` for formatting.
|
* `isort`, `black` and `shfmt` for formatting.
|
||||||
* `shellcheck` for shell script linting
|
* `ruff` and `shellcheck` for linting.
|
||||||
* `mdbook` for the documentation
|
* `mdbook` for the documentation
|
||||||
|
|
||||||
Here are the tools:
|
Here are the tools:
|
||||||
@@ -52,18 +52,3 @@ mvdan.cc/sh/v3/cmd/shfmt@latest`, depending on your go build environment.
|
|||||||
|
|
||||||
For details about rustup and the toolchains, see [the installation
|
For details about rustup and the toolchains, see [the installation
|
||||||
section](./installation.md).
|
section](./installation.md).
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### Why nightly?
|
|
||||||
|
|
||||||
For now, GRM requires the nightly toolchain for two reasons:
|
|
||||||
|
|
||||||
* [`io_error_more`](https://github.com/rust-lang/rust/issues/86442) to get
|
|
||||||
better error messages on IO errors
|
|
||||||
* [`const_option_ext`](https://github.com/rust-lang/rust/issues/91930) to have
|
|
||||||
static variables read from the environment that fall back to hard coded
|
|
||||||
defaults
|
|
||||||
|
|
||||||
Honestly, both of those are not really necessary or can be handled without
|
|
||||||
nightly. It's just that I'm using nightly anyway.
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Building GRM currently requires the nightly Rust toolchain. The easiest way is
|
Building GRM requires the Rust toolchain to be installed. The easiest way is
|
||||||
using [`rustup`](https://rustup.rs/). Make sure that rustup is properly
|
using [`rustup`](https://rustup.rs/). Make sure that rustup is properly
|
||||||
installed.
|
installed.
|
||||||
|
|
||||||
Make sure that the nightly toolchain is installed:
|
Make sure that the stable toolchain is installed:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rustup toolchain install nightly
|
$ rustup toolchain install stable
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, install the build dependencies:
|
Then, install the build dependencies:
|
||||||
@@ -22,13 +22,13 @@ Then, install the build dependencies:
|
|||||||
Then, it's a simple command to install the latest stable version:
|
Then, it's a simple command to install the latest stable version:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo +nightly install git-repo-manager
|
$ cargo install git-repo-manager
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're brave, you can also run the development build:
|
If you're brave, you can also run the development build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo +nightly install --git https://github.com/hakoerber/git-repo-manager.git --branch develop
|
$ cargo install --git https://github.com/hakoerber/git-repo-manager.git --branch develop
|
||||||
```
|
```
|
||||||
|
|
||||||
## Static build
|
## Static build
|
||||||
@@ -47,11 +47,11 @@ need `musl` and a few other build dependencies installed installed:
|
|||||||
The, add the musl target via `rustup`:
|
The, add the musl target via `rustup`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rustup +nightly target add x86_64-unknown-linux-musl
|
$ rustup target add x86_64-unknown-linux-musl
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, use a modified build command to get a statically linked binary:
|
Then, use a modified build command to get a statically linked binary:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo +nightly install git-repo-manager --target x86_64-unknown-linux-musl --features=static-build
|
$ cargo install git-repo-manager --target x86_64-unknown-linux-musl --features=static-build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Let's try it out:
|
|||||||
## Get the example configuration
|
## Get the example configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ curl --proto '=https' --tlsv1.2 -sSfO https://raw.githubusercontent.com/hakoerber/git-repo-manager/master/example.config.toml
|
curl --proto '=https' --tlsv1.2 -sSfO https://raw.githubusercontent.com/hakoerber/git-repo-manager/master/example.config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, you're ready to run the first sync. This will clone all configured
|
Then, you're ready to run the first sync. This will clone all configured
|
||||||
@@ -30,7 +30,7 @@ $ grm repos sync config --config example.config.toml
|
|||||||
|
|
||||||
If you run it again, it will report no changes:
|
If you run it again, it will report no changes:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ grm repos sync config -c example.config.toml
|
$ grm repos sync config -c example.config.toml
|
||||||
[✔] git-repo-manager: OK
|
[✔] git-repo-manager: OK
|
||||||
[✔] dotfiles: OK
|
[✔] dotfiles: OK
|
||||||
@@ -43,11 +43,18 @@ write a configuration from scratch. Luckily, GRM has a way to generate a
|
|||||||
configuration from an existing file tree:
|
configuration from an existing file tree:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ grm repos find local ~/your/project/root > config.toml
|
grm repos find local ~/your/project/root > config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
This will detect all repositories and remotes and write them to `config.toml`.
|
This will detect all repositories and remotes and write them to `config.toml`.
|
||||||
|
|
||||||
|
You can exclude repositories from the generated configuration by providing
|
||||||
|
a regex that will be test against the path of each discovered repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grm repos find local ~/your/project/root --exclude "^.*/subdir/match-(foo|bar)/.*$" > config.toml
|
||||||
|
```
|
||||||
|
|
||||||
### Show the state of your projects
|
### Show the state of your projects
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -65,7 +72,7 @@ $ grm repos status --config example.config.toml
|
|||||||
You can also use `status` without `--config` to check the repository you're
|
You can also use `status` without `--config` to check the repository you're
|
||||||
currently in:
|
currently in:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ cd ~/example-projects/dotfiles
|
$ cd ~/example-projects/dotfiles
|
||||||
$ grm repos status
|
$ grm repos status
|
||||||
╭──────────┬──────────┬────────┬──────────┬───────┬─────────╮
|
╭──────────┬──────────┬────────┬──────────┬───────┬─────────╮
|
||||||
@@ -79,5 +86,5 @@ $ grm repos status
|
|||||||
|
|
||||||
By default, the repo configuration uses TOML. If you prefer YAML, just give it a
|
By default, the repo configuration uses TOML. If you prefer YAML, just give it a
|
||||||
YAML file instead (file ending does not matter, `grm` will figure out the
|
YAML file instead (file ending does not matter, `grm` will figure out the
|
||||||
format). For generating a configuration, pass `--format yaml` to `grm repo
|
format). For generating a configuration, pass `--format yaml` to `grm repo
|
||||||
find` which generates a YAML configuration instead of a TOML configuration.
|
find` which generates a YAML configuration instead of a TOML configuration.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Note: You will most likely not need to read this.
|
|||||||
Each test parameter will exponentially increase the number of tests that will be
|
Each test parameter will exponentially increase the number of tests that will be
|
||||||
run. As a general rule, comprehensiveness is more important than test suite
|
run. As a general rule, comprehensiveness is more important than test suite
|
||||||
runtime (so if in doubt, better to add another parameter to catch every edge
|
runtime (so if in doubt, better to add another parameter to catch every edge
|
||||||
case). But try to keep the total runtime sane. Currently, the whole `just e2e`
|
case). But try to keep the total runtime sane. Currently, the whole `just test-e2e`
|
||||||
target runs ~8'000 tests and takes around 5 minutes on my machine, exlucding
|
target runs ~8'000 tests and takes around 5 minutes on my machine, exlucding
|
||||||
binary and docker build time. I'd say that keeping it under 10 minutes is a good
|
binary and docker build time. I'd say that keeping it under 10 minutes is a good
|
||||||
idea.
|
idea.
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
os.environ["GIT_AUTHOR_NAME"] = "Example user"
|
os.environ["GIT_AUTHOR_NAME"] = "Example user"
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ from flask import Flask
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
|
|
||||||
import github
|
import github # noqa: E402,F401
|
||||||
import gitlab
|
import gitlab # noqa: E402,F401
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
from flask import Flask, request, abort, jsonify, make_response
|
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
from app import app
|
||||||
|
from flask import abort, jsonify, make_response, request
|
||||||
|
|
||||||
|
|
||||||
def check_headers():
|
def check_headers():
|
||||||
@@ -48,7 +46,7 @@ def add_pagination(response, page, last_page):
|
|||||||
|
|
||||||
def read_project_files(namespaces=[]):
|
def read_project_files(namespaces=[]):
|
||||||
last_page = 4
|
last_page = 4
|
||||||
page = username = int(request.args.get("page", "1"))
|
page = int(request.args.get("page", "1"))
|
||||||
response_file = f"./github_api_page_{page}.json.j2"
|
response_file = f"./github_api_page_{page}.json.j2"
|
||||||
if not os.path.exists(response_file):
|
if not os.path.exists(response_file):
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
from flask import Flask, request, abort, jsonify, make_response
|
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
from app import app
|
||||||
|
from flask import abort, jsonify, make_response, request
|
||||||
|
|
||||||
|
|
||||||
def check_headers():
|
def check_headers():
|
||||||
@@ -48,7 +46,7 @@ def add_pagination(response, page, last_page):
|
|||||||
|
|
||||||
def read_project_files(namespaces=[]):
|
def read_project_files(namespaces=[]):
|
||||||
last_page = 4
|
last_page = 4
|
||||||
page = username = int(request.args.get("page", "1"))
|
page = int(request.args.get("page", "1"))
|
||||||
response_file = f"./gitlab_api_page_{page}.json"
|
response_file = f"./gitlab_api_page_{page}.json"
|
||||||
if not os.path.exists(response_file):
|
if not os.path.exists(response_file):
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import hashlib
|
|
||||||
import shutil
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
import git
|
import git
|
||||||
|
|
||||||
@@ -176,7 +176,6 @@ class TempGitRemote:
|
|||||||
newobj.remoteid = remoteid
|
newobj.remoteid = remoteid
|
||||||
return newobj, remoteid
|
return newobj, remoteid
|
||||||
else:
|
else:
|
||||||
refresh = False
|
|
||||||
if cachekey not in cls.obj:
|
if cachekey not in cls.obj:
|
||||||
tmpdir = get_temporary_directory()
|
tmpdir = get_temporary_directory()
|
||||||
shell(
|
shell(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from helpers import *
|
from helpers import grm
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_command():
|
def test_invalid_command():
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import toml
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import toml
|
||||||
import yaml
|
import yaml
|
||||||
|
from helpers import NonExistentPath, TempGitRepository, grm, shell
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
|
|
||||||
def test_repos_find_nonexistent():
|
def test_repos_find_nonexistent():
|
||||||
@@ -40,7 +41,7 @@ def test_repos_find_invalid_format():
|
|||||||
)
|
)
|
||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
assert len(cmd.stdout) == 0
|
assert len(cmd.stdout) == 0
|
||||||
assert "isn't a valid value" in cmd.stderr
|
assert "invalid value 'invalidformat'" in cmd.stderr
|
||||||
|
|
||||||
|
|
||||||
def test_repos_find_non_git_repos():
|
def test_repos_find_non_git_repos():
|
||||||
@@ -63,9 +64,10 @@ def test_repos_find_non_git_repos():
|
|||||||
assert len(cmd.stderr) != 0
|
assert len(cmd.stderr) != 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("default", [True, False])
|
@pytest.mark.parametrize("default_format", [True, False])
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
def test_repos_find(configtype, default):
|
@pytest.mark.parametrize("exclude", [None, "^.*/repo2$", "^not_matching$"])
|
||||||
|
def test_repos_find(configtype, exclude, default_format):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
shell(
|
shell(
|
||||||
f"""
|
f"""
|
||||||
@@ -99,13 +101,19 @@ def test_repos_find(configtype, default):
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = ["repos", "find", "local", tmpdir]
|
args = ["repos", "find", "local", tmpdir]
|
||||||
if not default:
|
if not default_format:
|
||||||
args += ["--format", configtype]
|
args += ["--format", configtype]
|
||||||
|
if exclude:
|
||||||
|
args += ["--exclude", exclude]
|
||||||
cmd = grm(args)
|
cmd = grm(args)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
assert len(cmd.stderr) == 0
|
if exclude == "^.*/repo2$":
|
||||||
|
assert re.match(r"^.*\[skipped\] .*\/repo2$", cmd.stderr.lower())
|
||||||
|
assert "repo2" in cmd.stderr.lower()
|
||||||
|
else:
|
||||||
|
assert len(cmd.stderr) == 0
|
||||||
|
|
||||||
if default or configtype == "toml":
|
if default_format or configtype == "toml":
|
||||||
output = toml.loads(cmd.stdout)
|
output = toml.loads(cmd.stdout)
|
||||||
elif configtype == "yaml":
|
elif configtype == "yaml":
|
||||||
output = yaml.safe_load(cmd.stdout)
|
output = yaml.safe_load(cmd.stdout)
|
||||||
@@ -120,7 +128,7 @@ def test_repos_find(configtype, default):
|
|||||||
assert set(tree.keys()) == {"root", "repos"}
|
assert set(tree.keys()) == {"root", "repos"}
|
||||||
assert tree["root"] == tmpdir
|
assert tree["root"] == tmpdir
|
||||||
assert isinstance(tree["repos"], list)
|
assert isinstance(tree["repos"], list)
|
||||||
assert len(tree["repos"]) == 2
|
assert len(tree["repos"]) == (1 if exclude == "^.*/repo2$" else 2)
|
||||||
|
|
||||||
repo1 = [r for r in tree["repos"] if r["name"] == "repo1"][0]
|
repo1 = [r for r in tree["repos"] if r["name"] == "repo1"][0]
|
||||||
assert repo1["worktree_setup"] is False
|
assert repo1["worktree_setup"] is False
|
||||||
@@ -137,30 +145,32 @@ def test_repos_find(configtype, default):
|
|||||||
assert someremote["type"] == "ssh"
|
assert someremote["type"] == "ssh"
|
||||||
assert someremote["url"] == "ssh://example.com/repo2.git"
|
assert someremote["url"] == "ssh://example.com/repo2.git"
|
||||||
|
|
||||||
repo2 = [r for r in tree["repos"] if r["name"] == "repo2"][0]
|
if exclude == "^.*/repo2$":
|
||||||
assert repo2["worktree_setup"] is False
|
assert [r for r in tree["repos"] if r["name"] == "repo2"] == []
|
||||||
assert isinstance(repo1["remotes"], list)
|
else:
|
||||||
assert len(repo2["remotes"]) == 1
|
repo2 = [r for r in tree["repos"] if r["name"] == "repo2"][0]
|
||||||
|
assert repo2["worktree_setup"] is False
|
||||||
|
assert isinstance(repo1["remotes"], list)
|
||||||
|
assert len(repo2["remotes"]) == 1
|
||||||
|
|
||||||
origin = [r for r in repo2["remotes"] if r["name"] == "origin"][0]
|
origin = [r for r in repo2["remotes"] if r["name"] == "origin"][0]
|
||||||
assert set(origin.keys()) == {"name", "type", "url"}
|
assert set(origin.keys()) == {"name", "type", "url"}
|
||||||
assert origin["type"] == "https"
|
assert origin["type"] == "https"
|
||||||
assert origin["url"] == "https://example.com/repo2.git"
|
assert origin["url"] == "https://example.com/repo2.git"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("default", [True, False])
|
@pytest.mark.parametrize("default_format", [True, False])
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
def test_repos_find_in_root(configtype, default):
|
def test_repos_find_in_root(configtype, default_format):
|
||||||
with TempGitRepository() as repo_dir:
|
with TempGitRepository() as repo_dir:
|
||||||
|
|
||||||
args = ["repos", "find", "local", repo_dir]
|
args = ["repos", "find", "local", repo_dir]
|
||||||
if not default:
|
if not default_format:
|
||||||
args += ["--format", configtype]
|
args += ["--format", configtype]
|
||||||
cmd = grm(args)
|
cmd = grm(args)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
assert len(cmd.stderr) == 0
|
assert len(cmd.stderr) == 0
|
||||||
|
|
||||||
if default or configtype == "toml":
|
if default_format or configtype == "toml":
|
||||||
output = toml.loads(cmd.stdout)
|
output = toml.loads(cmd.stdout)
|
||||||
elif configtype == "yaml":
|
elif configtype == "yaml":
|
||||||
output = yaml.safe_load(cmd.stdout)
|
output = yaml.safe_load(cmd.stdout)
|
||||||
@@ -194,8 +204,8 @@ def test_repos_find_in_root(configtype, default):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
@pytest.mark.parametrize("default", [True, False])
|
@pytest.mark.parametrize("default_format", [True, False])
|
||||||
def test_repos_find_with_invalid_repo(configtype, default):
|
def test_repos_find_with_invalid_repo(configtype, default_format):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
shell(
|
shell(
|
||||||
f"""
|
f"""
|
||||||
@@ -229,13 +239,13 @@ def test_repos_find_with_invalid_repo(configtype, default):
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = ["repos", "find", "local", tmpdir]
|
args = ["repos", "find", "local", tmpdir]
|
||||||
if not default:
|
if not default_format:
|
||||||
args += ["--format", configtype]
|
args += ["--format", configtype]
|
||||||
cmd = grm(args)
|
cmd = grm(args)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
assert "broken" in cmd.stderr
|
assert "broken" in cmd.stderr
|
||||||
|
|
||||||
if default or configtype == "toml":
|
if default_format or configtype == "toml":
|
||||||
output = toml.loads(cmd.stdout)
|
output = toml.loads(cmd.stdout)
|
||||||
elif configtype == "yaml":
|
elif configtype == "yaml":
|
||||||
output = yaml.safe_load(cmd.stdout)
|
output = yaml.safe_load(cmd.stdout)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
|
||||||
import toml
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import toml
|
||||||
import yaml
|
import yaml
|
||||||
|
from helpers import grm
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
|
|
||||||
ALTERNATE_DOMAIN = os.environ["ALTERNATE_DOMAIN"]
|
ALTERNATE_DOMAIN = os.environ["ALTERNATE_DOMAIN"]
|
||||||
PROVIDERS = ["github", "gitlab"]
|
PROVIDERS = ["github", "gitlab"]
|
||||||
@@ -44,7 +43,9 @@ def test_repos_find_remote_invalid_provider(use_config):
|
|||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
assert len(cmd.stdout) == 0
|
assert len(cmd.stdout) == 0
|
||||||
if not use_config:
|
if not use_config:
|
||||||
assert re.match(".*isn't a valid value for.*provider", cmd.stderr)
|
assert re.match(
|
||||||
|
".*invalid value 'thisproviderdoesnotexist' for.*provider", cmd.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("provider", PROVIDERS)
|
@pytest.mark.parametrize("provider", PROVIDERS)
|
||||||
@@ -67,7 +68,7 @@ def test_repos_find_remote_invalid_format(provider):
|
|||||||
)
|
)
|
||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
assert len(cmd.stdout) == 0
|
assert len(cmd.stdout) == 0
|
||||||
assert "isn't a valid value" in cmd.stderr
|
assert "invalid value 'invalidformat'" in cmd.stderr
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("provider", PROVIDERS)
|
@pytest.mark.parametrize("provider", PROVIDERS)
|
||||||
@@ -275,9 +276,9 @@ def test_repos_find_remote_user(
|
|||||||
if not worktree_default:
|
if not worktree_default:
|
||||||
cfg += f"worktree = {str(worktree).lower()}\n"
|
cfg += f"worktree = {str(worktree).lower()}\n"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
cfg += f"force_ssh = true\n"
|
cfg += "force_ssh = true\n"
|
||||||
if override_remote_name:
|
if override_remote_name:
|
||||||
cfg += f'remote_name = "otherremote"\n'
|
cfg += 'remote_name = "otherremote"\n'
|
||||||
if use_owner:
|
if use_owner:
|
||||||
cfg += """
|
cfg += """
|
||||||
[filters]
|
[filters]
|
||||||
@@ -475,7 +476,7 @@ def test_repos_find_remote_group(
|
|||||||
if not worktree_default:
|
if not worktree_default:
|
||||||
cfg += f"worktree = {str(worktree).lower()}\n"
|
cfg += f"worktree = {str(worktree).lower()}\n"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
cfg += f"force_ssh = true\n"
|
cfg += "force_ssh = true\n"
|
||||||
if use_alternate_endpoint:
|
if use_alternate_endpoint:
|
||||||
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
||||||
cfg += """
|
cfg += """
|
||||||
@@ -591,7 +592,7 @@ def test_repos_find_remote_user_and_group(
|
|||||||
if not worktree_default:
|
if not worktree_default:
|
||||||
cfg += f"worktree = {str(worktree).lower()}\n"
|
cfg += f"worktree = {str(worktree).lower()}\n"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
cfg += f"force_ssh = true\n"
|
cfg += "force_ssh = true\n"
|
||||||
if use_alternate_endpoint:
|
if use_alternate_endpoint:
|
||||||
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
||||||
cfg += """
|
cfg += """
|
||||||
@@ -742,7 +743,7 @@ def test_repos_find_remote_owner(
|
|||||||
if not worktree_default:
|
if not worktree_default:
|
||||||
cfg += f"worktree = {str(worktree).lower()}\n"
|
cfg += f"worktree = {str(worktree).lower()}\n"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
cfg += f"force_ssh = true\n"
|
cfg += "force_ssh = true\n"
|
||||||
if use_alternate_endpoint:
|
if use_alternate_endpoint:
|
||||||
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
cfg += f'api_url = "http://{ALTERNATE_DOMAIN}:5000/{provider}"\n'
|
||||||
cfg += """
|
cfg += """
|
||||||
@@ -873,13 +874,11 @@ def test_repos_find_remote_owner(
|
|||||||
assert repo["remotes"][0]["name"] == "origin"
|
assert repo["remotes"][0]["name"] == "origin"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
assert (
|
assert (
|
||||||
repo["remotes"][0]["url"] == f"ssh://git@example.com/myuser2/myproject3.git"
|
repo["remotes"][0]["url"] == "ssh://git@example.com/myuser2/myproject3.git"
|
||||||
)
|
)
|
||||||
assert repo["remotes"][0]["type"] == "ssh"
|
assert repo["remotes"][0]["type"] == "ssh"
|
||||||
else:
|
else:
|
||||||
assert (
|
assert repo["remotes"][0]["url"] == "https://example.com/myuser2/myproject3.git"
|
||||||
repo["remotes"][0]["url"] == f"https://example.com/myuser2/myproject3.git"
|
|
||||||
)
|
|
||||||
assert repo["remotes"][0]["type"] == "https"
|
assert repo["remotes"][0]["type"] == "https"
|
||||||
|
|
||||||
group_namespace_1 = [t for t in output["trees"] if t["root"] == "/myroot/mygroup1"][
|
group_namespace_1 = [t for t in output["trees"] if t["root"] == "/myroot/mygroup1"][
|
||||||
@@ -923,13 +922,13 @@ def test_repos_find_remote_owner(
|
|||||||
if force_ssh:
|
if force_ssh:
|
||||||
assert (
|
assert (
|
||||||
repo["remotes"][0]["url"]
|
repo["remotes"][0]["url"]
|
||||||
== f"ssh://git@example.com/mygroup1/myproject4.git"
|
== "ssh://git@example.com/mygroup1/myproject4.git"
|
||||||
)
|
)
|
||||||
assert repo["remotes"][0]["type"] == "ssh"
|
assert repo["remotes"][0]["type"] == "ssh"
|
||||||
else:
|
else:
|
||||||
assert (
|
assert (
|
||||||
repo["remotes"][0]["url"]
|
repo["remotes"][0]["url"]
|
||||||
== f"https://example.com/mygroup1/myproject4.git"
|
== "https://example.com/mygroup1/myproject4.git"
|
||||||
)
|
)
|
||||||
assert repo["remotes"][0]["type"] == "https"
|
assert repo["remotes"][0]["type"] == "https"
|
||||||
|
|
||||||
@@ -948,12 +947,11 @@ def test_repos_find_remote_owner(
|
|||||||
assert repo["remotes"][0]["name"] == "origin"
|
assert repo["remotes"][0]["name"] == "origin"
|
||||||
if force_ssh:
|
if force_ssh:
|
||||||
assert (
|
assert (
|
||||||
repo["remotes"][0]["url"]
|
repo["remotes"][0]["url"] == "ssh://git@example.com/mygroup2/myproject5.git"
|
||||||
== f"ssh://git@example.com/mygroup2/myproject5.git"
|
|
||||||
)
|
)
|
||||||
assert repo["remotes"][0]["type"] == "ssh"
|
assert repo["remotes"][0]["type"] == "ssh"
|
||||||
else:
|
else:
|
||||||
assert (
|
assert (
|
||||||
repo["remotes"][0]["url"] == f"https://example.com/mygroup2/myproject5.git"
|
repo["remotes"][0]["url"] == "https://example.com/mygroup2/myproject5.git"
|
||||||
)
|
)
|
||||||
assert repo["remotes"][0]["type"] == "https"
|
assert repo["remotes"][0]["type"] == "https"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import tempfile
|
from helpers import RepoTree, grm
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
|
|
||||||
def test_repos_sync_worktree_clone():
|
def test_repos_sync_worktree_clone():
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import tempfile
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import pytest
|
|
||||||
import toml
|
|
||||||
import git
|
import git
|
||||||
|
import pytest
|
||||||
from helpers import *
|
from helpers import (
|
||||||
|
NonExistentPath,
|
||||||
|
TempGitFileRemote,
|
||||||
|
TempGitRepository,
|
||||||
|
checksum_directory,
|
||||||
|
grm,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
"repo_simple": {
|
"repo_simple": {
|
||||||
@@ -291,7 +298,9 @@ def test_repos_sync_unmanaged_repos(configtype):
|
|||||||
# this removes the prefix (root) from the path (unmanaged_repo)
|
# this removes the prefix (root) from the path (unmanaged_repo)
|
||||||
unmanaged_repo_name = os.path.relpath(unmanaged_repo, root)
|
unmanaged_repo_name = os.path.relpath(unmanaged_repo, root)
|
||||||
regex = f".*unmanaged.*{unmanaged_repo_name}"
|
regex = f".*unmanaged.*{unmanaged_repo_name}"
|
||||||
assert any([re.match(regex, l) for l in cmd.stderr.lower().split("\n")])
|
assert any(
|
||||||
|
[re.match(regex, line) for line in cmd.stderr.lower().split("\n")]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
@@ -303,7 +312,7 @@ def test_repos_sync_root_is_file(configtype):
|
|||||||
|
|
||||||
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
assert "not a directory" in cmd.stderr.lower()
|
assert "notadirectory" in cmd.stderr.lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
@@ -374,7 +383,7 @@ def test_repos_sync_repo_in_subdirectory(configtype):
|
|||||||
assert urls[0] == f"file://{remote}"
|
assert urls[0] == f"file://{remote}"
|
||||||
|
|
||||||
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
||||||
assert not "found unmanaged repository" in cmd.stderr.lower()
|
assert "found unmanaged repository" not in cmd.stderr.lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
@@ -419,7 +428,7 @@ def test_repos_sync_nested_clone(configtype):
|
|||||||
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
||||||
print(cmd.stdout)
|
print(cmd.stdout)
|
||||||
print(cmd.stderr)
|
print(cmd.stderr)
|
||||||
assert not "found unmanaged repository" in cmd.stderr.lower()
|
assert "found unmanaged repository" not in cmd.stderr.lower()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
@pytest.mark.parametrize("configtype", ["toml", "yaml"])
|
||||||
@@ -720,14 +729,14 @@ def test_repos_sync_invalid_syntax(configtype):
|
|||||||
with open(config.name, "w") as f:
|
with open(config.name, "w") as f:
|
||||||
if configtype == "toml":
|
if configtype == "toml":
|
||||||
f.write(
|
f.write(
|
||||||
f"""
|
"""
|
||||||
[[trees]]
|
[[trees]]
|
||||||
root = invalid as there are no quotes ;)
|
root = invalid as there are no quotes ;)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
elif configtype == "yaml":
|
elif configtype == "yaml":
|
||||||
f.write(
|
f.write(
|
||||||
f"""
|
"""
|
||||||
trees:
|
trees:
|
||||||
wrong:
|
wrong:
|
||||||
indentation:
|
indentation:
|
||||||
@@ -779,8 +788,6 @@ def test_repos_sync_normal_change_to_worktree(configtype):
|
|||||||
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
|
||||||
git_dir = os.path.join(target, "test")
|
|
||||||
|
|
||||||
with open(config.name, "w") as f:
|
with open(config.name, "w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
templates["worktree_repo_with_remote"][configtype].format(
|
templates["worktree_repo_with_remote"][configtype].format(
|
||||||
@@ -810,8 +817,6 @@ def test_repos_sync_worktree_change_to_normal(configtype):
|
|||||||
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
cmd = grm(["repos", "sync", "config", "--config", config.name])
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
|
||||||
git_dir = os.path.join(target, "test")
|
|
||||||
|
|
||||||
with open(config.name, "w") as f:
|
with open(config.name, "w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
templates["repo_with_remote"][configtype].format(
|
templates["repo_with_remote"][configtype].format(
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import pytest
|
import os
|
||||||
|
|
||||||
from helpers import *
|
import pytest
|
||||||
|
from helpers import (
|
||||||
|
NonGitDir,
|
||||||
|
TempGitRepository,
|
||||||
|
TempGitRepositoryWorktree,
|
||||||
|
checksum_directory,
|
||||||
|
funcname,
|
||||||
|
grm,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_clean():
|
def test_worktree_clean():
|
||||||
@@ -153,13 +162,13 @@ def test_worktree_clean_configured_default_branch(
|
|||||||
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
|
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
|
||||||
if branch_list_empty:
|
if branch_list_empty:
|
||||||
f.write(
|
f.write(
|
||||||
f"""
|
"""
|
||||||
persistent_branches = []
|
persistent_branches = []
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
f.write(
|
f.write(
|
||||||
f"""
|
"""
|
||||||
persistent_branches = [
|
persistent_branches = [
|
||||||
"mybranch"
|
"mybranch"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from helpers import *
|
import git
|
||||||
|
from helpers import TempGitRepositoryWorktree, checksum_directory, funcname, grm, shell
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_never_clean_persistent_branches():
|
def test_worktree_never_clean_persistent_branches():
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import tempfile
|
import os
|
||||||
|
|
||||||
from helpers import *
|
from helpers import (
|
||||||
|
EmptyDir,
|
||||||
|
NonGitDir,
|
||||||
|
TempGitRepository,
|
||||||
|
TempGitRepositoryWorktree,
|
||||||
|
checksum_directory,
|
||||||
|
funcname,
|
||||||
|
grm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_convert():
|
def test_convert():
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
|
||||||
import git
|
import git
|
||||||
|
import pytest
|
||||||
|
from helpers import (
|
||||||
|
EmptyDir,
|
||||||
|
TempGitFileRemote,
|
||||||
|
TempGitRepositoryWorktree,
|
||||||
|
funcname,
|
||||||
|
grm,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_fetch():
|
def test_worktree_fetch():
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from helpers import *
|
import os
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pytest
|
|
||||||
import git
|
import git
|
||||||
|
import pytest
|
||||||
|
from helpers import TempGitRepositoryWorktree, funcname, grm, shell
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("pull", [True, False])
|
@pytest.mark.parametrize("pull", [True, False])
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from helpers import *
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from helpers import (
|
||||||
|
NonGitDir,
|
||||||
|
TempGitRepository,
|
||||||
|
TempGitRepositoryWorktree,
|
||||||
|
funcname,
|
||||||
|
grm,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("has_config", [True, False])
|
@pytest.mark.parametrize("has_config", [True, False])
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from helpers import *
|
import datetime
|
||||||
|
import os.path
|
||||||
|
|
||||||
import git
|
import git
|
||||||
import pytest
|
import pytest
|
||||||
import datetime
|
from helpers import (
|
||||||
|
TempGitRepositoryWorktree,
|
||||||
import os.path
|
checksum_directory,
|
||||||
|
funcname,
|
||||||
|
grm,
|
||||||
|
shell,
|
||||||
|
tempfile,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -151,18 +157,20 @@ def test_worktree_add(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
cachefn = lambda nr: "_".join(
|
def cachefn(nr):
|
||||||
[
|
return "_".join(
|
||||||
str(nr),
|
[
|
||||||
str(default_remote),
|
str(nr),
|
||||||
str(local_branch_exists),
|
str(default_remote),
|
||||||
str(remote_branch_already_exists),
|
str(local_branch_exists),
|
||||||
str(remote_branch_with_prefix_already_exists),
|
str(remote_branch_already_exists),
|
||||||
str(remote_count),
|
str(remote_branch_with_prefix_already_exists),
|
||||||
str(remotes_differ),
|
str(remote_count),
|
||||||
str(worktree_name),
|
str(remotes_differ),
|
||||||
]
|
str(worktree_name),
|
||||||
)
|
]
|
||||||
|
)
|
||||||
|
|
||||||
remote1_cache_key = cachefn(1)
|
remote1_cache_key = cachefn(1)
|
||||||
remote2_cache_key = cachefn(2)
|
remote2_cache_key = cachefn(2)
|
||||||
|
|
||||||
@@ -281,7 +289,7 @@ def test_worktree_add(
|
|||||||
and remotes_differ
|
and remotes_differ
|
||||||
):
|
):
|
||||||
assert (
|
assert (
|
||||||
f"branch exists on multiple remotes, but they deviate"
|
"branch exists on multiple remotes, but they deviate"
|
||||||
in cmd.stderr.lower()
|
in cmd.stderr.lower()
|
||||||
)
|
)
|
||||||
assert len(cmd.stderr.strip().split("\n")) == base + 1
|
assert len(cmd.stderr.strip().split("\n")) == base + 1
|
||||||
|
|||||||
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
targets = ["x86_64-unknown-linux-musl"]
|
||||||
@@ -63,6 +63,14 @@ pub struct FindLocalArgs {
|
|||||||
#[clap(help = "The path to search through")]
|
#[clap(help = "The path to search through")]
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
|
||||||
|
#[clap(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
help = "Exclude repositories that match the given regex",
|
||||||
|
name = "REGEX"
|
||||||
|
)]
|
||||||
|
pub exclude: Option<String>,
|
||||||
|
|
||||||
#[clap(
|
#[clap(
|
||||||
value_enum,
|
value_enum,
|
||||||
short,
|
short,
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (found_repos, warnings) = match find_in_tree(&path) {
|
let (found_repos, warnings) = match find_in_tree(&path, args.exclude.as_deref())
|
||||||
|
{
|
||||||
Ok((repos, warnings)) => (repos, warnings),
|
Ok((repos, warnings)) => (repos, warnings),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&error);
|
print_error(&error);
|
||||||
|
|||||||
31
src/lib.rs
31
src/lib.rs
@@ -1,5 +1,3 @@
|
|||||||
#![feature(io_error_more)]
|
|
||||||
#![feature(const_option_ext)]
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -19,12 +17,22 @@ pub mod worktree;
|
|||||||
/// 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::Repo>, Vec<String>, bool)>, String> {
|
fn find_repos(
|
||||||
|
root: &Path,
|
||||||
|
exclusion_pattern: Option<&str>,
|
||||||
|
) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)>, String> {
|
||||||
let mut repos: Vec<repo::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();
|
||||||
|
|
||||||
|
let exlusion_regex: regex::Regex = regex::Regex::new(exclusion_pattern.unwrap_or(r"^$"))
|
||||||
|
.map_err(|e| format!("invalid regex: {e}"))?;
|
||||||
for path in tree::find_repo_paths(root)? {
|
for path in tree::find_repo_paths(root)? {
|
||||||
|
if exclusion_pattern.is_some() && exlusion_regex.is_match(&path::path_as_string(&path)) {
|
||||||
|
warnings.push(format!("[skipped] {}", &path::path_as_string(&path)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let is_worktree = repo::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;
|
||||||
@@ -63,12 +71,13 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)
|
|||||||
let name = remote.name();
|
let name = remote.name();
|
||||||
let url = remote.url();
|
let url = remote.url();
|
||||||
let remote_type = match repo::detect_remote_type(&url) {
|
let remote_type = match repo::detect_remote_type(&url) {
|
||||||
Some(t) => t,
|
Ok(t) => t,
|
||||||
None => {
|
Err(e) => {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"{}: Could not detect remote type of \"{}\"",
|
"{}: Could not handle URL {}. Reason: {}",
|
||||||
&path::path_as_string(&path),
|
&path::path_as_string(&path),
|
||||||
&url
|
&url,
|
||||||
|
e
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -130,10 +139,14 @@ fn find_repos(root: &Path) -> Result<Option<(Vec<repo::Repo>, Vec<String>, bool)
|
|||||||
Ok(Some((repos, warnings, repo_in_root)))
|
Ok(Some((repos, warnings, repo_in_root)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_in_tree(path: &Path) -> Result<(tree::Tree, Vec<String>), String> {
|
pub fn find_in_tree(
|
||||||
|
path: &Path,
|
||||||
|
exclusion_pattern: Option<&str>,
|
||||||
|
) -> Result<(tree::Tree, Vec<String>), String> {
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
|
|
||||||
let (repos, repo_in_root): (Vec<repo::Repo>, bool) = match find_repos(path)? {
|
let (repos, repo_in_root): (Vec<repo::Repo>, bool) = match find_repos(path, exclusion_pattern)?
|
||||||
|
{
|
||||||
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)
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use super::Project;
|
|||||||
use super::Provider;
|
use super::Provider;
|
||||||
|
|
||||||
const ACCEPT_HEADER_JSON: &str = "application/vnd.github.v3+json";
|
const ACCEPT_HEADER_JSON: &str = "application/vnd.github.v3+json";
|
||||||
const GITHUB_API_BASEURL: &str =
|
const GITHUB_API_BASEURL: &str = match option_env!("GITHUB_API_BASEURL") {
|
||||||
option_env!("GITHUB_API_BASEURL").unwrap_or("https://api.github.com");
|
Some(url) => url,
|
||||||
|
None => "https://api.github.com",
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct GithubProject {
|
pub struct GithubProject {
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ use super::Project;
|
|||||||
use super::Provider;
|
use super::Provider;
|
||||||
|
|
||||||
const ACCEPT_HEADER_JSON: &str = "application/json";
|
const ACCEPT_HEADER_JSON: &str = "application/json";
|
||||||
const GITLAB_API_BASEURL: &str = option_env!("GITLAB_API_BASEURL").unwrap_or("https://gitlab.com");
|
const GITLAB_API_BASEURL: &str = match option_env!("GITLAB_API_BASEURL") {
|
||||||
|
Some(url) => url,
|
||||||
|
None => "https://gitlab.com",
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ pub trait Provider {
|
|||||||
// about the data exchange format here.
|
// about the data exchange format here.
|
||||||
repo.remove_namespace();
|
repo.remove_namespace();
|
||||||
|
|
||||||
ret.entry(namespace).or_insert(vec![]).push(repo);
|
ret.entry(namespace).or_default().push(repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|||||||
84
src/repo.rs
84
src/repo.rs
@@ -406,50 +406,78 @@ mod tests {
|
|||||||
fn check_ssh_remote() {
|
fn check_ssh_remote() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
detect_remote_type("ssh://git@example.com"),
|
detect_remote_type("ssh://git@example.com"),
|
||||||
Some(RemoteType::Ssh)
|
Ok(RemoteType::Ssh)
|
||||||
);
|
);
|
||||||
assert_eq!(detect_remote_type("git@example.git"), Some(RemoteType::Ssh));
|
assert_eq!(detect_remote_type("git@example.git"), Ok(RemoteType::Ssh));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_https_remote() {
|
fn check_https_remote() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
detect_remote_type("https://example.com"),
|
detect_remote_type("https://example.com"),
|
||||||
Some(RemoteType::Https)
|
Ok(RemoteType::Https)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
detect_remote_type("https://example.com/test.git"),
|
detect_remote_type("https://example.com/test.git"),
|
||||||
Some(RemoteType::Https)
|
Ok(RemoteType::Https)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_file_remote() {
|
fn check_file_remote() {
|
||||||
assert_eq!(
|
assert_eq!(detect_remote_type("file:///somedir"), Ok(RemoteType::File));
|
||||||
detect_remote_type("file:///somedir"),
|
|
||||||
Some(RemoteType::File)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_invalid_remotes() {
|
fn check_invalid_remotes() {
|
||||||
assert_eq!(detect_remote_type("https//example.com"), None);
|
assert_eq!(
|
||||||
assert_eq!(detect_remote_type("https:example.com"), None);
|
detect_remote_type("https//example.com"),
|
||||||
assert_eq!(detect_remote_type("ssh//example.com"), None);
|
Err(String::from(
|
||||||
assert_eq!(detect_remote_type("ssh:example.com"), None);
|
"The remote URL starts with an unimplemented protocol"
|
||||||
assert_eq!(detect_remote_type("git@example.com"), None);
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_remote_type("https:example.com"),
|
||||||
|
Err(String::from(
|
||||||
|
"The remote URL starts with an unimplemented protocol",
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_remote_type("ssh//example.com"),
|
||||||
|
Err(String::from(
|
||||||
|
"The remote URL starts with an unimplemented protocol",
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_remote_type("ssh:example.com"),
|
||||||
|
Err(String::from(
|
||||||
|
"The remote URL starts with an unimplemented protocol",
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
detect_remote_type("git@example.com"),
|
||||||
|
Err(String::from(
|
||||||
|
"The remote URL starts with an unimplemented protocol",
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn check_unsupported_protocol_http() {
|
fn check_unsupported_protocol_http() {
|
||||||
detect_remote_type("http://example.com");
|
assert_eq!(
|
||||||
|
detect_remote_type("http://example.com"),
|
||||||
|
Err(String::from(
|
||||||
|
"Remotes using HTTP protocol are not supported",
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn check_unsupported_protocol_git() {
|
fn check_unsupported_protocol_git() {
|
||||||
detect_remote_type("git://example.com");
|
assert_eq!(
|
||||||
|
detect_remote_type("git://example.com"),
|
||||||
|
Err(String::from("Remotes using git protocol are not supported"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -473,27 +501,31 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect_remote_type(remote_url: &str) -> Option<RemoteType> {
|
pub fn detect_remote_type(remote_url: &str) -> Result<RemoteType, String> {
|
||||||
let git_regex = regex::Regex::new(r"^[a-zA-Z]+@.*$").unwrap();
|
let git_regex = regex::Regex::new(r"^[a-zA-Z]+@.*$").unwrap();
|
||||||
if remote_url.starts_with("ssh://") {
|
if remote_url.starts_with("ssh://") {
|
||||||
return Some(RemoteType::Ssh);
|
return Ok(RemoteType::Ssh);
|
||||||
}
|
}
|
||||||
if git_regex.is_match(remote_url) && remote_url.ends_with(".git") {
|
if git_regex.is_match(remote_url) && remote_url.ends_with(".git") {
|
||||||
return Some(RemoteType::Ssh);
|
return Ok(RemoteType::Ssh);
|
||||||
}
|
}
|
||||||
if remote_url.starts_with("https://") {
|
if remote_url.starts_with("https://") {
|
||||||
return Some(RemoteType::Https);
|
return Ok(RemoteType::Https);
|
||||||
}
|
}
|
||||||
if remote_url.starts_with("file://") {
|
if remote_url.starts_with("file://") {
|
||||||
return Some(RemoteType::File);
|
return Ok(RemoteType::File);
|
||||||
}
|
}
|
||||||
if remote_url.starts_with("http://") {
|
if remote_url.starts_with("http://") {
|
||||||
unimplemented!("Remotes using HTTP protocol are not supported");
|
return Err(String::from(
|
||||||
|
"Remotes using HTTP protocol are not supported",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if remote_url.starts_with("git://") {
|
if remote_url.starts_with("git://") {
|
||||||
unimplemented!("Remotes using git protocol are not supported");
|
return Err(String::from("Remotes using git protocol are not supported"));
|
||||||
}
|
}
|
||||||
None
|
Err(String::from(
|
||||||
|
"The remote URL starts with an unimplemented protocol",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RepoHandle(git2::Repository);
|
pub struct RepoHandle(git2::Repository);
|
||||||
@@ -1588,7 +1620,7 @@ impl RemoteHandle<'_> {
|
|||||||
|
|
||||||
pub fn is_pushable(&self) -> Result<bool, String> {
|
pub fn is_pushable(&self) -> Result<bool, String> {
|
||||||
let remote_type = detect_remote_type(self.0.url().expect("Remote name is not valid utf-8"))
|
let remote_type = detect_remote_type(self.0.url().expect("Remote name is not valid utf-8"))
|
||||||
.ok_or_else(|| String::from("Could not detect remote type"))?;
|
.expect("Could not detect remote type");
|
||||||
Ok(matches!(remote_type, RemoteType::Ssh | RemoteType::File))
|
Ok(matches!(remote_type, RemoteType::Ssh | RemoteType::File))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/table.rs
17
src/table.rs
@@ -4,6 +4,7 @@ use super::repo;
|
|||||||
|
|
||||||
use comfy_table::{Cell, Table};
|
use comfy_table::{Cell, Table};
|
||||||
|
|
||||||
|
use std::fmt::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
fn add_table_header(table: &mut Table) {
|
fn add_table_header(table: &mut Table) {
|
||||||
@@ -56,9 +57,10 @@ fn add_repo_status(
|
|||||||
repo_status
|
repo_status
|
||||||
.branches
|
.branches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(branch_name, remote_branch)| {
|
.fold(String::new(), |mut s, (branch_name, remote_branch)| {
|
||||||
format!(
|
writeln!(
|
||||||
"branch: {}{}\n",
|
&mut s,
|
||||||
|
"branch: {}{}",
|
||||||
&branch_name,
|
&branch_name,
|
||||||
&match remote_branch {
|
&match remote_branch {
|
||||||
None => String::from(" <!local>"),
|
None => String::from(" <!local>"),
|
||||||
@@ -78,8 +80,9 @@ fn add_repo_status(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.unwrap();
|
||||||
|
s
|
||||||
})
|
})
|
||||||
.collect::<String>()
|
|
||||||
.trim(),
|
.trim(),
|
||||||
&match is_worktree {
|
&match is_worktree {
|
||||||
true => String::from(""),
|
true => String::from(""),
|
||||||
@@ -91,8 +94,10 @@ fn add_repo_status(
|
|||||||
repo_status
|
repo_status
|
||||||
.remotes
|
.remotes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| format!("{}\n", r))
|
.fold(String::new(), |mut s, r| {
|
||||||
.collect::<String>()
|
writeln!(&mut s, "{r}").unwrap();
|
||||||
|
s
|
||||||
|
})
|
||||||
.trim(),
|
.trim(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -128,8 +128,6 @@ pub fn find_repo_paths(path: &Path) -> Result<Vec<PathBuf>, String> {
|
|||||||
"Failed to open \"{}\": {}",
|
"Failed to open \"{}\": {}",
|
||||||
&path.display(),
|
&path.display(),
|
||||||
match e.kind() {
|
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"),
|
std::io::ErrorKind::NotFound => String::from("not found"),
|
||||||
_ => format!("{:?}", e.kind()),
|
_ => format!("{:?}", e.kind()),
|
||||||
}
|
}
|
||||||
@@ -181,7 +179,7 @@ fn sync_repo(root_path: &Path, repo: &repo::Repo, init_worktree: bool) -> Result
|
|||||||
"Repo already exists, but is not using a worktree setup",
|
"Repo already exists, but is not using a worktree setup",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
} else if matches!(&repo.remotes, None) || repo.remotes.as_ref().unwrap().is_empty() {
|
} else if repo.remotes.is_none() || repo.remotes.as_ref().unwrap().is_empty() {
|
||||||
print_repo_action(
|
print_repo_action(
|
||||||
&repo.name,
|
&repo.name,
|
||||||
"Repository does not have remotes configured, initializing new",
|
"Repository does not have remotes configured, initializing new",
|
||||||
|
|||||||
Reference in New Issue
Block a user