Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions cmd/chisel/cmd_conflicts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package main

import (
"fmt"
"sort"
"strings"

"github.com/jessevdk/go-flags"

"github.com/canonical/chisel/internal/setup"
"github.com/canonical/chisel/internal/strdist"
)

var shortConflictHelp = "Count number of conlicting implicit parent dirs"
var longConflictHelp = `
Count number of conlicting implicit parent dirs
`

var conflictDescs = map[string]string{
"release": "Chisel release name or directory (e.g. ubuntu-22.04)",
"details": "Show conflict details",
}

type cmdConflict struct {
Release string `long:"release" value-name:"<dir>"`
Details bool `long:"details"`
}

func init() {
addCommand(
"conflicts", shortConflictHelp, longConflictHelp,
func() flags.Commander { return &cmdConflict{} },
conflictDescs, nil,
)
}

func (cmd *cmdConflict) Execute(args []string) error {
if len(args) > 0 {
return ErrExtraArgs
}

release, err := obtainRelease(cmd.Release)
if err != nil {
return err
}

conflicts, err := getConflicts(release)
if err != nil {
return err
}

w := tabWriter()
defer w.Flush()
fmt.Fprintf(Stdout, "Total conflicts: %d\n", len(conflicts))
if cmd.Details {
keys := make([]string, 0, len(conflicts))
for p := range conflicts {
keys = append(keys, p)
}
sort.Strings(keys)
for _, p := range keys {
c := conflicts[p]
fmt.Fprintf(w, "%s\t%s\t%s\n", p, c.path, c.reason)
}
}
return nil
}

type conflictInfo struct {
path string
reason string
}

// Get all conflicts.
//
// Assumptions:
// - Same paths across slices/packages are considered the same path.
// - Paths conflict if they share at least one ancestor.
func getConflicts(r *setup.Release) (map[string]*conflictInfo, error) {
if r == nil {
return nil, nil
}

var paths []string
for _, pkg := range r.Packages {
for _, slice := range pkg.Slices {
for p := range slice.Contents {
paths = append(paths, p)
}
}
}

c := make(map[string]*conflictInfo)
for i, p := range paths {
for _, q := range paths[:i] {
prefix := hasConflict(p, q)
if prefix != "" {
c[p] = &conflictInfo{
path: q,
reason: prefix,
}
c[q] = &conflictInfo{
path: p,
reason: prefix,
}
}
}
}
return c, nil
}

// Returns the conflicting prefix.
func hasConflict(p, q string) string {
ps := strings.Split(p, "/")[1:]
qs := strings.Split(q, "/")[1:]

if len(ps) == 0 || len(qs) == 0 {
return ""
}

if len(ps) == 1 || len(qs) == 1 {
var wild bool
a := ps[0]
b := qs[0]
if i := strings.Index(a, "**"); i != -1 {
a = a[:i] + "**"
wild = true
}
if i := strings.Index(b, "**"); i != -1 {
b = b[:i] + "**"
wild = true
}
if !wild {
// One of them must have **.
return ""
}
if a == b || strdist.GlobPath(a, b) {
return "/" + a
}
return ""
}

ps = ps[:len(ps)-1]
qs = qs[:len(qs)-1]

if ps[0] == qs[0] {
// First directory matches.
return "/" + ps[0]
}

if strdist.GlobPath(ps[0], qs[0]) {
return "/" + ps[0]
}

return ""
}
69 changes: 69 additions & 0 deletions cmd/chisel/cmd_conflicts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main_test

import (
. "gopkg.in/check.v1"

chisel "github.com/canonical/chisel/cmd/chisel"
)

type conflictTest struct {
a, b string
cnfl string
}

var conflictTests = []conflictTest{{
a: "/bin/foo",
b: "/bin/bar",
cnfl: "/bin",
}, {
a: "/dir/*/foo",
b: "/dir/bar/bar",
cnfl: "/dir",
}, {
a: "/*/foo",
b: "/dir/bar",
cnfl: "/*",
}, {
a: "/*a/foo",
b: "/dir/bar",
cnfl: "",
}, {
a: "/*r/foo",
b: "/dir/bar",
cnfl: "/*r",
}, {
a: "/d?r/foo",
b: "/dir/bar",
cnfl: "/d?r",
}, {
a: "/dir?/foo",
b: "/dir/bar",
cnfl: "",
}, {
a: "/dir*/foo",
b: "/d*/bar",
cnfl: "/dir*",
}, {
a: "/**",
b: "/dir/bar",
cnfl: "/**",
}, {
a: "/d**r",
b: "/dir/bar",
cnfl: "/d**",
}, {
a: "/d**r",
b: "/foo/bar",
cnfl: "",
}, {
a: "/dir/*/foo",
b: "/xyz/**",
cnfl: "",
}}

func (s *ChiselSuite) TestConflicts(c *C) {
for _, test := range conflictTests {
s := chisel.HasConflict(test.a, test.b)
c.Assert(s, Equals, test.cnfl)
}
}
2 changes: 1 addition & 1 deletion cmd/chisel/cmd_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ type helpCategory struct {
var helpCategories = []helpCategory{{
Label: "Basic",
Description: "general operations",
Commands: []string{"find", "info", "help", "version"},
Commands: []string{"find", "info", "conflicts", "help", "version"},
}, {
Label: "Action",
Description: "make things happen",
Expand Down
2 changes: 2 additions & 0 deletions cmd/chisel/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ func FakeIsStdinTTY(t bool) (restore func()) {
}

var FindSlices = findSlices

var HasConflict = hasConflict