Compare commits

...

5 commits

Author SHA1 Message Date
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
5 changed files with 118 additions and 24 deletions

View file

@ -128,10 +128,18 @@ splitsh-lite --prefix=lib/ --origin=origin/1.0 --path=/path/to/repo
Available options: Available options:
* `--prefix` is the prefix of the directory to split; you can put the split * `--prefix` is the prefix of the directory to split; the value can be one of
contents in a sub-directory of the target repository by using the the following:
`--prefix=from:to` syntax; split several directories by passing multiple
`--prefix` flags; * `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); * `--path` is the path of the repository to split (current directory by default);

17
main.go
View file

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

View file

@ -77,6 +77,9 @@ func key(config *Config) []byte {
for _, prefix := range config.Prefixes { for _, prefix := range config.Prefixes {
io.WriteString(h, prefix.From) io.WriteString(h, prefix.From)
io.WriteString(h, prefix.To) io.WriteString(h, prefix.To)
for _, exclude := range prefix.Excludes {
io.WriteString(h, exclude)
}
} }
return h.Sum(nil) return h.Sum(nil)

View file

@ -3,6 +3,7 @@ package splitter
import ( import (
"fmt" "fmt"
"log" "log"
"strings"
"sync" "sync"
git "github.com/libgit2/git2go/v34" git "github.com/libgit2/git2go/v34"
@ -11,8 +12,27 @@ import (
// Prefix represents which paths to split // Prefix represents which paths to split
type Prefix struct { type Prefix struct {
From string From string
To 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 // Config represents a split configuration

View file

@ -61,13 +61,17 @@ func newState(config *Config, result *Result) (*state, error) {
} }
if config.Debug { if config.Debug {
state.logger.Printf("Splitting %s\n", state.originBranch) state.logger.Printf("Splitting %s", state.originBranch)
for _, v := range config.Prefixes { for _, v := range config.Prefixes {
to := v.To to := v.To
if to == "" { if to == "" {
to = "ROOT" 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 // simplePrefix contains the prefix when there is only one
// with an empty value (target) // 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 state.simplePrefix = config.Prefixes[0].From
} }
@ -284,6 +288,14 @@ func (s *state) treeByPaths(tree *git.Tree) (*git.Tree, error) {
continue continue
} }
if len(prefix.Excludes) > 0 {
prunedTree, err := s.pruneTree(splitTree, prefix.Excludes)
if err != nil {
return nil, err
}
splitTree = prunedTree
}
// adding the prefix // adding the prefix
if prefix.To != "" { if prefix.To != "" {
prefixedTree, err = s.addPrefixToTree(splitTree, prefix.To) prefixedTree, err = s.addPrefixToTree(splitTree, prefix.To)
@ -360,6 +372,53 @@ func (s *state) addPrefixToTree(tree *git.Tree, prefix string) (*git.Tree, error
return prefixedTree, nil 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) { func (s *state) copyOrSkip(rev *git.Commit, tree *git.Tree, newParents []*git.Oid) (*git.Oid, bool, error) {
var identical, nonIdentical *git.Oid var identical, nonIdentical *git.Oid
var gotParents []*git.Oid var gotParents []*git.Oid
@ -522,12 +581,18 @@ func (s *state) legacyMessage(rev *git.Commit) string {
// pushRevs sets the range to split // pushRevs sets the range to split
func (s *state) pushRevs(revWalk *git.RevWalk) error { 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() s.repoMu.Lock()
defer s.repoMu.Unlock() defer s.repoMu.Unlock()
// find the latest split sha1 if any on origin
var start *git.Oid 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.originBranch))
}
// find the latest split sha1 if any on origin
var err error var err error
if s.config.Commit != "" { if s.config.Commit != "" {
start, err = git.NewOid(s.config.Commit) start, err = git.NewOid(s.config.Commit)
@ -538,13 +603,6 @@ func (s *state) pushRevs(revWalk *git.RevWalk) error {
return revWalk.PushRange(fmt.Sprintf("%s^..%s", start, s.originBranch)) return revWalk.PushRange(fmt.Sprintf("%s^..%s", start, s.originBranch))
} }
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.originBranch)
if err != nil { if err != nil {
return err return err