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
29 changed files with 731 additions and 1926 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

145
Cargo.lock generated
View File

@@ -80,16 +80,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.7"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7b16274bb247b45177db843202209b12191b631a14a9d06e41b3777d6ecf14"
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.7"
version = "3.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
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.4"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
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.4"
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",
@@ -535,9 +544,9 @@ dependencies = [
[[package]]
name = "linked-hash-map"
version = "0.5.6"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
@@ -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"
@@ -895,9 +904,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.82"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"ryu",
@@ -974,9 +983,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.8.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "socket2"
@@ -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.2"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
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.28"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
dependencies = [
"once_cell",
"lazy_static",
]
[[package]]
@@ -1154,19 +1163,25 @@ 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"
version = "0.1.20"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
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.4"
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.7"
version = "=3.1.18"
features = ["derive", "cargo"]
[dependencies.console]
@@ -64,13 +64,13 @@ 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"
[dependencies.serde_json]
version = "=1.0.82"
version = "=1.0.81"
[dependencies.isahc]
version = "=1.7.2"

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 --target {{static_target}} --features=static-build
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,9 +52,9 @@ test-e2e +tests=".": test-binary
&& docker-compose build \
&& docker-compose run \
--rm \
-v $PWD/../target/x86_64-unknown-linux-musl/e2e-tests/grm:/grm \
-v $PWD/../target/{{target}}/e2e-tests/grm:/grm \
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 -p no:cacheprovider --color=yes "$@"" \
&& docker-compose rm --stop -f
update-dependencies: update-cargo-dependencies

View File

@@ -1,14 +1,8 @@
import os
from helpers import *
def pytest_configure(config):
os.environ["GIT_AUTHOR_NAME"] = "Example user"
os.environ["GIT_AUTHOR_EMAIL"] = "user@example.com"
os.environ["GIT_COMMITTER_NAME"] = "Example user"
os.environ["GIT_COMMITTER_EMAIL"] = "user@example.com"
def pytest_unconfigure(config):
pass

View File

@@ -1,7 +1,7 @@
FROM docker.io/debian:11.3
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
&& apt-get install -y \
python3-pytest \
python3-toml \
python3-git \

View File

@@ -5,26 +5,12 @@ import os.path
import subprocess
import tempfile
import hashlib
import shutil
import inspect
import git
binary = os.environ["GRM_BINARY"]
def funcname():
return inspect.stack()[1][3]
def copytree(src, dest):
shutil.copytree(src, dest, dirs_exist_ok=True)
def get_temporary_directory(dir=None):
return tempfile.TemporaryDirectory(dir=dir)
def grm(args, cwd=None, is_invalid=False):
cmd = subprocess.run([binary] + args, cwd=cwd, capture_output=True, text=True)
if not is_invalid:
@@ -39,12 +25,8 @@ def grm(args, cwd=None, is_invalid=False):
def shell(script):
script = "set -o errexit\nset -o nounset\nset -o pipefail\n" + script
cmd = subprocess.run(["bash"], input=script, text=True, capture_output=True)
if cmd.returncode != 0:
print(cmd.stdout)
print(cmd.stderr)
cmd.check_returncode()
script = "set -o errexit\nset -o nounset\n" + script
subprocess.run(["bash"], input=script, text=True, check=True)
def checksum_directory(path):
@@ -130,204 +112,78 @@ def checksum_directory(path):
class TempGitRepository:
def __init__(self, dir=None):
self.dir = dir
pass
def __enter__(self):
self.tmpdir = get_temporary_directory(self.dir)
self.remote_1 = get_temporary_directory()
self.remote_2 = get_temporary_directory()
cmd = f"""
self.tmpdir = tempfile.TemporaryDirectory(dir=self.dir)
self.remote_1_dir = tempfile.TemporaryDirectory()
self.remote_2_dir = tempfile.TemporaryDirectory()
shell(
f"""
cd {self.tmpdir.name}
git -c init.defaultBranch=master init
git init
echo test > root-commit
git add root-commit
git commit -m "root-commit"
git remote add origin file://{self.remote_1.name}
git remote add otherremote file://{self.remote_2.name}
git remote add origin file://{self.remote_1_dir.name}
git remote add otherremote file://{self.remote_2_dir.name}
"""
shell(cmd)
)
return self.tmpdir.name
def __exit__(self, exc_type, exc_val, exc_tb):
pass
class TempGitRemote:
obj = {}
def __init__(self, tmpdir, remoteid=None):
self.tmpdir = tmpdir
self.remoteid = remoteid
@classmethod
def get(cls, cachekey=None, initfunc=None):
if cachekey is None:
tmpdir = get_temporary_directory()
shell(
f"""
cd {tmpdir.name}
git -c init.defaultBranch=master init --bare
"""
)
newobj = cls(tmpdir)
remoteid = None
if initfunc is not None:
remoteid = newobj.init(initfunc)
newobj.remoteid = remoteid
return newobj, remoteid
else:
refresh = False
if cachekey not in cls.obj:
tmpdir = get_temporary_directory()
shell(
f"""
cd {tmpdir.name}
git -c init.defaultBranch=master init --bare
"""
)
newobj = cls(tmpdir)
remoteid = newobj.init(initfunc)
newobj.remoteid = remoteid
cls.obj[cachekey] = newobj
return cls.clone(cls.obj[cachekey])
@classmethod
def clone(cls, source):
new_remote = get_temporary_directory()
copytree(source.tmpdir.name, new_remote.name)
return cls(new_remote, source.remoteid), source.remoteid
def init(self, func):
return func(self.tmpdir.name)
def __enter__(self):
return self.tmpdir
def __exit__(self, exc_type, exc_val, exc_tb):
pass
del self.tmpdir
del self.remote_1_dir
del self.remote_2_dir
class TempGitRepositoryWorktree:
obj = {}
def __init__(self, remotes, tmpdir, commit, remote1, remote2, remote1id, remote2id):
self.remotes = remotes
self.tmpdir = tmpdir
self.commit = commit
self.remote1 = remote1
self.remote2 = remote2
self.remote1id = remote1id
self.remote2id = remote2id
@classmethod
def get(cls, cachekey, branch=None, remotes=2, basedir=None, remote_setup=None):
if cachekey not in cls.obj:
tmpdir = get_temporary_directory()
shell(
f"""
cd {tmpdir.name}
git -c init.defaultBranch=master init
echo test > root-commit-in-worktree-1
git add root-commit-in-worktree-1
git commit -m "root-commit-in-worktree-1"
echo test > root-commit-in-worktree-2
git add root-commit-in-worktree-2
git commit -m "root-commit-in-worktree-2"
git ls-files | xargs rm -rf
mv .git .git-main-working-tree
git --git-dir .git-main-working-tree config core.bare true
"""
)
repo = git.Repo(f"{tmpdir.name}/.git-main-working-tree")
commit = repo.head.commit.hexsha
if branch is not None:
repo.create_head(branch)
remote1 = None
remote2 = None
remote1id = None
remote2id = None
if remotes >= 1:
cachekeyremote, initfunc = (remote_setup or ((None, None),))[0]
remote1, remote1id = TempGitRemote.get(
cachekey=cachekeyremote, initfunc=initfunc
)
remote1 = remote1
remote1id = remote1id
shell(
f"""
cd {tmpdir.name}
git --git-dir .git-main-working-tree remote add origin file://{remote1.tmpdir.name}
"""
)
repo.remotes.origin.fetch()
repo.remotes.origin.push("master")
if remotes >= 2:
cachekeyremote, initfunc = (remote_setup or (None, (None, None)))[1]
remote2, remote2id = TempGitRemote.get(
cachekey=cachekeyremote, initfunc=initfunc
)
remote2 = remote2
remote2id = remote2id
shell(
f"""
cd {tmpdir.name}
git --git-dir .git-main-working-tree remote add otherremote file://{remote2.tmpdir.name}
"""
)
repo.remotes.otherremote.fetch()
repo.remotes.otherremote.push("master")
cls.obj[cachekey] = cls(
remotes, tmpdir, commit, remote1, remote2, remote1id, remote2id
)
return cls.clone(cls.obj[cachekey], remote_setup=remote_setup)
@classmethod
def clone(cls, source, remote_setup):
newdir = get_temporary_directory()
copytree(source.tmpdir.name, newdir.name)
remote1 = None
remote2 = None
remote1id = None
remote2id = None
repo = git.Repo(os.path.join(newdir.name, ".git-main-working-tree"))
if source.remotes >= 1:
cachekey, initfunc = (remote_setup or ((None, None),))[0]
remote1, remote1id = TempGitRemote.get(cachekey=cachekey, initfunc=initfunc)
if remote1id != source.remote1id:
repo.remotes.origin.fetch()
repo.remotes.origin.push("master")
if source.remotes >= 2:
cachekey, initfunc = (remote_setup or (None, (None, None)))[1]
remote2, remote2id = TempGitRemote.get(cachekey=cachekey, initfunc=initfunc)
if remote2id != source.remote2id:
repo.remotes.otherremote.fetch()
repo.remotes.otherremote.push("master")
return cls(
source.remotes,
newdir,
source.commit,
remote1,
remote2,
remote1id,
remote2id,
)
def __init__(self):
pass
def __enter__(self):
return (self.tmpdir.name, self.commit)
self.tmpdir = tempfile.TemporaryDirectory()
self.remote_1_dir = tempfile.TemporaryDirectory()
self.remote_2_dir = tempfile.TemporaryDirectory()
shell(
f"""
cd {self.remote_1_dir.name}
git init --bare
"""
)
shell(
f"""
cd {self.remote_2_dir.name}
git init --bare
"""
)
shell(
f"""
cd {self.tmpdir.name}
git init
echo test > root-commit-in-worktree-1
git add root-commit-in-worktree-1
git commit -m "root-commit-in-worktree-1"
echo test > root-commit-in-worktree-2
git add root-commit-in-worktree-2
git commit -m "root-commit-in-worktree-2"
git remote add origin file://{self.remote_1_dir.name}
git remote add otherremote file://{self.remote_2_dir.name}
git push origin HEAD:master
git ls-files | xargs rm -rf
mv .git .git-main-working-tree
git --git-dir .git-main-working-tree config core.bare true
"""
)
commit = git.Repo(
f"{self.tmpdir.name}/.git-main-working-tree"
).head.commit.hexsha
return (self.tmpdir.name, commit)
def __exit__(self, exc_type, exc_val, exc_tb):
pass
del self.tmpdir
del self.remote_1_dir
del self.remote_2_dir
class RepoTree:
@@ -335,7 +191,7 @@ class RepoTree:
pass
def __enter__(self):
self.root = get_temporary_directory()
self.root = tempfile.TemporaryDirectory()
self.config = tempfile.NamedTemporaryFile()
with open(self.config.name, "w") as f:
f.write(
@@ -366,7 +222,7 @@ class EmptyDir:
pass
def __enter__(self):
self.tmpdir = get_temporary_directory()
self.tmpdir = tempfile.TemporaryDirectory()
return self.tmpdir.name
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -378,7 +234,7 @@ class NonGitDir:
pass
def __enter__(self):
self.tmpdir = get_temporary_directory()
self.tmpdir = tempfile.TemporaryDirectory()
shell(
f"""
cd {self.tmpdir.name}
@@ -398,11 +254,11 @@ class TempGitFileRemote:
pass
def __enter__(self):
self.tmpdir = get_temporary_directory()
self.tmpdir = tempfile.TemporaryDirectory()
shell(
f"""
cd {self.tmpdir.name}
git -c init.defaultBranch=master init
git init
echo test > root-commit-in-remote-1
git add root-commit-in-remote-1
git commit -m "root-commit-in-remote-1"

View File

@@ -73,7 +73,7 @@ def test_repos_find(configtype, default):
mkdir repo1
(
cd ./repo1
git -c init.defaultBranch=master init
git init
echo test > test
git add test
git commit -m "commit1"
@@ -83,7 +83,7 @@ def test_repos_find(configtype, default):
mkdir repo2
(
cd ./repo2
git -c init.defaultBranch=master init
git init
git checkout -b main
echo test > test
git add test
@@ -203,7 +203,7 @@ def test_repos_find_with_invalid_repo(configtype, default):
mkdir repo1
(
cd ./repo1
git -c init.defaultBranch=master init
git init
echo test > test
git add test
git commit -m "commit1"
@@ -213,7 +213,7 @@ def test_repos_find_with_invalid_repo(configtype, default):
mkdir repo2
(
cd ./repo2
git -c init.defaultBranch=master init
git init
git checkout -b main
echo test > test
git add test

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

@@ -6,7 +6,7 @@ from helpers import *
def test_worktree_clean():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
assert "test" in os.listdir(base_dir)
@@ -17,7 +17,7 @@ def test_worktree_clean():
def test_worktree_clean_refusal_no_tracking_branch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -31,7 +31,7 @@ def test_worktree_clean_refusal_no_tracking_branch():
def test_worktree_clean_refusal_uncommited_changes_new_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -47,7 +47,7 @@ def test_worktree_clean_refusal_uncommited_changes_new_file():
def test_worktree_clean_refusal_uncommited_changes_changed_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -63,7 +63,7 @@ def test_worktree_clean_refusal_uncommited_changes_changed_file():
def test_worktree_clean_refusal_uncommited_changes_cleand_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -81,7 +81,7 @@ def test_worktree_clean_refusal_uncommited_changes_cleand_file():
def test_worktree_clean_refusal_commited_changes():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -99,7 +99,7 @@ def test_worktree_clean_refusal_commited_changes():
def test_worktree_clean_refusal_tracking_branch_mismatch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -117,7 +117,7 @@ def test_worktree_clean_refusal_tracking_branch_mismatch():
def test_worktree_clean_fail_from_subdir():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -148,7 +148,7 @@ def test_worktree_clean_non_git():
def test_worktree_clean_configured_default_branch(
configure_default_branch, branch_list_empty
):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
if configure_default_branch:
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
if branch_list_empty:

View File

@@ -6,7 +6,7 @@ from helpers import *
def test_worktree_never_clean_persistent_branches():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
"""
@@ -33,7 +33,7 @@ def test_worktree_never_clean_persistent_branches():
def test_worktree_clean_branch_merged_into_persistent():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
"""
@@ -72,7 +72,7 @@ def test_worktree_clean_branch_merged_into_persistent():
def test_worktree_no_clean_unmerged_branch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
"""
@@ -105,7 +105,7 @@ def test_worktree_no_clean_unmerged_branch():
def test_worktree_delete_branch_merged_into_persistent():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
"""

View File

@@ -23,7 +23,7 @@ def test_convert():
def test_convert_already_worktree():
with TempGitRepositoryWorktree.get(funcname()) as (git_dir, _commit):
with TempGitRepositoryWorktree() as (git_dir, _commit):
before = checksum_directory(git_dir)
cmd = grm(["wt", "convert"], cwd=git_dir)

View File

@@ -9,7 +9,7 @@ import git
def test_worktree_fetch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, root_commit):
with TempGitRepositoryWorktree() as (base_dir, root_commit):
with TempGitFileRemote() as (remote_path, _remote_sha):
shell(
f"""
@@ -56,7 +56,7 @@ def test_worktree_fetch():
@pytest.mark.parametrize("has_changes", [True, False])
@pytest.mark.parametrize("stash", [True, False])
def test_worktree_pull(rebase, ffable, has_changes, stash):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, root_commit):
with TempGitRepositoryWorktree() as (base_dir, root_commit):
with TempGitFileRemote() as (remote_path, _remote_sha):
shell(
f"""

View File

@@ -14,7 +14,7 @@ import git
@pytest.mark.parametrize("has_changes", [True, False])
@pytest.mark.parametrize("stash", [True, False])
def test_worktree_rebase(pull, rebase, ffable, has_changes, stash):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _root_commit):
with TempGitRepositoryWorktree() as (base_dir, _root_commit):
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write('persistent_branches = ["mybasebranch"]')

View File

@@ -9,7 +9,7 @@ import pytest
@pytest.mark.parametrize("has_config", [True, False])
def test_worktree_status(has_config):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
if has_config:
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write("")
@@ -24,7 +24,7 @@ def test_worktree_status(has_config):
def test_worktree_status_fail_from_subdir():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -51,7 +51,7 @@ def test_worktree_status_non_git():
def test_worktree_status_warn_with_non_worktree_dir():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0

View File

@@ -4,565 +4,96 @@ from helpers import *
import git
import pytest
import datetime
import os.path
@pytest.mark.parametrize(
"config_setup",
(
(False, False, False),
(True, False, False),
(True, False, True),
(True, True, False),
(True, True, True),
),
)
@pytest.mark.parametrize("explicit_notrack", [True, False])
@pytest.mark.parametrize("explicit_track", [True, False])
@pytest.mark.parametrize(
"local_branch_setup", ((False, False), (True, False), (True, True))
)
@pytest.mark.parametrize("remote_branch_already_exists", [True, False])
@pytest.mark.parametrize("remote_branch_with_prefix_already_exists", [True, False])
@pytest.mark.parametrize(
"remote_setup",
(
(0, "origin", False),
(1, "origin", False),
(2, "origin", False),
(2, "otherremote", False),
(2, "origin", True),
(2, "otherremote", True),
),
)
@pytest.mark.parametrize("track_differs_from_existing_branch_upstream", [True, False])
@pytest.mark.parametrize("worktree_with_slash", [True, False])
def test_worktree_add(
config_setup,
explicit_notrack,
explicit_track,
local_branch_setup,
remote_branch_already_exists,
remote_branch_with_prefix_already_exists,
remote_setup,
track_differs_from_existing_branch_upstream,
worktree_with_slash,
@pytest.mark.parametrize("has_config", [True, False])
@pytest.mark.parametrize("has_default", [True, False])
@pytest.mark.parametrize("has_prefix", [True, False])
def test_worktree_add_simple(
remote_branch_already_exists, has_config, has_default, has_prefix
):
(remote_count, default_remote, remotes_differ) = remote_setup
(
config_enabled,
config_has_default_remote_prefix,
config_has_default_track_enabled,
) = config_setup
(local_branch_exists, local_branch_has_tracking_branch) = local_branch_setup
has_remotes = True if remote_count > 0 else False
if worktree_with_slash:
worktree_name = "dir/nested/test"
else:
worktree_name = "test"
if track_differs_from_existing_branch_upstream:
explicit_track_branch_name = f"{default_remote}/somethingelse"
else:
explicit_track_branch_name = f"{default_remote}/{worktree_name}"
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
# GitPython has some weird behaviour here. It is not possible to use kwargs
# to set the commit and author date.
#
# `committer_date=x` (which is documented) does not work, as `git commit`
# does not accept --committer-date
#
# `author_date=x` does not work, as it's now called --date in `git commit`
#
# `date=x` should work, but is refused by GitPython, as it does not know
# about the new behaviour in `git commit`
#
# Fortunately, there are env variables that control those timestamps.
os.environ["GIT_COMMITTER_DATE"] = str(timestamp)
os.environ["GIT_AUTHOR_DATE"] = str(timestamp)
def setup_remote1(directory):
if remote_branch_already_exists:
with tempfile.TemporaryDirectory() as cloned:
repo = git.Repo.clone_from(directory, cloned)
newfile = os.path.join(cloned, "change")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit")
repo.remotes.origin.push(f"HEAD:{worktree_name}", force=True)
if remote_branch_with_prefix_already_exists:
with tempfile.TemporaryDirectory() as cloned:
repo = git.Repo.clone_from(directory, cloned)
newfile = os.path.join(cloned, "change2")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit")
repo.remotes.origin.push(f"HEAD:myprefix/{worktree_name}", force=True)
return "_".join(
[
str(worktree_with_slash),
str(remote_branch_already_exists),
str(remote_branch_with_prefix_already_exists),
str(remotes_differ),
]
)
def setup_remote2(directory):
if remote_branch_already_exists:
with tempfile.TemporaryDirectory() as cloned:
repo = git.Repo.clone_from(directory, cloned)
newfile = os.path.join(cloned, "change")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit")
if remotes_differ:
newfile = os.path.join(cloned, "change_on_second_remote")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit_on_second_remote")
repo.remotes.origin.push(f"HEAD:{worktree_name}", force=True)
if remote_branch_with_prefix_already_exists:
with tempfile.TemporaryDirectory() as cloned:
repo = git.Repo.clone_from(directory, cloned)
newfile = os.path.join(cloned, "change2")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit")
if remotes_differ:
newfile = os.path.join(cloned, "change_on_second_remote2")
open(newfile, "w").close()
repo.index.add([newfile])
repo.index.commit("commit_on_second_remote2")
repo.remotes.origin.push(f"HEAD:myprefix/{worktree_name}", force=True)
return "_".join(
[
str(worktree_with_slash),
str(remote_branch_already_exists),
str(remote_branch_with_prefix_already_exists),
str(remotes_differ),
]
)
cachefn = lambda nr: "_".join(
[
str(nr),
str(default_remote),
str(local_branch_exists),
str(remote_branch_already_exists),
str(remote_branch_with_prefix_already_exists),
str(remote_count),
str(remotes_differ),
str(worktree_name),
]
)
remote1_cache_key = cachefn(1)
remote2_cache_key = cachefn(2)
cachekey = "_".join(
[
str(local_branch_exists),
str(local_branch_has_tracking_branch),
str(remote_branch_already_exists),
str(remote_branch_with_prefix_already_exists),
str(remote_count),
str(remotes_differ),
str(worktree_name),
]
)
with TempGitRepositoryWorktree.get(
cachekey=cachekey,
branch=worktree_name if local_branch_exists else None,
remotes=remote_count,
remote_setup=[
[remote1_cache_key, setup_remote1],
[remote2_cache_key, setup_remote2],
],
) as (base_dir, initial_commit):
repo = git.Repo(os.path.join(base_dir, ".git-main-working-tree"))
if config_enabled:
with TempGitRepositoryWorktree() as (base_dir, _commit):
if has_config:
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
f"""
[track]
default = {str(config_has_default_track_enabled).lower()}
default_remote = "{default_remote}"
"""
[track]
default = {str(has_default).lower()}
default_remote = "origin"
"""
)
if config_has_default_remote_prefix:
if has_prefix:
f.write(
"""
default_remote_prefix = "myprefix"
"""
)
if local_branch_exists:
if has_remotes and local_branch_has_tracking_branch:
origin = repo.remote(default_remote)
if remote_count >= 2:
otherremote = repo.remote("otherremote")
br = list(filter(lambda x: x.name == worktree_name, repo.branches))[0]
assert os.path.exists(base_dir)
if track_differs_from_existing_branch_upstream:
origin.push(
f"{worktree_name}:someothername", force=True, set_upstream=True
)
if remote_count >= 2:
otherremote.push(
f"{worktree_name}:someothername",
force=True,
set_upstream=True,
)
br.set_tracking_branch(
list(
filter(
lambda x: x.remote_head == "someothername", origin.refs
)
)[0]
)
else:
origin.push(
f"{worktree_name}:{worktree_name}",
force=True,
set_upstream=True,
)
if remote_count >= 2:
otherremote.push(
f"{worktree_name}:{worktree_name}",
force=True,
set_upstream=True,
)
br.set_tracking_branch(
list(
filter(
lambda x: x.remote_head == worktree_name, origin.refs
)
)[0]
)
args = ["wt", "add", worktree_name]
if explicit_track:
args.extend(["--track", explicit_track_branch_name])
if explicit_notrack:
args.extend(["--no-track"])
cmd = grm(args, cwd=base_dir)
if explicit_track and not explicit_notrack and not has_remotes:
assert cmd.returncode != 0
assert f'remote "{default_remote}" not found' in cmd.stderr.lower()
return
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)
assert cmd.returncode == 0
assert len(cmd.stdout.strip().split("\n")) == 1
assert f"worktree {worktree_name} created" in cmd.stdout.lower()
def check_deviation_error(base):
if (
not local_branch_exists
and (explicit_notrack or (not explicit_notrack and not explicit_track))
and (
remote_branch_already_exists
or (
config_enabled
and config_has_default_remote_prefix
and remote_branch_with_prefix_already_exists
)
)
and remote_count >= 2
and remotes_differ
):
assert (
f"branch exists on multiple remotes, but they deviate"
in cmd.stderr.lower()
)
assert len(cmd.stderr.strip().split("\n")) == base + 1
else:
if base == 0:
assert len(cmd.stderr) == base
else:
assert len(cmd.stderr.strip().split("\n")) == base
if explicit_track and explicit_notrack:
assert "--track will be ignored" in cmd.stderr.lower()
check_deviation_error(1)
else:
check_deviation_error(0)
files = os.listdir(base_dir)
if config_enabled is True:
if worktree_with_slash:
assert set(files) == {".git-main-working-tree", "grm.toml", "dir"}
else:
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
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"))) == {"nested"}
assert set(os.listdir(os.path.join(base_dir, "dir/nested"))) == {"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"))) == {"nested"}
assert set(os.listdir(os.path.join(base_dir, "dir/nested"))) == {"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()
assert str(repo.head.ref) == worktree_name
local_commit = repo.head.commit.hexsha
if not has_remotes:
assert local_commit == initial_commit
elif local_branch_exists:
assert local_commit == initial_commit
elif explicit_track and not explicit_notrack:
assert local_commit == repo.commit(explicit_track_branch_name).hexsha
elif explicit_notrack:
if config_enabled and config_has_default_remote_prefix:
if remote_branch_with_prefix_already_exists:
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
elif remote_count == 1:
if config_enabled and config_has_default_remote_prefix:
if remote_branch_with_prefix_already_exists:
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
elif remotes_differ:
if config_enabled: # we have a default remote
if (
config_has_default_remote_prefix
and remote_branch_with_prefix_already_exists
):
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
else:
assert local_commit == initial_commit
else:
if config_enabled and config_has_default_remote_prefix:
if remote_branch_with_prefix_already_exists:
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
elif config_enabled:
if not config_has_default_remote_prefix:
if config_has_default_track_enabled:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
if remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
else:
if remote_branch_with_prefix_already_exists:
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
elif remote_branch_already_exists:
assert (
local_commit
== repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
elif config_has_default_track_enabled:
assert (
local_commit
== repo.commit(
f"{default_remote}/myprefix/{worktree_name}"
).hexsha
)
else:
assert local_commit == initial_commit
elif remote_branch_already_exists and not remotes_differ:
assert (
local_commit == repo.commit(f"{default_remote}/{worktree_name}").hexsha
)
else:
assert local_commit == initial_commit
# Check whether tracking is ok
if not has_remotes:
assert repo.active_branch.tracking_branch() is None
elif explicit_notrack:
if local_branch_exists and local_branch_has_tracking_branch:
if track_differs_from_existing_branch_upstream:
assert (
str(repo.active_branch.tracking_branch())
== f"{default_remote}/someothername"
)
else:
assert (
str(repo.active_branch.tracking_branch())
== f"{default_remote}/{worktree_name}"
)
else:
assert repo.active_branch.tracking_branch() is None
elif explicit_track:
assert (
str(repo.active_branch.tracking_branch()) == explicit_track_branch_name
)
elif config_enabled and config_has_default_track_enabled:
if config_has_default_remote_prefix:
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"{default_remote}/myprefix/{worktree_name}"
str(repo.active_branch.tracking_branch()) == "origin/myprefix/test"
)
else:
assert (
str(repo.active_branch.tracking_branch())
== f"{default_remote}/{worktree_name}"
)
elif local_branch_exists and local_branch_has_tracking_branch:
if track_differs_from_existing_branch_upstream:
assert (
str(repo.active_branch.tracking_branch())
== f"{default_remote}/someothername"
)
else:
assert (
str(repo.active_branch.tracking_branch())
== f"{default_remote}/{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():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
for worktree_name in [
"/absolute/path",
"trailingslash/",
"with spaces",
"with\t tabs",
"with\nnewline",
]:
args = ["wt", "add", worktree_name]
cmd = grm(args, cwd=base_dir)
assert cmd.returncode != 0
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())
def test_worktree_add_into_subdirectory():
with TempGitRepositoryWorktree() as (base_dir, _commit):
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"}
def test_worktree_add_invalid_track():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
for track in ["/absolute/path", "trailingslash/", "/"]:
args = ["wt", "add", "foo", "--track", track]
cmd = grm(args, cwd=base_dir)
assert cmd.returncode != 0
assert len(cmd.stderr.strip().split("\n")) == 1
assert not os.path.exists("foo")
assert not os.path.exists(os.path.join(base_dir, "foo"))
assert "tracking branch" in str(cmd.stderr.lower())
files = os.listdir(os.path.join(base_dir, "dir"))
assert set(files) == {"test"}
@pytest.mark.parametrize("use_track", [True, False])
@pytest.mark.parametrize("use_configuration", [True, False])
@pytest.mark.parametrize("use_configuration_default", [True, False])
def test_worktree_add_invalid_remote_name(
use_track, use_configuration, use_configuration_default
):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
if use_configuration:
with open(os.path.join(base_dir, "grm.toml"), "w") as f:
f.write(
f"""
[track]
default = {str(use_configuration_default).lower()}
default_remote = "thisremotedoesnotexist"
"""
)
args = ["wt", "add", "foo"]
if use_track:
args.extend(["--track", "thisremotedoesnotexist/master"])
cmd = grm(args, cwd=base_dir)
if use_track or (use_configuration and use_configuration_default):
assert cmd.returncode != 0
assert "thisremotedoesnotexist" in cmd.stderr
else:
assert cmd.returncode == 0
assert len(cmd.stderr) == 0
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():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "/dir/test"], cwd=base_dir)
assert cmd.returncode == 1
assert "dir" not in os.listdir(base_dir)
@@ -573,8 +104,177 @@ def test_worktree_add_into_invalid_subdirectory():
assert "dir" not in os.listdir(base_dir)
@pytest.mark.parametrize("remote_branch_already_exists", [True, False])
@pytest.mark.parametrize("has_config", [True, False])
@pytest.mark.parametrize("has_default", [True, False])
@pytest.mark.parametrize("has_prefix", [True, False])
def test_worktree_add_with_tracking(
remote_branch_already_exists, has_config, has_default, has_prefix
):
with TempGitRepositoryWorktree() as (base_dir, _commit):
if has_config:
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", "--track", "origin/test"], cwd=base_dir)
print(cmd.stderr)
assert cmd.returncode == 0
files = os.listdir(base_dir)
if has_config is True:
assert len(files) == 3
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
else:
assert len(files) == 2
assert set(files) == {".git-main-working-tree", "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"
assert str(repo.active_branch.tracking_branch()) == "origin/test"
@pytest.mark.parametrize("has_config", [True, False])
@pytest.mark.parametrize("has_default", [True, False])
@pytest.mark.parametrize("has_prefix", [True, False])
@pytest.mark.parametrize("track", [True, False])
def test_worktree_add_with_explicit_no_tracking(
has_config, has_default, has_prefix, track
):
with TempGitRepositoryWorktree() as (base_dir, _commit):
if has_config:
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 track is True:
cmd = grm(
["wt", "add", "test", "--track", "origin/test", "--no-track"],
cwd=base_dir,
)
else:
cmd = grm(["wt", "add", "test", "--no-track"], cwd=base_dir)
print(cmd.stderr)
assert cmd.returncode == 0
files = os.listdir(base_dir)
if has_config is True:
assert len(files) == 3
assert set(files) == {".git-main-working-tree", "grm.toml", "test"}
else:
assert len(files) == 2
assert set(files) == {".git-main-working-tree", "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"
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.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
assert "test" in os.listdir(base_dir)
@@ -586,35 +286,12 @@ def test_worktree_delete():
cmd = grm(["wt", "add", "check"], cwd=base_dir)
assert cmd.returncode == 0
repo = git.Repo(os.path.join(base_dir, ".git-main-working-tree"))
print(repo.branches)
assert "test" not in [str(b) for b in repo.branches]
@pytest.mark.parametrize("has_other_worktree", [True, False])
def test_worktree_delete_in_subfolder(has_other_worktree):
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
cmd = grm(["wt", "add", "dir/test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
assert "dir" in os.listdir(base_dir)
if has_other_worktree is True:
cmd = grm(
["wt", "add", "dir/test2", "--track", "origin/test"], cwd=base_dir
)
assert cmd.returncode == 0
assert {"test", "test2"} == set(os.listdir(os.path.join(base_dir, "dir")))
else:
assert {"test"} == set(os.listdir(os.path.join(base_dir, "dir")))
cmd = grm(["wt", "delete", "dir/test"], cwd=base_dir)
assert cmd.returncode == 0
if has_other_worktree is True:
assert {"test2"} == set(os.listdir(os.path.join(base_dir, "dir")))
else:
assert "dir" not in os.listdir(base_dir)
def test_worktree_delete_refusal_no_tracking_branch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -630,7 +307,7 @@ def test_worktree_delete_refusal_no_tracking_branch():
def test_worktree_delete_refusal_uncommited_changes_new_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -648,7 +325,7 @@ def test_worktree_delete_refusal_uncommited_changes_new_file():
def test_worktree_delete_refusal_uncommited_changes_changed_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -666,7 +343,7 @@ def test_worktree_delete_refusal_uncommited_changes_changed_file():
def test_worktree_delete_refusal_uncommited_changes_deleted_file():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -686,7 +363,7 @@ def test_worktree_delete_refusal_uncommited_changes_deleted_file():
def test_worktree_delete_refusal_commited_changes():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -706,7 +383,7 @@ def test_worktree_delete_refusal_commited_changes():
def test_worktree_delete_refusal_tracking_branch_mismatch():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -726,7 +403,7 @@ def test_worktree_delete_refusal_tracking_branch_mismatch():
def test_worktree_delete_force_refusal():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test"], cwd=base_dir)
assert cmd.returncode == 0
@@ -736,7 +413,7 @@ def test_worktree_delete_force_refusal():
def test_worktree_add_delete_add():
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
with TempGitRepositoryWorktree() as (base_dir, _commit):
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
assert cmd.returncode == 0
assert "test" in os.listdir(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)
}
};
@@ -483,9 +463,6 @@ fn main() {
match args.action {
cmd::WorktreeAction::Add(action_args) => {
if action_args.track.is_some() && action_args.no_track {
print_warning("You are using --track and --no-track at the same time. --track will be ignored");
}
let track = match &action_args.track {
Some(branch) => {
let split = branch.split_once('/');
@@ -507,20 +484,29 @@ 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,
) {
Ok(warnings) => {
if let Some(warnings) = warnings {
for warning in warnings {
print_warning(&warning);
}
}
print_success(&format!("Worktree {} created", &action_args.name));
}
Ok(_) => print_success(&format!("Worktree {} created", &action_args.name)),
Err(error) => {
print_error(&format!("Error creating worktree: {}", error));
process::exit(1);
@@ -528,6 +514,8 @@ fn main() {
}
}
cmd::WorktreeAction::Delete(action_args) => {
let worktree_dir = cwd.join(&action_args.name);
let worktree_config = match repo::read_worktree_root_config(&cwd) {
Ok(config) => config,
Err(error) => {
@@ -545,9 +533,8 @@ fn main() {
});
match repo.remove_worktree(
&cwd,
&action_args.name,
Path::new(&action_args.name),
&worktree_dir,
action_args.force,
&worktree_config,
) {

View File

@@ -1,6 +1,5 @@
#![feature(io_error_more)]
#![feature(const_option_ext)]
#![forbid(unsafe_code)]
use std::path::Path;
@@ -14,6 +13,8 @@ pub mod table;
pub mod tree;
pub mod worktree;
const BRANCH_NAMESPACE_SEPARATOR: &str = "/";
/// Find all git repositories under root, recursively
///
/// The bool in the return value specifies whether there is a repository

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));
}
}
}
}
@@ -1153,21 +1157,18 @@ impl RepoHandle {
pub fn remove_worktree(
&self,
base_dir: &Path,
name: &str,
worktree_dir: &Path,
force: bool,
worktree_config: &Option<WorktreeRootConfig>,
) -> Result<(), WorktreeRemoveFailureReason> {
let fullpath = base_dir.join(worktree_dir);
if !fullpath.exists() {
if !worktree_dir.exists() {
return Err(WorktreeRemoveFailureReason::Error(format!(
"{} does not exist",
name
)));
}
let worktree_repo = RepoHandle::open(&fullpath, false).map_err(|error| {
let worktree_repo = RepoHandle::open(worktree_dir, false).map_err(|error| {
WorktreeRemoveFailureReason::Error(format!("Error opening repo: {}", error))
})?;
@@ -1179,11 +1180,12 @@ impl RepoHandle {
WorktreeRemoveFailureReason::Error(format!("Failed getting name of branch: {}", error))
})?;
if branch_name != name {
if branch_name != name
&& !branch_name.ends_with(&format!("{}{}", super::BRANCH_NAMESPACE_SEPARATOR, name))
{
return Err(WorktreeRemoveFailureReason::Error(format!(
"Branch \"{}\" is checked out in worktree \"{}\", this does not look correct",
&branch_name,
&worktree_dir.display(),
"Branch \"{}\" is checked out in worktree, this does not look correct",
&branch_name
)));
}
@@ -1253,47 +1255,13 @@ impl RepoHandle {
}
}
// worktree_dir is a relative path, starting from base_dir. We walk it
// upwards (from subdirectory to parent directories) and remove each
// component, in case it is empty. Only the leaf directory can be
// removed unconditionally (as it contains the worktree itself).
if let Err(e) = std::fs::remove_dir_all(&fullpath) {
if let Err(e) = std::fs::remove_dir_all(&worktree_dir) {
return Err(WorktreeRemoveFailureReason::Error(format!(
"Error deleting {}: {}",
&worktree_dir.display(),
e
)));
}
if let Some(current_dir) = worktree_dir.parent() {
for current_dir in current_dir.ancestors() {
let current_dir = base_dir.join(current_dir);
println!("deleting {}", current_dir.display());
if current_dir
.read_dir()
.map_err(|error| {
WorktreeRemoveFailureReason::Error(format!(
"Error reading {}: {}",
&current_dir.display(),
error
))
})?
.next()
.is_none()
{
if let Err(e) = std::fs::remove_dir_all(&current_dir) {
return Err(WorktreeRemoveFailureReason::Error(format!(
"Error deleting {}: {}",
&worktree_dir.display(),
e
)));
}
} else {
break;
}
}
}
self.prune_worktree(name)
.map_err(WorktreeRemoveFailureReason::Error)?;
branch
@@ -1346,13 +1314,7 @@ impl RepoHandle {
{
let repo_dir = &directory.join(&worktree.name());
if repo_dir.exists() {
match self.remove_worktree(
directory,
worktree.name(),
Path::new(worktree.name()),
false,
&config,
) {
match self.remove_worktree(worktree.name(), repo_dir, false, &config) {
Ok(_) => print_success(&format!("Worktree {} deleted", &worktree.name())),
Err(error) => match error {
WorktreeRemoveFailureReason::Changes(changes) => {
@@ -1477,7 +1439,7 @@ impl<'a> Branch<'a> {
}
}
impl<'a> Branch<'a> {
impl Branch<'_> {
pub fn commit(&self) -> Result<Commit, String> {
Ok(Commit(
self.0
@@ -1487,15 +1449,6 @@ impl<'a> Branch<'a> {
))
}
pub fn commit_owned(self) -> Result<Commit<'a>, String> {
Ok(Commit(
self.0
.into_reference()
.peel_to_commit()
.map_err(convert_libgit2_error)?,
))
}
pub fn set_upstream(&mut self, remote_name: &str, branch_name: &str) -> Result<(), String> {
self.0
.set_upstream(Some(&format!("{}/{}", remote_name, branch_name)))

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,

File diff suppressed because it is too large Load Diff