diff --git a/cmd.go b/cmd.go index d5706489..1b421267 100644 --- a/cmd.go +++ b/cmd.go @@ -304,6 +304,10 @@ func handleConfig(option, value string) bool { config.PGPFetch = true case "nopgpfetch": config.PGPFetch = false + case "showdiffs": + config.ShowDiffs = true + case "noshowdiffs": + config.ShowDiffs = false case "a", "aur": mode = ModeAUR case "repo": @@ -603,7 +607,6 @@ func passToMakepkgCapture(dir string, args ...string) (string, string, error) { args = append(args, mflags...) cmd := exec.Command(config.MakepkgBin, args...) - cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Dir = dir cmd.Stdout = &outbuf cmd.Stderr = &errbuf @@ -630,3 +633,22 @@ func passToGit(dir string, _args ...string) (err error) { err = cmd.Run() return } + +func passToGitCapture(dir string, _args ...string) (string, string, error) { + var outbuf, errbuf bytes.Buffer + gitflags := strings.Fields(config.GitFlags) + args := []string{"-C", dir} + args = append(args, gitflags...) + args = append(args, _args...) + + cmd := exec.Command(config.GitBin, args...) + cmd.Dir = dir + cmd.Stdout = &outbuf + cmd.Stderr = &errbuf + + err := cmd.Run() + stdout := outbuf.String() + stderr := errbuf.String() + + return stdout, stderr, err +} diff --git a/config.go b/config.go index cf85b017..9eaa02e4 100644 --- a/config.go +++ b/config.go @@ -64,6 +64,7 @@ type Configuration struct { GitClone bool `json:"gitclone"` Provides bool `json:"provides"` PGPFetch bool `json:"pgpfetch"` + ShowDiffs bool `json:"showdifs"` } var version = "5.688" @@ -167,6 +168,7 @@ func defaultSettings(config *Configuration) { config.AnswerUpgrade = "" config.GitClone = true config.Provides = true + config.ShowDiffs = true } // Editor returns the preferred system editor. diff --git a/download.go b/download.go index 7359aed9..7aad2d7e 100644 --- a/download.go +++ b/download.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" ) // Decide what download method to use: @@ -20,7 +21,7 @@ func shouldUseGit(path string) bool { } _, err = os.Stat(filepath.Join(path, ".git")) - return os.IsExist(err) + return err == nil || os.IsExist(err) } func downloadFile(path string, url string) (err error) { @@ -43,6 +44,15 @@ func downloadFile(path string, url string) (err error) { return err } +func gitGetHash(path string, name string) (string, error) { + stdout, stderr, err := passToGitCapture(filepath.Join(path, name), "rev-parse", "HEAD") + if err != nil { + return "", fmt.Errorf("%s%s", stderr, err) + } + + return strings.TrimSpace(stdout), nil +} + func gitDownload(url string, path string, name string) error { _, err := os.Stat(filepath.Join(path, name, ".git")) if os.IsNotExist(err) { @@ -74,6 +84,12 @@ func gitDownload(url string, path string, name string) error { return nil } +func gitDiff(path string, name string) error { + err := passToGit(filepath.Join(path, name), "diff", "HEAD..HEAD@{upstream}") + + return err +} + // DownloadAndUnpack downloads url tgz and extracts to path. func downloadAndUnpack(url string, path string) (err error) { err = os.MkdirAll(path, 0755) diff --git a/install.go b/install.go index c4a5829f..8cdf223e 100644 --- a/install.go +++ b/install.go @@ -153,13 +153,17 @@ func install(parser *arguments) error { cleanBuilds(toClean) - err = downloadPkgBuilds(do.Aur, parser.targets, do.Bases) + oldHashes, err := downloadPkgBuilds(do.Aur, parser.targets, do.Bases) if err != nil { return err } if len(toEdit) > 0 { - err = editPkgBuilds(toEdit) + if config.ShowDiffs { + err = showPkgBuildDiffs(toEdit, do.Bases, oldHashes) + } else { + err = editPkgBuilds(toEdit, do.Bases, oldHashes) + } if err != nil { return err } @@ -422,7 +426,11 @@ func cleanEditNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed } } - fmt.Println(bold(green(arrow + " PKGBUILDs to edit?"))) + if config.ShowDiffs { + fmt.Println(bold(green(arrow + " Diffs to show?"))) + } else { + fmt.Println(bold(green(arrow + " PKGBUILDs to edit?"))) + } fmt.Println(bold(green(arrow) + cyan(" [N]one ") + "[A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)")) fmt.Print(bold(green(arrow + " "))) @@ -481,20 +489,66 @@ func cleanBuilds(pkgs []*rpc.Pkg) { } } -func editPkgBuilds(pkgs []*rpc.Pkg) error { +func showPkgBuildDiffs(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, hashes map[string]string) error { + for _, pkg := range pkgs { + dir := filepath.Join(config.BuildDir, pkg.PackageBase) + if shouldUseGit(dir) { + hash, _ := hashes[pkg.PackageBase] + if hash == "" { + hash = gitEmptyTree + } + + head, err := gitGetHash(config.BuildDir, pkg.PackageBase) + if err != nil { + return err + } + + if head == hash { + fmt.Printf("%s %s: %s\n", bold(yellow(arrow)), cyan(formatPkgbase(pkg, bases)), bold("No changes -- skipping")) + continue + } + + args := []string{"diff", hash + "..HEAD", "--src-prefix", dir + "/", "--dst-prefix", dir + "/"} + if useColor { + args = append(args, "--color=always") + } else { + args = append(args, "--color=never") + } + err = passToGit(dir, args...) + if err != nil { + return err + } + } else { + editor, editorArgs := editor() + editorArgs = append(editorArgs, filepath.Join(dir, "PKGBUILD")) + editcmd := exec.Command(editor, editorArgs...) + editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr + err := editcmd.Run() + if err != nil { + return fmt.Errorf("Editor did not exit successfully, Aborting: %s", err) + } + } + } + + return nil +} + +func editPkgBuilds(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, hashes map[string]string) error { pkgbuilds := make([]string, 0, len(pkgs)) for _, pkg := range pkgs { dir := filepath.Join(config.BuildDir, pkg.PackageBase) pkgbuilds = append(pkgbuilds, filepath.Join(dir, "PKGBUILD")) } - editor, editorArgs := editor() - editorArgs = append(editorArgs, pkgbuilds...) - editcmd := exec.Command(editor, editorArgs...) - editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr - err := editcmd.Run() - if err != nil { - return fmt.Errorf("Editor did not exit successfully, Aborting: %s", err) + if len(pkgbuilds) > 0 { + editor, editorArgs := editor() + editorArgs = append(editorArgs, pkgbuilds...) + editcmd := exec.Command(editor, editorArgs...) + editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr + err := editcmd.Run() + if err != nil { + return fmt.Errorf("Editor did not exit successfully, Aborting: %s", err) + } } return nil @@ -535,8 +589,11 @@ func tryParsesrcinfosFile(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, } } -func downloadPkgBuilds(pkgs []*rpc.Pkg, targets stringSet, bases map[string][]*rpc.Pkg) error { - for k, pkg := range pkgs { +func downloadPkgBuilds(pkgs []*rpc.Pkg, targets stringSet, bases map[string][]*rpc.Pkg) (map[string]string, error) { + toSkip := make(stringSet) + hashes := make(map[string]string) + + for _, pkg := range pkgs { if config.ReDownload == "no" || (config.ReDownload == "yes" && !targets.get(pkg.Name)) { dir := filepath.Join(config.BuildDir, pkg.PackageBase, ".SRCINFO") pkgbuild, err := gopkg.ParseSRCINFO(dir) @@ -546,13 +603,28 @@ func downloadPkgBuilds(pkgs []*rpc.Pkg, targets stringSet, bases map[string][]*r versionPKG, errP := gopkg.NewCompleteVersion(pkgbuild.Version()) if errP == nil && errR == nil { if !versionRPC.Newer(versionPKG) { - str := bold(cyan("::") + " PKGBUILD up to date, Skipping (%d/%d): %s\n") - fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases))) - continue + toSkip.set(pkg.PackageBase) } } } } + } + + for k, pkg := range pkgs { + if shouldUseGit(filepath.Join(config.BuildDir, pkg.PackageBase)) { + hash, err := gitGetHash(config.BuildDir, pkg.PackageBase) + if err == nil { + hashes[pkg.PackageBase] = hash + } else { + hashes[pkg.PackageBase] = "" + } + } + + if toSkip.get(pkg.PackageBase) { + str := bold(cyan("::") + " PKGBUILD up to date, Skipping (%d/%d): %s\n") + fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases))) + continue + } str := bold(cyan("::") + " Downloading PKGBUILD (%d/%d): %s\n") @@ -561,15 +633,18 @@ func downloadPkgBuilds(pkgs []*rpc.Pkg, targets stringSet, bases map[string][]*r var err error if shouldUseGit(filepath.Join(config.BuildDir, pkg.PackageBase)) { err = gitDownload(baseURL+"/"+pkg.PackageBase+".git", config.BuildDir, pkg.PackageBase) + if err != nil { + return hashes, err + } } else { err = downloadAndUnpack(baseURL+pkg.URLPath, config.BuildDir) } if err != nil { - return err + return hashes, err } } - return nil + return hashes, nil } func downloadPkgBuildsSources(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, incompatible stringSet) (err error) { diff --git a/utils.go b/utils.go index 71d0dd21..522bebd3 100644 --- a/utils.go +++ b/utils.go @@ -4,6 +4,8 @@ import ( "unicode" ) +const gitEmptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + type mapStringSet map[string]stringSet type intRange struct {