Compare commits

...

27 commits

Author SHA1 Message Date
Fabien Potencier
36e0e74cfe
Merge pull request #83 from mharacewiat/preserve-file-permissions
Preserve file permissions
2025-11-30 17:05:39 +01:00
Michał Haracewiat
bc1bb28a9c
test: add test for filemode 2025-08-28 15:30:24 +02:00
Michał Haracewiat
0a0c9e0be9
fix: preserve filemode in root directory 2025-08-28 15:29:55 +02:00
Fabien Potencier
fd72641be1
Rename originBranch to origin 2024-03-08 14:12:51 +01:00
Fabien Potencier
931f587a87
Merge pull request #80 from splitsh/excludes
Support excluding directories
2024-03-08 11:25:27 +01:00
Fabien Potencier
bd864e2ca6
Sanitize prefix values 2024-03-08 10:58:45 +01:00
Fabien Potencier
5dbf39b5b4
Add excluding directories support 2024-03-08 10:43:48 +01:00
Fabien Potencier
eaede0baa4
Merge pull request #79 from splitsh/aggressive-cache
Move head check for a more aggressive use of cache
2024-03-08 08:11:47 +01:00
Fabien Potencier
ca1a6cef48
Move head check for a more aggressive use of cache 2024-03-08 08:09:53 +01:00
Fabien Potencier
cf9970e7ff
Merge pull request #78 from splitsh/cs
Fix a few lint issues
2024-03-07 17:30:54 +01:00
Fabien Potencier
a5c6982fcd
Use a more recent version of libgit2 2024-03-07 17:27:50 +01:00
Fabien Potencier
8f693c4bc2
Fix a few lint issues 2024-03-07 15:26:05 +01:00
Fabien Potencier
2e82f4a273
Merge pull request #77 from splitsh/dead-code
Remove dead code
2024-03-07 15:18:00 +01:00
Fabien Potencier
e6d25ea821
Remove dead code 2024-03-07 15:10:38 +01:00
Fabien Potencier
4aa9676740
Merge pull request #76 from splitsh/cache-flush-late
Sync DB only at the end of splits
2024-03-06 12:56:19 +01:00
Fabien Potencier
26da73eea4 Sync DB only at the end of splits 2024-03-06 12:53:42 +01:00
Fabien Potencier
5d55360bed Remove unneeded argument 2024-03-06 08:36:31 +01:00
Fabien Potencier
d1ff5e1dc9 Add vendor to .gitignore 2024-03-02 09:40:11 +01:00
Fabien Potencier
3a3e4e00a4
Merge pull request #74 from splitsh/bbolt
Use bbolt as boltdb is not maintained anymore
2024-03-01 20:17:43 +01:00
Fabien Potencier
336cb150a8 Bump to Go 1.22 and update deps 2024-03-01 20:15:34 +01:00
Fabien Potencier
f19d527a89 Use bbolt as boltdb is not maintained anymore 2024-03-01 19:35:06 +01:00
Fabien Potencier
6cdc0a4137 Fix CHANGELOG 2024-03-01 19:22:35 +01:00
Fabien Potencier
08804f1e89 Tweak README 2023-10-29 16:02:40 -07:00
Fabien Potencier
e5e71931e0
Merge pull request #72 from splitsh/deprecated-features-removal
Remove the --quiet and --legacy options
2023-10-26 03:11:09 +02:00
Fabien Potencier
f2d9253550 Add more detail about how to compile the binary 2023-10-25 18:09:24 -07:00
Fabien Potencier
852bb257f0
Merge pull request #71 from splitsh/github-actions
Add GitHub actions
2023-10-25 23:55:50 +02:00
Fabien Potencier
f394dc9e6e Add GitHub actions 2023-10-25 14:54:04 -07:00
14 changed files with 301 additions and 135 deletions

42
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: tests
on:
pull_request:
push:
jobs:
update:
name: Run tests
runs-on: ubuntu-latest
steps:
-
name: Install deps
run: sudo apt-get install -y pkg-config cmake
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '^1.22.0'
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
-
name: Building
run: |
go mod vendor
rm -rf vendor/github.com/libgit2/git2go
git clone https://github.com/libgit2/git2go vendor/github.com/libgit2/git2go/v34
cd vendor/github.com/libgit2/git2go/v34 && git checkout v34.0.0 && git submodule update --init && make install-static
-
name: Test
run: |
export PKG_CONFIG_PATH=/home/runner/work/lite/lite/vendor/github.com/libgit2/git2go/v34/static-build/build
go test -v ./...

1
.gitignore vendored
View file

@ -1 +1,2 @@
splitter-lite-tests/
vendor/

View file

@ -1,12 +1,11 @@
CHANGELOG
=========
* 2.0.0 (2023-XX-XX)
* 2.0.0 (2023-10-25)
* move to go.mod
* remove the `--quiet` option (append `2>/dev/null` to the command instead)
* remove the `--legacy` option (same as `--git '<1.8.2'`)
* move information console display to stderr instead of stdout
* 1.0.1 (2017-02-24)

View file

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Fabien Potencier
Copyright (c) 2015-2024 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -40,11 +40,17 @@ Manual Installation
First, you need to install `libgit2`, preferably using your package manager of
choice.
If you get version `1.5`, jump to the compilation step below. If not, you first
need to change the version used in the code. Using the table on the
[libgit2](https://github.com/libgit2/git2go#which-go-version-to-use)
repository, figure out which version you need. Then, replace the `v34` in the
`go.mod` file and in all files under the `splitter/` directory. Run `go mod tidy`.
If you get `libgit2` version `1.5`, you're all set and jump to the compilation
step below. If not, you first need to change the `git2go` version used in the
code. Using the table on the
[libgit2](https://github.com/libgit2/git2go#which-go-version-to-use) repository,
figure out which version of the `git2go` you need based on the `liggit2` library
you installed. Let's say you need version `v31`:
```bash
sed -i -e 's/v34/v31/g' go.mod splitter/*.go
go mod tidy
```
Then, compile `splitsh-lite`:
@ -122,10 +128,18 @@ splitsh-lite --prefix=lib/ --origin=origin/1.0 --path=/path/to/repo
Available options:
* `--prefix` is the prefix of the directory to split; you can put the split
contents in a sub-directory of the target repository by using the
`--prefix=from:to` syntax; split several directories by passing multiple
`--prefix` flags;
* `--prefix` is the prefix of the directory to split; the value can be one of
the following:
* `from`: the origin directory to split;
* `from:to`: move the split content to a sub-directory on the target;
* `from:to:exclude`: exclude a directory from the origin `from` directory
(use `from:to:exclude1:exclude2:...` to exclude more than one
directory).
Split several directories by passing multiple `--prefix` flags;
* `--path` is the path of the repository to split (current directory by default);
@ -137,9 +151,6 @@ Available options:
* `--progress` displays a progress bar;
* `--quiet` suppresses all output on stderr (useful when run from an automated
script);
* `--scratch` flushes the cache (useful when a branch is force pushed or in
case of a cache corruption).

8
go.mod
View file

@ -1,13 +1,13 @@
module github.com/splitsh/lite
go 1.17
go 1.22
require (
github.com/boltdb/bolt v1.3.1
github.com/libgit2/git2go/v34 v34.0.0
go.etcd.io/bbolt v1.3.9
)
require (
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/sys v0.17.0 // indirect
)

58
go.sum
View file

@ -1,52 +1,30 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/libgit2/git2go/v34 v34.0.0 h1:UKoUaKLmiCRbOCD3PtUi2hD6hESSXzME/9OUZrGcgu8=
github.com/libgit2/git2go/v34 v34.0.0/go.mod h1:blVco2jDAw6YTXkErMMqzHLcAjKkwF0aWIRHBqiJkZ0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

27
main.go
View file

@ -24,19 +24,24 @@ func (p *prefixesFlag) Set(value string) error {
parts := strings.Split(value, ":")
from := parts[0]
to := ""
if len(parts) > 1 {
to = parts[1]
}
// value must be unique
for _, prefix := range []*splitter.Prefix(*p) {
// FIXME: to should be normalized (xxx vs xxx/ for instance)
if prefix.To == to {
return fmt.Errorf("Cannot have two prefix splits under the same directory: %s -> %s vs %s -> %s", prefix.From, prefix.To, from, to)
excludes := make([]string, 0)
if len(parts) >= 2 {
to = strings.TrimRight(parts[1], "/")
if len(parts) > 2 {
for _, exclude := range parts[2:] {
excludes = append(excludes, exclude)
}
}
}
*p = append(*p, &splitter.Prefix{From: from, To: to})
// value must be unique
for _, prefix := range *p {
if prefix.To == to {
return fmt.Errorf("cannot have two prefix splits under the same directory: %s -> %s vs %s -> %s", prefix.From, prefix.To, from, to)
}
}
*p = append(*p, splitter.NewPrefix(from, to, excludes))
return nil
}
@ -73,7 +78,7 @@ func main() {
config := &splitter.Config{
Path: path,
Origin: origin,
Prefixes: []*splitter.Prefix(prefixes),
Prefixes: prefixes,
Target: target,
Commit: commit,
Debug: debug,

View file

@ -155,6 +155,37 @@ twigSplitTest() {
cd ../
}
filemodeTest() {
rm -rf filemode
mkdir filemode
cd filemode
git init > /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:01:02 +0200" "Sat, 24 Nov 1973 19:11:22 +0200"
echo "a" > a
git add a
git commit -m"added a" > /dev/null
switchAsFred "Sat, 24 Nov 1973 20:01:02 +0200" "Sat, 24 Nov 1973 20:11:22 +0200"
mkdir b/
echo "b" > b/b
chmod +x b/b
git add b
git commit -m"added b" > /dev/null
$LITE_PATH --prefix=b/::not-important:also-not-important --target refs/heads/split 2>/dev/null
FILEMODE=`git ls-tree -r --format='%(objectmode)' split b`
if test "$FILEMODE" = "100755"; then
echo "Test #6 - OK"
else
echo "Test #6 - NOT OK"
exit 1
fi
cd ../
}
LITE_PATH=`pwd`/splitsh-lite
if [ ! -e $LITE_PATH ]; then
echo "You first need to compile the splitsh-lite binary"
@ -169,3 +200,4 @@ cd splitter-lite-tests
simpleTest
mergeTest
twigSplitTest
filemodeTest

View file

@ -8,14 +8,15 @@ import (
"strconv"
"time"
"github.com/boltdb/bolt"
git "github.com/libgit2/git2go/v34"
bolt "go.etcd.io/bbolt"
)
type cache struct {
key []byte
branch string
db *bolt.DB
data map[string][]byte
}
func newCache(branch string, config *Config) (*cache, error) {
@ -32,6 +33,7 @@ func newCache(branch string, config *Config) (*cache, error) {
db: db,
branch: branch,
key: key(config),
data: make(map[string][]byte),
}
err = db.Update(func(tx *bolt.Tx) error {
@ -39,13 +41,25 @@ func newCache(branch string, config *Config) (*cache, error) {
return err1
})
if err != nil {
return nil, fmt.Errorf("Impossible to create bucket: %s", err)
return nil, fmt.Errorf("impossible to create bucket: %s", err)
}
return c, nil
}
func (c *cache) close() error {
err := c.db.Update(func(tx *bolt.Tx) error {
for k, v := range c.data {
if err := tx.Bucket(c.key).Put([]byte(k), v); err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
return c.db.Close()
}
@ -63,22 +77,28 @@ func key(config *Config) []byte {
for _, prefix := range config.Prefixes {
io.WriteString(h, prefix.From)
io.WriteString(h, prefix.To)
for _, exclude := range prefix.Excludes {
io.WriteString(h, exclude)
}
}
return h.Sum(nil)
}
func (c *cache) setHead(head *git.Oid) error {
return c.db.Update(func(tx *bolt.Tx) error {
return tx.Bucket(c.key).Put([]byte("head/"+c.branch), head[0:20])
})
func (c *cache) setHead(head *git.Oid) {
c.data["head/"+c.branch] = head[0:20]
}
func (c *cache) getHead() *git.Oid {
if head, ok := c.data["head"+c.branch]; ok {
return git.NewOidFromBytes(head)
}
var oid *git.Oid
c.db.View(func(tx *bolt.Tx) error {
result := tx.Bucket(c.key).Get([]byte("head/" + c.branch))
if result != nil {
c.data["head/"+c.branch] = result
oid = git.NewOidFromBytes(result)
}
return nil
@ -87,10 +107,15 @@ func (c *cache) getHead() *git.Oid {
}
func (c *cache) get(rev *git.Oid) *git.Oid {
if v, ok := c.data[string(rev[0:20])]; ok {
return git.NewOidFromBytes(v)
}
var oid *git.Oid
c.db.View(func(tx *bolt.Tx) error {
result := tx.Bucket(c.key).Get(rev[0:20])
if result != nil {
c.data[string(rev[0:20])] = result
oid = git.NewOidFromBytes(result)
}
return nil
@ -98,38 +123,27 @@ func (c *cache) get(rev *git.Oid) *git.Oid {
return oid
}
func (c *cache) set(rev, newrev *git.Oid, created bool) error {
return c.db.Update(func(tx *bolt.Tx) error {
err := tx.Bucket(c.key).Put(rev[0:20], newrev[0:20])
if err != nil {
return err
}
postfix := "/newest"
if created {
postfix = "/oldest"
}
key := append(newrev[0:20], []byte(postfix)...)
return tx.Bucket(c.key).Put(key, rev[0:20])
})
func (c *cache) set(rev, newrev *git.Oid) {
c.data[string(rev[0:20])] = newrev[0:20]
}
func (c *cache) gets(commits []*git.Oid) []*git.Oid {
var oids []*git.Oid
c.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(c.key)
for _, commit := range commits {
result := b.Get(commit[0:20])
result := c.data[string(commit[0:20])]
if result != nil {
oids = append(oids, git.NewOidFromBytes(result))
} else {
result := b.Get(commit[0:20])
if result != nil {
oids = append(oids, git.NewOidFromBytes(result))
}
}
}
return nil
})
return oids
}

View file

@ -3,16 +3,36 @@ package splitter
import (
"fmt"
"log"
"strings"
"sync"
"github.com/boltdb/bolt"
git "github.com/libgit2/git2go/v34"
bolt "go.etcd.io/bbolt"
)
// Prefix represents which paths to split
type Prefix struct {
From string
To string
From string
To string
Excludes []string
}
// NewPrefix returns a new prefix, sanitizing the input
func NewPrefix(from, to string, excludes []string) *Prefix {
// remove the trailing slash (to avoid duplicating cache)
from = strings.TrimRight(from, "/")
to = strings.TrimRight(to, "/")
// remove trailing slashes from excludes (as it does not mean anything)
for i, exclude := range excludes {
excludes[i] = strings.TrimRight(exclude, "/")
}
return &Prefix{
From: from,
To: to,
Excludes: excludes,
}
}
// Config represents a split configuration
@ -53,17 +73,25 @@ func Split(config *Config, result *Result) error {
// Validate validates the configuration
func (config *Config) Validate() error {
if !git.ReferenceIsValidName(config.Origin) {
return fmt.Errorf("The origin is not a valid Git reference")
ok, err := git.ReferenceNameIsValid(config.Origin)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("the origin is not a valid Git reference")
}
if config.Target != "" && !git.ReferenceIsValidName(config.Target) {
return fmt.Errorf("The target is not a valid Git reference")
ok, err = git.ReferenceNameIsValid(config.Target)
if err != nil {
return err
}
if config.Target != "" && !ok {
return fmt.Errorf("the target is not a valid Git reference")
}
git, ok := supportedGitVersions[config.GitVersion]
if !ok {
return fmt.Errorf(`The git version can only be one of "<1.8.2", "<2.8.0", or "latest"`)
return fmt.Errorf(`the git version can only be one of "<1.8.2", "<2.8.0", or "latest"`)
}
config.Git = git

View file

@ -84,12 +84,12 @@ func roundDuration(d, r time.Duration) time.Duration {
}
neg := d < 0
if neg {
d = -d
d -= d
}
if m := d % r; m+m < r {
d = d - m
d -= m
} else {
d = d + r - m
d += r - m
}
if neg {
return -d

View file

@ -13,7 +13,7 @@ import (
type state struct {
config *Config
originBranch string
origin string
repoMu *sync.Mutex
repo *git.Repository
cache *cache
@ -52,22 +52,26 @@ func newState(config *Config, result *Result) (*state, error) {
state.logger = log.New(os.Stderr, "", log.LstdFlags)
}
if state.originBranch, err = normalizeOriginBranch(state.repo, config.Origin); err != nil {
if state.origin, err = normalizeOrigin(state.repo, config.Origin); err != nil {
return nil, err
}
if state.cache, err = newCache(state.originBranch, config); err != nil {
if state.cache, err = newCache(state.origin, config); err != nil {
return nil, err
}
if config.Debug {
state.logger.Printf("Splitting %s\n", state.originBranch)
state.logger.Printf("Splitting %s", state.origin)
for _, v := range config.Prefixes {
to := v.To
if to == "" {
to = "ROOT"
}
state.logger.Printf(" From \"%s\" to \"%s\"\n", v.From, to)
state.logger.Printf(` From "%s" to "%s"`, v.From, to)
if (len(v.Excludes)) == 0 {
} else {
state.logger.Printf(` Excluding "%s"`, strings.Join(v.Excludes, `", "`))
}
}
}
@ -79,7 +83,7 @@ func newState(config *Config, result *Result) (*state, error) {
// simplePrefix contains the prefix when there is only one
// with an empty value (target)
if len(config.Prefixes) == 1 && config.Prefixes[0].To == "" {
if len(config.Prefixes) == 1 && config.Prefixes[0].To == "" && len(config.Prefixes[0].Excludes) == 0 {
state.simplePrefix = config.Prefixes[0].From
}
@ -119,7 +123,7 @@ func (s *state) split() error {
revWalk, err := s.walker()
if err != nil {
return fmt.Errorf("Impossible to walk the repository: %s", err)
return fmt.Errorf("impossible to walk the repository: %s", err)
}
defer revWalk.Free()
@ -163,12 +167,12 @@ func (s *state) split() error {
func (s *state) walker() (*git.RevWalk, error) {
revWalk, err := s.repo.Walk()
if err != nil {
return nil, fmt.Errorf("Impossible to walk the repository: %s", err)
return nil, fmt.Errorf("impossible to walk the repository: %s", err)
}
err = s.pushRevs(revWalk)
if err != nil {
return nil, fmt.Errorf("Impossible to determine split range: %s", err)
return nil, fmt.Errorf("impossible to determine split range: %s", err)
}
revWalk.Sorting(git.SortTopological | git.SortReverse)
@ -239,9 +243,7 @@ func (s *state) splitRev(rev *git.Commit) (*git.Oid, error) {
s.result.incCreated()
}
if err := s.cache.set(rev.Id(), newrev, created); err != nil {
return nil, err
}
s.cache.set(rev.Id(), newrev)
return newrev, nil
}
@ -257,7 +259,7 @@ func (s *state) subtreeForCommit(commit *git.Commit) (*git.Tree, error) {
return s.treeByPath(tree, s.simplePrefix)
}
return s.treeByPaths(tree, s.config.Prefixes)
return s.treeByPaths(tree)
}
func (s *state) treeByPath(tree *git.Tree, prefix string) (*git.Tree, error) {
@ -274,7 +276,7 @@ func (s *state) treeByPath(tree *git.Tree, prefix string) (*git.Tree, error) {
return s.repo.LookupTree(treeEntry.Id)
}
func (s *state) treeByPaths(tree *git.Tree, prefixes []*Prefix) (*git.Tree, error) {
func (s *state) treeByPaths(tree *git.Tree) (*git.Tree, error) {
var currentTree, prefixedTree, mergedTree *git.Tree
for _, prefix := range s.config.Prefixes {
// splitting
@ -286,6 +288,14 @@ func (s *state) treeByPaths(tree *git.Tree, prefixes []*Prefix) (*git.Tree, erro
continue
}
if len(prefix.Excludes) > 0 {
prunedTree, err := s.pruneTree(splitTree, prefix.Excludes)
if err != nil {
return nil, err
}
splitTree = prunedTree
}
// adding the prefix
if prefix.To != "" {
prefixedTree, err = s.addPrefixToTree(splitTree, prefix.To)
@ -322,7 +332,7 @@ func (s *state) mergeTrees(t1, t2 *git.Tree) (*git.Tree, error) {
defer index.Free()
if index.HasConflicts() {
return nil, fmt.Errorf("Cannot split as there is a merge conflict between two paths")
return nil, fmt.Errorf("cannot split as there is a merge conflict between two paths")
}
oid, err := index.WriteTreeTo(s.repo)
@ -362,6 +372,53 @@ func (s *state) addPrefixToTree(tree *git.Tree, prefix string) (*git.Tree, error
return prefixedTree, nil
}
func (s *state) pruneTree(tree *git.Tree, excludes []string) (*git.Tree, error) {
var err error
treeBuilder, err := s.repo.TreeBuilder()
if err != nil {
return nil, err
}
defer treeBuilder.Free()
err = tree.Walk(func(path string, entry *git.TreeEntry) error {
// always add files at the root directory
if entry.Type == git.ObjectBlob {
if err := treeBuilder.Insert(entry.Name, entry.Id, entry.Filemode); err != nil {
return err
}
return nil
}
if entry.Type != git.ObjectTree {
// should never happen
return fmt.Errorf("Unexpected entry %s/%s (type %s)", path, entry.Name, entry.Type)
}
// exclude directory in excludes
for _, exclude := range excludes {
if entry.Name == exclude {
return git.TreeWalkSkip
}
}
if err := treeBuilder.Insert(entry.Name, entry.Id, git.FilemodeTree); err != nil {
return err
}
return git.TreeWalkSkip
})
if err != nil {
return nil, err
}
treeOid, err := treeBuilder.Write()
if err != nil {
return nil, err
}
return s.repo.LookupTree(treeOid)
}
func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oid) (*git.Oid, bool, error) {
var identical, nonIdentical *git.Oid
var gotParents []*git.Oid
@ -375,7 +432,7 @@ func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oi
continue
}
if 0 == ptree.Cmp(tree.Id()) {
if ptree.Cmp(tree.Id()) == 0 {
// an identical parent could be used in place of this rev.
identical = parent
} else {
@ -386,7 +443,7 @@ func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oi
// eliminate duplicates
isNew := true
for _, gp := range gotParents {
if 0 == gp.Cmp(parent) {
if gp.Cmp(parent) == 0 {
isNew = false
break
}
@ -407,7 +464,7 @@ func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oi
if s.config.Git > 2 && nil != identical && nil != nonIdentical {
revWalk, err := s.repo.Walk()
if err != nil {
return nil, false, fmt.Errorf("Impossible to walk the repository: %s", err)
return nil, false, fmt.Errorf("impossible to walk the repository: %s", err)
}
s.repoMu.Lock()
@ -415,7 +472,7 @@ func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oi
err = revWalk.PushRange(fmt.Sprintf("%s..%s", identical, nonIdentical))
if err != nil {
return nil, false, fmt.Errorf("Impossible to determine split range: %s", err)
return nil, false, fmt.Errorf("impossible to determine split range: %s", err)
}
err = revWalk.Iterate(func(rev *git.Commit) bool {
@ -496,7 +553,7 @@ func (s *state) updateTarget() error {
}
if nil == s.result.Head() {
return fmt.Errorf("Unable to create branch %s as it is empty (no commits were split)", s.config.Target)
return fmt.Errorf("unable to create branch %s as it is empty (no commits were split)", s.config.Target)
}
obj, ref, err := s.repo.RevparseExt(s.config.Target)
@ -524,12 +581,18 @@ func (s *state) legacyMessage(rev *git.Commit) string {
// pushRevs sets the range to split
func (s *state) pushRevs(revWalk *git.RevWalk) error {
// this is needed as origin might be in the process of being updated by git.FetchOrigin()
s.repoMu.Lock()
defer s.repoMu.Unlock()
// find the latest split sha1 if any on origin
var start *git.Oid
start = s.cache.getHead()
if start != nil {
s.result.moveHead(s.cache.get(start))
// FIXME: CHECK that this is an ancestor of the branch?
return revWalk.PushRange(fmt.Sprintf("%s..%s", start, s.origin))
}
// find the latest split sha1 if any on origin
var err error
if s.config.Commit != "" {
start, err = git.NewOid(s.config.Commit)
@ -537,17 +600,10 @@ func (s *state) pushRevs(revWalk *git.RevWalk) error {
return err
}
s.result.moveHead(s.cache.get(start))
return revWalk.PushRange(fmt.Sprintf("%s^..%s", start, s.originBranch))
return revWalk.PushRange(fmt.Sprintf("%s^..%s", start, s.origin))
}
start = s.cache.getHead()
if start != nil {
s.result.moveHead(s.cache.get(start))
// FIXME: CHECK that this is an ancestor of the branch?
return revWalk.PushRange(fmt.Sprintf("%s..%s", start, s.originBranch))
}
branch, err := s.repo.RevparseSingle(s.originBranch)
branch, err := s.repo.RevparseSingle(s.origin)
if err != nil {
return err
}

View file

@ -56,14 +56,14 @@ func SplitMessage(message string) (string, string) {
return subject, body
}
func normalizeOriginBranch(repo *git.Repository, origin string) (string, error) {
func normalizeOrigin(repo *git.Repository, origin string) (string, error) {
if origin == "" {
origin = "HEAD"
}
obj, ref, err := repo.RevparseExt(origin)
if err != nil {
return "", fmt.Errorf("Bad revision for origin: %s", err)
return "", fmt.Errorf("bad revision for origin: %s", err)
}
if obj != nil {
obj.Free()