diff --git a/README.md b/README.md index 52a5c3a..afe7814 100644 --- a/README.md +++ b/README.md @@ -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); diff --git a/main.go b/main.go index bbd9a61..a67b430 100644 --- a/main.go +++ b/main.go @@ -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, diff --git a/splitter/cache.go b/splitter/cache.go index 60b1ac4..fca8953 100644 --- a/splitter/cache.go +++ b/splitter/cache.go @@ -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) diff --git a/splitter/config.go b/splitter/config.go index 3448daa..b5aefdb 100644 --- a/splitter/config.go +++ b/splitter/config.go @@ -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 diff --git a/splitter/state.go b/splitter/state.go index 124fd36..7cdc417 100644 --- a/splitter/state.go +++ b/splitter/state.go @@ -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