17 Commits

Author SHA1 Message Date
d73314cefb tmp: tests 2022-06-13 22:38:43 +02:00
239e587cbc Use new cargo fmt 2022-06-13 22:38:43 +02:00
742c8587ce Enable output in rust unit tests 2022-06-13 22:38:27 +02:00
Hannes Körber
fb45087477 Update handling of branches on worktree setup 2022-06-13 22:37:58 +02:00
Hannes Körber
a3dc2df37d Quote branch name on output 2022-06-13 22:37:44 +02:00
Hannes Körber
0f45708e81 Improve default branch guessing 2022-06-13 22:37:36 +02:00
Hannes Körber
d036b53037 Print ok-ish stuff to stdout 2022-06-13 22:37:27 +02:00
2be1dec818 Add some comments about repo syncing 2022-06-13 22:37:19 +02:00
f89c9c2ca5 Do not fail on empty clone target 2022-06-13 22:37:11 +02:00
62c2fc24cd Initialize local branches on clone 2022-06-13 22:37:06 +02:00
d0425e2fdb Add function to get all remote branches 2022-06-13 22:37:03 +02:00
a8665ae741 Add function to get basename of branch 2022-06-13 22:36:54 +02:00
d26b6e799c Refactor default_branch() for readability 2022-06-13 22:36:47 +02:00
60eb059f60 providers: Use references for field access 2022-06-13 22:32:31 +02:00
d9f416018d Use opaque type for auth token
So we cannot accidentially output it, as it does not implement
`Display`.
2022-06-13 22:32:31 +02:00
Max Volk
461c69dacb Reword some of the documentation and spelling fixes 2022-06-13 22:32:26 +02:00
ef21f9cad4 Remove accidentially added file 2022-06-13 22:32:26 +02:00
19 changed files with 337 additions and 450 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
github: hakoerber

View File

@@ -21,8 +21,7 @@ If you want, add yourself to the `CONTRIBUTORS` file in your pull request.
For Rust, just use `cargo fmt`. For Python, use
[black](https://github.com/psf/black). I'd rather not spend any effort in
configuring the formatters (not possible for black anyway). For shell scripts,
use [`shfmt`](https://github.com/mvdan/sh).
configuring the formatters (not possible for black anyway).
## Tooling
@@ -42,9 +41,6 @@ When contributing, consider whether it makes sense to add tests which could
prevent regressions in the future. When fixing bugs, it makes sense to add
tests that expose the wrong behaviour beforehand.
To also ensure proper formatting and that the linter is happy, use `just check`.
If that succeeds, your code is most likely fine to push!
## Documentation
The documentation lives in `docs` and uses

View File

@@ -1,2 +1 @@
nonnominandus
Maximilian Volk

129
Cargo.lock generated
View File

@@ -80,16 +80,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.6"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a"
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"lazy_static",
"strsim",
"termcolor",
"textwrap",
@@ -97,11 +97,11 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "3.2.6"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a"
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
dependencies = [
"heck",
"heck 0.4.0",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -110,18 +110,18 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.2.3"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4"
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "comfy-table"
version = "6.0.0"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121d8a5b0346092c18a4b2fd6f620d7a06f0eb7ac0a45860939a0884bc579c56"
checksum = "b103d85ca6e209388771bfb7aa6b68a7aeec4afbf6f0a0264bfbf50360e5212e"
dependencies = [
"crossterm",
"strum",
@@ -155,12 +155,12 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.10"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if",
"once_cell",
"lazy_static",
]
[[package]]
@@ -321,18 +321,18 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.7"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "git-repo-manager"
version = "0.7.3"
version = "0.7.1"
dependencies = [
"clap",
"comfy-table",
@@ -367,9 +367,18 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.12.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
@@ -388,9 +397,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.8"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
dependencies = [
"bytes",
"fnv",
@@ -410,9 +419,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
dependencies = [
"autocfg",
"hashbrown",
@@ -523,9 +532,9 @@ dependencies = [
[[package]]
name = "libz-sys"
version = "1.1.8"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e"
dependencies = [
"cc",
"libc",
@@ -578,13 +587,13 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mio"
version = "0.8.4"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
@@ -602,18 +611,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.21.0+1.1.1p"
version = "111.20.0+1.1.1o"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0a8313729211913936f1b95ca47a5fc7f2e04cd658c115388287f8a8361008"
checksum = "92892c4f87d56e376e469ace79f1128fdaded07646ddf73aa0be4706ff712dec"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.74"
version = "0.9.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1"
checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0"
dependencies = [
"autocfg",
"cc",
@@ -637,9 +646,9 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "parking_lot"
version = "0.12.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -746,18 +755,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.40"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
@@ -847,9 +856,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.7"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
name = "ryu"
@@ -996,17 +1005,17 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.24.1"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
[[package]]
name = "strum_macros"
version = "0.24.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
dependencies = [
"heck",
"heck 0.3.3",
"proc-macro2",
"quote",
"rustversion",
@@ -1015,9 +1024,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.98"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
dependencies = [
"proc-macro2",
"quote",
@@ -1105,9 +1114,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.35"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
dependencies = [
"cfg-if",
"log",
@@ -1129,11 +1138,11 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.27"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
dependencies = [
"once_cell",
"lazy_static",
]
[[package]]
@@ -1154,9 +1163,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]]
name = "unicode-normalization"
@@ -1167,6 +1176,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
[[package]]
name = "unicode-width"
version = "0.1.9"
@@ -1212,6 +1227,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@@ -1,6 +1,6 @@
[package]
name = "git-repo-manager"
version = "0.7.3"
version = "0.7.1"
edition = "2021"
authors = [
@@ -28,7 +28,7 @@ rust-version = "1.57"
license = "GPL-3.0-only"
[profile.e2e-tests]
inherits = "dev"
inherits = "release"
[lib]
name = "grm"
@@ -54,7 +54,7 @@ version = "=0.14.4"
version = "=2.1.0"
[dependencies.clap]
version = "=3.2.6"
version = "=3.1.18"
features = ["derive", "cargo"]
[dependencies.console]
@@ -64,7 +64,7 @@ version = "=0.15.0"
version = "=1.5.6"
[dependencies.comfy-table]
version = "=6.0.0"
version = "=5.0.1"
[dependencies.serde_yaml]
version = "=0.8.24"

View File

@@ -1,54 +1,42 @@
set positional-arguments
static_target := "x86_64-unknown-linux-musl"
target := "x86_64-unknown-linux-musl"
check: fmt-check lint test
check: test
cargo check
clean:
cargo clean
git clean -f -d -X
cargo fmt --check
cargo clippy --no-deps -- -Dwarnings
fmt:
cargo fmt
git ls-files | grep '\.py$' | xargs black
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --write
fmt-check:
cargo fmt --check
git ls-files | grep '\.py$' | xargs black --check
git ls-files | grep '\.sh$' | xargs -L 1 shfmt --indent 4 --diff
lint:
cargo clippy --no-deps -- -Dwarnings
git ls-files | grep '\.sh$' | xargs -L 1 shellcheck --norc
cargo clippy --no-deps
lint-fix:
cargo clippy --no-deps --fix
release:
cargo build --release
release-static:
cargo build --release --target {{static_target}} --features=static-build
cargo build --release --target {{target}}
test-binary:
env \
GITHUB_API_BASEURL=http://rest:5000/github \
GITLAB_API_BASEURL=http://rest:5000/gitlab \
cargo build --profile e2e-tests
cargo build --target {{target}} --profile e2e-tests --features=static-build
install:
cargo install --path .
install-static:
cargo install --target {{static_target}} --features=static-build --path .
cargo install --target {{target}} --features=static-build --path .
build:
cargo build
build-static:
cargo build --target {{static_target}} --features=static-build
cargo build --target {{target}} --features=static-build
test: test-unit test-integration test-e2e
@@ -64,7 +52,7 @@ test-e2e +tests=".": test-binary
&& docker-compose build \
&& docker-compose run \
--rm \
-v $PWD/../target/e2e-tests/grm:/grm \
-v $PWD/../target/{{target}}/e2e-tests/grm:/grm \
pytest \
"GRM_BINARY=/grm ALTERNATE_DOMAIN=alternate-rest python3 -m pytest -p no:cacheprovider --color=yes "$@"" \
&& docker-compose rm --stop -f

View File

@@ -248,7 +248,6 @@ def test_repos_find_remote_user_empty(
@pytest.mark.parametrize("force_ssh", [True, False])
@pytest.mark.parametrize("use_alternate_endpoint", [True, False])
@pytest.mark.parametrize("use_config", [True, False])
@pytest.mark.parametrize("override_remote_name", [True, False])
def test_repos_find_remote_user(
provider,
configtype,
@@ -259,7 +258,6 @@ def test_repos_find_remote_user(
force_ssh,
use_alternate_endpoint,
use_config,
override_remote_name,
):
if use_config:
with tempfile.NamedTemporaryFile() as config:
@@ -276,8 +274,6 @@ def test_repos_find_remote_user(
cfg += f"worktree = {str(worktree).lower()}\n"
if force_ssh:
cfg += f"force_ssh = true\n"
if override_remote_name:
cfg += f'remote_name = "otherremote"\n'
if use_owner:
cfg += """
[filters]
@@ -314,8 +310,6 @@ def test_repos_find_remote_user(
args += ["--user", "myuser1"]
if force_ssh:
args += ["--force-ssh"]
if override_remote_name:
args += ["--remote-name", "otherremote"]
if not worktree_default:
args += ["--worktree", str(worktree).lower()]
if use_alternate_endpoint:
@@ -356,10 +350,7 @@ def test_repos_find_remote_user(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
if override_remote_name:
assert repo["remotes"][0]["name"] == "otherremote"
else:
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -544,14 +535,14 @@ def test_repos_find_remote_group(
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
if force_ssh or i == 1:
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
assert (
repo["remotes"][0]["url"]
== f"ssh://git@example.com/mygroup1/myproject{i}.git"
)
assert repo["remotes"][0]["type"] == "ssh"
else:
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
assert (
repo["remotes"][0]["url"]
== f"https://example.com/mygroup1/myproject{i}.git"
@@ -668,7 +659,7 @@ def test_repos_find_remote_user_and_group(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -693,7 +684,7 @@ def test_repos_find_remote_user_and_group(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -823,7 +814,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -846,7 +837,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -870,7 +861,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh:
assert (
repo["remotes"][0]["url"] == f"ssh://git@example.com/myuser2/myproject3.git"
@@ -899,7 +890,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh or i == 1:
assert (
repo["remotes"][0]["url"]
@@ -919,7 +910,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh:
assert (
repo["remotes"][0]["url"]
@@ -945,7 +936,7 @@ def test_repos_find_remote_owner(
assert repo["worktree_setup"] is (not worktree_default and worktree)
assert isinstance(repo["remotes"], list)
assert len(repo["remotes"]) == 1
assert repo["remotes"][0]["name"] == "origin"
assert repo["remotes"][0]["name"] == provider
if force_ssh:
assert (
repo["remotes"][0]["url"]

View File

@@ -12,18 +12,9 @@ import os.path
@pytest.mark.parametrize("has_config", [True, False])
@pytest.mark.parametrize("has_default", [True, False])
@pytest.mark.parametrize("has_prefix", [True, False])
@pytest.mark.parametrize("worktree_with_slash", [True, False])
def test_worktree_add(
remote_branch_already_exists,
has_config,
has_default,
has_prefix,
worktree_with_slash,
def test_worktree_add_simple(
remote_branch_already_exists, has_config, has_default, has_prefix
):
if worktree_with_slash:
worktree_name = "dir/test"
else:
worktree_name = "test"
with TempGitRepositoryWorktree() as (base_dir, _commit):
if has_config:
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
@@ -51,61 +42,54 @@ def test_worktree_add(
touch change
git add change
git commit -m commit
git push origin HEAD:{worktree_name}
git push origin HEAD:test
#git reset --hard 'HEAD@{1}'
git branch -va
)
git --git-dir ./.git-main-working-tree worktree remove tmp
"""
)
cmd = grm(["wt", "add", worktree_name], cwd=base_dir)
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
files = os.listdir(base_dir)
if has_config is True:
assert len(files) == 3
if worktree_with_slash:
assert set(files) == {".git-main-working-tree", "grm.toml", "dir"}
assert set(os.listdir(os.path.join(base_dir, "dir"))) == {"test"}
else:
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
else:
assert len(files) == 2
if worktree_with_slash:
assert set(files) == {".git-main-working-tree", "dir"}
assert set(os.listdir(os.path.join(base_dir, "dir"))) == {"test"}
else:
assert set(files) == {".git-main-working-tree", "test"}
assert set(files) == {".git-main-working-tree", "test"}
repo = git.Repo(os.path.join(base_dir, worktree_name))
repo = git.Repo(os.path.join(base_dir, "test"))
assert not repo.bare
assert not repo.is_dirty()
if has_config and has_default:
if has_prefix and not remote_branch_already_exists:
assert (
str(repo.active_branch.tracking_branch())
== f"origin/myprefix/{worktree_name}"
str(repo.active_branch.tracking_branch()) == "origin/myprefix/test"
)
else:
assert (
str(repo.active_branch.tracking_branch())
== f"origin/{worktree_name}"
)
assert str(repo.active_branch.tracking_branch()) == "origin/test"
else:
assert repo.active_branch.tracking_branch() is None
def test_worktree_add_invalid_name():
def test_worktree_add_into_subdirectory():
with TempGitRepositoryWorktree() as (base_dir, _commit):
for worktree_name in ["/absolute/path" "trailingslash/"]:
args = ["wt", "add", worktree_name]
cmd = grm(args, cwd=base_dir)
assert cmd.returncode != 0
print(cmd.stdout)
print(cmd.stderr)
assert not os.path.exists(worktree_name)
assert not os.path.exists(os.path.join(base_dir, worktree_name))
assert "invalid worktree name" in str(cmd.stderr.lower())
cmd = grm(["wt", "add", "dir/test"], cwd=base_dir)
assert cmd.returncode == 0
files = os.listdir(base_dir)
assert len(files) == 2
assert set(files) == {".git-main-working-tree", "dir"}
files = os.listdir(os.path.join(base_dir, "dir"))
assert set(files) == {"test"}
repo = git.Repo(os.path.join(base_dir, "dir", "test"))
assert not repo.bare
assert not repo.is_dirty()
assert repo.active_branch.tracking_branch() is None
def test_worktree_add_into_invalid_subdirectory():
@@ -228,6 +212,67 @@ def test_worktree_add_with_explicit_no_tracking(
assert repo.active_branch.tracking_branch() is None
@pytest.mark.parametrize("remote_branch_already_exists", [True, False])
@pytest.mark.parametrize("has_default", [True, False])
@pytest.mark.parametrize("has_prefix", [True, False])
def test_worktree_add_with_config(
remote_branch_already_exists, has_default, has_prefix
):
with TempGitRepositoryWorktree() as (base_dir, _commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
f"""
[track]
default = {str(has_default).lower()}
default_remote = "origin"
"""
)
if has_prefix:
f.write(
"""
default_remote_prefix = "myprefix"
"""
)
if remote_branch_already_exists:
shell(
f"""
cd {base_dir}
git --git-dir ./.git-main-working-tree worktree add tmp
(
cd tmp
touch change
git add change
git commit -m commit
git push origin HEAD:test
#git reset --hard 'HEAD@{1}'
git branch -va
)
git --git-dir ./.git-main-working-tree worktree remove tmp
"""
)
cmd = grm(["wt", "add", "test"], cwd=base_dir)
print(cmd.stderr)
assert cmd.returncode == 0
files = os.listdir(base_dir)
assert len(files) == 3
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
repo = git.Repo(os.path.join(base_dir, "test"))
assert not repo.bare
assert not repo.is_dirty()
assert str(repo.active_branch) == "test"
if has_default:
if has_prefix and not remote_branch_already_exists:
assert (
str(repo.active_branch.tracking_branch()) == "origin/myprefix/test"
)
else:
assert str(repo.active_branch.tracking_branch()) == "origin/test"
else:
assert repo.active_branch.tracking_branch() is None
def test_worktree_delete():
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)

View File

@@ -1,164 +0,0 @@
#!/usr/bin/env bash
set -o nounset
set -o errexit
set -o pipefail
usage() {
printf '%s\n' "usage: $0 (master|minor|patch)" >&2
}
if (($# != 1)); then
usage
exit 1
fi
current_version="$(grep '^version \?=' Cargo.toml | head -1 | cut -d '=' -f 2 | tr -d " '"'"')"
major="$(printf '%s' "${current_version}" | grep -oP '^\d+')"
minor="$(printf '%s' "${current_version}" | grep -oP '\.\d+\.' | tr -d '.')"
patch="$(printf '%s' "${current_version}" | grep -oP '\d+$' | tr -d '.')"
case "$1" in
major)
((major++)) || true
minor=0
patch=0
;;
minor)
((minor++)) || true
patch=0
;;
patch)
((patch++)) || true
;;
*)
usage
exit 1
;;
esac
new_version="${major}.${minor}.${patch}"
if ! [[ "${new_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
printf '%s\n' 'Version has to a complete semver' >&2
exit 1
fi
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "${current_branch}" != "develop" ]]; then
printf '%s\n' 'You need to be on develop' >&2
exit 1
fi
gitstatus="$(git status --porcelain)"
if [[ -n "${gitstatus}" ]]; then
printf '%s\n' 'There are uncommitted changes' >&2
exit 1
fi
if git tag --list "v${new_version}" | grep -q .; then
printf 'Tag %s already exists\n' "v${new_version}" >&2
exit 1
fi
for remote in $(git remote); do
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
exit 1
fi
done
git fetch --all
for remote in $(git remote); do
for branch in master develop; do
if ! git diff --quiet "${remote}/${branch}..${branch}"; then
printf 'Remote branch %s/%s not up to date, synchronize first!\n' "${remote}" "${branch}" >&2
exit 1
fi
done
done
if ! git merge-base --is-ancestor master develop; then
printf '%s\n' 'Develop is not a straight descendant of master, rebase!' >&2
exit 1
fi
changes="$(git log --oneline master..develop | wc -l)"
if ((changes == 0)); then
printf '%s\n' 'No changes between master and develop?' >&2
exit 1
fi
just update-dependencies
just check
sed -i "0,/^version/{s/^version.*$/version = \"${new_version}\"/}" Cargo.toml
cargo update --package git-repo-manager --precise "${new_version}"
diff="$(git diff --numstat)"
if (($(printf '%s\n' "${diff}" | wc -l || true) != 2)); then
printf '%s\n' 'Weird changes detected, bailing' >&2
exit 1
fi
if ! printf '%s\n' "${diff}" | grep -Pq '^1\s+1\s+Cargo.lock$'; then
printf '%s\n' 'Weird changes detected, bailing' >&2
exit 1
fi
if ! printf '%s\n' "${diff}" | grep -Pq '^1\s+1\s+Cargo.toml$'; then
printf '%s\n' 'Weird changes detected, bailing' >&2
exit 1
fi
git add Cargo.lock Cargo.toml
git commit -m "Release v${new_version}"
git switch master 2>/dev/null || { [[ -d "../master" ]] && cd "../master"; } || {
printf '%s\n' 'Could not change to master' >&2
exit 1
}
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "${current_branch}" != "master" ]]; then
printf '%s\n' 'Looks like branch switching to master did not work' >&2
exit 1
fi
git merge --no-ff --no-edit develop
git tag "v${new_version}"
for remote in $(git remote); do
while ! git push "${remote}" "v${new_version}" master; do
:
done
done
git switch develop 2>/dev/null || { [[ -d "../develop" ]] && cd "../develop"; } || {
printf '%s\n' 'Could not change to develop' >&2
exit 1
}
current_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "${current_branch}" != "develop" ]]; then
printf '%s\n' 'Looks like branch switching to develop did not work' >&2
exit 1
fi
git merge --ff-only master
for remote in $(git remote); do
while ! git push "${remote}" develop; do
:
done
done
cargo publish
printf 'Published %s successfully\n' "${new_version}"
exit 0

View File

@@ -53,8 +53,6 @@ pub struct ConfigProvider {
pub worktree: Option<bool>,
pub init_worktree: Option<bool>,
pub remote_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -194,7 +192,6 @@ impl Config {
.get_repos(
config.worktree.unwrap_or(false),
config.force_ssh.unwrap_or(false),
config.remote_name,
)?
}
RemoteProvider::Gitlab => {
@@ -208,7 +205,6 @@ impl Config {
.get_repos(
config.worktree.unwrap_or(false),
config.force_ssh.unwrap_or(false),
config.remote_name,
)?
}
};

View File

@@ -103,9 +103,6 @@ pub struct FindRemoteArgs {
#[clap(arg_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider,
#[clap(short, long, help = "Name of the remote to use")]
pub remote_name: Option<String>,
#[clap(
multiple_occurrences = true,
name = "user",
@@ -192,9 +189,6 @@ pub struct SyncRemoteArgs {
#[clap(arg_enum, short, long, help = "Remote provider to use")]
pub provider: RemoteProvider,
#[clap(short, long, help = "Name of the remote to use")]
pub remote_name: Option<String>,
#[clap(
multiple_occurrences = true,
name = "user",

View File

@@ -1,5 +1,3 @@
#![forbid(unsafe_code)]
use std::path::Path;
use std::process;
@@ -66,11 +64,7 @@ fn main() {
process::exit(1);
}
}
.get_repos(
worktree,
args.force_ssh,
args.remote_name,
)
.get_repos(worktree, args.force_ssh)
}
cmd::RemoteProvider::Gitlab => {
match provider::Gitlab::new(filter, token, args.api_url) {
@@ -80,11 +74,7 @@ fn main() {
process::exit(1);
}
}
.get_repos(
worktree,
args.force_ssh,
args.remote_name,
)
.get_repos(worktree, args.force_ssh)
}
};
@@ -290,7 +280,6 @@ fn main() {
.get_repos(
config.worktree.unwrap_or(false),
config.force_ssh.unwrap_or(false),
config.remote_name,
) {
Ok(provider) => provider,
Err(error) => {
@@ -310,7 +299,6 @@ fn main() {
.get_repos(
config.worktree.unwrap_or(false),
config.force_ssh.unwrap_or(false),
config.remote_name,
) {
Ok(provider) => provider,
Err(error) => {
@@ -394,11 +382,7 @@ fn main() {
process::exit(1);
}
}
.get_repos(
worktree,
args.force_ssh,
args.remote_name,
)
.get_repos(worktree, args.force_ssh)
}
cmd::RemoteProvider::Gitlab => {
match provider::Gitlab::new(filter, token, args.api_url) {
@@ -408,11 +392,7 @@ fn main() {
process::exit(1);
}
}
.get_repos(
worktree,
args.force_ssh,
args.remote_name,
)
.get_repos(worktree, args.force_ssh)
}
};
@@ -504,9 +484,25 @@ fn main() {
None => None,
};
let mut name: &str = &action_args.name;
let subdirectory;
let split = name.split_once('/');
match split {
None => subdirectory = None,
Some(split) => {
if split.0.is_empty() || split.1.is_empty() {
print_error("Worktree name cannot start or end with a slash");
process::exit(1);
} else {
(subdirectory, name) = (Some(Path::new(split.0)), split.1);
}
}
}
match worktree::add_worktree(
&cwd,
&action_args.name,
name,
subdirectory,
track,
action_args.no_track,
) {

View File

@@ -1,6 +1,5 @@
#![feature(io_error_more)]
#![feature(const_option_ext)]
#![forbid(unsafe_code)]
use std::path::Path;

View File

@@ -8,6 +8,7 @@ use super::JsonError;
use super::Project;
use super::Provider;
const PROVIDER_NAME: &str = "github";
const ACCEPT_HEADER_JSON: &str = "application/vnd.github.v3+json";
const GITHUB_API_BASEURL: &str =
option_env!("GITHUB_API_BASEURL").unwrap_or("https://api.github.com");
@@ -87,6 +88,10 @@ impl Provider for Github {
})
}
fn name(&self) -> &str {
PROVIDER_NAME
}
fn filter(&self) -> &Filter {
&self.filter
}
@@ -131,8 +136,8 @@ impl Provider for Github {
fn get_current_user(&self) -> Result<String, ApiErrorResponse<GithubApiErrorResponse>> {
Ok(super::call::<GithubUser, GithubApiErrorResponse>(
&format!("{GITHUB_API_BASEURL}/user"),
Self::auth_header_key(),
self.secret_token(),
&Self::auth_header_key(),
&self.secret_token(),
Some(ACCEPT_HEADER_JSON),
)?
.username)

View File

@@ -8,6 +8,7 @@ use super::JsonError;
use super::Project;
use super::Provider;
const PROVIDER_NAME: &str = "gitlab";
const ACCEPT_HEADER_JSON: &str = "application/json";
const GITLAB_API_BASEURL: &str = option_env!("GITLAB_API_BASEURL").unwrap_or("https://gitlab.com");
@@ -104,6 +105,10 @@ impl Provider for Gitlab {
})
}
fn name(&self) -> &str {
PROVIDER_NAME
}
fn filter(&self) -> &Filter {
&self.filter
}
@@ -152,8 +157,8 @@ impl Provider for Gitlab {
fn get_current_user(&self) -> Result<String, ApiErrorResponse<GitlabApiErrorResponse>> {
Ok(super::call::<GitlabUser, GitlabApiErrorResponse>(
&format!("{}/api/v4/user", self.api_url()),
Self::auth_header_key(),
self.secret_token(),
&Self::auth_header_key(),
&self.secret_token(),
Some(ACCEPT_HEADER_JSON),
)?
.username)

View File

@@ -14,8 +14,6 @@ use super::repo;
use std::collections::HashMap;
const DEFAULT_REMOTE_NAME: &str = "origin";
#[derive(Debug, Deserialize, Serialize, clap::ArgEnum, Clone)]
pub enum RemoteProvider {
#[serde(alias = "github", alias = "GitHub")]
@@ -124,6 +122,7 @@ pub trait Provider {
where
Self: Sized;
fn name(&self) -> &str;
fn filter(&self) -> &Filter;
fn secret_token(&self) -> &auth::AuthToken;
fn auth_header_key() -> &'static str;
@@ -214,7 +213,6 @@ pub trait Provider {
&self,
worktree_setup: bool,
force_ssh: bool,
remote_name: Option<String>,
) -> Result<HashMap<Option<String>, Vec<repo::Repo>>, String> {
let mut repos = vec![];
@@ -294,12 +292,10 @@ pub trait Provider {
let mut ret: HashMap<Option<String>, Vec<repo::Repo>> = HashMap::new();
let remote_name = remote_name.unwrap_or_else(|| DEFAULT_REMOTE_NAME.to_string());
for repo in repos {
let namespace = repo.namespace();
let mut repo = repo.into_repo_config(&remote_name, worktree_setup, force_ssh);
let mut repo = repo.into_repo_config(&self.name(), worktree_setup, force_ssh);
// Namespace is already part of the hashmap key. I'm not too happy
// about the data exchange format here.

View File

@@ -1056,12 +1056,12 @@ impl RepoHandle {
// Note that <remote>/HEAD only exists after a normal clone, there is no way to get the
// remote HEAD afterwards. So this is a "best effort" approach.
if let Ok(remote_head) = self.find_remote_branch(remote_name, "HEAD") {
if let Ok(remote_head) = self.find_remote_branch(&remote_name, "HEAD") {
if let Some(pointer_name) = remote_head.as_reference().symbolic_target() {
if let Some(local_branch_name) =
pointer_name.strip_prefix(&format!("refs/remotes/{}/", remote_name))
{
return Ok(Some(self.find_local_branch(local_branch_name)?));
return Ok(Some(self.find_local_branch(&local_branch_name)?));
} else {
eprintln!("Remote HEAD ({}) pointer is invalid", pointer_name);
}
@@ -1088,7 +1088,7 @@ impl RepoHandle {
if remotes.len() == 1 {
let remote_name = &remotes[0];
if let Some(default_branch) = self.get_remote_default_branch(remote_name)? {
if let Some(default_branch) = self.get_remote_default_branch(&remote_name)? {
return Ok(default_branch);
}
} else {
@@ -1099,13 +1099,17 @@ impl RepoHandle {
}
}
if !default_branches.is_empty()
&& (default_branches.len() == 1
|| default_branches
if !default_branches.is_empty() {
if default_branches.len() == 1 {
return Ok(default_branches.remove(0));
} else {
if default_branches
.windows(2)
.all(|w| w[0].name() == w[1].name()))
{
return Ok(default_branches.remove(0));
.all(|w| w[0].name() == w[1].name())
{
return Ok(default_branches.remove(0));
}
}
}
}

View File

@@ -222,7 +222,7 @@ fn sync_repo(root_path: &Path, repo: &repo::Repo, init_worktree: bool) -> Result
if newly_created && repo.worktree_setup && init_worktree {
match repo_handle.default_branch() {
Ok(branch) => {
worktree::add_worktree(&repo_path, &branch.name()?, None, false)?;
worktree::add_worktree(&repo_path, &branch.name()?, None, None, false)?;
}
Err(_error) => print_repo_error(
&repo.name,

View File

@@ -5,6 +5,95 @@ use super::repo;
pub const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::process::Command;
use tempdir::TempDir;
fn init_empty_repo() -> PathBuf {
let directory = TempDir::new("grm-worktree").unwrap();
Command::new("git")
.args(["init", "."])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("touch")
.args(["test"])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("git")
.args(["add", "test"])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("bash")
.args(["-c", "git ls-files | xargs rm -rf"])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("mv")
.args([".git", GIT_MAIN_WORKTREE_DIRECTORY])
.current_dir(directory.path())
.output()
.unwrap();
Command::new("git")
.args([
"--git-dir",
GIT_MAIN_WORKTREE_DIRECTORY,
"config",
"core.bare",
"true",
])
.current_dir(directory.path())
.output()
.unwrap();
directory.into_path()
}
#[test]
fn add_new_worktree() {
let repo_path = init_empty_repo();
let out = add_worktree(&repo_path, "test", None, None, true);
assert!(out.is_ok());
let repo = git2::Repository::open(repo_path.join("test")).unwrap();
assert_eq!(repo.head().unwrap().shorthand().unwrap(), "test");
assert!(repo
.find_branch("test", git2::BranchType::Local)
.unwrap()
.upstream()
.is_err());
}
#[test]
fn add_new_worktree_with_remote_track() {
let repo_path = init_empty_repo();
let out = add_worktree(&repo_path, "test", None, Some(("origin", "test")), true);
assert!(out.is_ok());
let repo = git2::Repository::open(repo_path.join("test")).unwrap();
assert_eq!(repo.head().unwrap().shorthand().unwrap(), "test");
assert_eq!(
repo.find_branch("test", git2::BranchType::Local)
.unwrap()
.upstream()
.unwrap()
.name()
.unwrap()
.unwrap(),
"test"
);
}
}
// The logic about the base branch and the tracking branch is as follows:
//
// * If a branch with the same name does not exist and no track is given, use the default
@@ -21,17 +110,10 @@ pub const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
pub fn add_worktree(
directory: &Path,
name: &str,
subdirectory: Option<&Path>,
track: Option<(&str, &str)>,
no_track: bool,
) -> Result<(), String> {
// A branch name must never start or end with a slash. Everything else is ok.
if name.starts_with('/') || name.ends_with('/') {
return Err(format!(
"Invalid worktree name: {}. It cannot start or end with a slash",
name
));
}
let repo = repo::RepoHandle::open(directory, true).map_err(|error| match error.kind {
repo::RepoErrorKind::NotFound => {
String::from("Current directory does not contain a worktree setup")
@@ -45,6 +127,11 @@ pub fn add_worktree(
return Err(format!("Worktree {} already exists", &name));
}
let path = match subdirectory {
Some(dir) => directory.join(dir).join(name),
None => directory.join(Path::new(name)),
};
let mut remote_branch_exists = false;
let mut target_branch = match repo.find_local_branch(name) {
@@ -57,9 +144,9 @@ pub fn add_worktree(
if let Ok(local_upstream_branch) = branchref.upstream() {
if remote_branch.name()? != local_upstream_branch.name()? {
print_warning(&format!(
"You specified a tracking branch ({}/{}) for an existing branch ({}), but \
"You specified a tracking branch ({}) for an existing branch ({}), but \
it differs from the current upstream ({}). Will keep current upstream"
, remote_name, remote_branch_name, branchref.name()?, local_upstream_branch.name()?))
, format!("{}/{}", remote_name, remote_branch_name), branchref.name()?, local_upstream_branch.name()?))
}
}
}
@@ -195,80 +282,10 @@ pub fn add_worktree(
}
}
// We have to create subdirectories first, otherwise adding the worktree
// will fail
if name.contains('/') {
let path = Path::new(&name);
if let Some(base) = path.parent() {
// This is a workaround of a bug in libgit2 (?)
//
// When *not* doing this, we will receive an error from the `Repository::worktree()`
// like this:
//
// > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test
//
// This is a discrepancy between the behaviour of libgit2 and the
// git CLI when creating worktrees with slashes:
//
// The git CLI will create the worktree's configuration directory
// inside {git_dir}/worktrees/{last_path_component}. Look at this:
//
// ```
// $ git worktree add 1/2/3 -b 1/2/3
// $ ls .git/worktrees
// 3
// ```
//
// Interesting: When adding a worktree with a different name but the
// same final path component, git starts adding a counter suffix to
// the worktree directories:
//
// ```
// $ git worktree add 1/3/3 -b 1/3/3
// $ git worktree add 1/4/3 -b 1/4/3
// $ ls .git/worktrees
// 3
// 31
// 32
// ```
//
// I *guess* that the mapping back from the worktree directory under .git to the actual
// worktree directory is done via the `gitdir` file inside `.git/worktrees/{worktree}.
// This means that the actual directory would not matter. You can verify this by
// just renaming it:
//
// ```
// $ mv .git/worktrees/3 .git/worktrees/foobar
// $ git worktree list
// /tmp/ fcc8a2a7 [master]
// /tmp/1/2/3 fcc8a2a7 [1/2/3]
// /tmp/1/3/3 fcc8a2a7 [1/3/3]
// /tmp/1/4/3 fcc8a2a7 [1/4/3]
// ```
//
// => Still works
//
// Anyway, libgit2 does not do this: It tries to create the worktree
// directory inside .git with the exact name of the worktree, including
// any slashes. It should be this code:
//
// https://github.com/libgit2/libgit2/blob/f98dd5438f8d7bfd557b612fdf1605b1c3fb8eaf/src/libgit2/worktree.c#L346
//
// As a workaround, we can create the base directory manually for now.
//
// Tracking upstream issue: https://github.com/libgit2/libgit2/issues/6327
std::fs::create_dir_all(
directory
.join(GIT_MAIN_WORKTREE_DIRECTORY)
.join("worktrees")
.join(base),
)
.map_err(|error| error.to_string())?;
std::fs::create_dir_all(base).map_err(|error| error.to_string())?;
}
if let Some(subdirectory) = subdirectory {
std::fs::create_dir_all(subdirectory).map_err(|error| error.to_string())?;
}
repo.new_worktree(name, &directory.join(&name), &target_branch)?;
repo.new_worktree(name, &path, &target_branch)?;
Ok(())
}