Merge pull request #80 from splitsh/excludes

Support excluding directories
This commit is contained in:
Fabien Potencier 2024-03-08 11:25:27 +01:00 committed by GitHub
commit 931f587a87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 110 additions and 15 deletions

View file

@ -128,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);

17
main.go
View file

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

View file

@ -77,6 +77,9 @@ 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)

View file

@ -3,6 +3,7 @@ package splitter
import (
"fmt"
"log"
"strings"
"sync"
git "github.com/libgit2/git2go/v34"
@ -11,8 +12,27 @@ import (
// 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

View file

@ -61,13 +61,17 @@ func newState(config *Config, result *Result) (*state, error) {
}
if config.Debug {
state.logger.Printf("Splitting %s\n", state.originBranch)
state.logger.Printf("Splitting %s", state.originBranch)
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
}
@ -284,6 +288,14 @@ func (s *state) treeByPaths(tree *git.Tree) (*git.Tree, error) {
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)
@ -360,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