mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-26 23:59:19 +03:00
Merge pull request #5416 from gitbutlerapp/tree-merge
Replace `merge-tree` in more places
This commit is contained in:
commit
7fe5d0c85e
103
Cargo.lock
generated
103
Cargo.lock
generated
@ -2687,6 +2687,7 @@ dependencies = [
|
||||
"gitbutler-command-context",
|
||||
"gitbutler-diff",
|
||||
"gitbutler-fs",
|
||||
"gitbutler-oxidize",
|
||||
"gitbutler-project",
|
||||
"gitbutler-reference",
|
||||
"gitbutler-repo",
|
||||
@ -3038,7 +3039,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix"
|
||||
version = "0.67.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-actor 0.33.0",
|
||||
"gix-attributes 0.23.0",
|
||||
@ -3106,7 +3107,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-actor"
|
||||
version = "0.33.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-date 0.9.1",
|
||||
@ -3136,7 +3137,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-attributes"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-glob 0.17.0",
|
||||
@ -3161,7 +3162,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-bitmap"
|
||||
version = "0.2.12"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@ -3178,7 +3179,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-chunk"
|
||||
version = "0.4.9"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@ -3186,7 +3187,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-command"
|
||||
version = "0.3.10"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-path 0.10.12",
|
||||
@ -3211,7 +3212,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-commitgraph"
|
||||
version = "0.25.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-chunk 0.4.9",
|
||||
@ -3224,7 +3225,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-config"
|
||||
version = "0.41.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-config-value",
|
||||
@ -3244,7 +3245,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-config-value"
|
||||
version = "0.14.9"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
@ -3256,7 +3257,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-credentials"
|
||||
version = "0.25.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-command",
|
||||
@ -3284,7 +3285,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-date"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"itoa 1.0.11",
|
||||
@ -3295,7 +3296,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-diff"
|
||||
version = "0.47.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-command",
|
||||
@ -3315,7 +3316,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-dir"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-discover 0.36.0",
|
||||
@ -3350,7 +3351,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-discover"
|
||||
version = "0.36.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"dunce",
|
||||
@ -3380,7 +3381,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-features"
|
||||
version = "0.39.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"crc32fast",
|
||||
@ -3402,7 +3403,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-filter"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"encoding_rs",
|
||||
@ -3433,7 +3434,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-fs"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"fastrand 2.1.1",
|
||||
"gix-features 0.39.0",
|
||||
@ -3455,7 +3456,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-glob"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
@ -3476,7 +3477,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-hash"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"faster-hex",
|
||||
"thiserror",
|
||||
@ -3496,7 +3497,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-hashtable"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-hash 0.15.0",
|
||||
"hashbrown 0.14.5",
|
||||
@ -3519,7 +3520,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-ignore"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-glob 0.17.0",
|
||||
@ -3559,7 +3560,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-index"
|
||||
version = "0.36.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
@ -3597,7 +3598,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-lock"
|
||||
version = "15.0.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-tempfile 15.0.0",
|
||||
"gix-utils 0.1.13",
|
||||
@ -3607,7 +3608,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-merge"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-command",
|
||||
@ -3630,7 +3631,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-negotiate"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"gix-commitgraph 0.25.0",
|
||||
@ -3664,7 +3665,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-object"
|
||||
version = "0.45.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-actor 0.33.0",
|
||||
@ -3683,7 +3684,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-odb"
|
||||
version = "0.64.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"gix-date 0.9.1",
|
||||
@ -3703,7 +3704,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-pack"
|
||||
version = "0.54.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"clru",
|
||||
"gix-chunk 0.4.9",
|
||||
@ -3723,7 +3724,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-packetline"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"faster-hex",
|
||||
@ -3734,7 +3735,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-packetline-blocking"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"faster-hex",
|
||||
@ -3758,7 +3759,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-path"
|
||||
version = "0.10.12"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-trace 0.1.11",
|
||||
@ -3770,7 +3771,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-pathspec"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
@ -3784,7 +3785,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-prompt"
|
||||
version = "0.8.8"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-command",
|
||||
"gix-config-value",
|
||||
@ -3796,7 +3797,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-protocol"
|
||||
version = "0.46.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-credentials",
|
||||
@ -3824,7 +3825,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-quote"
|
||||
version = "0.4.13"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-utils 0.1.13",
|
||||
@ -3856,7 +3857,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-ref"
|
||||
version = "0.48.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-actor 0.33.0",
|
||||
"gix-features 0.39.0",
|
||||
@ -3876,7 +3877,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-refspec"
|
||||
version = "0.26.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-hash 0.15.0",
|
||||
@ -3889,7 +3890,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-revision"
|
||||
version = "0.30.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bstr",
|
||||
@ -3921,7 +3922,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-revwalk"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"gix-commitgraph 0.25.0",
|
||||
"gix-date 0.9.1",
|
||||
@ -3947,7 +3948,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-sec"
|
||||
version = "0.10.9"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"gix-path 0.10.12",
|
||||
@ -3958,7 +3959,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-status"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"filetime",
|
||||
@ -3980,7 +3981,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-submodule"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-config",
|
||||
@ -4009,7 +4010,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-tempfile"
|
||||
version = "15.0.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"gix-fs 0.12.0",
|
||||
@ -4054,7 +4055,7 @@ checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b"
|
||||
[[package]]
|
||||
name = "gix-trace"
|
||||
version = "0.1.11"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
@ -4062,7 +4063,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-transport"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bstr",
|
||||
@ -4097,7 +4098,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-traverse"
|
||||
version = "0.42.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"gix-commitgraph 0.25.0",
|
||||
@ -4113,7 +4114,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-url"
|
||||
version = "0.28.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-features 0.39.0",
|
||||
@ -4135,7 +4136,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-utils"
|
||||
version = "0.1.13"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"fastrand 2.1.1",
|
||||
@ -4155,7 +4156,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-validate"
|
||||
version = "0.9.1"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"thiserror",
|
||||
@ -4183,7 +4184,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-worktree"
|
||||
version = "0.37.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-attributes 0.23.0",
|
||||
@ -4201,7 +4202,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gix-worktree-state"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=3fb989be21c739bbfeac93953c1685e7c6cd2106#3fb989be21c739bbfeac93953c1685e7c6cd2106"
|
||||
source = "git+https://github.com/Byron/gitoxide?rev=a8765330fc16997dee275866b18a128dec1c5d55#a8765330fc16997dee275866b18a128dec1c5d55"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-features 0.39.0",
|
||||
|
@ -42,7 +42,7 @@ resolver = "2"
|
||||
[workspace.dependencies]
|
||||
bstr = "1.10.0"
|
||||
# Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes.
|
||||
gix = { git = "https://github.com/Byron/gitoxide", rev = "3fb989be21c739bbfeac93953c1685e7c6cd2106", default-features = false, features = [
|
||||
gix = { git = "https://github.com/Byron/gitoxide", rev = "a8765330fc16997dee275866b18a128dec1c5d55", default-features = false, features = [
|
||||
] }
|
||||
git2 = { version = "0.19.0", features = [
|
||||
"vendored-openssl",
|
||||
|
@ -9,7 +9,7 @@ publish = false
|
||||
tracing.workspace = true
|
||||
anyhow = "1.0.92"
|
||||
git2.workspace = true
|
||||
gix = { workspace = true, features = ["blob-diff", "revision", "blob-merge"] }
|
||||
gix = { workspace = true, features = ["blob-diff", "revision", "merge"] }
|
||||
tokio.workspace = true
|
||||
gitbutler-oplog.workspace = true
|
||||
gitbutler-repo.workspace = true
|
||||
|
@ -1,16 +1,5 @@
|
||||
use std::{path::Path, time};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gitbutler_branch::GITBUTLER_WORKSPACE_REFERENCE;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_error::error::Marker;
|
||||
use gitbutler_project::FetchResult;
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gitbutler_repo::{LogUntil, RepositoryExt};
|
||||
use gitbutler_repo_actions::RepoActionsExt;
|
||||
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
conflicts::RepoConflictsExt,
|
||||
hunk::VirtualBranchHunk,
|
||||
@ -18,6 +7,17 @@ use crate::{
|
||||
remote::{commit_to_remote_commit, RemoteCommit},
|
||||
VirtualBranchesExt,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use gitbutler_branch::GITBUTLER_WORKSPACE_REFERENCE;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_error::error::Marker;
|
||||
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
|
||||
use gitbutler_project::FetchResult;
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gitbutler_repo::{GixRepositoryExt, LogUntil, RepositoryExt};
|
||||
use gitbutler_repo_actions::RepoActionsExt;
|
||||
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -50,8 +50,8 @@ pub(crate) fn get_base_branch_data(ctx: &CommandContext) -> Result<BaseBranch> {
|
||||
}
|
||||
|
||||
fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Result<BaseBranch> {
|
||||
let statuses = ctx
|
||||
.repository()
|
||||
let repo = ctx.repository();
|
||||
let statuses = repo
|
||||
.statuses(Some(
|
||||
git2::StatusOptions::new()
|
||||
.show(git2::StatusShow::IndexAndWorkdir)
|
||||
@ -67,41 +67,36 @@ fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Resu
|
||||
.list_branches_in_workspace()
|
||||
.context("failed to read virtual branches")?;
|
||||
|
||||
let target_commit = ctx
|
||||
.repository()
|
||||
let target_commit = repo
|
||||
.find_commit(default_target.sha)
|
||||
.context("failed to find target commit")?;
|
||||
|
||||
let base_tree = target_commit
|
||||
.tree()
|
||||
.context("failed to get base tree from commit")?;
|
||||
let mut final_tree = target_commit
|
||||
.tree()
|
||||
.context("failed to get base tree from commit")?;
|
||||
let base_tree = git2_to_gix_object_id(target_commit.tree_id());
|
||||
let mut final_tree_id = git2_to_gix_object_id(target_commit.tree_id());
|
||||
let gix_repo = ctx.gix_repository_for_merging()?;
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
|
||||
for branch in &virtual_branches {
|
||||
// merge this branches tree with our tree
|
||||
let branch_head = ctx
|
||||
.repository()
|
||||
.find_commit(branch.head())
|
||||
.context("failed to find branch head")?;
|
||||
let branch_tree = branch_head
|
||||
.tree()
|
||||
.context("failed to get branch head tree")?;
|
||||
let mut result = ctx
|
||||
.repository()
|
||||
.merge_trees(&base_tree, &final_tree, &branch_tree, None)
|
||||
.context("failed to merge")?;
|
||||
let final_tree_oid = result
|
||||
.write_tree_to(ctx.repository())
|
||||
.context("failed to write tree")?;
|
||||
final_tree = ctx
|
||||
.repository()
|
||||
.find_tree(final_tree_oid)
|
||||
.context("failed to find written tree")?;
|
||||
let branch_tree_id = git2_to_gix_object_id(
|
||||
repo.find_commit(branch.head())
|
||||
.context("failed to find branch head")?
|
||||
.tree_id(),
|
||||
);
|
||||
let mut merge = gix_repo.merge_trees(
|
||||
base_tree,
|
||||
final_tree_id,
|
||||
branch_tree_id,
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?;
|
||||
if merge.has_unresolved_conflicts(conflict_kind) {
|
||||
bail!("Merge failed with conflicts");
|
||||
}
|
||||
final_tree_id = merge.tree.write()?.detach();
|
||||
}
|
||||
|
||||
ctx.repository()
|
||||
.checkout_tree_builder(&final_tree)
|
||||
let final_tree = repo.find_tree(gix_to_git2_oid(final_tree_id))?;
|
||||
repo.checkout_tree_builder(&final_tree)
|
||||
.force()
|
||||
.checkout()
|
||||
.context("failed to checkout tree")?;
|
||||
|
@ -8,6 +8,7 @@ use gitbutler_error::error::Marker;
|
||||
use gitbutler_oplog::SnapshotExt;
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gitbutler_repo::GixRepositoryExt;
|
||||
use gitbutler_repo::{
|
||||
rebase::{cherry_rebase_group, gitbutler_merge_commits},
|
||||
LogUntil, RepositoryExt,
|
||||
@ -301,29 +302,27 @@ impl BranchManager<'_> {
|
||||
))?;
|
||||
|
||||
// Branch is out of date, merge or rebase it
|
||||
let merge_base_tree = repo
|
||||
let merge_base_tree_id = repo
|
||||
.find_commit(merge_base)
|
||||
.context(format!("failed to find merge base commit {}", merge_base))?
|
||||
.tree()
|
||||
.context("failed to find merge base tree")?;
|
||||
|
||||
let branch_tree = repo
|
||||
.find_tree(branch.tree)
|
||||
.context("failed to find branch tree")?;
|
||||
.context("failed to find merge base tree")?
|
||||
.id();
|
||||
let branch_tree_id = branch.tree;
|
||||
|
||||
// We don't support having two branches applied that conflict with each other
|
||||
{
|
||||
let uncommited_changes_tree = repo.create_wd_tree()?;
|
||||
let branch_merged_with_other_applied_branches = repo
|
||||
.merge_trees(
|
||||
&merge_base_tree,
|
||||
&branch_tree,
|
||||
&uncommited_changes_tree,
|
||||
None,
|
||||
let uncommited_changes_tree_id = repo.create_wd_tree()?.id();
|
||||
let gix_repo = self.ctx.gix_repository_for_merging_non_persisting()?;
|
||||
let merges_cleanly = gix_repo
|
||||
.merges_cleanly_compat(
|
||||
merge_base_tree_id,
|
||||
branch_tree_id,
|
||||
uncommited_changes_tree_id,
|
||||
)
|
||||
.context("failed to merge trees")?;
|
||||
|
||||
if branch_merged_with_other_applied_branches.has_conflicts() {
|
||||
if !merges_cleanly {
|
||||
for branch in vb_state
|
||||
.list_branches_in_workspace()?
|
||||
.iter()
|
||||
|
@ -5,8 +5,11 @@ use git2::Commit;
|
||||
use gitbutler_branch::BranchExt;
|
||||
use gitbutler_commit::commit_headers::CommitHeadersV2;
|
||||
use gitbutler_oplog::SnapshotExt;
|
||||
use gitbutler_oxidize::git2_to_gix_object_id;
|
||||
use gitbutler_oxidize::gix_to_git2_oid;
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_reference::{normalize_branch_name, ReferenceName, Refname};
|
||||
use gitbutler_repo::GixRepositoryExt;
|
||||
use gitbutler_repo::RepositoryExt;
|
||||
use gitbutler_repo::SignaturePurpose;
|
||||
use gitbutler_repo_actions::RepoActionsExt;
|
||||
@ -79,7 +82,10 @@ impl BranchManager<'_> {
|
||||
|
||||
let repo = self.ctx.repository();
|
||||
|
||||
let base_tree = target_commit.tree().context("failed to get target tree")?;
|
||||
let base_tree_id = target_commit
|
||||
.tree()
|
||||
.context("failed to get target tree")?
|
||||
.id();
|
||||
|
||||
let applied_statuses = get_applied_status(self.ctx, None)
|
||||
.context("failed to get status by branch")?
|
||||
@ -98,13 +104,14 @@ impl BranchManager<'_> {
|
||||
num_branches = applied_statuses.len() - 1
|
||||
)
|
||||
.entered();
|
||||
applied_statuses
|
||||
let gix_repo = self.ctx.gix_repository()?;
|
||||
let merge_options = gix_repo.tree_merge_options()?;
|
||||
let final_tree_id = applied_statuses
|
||||
.into_iter()
|
||||
.filter(|(branch, _)| branch.id != branch_id)
|
||||
.fold(
|
||||
target_commit.tree().context("failed to get target tree"),
|
||||
|final_tree, status| {
|
||||
let final_tree = final_tree?;
|
||||
.try_fold(
|
||||
git2_to_gix_object_id(target_commit.tree_id()),
|
||||
|final_tree_id, status| -> Result<_> {
|
||||
let branch = status.0;
|
||||
let files = status
|
||||
.1
|
||||
@ -113,14 +120,18 @@ impl BranchManager<'_> {
|
||||
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
|
||||
let tree_oid =
|
||||
gitbutler_diff::write::hunks_onto_oid(self.ctx, branch.head(), files)?;
|
||||
let branch_tree = repo.find_tree(tree_oid)?;
|
||||
let mut result =
|
||||
repo.merge_trees(&base_tree, &final_tree, &branch_tree, None)?;
|
||||
let final_tree_oid = result.write_tree_to(repo)?;
|
||||
repo.find_tree(final_tree_oid)
|
||||
.context("failed to find tree")
|
||||
let mut merge = gix_repo.merge_trees(
|
||||
git2_to_gix_object_id(base_tree_id),
|
||||
final_tree_id,
|
||||
git2_to_gix_object_id(tree_oid),
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options.clone(),
|
||||
)?;
|
||||
let final_tree_id = merge.tree.write()?.detach();
|
||||
Ok(final_tree_id)
|
||||
},
|
||||
)?
|
||||
)?;
|
||||
repo.find_tree(gix_to_git2_oid(final_tree_id))?
|
||||
};
|
||||
|
||||
let _span = tracing::debug_span!("checkout final tree").entered();
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::VirtualBranchesExt as _;
|
||||
use anyhow::{bail, Result};
|
||||
use gitbutler_cherry_pick::RepositoryExt;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_commit::commit_ext::CommitExt as _;
|
||||
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_repo::rebase::cherry_rebase_group;
|
||||
use gitbutler_repo::RepositoryExt as _;
|
||||
use gitbutler_repo::{GixRepositoryExt, RepositoryExt as _};
|
||||
use gitbutler_stack::Stack;
|
||||
|
||||
use crate::VirtualBranchesExt as _;
|
||||
use tracing::instrument;
|
||||
|
||||
/// Checks out the combined trees of all branches in the workspace.
|
||||
///
|
||||
/// This function will fail if the applied branches conflict with each other.
|
||||
#[instrument(level = tracing::Level::DEBUG, skip(ctx, _perm), err(Debug))]
|
||||
pub fn checkout_branch_trees<'a>(
|
||||
ctx: &'a CommandContext,
|
||||
_perm: &mut WorktreeWritePermission,
|
||||
@ -38,23 +40,30 @@ pub fn checkout_branch_trees<'a>(
|
||||
let merge_base = repository
|
||||
.merge_base_octopussy(&branches.iter().map(|b| b.head()).collect::<Vec<_>>())?;
|
||||
|
||||
let merge_base_tree = repository.find_commit(merge_base)?.tree()?;
|
||||
|
||||
let mut final_tree = merge_base_tree.clone();
|
||||
let gix_repo = ctx.gix_repository_for_merging()?;
|
||||
let merge_base_tree_id =
|
||||
git2_to_gix_object_id(repository.find_commit(merge_base)?.tree_id());
|
||||
let mut final_tree_id = merge_base_tree_id;
|
||||
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
|
||||
for branch in branches {
|
||||
let theirs = repository.find_tree(branch.tree)?;
|
||||
let mut merge_index =
|
||||
repository.merge_trees(&merge_base_tree, &final_tree, &theirs, None)?;
|
||||
let their_tree_id = git2_to_gix_object_id(branch.tree);
|
||||
let mut merge = gix_repo.merge_trees(
|
||||
merge_base_tree_id,
|
||||
final_tree_id,
|
||||
their_tree_id,
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?;
|
||||
|
||||
if merge_index.has_conflicts() {
|
||||
if merge.has_unresolved_conflicts(conflict_kind) {
|
||||
bail!("There appears to be conflicts between the virtual branches");
|
||||
};
|
||||
|
||||
let tree_oid = merge_index.write_tree_to(repository)?;
|
||||
final_tree = repository.find_tree(tree_oid)?;
|
||||
final_tree_id = merge.tree.write()?.detach();
|
||||
}
|
||||
|
||||
let final_tree = repository.find_tree(gix_to_git2_oid(final_tree_id))?;
|
||||
repository
|
||||
.checkout_tree_builder(&final_tree)
|
||||
.force()
|
||||
|
@ -9,8 +9,9 @@ use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_commit::commit_ext::CommitExt;
|
||||
use gitbutler_error::error::Marker;
|
||||
use gitbutler_operating_modes::OPEN_WORKSPACE_REFS;
|
||||
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_repo::SignaturePurpose;
|
||||
use gitbutler_repo::{GixRepositoryExt, SignaturePurpose};
|
||||
use gitbutler_repo::{LogUntil, RepositoryExt};
|
||||
use gitbutler_stack::{Stack, VirtualBranchesHandle};
|
||||
use tracing::instrument;
|
||||
@ -41,6 +42,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
|
||||
|
||||
let target_commit = repo.find_commit(target.sha)?;
|
||||
let mut workspace_tree = repo.find_real_tree(&target_commit, Default::default())?;
|
||||
let mut workspace_tree_id = git2_to_gix_object_id(workspace_tree.id());
|
||||
|
||||
if conflicts::is_conflicting(ctx, None)? {
|
||||
let merge_parent = conflicts::merge_parent(ctx)?.ok_or(anyhow!("No merge parent"))?;
|
||||
@ -49,15 +51,24 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
|
||||
let merge_base = repo.merge_base(first_branch.head(), merge_parent)?;
|
||||
workspace_tree = repo.find_commit(merge_base)?.tree()?;
|
||||
} else {
|
||||
let gix_repo = ctx.gix_repository_for_merging()?;
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
|
||||
let merge_tree_id = git2_to_gix_object_id(repo.find_commit(target.sha)?.tree_id());
|
||||
for branch in virtual_branches.iter_mut() {
|
||||
let merge_tree = repo.find_commit(target.sha)?.tree()?;
|
||||
let branch_tree = repo.find_commit(branch.head())?;
|
||||
let branch_tree = repo.find_real_tree(&branch_tree, Default::default())?;
|
||||
let branch_head = repo.find_commit(branch.head())?;
|
||||
let branch_tree_id =
|
||||
git2_to_gix_object_id(repo.find_real_tree(&branch_head, Default::default())?.id());
|
||||
|
||||
let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
|
||||
let mut merge = gix_repo.merge_trees(
|
||||
merge_tree_id,
|
||||
workspace_tree_id,
|
||||
branch_tree_id,
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?;
|
||||
|
||||
if !index.has_conflicts() {
|
||||
workspace_tree = repo.find_tree(index.write_tree_to(repo)?)?;
|
||||
if !merge.has_unresolved_conflicts(conflict_kind) {
|
||||
workspace_tree_id = merge.tree.write()?.detach();
|
||||
} else {
|
||||
// This branch should have already been unapplied during the "update" command but for some reason that failed
|
||||
tracing::warn!("Merge conflict between base and {:?}", branch.name);
|
||||
@ -65,6 +76,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
|
||||
vb_state.set_branch(branch.clone())?;
|
||||
}
|
||||
}
|
||||
workspace_tree = repo.find_tree(gix_to_git2_oid(workspace_tree_id))?;
|
||||
}
|
||||
|
||||
let committer = gitbutler_repo::signature(SignaturePurpose::Committer)?;
|
||||
|
@ -7,7 +7,6 @@ use gitbutler_oplog::entry::{OperationKind, SnapshotDetails};
|
||||
use gitbutler_oplog::{OplogExt, SnapshotExt};
|
||||
use gitbutler_project::Project;
|
||||
use gitbutler_reference::normalize_branch_name;
|
||||
use gitbutler_repo::GixRepositoryExt;
|
||||
use gitbutler_repo_actions::RepoActionsExt;
|
||||
use gitbutler_stack::{Branch, CommitOrChangeId, ForgeIdentifier, PatchReferenceUpdate, Series};
|
||||
use gitbutler_stack::{Stack, StackId, Target};
|
||||
@ -190,10 +189,7 @@ pub fn push_stack(project: &Project, branch_id: StackId, with_force: bool) -> Re
|
||||
|
||||
// First fetch, because we dont want to push integrated series
|
||||
ctx.fetch(&default_target.push_remote_name(), None)?;
|
||||
let gix_repo = ctx
|
||||
.gix_repository()?
|
||||
.for_tree_diffing()?
|
||||
.with_object_memory();
|
||||
let gix_repo = ctx.gix_repository_for_merging_non_persisting()?;
|
||||
let cache = gix_repo.commit_graph_if_enabled()?;
|
||||
let mut graph = gix_repo.revision_graph(cache.as_ref());
|
||||
let mut check_commit = IsCommitIntegrated::new(ctx, &default_target, &gix_repo, &mut graph)?;
|
||||
|
@ -1,19 +1,19 @@
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use gitbutler_cherry_pick::RepositoryExt as _;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_repo::{
|
||||
rebase::{cherry_rebase_group, gitbutler_merge_commits},
|
||||
LogUntil, RepositoryExt as _,
|
||||
};
|
||||
use gitbutler_repo_actions::RepoActionsExt as _;
|
||||
use gitbutler_stack::{Stack, StackId, Target, VirtualBranchesHandle};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
branch_trees::{checkout_branch_trees, compute_updated_branch_head, BranchHeadAndTree},
|
||||
BranchManagerExt, VirtualBranchesExt as _,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use gitbutler_cherry_pick::RepositoryExt as _;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_oxidize::git2_to_gix_object_id;
|
||||
use gitbutler_project::access::WorktreeWritePermission;
|
||||
use gitbutler_repo::{
|
||||
rebase::{cherry_rebase_group, gitbutler_merge_commits},
|
||||
GixRepositoryExt, LogUntil, RepositoryExt as _,
|
||||
};
|
||||
use gitbutler_repo_actions::RepoActionsExt as _;
|
||||
use gitbutler_stack::{Stack, StackId, Target, VirtualBranchesHandle};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, PartialEq, Debug)]
|
||||
#[serde(tag = "type", content = "subject", rename_all = "camelCase")]
|
||||
@ -41,9 +41,9 @@ pub enum BaseBranchResolutionApproach {
|
||||
HardReset,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
#[serde(tag = "type", content = "subject", rename_all = "camelCase")]
|
||||
enum ResolutionApproach {
|
||||
pub enum ResolutionApproach {
|
||||
Rebase,
|
||||
Merge,
|
||||
Unapply,
|
||||
@ -75,11 +75,11 @@ impl BranchStatus {
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Resolution {
|
||||
branch_id: StackId,
|
||||
pub branch_id: StackId,
|
||||
/// Used to ensure a given branch hasn't changed since the UI issued the command.
|
||||
#[serde(with = "gitbutler_serde::oid")]
|
||||
branch_tree: git2::Oid,
|
||||
approach: ResolutionApproach,
|
||||
pub branch_tree: git2::Oid,
|
||||
pub approach: ResolutionApproach,
|
||||
}
|
||||
|
||||
enum IntegrationResult {
|
||||
@ -140,8 +140,19 @@ pub fn upstream_integration_statuses(
|
||||
..
|
||||
} = context;
|
||||
// look up the target and see if there is a new oid
|
||||
let old_target_tree = repository.find_real_tree(old_target, Default::default())?;
|
||||
let new_target_tree = repository.find_real_tree(new_target, Default::default())?;
|
||||
let old_target_tree_id = git2_to_gix_object_id(
|
||||
repository
|
||||
.find_real_tree(old_target, Default::default())?
|
||||
.id(),
|
||||
);
|
||||
let new_target_tree_id = git2_to_gix_object_id(
|
||||
repository
|
||||
.find_real_tree(new_target, Default::default())?
|
||||
.id(),
|
||||
);
|
||||
let gix_repo = gitbutler_command_context::gix_repository_for_merging(repository.path())?;
|
||||
let gix_repo_in_memory = gix_repo.clone().with_object_memory();
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
|
||||
|
||||
if new_target.id() == old_target.id() {
|
||||
return Ok(BranchStatuses::UpToDate);
|
||||
@ -151,8 +162,10 @@ pub fn upstream_integration_statuses(
|
||||
.iter()
|
||||
.map(|virtual_branch| {
|
||||
let tree = repository.find_tree(virtual_branch.tree)?;
|
||||
let tree_id = git2_to_gix_object_id(tree.id());
|
||||
let head = repository.find_commit(virtual_branch.head())?;
|
||||
let head_tree = repository.find_real_tree(&head, Default::default())?;
|
||||
let head_tree_id = git2_to_gix_object_id(head_tree.id());
|
||||
|
||||
// Try cherry pick the branch's head commit onto the target to
|
||||
// see if it conflics. This is equivalent to doing a merge
|
||||
@ -168,25 +181,33 @@ pub fn upstream_integration_statuses(
|
||||
};
|
||||
}
|
||||
|
||||
let head_merge_index =
|
||||
repository.merge_trees(&old_target_tree, &new_target_tree, &head_tree, None)?;
|
||||
let mut tree_merge_index =
|
||||
repository.merge_trees(&old_target_tree, &new_target_tree, &tree, None)?;
|
||||
let mut tree_merge = gix_repo.merge_trees(
|
||||
old_target_tree_id,
|
||||
new_target_tree_id,
|
||||
tree_id,
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?;
|
||||
|
||||
// Is the branch conflicted?
|
||||
// A branch can't be integrated if its conflicted
|
||||
{
|
||||
let commits_conflicted = head_merge_index.has_conflicts();
|
||||
let commits_conflicted = gix_repo_in_memory
|
||||
.merge_trees(
|
||||
old_target_tree_id,
|
||||
new_target_tree_id,
|
||||
head_tree_id,
|
||||
Default::default(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?
|
||||
.has_unresolved_conflicts(conflict_kind);
|
||||
gix_repo_in_memory.objects.reset_object_memory();
|
||||
|
||||
// See whether uncommited changes are potentially conflicted
|
||||
let potentially_conflicted_uncommited_changes = if has_uncommited_changes {
|
||||
// If the commits are conflicted, we can guarentee that the
|
||||
// tree will be conflicted.
|
||||
if commits_conflicted {
|
||||
true
|
||||
} else {
|
||||
tree_merge_index.has_conflicts()
|
||||
}
|
||||
commits_conflicted || tree_merge.has_unresolved_conflicts(conflict_kind)
|
||||
} else {
|
||||
// If there are no uncommited changes, then there can't be
|
||||
// any conflicts.
|
||||
@ -205,13 +226,18 @@ pub fn upstream_integration_statuses(
|
||||
|
||||
// Is the branch fully integrated?
|
||||
{
|
||||
if tree_merge.has_unresolved_conflicts(conflict_kind) {
|
||||
bail!(
|
||||
"Merge result unexpectedly has conflicts between base, \
|
||||
ours, theirs: {old_target_tree_id}, {new_target_tree_id}, {tree_id}"
|
||||
)
|
||||
}
|
||||
// We're safe to write the tree as we've ensured it's
|
||||
// unconflicted in the previous test.
|
||||
let tree_merge_index_tree = tree_merge_index.write_tree_to(repository)?;
|
||||
let tree_merge_index_tree_id = tree_merge.tree.write()?.detach();
|
||||
|
||||
// Identical trees will have the same Oid so we can compare
|
||||
// the two
|
||||
if tree_merge_index_tree == new_target_tree.id() {
|
||||
// Identical trees will have the same Oid so we can compare the two
|
||||
if tree_merge_index_tree_id == new_target_tree_id {
|
||||
return Ok((virtual_branch.id, BranchStatus::FullyIntegrated));
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ use gitbutler_stack::{
|
||||
VirtualBranchesHandle,
|
||||
};
|
||||
use gitbutler_time::time::now_since_unix_epoch_ms;
|
||||
use gix::objs::Write;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashMap, path::PathBuf, vec};
|
||||
@ -180,25 +179,34 @@ pub fn unapply_ownership(
|
||||
.find_commit(workspace_commit_id)
|
||||
.context("failed to find target commit")?;
|
||||
|
||||
let base_tree = target_commit.tree().context("failed to get target tree")?;
|
||||
let final_tree = applied_statuses.into_iter().fold(
|
||||
target_commit.tree().context("failed to get target tree"),
|
||||
|final_tree, status| {
|
||||
let final_tree = final_tree?;
|
||||
let base_tree_id = git2_to_gix_object_id(target_commit.tree_id());
|
||||
let gix_repo = ctx.gix_repository_for_merging()?;
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
|
||||
let final_tree_id = applied_statuses.into_iter().try_fold(
|
||||
git2_to_gix_object_id(target_commit.tree_id()),
|
||||
|final_tree_id, status| -> Result<_> {
|
||||
let files = status
|
||||
.1
|
||||
.into_iter()
|
||||
.map(|file| (file.path, file.hunks))
|
||||
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
|
||||
let tree_oid = gitbutler_diff::write::hunks_onto_oid(ctx, workspace_commit_id, files)?;
|
||||
let branch_tree = repo.find_tree(tree_oid)?;
|
||||
let mut result = repo.merge_trees(&base_tree, &final_tree, &branch_tree, None)?;
|
||||
let final_tree_oid = result.write_tree_to(ctx.repository())?;
|
||||
repo.find_tree(final_tree_oid)
|
||||
.context("failed to find tree")
|
||||
let branch_tree_id =
|
||||
gitbutler_diff::write::hunks_onto_oid(ctx, workspace_commit_id, files)?;
|
||||
let mut merge = gix_repo.merge_trees(
|
||||
base_tree_id,
|
||||
final_tree_id,
|
||||
git2_to_gix_object_id(branch_tree_id),
|
||||
gix_repo.default_merge_labels(),
|
||||
merge_options_fail_fast.clone(),
|
||||
)?;
|
||||
if merge.has_unresolved_conflicts(conflict_kind) {
|
||||
bail!("Tree has conflicts after merge")
|
||||
}
|
||||
Ok(merge.tree.write()?.detach())
|
||||
},
|
||||
)?;
|
||||
|
||||
let final_tree = repo.find_tree(gix_to_git2_oid(final_tree_id))?;
|
||||
let final_tree_oid = gitbutler_diff::write::hunks_onto_tree(ctx, &final_tree, diff, true)?;
|
||||
let final_tree = repo
|
||||
.find_tree(final_tree_oid)
|
||||
@ -302,10 +310,7 @@ pub fn list_virtual_branches_cached(
|
||||
let branches_span =
|
||||
tracing::debug_span!("handle branches", num_branches = status.branches.len()).entered();
|
||||
let repo = ctx.repository();
|
||||
let gix_repo = ctx
|
||||
.gix_repository()?
|
||||
.for_tree_diffing()?
|
||||
.with_object_memory();
|
||||
let gix_repo = ctx.gix_repository_for_merging_non_persisting()?;
|
||||
// We will perform virtual merges, no need to write them to the ODB.
|
||||
let cache = gix_repo.commit_graph_if_enabled()?;
|
||||
let mut graph = gix_repo.revision_graph(cache.as_ref());
|
||||
@ -1038,9 +1043,7 @@ impl IsCommitIntegrated<'_, '_, '_> {
|
||||
}
|
||||
|
||||
// try to merge our tree into the upstream tree
|
||||
let mut merge_options = self.gix_repo.tree_merge_options()?;
|
||||
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
|
||||
merge_options.fail_on_conflict = Some(conflict_kind);
|
||||
let (merge_options, conflict_kind) = self.gix_repo.merge_options_fail_fast()?;
|
||||
let mut merge_output = self
|
||||
.gix_repo
|
||||
.merge_trees(
|
||||
@ -1056,10 +1059,7 @@ impl IsCommitIntegrated<'_, '_, '_> {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let merge_tree_id = merge_output
|
||||
.tree
|
||||
.write(|tree| self.gix_repo.write(tree))
|
||||
.map_err(|err| anyhow!("failed to write tree: {err}"))?;
|
||||
let merge_tree_id = merge_output.tree.write()?.detach();
|
||||
|
||||
// if the merge_tree is the same as the new_target_tree and there are no files (uncommitted changes)
|
||||
// then the vbranch is fully merged
|
||||
@ -1094,11 +1094,18 @@ pub fn is_remote_branch_mergeable(
|
||||
let wd_tree = ctx.repository().create_wd_tree()?;
|
||||
|
||||
let branch_tree = branch_commit.tree().context("failed to find branch tree")?;
|
||||
let mergeable = !ctx
|
||||
.repository()
|
||||
.merge_trees(&base_tree, &branch_tree, &wd_tree, None)
|
||||
let gix_repo_in_memory = ctx.gix_repository_for_merging()?.with_object_memory();
|
||||
let (merge_options_fail_fast, conflict_kind) = gix_repo_in_memory.merge_options_fail_fast()?;
|
||||
let mergeable = !gix_repo_in_memory
|
||||
.merge_trees(
|
||||
git2_to_gix_object_id(base_tree.id()),
|
||||
git2_to_gix_object_id(branch_tree.id()),
|
||||
git2_to_gix_object_id(wd_tree.id()),
|
||||
Default::default(),
|
||||
merge_options_fail_fast,
|
||||
)
|
||||
.context("failed to merge trees")?
|
||||
.has_conflicts();
|
||||
.has_unresolved_conflicts(conflict_kind);
|
||||
|
||||
Ok(mergeable)
|
||||
}
|
||||
|
@ -14,8 +14,31 @@ pub struct Args {
|
||||
pub cmd: Subcommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||
pub enum UpdateMode {
|
||||
Rebase,
|
||||
Merge,
|
||||
Unapply,
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Subcommands {
|
||||
/// Unapply the given ownership claim.
|
||||
UnapplyOwnership {
|
||||
/// The path to remove the claim from.
|
||||
filepath: PathBuf,
|
||||
/// The first line of hunks that should be removed.
|
||||
from_line: u32,
|
||||
/// The last line of hunks that should be removed.
|
||||
to_line: u32,
|
||||
},
|
||||
/// Update the local workspace against an updated remote or target branch.
|
||||
IntegrateUpstream {
|
||||
/// Specify how all branches should be merged in.
|
||||
#[clap(value_enum)]
|
||||
mode: UpdateMode,
|
||||
},
|
||||
/// List and manipulate virtual branches.
|
||||
#[clap(visible_alias = "branches")]
|
||||
Branch(vbranch::Platform),
|
||||
@ -42,6 +65,11 @@ pub mod vbranch {
|
||||
ListLocal,
|
||||
/// Provide the current state of all applied virtual branches.
|
||||
Status,
|
||||
/// Switch to the GitButler workspace.
|
||||
SetBase {
|
||||
/// The name of the remote branch to integrate with, like `origin/main`.
|
||||
short_tracking_branch_name: String,
|
||||
},
|
||||
/// Make the named branch the default so all worktree or index changes are associated with it automatically.
|
||||
SetDefault {
|
||||
/// The name of the new default virtual branch.
|
||||
@ -52,6 +80,11 @@ pub mod vbranch {
|
||||
/// The name of the virtual branch to unapply.
|
||||
name: String,
|
||||
},
|
||||
/// Add a branch to the workspace.
|
||||
Apply {
|
||||
/// The name of the virtual branch to apply.
|
||||
name: String,
|
||||
},
|
||||
/// Create a new commit to named virtual branch with all changes currently in the worktree or staging area assigned to it.
|
||||
Commit {
|
||||
/// The commit message
|
||||
@ -141,6 +174,11 @@ pub mod snapshot {
|
||||
/// The snapshot to restore
|
||||
snapshot_id: String,
|
||||
},
|
||||
/// Show what is stored in a given snapshot.
|
||||
Diff {
|
||||
/// The hex-hash of the commit-id of the snapshot.
|
||||
snapshot_id: String,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
pub mod prepare;
|
||||
pub mod project;
|
||||
pub mod vbranch;
|
||||
|
||||
pub mod snapshot {
|
||||
use crate::command::debug_print;
|
||||
use anyhow::Result;
|
||||
use gitbutler_oplog::OplogExt;
|
||||
use gitbutler_project::Project;
|
||||
@ -24,9 +24,64 @@ pub mod snapshot {
|
||||
project.restore_snapshot(snapshot_id.parse()?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn diff(project: Project, snapshot_id: String) -> Result<()> {
|
||||
debug_print(project.snapshot_diff(snapshot_id.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_print(this: impl std::fmt::Debug) -> anyhow::Result<()> {
|
||||
println!("{:#?}", this);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub mod ownership {
|
||||
use gitbutler_diff::Hunk;
|
||||
use gitbutler_project::Project;
|
||||
use gitbutler_stack::{BranchOwnershipClaims, OwnershipClaim};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn unapply(
|
||||
project: Project,
|
||||
file_path: PathBuf,
|
||||
from_line: u32,
|
||||
to_line: u32,
|
||||
) -> anyhow::Result<()> {
|
||||
let claims = BranchOwnershipClaims {
|
||||
claims: vec![OwnershipClaim {
|
||||
file_path,
|
||||
hunks: vec![Hunk {
|
||||
hash: None,
|
||||
start: from_line,
|
||||
end: to_line,
|
||||
}],
|
||||
}],
|
||||
};
|
||||
gitbutler_branch_actions::unapply_ownership(&project, &claims)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod workspace {
|
||||
use crate::args::UpdateMode;
|
||||
use gitbutler_branch_actions::upstream_integration;
|
||||
use gitbutler_project::Project;
|
||||
|
||||
pub fn update(project: Project, mode: UpdateMode) -> anyhow::Result<()> {
|
||||
let approach = match mode {
|
||||
UpdateMode::Rebase => upstream_integration::ResolutionApproach::Rebase,
|
||||
UpdateMode::Merge => upstream_integration::ResolutionApproach::Merge,
|
||||
UpdateMode::Unapply => upstream_integration::ResolutionApproach::Unapply,
|
||||
UpdateMode::Delete => upstream_integration::ResolutionApproach::Delete,
|
||||
};
|
||||
let resolutions: Vec<_> = gitbutler_branch_actions::list_virtual_branches(&project)?
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|b| upstream_integration::Resolution {
|
||||
branch_id: b.id,
|
||||
branch_tree: b.tree,
|
||||
approach,
|
||||
})
|
||||
.collect();
|
||||
gitbutler_branch_actions::integrate_upstream(&project, &resolutions, None)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,22 @@
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use gitbutler_branch::{BranchCreateRequest, BranchIdentity, BranchUpdateRequest};
|
||||
use gitbutler_branch_actions::{get_branch_listing_details, list_branches};
|
||||
use gitbutler_branch_actions::{get_branch_listing_details, list_branches, BranchManagerExt};
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_project::Project;
|
||||
use gitbutler_stack::{Stack, VirtualBranchesHandle};
|
||||
|
||||
use crate::command::debug_print;
|
||||
|
||||
pub fn set_base(project: Project, short_tracking_branch_name: String) -> Result<()> {
|
||||
let branch_name = format!("refs/remotes/{}", short_tracking_branch_name)
|
||||
.parse()
|
||||
.context("Invalid branch name")?;
|
||||
debug_print(gitbutler_branch_actions::set_base_branch(
|
||||
&project,
|
||||
&branch_name,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn list_all(project: Project) -> Result<()> {
|
||||
let ctx = CommandContext::open(&project)?;
|
||||
debug_print(list_branches(&ctx, None, None)?)
|
||||
@ -53,6 +63,23 @@ pub fn unapply(project: Project, branch_name: String) -> Result<()> {
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn apply(project: Project, branch_name: String) -> Result<()> {
|
||||
let branch = branch_by_name(&project, &branch_name)?;
|
||||
let ctx = CommandContext::open(&project)?;
|
||||
let mut guard = project.exclusive_worktree_access();
|
||||
debug_print(
|
||||
ctx.branch_manager().create_virtual_branch_from_branch(
|
||||
branch
|
||||
.source_refname
|
||||
.as_ref()
|
||||
.context("local reference name was missing")?,
|
||||
None,
|
||||
None,
|
||||
guard.write_permission(),
|
||||
)?,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create(project: Project, branch_name: String, set_default: bool) -> Result<()> {
|
||||
let new = gitbutler_branch_actions::create_virtual_branch(
|
||||
&project,
|
||||
|
@ -17,14 +17,32 @@ fn main() -> Result<()> {
|
||||
let _op_span = tracing::info_span!("cli-op").entered();
|
||||
|
||||
match args.cmd {
|
||||
args::Subcommands::IntegrateUpstream { mode } => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
command::workspace::update(project, mode)
|
||||
}
|
||||
args::Subcommands::UnapplyOwnership {
|
||||
filepath,
|
||||
from_line,
|
||||
to_line,
|
||||
} => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
command::ownership::unapply(project, filepath, from_line, to_line)
|
||||
}
|
||||
args::Subcommands::Branch(vbranch::Platform { cmd }) => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
match cmd {
|
||||
Some(vbranch::SubCommands::SetBase {
|
||||
short_tracking_branch_name,
|
||||
}) => command::vbranch::set_base(project, short_tracking_branch_name),
|
||||
Some(vbranch::SubCommands::ListLocal) => command::vbranch::list_local(project),
|
||||
Some(vbranch::SubCommands::Status) => command::vbranch::status(project),
|
||||
Some(vbranch::SubCommands::Unapply { name }) => {
|
||||
command::vbranch::unapply(project, name)
|
||||
}
|
||||
Some(vbranch::SubCommands::Apply { name }) => {
|
||||
command::vbranch::apply(project, name)
|
||||
}
|
||||
Some(vbranch::SubCommands::SetDefault { name }) => {
|
||||
command::vbranch::set_default(project, name)
|
||||
}
|
||||
@ -71,6 +89,9 @@ fn main() -> Result<()> {
|
||||
Some(snapshot::SubCommands::Restore { snapshot_id }) => {
|
||||
command::snapshot::restore(project, snapshot_id)
|
||||
}
|
||||
Some(snapshot::SubCommands::Diff { snapshot_id }) => {
|
||||
command::snapshot::diff(project, snapshot_id)
|
||||
}
|
||||
None => command::snapshot::list(project),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use gitbutler_project::Project;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct CommandContext {
|
||||
/// The git repository of the `project` itself.
|
||||
@ -86,6 +87,23 @@ impl CommandContext {
|
||||
Ok(gix::open(self.repository().path())?)
|
||||
}
|
||||
|
||||
/// Return a newly opened `gitoxide` repository, with all configuration available
|
||||
/// to correctly figure out author and committer names (i.e. with most global configuration loaded),
|
||||
/// *and* which will perform diffs quickly thanks to an adequate object cache.
|
||||
pub fn gix_repository_for_merging(&self) -> Result<gix::Repository> {
|
||||
gix_repository_for_merging(self.repository().path())
|
||||
}
|
||||
|
||||
/// Return a newly opened `gitoxide` repository, with all configuration available
|
||||
/// to correctly figure out author and committer names (i.e. with most global configuration loaded),
|
||||
/// *and* which will perform diffs quickly thanks to an adequate object cache, *and*
|
||||
/// which **writes all objects into memory**.
|
||||
///
|
||||
/// This means *changes are non-persisting*.
|
||||
pub fn gix_repository_for_merging_non_persisting(&self) -> Result<gix::Repository> {
|
||||
Ok(self.gix_repository_for_merging()?.with_object_memory())
|
||||
}
|
||||
|
||||
/// Return a newly opened `gitoxide` repository with only the repository-local configuration
|
||||
/// available. This is a little faster as it has to open less files upon startup.
|
||||
///
|
||||
@ -99,5 +117,15 @@ impl CommandContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a newly opened `gitoxide` repository, with all configuration available
|
||||
/// to correctly figure out author and committer names (i.e. with most global configuration loaded),
|
||||
/// *and* which will perform diffs quickly thanks to an adequate object cache.
|
||||
pub fn gix_repository_for_merging(worktree_or_git_dir: &Path) -> Result<gix::Repository> {
|
||||
let mut repo = gix::open(worktree_or_git_dir)?;
|
||||
let bytes = repo.compute_object_cache_size_for_tree_diffs(&***repo.index_or_empty()?);
|
||||
repo.object_cache_size_if_unset(bytes);
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
mod repository_ext;
|
||||
pub use repository_ext::RepositoryExtLite;
|
||||
|
@ -17,6 +17,7 @@ gix = { workspace = true, features = ["dirwalk", "credentials", "parallel"] }
|
||||
toml.workspace = true
|
||||
gitbutler-command-context.workspace = true
|
||||
gitbutler-project.workspace = true
|
||||
gitbutler-oxidize.workspace = true
|
||||
gitbutler-branch.workspace = true
|
||||
gitbutler-serde.workspace = true
|
||||
gitbutler-fs.workspace = true
|
||||
|
@ -6,24 +6,27 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use git2::{DiffOptions, FileMode};
|
||||
use gitbutler_command_context::RepositoryExtLite;
|
||||
use gitbutler_diff::{hunks_by_filepath, FileDiff};
|
||||
use gitbutler_project::{
|
||||
access::{WorktreeReadPermission, WorktreeWritePermission},
|
||||
Project,
|
||||
};
|
||||
use gitbutler_repo::RepositoryExt;
|
||||
use gitbutler_repo::SignaturePurpose;
|
||||
use gitbutler_stack::{Stack, VirtualBranchesHandle, VirtualBranchesState};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::{
|
||||
entry::{OperationKind, Snapshot, SnapshotDetails, Trailer},
|
||||
reflog::set_reference_to_oplog,
|
||||
state::OplogHandle,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use git2::FileMode;
|
||||
use gitbutler_command_context::RepositoryExtLite;
|
||||
use gitbutler_diff::{hunks_by_filepath, FileDiff};
|
||||
use gitbutler_oxidize::{git2_to_gix_object_id, gix_time_to_git2, gix_to_git2_oid};
|
||||
use gitbutler_project::{
|
||||
access::{WorktreeReadPermission, WorktreeWritePermission},
|
||||
Project,
|
||||
};
|
||||
use gitbutler_repo::SignaturePurpose;
|
||||
use gitbutler_repo::{GixRepositoryExt, RepositoryExt};
|
||||
use gitbutler_stack::{Stack, VirtualBranchesHandle, VirtualBranchesState};
|
||||
use gix::bstr::ByteSlice;
|
||||
use gix::object::tree::diff::Change;
|
||||
use gix::prelude::ObjectIdExt;
|
||||
use tracing::instrument;
|
||||
|
||||
const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024;
|
||||
|
||||
@ -165,10 +168,10 @@ impl OplogExt for Project {
|
||||
limit: usize,
|
||||
oplog_commit_id: Option<git2::Oid>,
|
||||
) -> Result<Vec<Snapshot>> {
|
||||
let repo_path = self.path.as_path();
|
||||
let repo = git2::Repository::open(repo_path)?;
|
||||
let worktree_dir = self.path.as_path();
|
||||
let repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
|
||||
|
||||
let traversal_root_id = match oplog_commit_id {
|
||||
let traversal_root_id = git2_to_gix_object_id(match oplog_commit_id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
let oplog_state = OplogHandle::new(&self.gb_dir());
|
||||
@ -178,30 +181,29 @@ impl OplogExt for Project {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let oplog_head_commit = repo.find_commit(traversal_root_id)?;
|
||||
|
||||
let mut revwalk = repo.revwalk()?;
|
||||
revwalk.push(oplog_head_commit.id())?;
|
||||
})
|
||||
.attach(&repo);
|
||||
|
||||
let mut snapshots = Vec::new();
|
||||
let mut wd_trees_cache: HashMap<gix::ObjectId, gix::ObjectId> = HashMap::new();
|
||||
|
||||
let mut wd_trees_cache: HashMap<git2::Oid, git2::Oid> = HashMap::new();
|
||||
|
||||
for commit_id in revwalk {
|
||||
for commit_info in traversal_root_id.ancestors().all()? {
|
||||
if snapshots.len() == limit {
|
||||
break;
|
||||
}
|
||||
let commit_id = commit_id?;
|
||||
let commit = repo.find_commit(commit_id)?;
|
||||
|
||||
if commit.parent_count() > 1 {
|
||||
let commit_id = commit_info?.id();
|
||||
let commit = commit_id.object()?.into_commit();
|
||||
let mut parents = commit.parent_ids();
|
||||
let (first_parent, second_parent) = (parents.next(), parents.next());
|
||||
if second_parent.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
let tree = commit.tree()?;
|
||||
if tree.get_name("virtual_branches.toml").is_none() {
|
||||
if tree
|
||||
.lookup_entry_by_path("virtual_branches.toml")?
|
||||
.is_none()
|
||||
{
|
||||
// We reached a tree that is not a snapshot
|
||||
tracing::warn!("Commit {commit_id} didn't seem to be an oplog commit - skipping");
|
||||
continue;
|
||||
@ -210,36 +212,55 @@ impl OplogExt for Project {
|
||||
// Get tree id from cache or calculate it
|
||||
let wd_tree = get_workdir_tree(&mut wd_trees_cache, commit_id, &repo)?;
|
||||
|
||||
let commit_id = gix_to_git2_oid(commit_id);
|
||||
let details = commit
|
||||
.message()
|
||||
.message_raw()?
|
||||
.to_str()
|
||||
.ok()
|
||||
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
|
||||
let commit_time = gix_time_to_git2(commit.time()?);
|
||||
|
||||
if let Ok(parent) = commit.parent(0) {
|
||||
if let Some(parent_id) = first_parent {
|
||||
// Get tree id from cache or calculate it
|
||||
let parent_tree = get_workdir_tree(&mut wd_trees_cache, parent.id(), &repo)?;
|
||||
|
||||
let mut opts = DiffOptions::new();
|
||||
opts.include_untracked(true);
|
||||
opts.ignore_submodules(true);
|
||||
let diff =
|
||||
repo.diff_tree_to_tree(Some(&parent_tree), Some(&wd_tree), Some(&mut opts))?;
|
||||
|
||||
let mut files_changed = Vec::new();
|
||||
diff.print(git2::DiffFormat::NameOnly, |delta, _, _| {
|
||||
if let Some(path) = delta.new_file().path() {
|
||||
files_changed.push(path.to_path_buf());
|
||||
}
|
||||
true
|
||||
})?;
|
||||
let mut resource_cache = repo.diff_resource_cache_for_tree_diff()?;
|
||||
let (mut lines_added, mut lines_removed) = (0, 0);
|
||||
let parent_tree = get_workdir_tree(&mut wd_trees_cache, parent_id, &repo)?;
|
||||
parent_tree
|
||||
.changes()?
|
||||
.options(|opts| {
|
||||
opts.track_rewrites(None).track_path();
|
||||
})
|
||||
.for_each_to_obtain_tree(&wd_tree, |change| -> Result<_> {
|
||||
match change {
|
||||
Change::Addition { location, .. } => {
|
||||
files_changed.push(gix::path::from_bstr(location).into_owned());
|
||||
}
|
||||
Change::Deletion { .. }
|
||||
| Change::Modification { .. }
|
||||
| Change::Rewrite { .. } => {}
|
||||
}
|
||||
if let Some(counts) = change
|
||||
.diff(&mut resource_cache)
|
||||
.ok()
|
||||
.and_then(|mut platform| platform.line_counts().ok().flatten())
|
||||
{
|
||||
lines_added += u64::from(counts.insertions);
|
||||
lines_removed += u64::from(counts.removals);
|
||||
}
|
||||
resource_cache.clear_resource_cache_keep_allocation();
|
||||
|
||||
Ok(gix::object::tree::diff::Action::Continue)
|
||||
})?;
|
||||
|
||||
let stats = diff.stats()?;
|
||||
snapshots.push(Snapshot {
|
||||
commit_id,
|
||||
details,
|
||||
lines_added: stats.insertions(),
|
||||
lines_removed: stats.deletions(),
|
||||
lines_added: lines_added as usize,
|
||||
lines_removed: lines_removed as usize,
|
||||
files_changed,
|
||||
created_at: commit.time(),
|
||||
created_at: commit_time,
|
||||
});
|
||||
} else {
|
||||
// this is the very first snapshot
|
||||
@ -249,7 +270,7 @@ impl OplogExt for Project {
|
||||
lines_added: 0,
|
||||
lines_removed: 0,
|
||||
files_changed: Vec::new(),
|
||||
created_at: commit.time(),
|
||||
created_at: commit_time,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -279,13 +300,14 @@ impl OplogExt for Project {
|
||||
|
||||
fn snapshot_diff(&self, sha: git2::Oid) -> Result<HashMap<PathBuf, FileDiff>> {
|
||||
let worktree_dir = self.path.as_path();
|
||||
let gix_repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
|
||||
let repo = git2::Repository::init(worktree_dir)?;
|
||||
|
||||
let commit = repo.find_commit(sha)?;
|
||||
|
||||
let wd_tree_id = tree_from_applied_vbranches(&repo, commit.id())?;
|
||||
let wd_tree_id = tree_from_applied_vbranches(&gix_repo, commit.id())?;
|
||||
let wd_tree = repo.find_tree(wd_tree_id)?;
|
||||
let old_wd_tree_id = tree_from_applied_vbranches(&repo, commit.parent(0)?.id())?;
|
||||
let old_wd_tree_id = tree_from_applied_vbranches(&gix_repo, commit.parent(0)?.id())?;
|
||||
let old_wd_tree = repo.find_tree(old_wd_tree_id)?;
|
||||
|
||||
repo.ignore_large_files_in_diffs(SNAPSHOT_FILE_LIMIT_BYTES)?;
|
||||
@ -314,20 +336,20 @@ impl OplogExt for Project {
|
||||
|
||||
/// Get a tree of the working dir (applied branches merged)
|
||||
fn get_workdir_tree<'a>(
|
||||
wd_trees_cache: &mut HashMap<git2::Oid, git2::Oid>,
|
||||
commit_id: git2::Oid,
|
||||
repo: &'a git2::Repository,
|
||||
) -> Result<git2::Tree<'a>, anyhow::Error> {
|
||||
wd_trees_cache: &mut HashMap<gix::ObjectId, gix::ObjectId>,
|
||||
commit_id: impl Into<gix::ObjectId>,
|
||||
repo: &'a gix::Repository,
|
||||
) -> Result<gix::Tree<'a>, anyhow::Error> {
|
||||
let commit_id = commit_id.into();
|
||||
if let Entry::Vacant(e) = wd_trees_cache.entry(commit_id) {
|
||||
if let Ok(wd_tree_id) = tree_from_applied_vbranches(repo, commit_id) {
|
||||
e.insert(wd_tree_id);
|
||||
if let Ok(wd_tree_id) = tree_from_applied_vbranches(repo, gix_to_git2_oid(commit_id)) {
|
||||
e.insert(git2_to_gix_object_id(wd_tree_id));
|
||||
}
|
||||
}
|
||||
let wd_tree_id = wd_trees_cache.get(&commit_id).ok_or(anyhow!(
|
||||
let id = wd_trees_cache.get(&commit_id).copied().ok_or(anyhow!(
|
||||
"Could not get a tree of all applied virtual branches merged"
|
||||
))?;
|
||||
let wd_tree = repo.find_tree(wd_tree_id.to_owned())?;
|
||||
Ok(wd_tree)
|
||||
Ok(repo.find_tree(id)?)
|
||||
}
|
||||
|
||||
fn prepare_snapshot(ctx: &Project, _shared_access: &WorktreeReadPermission) -> Result<git2::Oid> {
|
||||
@ -574,7 +596,8 @@ fn restore_snapshot(
|
||||
"We will not change a worktree which for some reason isn't on the workspace branch",
|
||||
)?;
|
||||
|
||||
let workdir_tree_id = tree_from_applied_vbranches(&repo, snapshot_commit_id)?;
|
||||
let gix_repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
|
||||
let workdir_tree_id = tree_from_applied_vbranches(&gix_repo, snapshot_commit_id)?;
|
||||
let workdir_tree = repo.find_tree(workdir_tree_id)?;
|
||||
|
||||
repo.ignore_large_files_in_diffs(SNAPSHOT_FILE_LIMIT_BYTES)?;
|
||||
@ -794,59 +817,54 @@ fn deserialize_commit(
|
||||
}
|
||||
|
||||
/// Creates a tree that is the merge of all applied branches from a given snapshot and returns the tree id.
|
||||
/// Note that `repo` must have caching setup for merges.
|
||||
fn tree_from_applied_vbranches(
|
||||
repo: &git2::Repository,
|
||||
repo: &gix::Repository,
|
||||
snapshot_commit_id: git2::Oid,
|
||||
) -> Result<git2::Oid> {
|
||||
let snapshot_commit = repo.find_commit(snapshot_commit_id)?;
|
||||
let snapshot_commit = repo.find_commit(git2_to_gix_object_id(snapshot_commit_id))?;
|
||||
let snapshot_tree = snapshot_commit.tree()?;
|
||||
|
||||
let target_tree_entry = snapshot_tree
|
||||
.get_name("target_tree")
|
||||
.context("failed to get target tree entry")?;
|
||||
let target_tree = repo
|
||||
.find_tree(target_tree_entry.id())
|
||||
.context("failed to convert target tree entry to tree")?;
|
||||
.lookup_entry_by_path("target_tree")?
|
||||
.context("no entry at 'target_entry'")?;
|
||||
let target_tree_id = target_tree_entry.id().detach();
|
||||
|
||||
let vb_toml_entry = snapshot_tree
|
||||
.get_name("virtual_branches.toml")
|
||||
.lookup_entry_by_path("virtual_branches.toml")?
|
||||
.context("failed to get virtual_branches.toml blob")?;
|
||||
// virtual_branches.toml blob
|
||||
let vb_toml_blob = repo
|
||||
.find_blob(vb_toml_entry.id())
|
||||
.context("failed to convert virtual_branches tree entry to blob")?;
|
||||
|
||||
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(vb_toml_blob.content())?)?;
|
||||
let applied_branch_trees: Vec<git2::Oid> = vbs_from_toml
|
||||
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(&vb_toml_blob.data)?)?;
|
||||
let applied_branch_trees: Vec<_> = vbs_from_toml
|
||||
.list_branches_in_workspace()?
|
||||
.iter()
|
||||
.map(|b| b.tree)
|
||||
.map(|b| git2_to_gix_object_id(b.tree))
|
||||
.collect();
|
||||
|
||||
let mut workdir_tree_id = target_tree.id();
|
||||
let base_tree = target_tree;
|
||||
let mut current_ours = base_tree.clone();
|
||||
let mut workdir_tree_id = target_tree_id;
|
||||
let base_tree_id = target_tree_id;
|
||||
let mut current_ours_id = target_tree_id;
|
||||
|
||||
for branch in applied_branch_trees {
|
||||
let branch_tree = repo.find_tree(branch)?;
|
||||
let mut merge_options: git2::MergeOptions = git2::MergeOptions::new();
|
||||
merge_options.fail_on_conflict(false);
|
||||
let mut workdir_temp_index = repo.merge_trees(
|
||||
&base_tree,
|
||||
¤t_ours,
|
||||
&branch_tree,
|
||||
Some(&merge_options),
|
||||
let (merge_option_fail_fast, conflict_kind) = repo.merge_options_fail_fast()?;
|
||||
for branch_id in applied_branch_trees {
|
||||
let mut merge = repo.merge_trees(
|
||||
base_tree_id,
|
||||
current_ours_id,
|
||||
branch_id,
|
||||
repo.default_merge_labels(),
|
||||
merge_option_fail_fast.clone(),
|
||||
)?;
|
||||
match workdir_temp_index.write_tree_to(repo) {
|
||||
Ok(id) => {
|
||||
workdir_tree_id = id;
|
||||
current_ours = repo.find_tree(workdir_tree_id)?;
|
||||
}
|
||||
Err(_err) => {
|
||||
tracing::warn!("Failed to merge tree {branch} - this branch is probably applied at a time when it should not be");
|
||||
}
|
||||
if merge.has_unresolved_conflicts(conflict_kind) {
|
||||
tracing::warn!("Failed to merge tree {branch_id} - this branch is probably applied at a time when it should not be");
|
||||
} else {
|
||||
let id = merge.tree.write()?.detach();
|
||||
workdir_tree_id = id;
|
||||
current_ours_id = id;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(workdir_tree_id)
|
||||
Ok(gix_to_git2_oid(workdir_tree_id))
|
||||
}
|
||||
|
@ -4,6 +4,10 @@ use anyhow::Context;
|
||||
use gix::bstr::ByteSlice;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
pub fn gix_time_to_git2(time: gix::date::Time) -> git2::Time {
|
||||
git2::Time::new(time.seconds, time.offset)
|
||||
}
|
||||
|
||||
pub fn git2_to_gix_object_id(id: git2::Oid) -> gix::ObjectId {
|
||||
gix::ObjectId::try_from(id.as_bytes()).expect("git2 oid is always valid")
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
git2.workspace = true
|
||||
gix = { workspace = true, features = ["status", "tree-editor"] }
|
||||
gix = { workspace = true, features = ["merge", "status", "tree-editor"] }
|
||||
anyhow = "1.0.92"
|
||||
bstr.workspace = true
|
||||
tracing.workspace = true
|
||||
|
@ -17,6 +17,7 @@ use gitbutler_oxidize::{
|
||||
};
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gix::fs::is_executable;
|
||||
use gix::merge::tree::{Options, UnresolvedConflict};
|
||||
use gix::objs::WriteTo;
|
||||
use tracing::instrument;
|
||||
|
||||
@ -699,6 +700,47 @@ pub trait GixRepositoryExt: Sized {
|
||||
/// Configure the repository for diff operations between trees.
|
||||
/// This means it needs an object cache relative to the amount of files in the repository.
|
||||
fn for_tree_diffing(self) -> Result<Self>;
|
||||
|
||||
/// Returns `true` if the merge between `our_tree` and `their_tree` is free of conflicts.
|
||||
/// Conflicts entail content merges with conflict markers, or anything else that doesn't merge cleanly in the tree.
|
||||
///
|
||||
/// # Important
|
||||
///
|
||||
/// Make sure the repository is configured [`with_object_memory()`](gix::Repository::with_object_memory()).
|
||||
fn merges_cleanly_compat(
|
||||
&self,
|
||||
ancestor_tree: git2::Oid,
|
||||
our_tree: git2::Oid,
|
||||
their_tree: git2::Oid,
|
||||
) -> Result<bool>;
|
||||
|
||||
/// Just like the above, but with `gix` types.
|
||||
fn merges_cleanly(
|
||||
&self,
|
||||
ancestor_tree: gix::ObjectId,
|
||||
our_tree: gix::ObjectId,
|
||||
their_tree: gix::ObjectId,
|
||||
) -> Result<bool>;
|
||||
|
||||
/// Return default lable names when merging trees.
|
||||
///
|
||||
/// Note that these should probably rather be branch names, but that's for another day.
|
||||
fn default_merge_labels(&self) -> gix::merge::blob::builtin_driver::text::Labels<'static> {
|
||||
gix::merge::blob::builtin_driver::text::Labels {
|
||||
ancestor: Some("base".into()),
|
||||
current: Some("ours".into()),
|
||||
other: Some("theirs".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return options suitable for merging so that the merge stops immediately after the first conflict.
|
||||
/// It also returns the conflict kind to use when checking for unresolved conflicts.
|
||||
fn merge_options_fail_fast(
|
||||
&self,
|
||||
) -> Result<(
|
||||
gix::merge::tree::Options,
|
||||
gix::merge::tree::UnresolvedConflict,
|
||||
)>;
|
||||
}
|
||||
|
||||
impl GixRepositoryExt for gix::Repository {
|
||||
@ -707,6 +749,46 @@ impl GixRepositoryExt for gix::Repository {
|
||||
self.object_cache_size_if_unset(bytes);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn merges_cleanly_compat(
|
||||
&self,
|
||||
ancestor_tree: git2::Oid,
|
||||
our_tree: git2::Oid,
|
||||
their_tree: git2::Oid,
|
||||
) -> Result<bool> {
|
||||
self.merges_cleanly(
|
||||
git2_to_gix_object_id(ancestor_tree),
|
||||
git2_to_gix_object_id(our_tree),
|
||||
git2_to_gix_object_id(their_tree),
|
||||
)
|
||||
}
|
||||
|
||||
fn merges_cleanly(
|
||||
&self,
|
||||
ancestor_tree: gix::ObjectId,
|
||||
our_tree: gix::ObjectId,
|
||||
their_tree: gix::ObjectId,
|
||||
) -> Result<bool> {
|
||||
let (options, conflict_kind) = self.merge_options_fail_fast()?;
|
||||
let merge_outcome = self
|
||||
.merge_trees(
|
||||
ancestor_tree,
|
||||
our_tree,
|
||||
their_tree,
|
||||
Default::default(),
|
||||
options,
|
||||
)
|
||||
.context("failed to merge trees")?;
|
||||
Ok(!merge_outcome.has_unresolved_conflicts(conflict_kind))
|
||||
}
|
||||
|
||||
fn merge_options_fail_fast(&self) -> Result<(Options, UnresolvedConflict)> {
|
||||
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
|
||||
let options = self
|
||||
.tree_merge_options()?
|
||||
.with_fail_on_conflict(Some(conflict_kind));
|
||||
Ok((options, conflict_kind))
|
||||
}
|
||||
}
|
||||
|
||||
type OidFilter = dyn Fn(&git2::Commit) -> Result<bool>;
|
||||
|
@ -29,13 +29,7 @@ mod head_upsert_truthtable {
|
||||
// | add | delete | no-action |
|
||||
#[test]
|
||||
fn index_new_worktree_delete() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
||||
|
||||
@ -53,13 +47,8 @@ mod head_upsert_truthtable {
|
||||
// | modify | delete | remove |
|
||||
#[test]
|
||||
fn index_modify_worktree_delete() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository =
|
||||
TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
||||
|
||||
@ -77,13 +66,8 @@ mod head_upsert_truthtable {
|
||||
// | | delete | remove |
|
||||
#[test]
|
||||
fn worktree_delete() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository =
|
||||
TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
std::fs::remove_file(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
||||
|
||||
@ -95,13 +79,8 @@ mod head_upsert_truthtable {
|
||||
// | delete | | remove |
|
||||
#[test]
|
||||
fn index_delete() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository =
|
||||
TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
let mut index = test_repository.repository.index().unwrap();
|
||||
index.remove_all(["*"], None).unwrap();
|
||||
@ -120,13 +99,8 @@ mod head_upsert_truthtable {
|
||||
// | delete | add | upsert |
|
||||
#[test]
|
||||
fn index_delete_worktree_add() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository =
|
||||
TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
let mut index = test_repository.repository.index().unwrap();
|
||||
index.remove_all(["*"], None).unwrap();
|
||||
@ -147,13 +121,7 @@ mod head_upsert_truthtable {
|
||||
// | add | | upsert |
|
||||
#[test]
|
||||
fn index_add() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
||||
|
||||
@ -174,13 +142,7 @@ mod head_upsert_truthtable {
|
||||
// | | add | upsert |
|
||||
#[test]
|
||||
fn worktree_add() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
||||
|
||||
@ -197,13 +159,7 @@ mod head_upsert_truthtable {
|
||||
// | add | modify | upsert |
|
||||
#[test]
|
||||
fn index_add_worktree_modify() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
||||
|
||||
@ -226,13 +182,8 @@ mod head_upsert_truthtable {
|
||||
// | modify | modify | upsert |
|
||||
#[test]
|
||||
fn index_modify_worktree_modify() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository =
|
||||
TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
||||
|
||||
@ -255,16 +206,7 @@ mod head_upsert_truthtable {
|
||||
|
||||
#[test]
|
||||
fn lists_uncommited_changes() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
// Initial commit
|
||||
// Create wd tree requires the HEAD branch to exist and for there
|
||||
// to be at least one commit on that branch.
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
||||
std::fs::write(test_repository.tempdir.path().join("file2.txt"), "content2").unwrap();
|
||||
@ -280,16 +222,7 @@ fn lists_uncommited_changes() {
|
||||
|
||||
#[test]
|
||||
fn does_not_include_staged_but_deleted_files() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
// Initial commit
|
||||
// Create wd tree requires the HEAD branch to exist and for there
|
||||
// to be at least one commit on that branch.
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
||||
std::fs::write(test_repository.tempdir.path().join("file2.txt"), "content2").unwrap();
|
||||
@ -312,16 +245,10 @@ fn does_not_include_staged_but_deleted_files() {
|
||||
|
||||
#[test]
|
||||
fn should_be_empty_after_checking_out_empty_tree() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(
|
||||
None,
|
||||
&[("file1.txt", "content1"), ("file2.txt", "content2")],
|
||||
);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[
|
||||
("file1.txt", "content1"),
|
||||
("file2.txt", "content2"),
|
||||
]);
|
||||
|
||||
// Checkout an empty tree
|
||||
{
|
||||
@ -353,16 +280,10 @@ fn should_be_empty_after_checking_out_empty_tree() {
|
||||
|
||||
#[test]
|
||||
fn should_track_deleted_files() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(
|
||||
None,
|
||||
&[("file1.txt", "content1"), ("file2.txt", "content2")],
|
||||
);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[
|
||||
("file1.txt", "content1"),
|
||||
("file2.txt", "content2"),
|
||||
]);
|
||||
|
||||
// Make sure the index is empty, perhaps the user did this action
|
||||
let mut index: git2::Index = test_repository.repository.index().unwrap();
|
||||
@ -384,13 +305,7 @@ fn should_track_deleted_files() {
|
||||
|
||||
#[test]
|
||||
fn should_not_change_index() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[("file1.txt", "content1")]);
|
||||
|
||||
let mut index = test_repository.repository.index().unwrap();
|
||||
index.remove_all(["*"], None).unwrap();
|
||||
@ -417,20 +332,10 @@ fn should_not_change_index() {
|
||||
|
||||
#[test]
|
||||
fn tree_behavior() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(
|
||||
None,
|
||||
&[
|
||||
("dir1/file1.txt", "content1"),
|
||||
("dir2/file2.txt", "content2"),
|
||||
],
|
||||
);
|
||||
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[
|
||||
("dir1/file1.txt", "content1"),
|
||||
("dir2/file2.txt", "content2"),
|
||||
]);
|
||||
|
||||
// Update a file in a directory
|
||||
std::fs::write(
|
||||
@ -464,13 +369,7 @@ fn tree_behavior() {
|
||||
fn executable_blobs() {
|
||||
use std::{io::Write, os::unix::fs::PermissionsExt as _};
|
||||
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[]);
|
||||
|
||||
let mut file = File::create(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
||||
file.set_permissions(Permissions::from_mode(0o755)).unwrap();
|
||||
@ -492,13 +391,7 @@ fn executable_blobs() {
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn links() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[("target", "helloworld")]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
let test_repository = TestingRepository::open_with_initial_commit(&[("target", "helloworld")]);
|
||||
|
||||
std::os::unix::fs::symlink("target", test_repository.tempdir.path().join("link1.txt")).unwrap();
|
||||
|
||||
|
@ -15,6 +15,22 @@ impl TestingRepository {
|
||||
pub fn open() -> Self {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let repository = git2::Repository::init_opts(tempdir.path(), &init_opts()).unwrap();
|
||||
// TODO(ST): remove this once `gix::Repository::index_or_load_from_tree_or_empty()`
|
||||
// is available and used to get merge/diff resource caches. Also: name this
|
||||
// `open_unborn()` to make it clear.
|
||||
// For now we need a resemblance of an initialized repo.
|
||||
let signature = git2::Signature::now("Caleb", "caleb@gitbutler.com").unwrap();
|
||||
let empty_tree_id = repository.treebuilder(None).unwrap().write().unwrap();
|
||||
repository
|
||||
.commit(
|
||||
Some("refs/heads/master"),
|
||||
&signature,
|
||||
&signature,
|
||||
"init to prevent load index failure",
|
||||
&repository.find_tree(empty_tree_id).unwrap(),
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = repository.config().unwrap();
|
||||
match config.open_level(git2::ConfigLevel::Local) {
|
||||
@ -34,9 +50,39 @@ impl TestingRepository {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_with_initial_commit(files: &[(&str, &str)]) -> Self {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let repository = git2::Repository::init_opts(tempdir.path(), &init_opts()).unwrap();
|
||||
|
||||
let config = repository.config().unwrap();
|
||||
match config.open_level(git2::ConfigLevel::Local) {
|
||||
Ok(mut local) => {
|
||||
local.set_str("commit.gpgsign", "false").unwrap();
|
||||
local.set_str("user.name", "gitbutler-test").unwrap();
|
||||
local
|
||||
.set_str("user.email", "gitbutler-test@example.com")
|
||||
.unwrap();
|
||||
}
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
|
||||
let repository = Self {
|
||||
tempdir,
|
||||
repository,
|
||||
};
|
||||
{
|
||||
let commit = repository.commit_tree(None, files);
|
||||
repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
}
|
||||
repository
|
||||
}
|
||||
|
||||
pub fn commit_tree<'a>(
|
||||
&'a self,
|
||||
parent: Option<&git2::Commit<'a>>,
|
||||
parent: Option<&git2::Commit<'_>>,
|
||||
files: &[(&str, &str)],
|
||||
) -> git2::Commit<'a> {
|
||||
self.commit_tree_with_message(parent, &Uuid::new_v4().to_string(), files)
|
||||
@ -44,7 +90,7 @@ impl TestingRepository {
|
||||
|
||||
pub fn commit_tree_with_message<'a>(
|
||||
&'a self,
|
||||
parent: Option<&git2::Commit<'a>>,
|
||||
parent: Option<&git2::Commit<'_>>,
|
||||
message: &str,
|
||||
files: &[(&str, &str)],
|
||||
) -> git2::Commit<'a> {
|
||||
|
Loading…
Reference in New Issue
Block a user