// DiffPackages takes two packages to produce the changes between them. func DiffPackages(pkg1, pkg2 *Package) PackageChanges { diff := PackageChanges{ Before: pkg1, After: pkg2, Changes: map[ObjectCategory]map[string]Change{ ObjectCategoryFunc: {}, ObjectCategoryType: {}, ObjectCategoryValue: {}, }, } for _, name := range util.SortedStringSet(util.MapKeys(pkg1.Funcs), util.MapKeys(pkg2.Funcs)) { Debugf("%q", name) diff.Changes[ObjectCategoryFunc][name] = FuncChange{ Before: pkg1.Funcs[name], After: pkg2.Funcs[name], } } for _, name := range util.SortedStringSet(util.MapKeys(pkg1.Types), util.MapKeys(pkg2.Types)) { type1 := pkg1.Types[name] type2 := pkg2.Types[name] diff.Changes[ObjectCategoryType][name] = TypeChange{ Before: pkg1.Types[name], After: pkg2.Types[name], } if type1 != nil && type2 != nil { for _, fname := range util.SortedStringSet(util.MapKeys(type1.Funcs), util.MapKeys(type2.Funcs)) { diff.Changes[ObjectCategoryFunc][fname] = FuncChange{ Before: type1.Funcs[fname], After: type2.Funcs[fname], } } for _, mname := range util.SortedStringSet(util.MapKeys(type1.Methods), util.MapKeys(type2.Methods)) { diff.Changes[ObjectCategoryFunc][name+"."+mname] = FuncChange{ Before: type1.Methods[mname], After: type2.Methods[mname], } } } } for _, name := range util.SortedStringSet(util.MapKeys(pkg1.Values), util.MapKeys(pkg2.Values)) { Debugf("%q", name) diff.Changes[ObjectCategoryValue][name] = ValueChange{ Before: pkg1.Values[name], After: pkg2.Values[name], } } return diff }
func main() { var ( flagAll = flag.Bool("a", false, "show also unchanged APIs") flagRecurse = flag.Bool("r", false, `recurse into subdirectories (can be specified by "/..." suffix to the import path)`) flagDiff = flag.Bool("d", false, "run diff on multi-line changes") ) flag.Parse() flag.Usage = usage args := flag.Args() if len(args) < 1 { usage() } // TODO: support mercurial and other vcs vcsType := "git" revs := strings.SplitN(args[0], "..", 2) if len(revs) == 1 { revs = []string{revs[0] + "~1", revs[0]} } else if revs[1] == "" { revs = []string{revs[0], ""} } path := "." if len(args) >= 2 { path = args[1] if strings.HasSuffix(path, "...") { path = strings.TrimSuffix(path, "...") *flagRecurse = true } } dir1, err := gompatible.NewDirSpec(path, vcsType, revs[0]) dieIf(err) pkgs1, err := gompatible.LoadDir(dir1, *flagRecurse) dieIf(err) dir2, err := gompatible.NewDirSpec(path, vcsType, revs[1]) dieIf(err) pkgs2, err := gompatible.LoadDir(dir2, *flagRecurse) dieIf(err) diffs := map[string]gompatible.PackageChanges{} for _, name := range util.SortedStringSet(util.MapKeys(pkgs1), util.MapKeys(pkgs2)) { diffs[name] = gompatible.DiffPackages( pkgs1[name], pkgs2[name], ) } var packageIndex int var hasBreaking bool for _, name := range util.SortedStringSet(util.MapKeys(diffs)) { diff := diffs[name] var headerShown bool printHeader := func() { if *flagRecurse == false { return } if !headerShown { // FIXME strictly not a package if inspecting local import if packageIndex > 0 { fmt.Println() } fmt.Printf("package %s\n", name) headerShown = true packageIndex++ } } funcs := diff.Funcs() for _, name := range util.SortedStringSet(util.MapKeys(funcs)) { change := funcs[name] if *flagAll || change.Kind() != gompatible.ChangeUnchanged { printHeader() printChange(change, *flagDiff) } if change.Kind() == gompatible.ChangeBreaking || change.Kind() == gompatible.ChangeRemoved { hasBreaking = true } } types := diff.Types() for _, name := range util.SortedStringSet(util.MapKeys(types)) { change := types[name] if *flagAll || change.Kind() != gompatible.ChangeUnchanged { printHeader() printChange(change, *flagDiff) } if change.Kind() == gompatible.ChangeBreaking || change.Kind() == gompatible.ChangeRemoved { hasBreaking = true } } values := diff.Values() for _, name := range util.SortedStringSet(util.MapKeys(values)) { change := values[name] if *flagAll || change.Kind() != gompatible.ChangeUnchanged { printHeader() printChange(change, *flagDiff) } if change.Kind() == gompatible.ChangeBreaking || change.Kind() == gompatible.ChangeRemoved { hasBreaking = true } } } if hasBreaking { os.Exit(1) } }