Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0746be904a | |||
| 53c2ee404c | |||
| bd694c3f7d | |||
| 95e9fcbffe | |||
| 98665a3231 | |||
| 7a51ad135f | |||
| e386935bc7 | |||
| c62562e6f0 | |||
| 00e37996b7 | |||
| a7e2c61984 | |||
| 58919b2d58 | |||
| dd36eb886f | |||
|
|
d2e01db0ae | ||
| bfd7b01ea4 | |||
| da7a499da0 | |||
| 64965c32dd | |||
| 3207bdfdfb | |||
| d8dd604174 | |||
| 7ca9459675 | |||
| 989b0cdcce | |||
| 64d8397092 | |||
| a1b054a672 | |||
| 193c96c5aa | |||
| ee973432be | |||
| 38d0252101 | |||
| 280048264e | |||
| 129111273d | |||
| d62a19d741 | |||
| e34a6243c0 | |||
| 4464bb607b | |||
| 48fa888f9b |
@@ -1,53 +1,3 @@
|
|||||||
# Contributing
|
Check out [the developer
|
||||||
|
documentation](https://hakoerber.github.io/git-repo-manager/developing.html) it
|
||||||
GRM is still in very early development. I started GRM mainly to scratch my own
|
you want to contribute!
|
||||||
itches (and am heavily dogfooding it). If you have a new use case for GRM, go
|
|
||||||
for it!
|
|
||||||
|
|
||||||
The branching strategy is a simplified
|
|
||||||
[git-flow](https://nvie.com/posts/a-successful-git-branching-model/).
|
|
||||||
|
|
||||||
* `master` is the "production" branch. Each commit is a new release.
|
|
||||||
* `develop` is the branch where new stuff is coming in.
|
|
||||||
* feature branches branch off of `develop` and merge back into it.
|
|
||||||
|
|
||||||
So to contribute, just fork the repo and create a pull request against
|
|
||||||
`develop`. If you plan bigger changes, please consider opening an issue first,
|
|
||||||
so we can discuss it.
|
|
||||||
|
|
||||||
If you want, add yourself to the `CONTRIBUTORS` file in your pull request.
|
|
||||||
|
|
||||||
## Code formatting
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
## Tooling
|
|
||||||
|
|
||||||
GRM uses [`just`](https://github.com/casey/just) as a command runner. See
|
|
||||||
[here](https://github.com/casey/just#installation) for installation
|
|
||||||
instructions (it's most likely just a simple `cargo install just`).
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
There are two distinct test suites: One for unit test (`just test-unit`) and
|
|
||||||
integration tests (`just test-integration`) that is part of the rust crate, and
|
|
||||||
a separate e2e test suite in python (`just test-e2e`).
|
|
||||||
|
|
||||||
To run all tests, run `just test`.
|
|
||||||
|
|
||||||
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
|
|
||||||
[mdBook](https://github.com/rust-lang/mdBook). Please document new user-facing
|
|
||||||
features here!
|
|
||||||
|
|
||||||
|
|||||||
85
Cargo.lock
generated
85
Cargo.lock
generated
@@ -47,9 +47,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cache-padded"
|
name = "cache-padded"
|
||||||
@@ -80,9 +80,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.7"
|
version = "3.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b7b16274bb247b45177db843202209b12191b631a14a9d06e41b3777d6ecf14"
|
checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
@@ -332,7 +332,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git-repo-manager"
|
name = "git-repo-manager"
|
||||||
version = "0.7.4"
|
version = "0.7.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
@@ -367,9 +367,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.1"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -590,9 +590,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.12.0"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
@@ -602,18 +602,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-src"
|
name = "openssl-src"
|
||||||
version = "111.21.0+1.1.1p"
|
version = "111.22.0+1.1.1q"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d0a8313729211913936f1b95ca47a5fc7f2e04cd658c115388287f8a8361008"
|
checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.74"
|
version = "0.9.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1"
|
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -625,9 +625,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.1.0"
|
version = "6.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
|
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
@@ -677,18 +677,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.10"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
|
checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.0.10"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
|
checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -821,9 +821,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.6"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -832,9 +832,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.26"
|
version = "0.6.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
@@ -847,9 +847,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
|
checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
@@ -875,18 +875,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.137"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.137"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -906,9 +906,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.8.24"
|
version = "0.8.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
|
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -957,9 +957,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.6"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sluice"
|
name = "sluice"
|
||||||
@@ -974,9 +977,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.8.1"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
|
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -1118,9 +1121,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.21"
|
version = "0.1.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
|
checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1154,15 +1157,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd"
|
checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|||||||
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "git-repo-manager"
|
name = "git-repo-manager"
|
||||||
version = "0.7.4"
|
version = "0.7.6"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
authors = [
|
authors = [
|
||||||
@@ -44,7 +44,7 @@ path = "src/grm/main.rs"
|
|||||||
version = "=0.5.9"
|
version = "=0.5.9"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "=1.0.137"
|
version = "=1.0.140"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
[dependencies.git2]
|
[dependencies.git2]
|
||||||
@@ -54,20 +54,20 @@ version = "=0.14.4"
|
|||||||
version = "=2.1.0"
|
version = "=2.1.0"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "=3.2.7"
|
version = "=3.2.14"
|
||||||
features = ["derive", "cargo"]
|
features = ["derive", "cargo"]
|
||||||
|
|
||||||
[dependencies.console]
|
[dependencies.console]
|
||||||
version = "=0.15.0"
|
version = "=0.15.0"
|
||||||
|
|
||||||
[dependencies.regex]
|
[dependencies.regex]
|
||||||
version = "=1.5.6"
|
version = "=1.6.0"
|
||||||
|
|
||||||
[dependencies.comfy-table]
|
[dependencies.comfy-table]
|
||||||
version = "=6.0.0"
|
version = "=6.0.0"
|
||||||
|
|
||||||
[dependencies.serde_yaml]
|
[dependencies.serde_yaml]
|
||||||
version = "=0.8.24"
|
version = "=0.8.26"
|
||||||
|
|
||||||
[dependencies.serde_json]
|
[dependencies.serde_json]
|
||||||
version = "=1.0.82"
|
version = "=1.0.82"
|
||||||
|
|||||||
17
Justfile
17
Justfile
@@ -26,12 +26,22 @@ lint:
|
|||||||
lint-fix:
|
lint-fix:
|
||||||
cargo clippy --no-deps --fix
|
cargo clippy --no-deps --fix
|
||||||
|
|
||||||
release:
|
build-release:
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
|
||||||
release-static:
|
build-release-static:
|
||||||
cargo build --release --target {{static_target}} --features=static-build
|
cargo build --release --target {{static_target}} --features=static-build
|
||||||
|
|
||||||
|
pushall:
|
||||||
|
for r in $(git remote) ; do \
|
||||||
|
for branch in develop master ; do \
|
||||||
|
git push $r $branch ; \
|
||||||
|
done ; \
|
||||||
|
done
|
||||||
|
|
||||||
|
release-patch:
|
||||||
|
./release.sh patch
|
||||||
|
|
||||||
test-binary:
|
test-binary:
|
||||||
env \
|
env \
|
||||||
GITHUB_API_BASEURL=http://rest:5000/github \
|
GITHUB_API_BASEURL=http://rest:5000/github \
|
||||||
@@ -77,3 +87,6 @@ update-cargo-dependencies:
|
|||||||
&& . ./venv/bin/activate \
|
&& . ./venv/bin/activate \
|
||||||
&& pip --disable-pip-version-check install -r ./requirements.txt > /dev/null \
|
&& pip --disable-pip-version-check install -r ./requirements.txt > /dev/null \
|
||||||
&& ./update-cargo-dependencies.py
|
&& ./update-cargo-dependencies.py
|
||||||
|
|
||||||
|
wait:
|
||||||
|
read -p "[ENTER] to continue "
|
||||||
|
|||||||
@@ -7,3 +7,8 @@ title = "Git Repo Manager"
|
|||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
mathjax-support = true
|
mathjax-support = true
|
||||||
|
|
||||||
|
# [output.linkcheck]
|
||||||
|
# follow-web-links = true
|
||||||
|
# traverse-parent-directories = false
|
||||||
|
# warning-policy = "error"
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
# Summary
|
# Summary
|
||||||
|
|
||||||
- [Overview](./overview.md)
|
[Overview](./overview.md)
|
||||||
|
|
||||||
- [Installation](./installation.md)
|
- [Installation](./installation.md)
|
||||||
- [Repository trees](./repos.md)
|
- [Tutorial](./tutorial.md)
|
||||||
|
- [Managing Repositories](./repos.md)
|
||||||
|
- [Local Configuration](./local_configuration.md)
|
||||||
|
- [Forge Integrations](./forge_integration.md)
|
||||||
- [Git Worktrees](./worktrees.md)
|
- [Git Worktrees](./worktrees.md)
|
||||||
- [Forge Integrations](./forge_integration.md)
|
- [Working with Worktrees](./worktree_working.md)
|
||||||
|
- [Worktrees and Remotes](./worktree_remotes.md)
|
||||||
|
- [Behavior Details](./worktree_behavior.md)
|
||||||
- [FAQ](./faq.md)
|
- [FAQ](./faq.md)
|
||||||
- [Contributing](./contributing.md)
|
- [Developer Documentation](./developing.md)
|
||||||
|
- [Testing](./testing.md)
|
||||||
|
- [Dependency updates](./dependency_updates.md)
|
||||||
|
- [Releases](./releases.md)
|
||||||
|
- [Formatting & Style](./formatting_and_style.md)
|
||||||
|
- [The Docs Themselves](./documentation.md)
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../CONTRIBUTING.md
|
|
||||||
10
docs/src/dependency_updates.md
Normal file
10
docs/src/dependency_updates.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Dependency updates
|
||||||
|
|
||||||
|
Rust has the same problem as the node ecosystem, just a few magnitudes smaller:
|
||||||
|
Dependency sprawl. GRM has a dozen direct dependencies, but over 150 transitive
|
||||||
|
ones.
|
||||||
|
|
||||||
|
To keep them up to date, there is a script:
|
||||||
|
`depcheck/update-cargo-dependencies.py`. It updates direct dependencies to the
|
||||||
|
latest stable version and updates transitive dependencies where possible. To run
|
||||||
|
it, use `just update-dependencies`, which will create commits for each update.
|
||||||
69
docs/src/developing.md
Normal file
69
docs/src/developing.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
GRM is still in very early development. I started GRM mainly to scratch my own
|
||||||
|
itches (and am heavily dogfooding it). If you have a new use case for GRM, go
|
||||||
|
for it!
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
To contribute, just fork the repo and create a pull request against `develop`.
|
||||||
|
If you plan bigger changes, please consider opening an issue first, so we can
|
||||||
|
discuss it.
|
||||||
|
|
||||||
|
If you want, add yourself to the `CONTRIBUTORS` file in your pull request.
|
||||||
|
|
||||||
|
## Branching strategy
|
||||||
|
|
||||||
|
The branching strategy is a simplified
|
||||||
|
[git-flow](https://nvie.com/posts/a-successful-git-branching-model/).
|
||||||
|
|
||||||
|
* `master` is the "production" branch. Each commit is a new release.
|
||||||
|
* `develop` is the branch where new stuff is coming in.
|
||||||
|
* feature branches branch off of `develop` and merge back into it.
|
||||||
|
|
||||||
|
Feature branches are not required, there are also changes happening directly on
|
||||||
|
`develop`.
|
||||||
|
|
||||||
|
## Required tooling
|
||||||
|
|
||||||
|
You will need the following tools:
|
||||||
|
|
||||||
|
* Rust (obviously) (easiest via `rustup`), with the nightly toolchain
|
||||||
|
* Python3
|
||||||
|
* [`just`](https://github.com/casey/just), a command runner like `make`. See
|
||||||
|
[here](https://github.com/casey/just#installation) for installation
|
||||||
|
instructions (it's most likely just a simple `cargo install just`).
|
||||||
|
* Docker & docker-compose for the e2e tests
|
||||||
|
* `black` and `shfmt` for formatting.
|
||||||
|
* `shellcheck` for shell script linting
|
||||||
|
* `mdbook` for the documentation
|
||||||
|
|
||||||
|
Here are the tools:
|
||||||
|
|
||||||
|
| Distribution | Command |
|
||||||
|
| ------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
|
| Arch Linux | `pacman -S --needed python3 rustup just docker docker-compose python-black shfmt shellcheck mdbook` |
|
||||||
|
| Ubuntu/Debian | `apt-get install --no-install-recommends python3 docker.io docker-compose black shellcheck` |
|
||||||
|
|
||||||
|
Note that you will have to install `just` and `mdbook` manually on Ubuntu (e.g.
|
||||||
|
via `cargo install just mdbook` if your rust build environment is set up
|
||||||
|
correctly). Same for `shfmt`, which may just be a `go install
|
||||||
|
mvdan.cc/sh/v3/cmd/shfmt@latest`, depending on your go build environment.
|
||||||
|
|
||||||
|
For details about rustup and the toolchains, see [the installation
|
||||||
|
section](./installation.md).
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Why nightly?
|
||||||
|
|
||||||
|
For now, GRM requires the nightly toolchain for two reasons:
|
||||||
|
|
||||||
|
* [`io_error_more`](https://github.com/rust-lang/rust/issues/86442) to get
|
||||||
|
better error messages on IO errors
|
||||||
|
* [`const_option_ext`](https://github.com/rust-lang/rust/issues/91930) to have
|
||||||
|
static variables read from the environment that fall back to hard coded
|
||||||
|
defaults
|
||||||
|
|
||||||
|
Honestly, both of those are not really necessary or can be handled without
|
||||||
|
nightly. It's just that I'm using nightly anyway.
|
||||||
11
docs/src/documentation.md
Normal file
11
docs/src/documentation.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Documentation
|
||||||
|
|
||||||
|
The documentation lives in the `docs` folder and uses
|
||||||
|
[mdBook](https://github.com/rust-lang/mdBook). Please document new user-facing
|
||||||
|
features here!
|
||||||
|
|
||||||
|
Using [GitHub actions](https://github.com/features/actions), the documentation
|
||||||
|
on `master` is automatically published to [the project
|
||||||
|
homepage](https://hakoerber.github.io/git-repo-manager/) via GitHub pages. See
|
||||||
|
`.github/workflows/gh-pages.yml` for the configuration of GitHub Actions.
|
||||||
|
|
||||||
@@ -1,10 +1,3 @@
|
|||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
## Why is the nightly toolchain required?
|
Currently empty, as there are no questions that are asked frequently :D
|
||||||
|
|
||||||
Building GRM currently requires nightly features due to the usage of
|
|
||||||
[`std::path::Path::is_symlink()`](https://doc.rust-lang.org/std/fs/struct.FileType.html#method.is_symlink).
|
|
||||||
See the [tracking issue](https://github.com/rust-lang/rust/issues/85748).
|
|
||||||
|
|
||||||
`is_symlink()` is actually available in rustc 1.57, so it will be on stable in
|
|
||||||
the near future. This would mean that GRM can be built using the stable toolchain!
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
# Forge Integrations
|
# Forge Integrations
|
||||||
|
|
||||||
In addition to manging repositories locally, `grm` also integrates with source
|
In addition to managing repositories locally, `grm` also integrates with source
|
||||||
code hosting platforms. Right now, the following platforms are supported:
|
code hosting platforms. Right now, the following platforms are supported:
|
||||||
|
|
||||||
* [GitHub](https://github.com/)
|
* [GitHub](https://github.com/)
|
||||||
* [GitLab](https://gitlab.com/)
|
* [GitLab](https://gitlab.com/)
|
||||||
|
|
||||||
Imagine you are just starting out with `grm` and want to clone all your repositories
|
Imagine you are just starting out with `grm` and want to clone all your
|
||||||
from GitHub. This is as simple as:
|
repositories from GitHub. This is as simple as:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ grm repos sync remote --provider github --owner --token-command "pass show github_grm_access_token" --path ~/projects
|
$ grm repos sync remote --provider github --owner --token-command "pass show github_grm_access_token" --path ~/projects
|
||||||
```
|
```
|
||||||
|
|
||||||
You will end up with your projects cloned into `~/projects/{your_github_username}/`
|
You will end up with your projects cloned into
|
||||||
|
`~/projects/{your_github_username}/`
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
@@ -34,8 +35,8 @@ See the GitLab documentation for personal access tokens:
|
|||||||
|
|
||||||
The required scopes are a bit weird. Actually, the following should suffice:
|
The required scopes are a bit weird. Actually, the following should suffice:
|
||||||
|
|
||||||
* * `read_user` to get user information (required to get the current authenticated
|
* `read_user` to get user information (required to get the current
|
||||||
user name for the `--owner` filter.
|
authenticated user name for the `--owner` filter.
|
||||||
* A scope that allows reading private repositories. (`read_repository` is just
|
* A scope that allows reading private repositories. (`read_repository` is just
|
||||||
for *cloning* private repos). This unfortunately does not exist.
|
for *cloning* private repos). This unfortunately does not exist.
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ The options in the file map to the command line options of the `grm repos sync
|
|||||||
remote` command.
|
remote` command.
|
||||||
|
|
||||||
You'd then run the `grm repos sync` command the same way as with a list of
|
You'd then run the `grm repos sync` command the same way as with a list of
|
||||||
repositories in a config:
|
repositories in a configuration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ grm repos sync --config example.config.toml
|
$ grm repos sync --config example.config.toml
|
||||||
@@ -120,11 +121,11 @@ $ grm repos find config --config example.config.toml > repos.toml
|
|||||||
$ grm repos sync config --config repos.toml
|
$ grm repos sync config --config repos.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using with selfhosted GitLab
|
## Using with self-hosted GitLab
|
||||||
|
|
||||||
By default, `grm` uses the default GitLab API endpoint
|
By default, `grm` uses the default GitLab API endpoint
|
||||||
([https://gitlab.com](https://gitlab.com)). You can override the
|
([https://gitlab.com](https://gitlab.com)). You can override the endpoint by
|
||||||
endpoint by specifying the `--api-url` parameter. Like this:
|
specifying the `--api-url` parameter. Like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ grm repos sync remote --provider gitlab --api-url https://gitlab.example.com [...]
|
$ grm repos sync remote --provider gitlab --api-url https://gitlab.example.com [...]
|
||||||
@@ -138,26 +139,28 @@ can be overridden with the `--force-ssh` switch.
|
|||||||
## About the token command
|
## About the token command
|
||||||
|
|
||||||
To ensure maximum flexibility, `grm` has a single way to get the token it uses
|
To ensure maximum flexibility, `grm` has a single way to get the token it uses
|
||||||
to authenticate: Specify a command that returns the token via stdout. This easily
|
to authenticate: Specify a command that returns the token via stdout. This
|
||||||
integrates with password managers like [`pass`](https://www.passwordstore.org/).
|
easily integrates with password managers like
|
||||||
|
[`pass`](https://www.passwordstore.org/).
|
||||||
|
|
||||||
Of course, you are also free to specify something like `echo mytoken` as the
|
Of course, you are also free to specify something like `echo mytoken` as the
|
||||||
command, as long as you are ok with the security implications (like having the
|
command, as long as you are OK with the security implications (like having the
|
||||||
token in cleartext in your shell history). It may be better to have the token
|
token in clear text in your shell history). It may be better to have the token
|
||||||
in a file instead and read it: `cat ~/.gitlab_token`.
|
in a file instead and read it: `cat ~/.gitlab_token`.
|
||||||
|
|
||||||
Generally, use whatever you want. The command just has to return sucessfully and
|
Generally, use whatever you want. The command just has to return successfully
|
||||||
return the token as the first line of stdout.
|
and return the token as the first line of stdout.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Maybe you just want to locally clone all repos from your github user?
|
Maybe you just want to locally clone all repos from your GitHub user?
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ grm repos sync remote --provider github --owner --root ~/github_projects --token-command "pass show github_grm_access_token"
|
$ grm repos sync remote --provider github --owner --root ~/github_projects --token-command "pass show github_grm_access_token"
|
||||||
```
|
```
|
||||||
|
|
||||||
This will clone all repositories into `~/github_projects/{your_github_username}`.
|
This will clone all repositories into
|
||||||
|
`~/github_projects/{your_github_username}`.
|
||||||
|
|
||||||
If instead you want to clone **all** repositories you have access to (e.g. via
|
If instead you want to clone **all** repositories you have access to (e.g. via
|
||||||
organizations or other users' private repos you have access to), just change the
|
organizations or other users' private repos you have access to), just change the
|
||||||
@@ -172,12 +175,13 @@ $ grm repos sync remote --provider github --access --root ~/github_projects --to
|
|||||||
### GitHub
|
### GitHub
|
||||||
|
|
||||||
Unfortunately, GitHub does not have a nice API endpoint to get **private**
|
Unfortunately, GitHub does not have a nice API endpoint to get **private**
|
||||||
repositories for a certain user ([`/users/{user}/repos/`](https://docs.github.com/en/rest/repos/repos#list-repositories-for-a-user) only returns public
|
repositories for a certain user
|
||||||
repositories).
|
([`/users/{user}/repos/`](https://docs.github.com/en/rest/repos/repos#list-repositories-for-a-user)
|
||||||
|
only returns public repositories).
|
||||||
|
|
||||||
Therefore, using `--user {user}` will only show public repositories for GitHub.
|
Therefore, using `--user {user}` will only show public repositories for GitHub.
|
||||||
Note that this does not apply to `--access`: If you have access to another user's
|
Note that this does not apply to `--access`: If you have access to another
|
||||||
private repository, it will be listed.
|
user's private repository, it will be listed.
|
||||||
|
|
||||||
## Adding integrations
|
## Adding integrations
|
||||||
|
|
||||||
@@ -197,9 +201,9 @@ Each repo has to have the following properties:
|
|||||||
|
|
||||||
* A name (which also acts as the identifier for diff between local and remote
|
* A name (which also acts as the identifier for diff between local and remote
|
||||||
repositories)
|
repositories)
|
||||||
* An SSH url to push to
|
* An SSH URL to push to
|
||||||
* An HTTPS url to clone and fetch from
|
* An HTTPS URL to clone and fetch from
|
||||||
* A flag that marks the repository as private
|
* A flag that marks the repository as private
|
||||||
|
|
||||||
If you plan to implement another forge, please first open an issue so we can
|
If you plan to implement another forge, please first open an issue so we can go
|
||||||
go through the required setup. I'm happy to help!
|
through the required setup. I'm happy to help!
|
||||||
|
|||||||
45
docs/src/formatting_and_style.md
Normal file
45
docs/src/formatting_and_style.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Formatting & Style
|
||||||
|
|
||||||
|
## Code formatting
|
||||||
|
|
||||||
|
I'm allergic to discussions about formatting. I'd rather make the computer do it
|
||||||
|
for me.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
To autoformat all code, use `just fmt`
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
Honestly, no idea about style. I'm still learning Rust, so I'm trying to find a
|
||||||
|
good style. Just try to keep it consistent when you add code.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
You can use `just lint` to run all lints.
|
||||||
|
|
||||||
|
### Rust
|
||||||
|
|
||||||
|
Clippy is the guard that prevents shitty code from getting into the code base.
|
||||||
|
When running `just check`, any clippy suggestions will make the command fail.
|
||||||
|
So make clippy happy! The easiest way:
|
||||||
|
|
||||||
|
* Commit your changes (so clippy can change safely).
|
||||||
|
* Run `cargo clippy --fix` to do the easy changes automatically.
|
||||||
|
* Run `cargo clippy` and take a look at the messages.
|
||||||
|
|
||||||
|
Until now, I had no need to override or silence any clippy suggestions.
|
||||||
|
|
||||||
|
### Shell
|
||||||
|
|
||||||
|
`shellcheck` lints all shell scripts. As they change very rarely, this is not
|
||||||
|
too important.
|
||||||
|
|
||||||
|
## Unsafe code
|
||||||
|
|
||||||
|
Any `unsafe` code is forbidden for now globally via `#![forbid(unsafe_code)]`.
|
||||||
|
I cannot think of any reason GRM may need `unsafe`. If it comes up, it needs to
|
||||||
|
be discussed.
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Building GRM currently requires the nightly Rust toolchain. The easiest way
|
Building GRM currently requires the nightly Rust toolchain. The easiest way is
|
||||||
is using [`rustup`](https://rustup.rs/). Make sure that rustup is properly installed.
|
using [`rustup`](https://rustup.rs/). Make sure that rustup is properly
|
||||||
|
installed.
|
||||||
|
|
||||||
Make sure that the nightly toolchain is installed:
|
Make sure that the nightly toolchain is installed:
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ Then, install the build dependencies:
|
|||||||
|
|
||||||
| Distribution | Command |
|
| Distribution | Command |
|
||||||
| ------------- | ------------------------------------------------------------------------------ |
|
| ------------- | ------------------------------------------------------------------------------ |
|
||||||
| Archlinux | `pacman -S --needed gcc openssl pkg-config` |
|
| Arch Linux | `pacman -S --needed gcc openssl pkg-config` |
|
||||||
| Ubuntu/Debian | `apt-get install --no-install-recommends pkg-config gcc libssl-dev zlib1g-dev` |
|
| Ubuntu/Debian | `apt-get install --no-install-recommends pkg-config gcc libssl-dev zlib1g-dev` |
|
||||||
|
|
||||||
Then, it's a simple command to install the latest stable version:
|
Then, it's a simple command to install the latest stable version:
|
||||||
@@ -33,12 +34,12 @@ $ cargo +nightly install --git https://github.com/hakoerber/git-repo-manager.git
|
|||||||
## Static build
|
## Static build
|
||||||
|
|
||||||
Note that by default, you will get a dynamically linked executable.
|
Note that by default, you will get a dynamically linked executable.
|
||||||
Alternatively, you can also build a statically linked binary. For this, you
|
Alternatively, you can also build a statically linked binary. For this, you will
|
||||||
will need `musl` and a few other build dependencies installed installed:
|
need `musl` and a few other build dependencies installed installed:
|
||||||
|
|
||||||
| Distribution | Command |
|
| Distribution | Command |
|
||||||
| ------------- | --------------------------------------------------------------------------- |
|
| ------------- | --------------------------------------------------------------------------- |
|
||||||
| Archlinux | `pacman -S --needed gcc musl perl make` |
|
| Arch Linux | `pacman -S --needed gcc musl perl make` |
|
||||||
| Ubuntu/Debian | `apt-get install --no-install-recommends gcc musl-tools libc-dev perl make` |
|
| Ubuntu/Debian | `apt-get install --no-install-recommends gcc musl-tools libc-dev perl make` |
|
||||||
|
|
||||||
(`perl` and `make` are required for the OpenSSL build script)
|
(`perl` and `make` are required for the OpenSSL build script)
|
||||||
|
|||||||
83
docs/src/local_configuration.md
Normal file
83
docs/src/local_configuration.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Local Configuration
|
||||||
|
|
||||||
|
When managing multiple git repositories with GRM, you'll generally have a
|
||||||
|
configuration file containing information about all the repos you have. GRM then
|
||||||
|
makes sure that you repositories match that configuration. If they don't exist
|
||||||
|
yet, it will clone them. It will also make sure that all remotes are configured
|
||||||
|
properly.
|
||||||
|
|
||||||
|
Let's try it out:
|
||||||
|
|
||||||
|
## Get the example configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl --proto '=https' --tlsv1.2 -sSfO https://raw.githubusercontent.com/hakoerber/git-repo-manager/master/example.config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you're ready to run the first sync. This will clone all configured
|
||||||
|
repositories and set up the remotes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos sync config --config example.config.toml
|
||||||
|
[⚙] Cloning into "/home/me/projects/git-repo-manager" from "https://code.hkoerber.de/hannes/git-repo-manager.git"
|
||||||
|
[✔] git-repo-manager: Repository successfully cloned
|
||||||
|
[⚙] git-repo-manager: Setting up new remote "github" to "https://github.com/hakoerber/git-repo-manager.git"
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
[⚙] Cloning into "/home/me/projects/dotfiles" from "https://github.com/hakoerber/dotfiles.git"
|
||||||
|
[✔] dotfiles: Repository successfully cloned
|
||||||
|
[✔] dotfiles: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run it again, it will report no changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm repos sync config -c example.config.toml
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
[✔] dotfiles: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate your own configuration
|
||||||
|
|
||||||
|
Now, if you already have a few repositories, it would be quite laborious to
|
||||||
|
write a configuration from scratch. Luckily, GRM has a way to generate a
|
||||||
|
configuration from an existing file tree:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos find local ~/your/project/root > config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
This will detect all repositories and remotes and write them to `config.toml`.
|
||||||
|
|
||||||
|
### Show the state of your projects
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos status --config example.config.toml
|
||||||
|
╭──────────────────┬──────────┬────────┬───────────────────┬────────┬─────────╮
|
||||||
|
│ Repo ┆ Worktree ┆ Status ┆ Branches ┆ HEAD ┆ Remotes │
|
||||||
|
╞══════════════════╪══════════╪════════╪═══════════════════╪════════╪═════════╡
|
||||||
|
│ git-repo-manager ┆ ┆ ✔ ┆ branch: master ┆ master ┆ github │
|
||||||
|
│ ┆ ┆ ┆ <origin/master> ✔ ┆ ┆ origin │
|
||||||
|
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
|
||||||
|
│ dotfiles ┆ ┆ ✔ ┆ ┆ Empty ┆ origin │
|
||||||
|
╰──────────────────┴──────────┴────────┴───────────────────┴────────┴─────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `status` without `--config` to check the repository you're
|
||||||
|
currently in:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd ~/example-projects/dotfiles
|
||||||
|
$ grm repos status
|
||||||
|
╭──────────┬──────────┬────────┬──────────┬───────┬─────────╮
|
||||||
|
│ Repo ┆ Worktree ┆ Status ┆ Branches ┆ HEAD ┆ Remotes │
|
||||||
|
╞══════════╪══════════╪════════╪══════════╪═══════╪═════════╡
|
||||||
|
│ dotfiles ┆ ┆ ✔ ┆ ┆ Empty ┆ origin │
|
||||||
|
╰──────────┴──────────┴────────┴──────────┴───────┴─────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
## YAML
|
||||||
|
|
||||||
|
By default, the repo configuration uses TOML. If you prefer YAML, just give it a
|
||||||
|
YAML file instead (file ending does not matter, `grm` will figure out the
|
||||||
|
format). For generating a configuration, pass `--format yaml` to `grm repo
|
||||||
|
find` which generates a YAML configuration instead of a TOML configuration.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Welcome! This is the documentation for [Git Repo
|
Welcome! This is the documentation for [Git Repo
|
||||||
Manager](https://github.com/hakoerber/git-repo-manager/) (GRM for short), a
|
Manager](https://github.com/hakoerber/git-repo-manager/) (GRM for short), a tool
|
||||||
tool that helps you manage git repositories.
|
that helps you manage git repositories in a declarative way.
|
||||||
|
|
||||||
GRM helps you manage git repositories in a declarative way. Configure your
|
GRM helps you manage git repositories in a declarative way. Configure your
|
||||||
repositories in a TOML or YAML file, GRM does the rest. Take a look at [the
|
repositories in a TOML or YAML file, GRM does the rest. Take a look at [the
|
||||||
@@ -12,12 +12,12 @@ to get a feel for the way you configure your repositories. See the [repository
|
|||||||
tree chapter](./repos.md) for details.
|
tree chapter](./repos.md) for details.
|
||||||
|
|
||||||
GRM also provides some tooling to work with single git repositories using
|
GRM also provides some tooling to work with single git repositories using
|
||||||
`git-worktree`. See [the worktree chapter](./worktree.md) for more details.
|
`git-worktree`. See [the worktree chapter](./worktrees.md) for more details.
|
||||||
|
|
||||||
## Why use GRM?
|
## Why use GRM?
|
||||||
|
|
||||||
If you're working with a lot of git repositories, GRM can help you to manage them
|
If you're working with a lot of git repositories, GRM can help you to manage
|
||||||
in an easy way:
|
them in an easy way:
|
||||||
|
|
||||||
* You want to easily clone many repositories to a new machine.
|
* You want to easily clone many repositories to a new machine.
|
||||||
* You want to change remotes for multiple repositories (e.g. because your GitLab
|
* You want to change remotes for multiple repositories (e.g. because your GitLab
|
||||||
|
|||||||
27
docs/src/releases.md
Normal file
27
docs/src/releases.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Releases
|
||||||
|
|
||||||
|
To make a release, make sure you are on a clean `develop` branch, sync your
|
||||||
|
remotes and then run `./release (major|minor|patch)`. It will handle a
|
||||||
|
git-flow-y release, meaning that it will perform a merge from `develop` to
|
||||||
|
`master`, create a git tag, sync all remotes and run `cargo publish`.
|
||||||
|
|
||||||
|
Make sure to run `just check` before releasing to make sure that nothing is
|
||||||
|
broken.
|
||||||
|
|
||||||
|
As GRM is still `v0.x`, there is not much consideration for backwards
|
||||||
|
compatibility. Generally, update the patch version for small stuff and the minor
|
||||||
|
version for bigger / backwards incompatible changes.
|
||||||
|
|
||||||
|
Generally, it's good to regularly release a new patch release with [updated
|
||||||
|
dependencies](./dependency_updates.md). As `./release.sh patch` is exposed as a
|
||||||
|
Justfile target (`release-patch`), it's possible to do both in one step:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ just update-dependencies check release-patch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release notes
|
||||||
|
|
||||||
|
There are currently no release notes. Things are changing quite quickly and
|
||||||
|
there is simply no need for a record of changes (except the git history of
|
||||||
|
course).
|
||||||
@@ -1,82 +1,13 @@
|
|||||||
# Managing tree of git repositories
|
# Managing Repositories
|
||||||
|
|
||||||
When managing multiple git repositories with GRM, you'll generally have a
|
GRM helps you manage a bunch of git repositories easily. There are generally two
|
||||||
configuration file containing information about all the repos you have. GRM then
|
ways to go about that:
|
||||||
makes sure that you repositories match that config. If they don't exist yet, it
|
|
||||||
will clone them. It will also make sure that all remotes are configured properly.
|
|
||||||
|
|
||||||
Let's try it out:
|
You can either manage a list of repositories in a TOML or YAML file, and use GRM
|
||||||
|
to sync the configuration with the state of the repository.
|
||||||
|
|
||||||
## Get the example configuration
|
Or, you can pull repository information from a forge (e.g. GitHub, GitLab) and
|
||||||
|
clone the repositories.
|
||||||
|
|
||||||
```bash
|
There are also hybrid modes where you pull information from a forge and create a
|
||||||
$ curl --proto '=https' --tlsv1.2 -sSfO https://raw.githubusercontent.com/hakoerber/git-repo-manager/master/example.config.toml
|
configuration file that you can use later.
|
||||||
```
|
|
||||||
|
|
||||||
Then, you're ready to run the first sync. This will clone all configured repositories
|
|
||||||
and set up the remotes.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ grm repos sync config --config example.config.toml
|
|
||||||
[⚙] Cloning into "/home/me/projects/git-repo-manager" from "https://code.hkoerber.de/hannes/git-repo-manager.git"
|
|
||||||
[✔] git-repo-manager: Repository successfully cloned
|
|
||||||
[⚙] git-repo-manager: Setting up new remote "github" to "https://github.com/hakoerber/git-repo-manager.git"
|
|
||||||
[✔] git-repo-manager: OK
|
|
||||||
[⚙] Cloning into "/home/me/projects/dotfiles" from "https://github.com/hakoerber/dotfiles.git"
|
|
||||||
[✔] dotfiles: Repository successfully cloned
|
|
||||||
[✔] dotfiles: OK
|
|
||||||
```
|
|
||||||
|
|
||||||
If you run it again, it will report no changes:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm repos sync config -c example.config.toml
|
|
||||||
[✔] git-repo-manager: OK
|
|
||||||
[✔] dotfiles: OK
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generate your own configuration
|
|
||||||
|
|
||||||
Now, if you already have a few repositories, it would be quite laborious to write
|
|
||||||
a configuration from scratch. Luckily, GRM has a way to generate a configuration
|
|
||||||
from an existing file tree:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ grm repos find local ~/your/project/root > config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
This will detect all repositories and remotes and write them to `config.toml`.
|
|
||||||
|
|
||||||
### Show the state of your projects
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ grm repos status --config example.config.toml
|
|
||||||
╭──────────────────┬──────────┬────────┬───────────────────┬────────┬─────────╮
|
|
||||||
│ Repo ┆ Worktree ┆ Status ┆ Branches ┆ HEAD ┆ Remotes │
|
|
||||||
╞══════════════════╪══════════╪════════╪═══════════════════╪════════╪═════════╡
|
|
||||||
│ git-repo-manager ┆ ┆ ✔ ┆ branch: master ┆ master ┆ github │
|
|
||||||
│ ┆ ┆ ┆ <origin/master> ✔ ┆ ┆ origin │
|
|
||||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
|
|
||||||
│ dotfiles ┆ ┆ ✔ ┆ ┆ Empty ┆ origin │
|
|
||||||
╰──────────────────┴──────────┴────────┴───────────────────┴────────┴─────────╯
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also use `status` without `--config` to check the repository you're currently
|
|
||||||
in:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cd ~/example-projects/dotfiles
|
|
||||||
$ grm repos status
|
|
||||||
╭──────────┬──────────┬────────┬──────────┬───────┬─────────╮
|
|
||||||
│ Repo ┆ Worktree ┆ Status ┆ Branches ┆ HEAD ┆ Remotes │
|
|
||||||
╞══════════╪══════════╪════════╪══════════╪═══════╪═════════╡
|
|
||||||
│ dotfiles ┆ ┆ ✔ ┆ ┆ Empty ┆ origin │
|
|
||||||
╰──────────┴──────────┴────────┴──────────┴───────┴─────────╯
|
|
||||||
```
|
|
||||||
|
|
||||||
## YAML
|
|
||||||
|
|
||||||
By default, the repo configuration uses TOML. If you prefer YAML, just give it
|
|
||||||
a YAML file instead (file ending does not matter, `grm` will figure out the format).
|
|
||||||
For generating a configuration, pass `--format yaml` to `grm repo find`
|
|
||||||
which generates a YAML config instead of a TOML configuration.
|
|
||||||
|
|||||||
124
docs/src/testing.md
Normal file
124
docs/src/testing.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
There are two distinct test suites: One for unit test (`just test-unit`) and
|
||||||
|
integration tests (`just test-integration`) that is part of the rust crate, and
|
||||||
|
a separate e2e test suite in python (`just test-e2e`).
|
||||||
|
|
||||||
|
To run all tests, run `just test`.
|
||||||
|
|
||||||
|
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 behavior beforehand.
|
||||||
|
|
||||||
|
The unit and integration tests are very small and only test a few self-contained
|
||||||
|
functions (like validation of certain input).
|
||||||
|
|
||||||
|
## E2E tests
|
||||||
|
|
||||||
|
The main focus of the testing setup lays on the e2e tests. Each user-facing
|
||||||
|
behavior *should* have a corresponding e2e test. These are the most important
|
||||||
|
tests, as they test functionality the user will use in the end.
|
||||||
|
|
||||||
|
The test suite is written in python and uses
|
||||||
|
[pytest](https://docs.pytest.org/en/stable/). There are helper functions that
|
||||||
|
set up temporary git repositories and remotes in a `tmpfs`.
|
||||||
|
|
||||||
|
Effectively, each tests works like this:
|
||||||
|
|
||||||
|
* Set up some prerequisites (e.g. different git repositories or configuration
|
||||||
|
files)
|
||||||
|
* Run `grm`
|
||||||
|
* Check that everything is according to expected behavior (e.g. that `grm` had
|
||||||
|
certain output and exit code, that the target repositories have certain
|
||||||
|
branches, heads and remotes, ...)
|
||||||
|
|
||||||
|
As there are many different scenarios, the tests make heavy use of the
|
||||||
|
[`@pytest.mark.parametrize`](https://docs.pytest.org/en/stable/how-to/parametrize.html#pytest-mark-parametrize)
|
||||||
|
decorator to get all permutations of input parameters (e.g. whether a
|
||||||
|
configuration exists, what a config value is set to, how the repository looks
|
||||||
|
like, ...)
|
||||||
|
|
||||||
|
Whenever you write a new test, think about the different circumstances that can
|
||||||
|
happen. What are the failure modes? What affects the behavior? Parametrize each
|
||||||
|
of these behaviors.
|
||||||
|
|
||||||
|
### Optimization
|
||||||
|
|
||||||
|
Note: You will most likely not need to read this.
|
||||||
|
|
||||||
|
Each test parameter will exponentially increase the number of tests that will be
|
||||||
|
run. As a general rule, comprehensiveness is more important than test suite
|
||||||
|
runtime (so if in doubt, better to add another parameter to catch every edge
|
||||||
|
case). But try to keep the total runtime sane. Currently, the whole `just e2e`
|
||||||
|
target runs ~8'000 tests and takes around 5 minutes on my machine, exlucding
|
||||||
|
binary and docker build time. I'd say that keeping it under 10 minutes is a good
|
||||||
|
idea.
|
||||||
|
|
||||||
|
To optimize tests, look out for two patterns: Dependency and Orthogonality
|
||||||
|
|
||||||
|
#### Dependency
|
||||||
|
|
||||||
|
If a parameter depends on another one, it makes little sense to handle them
|
||||||
|
independently. Example: You have a paramter that specifies whether a
|
||||||
|
configuration is used, and another parameter that sets a certain value in that
|
||||||
|
configuration file. It might look something like this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("use_config", [True, False])
|
||||||
|
@pytest.mark.parametrize("use_value", ["0", "1"])
|
||||||
|
def test(...):
|
||||||
|
```
|
||||||
|
|
||||||
|
This leads to 4 tests being instantiated. But there is little point in setting a
|
||||||
|
configuration value when no config is used, so the combinations `(False, "0")`
|
||||||
|
and `(False, "1")` are redundant. To remedy this, spell out the optimized
|
||||||
|
permutation manually:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("config", ((True, "0"), (True, "1"), (False, None)))
|
||||||
|
def test(...):
|
||||||
|
(use_config, use_value) = config
|
||||||
|
```
|
||||||
|
|
||||||
|
This cuts down the number of tests by 25%. If you have more dependent parameters
|
||||||
|
(e.g. additional configuration values), this gets even better. Generally, this
|
||||||
|
will cut down the number of tests to
|
||||||
|
|
||||||
|
\\[ \frac{1}{o \cdot c} + \frac{1}{(o \cdot c) ^ {(n + 1)}} \\]
|
||||||
|
|
||||||
|
with \\( o \\) being the number of values of a parent parameters a parameter is
|
||||||
|
dependent on, \\( c \\) being the cardinality of the test input (so you can
|
||||||
|
assume \\( o = 1 \\) and \\( c = 2 \\) for boolean parameters), and \\( n \\)
|
||||||
|
being the number of parameters that are optimized, i.e. folded into their
|
||||||
|
dependent parameter.
|
||||||
|
|
||||||
|
As an example: Folding down two boolean parameters into one dependent parent
|
||||||
|
boolean parameter will cut down the number of tests to 62.5%!
|
||||||
|
|
||||||
|
#### Orthogonality
|
||||||
|
|
||||||
|
If different test parameters are independent of each other, there is little
|
||||||
|
point in testing their combinations. Instead, split them up into different test
|
||||||
|
functions. For boolean parameters, this will cut the number of tests in half.
|
||||||
|
|
||||||
|
So instead of this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("param1", [True, False])
|
||||||
|
@pytest.mark.parametrize("param2", [True, False])
|
||||||
|
def test(...):
|
||||||
|
```
|
||||||
|
|
||||||
|
Rather do this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.parametrize("param1", [True, False])
|
||||||
|
def test_param1(...):
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("param2", [True, False])
|
||||||
|
def test_param2(...):
|
||||||
|
```
|
||||||
|
|
||||||
|
The tests are running in Docker via docker-compose. This is mainly needed to
|
||||||
|
test networking functionality like GitLab integration, with the GitLab API being
|
||||||
|
mocked by a simple flask container.
|
||||||
183
docs/src/tutorial.md
Normal file
183
docs/src/tutorial.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Tutorial
|
||||||
|
|
||||||
|
Here, you'll find a quick overview over the most common functionality of GRM.
|
||||||
|
|
||||||
|
## Managing existing repositories
|
||||||
|
|
||||||
|
Let's say you have your git repositories at `~/code`. To start managing them via
|
||||||
|
GRM, first create a configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grm repos find local ~/code --format yaml > ~/code/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
The result may look something like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
trees:
|
||||||
|
- root: ~/code
|
||||||
|
repos:
|
||||||
|
- name: git-repo-manager
|
||||||
|
worktree_setup: true
|
||||||
|
remotes:
|
||||||
|
- name: origin
|
||||||
|
url: "https://github.com/hakoerber/git-repo-manager.git"
|
||||||
|
type: https
|
||||||
|
```
|
||||||
|
|
||||||
|
To apply the configuration and check whether all repositories are in sync, run
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos sync config --config ~/code/config.yml
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Well, obiously there are no changes. To check how changes would be applied,
|
||||||
|
let's change the name of the remote (currently `origin`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sed -i 's/name: origin/name: github/' ~/code/config.yml
|
||||||
|
$ grm repos sync config --config ~/code/config.yml
|
||||||
|
[⚙] git-repo-manager: Setting up new remote "github" to "https://github.com/hakoerber/git-repo-manager.git"
|
||||||
|
[⚙] git-repo-manager: Deleting remote "origin"
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
GRM replaced the `origin` remote with `github`.
|
||||||
|
|
||||||
|
The configuration (`~/code/config.yml` in this example) would usually be
|
||||||
|
something you'd track in git or synchronize between machines via some other
|
||||||
|
means. Then, on every machine, all your repositories are a single `grm repos
|
||||||
|
sync` away!
|
||||||
|
|
||||||
|
## Getting repositories from a forge
|
||||||
|
|
||||||
|
Let's say you have a bunch of repositories on GitHub and you'd like to clone
|
||||||
|
them all to your local machine.
|
||||||
|
|
||||||
|
To authenticate, you'll need to get a personal access token, as described in
|
||||||
|
[the forge documentation](./forge_integration.md#github). Let's assume you put
|
||||||
|
your token into `~/.github_token` (please don't if you're doing this "for
|
||||||
|
real"!)
|
||||||
|
|
||||||
|
Let's first see what kind of repos we can find:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos sync remote --provider github --token-command "cat ~/.github_token" --root ~/code/github.com/ --format yaml
|
||||||
|
---
|
||||||
|
trees: []
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
Ummm, ok? No repos? This is because you have to *tell* GRM what to look for (if
|
||||||
|
you don't, GRM will just relax, as it's lazy).
|
||||||
|
|
||||||
|
There are different filters (see [the forge
|
||||||
|
documentation](./forge_integration.md#filters) for more info). In our case,
|
||||||
|
we'll just use the `--owner` filter to get all repos that belong to us:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos find remote --provider github --token-command "cat ~/.github_token" --root ~/code/github.com/ --format yaml
|
||||||
|
---
|
||||||
|
trees:
|
||||||
|
- root: ~/code/github.com
|
||||||
|
repos:
|
||||||
|
- name: git-repo-manager
|
||||||
|
worktree_setup: false
|
||||||
|
remotes:
|
||||||
|
- name: origin
|
||||||
|
url: "https://github.com/hakoerber/git-repo-manager.git"
|
||||||
|
type: https
|
||||||
|
```
|
||||||
|
|
||||||
|
Nice! The format is the same as we got from `grm repos find local` above. So if
|
||||||
|
we wanted, we could save this file and use it with `grm repos sync config` as
|
||||||
|
above. But there is an even easier way: We can directly clone the repositories!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos sync remote --provider github --token-command "cat ~/.github_token" --root ~/code/github.com/
|
||||||
|
[⚙] Cloning into "~/code/github.com/git-repo-manager" from "https://github.com/hakoerber/git-repo-manager.git"
|
||||||
|
[✔] git-repo-manager: Repository successfully cloned
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Nice! Just to make sure, let's run the same command again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm repos sync remote --provider github --token-command "cat ~/.github_token" --root ~/code/github.com/
|
||||||
|
[✔] git-repo-manager: OK
|
||||||
|
```
|
||||||
|
|
||||||
|
GRM saw that the repository is already there and did nothing (remember, it's
|
||||||
|
lazy).
|
||||||
|
|
||||||
|
## Using worktrees
|
||||||
|
|
||||||
|
Worktrees are something that make it easier to work with multiple branches at
|
||||||
|
the same time in a repository. Let's say we wanted to hack on the codebase of
|
||||||
|
GRM:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd ~/code/github.com/git-repo-manager
|
||||||
|
$ ls
|
||||||
|
.gitignore
|
||||||
|
Cargo.toml
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Well, this is just a normal git repository. But let's try worktrees! First, we
|
||||||
|
have to convert the existing repository to use the special worktree setup. For
|
||||||
|
all worktree operations, we will use `grm worktree` (or `grm wt` for short):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm wt convert
|
||||||
|
[✔] Conversion done
|
||||||
|
$ ls
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
So, the code is gone? Not really, there is just no active worktree right now. So
|
||||||
|
let's add one for `master`:
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm wt add master --track origin/master
|
||||||
|
[✔] Conversion done
|
||||||
|
$ ls
|
||||||
|
master
|
||||||
|
$ (cd ./master && git status)
|
||||||
|
On branch master
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, a single worktree is kind of pointless (if we only have one, we could also
|
||||||
|
just use the normal setup, without worktrees). So let's another one for
|
||||||
|
`develop`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm wt add develop --track origin/develop
|
||||||
|
[✔] Conversion done
|
||||||
|
$ ls
|
||||||
|
develop
|
||||||
|
master
|
||||||
|
$ (cd ./develop && git status)
|
||||||
|
On branch develop
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
```
|
||||||
|
|
||||||
|
What's the point? The cool thing is that we can now start working in the
|
||||||
|
`develop` worktree, without affecting the `master` worktree at all. If you're
|
||||||
|
working on `develop` and want to quickly see what a certain file looks like in
|
||||||
|
`master`, just look inside `./master`, it's all there!
|
||||||
|
|
||||||
|
This becomes especially interesting when you have many feature branches and are
|
||||||
|
working on multiple features at the same time.
|
||||||
|
|
||||||
|
There are a lot of options that influence how worktrees are handled. Maybe you
|
||||||
|
want to automatically track `origin/master` when you add a worktree called
|
||||||
|
`master`? Maybe you want your feature branches to have a prefix, so when you're
|
||||||
|
working on the `feature1` worktree, the remote branch will be
|
||||||
|
`origin/awesomefeatures/feature1`? Check out [the chapter on
|
||||||
|
worktrees](./worktrees.md) for all the things that are possible.
|
||||||
32
docs/src/worktree_behavior.md
Normal file
32
docs/src/worktree_behavior.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Behavior Details
|
||||||
|
|
||||||
|
When working with worktrees and GRM, there is a lot going on under the hood.
|
||||||
|
Each time you create a new worktree, GRM has to figure out what commit to set
|
||||||
|
your new branch to and how to configure any potential remote branches.
|
||||||
|
|
||||||
|
To state again, the most important guideline is the following:
|
||||||
|
|
||||||
|
**The branch inside the worktree is always the same as the directory name of the
|
||||||
|
worktree.**
|
||||||
|
|
||||||
|
The second set of guidelines relates to the commit to check out, and the remote
|
||||||
|
branches to use:
|
||||||
|
|
||||||
|
* When a branch already exists, you will get a worktree for that branch
|
||||||
|
* Existing local branches are never changed
|
||||||
|
* Only do remote operations if specifically requested (via configuration file or
|
||||||
|
command line parameters)
|
||||||
|
* When you specify `--track`, you will get that exact branch as the tracking
|
||||||
|
branch
|
||||||
|
* When you specify `--no-track`, you will get no tracking branch
|
||||||
|
|
||||||
|
Apart from that, GRM tries to do The Right Thing<sup>TM</sup>. It should be as
|
||||||
|
little surprising as possible.
|
||||||
|
|
||||||
|
In 99% of the cases, you will not have to care about the details, as the normal
|
||||||
|
workflows are covered by the rules above. In case you want to know the exact
|
||||||
|
behavior "specification", take a look at the [module documentation for
|
||||||
|
`grm::worktree`](https://docs.rs/git-repo-manager/latest/grm/worktree/index.html).
|
||||||
|
|
||||||
|
If you think existing behavior is super-duper confusing and you have a better
|
||||||
|
idea, do not hesitate to open a GitHub issue to discuss this!
|
||||||
75
docs/src/worktree_remotes.md
Normal file
75
docs/src/worktree_remotes.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Worktrees and Remotes
|
||||||
|
|
||||||
|
To fetch all remote references from all remotes in a worktree setup, you can use
|
||||||
|
the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt fetch
|
||||||
|
[✔] Fetched from all remotes
|
||||||
|
```
|
||||||
|
|
||||||
|
This is equivalent to running `git fetch --all` in any of the worktrees.
|
||||||
|
|
||||||
|
Often, you may want to pull all remote changes into your worktrees. For this,
|
||||||
|
use the `git pull` equivalent:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt pull
|
||||||
|
[✔] master: Done
|
||||||
|
[✔] my-cool-branch: Done
|
||||||
|
```
|
||||||
|
|
||||||
|
This will refuse when there are local changes, or if the branch cannot be fast
|
||||||
|
forwarded. If you want to rebase your local branches, use the `--rebase` switch:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt pull --rebase
|
||||||
|
[✔] master: Done
|
||||||
|
[✔] my-cool-branch: Done
|
||||||
|
```
|
||||||
|
|
||||||
|
As noted, this will fail if there are any local changes in your worktree. If you
|
||||||
|
want to stash these changes automatically before the pull (and unstash them
|
||||||
|
afterwards), use the `--stash` option.
|
||||||
|
|
||||||
|
This will rebase your changes onto the upstream branch. This is mainly helpful
|
||||||
|
for persistent branches that change on the remote side.
|
||||||
|
|
||||||
|
There is a similar rebase feature that rebases onto the **default** branch
|
||||||
|
instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt rebase
|
||||||
|
[✔] master: Done
|
||||||
|
[✔] my-cool-branch: Done
|
||||||
|
```
|
||||||
|
|
||||||
|
This is super helpful for feature branches. If you want to incorporate changes
|
||||||
|
made on the remote branches, use `grm wt rebase` and all your branches will be
|
||||||
|
up to date. If you want to also update to remote tracking branches in one go,
|
||||||
|
use the `--pull` flag, and `--rebase` if you want to rebase instead of aborting
|
||||||
|
on non-fast-forwards:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt rebase --pull --rebase
|
||||||
|
[✔] master: Done
|
||||||
|
[✔] my-cool-branch: Done
|
||||||
|
```
|
||||||
|
|
||||||
|
"So, what's the difference between `pull --rebase` and `rebase --pull`? Why the
|
||||||
|
hell is there a `--rebase` flag in the `rebase` command?"
|
||||||
|
|
||||||
|
Yes, it's kind of weird. Remember that `pull` only ever updates each worktree to
|
||||||
|
their remote branch, if possible. `rebase` rebases onto the **default** branch
|
||||||
|
instead. The switches to `rebase` are just convenience, so you do not have to
|
||||||
|
run two commands.
|
||||||
|
|
||||||
|
* `rebase --pull` is the same as `pull` && `rebase`
|
||||||
|
* `rebase --pull --rebase` is the same as `pull --rebase` && `rebase`
|
||||||
|
|
||||||
|
I understand that the UX is not the most intuitive. If you can think of an
|
||||||
|
improvement, please let me know (e.g. via an GitHub issue)!
|
||||||
|
|
||||||
|
As with `pull`, `rebase` will also refuse to run when there are changes in your
|
||||||
|
worktree. And you can also use the `--stash` option to stash/unstash changes
|
||||||
|
automatically.
|
||||||
173
docs/src/worktree_working.md
Normal file
173
docs/src/worktree_working.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Working with Worktrees
|
||||||
|
|
||||||
|
## Creating a new worktree
|
||||||
|
|
||||||
|
To actually work, you'll first have to create a new worktree checkout. All
|
||||||
|
worktree-related commands are available as subcommands of `grm worktree` (or
|
||||||
|
`grm wt` for short):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt add mybranch
|
||||||
|
[✔] Worktree mybranch created
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see that there is now a directory called `mybranch` that contains a
|
||||||
|
checkout of your repository, using the branch `mybranch`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd ./mybranch && git status
|
||||||
|
On branch mybranch
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
```
|
||||||
|
|
||||||
|
You can work in this repository as usual. Make changes, commit them, revert
|
||||||
|
them, whatever you're up to :)
|
||||||
|
|
||||||
|
Just note that you *should* not change the branch inside the worktree directory.
|
||||||
|
There is nothing preventing you from doing so, but you will notice that you'll
|
||||||
|
run into problems when trying to remove a worktree (more on that later). It may
|
||||||
|
also lead to confusing behavior, as there can be no two worktrees that have the
|
||||||
|
same branch checked out. So if you decide to use the worktree setup, go all in,
|
||||||
|
let `grm` manage your branches and bury `git branch` (and `git checkout -b`).
|
||||||
|
|
||||||
|
You will notice that there is no tracking branch set up for the new branch. You
|
||||||
|
can of course set up one manually after creating the worktree, but there is an
|
||||||
|
easier way, using the `--track` flag during creation. Let's create another
|
||||||
|
worktree. Go back to the root of the repository, and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ grm wt add mybranch2 --track origin/mybranch2
|
||||||
|
[✔] Worktree mybranch2 created
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see that this branch is now tracking `mybranch` on the `origin` remote:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd ./mybranch2 && git status
|
||||||
|
On branch mybranch
|
||||||
|
|
||||||
|
Your branch is up to date with 'origin/mybranch2'.
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
```
|
||||||
|
|
||||||
|
The behavior of `--track` differs depending on the existence of the remote
|
||||||
|
branch:
|
||||||
|
|
||||||
|
* If the remote branch already exists, `grm` uses it as the base of the new
|
||||||
|
local branch.
|
||||||
|
* If the remote branch does not exist (as in our example), `grm` will create a
|
||||||
|
new remote tracking branch, using the default branch (either `main` or
|
||||||
|
`master`) as the base
|
||||||
|
|
||||||
|
Often, you'll have a workflow that uses tracking branches by default. It would
|
||||||
|
be quite tedious to add `--track` every single time. Luckily, the `grm.toml`
|
||||||
|
file supports defaults for the tracking behavior. See this for an example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track]
|
||||||
|
default = true
|
||||||
|
default_remote = "origin"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will set up a tracking branch on `origin` that has the same name as the
|
||||||
|
local branch.
|
||||||
|
|
||||||
|
Sometimes, you might want to have a certain prefix for all your tracking
|
||||||
|
branches. Maybe to prevent collisions with other contributors. You can simply
|
||||||
|
set `default_remote_prefix` in `grm.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track]
|
||||||
|
default = true
|
||||||
|
default_remote = "origin"
|
||||||
|
default_remote_prefix = "myname"
|
||||||
|
```
|
||||||
|
|
||||||
|
When using branch `my-feature-branch`, the remote tracking branch would be
|
||||||
|
`origin/myname/my-feature-branch` in this case.
|
||||||
|
|
||||||
|
Note that `--track` overrides any configuration in `grm.toml`. If you want to
|
||||||
|
disable tracking, use `--no-track`.
|
||||||
|
|
||||||
|
## Showing the status of your worktrees
|
||||||
|
|
||||||
|
There is a handy little command that will show your an overview over all
|
||||||
|
worktrees in a repository, including their status (i.e. changes files). Just run
|
||||||
|
the following in the root of your repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt status
|
||||||
|
╭───────────┬────────┬──────────┬──────────────────╮
|
||||||
|
│ Worktree ┆ Status ┆ Branch ┆ Remote branch │
|
||||||
|
╞═══════════╪════════╪══════════╪══════════════════╡
|
||||||
|
│ mybranch ┆ ✔ ┆ mybranch ┆ │
|
||||||
|
│ mybranch2 ┆ ✔ ┆ mybranch ┆ origin/mybranch2 │
|
||||||
|
╰───────────┴────────┴──────────┴──────────────────╯
|
||||||
|
```
|
||||||
|
|
||||||
|
The "Status" column would show any uncommitted changes (new / modified / deleted
|
||||||
|
files) and the "Remote branch" would show differences to the remote branch (e.g.
|
||||||
|
if there are new pushes to the remote branch that are not yet incorporated into
|
||||||
|
your local branch).
|
||||||
|
|
||||||
|
|
||||||
|
## Deleting worktrees
|
||||||
|
|
||||||
|
If you're done with your worktrees, use `grm wt delete` to delete them. Let's
|
||||||
|
start with `mybranch2`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt delete mybranch2
|
||||||
|
[✔] Worktree mybranch2 deleted
|
||||||
|
```
|
||||||
|
|
||||||
|
Easy. On to `mybranch`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt delete mybranch
|
||||||
|
[!] Changes in worktree: No remote tracking branch for branch mybranch found. Refusing to delete
|
||||||
|
```
|
||||||
|
|
||||||
|
Hmmm. `grm` tells you:
|
||||||
|
|
||||||
|
"Hey, there is no remote branch that you could have pushed your changes to. I'd
|
||||||
|
rather not delete work that you cannot recover."
|
||||||
|
|
||||||
|
Note that `grm` is very cautious here. As your repository will not be deleted,
|
||||||
|
you could still recover the commits via
|
||||||
|
[`git-reflog`](https://git-scm.com/docs/git-reflog). But better safe than
|
||||||
|
sorry! Note that you'd get a similar error message if your worktree had any
|
||||||
|
uncommitted files, for the same reason. Now you can either commit & push your
|
||||||
|
changes, or your tell `grm` that you know what you're doing:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt delete mybranch --force
|
||||||
|
[✔] Worktree mybranch deleted
|
||||||
|
```
|
||||||
|
|
||||||
|
If you just want to delete all worktrees that do not contain any changes, you
|
||||||
|
can also use the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this will not delete the default branch of the repository. It can of
|
||||||
|
course still be delete with `grm wt delete` if necessary.
|
||||||
|
|
||||||
|
### Converting an existing repository
|
||||||
|
|
||||||
|
It is possible to convert an existing directory to a worktree setup, using `grm
|
||||||
|
wt convert`. This command has to be run in the root of the repository you want
|
||||||
|
to convert:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ grm wt convert
|
||||||
|
[✔] Conversion successful
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will refuse to run if you have any changes in your repository.
|
||||||
|
Commit them and try again!
|
||||||
|
|
||||||
|
Afterwards, the directory is empty, as there are no worktrees checked out yet.
|
||||||
|
Now you can use the usual commands to set up worktrees.
|
||||||
@@ -1,58 +1,60 @@
|
|||||||
# Git Worktrees
|
# Git Worktrees
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
The default workflow when using git is having your repository in a single
|
||||||
|
directory. Then, you can check out a certain reference (usually a branch),
|
||||||
|
which will update the files in the directory to match the state of that
|
||||||
|
reference. Most of the time, this is exactly what you need and works perfectly.
|
||||||
|
But especially when you're working with branches a lot, you may notice that
|
||||||
|
there is a lot of work required to make everything run smoothly.
|
||||||
|
|
||||||
The default workflow when using git is having your repository in a single directory.
|
Maybe you have experienced the following: You're working on a feature branch.
|
||||||
Then, you can check out a certain reference (usually a branch), which will update
|
Then, for some reason, you have to change branches (maybe to investigate some
|
||||||
the files in the directory to match the state of that reference. Most of the time,
|
issue). But you get the following:
|
||||||
this is exactly what you need and works perfectly. But especially when you're working
|
|
||||||
with branches a lot, you may notice that there is a lot of work required to make
|
|
||||||
everything run smoothly.
|
|
||||||
|
|
||||||
Maybe you have experienced the following: You're working on a feature branch. Then,
|
|
||||||
for some reason, you have to change branches (maybe to investigate some issue).
|
|
||||||
But you get the following:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
error: Your local changes to the following files would be overwritten by checkout
|
error: Your local changes to the following files would be overwritten by checkout
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can create a temporary commit or stash your changes. In any case, you have
|
Now you can create a temporary commit or stash your changes. In any case, you
|
||||||
some mental overhead before you can work on something else. Especially with stashes,
|
have some mental overhead before you can work on something else. Especially with
|
||||||
you'll have to remember to do a `git stash pop` before resuming your work (I
|
stashes, you'll have to remember to do a `git stash pop` before resuming your
|
||||||
cannot count the number of times where I "rediscovered" some code hidden in some
|
work (I cannot count the number of times where I "rediscovered" some code hidden
|
||||||
old stash I forgot about.
|
in some old stash I forgot about). Also, conflicts on a `git stash pop` are just
|
||||||
|
horrible.
|
||||||
|
|
||||||
And even worse: If you're currently in the process of resolving merge conflicts or an
|
And even worse: If you're currently in the process of resolving merge conflicts
|
||||||
interactive rebase, there is just no way to "pause" this work to check out a
|
or an interactive rebase, there is just no way to "pause" this work to check out
|
||||||
different branch.
|
a different branch.
|
||||||
|
|
||||||
Sometimes, it's crucial to have an unchanging state of your repository until some
|
Sometimes, it's crucial to have an unchanging state of your repository until
|
||||||
long-running process finishes. I'm thinking of Ansible and Terraform runs. I'd
|
some long-running process finishes. I'm thinking of Ansible and Terraform runs.
|
||||||
rather not change to a different branch while ansible or Terraform are running as
|
I'd rather not change to a different branch while ansible or Terraform are
|
||||||
I have no idea how those tools would behave (and I'm not too eager to find out).
|
running as I have no idea how those tools would behave (and I'm not too eager to
|
||||||
|
find out).
|
||||||
|
|
||||||
In any case, Git Worktrees are here for the rescue:
|
In any case, Git Worktrees are here for the rescue:
|
||||||
|
|
||||||
## What are git worktrees?
|
## What are git worktrees?
|
||||||
|
|
||||||
[Git Worktrees](https://git-scm.com/docs/git-worktree) allow you to have multiple
|
[Git Worktrees](https://git-scm.com/docs/git-worktree) allow you to have
|
||||||
independent checkouts of your repository on different directories. You can have
|
multiple independent checkouts of your repository on different directories. You
|
||||||
multiple directories that correspond to different references in your repository.
|
can have multiple directories that correspond to different references in your
|
||||||
Each worktree has it's independent working tree (duh) and index, so there is no
|
repository. Each worktree has it's independent working tree (duh) and index, so
|
||||||
way to run into conflicts. Changing to a different branch is just a `cd` away (if
|
there is no way to run into conflicts. Changing to a different branch is just a
|
||||||
the worktree is already set up).
|
`cd` away (if the worktree is already set up).
|
||||||
|
|
||||||
## Worktrees in GRM
|
## Worktrees in GRM
|
||||||
|
|
||||||
GRM exposes an opinionated way to use worktrees in your repositories. Opinionated,
|
GRM exposes an opinionated way to use worktrees in your repositories.
|
||||||
because there is a single invariant that makes reasoning about your worktree
|
Opinionated, because there is a single invariant that makes reasoning about your
|
||||||
setup quite easy:
|
worktree setup quite easy:
|
||||||
|
|
||||||
**The branch inside the worktree is always the same as the directory name of the worktree.**
|
**The branch inside the worktree is always the same as the directory name of the
|
||||||
|
worktree.**
|
||||||
|
|
||||||
In other words: If you're checking out branch `mybranch` into a new worktree, the
|
In other words: If you're checking out branch `mybranch` into a new worktree,
|
||||||
worktree directory will be named `mybranch`.
|
the worktree directory will be named `mybranch`.
|
||||||
|
|
||||||
GRM can be used with both "normal" and worktree-enabled repositories. But note
|
GRM can be used with both "normal" and worktree-enabled repositories. But note
|
||||||
that a single repository can be either the former or the latter. You'll have to
|
that a single repository can be either the former or the latter. You'll have to
|
||||||
@@ -67,303 +69,27 @@ name = "git-repo-manager"
|
|||||||
worktree_setup = true
|
worktree_setup = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, when you run a `grm sync`, you'll notice that the directory of the repository
|
Now, when you run a `grm sync`, you'll notice that the directory of the
|
||||||
is empty! Well, not totally, there is a hidden directory called `.git-main-working-tree`.
|
repository is empty! Well, not totally, there is a hidden directory called
|
||||||
This is where the repository actually "lives" (it's a bare checkout).
|
`.git-main-working-tree`. This is where the repository actually "lives" (it's a
|
||||||
|
bare checkout).
|
||||||
|
|
||||||
Note that there are few specific things you can configure for a certain
|
Note that there are few specific things you can configure for a certain
|
||||||
workspace. This is all done in an optional `grm.toml` file right in the root
|
workspace. This is all done in an optional `grm.toml` file right in the root of
|
||||||
of the worktree. More on that later.
|
the worktree. More on that later.
|
||||||
|
|
||||||
### Creating a new worktree
|
|
||||||
|
|
||||||
To actually work, you'll first have to create a new worktree checkout. All
|
## Manual access
|
||||||
worktree-related commands are available as subcommands of `grm worktree` (or
|
|
||||||
`grm wt` for short):
|
|
||||||
|
|
||||||
```
|
GRM isn't doing any magic, it's just git under the hood. If you need to have
|
||||||
$ grm wt add mybranch
|
access to the underlying git repository, you can always do this:
|
||||||
[✔] Worktree mybranch created
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll see that there is now a directory called `mybranch` that contains a checkout
|
|
||||||
of your repository, using the branch `mybranch`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd ./mybranch && git status
|
|
||||||
On branch mybranch
|
|
||||||
nothing to commit, working tree clean
|
|
||||||
```
|
|
||||||
|
|
||||||
You can work in this repository as usual. Make changes, commit them, revert them,
|
|
||||||
whatever you're up to :)
|
|
||||||
|
|
||||||
Just note that you *should* not change the branch inside the worktree
|
|
||||||
directory. There is nothing preventing you from doing so, but you will notice
|
|
||||||
that you'll run into problems when trying to remove a worktree (more on that
|
|
||||||
later). It may also lead to confusing behaviour, as there can be no two
|
|
||||||
worktrees that have the same branch checked out. So if you decide to use the
|
|
||||||
worktree setup, go all in, let `grm` manage your branches and bury `git branch`
|
|
||||||
(and `git checkout -b`).
|
|
||||||
|
|
||||||
You will notice that there is no tracking branch set up for the new branch. You
|
|
||||||
can of course set up one manually after creating the worktree, but there is an
|
|
||||||
easier way, using the `--track` flag during creation. Let's create another
|
|
||||||
worktree. Go back to the root of the repository, and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ grm wt add mybranch2 --track origin/mybranch2
|
|
||||||
[✔] Worktree mybranch2 created
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll see that this branch is now tracking `mybranch` on the `origin` remote:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd ./mybranch2 && git status
|
|
||||||
On branch mybranch
|
|
||||||
|
|
||||||
Your branch is up to date with 'origin/mybranch2'.
|
|
||||||
nothing to commit, working tree clean
|
|
||||||
```
|
|
||||||
|
|
||||||
The behaviour of `--track` differs depending on the existence of the remote branch:
|
|
||||||
|
|
||||||
* If the remote branch already exists, `grm` uses it as the base of the new
|
|
||||||
local branch.
|
|
||||||
* If the remote branch does not exist (as in our example), `grm` will create a
|
|
||||||
new remote tracking branch, using the default branch (either `main` or `master`)
|
|
||||||
as the base
|
|
||||||
|
|
||||||
Often, you'll have a workflow that uses tracking branches by default. It would
|
|
||||||
be quite tedious to add `--track` every single time. Luckily, the `grm.toml` file
|
|
||||||
supports defaults for the tracking behaviour. See this for an example:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[track]
|
|
||||||
default = true
|
|
||||||
default_remote = "origin"
|
|
||||||
```
|
|
||||||
|
|
||||||
This will set up a tracking branch on `origin` that has the same name as the local
|
|
||||||
branch.
|
|
||||||
|
|
||||||
Sometimes, you might want to have a certain prefix for all your tracking branches.
|
|
||||||
Maybe to prevent collissions with other contributors. You can simply set
|
|
||||||
`default_remote_prefix` in `grm.toml`:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[track]
|
|
||||||
default = true
|
|
||||||
default_remote = "origin"
|
|
||||||
default_remote_prefix = "myname"
|
|
||||||
```
|
|
||||||
|
|
||||||
When using branch `my-feature-branch`, the remote tracking branch would be
|
|
||||||
`origin/myname/my-feature-branch` in this case.
|
|
||||||
|
|
||||||
Note that `--track` overrides any configuration in `grm.toml`. If you want to
|
|
||||||
disable tracking, use `--no-track`.
|
|
||||||
|
|
||||||
### Showing the status of your worktrees
|
|
||||||
|
|
||||||
There is a handy little command that will show your an overview over all worktrees
|
|
||||||
in a repository, including their status (i.e. changes files). Just run the following
|
|
||||||
in the root of your repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt status
|
|
||||||
╭───────────┬────────┬──────────┬──────────────────╮
|
|
||||||
│ Worktree ┆ Status ┆ Branch ┆ Remote branch │
|
|
||||||
╞═══════════╪════════╪══════════╪══════════════════╡
|
|
||||||
│ mybranch ┆ ✔ ┆ mybranch ┆ │
|
|
||||||
│ mybranch2 ┆ ✔ ┆ mybranch ┆ origin/mybranch2 │
|
|
||||||
╰───────────┴────────┴──────────┴──────────────────╯
|
|
||||||
```
|
|
||||||
|
|
||||||
The "Status" column would show any uncommitted changes (new / modified / deleted
|
|
||||||
files) and the "Remote branch" would show differences to the remote branch (e.g.
|
|
||||||
if there are new pushes to the remote branch that are not yet incorporated into
|
|
||||||
your local branch).
|
|
||||||
|
|
||||||
|
|
||||||
### Deleting worktrees
|
|
||||||
|
|
||||||
If you're done with your worktrees, use `grm wt delete` to delete them. Let's
|
|
||||||
start with `mybranch2`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt delete mybranch2
|
|
||||||
[✔] Worktree mybranch2 deleted
|
|
||||||
```
|
|
||||||
|
|
||||||
Easy. On to `mybranch`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt delete mybranch
|
|
||||||
[!] Changes in worktree: No remote tracking branch for branch mybranch found. Refusing to delete
|
|
||||||
```
|
|
||||||
|
|
||||||
Hmmm. `grm` tells you:
|
|
||||||
|
|
||||||
"Hey, there is no remote branch that you could have pushed
|
|
||||||
your changes to. I'd rather not delete work that you cannot recover."
|
|
||||||
|
|
||||||
Note that `grm` is very cautious here. As your repository will not be deleted,
|
|
||||||
you could still recover the commits via [`git-reflog`](https://git-scm.com/docs/git-reflog).
|
|
||||||
But better safe than sorry! Note that you'd get a similar error message if your
|
|
||||||
worktree had any uncommitted files, for the same reason. Now you can either
|
|
||||||
commit & push your changes, or your tell `grm` that you know what you're doing:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt delete mybranch --force
|
|
||||||
[✔] Worktree mybranch deleted
|
|
||||||
```
|
|
||||||
|
|
||||||
If you just want to delete all worktrees that do not contain any changes, you
|
|
||||||
can also use the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt clean
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that this will not delete the default branch of the repository. It can of
|
|
||||||
course still be delete with `grm wt delete` if neccessary.
|
|
||||||
|
|
||||||
### Persistent branches
|
|
||||||
|
|
||||||
You most likely have a few branches that are "special", that you don't want to
|
|
||||||
clean up and that are the usual target for feature branches to merge into. GRM
|
|
||||||
calls them "persistent branches" and treats them a bit differently:
|
|
||||||
|
|
||||||
* Their worktrees will never be deleted by `grm wt clean`
|
|
||||||
* If the branches in other worktrees are merged into them, they will be cleaned
|
|
||||||
up, even though they may not be in line with their upstream. Same goes for
|
|
||||||
`grm wt delete`, which will not require a `--force` flag. Note that of
|
|
||||||
course, actual changes in the worktree will still block an automatic cleanup!
|
|
||||||
* As soon as you enable persistent branches, non-persistent branches will only
|
|
||||||
ever be cleaned up when merged into a persistent branch.
|
|
||||||
|
|
||||||
To elaborate: This is mostly relevant for a feature-branch workflow. Whenever a
|
|
||||||
feature branch is merged, it can usually be thrown away. As merging is usually
|
|
||||||
done on some remote code management platform (GitHub, GitLab, ...), this means
|
|
||||||
that you usually keep a branch around until it is merged into one of the "main"
|
|
||||||
branches (`master`, `main`, `develop`, ...)
|
|
||||||
|
|
||||||
Enable persistent branches by setting the following in the `grm.toml` in the
|
|
||||||
worktree root:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
persistent_branches = [
|
|
||||||
"master",
|
|
||||||
"develop",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that setting persistent branches will disable any detection of "default"
|
|
||||||
branches. The first entry will be considered your repositories' default branch.
|
|
||||||
|
|
||||||
### Converting an existing repository
|
|
||||||
|
|
||||||
It is possible to convert an existing directory to a worktree setup, using `grm
|
|
||||||
wt convert`. This command has to be run in the root of the repository you want
|
|
||||||
to convert:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt convert
|
|
||||||
[✔] Conversion successful
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will refuse to run if you have any changes in your repository.
|
|
||||||
Commit them and try again!
|
|
||||||
|
|
||||||
Afterwards, the directory is empty, as there are no worktrees checked out yet.
|
|
||||||
Now you can use the usual commands to set up worktrees.
|
|
||||||
|
|
||||||
### Working with remotes
|
|
||||||
|
|
||||||
To fetch all remote references from all remotes in a worktree setup, you can
|
|
||||||
use the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt fetch
|
|
||||||
[✔] Fetched from all remotes
|
|
||||||
```
|
|
||||||
|
|
||||||
This is equivalent to running `git fetch --all` in any of the worktrees.
|
|
||||||
|
|
||||||
Often, you may want to pull all remote changes into your worktrees. For this,
|
|
||||||
use the `git pull` equivalent:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt pull
|
|
||||||
[✔] master: Done
|
|
||||||
[✔] my-cool-branch: Done
|
|
||||||
```
|
|
||||||
|
|
||||||
This will refuse when there are local changes, or if the branch cannot be fast
|
|
||||||
forwarded. If you want to rebase your local branches, use the `--rebase` switch:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt pull --rebase
|
|
||||||
[✔] master: Done
|
|
||||||
[✔] my-cool-branch: Done
|
|
||||||
```
|
|
||||||
|
|
||||||
As noted, this will fail if there are any local changes in your worktree. If you
|
|
||||||
want to stash these changes automatically before the pull (and unstash them
|
|
||||||
afterwards), use the `--stash` option.
|
|
||||||
|
|
||||||
This will rebase your changes onto the upstream branch. This is mainly helpful
|
|
||||||
for persistent branches that change on the remote side.
|
|
||||||
|
|
||||||
There is a similar rebase feature that rebases onto the **default** branch instead:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt rebase
|
|
||||||
[✔] master: Done
|
|
||||||
[✔] my-cool-branch: Done
|
|
||||||
```
|
|
||||||
|
|
||||||
This is super helpful for feature branches. If you want to incorporate changes
|
|
||||||
made on the remote branches, use `grm wt rebase` and all your branches will
|
|
||||||
be up to date. If you want to also update to remote tracking branches in one go,
|
|
||||||
use the `--pull` flag, and `--rebase` if you want to rebase instead of aborting
|
|
||||||
on non-fast-forwards:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ grm wt rebase --pull --rebase
|
|
||||||
[✔] master: Done
|
|
||||||
[✔] my-cool-branch: Done
|
|
||||||
```
|
|
||||||
|
|
||||||
"So, what's the difference between `pull --rebase` and `rebase --pull`? Why the
|
|
||||||
hell is there a `--rebase` flag in the `rebase` command?"
|
|
||||||
|
|
||||||
Yes, it's kind of weird. Remember that `pull` only ever updates each worktree
|
|
||||||
to their remote branch, if possible. `rebase` rebases onto the **default** branch
|
|
||||||
instead. The switches to `rebase` are just convenience, so you do not have to
|
|
||||||
run two commands.
|
|
||||||
|
|
||||||
* `rebase --pull` is the same as `pull` && `rebase`
|
|
||||||
* `rebase --pull --rebase` is the same as `pull --rebase` && `rebase`
|
|
||||||
|
|
||||||
I understand that the UX is not the most intuitive. If you can think of an
|
|
||||||
improvement, please let me know (e.g. via an GitHub issue)!
|
|
||||||
|
|
||||||
As with `pull`, `rebase` will also refuse to run when there are changes in your
|
|
||||||
worktree. And you can also use the `--stash` option to stash/unstash changes
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
### Manual access
|
|
||||||
|
|
||||||
GRM isn't doing any magic, it's just git under the hood. If you need to have access
|
|
||||||
to the underlying git repository, you can always do this:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git --git-dir ./.git-main-working-tree [...]
|
$ git --git-dir ./.git-main-working-tree [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
This should never be required (whenever you have to do this, you can consider
|
This should never be required (whenever you have to do this, you can consider
|
||||||
this a bug in GRM and open an [issue](https://github.com/hakoerber/git-repo-manager/issues/new),
|
this a bug in GRM and open an
|
||||||
but it may help in a pinch.
|
[issue](https://github.com/hakoerber/git-repo-manager/issues/new), but it may
|
||||||
|
help in a pinch.
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def test_worktree_add(
|
|||||||
explicit_track_branch_name = f"{default_remote}/{worktree_name}"
|
explicit_track_branch_name = f"{default_remote}/{worktree_name}"
|
||||||
|
|
||||||
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
|
timestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||||
# GitPython has some weird behaviour here. It is not possible to use kwargs
|
# GitPython has some weird behavior here. It is not possible to use kwargs
|
||||||
# to set the commit and author date.
|
# to set the commit and author date.
|
||||||
#
|
#
|
||||||
# `committer_date=x` (which is documented) does not work, as `git commit`
|
# `committer_date=x` (which is documented) does not work, as `git commit`
|
||||||
@@ -79,7 +79,7 @@ def test_worktree_add(
|
|||||||
# `author_date=x` does not work, as it's now called --date in `git commit`
|
# `author_date=x` does not work, as it's now called --date in `git commit`
|
||||||
#
|
#
|
||||||
# `date=x` should work, but is refused by GitPython, as it does not know
|
# `date=x` should work, but is refused by GitPython, as it does not know
|
||||||
# about the new behaviour in `git commit`
|
# about the new behavior in `git commit`
|
||||||
#
|
#
|
||||||
# Fortunately, there are env variables that control those timestamps.
|
# Fortunately, there are env variables that control those timestamps.
|
||||||
os.environ["GIT_COMMITTER_DATE"] = str(timestamp)
|
os.environ["GIT_COMMITTER_DATE"] = str(timestamp)
|
||||||
@@ -581,6 +581,7 @@ def test_worktree_delete():
|
|||||||
|
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
assert len(cmd.stdout.strip().split("\n")) == 1
|
||||||
assert "test" not in os.listdir(base_dir)
|
assert "test" not in os.listdir(base_dir)
|
||||||
|
|
||||||
cmd = grm(["wt", "add", "check"], cwd=base_dir)
|
cmd = grm(["wt", "add", "check"], cwd=base_dir)
|
||||||
@@ -607,6 +608,7 @@ def test_worktree_delete_in_subfolder(has_other_worktree):
|
|||||||
|
|
||||||
cmd = grm(["wt", "delete", "dir/test"], cwd=base_dir)
|
cmd = grm(["wt", "delete", "dir/test"], cwd=base_dir)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
assert len(cmd.stdout.strip().split("\n")) == 1
|
||||||
if has_other_worktree is True:
|
if has_other_worktree is True:
|
||||||
assert {"test2"} == set(os.listdir(os.path.join(base_dir, "dir")))
|
assert {"test2"} == set(os.listdir(os.path.join(base_dir, "dir")))
|
||||||
else:
|
else:
|
||||||
@@ -621,6 +623,7 @@ def test_worktree_delete_refusal_no_tracking_branch():
|
|||||||
before = checksum_directory(f"{base_dir}/test")
|
before = checksum_directory(f"{base_dir}/test")
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
|
assert len(cmd.stdout) == 0
|
||||||
stderr = cmd.stderr.lower()
|
stderr = cmd.stderr.lower()
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
assert "refuse" in stderr or "refusing" in stderr
|
||||||
assert "test" in os.listdir(base_dir)
|
assert "test" in os.listdir(base_dir)
|
||||||
@@ -629,94 +632,45 @@ def test_worktree_delete_refusal_no_tracking_branch():
|
|||||||
assert before == after
|
assert before == after
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_delete_refusal_uncommited_changes_new_file():
|
@pytest.mark.parametrize(
|
||||||
|
"reason",
|
||||||
|
(
|
||||||
|
"new_file",
|
||||||
|
"changed_file",
|
||||||
|
"deleted_file",
|
||||||
|
"new_commit",
|
||||||
|
"tracking_branch_mismatch",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_worktree_delete_refusal(reason):
|
||||||
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
||||||
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
|
||||||
shell(f"cd {base_dir}/test && touch changed_file")
|
if reason == "new_file":
|
||||||
|
shell(f"cd {base_dir}/test && touch changed_file")
|
||||||
before = checksum_directory(f"{base_dir}/test")
|
elif reason == "changed_file":
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
shell(
|
||||||
assert cmd.returncode != 0
|
f"cd {base_dir}/test && git ls-files | shuf | head | while read f ; do echo $RANDOM > $f ; done"
|
||||||
stderr = cmd.stderr.lower()
|
)
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
elif reason == "deleted_file":
|
||||||
assert "test" in os.listdir(base_dir)
|
shell(f"cd {base_dir}/test && git ls-files | shuf | head | xargs rm -rf")
|
||||||
|
elif reason == "new_commit":
|
||||||
after = checksum_directory(f"{base_dir}/test")
|
shell(
|
||||||
assert before == after
|
f'cd {base_dir}/test && touch changed_file && git add changed_file && git commit -m "commitmsg"'
|
||||||
|
)
|
||||||
|
elif reason == "tracking_branch_mismatch":
|
||||||
def test_worktree_delete_refusal_uncommited_changes_changed_file():
|
shell(
|
||||||
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
f"cd {base_dir}/test && git push origin test && git reset --hard origin/test^"
|
||||||
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
)
|
||||||
assert cmd.returncode == 0
|
|
||||||
|
else:
|
||||||
shell(f"cd {base_dir}/test && git ls-files | shuf | head | xargs rm -rf")
|
raise NotImplementedError()
|
||||||
|
|
||||||
before = checksum_directory(f"{base_dir}/test")
|
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode != 0
|
|
||||||
stderr = cmd.stderr.lower()
|
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
|
||||||
assert "test" in os.listdir(base_dir)
|
|
||||||
|
|
||||||
after = checksum_directory(f"{base_dir}/test")
|
|
||||||
assert before == after
|
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_delete_refusal_uncommited_changes_deleted_file():
|
|
||||||
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
|
||||||
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode == 0
|
|
||||||
|
|
||||||
shell(
|
|
||||||
f"cd {base_dir}/test && git ls-files | shuf | head | while read f ; do echo $RANDOM > $f ; done"
|
|
||||||
)
|
|
||||||
|
|
||||||
before = checksum_directory(f"{base_dir}/test")
|
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode != 0
|
|
||||||
stderr = cmd.stderr.lower()
|
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
|
||||||
assert "test" in os.listdir(base_dir)
|
|
||||||
|
|
||||||
after = checksum_directory(f"{base_dir}/test")
|
|
||||||
assert before == after
|
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_delete_refusal_commited_changes():
|
|
||||||
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
|
||||||
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode == 0
|
|
||||||
|
|
||||||
shell(
|
|
||||||
f'cd {base_dir}/test && touch changed_file && git add changed_file && git commit -m "commitmsg"'
|
|
||||||
)
|
|
||||||
|
|
||||||
before = checksum_directory(f"{base_dir}/test")
|
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode != 0
|
|
||||||
stderr = cmd.stderr.lower()
|
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
|
||||||
assert "test" in os.listdir(base_dir)
|
|
||||||
|
|
||||||
after = checksum_directory(f"{base_dir}/test")
|
|
||||||
assert before == after
|
|
||||||
|
|
||||||
|
|
||||||
def test_worktree_delete_refusal_tracking_branch_mismatch():
|
|
||||||
with TempGitRepositoryWorktree.get(funcname()) as (base_dir, _commit):
|
|
||||||
cmd = grm(["wt", "add", "test", "--track", "origin/test"], cwd=base_dir)
|
|
||||||
assert cmd.returncode == 0
|
|
||||||
|
|
||||||
shell(
|
|
||||||
f"cd {base_dir}/test && git push origin test && git reset --hard origin/test^"
|
|
||||||
)
|
|
||||||
|
|
||||||
before = checksum_directory(f"{base_dir}/test")
|
before = checksum_directory(f"{base_dir}/test")
|
||||||
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
cmd = grm(["wt", "delete", "test"], cwd=base_dir)
|
||||||
assert cmd.returncode != 0
|
assert cmd.returncode != 0
|
||||||
|
assert len(cmd.stdout) == 0
|
||||||
stderr = cmd.stderr.lower()
|
stderr = cmd.stderr.lower()
|
||||||
assert "refuse" in stderr or "refusing" in stderr
|
assert "refuse" in stderr or "refusing" in stderr
|
||||||
assert "test" in os.listdir(base_dir)
|
assert "test" in os.listdir(base_dir)
|
||||||
@@ -732,6 +686,7 @@ def test_worktree_delete_force_refusal():
|
|||||||
|
|
||||||
cmd = grm(["wt", "delete", "test", "--force"], cwd=base_dir)
|
cmd = grm(["wt", "delete", "test", "--force"], cwd=base_dir)
|
||||||
assert cmd.returncode == 0
|
assert cmd.returncode == 0
|
||||||
|
assert len(cmd.stdout.strip().split("\n")) == 1
|
||||||
assert "test" not in os.listdir(base_dir)
|
assert "test" not in os.listdir(base_dir)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set -o errexit
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
printf '%s\n' "usage: $0 (master|minor|patch)" >&2
|
printf '%s\n' "usage: $0 (major|minor|patch)" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($# != 1)); then
|
if (($# != 1)); then
|
||||||
@@ -24,6 +24,9 @@ major)
|
|||||||
((major++)) || true
|
((major++)) || true
|
||||||
minor=0
|
minor=0
|
||||||
patch=0
|
patch=0
|
||||||
|
|
||||||
|
printf '%s\n' "Are you sure you want to release 1.x?" >&2
|
||||||
|
exit 1
|
||||||
;;
|
;;
|
||||||
minor)
|
minor)
|
||||||
((minor++)) || true
|
((minor++)) || true
|
||||||
@@ -91,10 +94,6 @@ if ((changes == 0)); then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
just update-dependencies
|
|
||||||
|
|
||||||
just check
|
|
||||||
|
|
||||||
sed -i "0,/^version/{s/^version.*$/version = \"${new_version}\"/}" Cargo.toml
|
sed -i "0,/^version/{s/^version.*$/version = \"${new_version}\"/}" Cargo.toml
|
||||||
|
|
||||||
cargo update --package git-repo-manager --precise "${new_version}"
|
cargo update --package git-repo-manager --precise "${new_version}"
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ impl Worktree {
|
|||||||
let operation = operation.map_err(convert_libgit2_error)?;
|
let operation = operation.map_err(convert_libgit2_error)?;
|
||||||
|
|
||||||
// This is required to preserve the commiter of the rebased
|
// This is required to preserve the commiter of the rebased
|
||||||
// commits, which is the expected behaviour.
|
// commits, which is the expected behavior.
|
||||||
let rebased_commit = repo
|
let rebased_commit = repo
|
||||||
.0
|
.0
|
||||||
.find_commit(operation.id())
|
.find_commit(operation.id())
|
||||||
@@ -357,7 +357,7 @@ impl Worktree {
|
|||||||
let operation = operation.map_err(convert_libgit2_error)?;
|
let operation = operation.map_err(convert_libgit2_error)?;
|
||||||
|
|
||||||
// This is required to preserve the commiter of the rebased
|
// This is required to preserve the commiter of the rebased
|
||||||
// commits, which is the expected behaviour.
|
// commits, which is the expected behavior.
|
||||||
let rebased_commit = repo
|
let rebased_commit = repo
|
||||||
.0
|
.0
|
||||||
.find_commit(operation.id())
|
.find_commit(operation.id())
|
||||||
@@ -1268,7 +1268,6 @@ impl RepoHandle {
|
|||||||
if let Some(current_dir) = worktree_dir.parent() {
|
if let Some(current_dir) = worktree_dir.parent() {
|
||||||
for current_dir in current_dir.ancestors() {
|
for current_dir in current_dir.ancestors() {
|
||||||
let current_dir = base_dir.join(current_dir);
|
let current_dir = base_dir.join(current_dir);
|
||||||
println!("deleting {}", current_dir.display());
|
|
||||||
if current_dir
|
if current_dir
|
||||||
.read_dir()
|
.read_dir()
|
||||||
.map_err(|error| {
|
.map_err(|error| {
|
||||||
@@ -1281,7 +1280,7 @@ impl RepoHandle {
|
|||||||
.next()
|
.next()
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
if let Err(e) = std::fs::remove_dir_all(¤t_dir) {
|
if let Err(e) = std::fs::remove_dir(¤t_dir) {
|
||||||
return Err(WorktreeRemoveFailureReason::Error(format!(
|
return Err(WorktreeRemoveFailureReason::Error(format!(
|
||||||
"Error deleting {}: {}",
|
"Error deleting {}: {}",
|
||||||
&worktree_dir.display(),
|
&worktree_dir.display(),
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ pub fn sync_trees(config: config::Config, init_worktree: bool) -> Result<bool, S
|
|||||||
|
|
||||||
match find_unmanaged_repos(&root_path, &repos) {
|
match find_unmanaged_repos(&root_path, &repos) {
|
||||||
Ok(repos) => {
|
Ok(repos) => {
|
||||||
unmanaged_repos_absolute_paths.extend(repos);
|
for path in repos.into_iter() {
|
||||||
|
if !unmanaged_repos_absolute_paths.contains(&path) {
|
||||||
|
unmanaged_repos_absolute_paths.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
print_error(&format!("Error getting unmanaged repos: {}", error));
|
print_error(&format!("Error getting unmanaged repos: {}", error));
|
||||||
|
|||||||
@@ -81,9 +81,9 @@
|
|||||||
//! * Instead of just picking `origin/prefix/foobar`, grm will complain because
|
//! * Instead of just picking `origin/prefix/foobar`, grm will complain because
|
||||||
//! it also selected `remote2/foobar`.
|
//! it also selected `remote2/foobar`.
|
||||||
//!
|
//!
|
||||||
//! This is just emergent behaviour of the logic above. Fixing it would require
|
//! This is just emergent behavior of the logic above. Fixing it would require
|
||||||
//! additional logic for that edge case. I assume that it's just so rare to get
|
//! additional logic for that edge case. I assume that it's just so rare to get
|
||||||
//! that behaviour that it's acceptable for now.
|
//! that behavior that it's acceptable for now.
|
||||||
//!
|
//!
|
||||||
//! Now we either have a commit, we aborted, or we do not have commit. In the
|
//! Now we either have a commit, we aborted, or we do not have commit. In the
|
||||||
//! last case, as stated above, we check out the "default" branch.
|
//! last case, as stated above, we check out the "default" branch.
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
//! `foobar`. As both `remote1/foobar` and `remote2/foobar` as the same, the new
|
//! `foobar`. As both `remote1/foobar` and `remote2/foobar` as the same, the new
|
||||||
//! worktree will use that as the state of the new branch. But as `grm` cannot
|
//! worktree will use that as the state of the new branch. But as `grm` cannot
|
||||||
//! tell which remote branch to track, it will not set up remote tracking. This
|
//! tell which remote branch to track, it will not set up remote tracking. This
|
||||||
//! behaviour may be a bit confusing, but first, there is no good way to resolve
|
//! behavior may be a bit confusing, but first, there is no good way to resolve
|
||||||
//! this, and second, the situation should be really rare (when having multiple
|
//! this, and second, the situation should be really rare (when having multiple
|
||||||
//! remotes, you would generally have a `default_remote` configured).
|
//! remotes, you would generally have a `default_remote` configured).
|
||||||
//!
|
//!
|
||||||
@@ -463,7 +463,7 @@ impl<'a> Worktree<'a, WithRemoteTrackingBranch<'a>> {
|
|||||||
//
|
//
|
||||||
// > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test
|
// > failed to make directory '/{repo}/.git-main-working-tree/worktrees/dir/test
|
||||||
//
|
//
|
||||||
// This is a discrepancy between the behaviour of libgit2 and the
|
// This is a discrepancy between the behavior of libgit2 and the
|
||||||
// git CLI when creating worktrees with slashes:
|
// git CLI when creating worktrees with slashes:
|
||||||
//
|
//
|
||||||
// The git CLI will create the worktree's configuration directory
|
// The git CLI will create the worktree's configuration directory
|
||||||
|
|||||||
Reference in New Issue
Block a user