From f20fbd25c11f5a7298830d9bc6faf79e6fefd3e2 Mon Sep 17 00:00:00 2001 From: morganamilo Date: Thu, 31 May 2018 20:31:45 +0100 Subject: [PATCH] Implement diffs for pkgbuild viewing. diff viewing can be toggled via --[no]showdiffs. When enabled diffs will be shown for packages between the current HEAD and upstream's HEAD. Packages downloaded via tarballs will be shown in full using the editor git diff is used to show diffs. Therefore the pager for diffs can be set via the PAGER and GIT_PAGER enviroment variables. --- cmd.go | 24 +++++++++++- config.go | 2 + download.go | 18 ++++++++- install.go | 111 +++++++++++++++++++++++++++++++++++++++++++--------- utils.go | 2 + 5 files changed, 137 insertions(+), 20 deletions(-) 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 {