From 5dbf39b5b414c0bc0d1eb14eee66ce025626f9d3 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 8 Mar 2024 08:28:46 +0100 Subject: [PATCH] Add excluding directories support --- README.md | 16 +++++++++--- main.go | 10 +++++-- splitter/cache.go | 3 +++ splitter/config.go | 5 ++-- splitter/state.go | 65 +++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 88 insertions(+), 11 deletions(-) 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..ea79764 100644 --- a/main.go +++ b/main.go @@ -24,8 +24,14 @@ func (p *prefixesFlag) Set(value string) error { parts := strings.Split(value, ":") from := parts[0] to := "" - if len(parts) > 1 { + excludes := make([]string, 0) + if len(parts) >= 2 { to = parts[1] + if len(parts) > 2 { + for _, exclude := range parts[2:] { + excludes = append(excludes, exclude) + } + } } // value must be unique @@ -36,7 +42,7 @@ func (p *prefixesFlag) Set(value string) error { } } - *p = append(*p, &splitter.Prefix{From: from, To: to}) + *p = append(*p, &splitter.Prefix{From: from, To: to, Excludes: excludes}) return nil } 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..7a4a0ef 100644 --- a/splitter/config.go +++ b/splitter/config.go @@ -11,8 +11,9 @@ import ( // Prefix represents which paths to split type Prefix struct { - From string - To string + From string + To string + Excludes []string } // 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