diff --git a/go.mod b/go.mod index 5f9c7f8..7e9cbe3 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,9 @@ module github.com/multiverse-vcs/go-multiverse go 1.16 require ( - github.com/alecthomas/chroma v0.8.2 + github.com/bmatcuk/doublestar/v3 v3.0.0 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect github.com/ipfs/go-bitswap v0.3.2 - github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-blockservice v0.1.4 github.com/ipfs/go-cid v0.0.7 github.com/ipfs/go-datastore v0.4.5 @@ -22,7 +21,6 @@ require ( github.com/ipfs/go-path v0.0.9 github.com/ipfs/go-unixfs v0.2.4 github.com/ipld/go-car v0.1.0 - github.com/julienschmidt/httprouter v1.3.0 github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-libp2p v0.12.0 github.com/libp2p/go-libp2p-connmgr v0.2.4 @@ -40,7 +38,6 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/onsi/ginkgo v1.14.0 // indirect github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 - github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f github.com/urfave/cli/v2 v2.3.0 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect diff --git a/go.sum b/go.sum index c6ff8a1..cbcb722 100644 --- a/go.sum +++ b/go.sum @@ -11,19 +11,12 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= -github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg= -github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= -github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= -github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= -github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/bmatcuk/doublestar/v3 v3.0.0 h1:TQtVPlDnAYwcrVNB2JiGuMc++H5qzWZd9PhkNo5WyHI= +github.com/bmatcuk/doublestar/v3 v3.0.0/go.mod h1:6PcTVMw80pCY1RVuoqu3V++99uQB3vsSYKPTd8AWA0k= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= @@ -60,8 +53,6 @@ github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= -github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -81,8 +72,6 @@ github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70d github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= -github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -306,8 +295,6 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -581,12 +568,9 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= @@ -702,9 +686,6 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f h1:8P2MkG70G76gnZBOPGwmMIgwBb/rESQuwsJ7K8ds4NE= -github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -853,11 +834,9 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210123111255-9b0068b26619 h1:yLLDsUUPDliIQpKl7BjVb1igwngIMH2GBjo1VpwLTE0= golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/ignore/ignore.go b/internal/ignore/ignore.go new file mode 100644 index 0000000..689665d --- /dev/null +++ b/internal/ignore/ignore.go @@ -0,0 +1,60 @@ +package ignore + +import ( + "os" + "path/filepath" + "strings" +) + +// IgnoreFile is the name of the ignore file. +const IgnoreFile = ".multignore" + +// Filter is a list of paths to ignore. +type Filter []Rule + +// New returns a new ignore filter. +func New(dir string, patterns ...string) Filter { + var rules []Rule + for _, p := range patterns { + rule := ParseRule(filepath.Join(dir, p)) + rules = append(rules, rule) + } + + return Filter(rules) +} + +// Load returns the ignore filter from the given directory. +func Load(dir string) (Filter, error) { + data, err := os.ReadFile(filepath.Join(dir, IgnoreFile)) + if os.IsNotExist(err) { + return nil, nil + } + + if err != nil { + return nil, err + } + + patterns := strings.Split(string(data), "\n") + return New(dir, patterns...), nil +} + +// Match returns true if the path matches any ignore rules. +func (f Filter) Match(name string) bool { + for _, r := range f { + if match, _ := r.Match(name); match { + return true + } + } + + return false +} + +// Merge combines the ignore rules with the other ignore. +func (f Filter) Merge(other Filter) Filter { + var merge Filter + for _, r := range other { + merge = append(merge, r) + } + + return append(f, merge...) +} diff --git a/internal/ignore/ignore_test.go b/internal/ignore/ignore_test.go new file mode 100644 index 0000000..6e17daa --- /dev/null +++ b/internal/ignore/ignore_test.go @@ -0,0 +1,58 @@ +package ignore + +import ( + "testing" +) + +func TestMatch(t *testing.T) { + ignore := New("test", "*.exe") + + if !ignore.Match("test/foo/bar.exe") { + t.Error("expected ignore to match") + } + + if !ignore.Match("foo.exe") { + t.Error("expected ignore to match") + } +} + +func TestLoad(t *testing.T) { + ignore, err := Load("testdata") + if err != nil { + t.Fatal("failed to load ignore file") + } + + if !ignore.Match("foo/bar") { + t.Error("expected ignore to match") + } + + if !ignore.Match("bar/foo") { + t.Error("expected ignore to match") + } + + if ignore.Match("foo.exe") { + t.Error("expected ignore not to match") + } +} + +func TestMerge(t *testing.T) { + ignore, err := Load("testdata") + if err != nil { + t.Fatal("failed to load ignore file") + } + + other := New("test", "*.exe") + merge := ignore.Merge(other) + + if !merge.Match("foo/bar") { + t.Error("expected ignore to match") + } + + if !merge.Match("bar/foo") { + t.Error("expected ignore to match") + } + + if !merge.Match("foo.exe") { + t.Error("expected ignore to match") + } +} diff --git a/internal/ignore/rule.go b/internal/ignore/rule.go new file mode 100644 index 0000000..3f39d4b --- /dev/null +++ b/internal/ignore/rule.go @@ -0,0 +1,79 @@ +package ignore + +import ( + "path/filepath" + "strings" +) + +// Rule is used to match file paths. +type Rule interface { + // Match returns true if the path matches. + Match(string) (bool, error) +} + +// compile time interface checks +var _ Rule = (*NegateRule)(nil) +var _ Rule = (BaseRule)("") +var _ Rule = (PathRule)("") +var _ Rule = (EmptyRule)("") + +// NegateRule negates a rule. +type NegateRule struct { + wrapped Rule +} + +// BaseRule matches the base of the path. +type BaseRule string + +// PathRule matches the entire path. +type PathRule string + +// EmptyRule never matches. +type EmptyRule string + +// Match returns true if the path matches. +func (r NegateRule) Match(name string) (bool, error) { + match, err := r.wrapped.Match(name) + return !match, err +} + +// Match returns true if the path matches. +func (r BaseRule) Match(name string) (bool, error) { + base := filepath.Base(name) + return filepath.Match(string(r), base) +} + +// Match returns true if the path matches. +func (r PathRule) Match(name string) (bool, error) { + return filepath.Match(string(r), name) +} + +// Match returns true if the path matches. +func (r EmptyRule) Match(name string) (bool, error) { + return false, nil +} + +// ParseRule returns a rule for the given pattern. +func ParseRule(pattern string) Rule { + pattern = strings.TrimSpace(pattern) + if pattern == "" { + return EmptyRule(pattern) + } + + if strings.HasPrefix(pattern, "#") { + return EmptyRule(pattern) + } + + var rule Rule + if strings.Contains(pattern, "/") { + rule = PathRule(pattern) + } else { + rule = BaseRule(filepath.Base(pattern)) + } + + if strings.HasPrefix(pattern, "!") { + rule = NegateRule{rule} + } + + return rule +} diff --git a/internal/ignore/testdata/.multignore b/internal/ignore/testdata/.multignore new file mode 100644 index 0000000..ac25c7f --- /dev/null +++ b/internal/ignore/testdata/.multignore @@ -0,0 +1,7 @@ +# *.exe + +foo + +!bar + +/foo/bar \ No newline at end of file diff --git a/pkg/command/checkout.go b/pkg/command/checkout.go index 2545fe9..9c6f6f5 100644 --- a/pkg/command/checkout.go +++ b/pkg/command/checkout.go @@ -5,11 +5,12 @@ import ( "os" cid "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/dag" "github.com/multiverse-vcs/go-multiverse/pkg/fs" "github.com/multiverse-vcs/go-multiverse/pkg/object" - "github.com/urfave/cli/v2" ) // NewCheckoutCommand returns a new cli command. @@ -71,7 +72,7 @@ func NewCheckoutCommand() *cli.Command { cli.ShowAppHelpAndExit(c, -1) } - stash, err := cc.Tree(c.Context) + stash, err := fs.Add(c.Context, cc.DAG, cc.Root, context.DefaultIgnore) if err != nil { return err } diff --git a/pkg/command/commit.go b/pkg/command/commit.go index 504b1ae..f300ac3 100644 --- a/pkg/command/commit.go +++ b/pkg/command/commit.go @@ -4,10 +4,12 @@ import ( "errors" "os" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/dag" + "github.com/multiverse-vcs/go-multiverse/pkg/fs" "github.com/multiverse-vcs/go-multiverse/pkg/object" - "github.com/urfave/cli/v2" ) // NewCommitCommand returns a new cli command. @@ -33,13 +35,12 @@ func NewCommitCommand() *cli.Command { return err } - branch := cc.Config.Branches[cc.Config.Branch] - - tree, err := cc.Tree(c.Context) + tree, err := fs.Add(c.Context, cc.DAG, cc.Root, context.DefaultIgnore) if err != nil { return err } + branch := cc.Config.Branches[cc.Config.Branch] diffs, err := dag.Status(c.Context, cc.DAG, tree, branch.Head) if err != nil { return err diff --git a/pkg/command/context/context.go b/pkg/command/context/context.go index ddc2483..cdcc2c6 100644 --- a/pkg/command/context/context.go +++ b/pkg/command/context/context.go @@ -1,7 +1,6 @@ package context import ( - "context" "errors" "os" "path/filepath" @@ -12,19 +11,15 @@ import ( offline "github.com/ipfs/go-ipfs-exchange-offline" ipld "github.com/ipfs/go-ipld-format" merkledag "github.com/ipfs/go-merkledag" - "github.com/multiverse-vcs/go-multiverse/pkg/fs" - ignore "github.com/sabhiram/go-gitignore" -) -const ( - // DotDir is the name of the dot directory. - DotDir = ".multi" - // IgnoreFile is the name of the ignore file. - IgnoreFile = "multi.ignore" + "github.com/multiverse-vcs/go-multiverse/internal/ignore" ) -// IgnoreRules contains default ignore rules. -var IgnoreRules = []string{".git", DotDir} +// DotDir is the name of the dot directory. +const DotDir = ".multi" + +// DefaultIgnore contans the default ignore rules. +var DefaultIgnore = ignore.New("", ".git", ".svn", ".hg", ".multi") // Context contains command context. type Context struct { @@ -105,29 +100,3 @@ func Root(root string) (string, error) { return Root(parent) } - -// Ignore returns ignore rules for the current context. -func (c *Context) Ignore() (*ignore.GitIgnore, error) { - path := filepath.Join(c.Root, IgnoreFile) - - _, err := os.Lstat(path) - if err == nil { - return ignore.CompileIgnoreFileAndLines(path, IgnoreRules...) - } - - if os.IsNotExist(err) { - return ignore.CompileIgnoreLines(IgnoreRules...), nil - } - - return nil, err -} - -// Tree adds and returns the working tree node. -func (c *Context) Tree(ctx context.Context) (ipld.Node, error) { - ignore, err := c.Ignore() - if err != nil { - return nil, err - } - - return fs.Add(ctx, c.DAG, c.Root, ignore) -} diff --git a/pkg/command/log.go b/pkg/command/log.go index 575018a..2fb009f 100644 --- a/pkg/command/log.go +++ b/pkg/command/log.go @@ -40,7 +40,7 @@ func NewLogCommand() *cli.Command { fmt.Printf("\n\t%s\n\n", commit.Message) return true } - + return dag.Walk(c.Context, cc.DAG, branch.Head, visit) }, } diff --git a/pkg/command/pull.go b/pkg/command/pull.go index f47da26..1845716 100644 --- a/pkg/command/pull.go +++ b/pkg/command/pull.go @@ -6,12 +6,14 @@ import ( "os" cid "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/dag" "github.com/multiverse-vcs/go-multiverse/pkg/fs" + "github.com/multiverse-vcs/go-multiverse/pkg/merge" "github.com/multiverse-vcs/go-multiverse/pkg/rpc" "github.com/multiverse-vcs/go-multiverse/pkg/rpc/repo" - "github.com/urfave/cli/v2" ) // NewPullCommand returns a new cli command. @@ -51,12 +53,12 @@ func NewPullCommand() *cli.Command { remote := branch.Remote source := cc.Config.Branch - tree, err := cc.Tree(c.Context) + stash, err := fs.Add(c.Context, cc.DAG, cc.Root, context.DefaultIgnore) if err != nil { return err } - status, err := dag.Status(c.Context, cc.DAG, tree, branch.Head) + status, err := dag.Status(c.Context, cc.DAG, stash, branch.Head) if err != nil { return err } @@ -102,22 +104,22 @@ func NewPullCommand() *cli.Command { return err } - base, err := dag.Base(c.Context, cc.DAG, branch.Head, root) + base, err := merge.Base(c.Context, cc.DAG, branch.Head, root) if err != nil { return err } - merge, err := dag.Merge(c.Context, cc.DAG, base, branch.Head, root) + tree, err := merge.Tree(c.Context, cc.DAG, base, branch.Head, root) if err != nil { return err } - if err := fs.Write(c.Context, cc.DAG, cc.Root, merge); err != nil { + if err := fs.Write(c.Context, cc.DAG, cc.Root, tree); err != nil { return err } branch.Head = root - branch.Stash = merge.Cid() + branch.Stash = tree.Cid() return cc.Config.Write() }, } diff --git a/pkg/command/push.go b/pkg/command/push.go index 801391e..445c401 100644 --- a/pkg/command/push.go +++ b/pkg/command/push.go @@ -5,11 +5,13 @@ import ( "errors" "os" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/dag" + "github.com/multiverse-vcs/go-multiverse/pkg/merge" "github.com/multiverse-vcs/go-multiverse/pkg/rpc" "github.com/multiverse-vcs/go-multiverse/pkg/rpc/repo" - "github.com/urfave/cli/v2" ) // NewPushCommand returns a new cli command. @@ -77,7 +79,7 @@ func NewPushCommand() *cli.Command { refs := reply.Repository.Heads() head := reply.Repository.Branches[target] - base, err := dag.Base(c.Context, cc.DAG, head, branch.Head) + base, err := merge.Base(c.Context, cc.DAG, head, branch.Head) if err != nil { return err } diff --git a/pkg/command/status.go b/pkg/command/status.go index 9e8fce8..b7f94c3 100644 --- a/pkg/command/status.go +++ b/pkg/command/status.go @@ -6,9 +6,11 @@ import ( "sort" "github.com/ipfs/go-merkledag/dagutils" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/dag" - "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/fs" ) // NewStatusCommand returns a new cli command. @@ -27,7 +29,7 @@ func NewStatusCommand() *cli.Command { return err } - tree, err := cc.Tree(c.Context) + tree, err := fs.Add(c.Context, cc.DAG, cc.Root, context.DefaultIgnore) if err != nil { return err } @@ -46,7 +48,7 @@ func NewStatusCommand() *cli.Command { fmt.Printf("Tracking changes on branch %s:\n", cc.Config.Branch) fmt.Printf(" (all files are automatically considered for commit)\n") - fmt.Printf(" (to stop tracking files add rules to '%s')\n", context.IgnoreFile) + fmt.Printf(" (to stop tracking files add rules to '.multignore')\n") for _, p := range paths { switch status[p] { diff --git a/pkg/command/switch.go b/pkg/command/switch.go index a55d516..e81fb97 100644 --- a/pkg/command/switch.go +++ b/pkg/command/switch.go @@ -4,9 +4,10 @@ import ( "errors" "os" + "github.com/urfave/cli/v2" + "github.com/multiverse-vcs/go-multiverse/pkg/command/context" "github.com/multiverse-vcs/go-multiverse/pkg/fs" - "github.com/urfave/cli/v2" ) // NewSwitchCommand returns a new cli command. @@ -48,7 +49,7 @@ func NewSwitchCommand() *cli.Command { return errors.New("branch does not exist") } - stash, err := cc.Tree(c.Context) + stash, err := fs.Add(c.Context, cc.DAG, cc.Root, context.DefaultIgnore) if err != nil { return err } diff --git a/pkg/fs/chunk.go b/pkg/dag/chunk.go similarity index 98% rename from pkg/fs/chunk.go rename to pkg/dag/chunk.go index 0d02b14..134d537 100644 --- a/pkg/fs/chunk.go +++ b/pkg/dag/chunk.go @@ -1,4 +1,4 @@ -package fs +package dag import ( "context" diff --git a/pkg/fs/add.go b/pkg/fs/add.go index 984ad45..c78c7f0 100644 --- a/pkg/fs/add.go +++ b/pkg/fs/add.go @@ -3,7 +3,6 @@ package fs import ( "context" "errors" - "io/ioutil" "os" "path/filepath" @@ -11,11 +10,13 @@ import ( "github.com/ipfs/go-merkledag" unixfs "github.com/ipfs/go-unixfs" "github.com/ipfs/go-unixfs/io" - ignore "github.com/sabhiram/go-gitignore" + + "github.com/multiverse-vcs/go-multiverse/internal/ignore" + "github.com/multiverse-vcs/go-multiverse/pkg/dag" ) // Add creates a node from the file at path and adds it to the merkle dag. -func Add(ctx context.Context, dag ipld.DAGService, path string, ignore *ignore.GitIgnore) (ipld.Node, error) { +func Add(ctx context.Context, ds ipld.DAGService, path string, filter ignore.Filter) (ipld.Node, error) { stat, err := os.Lstat(path) if err != nil { return nil, err @@ -23,29 +24,29 @@ func Add(ctx context.Context, dag ipld.DAGService, path string, ignore *ignore.G switch mode := stat.Mode(); { case mode.IsRegular(): - return addFile(ctx, dag, path) + return addFile(ctx, ds, path) case mode&os.ModeSymlink != 0: - return addSymlink(ctx, dag, path) + return addSymlink(ctx, ds, path) case mode.IsDir(): - return addDir(ctx, dag, path, ignore) + return addDir(ctx, ds, path, filter) default: return nil, errors.New("invalid file type") } } // addFile creates a dag node from the file at the given path. -func addFile(ctx context.Context, dag ipld.DAGService, path string) (ipld.Node, error) { +func addFile(ctx context.Context, ds ipld.DAGService, path string) (ipld.Node, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() - return Chunk(ctx, dag, file) + return dag.Chunk(ctx, ds, file) } // addSymlink creates a dag node from the symlink at the given path. -func addSymlink(ctx context.Context, dag ipld.DAGService, path string) (ipld.Node, error) { +func addSymlink(ctx context.Context, ds ipld.DAGService, path string) (ipld.Node, error) { target, err := os.Readlink(path) if err != nil { return nil, err @@ -57,7 +58,7 @@ func addSymlink(ctx context.Context, dag ipld.DAGService, path string) (ipld.Nod } node := merkledag.NodeWithData(data) - if err := dag.Add(ctx, node); err != nil { + if err := ds.Add(ctx, node); err != nil { return nil, err } @@ -65,35 +66,42 @@ func addSymlink(ctx context.Context, dag ipld.DAGService, path string) (ipld.Nod } // addDir creates a dag node from the directory entries at the given path. -func addDir(ctx context.Context, dag ipld.DAGService, path string, ignore *ignore.GitIgnore) (ipld.Node, error) { - entries, err := ioutil.ReadDir(path) +func addDir(ctx context.Context, ds ipld.DAGService, path string, filter ignore.Filter) (ipld.Node, error) { + entries, err := os.ReadDir(path) if err != nil { return nil, err } - dir := io.NewDirectory(dag) + other, err := ignore.Load(path) + if err != nil { + return nil, err + } + + filter = filter.Merge(other) + ufsdir := io.NewDirectory(ds) + for _, info := range entries { subpath := filepath.Join(path, info.Name()) - if ignore != nil && ignore.MatchesPath(subpath) { + if filter.Match(subpath) { continue } - subnode, err := Add(ctx, dag, subpath, ignore) + subnode, err := Add(ctx, ds, subpath, filter) if err != nil { return nil, err } - if err := dir.AddChild(ctx, info.Name(), subnode); err != nil { + if err := ufsdir.AddChild(ctx, info.Name(), subnode); err != nil { return nil, err } } - node, err := dir.GetNode() + node, err := ufsdir.GetNode() if err != nil { return nil, err } - if err := dag.Add(ctx, node); err != nil { + if err := ds.Add(ctx, node); err != nil { return nil, err } diff --git a/pkg/fs/merge.go b/pkg/fs/merge.go deleted file mode 100644 index e5a92f6..0000000 --- a/pkg/fs/merge.go +++ /dev/null @@ -1,33 +0,0 @@ -package fs - -import ( - "context" - "strings" - - "github.com/ipfs/go-cid" - ipld "github.com/ipfs/go-ipld-format" - "github.com/nasdf/diff3" -) - -// Merge combines the contents of two edited files into the original. -func Merge(ctx context.Context, dag ipld.DAGService, o, a, b cid.Cid) (ipld.Node, error) { - textO, err := Cat(ctx, dag, o) - if err != nil { - return nil, err - } - - textA, err := Cat(ctx, dag, a) - if err != nil { - return nil, err - } - - textB, err := Cat(ctx, dag, b) - if err != nil { - return nil, err - } - - merged := diff3.Merge(textO, textA, textB) - reader := strings.NewReader(merged) - - return Chunk(ctx, dag, reader) -} diff --git a/pkg/fs/merge_test.go b/pkg/fs/merge_test.go deleted file mode 100644 index fb3d300..0000000 --- a/pkg/fs/merge_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package fs - -import ( - "bytes" - "context" - "io/ioutil" - "testing" - - "github.com/ipfs/go-merkledag/dagutils" - io "github.com/ipfs/go-unixfs/io" -) - -func TestMerge(t *testing.T) { - ctx := context.Background() - dag := dagutils.NewMemoryDagService() - - result, err := ioutil.ReadFile("testdata/r.txt") - if err != nil { - t.Fatalf("failed to read file") - } - - nodeO, err := Add(ctx, dag, "testdata/o.txt", nil) - if err != nil { - t.Fatalf("failed to add file") - } - - nodeA, err := Add(ctx, dag, "testdata/a.txt", nil) - if err != nil { - t.Fatalf("failed to add file") - } - - nodeB, err := Add(ctx, dag, "testdata/b.txt", nil) - if err != nil { - t.Fatalf("failed to add file") - } - - merge, err := Merge(ctx, dag, nodeO.Cid(), nodeA.Cid(), nodeB.Cid()) - if err != nil { - t.Fatalf("failed to merge") - } - - r, err := io.NewDagReader(ctx, merge, dag) - if err != nil { - t.Fatalf("failed to read node") - } - - data, err := ioutil.ReadAll(r) - if err != nil { - t.Fatalf("failed to read node") - } - - if !bytes.Equal(result, data) { - t.Errorf("unexpected merge result") - } -} diff --git a/pkg/dag/base.go b/pkg/merge/base.go similarity index 76% rename from pkg/dag/base.go rename to pkg/merge/base.go index fcfaa30..5bbb629 100644 --- a/pkg/dag/base.go +++ b/pkg/merge/base.go @@ -1,10 +1,11 @@ -package dag +package merge import ( "context" cid "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" + "github.com/multiverse-vcs/go-multiverse/pkg/dag" ) // Base returns the best common ancestor of local and remote. @@ -14,7 +15,7 @@ func Base(ctx context.Context, ds ipld.NodeGetter, local, remote cid.Cid) (cid.C } refs := cid.NewSet() - if err := Walk(ctx, ds, local, refs.Visit); err != nil { + if err := dag.Walk(ctx, ds, local, refs.Visit); err != nil { return cid.Cid{}, err } @@ -38,14 +39,14 @@ func Base(ctx context.Context, ds ipld.NodeGetter, local, remote cid.Cid) (cid.C return true } - if match, err0 = IsAncestor(ctx, ds, best, id); !match { + if match, err0 = dag.IsAncestor(ctx, ds, best, id); !match { best = id } return false } - if err := Walk(ctx, ds, remote, visit); err != nil { + if err := dag.Walk(ctx, ds, remote, visit); err != nil { return cid.Cid{}, err } diff --git a/pkg/dag/base_test.go b/pkg/merge/base_test.go similarity index 99% rename from pkg/dag/base_test.go rename to pkg/merge/base_test.go index 513d2ba..e5fcea7 100644 --- a/pkg/dag/base_test.go +++ b/pkg/merge/base_test.go @@ -1,4 +1,4 @@ -package dag +package merge import ( "context" diff --git a/pkg/merge/file.go b/pkg/merge/file.go new file mode 100644 index 0000000..51cb88b --- /dev/null +++ b/pkg/merge/file.go @@ -0,0 +1,35 @@ +package merge + +import ( + "context" + "strings" + + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "github.com/multiverse-vcs/go-multiverse/pkg/dag" + "github.com/multiverse-vcs/go-multiverse/pkg/fs" + "github.com/nasdf/diff3" +) + +// File combines the contents of two edited files into the original. +func File(ctx context.Context, ds ipld.DAGService, o, a, b cid.Cid) (ipld.Node, error) { + textO, err := fs.Cat(ctx, ds, o) + if err != nil { + return nil, err + } + + textA, err := fs.Cat(ctx, ds, a) + if err != nil { + return nil, err + } + + textB, err := fs.Cat(ctx, ds, b) + if err != nil { + return nil, err + } + + merged := diff3.Merge(textO, textA, textB) + reader := strings.NewReader(merged) + + return dag.Chunk(ctx, ds, reader) +} diff --git a/pkg/merge/file_test.go b/pkg/merge/file_test.go new file mode 100644 index 0000000..ede7a3b --- /dev/null +++ b/pkg/merge/file_test.go @@ -0,0 +1,61 @@ +package merge + +import ( + "bytes" + "context" + "io" + "os" + "testing" + + "github.com/ipfs/go-merkledag/dagutils" + ufsio "github.com/ipfs/go-unixfs/io" + "github.com/multiverse-vcs/go-multiverse/pkg/fs" +) + +func TestFile(t *testing.T) { + ctx := context.Background() + mem := dagutils.NewMemoryDagService() + + nodeO, err := fs.Add(ctx, mem, "testdata/o/list.txt", nil) + if err != nil { + t.Fatal("failed to add file") + } + + nodeA, err := fs.Add(ctx, mem, "testdata/a/list.txt", nil) + if err != nil { + t.Fatal("failed to add file") + } + + nodeB, err := fs.Add(ctx, mem, "testdata/b/list.txt", nil) + if err != nil { + t.Fatal("failed to add file") + } + + merge, err := File(ctx, mem, nodeO.Cid(), nodeA.Cid(), nodeB.Cid()) + if err != nil { + t.Fatal("failed to merge") + } + + r, err := ufsio.NewDagReader(ctx, merge, mem) + if err != nil { + t.Fatal("failed to read node") + } + + result, err := io.ReadAll(r) + if err != nil { + t.Fatal("failed to read node") + } + + expect, err := os.ReadFile("testdata/merge.txt") + if err != nil { + t.Fatal("failed to read file") + } + + // fix for carriage returns on windows + result = bytes.ReplaceAll(result, []byte("\r"), nil) + expect = bytes.ReplaceAll(expect, []byte("\r"), nil) + + if !bytes.Equal(result, expect) { + t.Error("unexpected merge result") + } +} diff --git a/pkg/dag/testdata/a/list.txt b/pkg/merge/testdata/a/list.txt similarity index 100% rename from pkg/dag/testdata/a/list.txt rename to pkg/merge/testdata/a/list.txt diff --git a/pkg/dag/testdata/b/list.txt b/pkg/merge/testdata/b/list.txt similarity index 100% rename from pkg/dag/testdata/b/list.txt rename to pkg/merge/testdata/b/list.txt diff --git a/pkg/dag/testdata/merge.txt b/pkg/merge/testdata/merge.txt similarity index 100% rename from pkg/dag/testdata/merge.txt rename to pkg/merge/testdata/merge.txt diff --git a/pkg/dag/testdata/o/list.txt b/pkg/merge/testdata/o/list.txt similarity index 100% rename from pkg/dag/testdata/o/list.txt rename to pkg/merge/testdata/o/list.txt diff --git a/pkg/dag/merge.go b/pkg/merge/tree.go similarity index 86% rename from pkg/dag/merge.go rename to pkg/merge/tree.go index 5e638f2..8feda25 100644 --- a/pkg/dag/merge.go +++ b/pkg/merge/tree.go @@ -1,4 +1,4 @@ -package dag +package merge import ( "context" @@ -8,12 +8,11 @@ import ( ipld "github.com/ipfs/go-ipld-format" merkledag "github.com/ipfs/go-merkledag" "github.com/ipfs/go-merkledag/dagutils" - "github.com/multiverse-vcs/go-multiverse/pkg/fs" "github.com/multiverse-vcs/go-multiverse/pkg/object" ) -// Merge combines the changes to trees a and b onto the base o. -func Merge(ctx context.Context, ds ipld.DAGService, o, a, b cid.Cid) (ipld.Node, error) { +// Tree combines the changes to trees a and b onto the base o. +func Tree(ctx context.Context, ds ipld.DAGService, o, a, b cid.Cid) (ipld.Node, error) { // fast forward b if o == a { return object.GetCommitTree(ctx, ds, b) @@ -77,7 +76,7 @@ func resolve(ctx context.Context, ds ipld.DAGService, c dagutils.Conflict) (*dag return c.A, nil } - merge, err := fs.Merge(ctx, ds, c.A.Before, c.A.After, c.B.After) + merge, err := File(ctx, ds, c.A.Before, c.A.After, c.B.After) if err != nil { return nil, err } diff --git a/pkg/dag/merge_test.go b/pkg/merge/tree_test.go similarity index 95% rename from pkg/dag/merge_test.go rename to pkg/merge/tree_test.go index c649965..ac4c97e 100644 --- a/pkg/dag/merge_test.go +++ b/pkg/merge/tree_test.go @@ -1,4 +1,4 @@ -package dag +package merge import ( "bytes" @@ -13,7 +13,7 @@ import ( "github.com/multiverse-vcs/go-multiverse/pkg/object" ) -func TestMergeConflicts(t *testing.T) { +func TestTree(t *testing.T) { ctx := context.Background() mem := dagutils.NewMemoryDagService() @@ -56,7 +56,7 @@ func TestMergeConflicts(t *testing.T) { t.Fatal("failed to add commit") } - merge, err := Merge(ctx, mem, o, a, b) + merge, err := Tree(ctx, mem, o, a, b) if err != nil { t.Fatalf("failed to merge %s", err) } diff --git a/pkg/rpc/repo/push.go b/pkg/rpc/repo/push.go index 50b7cc5..137df8e 100644 --- a/pkg/rpc/repo/push.go +++ b/pkg/rpc/repo/push.go @@ -8,6 +8,7 @@ import ( "github.com/libp2p/go-libp2p-core/peer" "github.com/multiverse-vcs/go-multiverse/pkg/dag" + "github.com/multiverse-vcs/go-multiverse/pkg/merge" "github.com/multiverse-vcs/go-multiverse/pkg/object" "github.com/multiverse-vcs/go-multiverse/pkg/p2p" ) @@ -68,7 +69,7 @@ func (s *Service) Push(args *PushArgs, reply *PushReply) error { return err } - base, err := dag.Base(ctx, s.Peer.DAG, prev, next) + base, err := merge.Base(ctx, s.Peer.DAG, prev, next) if err != nil { return err }