mirror of
https://github.com/splitsh/lite.git
synced 2024-06-06 07:42:22 +02:00
Compare commits
63 commits
Author | SHA1 | Date | |
---|---|---|---|
fd72641be1 | |||
931f587a87 | |||
bd864e2ca6 | |||
5dbf39b5b4 | |||
eaede0baa4 | |||
ca1a6cef48 | |||
cf9970e7ff | |||
a5c6982fcd | |||
8f693c4bc2 | |||
2e82f4a273 | |||
e6d25ea821 | |||
4aa9676740 | |||
26da73eea4 | |||
5d55360bed | |||
d1ff5e1dc9 | |||
3a3e4e00a4 | |||
336cb150a8 | |||
f19d527a89 | |||
6cdc0a4137 | |||
08804f1e89 | |||
e5e71931e0 | |||
806f4764e8 | |||
f2d9253550 | |||
852bb257f0 | |||
f394dc9e6e | |||
f69839fdbe | |||
cb8dc18103 | |||
6e0e2b2401 | |||
faf6867922 | |||
a11445a29c | |||
cd4e65afcd | |||
000fd5e163 | |||
93e0ef1670 | |||
dba129b8af | |||
f54790e733 | |||
493f39ffeb | |||
01594a43d0 | |||
9b1b817de9 | |||
faf40091fa | |||
d47cfda32a | |||
8ac3dcd530 | |||
e577e7eae9 | |||
681e207a80 | |||
f75aea5311 | |||
34a1ea69dd | |||
7730112d8a | |||
a60a1c610f | |||
f53198b2fd | |||
1814017fb1 | |||
ea7e7e28b0 | |||
7a31d22b2d | |||
9d643aa621 | |||
cb9c28b8c3 | |||
7e385e9511 | |||
63a4daebad | |||
86cf1d949a | |||
260fd61355 | |||
3fb2ea224b | |||
538f5638df | |||
7ed5014241 | |||
0e46db1f94 | |||
ac76236d44 | |||
2ba69378ab |
42
.github/workflows/tests.yml
vendored
Normal file
42
.github/workflows/tests.yml
vendored
Normal 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
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
splitter-lite-tests/
|
||||
vendor/
|
||||
|
|
17
CHANGELOG
Normal file
17
CHANGELOG
Normal 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
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2015-2016 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
|
||||
|
|
142
README.md
142
README.md
|
@ -1,18 +1,23 @@
|
|||
Monolithic Repositories to Many Repositories made easy
|
||||
======================================================
|
||||
Git Subtree Splitter
|
||||
====================
|
||||
|
||||
**tl;dr**: **splitsh-lite** is a replacement for the `subtree split` Git
|
||||
built-in command that is much faster and has more features at the same time.
|
||||
**splitsh-lite** replaces the `subtree split` Git built-in command to make
|
||||
**splitting a monolithic repository** to read-only standalone repositories
|
||||
**easy and fast**.
|
||||
|
||||
When starting a new project, do you store all the code in one monolithic
|
||||
repository? Or are you creating many repositories?
|
||||
Why do I need this tool?
|
||||
------------------------
|
||||
|
||||
Both strategies work well but both have drawbacks as well. **splitsh** helps use
|
||||
both strategies at the same time by providing tools that automatically
|
||||
synchronize a mono repository to many repositories.
|
||||
When starting a project, do you store all the code in one repository? Or are
|
||||
you creating many standalone repositories?
|
||||
|
||||
**splitsh-lite** is a sub-project with the goal of providing a faster replacement
|
||||
of the `git subtree split` command.
|
||||
Both strategies work well and both have drawbacks as well. **splitsh** helps
|
||||
use both strategies by providing tools that automatically **synchronize a
|
||||
monolithic repository to standalone repositories** in real-time.
|
||||
|
||||
**splitsh-lite** is a sub-project that provides a faster implementation of the
|
||||
`git subtree split` command, which helps create standalone repositories for one
|
||||
or more sub-directories of a main repository.
|
||||
|
||||
If you want to learn more about monorepo vs manyrepos, watch this [4-minute
|
||||
lightning talk](http://www.thedotpost.com/2016/05/fabien-potencier-monolithic-repositories-vs-many-repositories)
|
||||
|
@ -20,37 +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.
|
||||
|
||||
The main **splitsh-lite** feature is its ability to create a branch in a repository
|
||||
from one or many directories.
|
||||
**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
|
||||
------------
|
||||
|
||||
Install libgit2:
|
||||
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
|
||||
go get -d github.com/libgit2/git2go
|
||||
cd $GOPATH/src/github.com/libgit2/git2go
|
||||
git checkout next
|
||||
git submodule update --init
|
||||
make install
|
||||
sed -i -e 's/v34/v31/g' go.mod splitter/*.go
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
Compiling
|
||||
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.
|
||||
|
||||
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
|
||||
|
@ -70,21 +102,21 @@ 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
|
||||
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.
|
||||
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 split
|
||||
a different branch by passing it explicitly with `--origin` (mandatory when
|
||||
splitting a bare repository):
|
||||
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):
|
||||
|
||||
```bash
|
||||
splitsh-lite --prefix=lib/ --origin=origin/1.0
|
||||
splitsh-lite --prefix=lib/ --origin=origin/master
|
||||
```
|
||||
|
||||
You don't even need to run the command from the Git repository directory if you
|
||||
|
@ -96,35 +128,39 @@ 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 directory by using the `--prefix=from:to` syntax; splitting
|
||||
several directories is also possible by passing multiple `--prefix` options;
|
||||
* `--prefix` is the prefix of the directory to split; the value can be one of
|
||||
the following:
|
||||
|
||||
* `--path` is the path to the repository to split (current directory by default);
|
||||
* `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);
|
||||
|
||||
* `--origin` is the Git reference for the origin (can be any Git reference
|
||||
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`);
|
||||
* `--target` creates a reference for the tip of the split (can be any Git
|
||||
reference like `heads/xxx`, `tags/xxx`, `origin/xxx`, or any `refs/xxx`);
|
||||
|
||||
* `--progress` displays a nice progress bar during the split;
|
||||
|
||||
* `--quiet` suppresses all output on stderr (useful when run from an automated
|
||||
script).
|
||||
* `--progress` displays a progress bar;
|
||||
|
||||
* `--scratch` flushes the cache (useful when a branch is force pushed or in
|
||||
case of corruption)
|
||||
case of a cache corruption).
|
||||
|
||||
* `--git` simulates old versions of `git subtree split`. **splitsh** generates
|
||||
the same `sha1`s as the latest version of Git by default (`latest`).
|
||||
Simulate old versions of Git by using `<1.8.2` or `<2.8.0`.
|
||||
Migrating from `git subtree split`
|
||||
----------------------------------
|
||||
|
||||
**splitsh** provides more features including a sanity checker, GitHub integration
|
||||
for real-time splitting, tagging 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!
|
||||
Migrating from `git subtree split` to `splith-lite` is easy as both tools
|
||||
generate the same `sha1`s.
|
||||
|
||||
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).
|
||||
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.
|
||||
|
|
13
go.mod
Normal file
13
go.mod
Normal 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
30
go.sum
Normal 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=
|
61
main.go
61
main.go
|
@ -10,6 +10,10 @@ import (
|
|||
"github.com/splitsh/lite/splitter"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
)
|
||||
|
||||
type prefixesFlag []*splitter.Prefix
|
||||
|
||||
func (p *prefixesFlag) String() string {
|
||||
|
@ -20,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 split 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 bool
|
||||
var scratch, debug, progress, v bool
|
||||
|
||||
func init() {
|
||||
flag.Var(&prefixes, "prefix", "The directory(ies) to split")
|
||||
|
@ -48,32 +57,31 @@ 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")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if len(prefixes) == 0 {
|
||||
fmt.Println("You must provide the directory to split via the --prefix flag")
|
||||
os.Exit(1)
|
||||
if v {
|
||||
fmt.Fprintf(os.Stderr, "splitsh-lite version %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if legacy {
|
||||
fmt.Fprintf(os.Stderr, `The --legacy option is deprecated (use --git="<1.8.2" instead)`)
|
||||
gitVersion = "<1.8.2"
|
||||
if len(prefixes) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "You must provide the directory to split via the --prefix flag")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
@ -81,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 {
|
||||
|
@ -90,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)
|
||||
}
|
||||
|
||||
|
@ -100,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())
|
||||
|
|
322
run-tests.sh
322
run-tests.sh
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -26,7 +26,7 @@ func newState(config *Config, result *Result) (*state, error) {
|
|||
var err error
|
||||
|
||||
// validate config
|
||||
if err := config.Validate(); err != nil {
|
||||
if err = config.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue