Compare commits

...

56 commits

Author SHA1 Message Date
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 806f4764e8 Remove the --quiet and --legacy options 2023-10-25 18:09:56 -07: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
Fabien Potencier f69839fdbe Fix tests 2023-10-25 12:43:55 -07:00
Fabien Potencier cb8dc18103 Fix installation instructions 2023-10-25 12:27:32 -07:00
Fabien Potencier 6e0e2b2401 Fix more issues with tests 2023-10-25 12:19:35 -07:00
Fabien Potencier faf6867922 Fix CS 2023-10-25 12:12:32 -07:00
Fabien Potencier a11445a29c Simplify README 2023-10-25 12:09:21 -07:00
Fabien Potencier cd4e65afcd Update CHANGELOG 2023-10-25 12:08:12 -07:00
Fabien Potencier 000fd5e163
Merge pull request #69 from kdambekalns/task/compile-on-arm
Allow to compile on Apple silicon, use go modules
2023-10-25 21:04:06 +02:00
Karsten Dambekalns 93e0ef1670
Adjust manual install part in README.md 2023-10-23 11:54:45 +02:00
Karsten Dambekalns dba129b8af
Update go modules
Result of

    $ go get -u
    go: downloading golang.org/x/crypto v0.14.0
    go: downloading golang.org/x/sys v0.13.0
    go: downloading golang.org/x/term v0.13.0
    go: upgraded golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c => v0.14.0
    go: upgraded golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 => v0.13.0
    $ go mod tidy
2023-10-23 11:45:12 +02:00
Karsten Dambekalns f54790e733
Use "original" libgit2/git2go
Works with libgit2 1.5.0 installed using ´brew install libgit2`
2022-10-06 19:37:16 +02:00
Morel Sébastien 493f39ffeb
Make it work with M1 2022-10-06 18:48:02 +02:00
Fabien Potencier 01594a43d0
Merge pull request #60 from taylorotwell/master
Fallback value for committer email
2021-05-12 15:24:27 +02:00
Taylor Otwell 9b1b817de9 fallback value for committer email 2021-05-11 14:48:41 -05:00
Fabien Potencier faf40091fa
Merge pull request #57 from FGRibreau/patch-1
Fix readme example
2020-06-24 12:07:40 +02:00
Francois-Guillaume Ribreau d47cfda32a
Fix readme example
Fix readme example.
Current example yield an error `The target is not a valid Git reference`
2020-06-24 10:37:16 +02:00
Fabien Potencier 8ac3dcd530 deprecated --quiet 2019-03-03 09:23:48 +01:00
Fabien Potencier e577e7eae9 fixed display sent to stdout instead of stderr 2019-03-03 09:18:59 +01:00
Fabien Potencier 681e207a80 fixed typos in the README 2019-03-03 09:02:36 +01:00
Fabien Potencier f75aea5311 added a CHANGELOG 2019-03-03 09:00:43 +01:00
Fabien Potencier 34a1ea69dd added some information about unarchiving the archive 2019-03-03 08:58:31 +01:00
Fabien Potencier 7730112d8a added a note about how to install it as a Git sub-command 2019-03-03 08:57:02 +01:00
Fabien Potencier a60a1c610f added a note in the README about tomono 2019-03-03 08:54:55 +01:00
Fabien Potencier f53198b2fd bumped license year 2019-03-03 08:48:51 +01:00
Fabien Potencier 1814017fb1
Merge pull request #50 from DonCallisto/patch-1
Update README.md
2019-02-13 16:38:39 +01:00
Samuele Lilli ea7e7e28b0
Update README.md
Fixed a broken link
2019-02-11 08:47:26 +01:00
Fabien Potencier 7a31d22b2d minor #44 fix errors found by github.com/dominikh/go-tools/... (jostillmanns)
This PR was merged into the master branch.

Discussion
----------

fix errors found by github.com/dominikh/go-tools/...

lite/splitter/result.go:76:15: should use time.Since instead of time.Now().Sub (S1012)
lite/splitter/state.go:433:25: should omit comparison to bool constant, can be simplified to !copyCommit (S1002)
lite/splitter/utils.go:13:25: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (S1007)
lite/splitter/cache.go:90:17: func (*cache).reverse is unused (U1000)

Commits
-------

9d643aa fix errors found by github.com/dominikh/go-tools/...
2018-03-08 07:50:02 -08:00
Joschka Tillmanns 9d643aa621 fix errors found by github.com/dominikh/go-tools/...
lite/splitter/result.go:76:15: should use time.Since instead of time.Now().Sub (S1012)
lite/splitter/state.go:433:25: should omit comparison to bool constant, can be simplified to !copyCommit (S1002)
lite/splitter/utils.go:13:25: should use raw string (`...`) with regexp.MustCompile to avoid having to escape twice (S1007)
lite/splitter/cache.go:90:17: func (*cache).reverse is unused (U1000)
2018-03-08 10:25:31 +01:00
Fabien Potencier cb9c28b8c3 added a ref in README 2017-11-11 08:42:22 -08:00
Fabien Potencier 7e385e9511
Merge pull request #36 from johnnypeck/patch-3
Update README.md
2017-11-09 21:10:05 -08:00
Johnny Peck 63a4daebad
Update README.md
Minor spelling/grammar changes.
2017-11-10 00:08:32 -05:00
Fabien Potencier 86cf1d949a removed an obsolete paragraph in the README 2017-05-29 11:57:47 -07:00
14 changed files with 538 additions and 337 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/

17
CHANGELOG Normal file
View file

@ -0,0 +1,17 @@
CHANGELOG
=========
* 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)
* add the `--version` flag
* 1.0.0 (2017-01-02)
* initial version

View file

@ -1,4 +1,4 @@
Copyright (c) 2015-2017 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

120
README.md
View file

@ -25,18 +25,64 @@ I gave at dotScale
(or [read the slides](https://speakerdeck.com/fabpot/a-monorepo-vs-manyrepos))...
or watch the longer version from
[DrupalCon](https://www.youtube.com/watch?v=4w3-f6Xhvu8).
["The Monorepo - Storing your source code has never been so much fun"](https://speakerdeck.com/garethr/the-monorepo-storing-your-source-code-has-never-been-so-much-fun)
is also a great resource.
**Note** If you currently have multiple repositories that you want to merge into
a monorepo, use the [tomono](https://github.com/unravelin/tomono) tool.
Installation
------------
The fastest way to get started is to download a [binary][1] for your platform.
Manual Installation
-------------------
You can also [install it manually](#manual-installation).
First, you need to install `libgit2`, preferably using your package manager of
choice.
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`:
```bash
go build -o splitsh-lite github.com/splitsh/lite
```
If everything goes fine, a `splitsh-lite` binary should be available in the
current directory.
If you get errors about an incompatible `libgit2` library, try exporting the
needed flags, e.g.
```bash
export LDFLAGS="-L/opt/homebrew/opt/libgit2@1.5/lib"
export CPPFLAGS="-I/opt/homebrew/opt/libgit2@1.5/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/libgit2@1.5/lib/pkgconfig"
```
before running `go build`.
If you want to integrate splitsh with Git, install it like this (and use it via
`git splitsh`):
```bash
cp splitsh-lite "$(git --exec-path)"/git-splitsh
```
Usage
-----
Let say you want to split the `lib/` directory of a repository to its own
Let's say you want to split the `lib/` directory of a repository to its own
branch; from the "master" Git repository (bare or clone), run:
```bash
@ -56,16 +102,16 @@ Automatically create a branch for the split by passing a branch name
via the `--target` option:
```bash
splitsh-lite --prefix=lib/ --target=branch-name
splitsh-lite --prefix=lib/ --target=heads/branch-name
```
If new commits are made on the repository, update the split by running the same
If new commits are made to the repository, update the split by running the same
command again. Updates are much faster as **splitsh-lite** keeps a cache of
already split commits. Caching is possible as **splitsh-lite** guarantees that
two splits of the same code always results in the same history and the same
`sha1`s for each commit.
By default, **splitsh-lite** splits the current checkout-ed branch but you can
By default, **splitsh-lite** splits the currently checked out branch but you can
split a different branch by passing it explicitly via the `--origin` flag
(mandatory when splitting a bare repository):
@ -82,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);
@ -93,16 +147,12 @@ Available options:
like `HEAD`, `heads/xxx`, `tags/xxx`, `origin/xxx`, or any `refs/xxx`);
* `--target` creates a reference for the tip of the split (can be any Git
reference like `HEAD`, `heads/xxx`, `tags/xxx`, `origin/xxx`, or any
`refs/xxx`);
reference like `heads/xxx`, `tags/xxx`, `origin/xxx`, or any `refs/xxx`);
* `--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);
case of a cache corruption).
Migrating from `git subtree split`
----------------------------------
@ -114,41 +164,3 @@ However, note that older versions of `git subtree split` used broken
algorithms, and so generated different `sha1`s than the latest version. You can
simulate those version via the `--git` flag. Use `<1.8.2` or `<2.8.0` depending
on which version of `git subtree split` you want to simulate.
Manual Installation
-------------------
If you want to contribute to `splitsh-lite` or use it as a library, you first
need to install `libgit2`:
```bash
go get -d github.com/libgit2/git2go
cd $GOPATH/src/github.com/libgit2/git2go
git checkout next
git submodule update --init
make install
```
Then, compile `splitsh-lite`:
```bash
go get github.com/splitsh/lite
go build -o splitsh-lite github.com/splitsh/lite
```
If everything goes fine, a `splitsh-lite` binary should be available in the
current directory.
Full-Version
------------
The full version of **splitsh** provides more features including a sanity
checker, GitHub integration for real-time splitting, tag management and
synchronization, and more. It has been used by the Symfony project for many
years but the tool is not yet ready for Open-Source. Stay tuned!
If you think that your Open-Source project might benefit from the full version
of splitsh, send me an email and I will consider splitting your project for
free on my servers (like I do for Symfony and Laravel).
[1]: https://github.com/splitsh/lite/releases

13
go.mod Normal file
View file

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

30
go.sum Normal file
View file

@ -0,0 +1,30 @@
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/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.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/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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

53
main.go
View file

@ -24,25 +24,30 @@ 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
}
var prefixes prefixesFlag
var origin, target, commit, path, gitVersion string
var scratch, debug, quiet, legacy, progress, v bool
var scratch, debug, progress, v bool
func init() {
flag.Var(&prefixes, "prefix", "The directory(ies) to split")
@ -52,8 +57,6 @@ func init() {
flag.StringVar(&path, "path", ".", "The repository path (optional, current directory by default)")
flag.BoolVar(&scratch, "scratch", false, "Flush the cache (optional)")
flag.BoolVar(&debug, "debug", false, "Enable the debug mode (optional)")
flag.BoolVar(&quiet, "quiet", false, "Suppress the output (optional)")
flag.BoolVar(&legacy, "legacy", false, "[DEPRECATED] Enable the legacy mode for projects migrating from an old version of git subtree split (optional)")
flag.StringVar(&gitVersion, "git", "latest", "Simulate a given version of Git (optional)")
flag.BoolVar(&progress, "progress", false, "Show progress bar (optional, cannot be enabled when debug is enabled)")
flag.BoolVar(&v, "version", false, "Show version")
@ -63,27 +66,22 @@ func main() {
flag.Parse()
if v {
fmt.Printf("splitsh-lite version %s\n", version)
fmt.Fprintf(os.Stderr, "splitsh-lite version %s\n", version)
os.Exit(0)
}
if len(prefixes) == 0 {
fmt.Println("You must provide the directory to split via the --prefix flag")
fmt.Fprintln(os.Stderr, "You must provide the directory to split via the --prefix flag")
os.Exit(1)
}
if legacy {
fmt.Fprintf(os.Stderr, `The --legacy option is deprecated (use --git="<1.8.2" instead)`)
gitVersion = "<1.8.2"
}
config := &splitter.Config{
Path: path,
Origin: origin,
Prefixes: []*splitter.Prefix(prefixes),
Prefixes: prefixes,
Target: target,
Commit: commit,
Debug: debug && !quiet,
Debug: debug,
Scratch: scratch,
GitVersion: gitVersion,
}
@ -91,7 +89,7 @@ func main() {
result := &splitter.Result{}
var ticker *time.Ticker
if progress && !debug && !quiet {
if progress && !debug {
ticker = time.NewTicker(time.Millisecond * 50)
go func() {
for range ticker.C {
@ -100,9 +98,8 @@ func main() {
}()
}
err := splitter.Split(config, result)
if err != nil {
fmt.Println(err)
if err := splitter.Split(config, result); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
@ -110,9 +107,7 @@ func main() {
ticker.Stop()
}
if !quiet {
fmt.Fprintf(os.Stderr, "%d commits created, %d commits traversed, in %s\n", result.Created(), result.Traversed(), result.Duration(time.Millisecond))
}
fmt.Fprintf(os.Stderr, "%d commits created, %d commits traversed, in %s\n", result.Created(), result.Traversed(), result.Duration(time.Millisecond))
if result.Head() != nil {
fmt.Println(result.Head().String())

View file

@ -1,167 +1,171 @@
#!/bin/bash
set -e
set -f
set -euo pipefail
switchAsSammy()
{
AUTHOR_DATE=$1
COMMITTER_DATE=$2
export GIT_AUTHOR_NAME="Sammy Cobol"
export GIT_AUTHOR_EMAIL="<sammy.cobol@example.com>"
export GIT_AUTHOR_DATE="${AUTHOR_DATE}"
export GIT_COMMITTER_NAME="Fred Foobar"
export GIT_COMMITTER_EMAIL="<fred.foobar@example.com>"
export GIT_COMMITTER_DATE="${COMMITTER_DATE}"
}
switchAsFred() {
AUTHOR_DATE=$1
COMMITTER_DATE=$2
export GIT_AUTHOR_NAME="Fred Foobar"
export GIT_AUTHOR_EMAIL="<fred.foobar@example.com>"
export GIT_AUTHOR_DATE="${AUTHOR_DATE}"
export GIT_COMMITTER_NAME="Sammy Cobol"
export GIT_COMMITTER_EMAIL="<sammy.cobol@example.com>"
export GIT_COMMITTER_DATE="${COMMITTER_DATE}"
}
simpleTest() {
rm -rf simple
mkdir simple
cd simple
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
git add b
git commit -m"added b" > /dev/null
switchAsFred "Sat, 24 Nov 1973 21:01:02 +0200" "Sat, 24 Nov 1973 21:11:22 +0200"
echo "aa" > a
git add a
git commit -m"updated a" > /dev/null
switchAsFred "Sat, 24 Nov 1973 22:01:02 +0200" "Sat, 24 Nov 1973 22:11:22 +0200"
git rm a > /dev/null
git commit -m"updated a" > /dev/null
switchAsFred "Sat, 24 Nov 1973 23:01:02 +0200" "Sat, 24 Nov 1973 23:11:22 +0200"
echo "bb" > b/b
git add b/
git commit -m"updated b" > /dev/null
GIT_SUBTREE_SPLIT_SHA1=`git subtree split --prefix=b/ -q`
GIT_SPLITSH_SHA1=`$LITE_PATH --prefix=b/ 2>/dev/null`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SPLITSH_SHA1" ]; then
echo "Test #1 - OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SPLITSH_SHA1)"
else
echo "Test #1 - NOT OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SPLITSH_SHA1)"
exit 1
fi
GIT_SUBTREE_SPLIT_SHA1=`git subtree split --prefix=b/ -q 71777969e7c0ddd02e0c060c5c892c083971b953`
GIT_SPLITSH_SHA1=`$LITE_PATH --prefix=b/ --commit=71777969e7c0ddd02e0c060c5c892c083971b953 2>/dev/null`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SPLITSH_SHA1" ]; then
echo "Test #2 - OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SPLITSH_SHA1)"
else
echo "Test #2 - NOT OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SPLITSH_SHA1)"
exit 1
fi
cd ../
}
mergeTest() {
rm -rf merge
mkdir -p merge/src
cd merge
git init > /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:01:01 +0200" "Sat, 24 Nov 1973 19:01:01 +0200"
echo -e "a\n\nb\n\nc\n\n" > src/foo
git add src/foo
git commit -m"init" > /dev/null
git checkout -b branch1 2> /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:02:02 +0200" "Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\n\n" > src/foo
git commit -a -m"change 2" > /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:02:02 +0200" "Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\nchange 3\n" > src/foo
git commit -a -m"change 3" > /dev/null
git checkout main 2> /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:02:02 +0200" "Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\nchange 1\nb\n\nc\n\n" > src/foo
git commit -a -m"change 1" > /dev/null
git checkout -b branch2 2> /dev/null
switchAsSammy "Sat, 24 Nov 1973 19:02:02 +0200" "Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\n\n" > src/foo
git commit -a -m"change 2" > /dev/null
git checkout main 2> /dev/null
git checkout -b branch3 2> /dev/null
git merge branch1 --no-edit > /dev/null
git merge branch2 --no-edit -s ours > /dev/null
GIT_SUBTREE_SPLIT_SHA1_2="a2c4245703f8dac149ab666242a12e1d4b2510d9"
GIT_SUBTREE_SPLIT_SHA1_3="ba0dab2c4e99d68d11088f2c556af92851e93b14"
GIT_SPLITSH_SHA1_2=`$LITE_PATH --git="<2.8.0" --prefix=src/ 2>/dev/null`
GIT_SPLITSH_SHA1_3=`$LITE_PATH --prefix=src/ 2>/dev/null`
if [ "$GIT_SUBTREE_SPLIT_SHA1_2" == "$GIT_SPLITSH_SHA1_2" ]; then
echo "Test #3 - OK ($GIT_SUBTREE_SPLIT_SHA1_2 == $GIT_SPLITSH_SHA1_2)"
else
echo "Test #3 - NOT OK ($GIT_SUBTREE_SPLIT_SHA1_2 != $GIT_SPLITSH_SHA1_2)"
exit 1
fi
if [ "$GIT_SUBTREE_SPLIT_SHA1_3" == "$GIT_SPLITSH_SHA1_3" ]; then
echo "Test #4 - OK ($GIT_SUBTREE_SPLIT_SHA1_3 == $GIT_SPLITSH_SHA1_3)"
else
echo "Test #4 - NOT OK ($GIT_SUBTREE_SPLIT_SHA1_3 != $GIT_SPLITSH_SHA1_3)"
exit 1
fi
cd ../
}
twigSplitTest() {
# run on some Open-Source repositories
if [ ! -d Twig ]; then
git clone https://github.com/twigphp/Twig > /dev/null
fi
GIT_SUBTREE_SPLIT_SHA1="ea449b0f2acba7d489a91f88154687250d2bdf42"
GIT_SPLITSH_SHA1=`$LITE_PATH --prefix=lib/ --origin=refs/tags/v1.24.1 --path=Twig --scratch 2>/dev/null`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SPLITSH_SHA1" ]; then
echo "Test #5 - OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SPLITSH_SHA1)"
else
echo "Test #5 - NOT OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SPLITSH_SHA1)"
exit 1
fi
cd ../
}
LITE_PATH=`pwd`/splitsh-lite
if [ ! -e $LITE_PATH ]; then
echo "You first need to compile the splitsh-lite binary"
exit 1
fi
if [ ! -d splitter-lite-tests ]; then
mkdir splitter-lite-tests
fi
cd splitter-lite-tests
rm -rf simple
mkdir simple
cd simple
git init > /dev/null
export GIT_AUTHOR_NAME="Sammy Cobol"
export GIT_AUTHOR_EMAIL="<sammy.cobol@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:01:02 +0200"
export GIT_COMMITTER_NAME="Fred Foobar"
export GIT_COMMITTER_EMAIL="<fred.foobar@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:11:22 +0200"
echo "a" > a
git add a
git commit -m"added a" > /dev/null
export GIT_AUTHOR_NAME="Fred Foobar"
export GIT_AUTHOR_EMAIL="<fred.foobar@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 20:01:02 +0200"
export GIT_COMMITTER_NAME="Sammy Cobol"
export GIT_COMMITTER_EMAIL="<sammy.cobol@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 20:11:22 +0200"
mkdir b/
echo "b" > b/b
git add b
git commit -m"added b" > /dev/null
export GIT_AUTHOR_NAME="Fred Foobar"
export GIT_AUTHOR_EMAIL="<fred.foobar@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 21:01:02 +0200"
export GIT_COMMITTER_NAME="Sammy Cobol"
export GIT_COMMITTER_EMAIL="<sammy.cobol@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 21:11:22 +0200"
echo "aa" > a
git add a
git commit -m"updated a" > /dev/null
export GIT_AUTHOR_NAME="Fred Foobar"
export GIT_AUTHOR_EMAIL="<fred.foobar@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 22:01:02 +0200"
export GIT_COMMITTER_NAME="Sammy Cobol"
export GIT_COMMITTER_EMAIL="<sammy.cobol@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 22:11:22 +0200"
git rm a > /dev/null
git commit -m"updated a" > /dev/null
export GIT_AUTHOR_NAME="Fred Foobar"
export GIT_AUTHOR_EMAIL="<fred.foobar@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 23:01:02 +0200"
export GIT_COMMITTER_NAME="Sammy Cobol"
export GIT_COMMITTER_EMAIL="<sammy.cobol@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 23:11:22 +0200"
echo "bb" > b/b
git add b/
git commit -m"updated b" > /dev/null
GIT_SUBTREE_SPLIT_SHA1=`git subtree split --prefix=b/ -q`
GIT_SPLITSH_SHA1=`$GOPATH/src/github.com/splitsh/lite/lite --prefix=b/ --quiet`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SUBTREE_SPLIT_SHA1" ]; then
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SUBTREE_SPLIT_SHA1)"
else
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SUBTREE_SPLIT_SHA1)"
exit 1
fi
GIT_SUBTREE_SPLIT_SHA1=`git subtree split --prefix=b/ -q bff8cdfaaf78a8842b8d9241ccfd8fb6e026f508...`
GIT_SPLITSH_SHA1=`$GOPATH/src/github.com/splitsh/lite/lite --prefix=b/ --quiet --commit=bff8cdfaaf78a8842b8d9241ccfd8fb6e026f508`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SUBTREE_SPLIT_SHA1" ]; then
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SUBTREE_SPLIT_SHA1)"
else
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SUBTREE_SPLIT_SHA1)"
exit 1
fi
cd ../
rm -rf merge
mkdir -p merge/src
cd merge
git init > /dev/null
export GIT_AUTHOR_NAME="Sammy Cobol"
export GIT_AUTHOR_EMAIL="<sammy.cobol@example.com>"
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:01:01 +0200"
export GIT_COMMITTER_NAME="Fred Foobar"
export GIT_COMMITTER_EMAIL="<fred.foobar@example.com>"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:01:01 +0200"
echo -e "a\n\nb\n\nc\n\n" > src/foo
git add src/foo
git commit -m"init" > /dev/null
git checkout -b branch1 2> /dev/null
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\n\n" > src/foo
git commit -a -m"change 2" > /dev/null
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\nchange 3\n" > src/foo
git commit -a -m"change 3" > /dev/null
git checkout master 2> /dev/null
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\nchange 1\nb\n\nc\n\n" > src/foo
git commit -a -m"change 1" > /dev/null
git checkout -b branch2 2> /dev/null
export GIT_AUTHOR_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
export GIT_COMMITTER_DATE="Sat, 24 Nov 1973 19:02:02 +0200"
echo -e "a\n\nb\nchange 2\nc\n\n" > src/foo
git commit -a -m"change 2" > /dev/null
git checkout master 2> /dev/null
git checkout -b branch3 2> /dev/null
git merge branch1 --no-edit > /dev/null
git merge branch2 --no-edit -s ours > /dev/null
GIT_SUBTREE_SPLIT_SHA1_2="a2c4245703f8dac149ab666242a12e1d4b2510d9"
GIT_SUBTREE_SPLIT_SHA1_3="ba0dab2c4e99d68d11088f2c556af92851e93b14"
GIT_SPLITSH_SHA1_2=`$GOPATH/src/github.com/splitsh/lite/lite --git="<2.8.0" --prefix=src/ --quiet`
GIT_SPLITSH_SHA1_3=`$GOPATH/src/github.com/splitsh/lite/lite --prefix=src/ --quiet`
if [ "$GIT_SUBTREE_SPLIT_SHA1_2" == "$GIT_SUBTREE_SPLIT_SHA1_2" ]; then
echo "OK ($GIT_SUBTREE_SPLIT_SHA1_2 == $GIT_SUBTREE_SPLIT_SHA1_2)"
else
echo "OK ($GIT_SUBTREE_SPLIT_SHA1_2 != $GIT_SUBTREE_SPLIT_SHA1_2)"
exit 1
fi
if [ "$GIT_SUBTREE_SPLIT_SHA1_3" == "$GIT_SUBTREE_SPLIT_SHA1_3" ]; then
echo "OK ($GIT_SUBTREE_SPLIT_SHA1_3 == $GIT_SUBTREE_SPLIT_SHA1_3)"
else
echo "OK ($GIT_SUBTREE_SPLIT_SHA1_3 != $GIT_SUBTREE_SPLIT_SHA1_3)"
exit 1
fi
cd ../
# run on some Open-Source repositories
if [ ! -d Twig ]; then
git clone https://github.com/twigphp/Twig > /dev/null
fi
GIT_SUBTREE_SPLIT_SHA1="ea449b0f2acba7d489a91f88154687250d2bdf42"
GIT_SPLITSH_SHA1=`$GOPATH/src/github.com/splitsh/lite/lite --prefix=lib/ --origin=refs/tags/v1.24.1 --path=Twig --quiet --scratch`
if [ "$GIT_SUBTREE_SPLIT_SHA1" == "$GIT_SUBTREE_SPLIT_SHA1" ]; then
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 == $GIT_SUBTREE_SPLIT_SHA1)"
else
echo "OK ($GIT_SUBTREE_SPLIT_SHA1 != $GIT_SUBTREE_SPLIT_SHA1)"
exit 1
fi
cd ../
simpleTest
mergeTest
twigSplitTest

View file

@ -8,14 +8,15 @@ import (
"strconv"
"time"
"github.com/boltdb/bolt"
"github.com/libgit2/git2go"
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,38 +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 {
oid = git.NewOidFromBytes(result)
}
return nil
})
return oid
}
// which is newest or oldest
func (c *cache) reverse(rev *git.Oid, which string) *git.Oid {
var oid *git.Oid
c.db.View(func(tx *bolt.Tx) error {
result := tx.Bucket(c.key).Get(append(rev[0:20], []byte("/"+which)...))
if result == nil && which == "newest" {
result = tx.Bucket(c.key).Get(append(rev[0:20], []byte("/oldest")...))
}
if result != nil {
c.data["head/"+c.branch] = result
oid = git.NewOidFromBytes(result)
}
return nil
@ -103,10 +107,15 @@ func (c *cache) reverse(rev *git.Oid, which string) *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
@ -114,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"
"github.com/libgit2/git2go"
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

@ -4,7 +4,7 @@ import (
"sync"
"time"
"github.com/libgit2/git2go"
git "github.com/libgit2/git2go/v34"
)
// Result represents the outcome of a split
@ -73,7 +73,7 @@ func (r *Result) incTraversed() {
func (r *Result) end(start time.Time) {
r.mu.Lock()
r.duration = time.Now().Sub(start)
r.duration = time.Since(start)
r.mu.Unlock()
}
@ -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

@ -8,12 +8,12 @@ import (
"sync"
"time"
"github.com/libgit2/git2go"
git "github.com/libgit2/git2go/v34"
)
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, git.FilemodeBlob); 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 {
@ -430,7 +487,7 @@ func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oi
revWalk.Free()
}
if nil != identical && copyCommit == false {
if nil != identical && !copyCommit {
return identical, false, nil
}
@ -477,7 +534,12 @@ func (s *state) copyCommit(rev *git.Commit, tree *git.Tree, parents []*git.Commi
author.Email = "nobody@example.com"
}
oid, err := s.repo.CreateCommit("", author, rev.Committer(), message, tree, parents...)
committer := rev.Committer()
if committer.Email == "" {
committer.Email = "nobody@example.com"
}
oid, err := s.repo.CreateCommit("", author, committer, message, tree, parents...)
if err != nil {
return nil, err
}
@ -491,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)
@ -519,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)
@ -532,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

@ -7,10 +7,10 @@ import (
"regexp"
"strings"
"github.com/libgit2/git2go"
git "github.com/libgit2/git2go/v34"
)
var messageNormalizer = regexp.MustCompile("\\s*\\r?\\n")
var messageNormalizer = regexp.MustCompile(`\s*\r?\n`)
// GitDirectory returns the .git directory for a given directory
func GitDirectory(path string) string {
@ -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()