diff --git a/Gopkg.lock b/Gopkg.lock index 158a2189..1f3275b6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -5,19 +5,19 @@ branch = "master" name = "github.com/jguer/go-alpm" packages = ["."] - revision = "11d6aadda57c8fb4f93969333cb990677d28d4f9" + revision = "1114f773cdfb05f577438f7a0538eccabc9cf012" [[projects]] branch = "master" name = "github.com/mikkeloscar/aur" packages = ["."] - revision = "9050804dc7d471393053322aaaa40428fbd32de3" + revision = "837b260b8e90895c45737e2e72313fe5bce6f2c4" [[projects]] branch = "master" name = "github.com/mikkeloscar/gopkgbuild" packages = ["."] - revision = "32274fc52aa8f5eb28711da734179e9aea27b31f" + revision = "2bb4f1f1db67f81fe50f9c1c4ad9db4f20fd6b22" [solve-meta] analyzer-name = "dep" diff --git a/README.md b/README.md index e46e9089..52131ae7 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ Yet another Yogurt - An AUR Helper written in Go #### Packages -[![yay](https://img.shields.io/aur/version/yay.svg?label=yay)](https://aur.archlinux.org/packages/yay/) [![yay-bin](https://img.shields.io/aur/version/yay-bin.svg?label=yay-bin)](https://aur.archlinux.org/packages/yay-bin/) [![yay-git](https://img.shields.io/aur/version/yay-git.svg?label=yay-git)](https://aur.archlinux.org/packages/yay-git/) [![GitHub license](https://img.shields.io/badge/license-AGPL-blue.svg)](https://raw.githubusercontent.com/Jguer/yay/master/LICENSE) - +[![yay](https://img.shields.io/aur/version/yay.svg?label=yay)](https://aur.archlinux.org/packages/yay/) [![yay-bin](https://img.shields.io/aur/version/yay-bin.svg?label=yay-bin)](https://aur.archlinux.org/packages/yay-bin/) [![yay-git](https://img.shields.io/aur/version/yay-git.svg?label=yay-git)](https://aur.archlinux.org/packages/yay-git/) [![GitHub license](https://img.shields.io/github/license/jguer/yay.svg)](https://github.com/Jguer/yay/blob/master/LICENSE) There's a point in everyone's life when you feel the need to write an AUR helper because there are only about 20 of them. So say hi to 20+1. -Yay was created with a few objectives in mind and based on the design of [yaourt](https://github.com/archlinuxfr/yaourt) and [apacman](https://github.com/oshazard/apacman): +Yay was created with a few objectives in mind and based on the design of [yaourt](https://github.com/archlinuxfr/yaourt), [apacman](https://github.com/oshazard/apacman) and [pacaur](https://github.com/rmarquis/pacaur): * Have almost no dependencies. * Provide an interface for pacman. @@ -25,15 +24,83 @@ Yay was created with a few objectives in mind and based on the design of [yaourt * Search narrowing (`yay linux header` will first search linux and then narrow on header) * No sourcing of PKGBUILD is done * The binary has no dependencies that pacman doesn't already have. -* Sources build dependencies -* Removes make dependencies at the end of build process +* Advanced dependency solving +* Remove make dependencies at the end of the build process -#### Frequently Asked Questions +## Installation -* Yay does not display colored output. How do I fix it? +If you are migrating from another AUR helper you can simply install Yay from +the AUR like any other package. + +The initial installation of Yay can be done by cloning the PKGBUILD and +building with makepkg. +```sh +git clone https://aur.archlinux.org/yay.git +cd yay +makepkg -si +``` + +## Contributing + +Contributors are always welcome! + +If you plan to make any large changes or changes that may not be 100% agreed +on, we suggest opening an issue detailing your ideas first. + +Otherwise send us a pull request and we will be happy to review it. + +### Code Style + +All code should be formatted through `go fmt`. This tool will automatically +format code for you. Although it is recommended you write code in this style +and just use this tool to catch mistakes. + +### Building + +Yay is easy to build with its only build dependency being `go` and the +assumption of `base-devel` being installed. + +Run `make` to build Yay. This will generate a binary called `yay` in the same +directory as the Makefile. + +Run `make test` to test Yay. This will check the code is formatted correctly, +run the code through `go vet` and run unit tests. + +Yay's Makefile automatically sets the `GOPATH` to `$PWD/.go`. This makes it easy to +build using the dependencies in `vendor/`. Running manual go commands such as +`go build` will require that you to either set the `GOPATH` manually or `go get` +The dependencies into your own `GOPATH`. + +### Vendored Dependencies + +Yay depends on a couple of other projects. These are stored in `vendor/` and +are built into Yay at build time. They do not need to be installed separately. + +Currently yay Depends on: + +* https://github.com/Jguer/go-alpm +* https://github.com/mikkeloscar/gopkgbuild +* https://github.com/mikkeloscar/aur + +## Frequently Asked Questions + +* Yay does not display colored output. How do I fix it? Make sure you have the `Color` option in your `/etc/pacman.conf` [#123](https://github.com/Jguer/yay/issues/123) -#### Example of Custom Operations +* Sometimes diffs are printed to the terminal and other times they are pages + via less. How do I fix this? + Yay uses `git diff` to display diffs, by default git tells less to not page + if the output can fit one terminal length. This can be overridden by + exporting your own flags `export LESS=SRX`. + +* Yay is not asking me to edit PKGBUILDS and I don't like diff menu! What do? + `yay --editmenu --nodiffmenu --save` + +* Only act on AUR packages or only on repo packages? + `yay -{OPERATION} --aur` + `yay -{OPERATION} --repo` + +## Examples of Custom Operations * `yay ` presents package selection menu * `yay -Ps` prints system statistics @@ -41,197 +108,12 @@ Yay was created with a few objectives in mind and based on the design of [yaourt * `yay -Yc` cleans unneeded dependencies * `yay -G` downloads PKGBUILD from ABS or AUR * `yay -Y --gendb` generates development package DB used for devel updates. +* `yay -Syu --devel --timeupdate` Normal update but also check for development + package updates and uses PKGBUILD modification time and not version to + determine update - - - +## Images -### Changelog - -#### v5.675 - -* Recursively remove dependencies when using yay -Yc -* Highlight diff between old and new versions better -* Fix regression where repo upgrades were marked as deps during sysupgrades -* Added `--editorflags` to add flags to editor execution - -#### v5.657 - -* By default running `yay` will trigger `yay -Syu` -* Updated Shell completions -* `-Ss` shows difference between installed and in-repo versions -* Allow sorting AUR results by fields other than votes - * votes|popularity|id|baseid|name|base|submitted|modified -* Added flags for automatic menu input - * --answerclean --answeredit --answerupgrade - * --noanswerclean --noansweredit --noanswerupgrade -* Fixed versioned dep checking -* Usual fixes to parsing, dependency sourcing and other PKGBUILD atrocities - -#### v5.608 - -* Updated Shell completions -* Added `-Qu` to extended pacman options -* Provides now supported in `-Si` -* Improved build method -* Improved conflict checking -* PKGBUILDs with unsupported arch can force build now -* PGP Key automatic importing -* GPG option passing -* `db/name` support re-added - -#### 4.505 - -* `yay` used to auto save permanent configuration options, now `--save` must be passed to save permanent configuration options -* Competions updated -* Number menu is now used to edit PKGBuilds and Clean Builds -* Devel updates of `-git` packages now uses `git ls-remote` which makes it compatible with other platforms besides github. -* Devel update checking is faster as well -* Updated man page - -#### 3.440 - -* Closed a lot of issues -* Updated bash and zsh completions -* New colour scheme -* Small parsing fixes -* Automatically delete package from transaction if $EDITOR exits with non-zero #140 -* Added check depends support - -#### 3.373 - -* Version bump to V3 to reflect all of the changes to syntax -* `yay -Pd` prints default config -* `yay -Pg` prints current config -* Fixes #174 -* Fixes #176 -* Fixes -G being unable to download split packages -* Fixes #171 -* Fixes -Si failing when given a non existing package on https://github.com/Jguer/yay/pull/155 -* Fixes other small bugs on 2.350 without adding new features - -#### 2.350 - -* Adds sudo loop (off by default, enable only by editing config file) #147 -* Adds replace package support #154 #134 -* Minor display improvements #150 for example -* Fixes GenDB -* Fixes Double options passing to pacman -* Noconfirm works more as expected -* Minor fixes and refactoring -* Yay filters out the repository name if it's included. -* Fixes #122 - -#### 2.298 - -* Adds #115 - -#### 2.296 - -* New argument parsing @Morganamilo (check manpage or --help for new - information) -* yay -Qstats changed to yay -Ps or yay -P --stats -* yay -Cd changed to yay -Yc or yay -Y --clean -* yay -Pu (--upgrades) prints update list -* yay -Pn (--numberupgrades) prints number of updates -* yay -G also possible through -Yg or -Y --getpkgbuild (yay -G will be - discontinued once it's possible to add options to the getpkgbuild operation) -* yay now counts from 1 instead of 0 @Morganamilo -* Support for ranges when selecting packages @samosaara -* Pacaur style ask all questions first and download first @Morganamilo -* Updated vendor dependencies (Fixes pacman.conf parsing errors and PKGBUILD - parsing errors) -* Updated completions - -#### 2.219 - -* Updated manpage -* Updated --help -* Fixed AUR update fails with large number of packages #59 -* Check if package is already in upgrade list and skip it. #60 -* Add -V and -h for flag parsing @AnthonyLam -* Prevent file corruption by truncating the files @maximbaz -* Print VCS error details @maximbaz -* Using '-' doesn't raise an error @PietroCarrara -* use Command.Dir in aur.PkgInstall; Fixes #32 #47 @afg984 -* Suffix YayConf.BuildDir with uid to avoid permission issues @afg984 (Not included in last changelog) - -#### 2.200 - -* Development github package support re-added - -#### 2.196 - -* XDG_CONFIG_HOME support -* XDG_CACHE_HOME support - -#### 2.165 - -* Upgrade list now allows skipping upgrade install - -#### 2.159 - -* Qstats now warns about packages not available in AUR - -#### 2.152 - -* Fetching backend changed to Mikkel Oscar's [Aur](https://github.com/mikkeloscar/aur) -* Added support for development packages from github. -* Pacman backend rewritten and simplified -* Added config framework. - -#### 1.115 - -* Added AUR completions (updates on first completion every 48h) - -#### 1.101 - -* Search speed and quality improved [#3](https://github.com/Jguer/yay/issues/3) - -#### 1.100 - -* Added manpage -* Improved search [#3](https://github.com/Jguer/yay/issues/3) -* Added -G to get pkgbuild from the AUR or ABS. [#6](https://github.com/Jguer/yay/issues/6) -* Fixed [#8](https://github.com/Jguer/yay/issues/8) -* Completed and decluttered zsh completions -* If `$EDITOR` or `$VISUAL` is not set yay will prompt you for an editor [#7](https://github.com/Jguer/yay/issues/7) - -#### 1.91 - -* `--downtop` has been replaced with `--bottomup` (as is logical) -* `yay -Ssq` and `yay -Sqs` now displays AUR packages with less information -* Repository search now uses the same criteria as pacman - -#### 1.85 - -* yay now does -Si for AUR packages -* Fixed package install bugs - -#### 1.83 - -* Added new dependency resolver for future features -* Sort package statistics - -#### 1.80 - -* yay now warns when installing orphan packages -* Added orphan status to number menu -* Qstats now checks if system has orphan packages installed - -#### 1.78 - -* Added foreign package statistics to Qstats -* Group installing is now possible -* Better handling of package dependency installing - -#### 1.76 - -* Fixed critical bug that prevented AUR dependencies from being installed. - -#### 1.70 - -* Stable for everyday use -* Bottom up package display -* Number menu like yaourt/apacman -* System package statistics + + + diff --git a/clean.go b/clean.go index a9ecf9c7..d4bd4836 100644 --- a/clean.go +++ b/clean.go @@ -124,7 +124,7 @@ func cleanAUR(keepInstalled, keepCurrent, removeAll bool) error { // Most people probably don't use keep current and that is the only // case where this is needed. - // Querying the AUR is slow and needs internet so dont do it if we + // Querying the AUR is slow and needs internet so don't do it if we // don't need to. if keepCurrent { info, err := aurInfo(cachedPackages, &aurWarnings{}) @@ -138,7 +138,11 @@ func cleanAUR(keepInstalled, keepCurrent, removeAll bool) error { } for _, pkg := range remotePackages { - installedBases.set(pkg.Name()) + if pkg.Base() != "" { + installedBases.set(pkg.Base()) + } else { + installedBases.set(pkg.Name()) + } } for _, file := range files { diff --git a/cmd.go b/cmd.go index a6957d2d..67607085 100644 --- a/cmd.go +++ b/cmd.go @@ -35,6 +35,9 @@ New operations: yay {-P --print} [options] yay {-G --getpkgbuild} [package(s)] +New options: + --repo Assume targets are from the repositories + -a --aur Assume targets are from the AUR Permanent configuration options: --save Causes the following options to be saved back to the config file when used @@ -53,13 +56,23 @@ Permanent configuration options: --config pacman.conf file to use --requestsplitn Max amount of packages to query per AUR request - --sortby Sort AUR results by a specific field during search + --sortby Sort AUR results by a specific field during search --answerclean Set a predetermined answer for the clean build menu + --answerdiff Set a predetermined answer for the diff menu --answeredit Set a predetermined answer for the edit pkgbuild menu --answerupgrade Set a predetermined answer for the upgrade menu --noanswerclean Unset the answer for the clean build menu + --noanswerdiff Unset the answer for the edit diff menu --noansweredit Unset the answer for the edit pkgbuild menu --noanswerupgrade Unset the answer for the upgrade menu + --cleanmenu Give the option to clean build PKGBUILDS + --diffmenu Give the option to show diffs for build files + --editmenu Give the option to edit/view PKGBUILDS + --upgrademenu Show a detailed list of updates with the option to skip any + --nocleanmenu Don't clean build PKGBUILDS + --nodiffmenu Don't show diffs for build files + --noeditmenu Don't edit/view PKGBUILDS + --noupgrademenu Don't show the upgrade menu --afterclean Remove package sources after successful install --noafterclean Do not remove package sources after successful build @@ -77,6 +90,10 @@ Permanent configuration options: --redownload Always download pkgbuilds of targets --noredownload Skip pkgbuild download if in cache and up to date --redownloadall Always download pkgbuilds of all AUR packages + --provides Look for matching provders when searching for packages + --noprovides Just look for packages by pkgname + --pgpfetch Prompt to import PGP keys from PKGBUILDs + --nopgpfetch Don't prompt to import PGP keys --sudoloop Loop sudo calls in the background to avoid timeout --nosudoloop Do not loop sudo calls in the background @@ -91,6 +108,7 @@ Print specific options: -n --numberupgrades Print number of updates -s --stats Display system package statistics -u --upgrades Print update list + -w --news Print arch news Yay specific options: -c --clean Remove unneeded dependencies @@ -252,6 +270,10 @@ func handleConfig(option, value string) bool { config.AnswerClean = value case "noanswerclean": config.AnswerClean = "" + case "answerdiff": + config.AnswerDiff = value + case "noanswerdiff": + config.AnswerDiff = "" case "answeredit": config.AnswerEdit = value case "noansweredit": @@ -295,6 +317,34 @@ func handleConfig(option, value string) bool { config.SudoLoop = true case "nosudoloop": config.SudoLoop = false + case "provides": + config.Provides = true + case "noprovides": + config.Provides = false + case "pgpfetch": + config.PGPFetch = true + case "nopgpfetch": + config.PGPFetch = false + case "upgrademenu": + config.UpgradeMenu = true + case "noupgrademenu": + config.UpgradeMenu = false + case "cleanmenu": + config.CleanMenu = true + case "nocleanmenu": + config.CleanMenu = false + case "diffmenu": + config.DiffMenu = true + case "nodiffmenu": + config.DiffMenu = false + case "editmenu": + config.EditMenu = true + case "noeditmenu": + config.EditMenu = false + case "a", "aur": + mode = ModeAUR + case "repo": + mode = ModeRepo default: return false } @@ -318,6 +368,8 @@ func handlePrint() (err error) { err = printNumberOfUpdates() case cmdArgs.existsArg("u", "upgrades"): err = printUpdateList(cmdArgs) + case cmdArgs.existsArg("w", "news"): + err = printNewsFeed() case cmdArgs.existsArg("c", "complete"): switch { case cmdArgs.existsArg("f", "fish"): @@ -350,22 +402,21 @@ func handleYay() (err error) { } func handleGetpkgbuild() (err error) { - err = getPkgbuilds(cmdArgs.formatTargets()) + err = getPkgbuilds(cmdArgs.targets) return } func handleYogurt() (err error) { options := cmdArgs.formatArgs() - targets := cmdArgs.formatTargets() config.SearchMode = NumberMenu - err = numberMenu(targets, options) + err = numberMenu(cmdArgs.targets, options) return } func handleSync() (err error) { - targets := cmdArgs.formatTargets() + targets := cmdArgs.targets if cmdArgs.existsArg("y", "refresh") { arguments := cmdArgs.copy() @@ -374,7 +425,7 @@ func handleSync() (err error) { arguments.delArg("s", "search") arguments.delArg("i", "info") arguments.delArg("l", "list") - arguments.targets = make(stringSet) + arguments.clearTargets() err = passToPacman(arguments) if err != nil { return @@ -407,30 +458,50 @@ func handleSync() (err error) { } func handleRemove() (err error) { - removeVCSPackage(cmdArgs.formatTargets()) + removeVCSPackage(cmdArgs.targets) err = passToPacman(cmdArgs) return } // NumberMenu presents a CLI for selecting packages to install. func numberMenu(pkgS []string, flags []string) (err error) { - aurQ, aurErr := narrowSearch(pkgS, true) - numaq := len(aurQ) - repoQ, numpq, err := queryRepo(pkgS) - if err != nil { - return + pkgS = removeInvalidTargets(pkgS) + var aurErr error + var repoErr error + var aq aurQuery + var pq repoQuery + var lenaq int + var lenpq int + + if mode == ModeAUR || mode == ModeAny { + aq, aurErr = narrowSearch(pkgS, true) + lenaq = len(aq) + } + if mode == ModeRepo || mode == ModeAny { + pq, lenpq, repoErr = queryRepo(pkgS) + if repoErr != nil { + return err + } } - if numpq == 0 && numaq == 0 { - return fmt.Errorf("no packages match search") + if lenpq == 0 && lenaq == 0 { + return fmt.Errorf("No packages match search") } if config.SortMode == BottomUp { - aurQ.printSearch(numpq + 1) - repoQ.printSearch() + if mode == ModeAUR || mode == ModeAny { + aq.printSearch(lenpq + 1) + } + if mode == ModeRepo || mode == ModeAny { + pq.printSearch() + } } else { - repoQ.printSearch() - aurQ.printSearch(numpq + 1) + if mode == ModeRepo || mode == ModeAny { + pq.printSearch() + } + if mode == ModeAUR || mode == ModeAny { + aq.printSearch(lenpq + 1) + } } if aurErr != nil { @@ -457,8 +528,8 @@ func numberMenu(pkgS []string, flags []string) (err error) { isInclude := len(exclude) == 0 && len(otherExclude) == 0 - for i, pkg := range repoQ { - target := len(repoQ) - i + for i, pkg := range pq { + target := len(pq) - i if config.SortMode == TopDown { target = i + 1 } @@ -471,10 +542,10 @@ func numberMenu(pkgS []string, flags []string) (err error) { } } - for i, pkg := range aurQ { - target := len(aurQ) - i + len(repoQ) + for i, pkg := range aq { + target := len(aq) - i + len(pq) if config.SortMode == TopDown { - target = i + 1 + len(repoQ) + target = i + 1 + len(pq) } if isInclude && include.get(target) { @@ -512,7 +583,7 @@ func passToPacman(args *arguments) error { argArr = append(argArr, "--") - argArr = append(argArr, args.formatTargets()...) + argArr = append(argArr, args.targets...) cmd = exec.Command(argArr[0], argArr[1:]...) @@ -544,7 +615,7 @@ func passToPacmanCapture(args *arguments) (string, string, error) { argArr = append(argArr, "--") - argArr = append(argArr, args.formatTargets()...) + argArr = append(argArr, args.targets...) cmd = exec.Command(argArr[0], argArr[1:]...) cmd.Stdout = &outbuf @@ -588,7 +659,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 @@ -615,3 +685,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/completions/bash b/completions/bash index cbc7c8cf..64fd5558 100644 --- a/completions/bash +++ b/completions/bash @@ -51,6 +51,9 @@ _pacman_pkg() { )" } + + + _yay() { local common core cur database prev query remove sync upgrade yays print o COMPREPLY=() @@ -65,14 +68,16 @@ _yay() { 'c g i l p s u w y') upgrade=('asdeps asexplicit force needed nodeps assume-installed print recursive' 'p') yays=('clean gendb' 'c') - print=('complete defaultconfig config numberupgrades stats upgrades' 'c d g n - s u') + print=('complete defaultconfig config numberupgrades stats upgrades news' 'c d g n + s u w') common=('arch cachedir color config confirm dbpath debug gpgdir help hookdir logfile noconfirm noprogressbar noscriptlet quiet save mflags buildir editor makepkg pacman tar git gpg gpgflags config requestsplitn sudoloop nosudoloop redownload noredownload redownloadall rebuild rebuildall rebuildtree norebuild - sortby answerclean answeredit answerupgrade noanswerclean noansweredit - noanswerupgrade root verbose' 'b d h q r v') + sortby answerclean answerdiff answeredit answerupgrade noanswerclean noanswerdiff + noansweredit noanswerupgrade cleanmenu diffmenu editmenu upgrademenu + nocleanmenu nodiffmenu noupgrademenu provides noprovides pgpfetch nopgpfetch + root verbose aur repo' 'a b d h q r v') core=('database files help query remove sync upgrade version' 'D F Q R S U V h') for o in 'D database' 'F files' 'Q query' 'R remove' 'S sync' 'U upgrade' 'Y yays' 'P print'; do diff --git a/completions/fish b/completions/fish index 97e782ed..3049384c 100644 --- a/completions/fish +++ b/completions/fish @@ -3,6 +3,8 @@ # Updated for yay by jguer set -l progname yay +complete -e -c $progname +complete -c $progname -f set -l listinstalled "(pacman -Q | string replace ' ' \t)" # This might be an issue if another package manager is also installed (e.g. for containers) @@ -10,7 +12,7 @@ set -l listall "(yay -Pcf)" set -l listrepos "(__fish_print_pacman_repos)" set -l listgroups "(pacman -Sg)\t'Package Group'" set -l listpacman "(__fish_print_packages)" -set -l noopt 'not __fish_contains_opt -s S -s D -s Q -s R -s U -s T -s F database query sync remove upgrade deptest files' +set -l noopt 'not __fish_contains_opt -s Y -s G -s V -s S -s D -s Q -s R -s U -s T -s F database query sync remove upgrade deptest files' set -l database '__fish_contains_opt -s D database' set -l getpkgbuild '__fish_contains_opt -s G getpkgbuild' set -l print '__fish_contains_opt -s P print' @@ -21,30 +23,29 @@ set -l upgrade '__fish_contains_opt -s U upgrade' set -l files '__fish_contains_opt -s F files' set -l yayspecific '__fish_contains_opt -s Y yay' -complete -c pacman -e -complete -c pacman -f - # HACK: We only need these two to coerce fish to stop file completion and complete options -complete -c $progname -n $noopt -a "-D" -d "Modify the package database" -complete -c $progname -n $noopt -a "-Q" -d "Query the package database" +# complete -c $progname -n $noopt -a "-D" -d "Modify the package database" +# complete -c $progname -n $noopt -a "-Q" -d "Query the package database" + # Primary operations complete -c $progname -s D -f -l database -n $noopt -d 'Modify the package database' -complete -c $progname -s Q -f -l query -n $noopt -d 'Query the package database' +complete -c $progname -s F -f -l files -n $noopt -d 'Query the files database' complete -c $progname -s G -f -l getpkgbuild -n $noopt -d 'Get PKGBUILD from ABS or AUR' +complete -c $progname -s P -f -l print -n $noopt -d 'Print information' +complete -c $progname -s Q -f -l query -n $noopt -d 'Query the package database' complete -c $progname -s R -f -l remove -n $noopt -d 'Remove packages from the system' complete -c $progname -s S -f -l sync -n $noopt -d 'Synchronize packages' complete -c $progname -s T -f -l deptest -n $noopt -d 'Check if dependencies are installed' complete -c $progname -s U -f -l upgrade -n $noopt -d 'Upgrade or add a local package' -complete -c $progname -s F -f -l files -n $noopt -d 'Query the files database' -complete -c $progname -s G -f -l getpkgbuild -n $noopt -d 'Get PKGBUILD from ABS or AUR' -complete -c $progname -s P -f -l print -n $noopt -d 'Print information' complete -c $progname -s Y -f -l yay -n $noopt -d 'Yay specific operations' - -complete -c $progname -s V -f -l version -d 'Display version and exit' -complete -c $progname -s h -f -l help -d 'Display help' +complete -c $progname -s V -f -l version -n $noopt -d 'Display version and exit' +complete -c $progname -s h -f -l help -n $noopt -d 'Display help' # General options # Only offer these once a command has been given so they get prominent display +complete -c $progname -n "not $noopt" -s a -l aur -d 'Assume targets are from the repositories' +complete -c $progname -n "not $noopt" -l repo -d 'Assume targets are from the AUR' + complete -c $progname -n "not $noopt" -s b -l dbpath -d 'Alternative database location' -xa '(__fish_complete_directories)' complete -c $progname -n "not $noopt" -s r -l root -d 'Alternative installation root' complete -c $progname -n "not $noopt" -s v -l verbose -d 'Output more status messages' @@ -73,6 +74,7 @@ complete -c $progname -n "not $noopt" -l mflags -d 'Pass the following options t complete -c $progname -n "not $noopt" -l gpgflags -d 'Pass the following options to gpg' -f complete -c $progname -n "not $noopt" -l buildir -d 'Specify the build directory' -f complete -c $progname -n "not $noopt" -l editor -d 'Editor to use' -f +complete -c $progname -n "not $noopt" -l editorflags -d 'Editor flags to use' -f complete -c $progname -n "not $noopt" -l makepkg -d 'Makepkg command to use' -f complete -c $progname -n "not $noopt" -l pacman -d 'Pacman command to use' -f complete -c $progname -n "not $noopt" -l tar -d 'Tar command to use' -f @@ -98,16 +100,32 @@ complete -c $progname -n "not $noopt" -l noanswerclean -d 'Unset the answer for complete -c $progname -n "not $noopt" -l noansweredit -d 'Unset the answer for the edit pkgbuild menu' -f complete -c $progname -n "not $noopt" -l noanswerupgrade -d 'Unset the answer for the upgrade menu' -f +complete -c $progname -n "not $noopt" -l cleanmenu -d 'Give the option to clean build PKGBUILDS' -f +complete -c $progname -n "not $noopt" -l diffmenu -d 'Give the option to show diffs for build files' -f +complete -c $progname -n "not $noopt" -l editmenu -d 'Give the option to edit/view PKGBUILDS' -f +complete -c $progname -n "not $noopt" -l upgrademenu -d 'Show a detailed list of updates with the option to skip any' -f +complete -c $progname -n "not $noopt" -l nocleanmenu -d 'Do not clean build PKGBUILDS' -f +complete -c $progname -n "not $noopt" -l nodiffmenu -d 'Do not show diffs for build files' -f +complete -c $progname -n "not $noopt" -l noeditmenu -d 'Do not edit/view PKGBUILDS' -f +complete -c $progname -n "not $noopt" -l noupgrademenu -d 'Do not show the upgrade menu' -f + + +complete -c $progname -n "not $noopt" -l provides -d 'Look for matching provders when searching for packages' +complete -c $progname -n "not $noopt" -l noprovides -d 'Just look for packages by pkgname' +complete -c $progname -n "not $noopt" -l pgpfetch -d 'Prompt to import PGP keys from PKGBUILDs' +complete -c $progname -n "not $noopt" -l nopgpfetch -d 'Do not prompt to import PGP keys' + # Yay options complete -c $progname -n $yayspecific -s c -l clean -d 'Remove unneeded dependencies' -f -complete -c $progname -n $yayspecific -s g -l getpkgbuild -d 'Download PKGBUILD from ABS or AUR' -xa "$listall" -f -complete -c $progname -n $yayspecific -l gendb -d 'Display system package statistics' -f +complete -c $progname -n $yayspecific -l gendb -d 'Generate development package DB' -f # Print options complete -c $progname -n $print -s d -l defaultconfig -d 'Print current yay configuration' -f complete -c $progname -n $print -s n -l numberupgrades -d 'Print number of updates' -f complete -c $progname -n $print -s s -l stats -d 'Display system package statistics' -f complete -c $progname -n $print -s u -l upgrades -d 'Print update list' -f +complete -c $progname -n $print -s w -l news -d 'Print arch news' +complete -c $progname -n $print -s q -l quiet -d 'Do not print news description' # Transaction options (sync, remove, upgrade) for condition in sync remove upgrade @@ -195,8 +213,7 @@ complete -c $progname -n "$files" -s q -l quiet -d 'Show less information' -f complete -c $progname -n "$files" -l machinereadable -d 'Show in machine readable format: repo\0pkgname\0pkgver\0path\n' -f # Upgrade options -# Theoretically, pacman reads packages in all formats that libarchive supports -# In practice, it's going to be tar.xz or tar.gz -# Using "pkg.tar.*" here would change __fish_complete_suffix's descriptions to "unknown" complete -c $progname -n "$upgrade" -xa '(__fish_complete_suffix pkg.tar.xz)' -d 'Package file' complete -c $progname -n "$upgrade" -xa '(__fish_complete_suffix pkg.tar.gz)' -d 'Package file' +complete -c $progname -n "$upgrade" -xa '(__fish_complete_suffix pkg.tar.lzo)' -d 'Package file' +complete -c $progname -n "$upgrade" -xa '(__fish_complete_suffix pkg.tar)' -d 'Package file' diff --git a/completions/zsh b/completions/zsh index b1c71db5..d1df34a1 100644 --- a/completions/zsh +++ b/completions/zsh @@ -22,6 +22,9 @@ _pacman_opts_commands=( # options for passing to _arguments: options common to all commands _pacman_opts_common=( + '--repo[Assume targets are from the repositories]' + {-a,--aur}'[Assume targets are from the AUR]' + '--arch[Set an alternate architecture]' {-b,--dbpath}'[Alternate database location]:database_location:_files -/' '--color[colorize the output]:color options:(always never auto)' @@ -43,6 +46,7 @@ _pacman_opts_common=( '--builddir[Directory to use for building AUR Packages]:build dir:_files -/' '--editor[Editor to use when editing PKGBUILDs]:editor:_files' + '--editorflags[Flags to pass to editor]' '--makepkg[makepkg command to use]:makepkg:_files' '--pacman[pacman command to use]:pacman:_files' '--tar[bsdtar command to use]:tar:_files' @@ -52,11 +56,18 @@ _pacman_opts_common=( '--sortby[Sort AUR results by a specific field during search]:sortby options:(votes popularity id baseid name base submitted modified)' '--answerclean[Set a predetermined answer for the clean build menu]:answer' '--answeredit[Set a predetermined answer for the edit pkgbuild menu]:answer' - '--answerupgrade[Set a predetermined answer for the upgrade menu]:answe' + '--answerupgrade[Set a predetermined answer for the upgrade menu]:answer' '--noanswerclean[Unset the answer for the clean build menu]' '--noansweredit[Unset the answer for the edit pkgbuild menu]' '--noanswerupgrade[Unset the answer for the upgrade menu]' - + '--cleanmenu[Give the option to clean build PKGBUILDS]' + '--diffmenu[Give the option to show diffs for build files]' + '--editmenu[Give the option to edit/view PKGBUILDS]' + '--upgrademenu[Show a detailed list of updates with the option to skip any]' + "--nocleanmenu[Don't clean build PKGBUILDS]" + "--nodiffmenu[Don't show diffs for build files]" + "--noeditmenu[Don't edit/view PKGBUILDS]" + "--noupgrademenu[Don't show the upgrade menu]" '--bottomup[Show AUR packages first]' '--topdown[Show repository packages first]' @@ -71,6 +82,10 @@ _pacman_opts_common=( '--noredownload[Skip pkgbuild download if in cache and up to date]' '--rebuild[Always build target packages]' '--rebuildall[Always build all AUR packages]' + '--provides[Look for matching provders when searching for packages]' + '--noprovides[Just look for packages by pkgname]' + '--pgpfetch[Prompt to import PGP keys from PKGBUILDs]' + "--nopgpfetch[Don't prompt to import PGP keys]" '--rebuildtree[Always build all AUR packages even if installed]' '--norebuild[Skip package build if in cache and up to date]' '--mflags[Pass arguments to makepkg]:mflags' @@ -134,6 +149,7 @@ _pacman_opts_print_modifiers=( {-n,--numberupgrades}'[Print number of updates]' {-s,--stats}'[Display system package statistics]' {-u,--upgrades}'[Print update list]' + {-w,--news}'[Print arch news]' ) # options for passing to _arguments: options for --remove command _pacman_opts_remove=( diff --git a/config.go b/config.go index 00a9c8f4..60d34c5d 100644 --- a/config.go +++ b/config.go @@ -25,6 +25,14 @@ const ( TopDown ) +type targetMode int + +const ( + ModeAUR targetMode = iota + ModeRepo + ModeAny +) + // Configuration stores yay's config. type Configuration struct { BuildDir string `json:"buildDir"` @@ -37,6 +45,7 @@ type Configuration struct { ReDownload string `json:"redownload"` ReBuild string `json:"rebuild"` AnswerClean string `json:"answerclean"` + AnswerDiff string `json:"answerdiff"` AnswerEdit string `json:"answeredit"` AnswerUpgrade string `json:"answerupgrade"` GitBin string `json:"gitbin"` @@ -54,6 +63,12 @@ type Configuration struct { Devel bool `json:"devel"` CleanAfter bool `json:"cleanAfter"` GitClone bool `json:"gitclone"` + Provides bool `json:"provides"` + PGPFetch bool `json:"pgpfetch"` + UpgradeMenu bool `json:"upgrademenu"` + CleanMenu bool `json:"cleanmenu"` + DiffMenu bool `json:"diffmenu"` + EditMenu bool `json:"editmenu"` } var version = "5.688" @@ -97,6 +112,9 @@ var alpmConf alpm.PacmanConfig // AlpmHandle is the alpm handle used by yay. var alpmHandle *alpm.Handle +// Mode is used to restrict yay to AUR or repo only modes +var mode targetMode = ModeAny + func readAlpmConfig(pacmanconf string) (conf alpm.PacmanConfig, err error) { file, err := os.Open(pacmanconf) if err != nil { @@ -134,6 +152,7 @@ func defaultSettings(config *Configuration) { config.MakepkgBin = "makepkg" config.NoConfirm = false config.PacmanBin = "pacman" + config.PGPFetch = true config.PacmanConf = "/etc/pacman.conf" config.GpgFlags = "" config.MFlags = "" @@ -149,9 +168,15 @@ func defaultSettings(config *Configuration) { config.ReDownload = "no" config.ReBuild = "no" config.AnswerClean = "" + config.AnswerDiff = "" config.AnswerEdit = "" config.AnswerUpgrade = "" config.GitClone = true + config.Provides = true + config.UpgradeMenu = true + config.CleanMenu = true + config.DiffMenu = true + config.EditMenu = false } // Editor returns the preferred system editor. diff --git a/conflicts.go b/conflicts.go deleted file mode 100644 index 759de489..00000000 --- a/conflicts.go +++ /dev/null @@ -1,354 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "sync" - - alpm "github.com/jguer/go-alpm" - gopkg "github.com/mikkeloscar/gopkgbuild" -) - -// Checks a single conflict against every other to be installed package's -// name and its provides. -func checkInnerConflict(name string, conflict string, conflicts map[string]stringSet, dc *depCatagories) { - deps, err := gopkg.ParseDeps([]string{conflict}) - if err != nil { - return - } - dep := deps[0] - - for _, pkg := range dc.Aur { - if name == pkg.Name { - continue - } - - version, err := gopkg.NewCompleteVersion(pkg.Version) - if err != nil { - return - } - if dep.Name == pkg.Name && version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name) - continue - } - - for _, provide := range pkg.Provides { - // Provides are not versioned unless explicitly defined as - // such. If a conflict is versioned but a provide is - // not it can not conflict. - if (dep.MaxVer != nil || dep.MinVer != nil) && !strings.ContainsAny(provide, "><=") { - continue - } - - var version *gopkg.CompleteVersion - var err error - - pname, pversion := splitNameFromDep(provide) - - if dep.Name != pname { - continue - } - - if pversion != "" { - version, err = gopkg.NewCompleteVersion(provide) - if err != nil { - return - } - } - - if version != nil && version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name) - break - } - - } - } - - for _, pkg := range dc.Repo { - if name == pkg.Name() { - continue - } - - version, err := gopkg.NewCompleteVersion(pkg.Version()) - if err != nil { - return - } - - if dep.Name == pkg.Name() && version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name()) - continue - } - - pkg.Provides().ForEach(func(provide alpm.Depend) error { - // Provides are not versioned unless explicitly defined as - // such. If a conflict is versioned but a provide is - // not it can not conflict. - if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny { - return nil - } - - if dep.Name != pkg.Name() { - return nil - } - - if provide.Mod == alpm.DepModAny { - addMapStringSet(conflicts, name, pkg.Name()) - return fmt.Errorf("") - } - - version, err := gopkg.NewCompleteVersion(provide.Version) - if err != nil { - return nil - } - - if version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name()) - return fmt.Errorf("") - } - - return nil - }) - } -} - -// Checks every to be installed package's conflicts against every other to be -// installed package and its provides. -func checkForInnerConflicts(dc *depCatagories) map[string]stringSet { - conflicts := make(map[string]stringSet) - - for _, pkg := range dc.Aur { - for _, cpkg := range pkg.Conflicts { - checkInnerConflict(pkg.Name, cpkg, conflicts, dc) - } - } - - for _, pkg := range dc.Repo { - pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { - checkInnerConflict(pkg.Name(), conflict.String(), conflicts, dc) - return nil - }) - } - - return conflicts -} - -// Checks a provide or packagename from a to be installed package -// against every already installed package's conflicts -func checkReverseConflict(name string, provide string, conflicts map[string]stringSet) error { - var version *gopkg.CompleteVersion - var err error - - localDb, err := alpmHandle.LocalDb() - if err != nil { - return err - } - - pname, pversion := splitNameFromDep(provide) - if pversion != "" { - version, err = gopkg.NewCompleteVersion(pversion) - if err != nil { - return nil - } - } - - localDb.PkgCache().ForEach(func(pkg alpm.Package) error { - if name == pkg.Name() { - return nil - } - - pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { - deps, err := gopkg.ParseDeps([]string{conflict.String()}) - if err != nil { - return nil - } - - dep := deps[0] - // Provides are not versioned unless explicitly defined as - // such. If a conflict is versioned but a provide is - // not it can not conflict. - if (dep.MaxVer != nil || dep.MinVer != nil) && version == nil { - return nil - } - - if dep.Name != pname { - return nil - } - - if version == nil || version.Satisfies(dep) { - // Todo - addMapStringSet(conflicts, name, pkg.Name()+" ("+provide+")") - return fmt.Errorf("") - } - - return nil - }) - - return nil - }) - - return nil -} - -// Checks the conflict of a to be installed package against the package name and -// provides of every installed package. -func checkConflict(name string, conflict string, conflicts map[string]stringSet) error { - localDb, err := alpmHandle.LocalDb() - if err != nil { - return err - } - - deps, err := gopkg.ParseDeps([]string{conflict}) - if err != nil { - return nil - } - - dep := deps[0] - - localDb.PkgCache().ForEach(func(pkg alpm.Package) error { - if name == pkg.Name() { - return nil - } - - version, err := gopkg.NewCompleteVersion(pkg.Version()) - if err != nil { - return nil - } - - if dep.Name == pkg.Name() && version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name()) - return nil - } - - pkg.Provides().ForEach(func(provide alpm.Depend) error { - if dep.Name != provide.Name { - return nil - } - - // Provides arent version unless explicitly defined as - // such. If a conflict is versioned but a provide is - // not it can not conflict. - if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny { - return nil - } - - if provide.Mod == alpm.DepModAny { - addMapStringSet(conflicts, name, pkg.Name()+" ("+provide.Name+")") - return fmt.Errorf("") - } - - version, err := gopkg.NewCompleteVersion(provide.Version) - if err != nil { - return nil - } - - if version.Satisfies(dep) { - addMapStringSet(conflicts, name, pkg.Name()+" ("+provide.Name+")") - return fmt.Errorf("") - } - - return nil - }) - - return nil - }) - - return nil -} - -// Checks every to be installed package's conflicts against the names and -// provides of every already installed package and checks every to be installed -// package's name and provides against every already installed package. -func checkForConflicts(dc *depCatagories) (map[string]stringSet, error) { - conflicts := make(map[string]stringSet) - - for _, pkg := range dc.Aur { - for _, cpkg := range pkg.Conflicts { - checkConflict(pkg.Name, cpkg, conflicts) - } - } - - for _, pkg := range dc.Repo { - pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { - checkConflict(pkg.Name(), conflict.String(), conflicts) - return nil - }) - } - - for _, pkg := range dc.Aur { - checkReverseConflict(pkg.Name, pkg.Name, conflicts) - for _, ppkg := range pkg.Provides { - checkReverseConflict(pkg.Name, ppkg, conflicts) - } - } - - for _, pkg := range dc.Repo { - checkReverseConflict(pkg.Name(), pkg.Name(), conflicts) - pkg.Provides().ForEach(func(provide alpm.Depend) error { - checkReverseConflict(pkg.Name(), provide.String(), conflicts) - return nil - }) - } - - return conflicts, nil -} - -// Combiles checkForConflicts() and checkForInnerConflicts() in parallel and -// does some printing. -func checkForAllConflicts(dc *depCatagories) error { - var err error - var conflicts map[string]stringSet - var innerConflicts map[string]stringSet - var wg sync.WaitGroup - wg.Add(2) - - fmt.Println(bold(cyan("::") + bold(" Checking for conflicts..."))) - go func() { - conflicts, err = checkForConflicts(dc) - wg.Done() - }() - - fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts..."))) - go func() { - innerConflicts = checkForInnerConflicts(dc) - wg.Done() - }() - - wg.Wait() - - if err != nil { - return err - } - - if len(innerConflicts) != 0 { - fmt.Println() - fmt.Println(bold(red(arrow)), bold("Inner conflicts found:")) - - for name, pkgs := range innerConflicts { - str := red(bold(smallArrow)) + " " + name + ":" - for pkg := range pkgs { - str += " " + cyan(pkg) - } - - fmt.Println(str) - } - - return fmt.Errorf("Unresolvable package conflicts, aborting") - } - - if len(conflicts) != 0 { - fmt.Println() - fmt.Println(bold(red(arrow)), bold("Package conflicts found:")) - for name, pkgs := range conflicts { - str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:" - for pkg := range pkgs { - str += " " + cyan(pkg) - } - - fmt.Println(str) - } - - fmt.Println() - } - - return nil -} diff --git a/dep.go b/dep.go new file mode 100644 index 00000000..3328331f --- /dev/null +++ b/dep.go @@ -0,0 +1,170 @@ +package main + +import ( + "fmt" + "strings" + + alpm "github.com/jguer/go-alpm" + rpc "github.com/mikkeloscar/aur" +) + +type providers struct { + lookfor string + Pkgs []*rpc.Pkg +} + +func makeProviders(name string) providers { + return providers{ + name, + make([]*rpc.Pkg, 0), + } +} + +func (q providers) Len() int { + return len(q.Pkgs) +} + +func (q providers) Less(i, j int) bool { + if q.lookfor == q.Pkgs[i].Name { + return true + } + + if q.lookfor == q.Pkgs[j].Name { + return false + } + + return lessRunes([]rune(q.Pkgs[i].Name), []rune(q.Pkgs[j].Name)) +} + +func (q providers) Swap(i, j int) { + q.Pkgs[i], q.Pkgs[j] = q.Pkgs[j], q.Pkgs[i] +} + +func splitDep(dep string) (string, string, string) { + mod := "" + + split := strings.FieldsFunc(dep, func(c rune) bool { + match := c == '>' || c == '<' || c == '=' + + if match { + mod += string(c) + } + + return match + }) + + if len(split) == 1 { + return split[0], "", "" + } + + return split[0], mod, split[1] +} + +func pkgSatisfies(name, version, dep string) bool { + depName, depMod, depVersion := splitDep(dep) + + if depName != name { + return false + } + + return verSatisfies(version, depMod, depVersion) +} + +func provideSatisfies(provide, dep string) bool { + depName, depMod, depVersion := splitDep(dep) + provideName, provideMod, provideVersion := splitDep(provide) + + if provideName != depName { + return false + } + + // Unversioned provieds can not satisfy a versioned dep + if provideMod == "" && depMod != "" { + return false + } + + return verSatisfies(provideVersion, depMod, depVersion) +} + +func verSatisfies(ver1, mod, ver2 string) bool { + switch mod { + case "=": + return alpm.VerCmp(ver1, ver2) == 0 + case "<": + return alpm.VerCmp(ver1, ver2) < 0 + case "<=": + return alpm.VerCmp(ver1, ver2) <= 0 + case ">": + return alpm.VerCmp(ver1, ver2) > 0 + case ">=": + return alpm.VerCmp(ver1, ver2) >= 0 + } + + return true +} + +func satisfiesAur(dep string, pkg *rpc.Pkg) bool { + if pkgSatisfies(pkg.Name, pkg.Version, dep) { + return true + } + + for _, provide := range pkg.Provides { + if provideSatisfies(provide, dep) { + return true + } + } + + return false +} + +func satisfiesRepo(dep string, pkg *alpm.Package) bool { + if pkgSatisfies(pkg.Name(), pkg.Version(), dep) { + return true + } + + if pkg.Provides().ForEach(func(provide alpm.Depend) error { + if provideSatisfies(provide.String(), dep) { + return fmt.Errorf("") + } + + return nil + }) != nil { + return true + } + + return false +} + +//split apart db/package to db and package +func splitDbFromName(pkg string) (string, string) { + split := strings.SplitN(pkg, "/", 2) + + if len(split) == 2 { + return split[0], split[1] + } + return "", split[0] +} + +func getBases(pkgs map[string]*rpc.Pkg) map[string][]*rpc.Pkg { + bases := make(map[string][]*rpc.Pkg) + + for _, pkg := range pkgs { + _, ok := bases[pkg.PackageBase] + if !ok { + bases[pkg.PackageBase] = make([]*rpc.Pkg, 0) + } + bases[pkg.PackageBase] = append(bases[pkg.PackageBase], pkg) + } + + return bases +} + +func isDevelName(name string) bool { + for _, suffix := range []string{"git", "svn", "hg", "bzr", "nightly"} { + if strings.HasSuffix(name, "-"+suffix) { + return true + } + } + + return strings.Contains(name, "-always-") +} diff --git a/depCheck.go b/depCheck.go new file mode 100644 index 00000000..08e9a632 --- /dev/null +++ b/depCheck.go @@ -0,0 +1,275 @@ +package main + +import ( + "fmt" + "strings" + "sync" + + alpm "github.com/jguer/go-alpm" + // gopkg "github.com/mikkeloscar/gopkgbuild" +) + +func (dp *depPool) checkInnerConflict(name string, conflict string, conflicts mapStringSet) { + for _, pkg := range dp.Aur { + if pkg.Name == name { + continue + } + + if satisfiesAur(conflict, pkg) { + conflicts.Add(name, pkg.Name) + } + } + + for _, pkg := range dp.Repo { + if pkg.Name() == name { + continue + } + + if satisfiesRepo(conflict, pkg) { + conflicts.Add(name, pkg.Name()) + } + } +} + +func (dp *depPool) checkForwardConflict(name string, conflict string, conflicts mapStringSet) { + dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error { + if pkg.Name() == name || dp.hasPackage(pkg.Name()) { + return nil + } + + if satisfiesRepo(conflict, &pkg) { + n := pkg.Name() + if n != conflict { + n += " (" + conflict + ")" + } + conflicts.Add(name, n) + } + + return nil + }) +} + +func (dp *depPool) checkReverseConflict(name string, conflict string, conflicts mapStringSet) { + for _, pkg := range dp.Aur { + if pkg.Name == name { + continue + } + + if satisfiesAur(conflict, pkg) { + if name != conflict { + name += " (" + conflict + ")" + } + + conflicts.Add(pkg.Name, name) + } + } + + for _, pkg := range dp.Repo { + if pkg.Name() == name { + continue + } + + if satisfiesRepo(conflict, pkg) { + if name != conflict { + name += " (" + conflict + ")" + } + + conflicts.Add(pkg.Name(), name) + } + } +} + +func (dp *depPool) checkInnerConflicts(conflicts mapStringSet) { + for _, pkg := range dp.Aur { + for _, conflict := range pkg.Conflicts { + dp.checkInnerConflict(pkg.Name, conflict, conflicts) + } + } + + for _, pkg := range dp.Repo { + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + dp.checkInnerConflict(pkg.Name(), conflict.String(), conflicts) + return nil + }) + } +} + +func (dp *depPool) checkForwardConflicts(conflicts mapStringSet) { + for _, pkg := range dp.Aur { + for _, conflict := range pkg.Conflicts { + dp.checkForwardConflict(pkg.Name, conflict, conflicts) + } + } + + for _, pkg := range dp.Repo { + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + dp.checkForwardConflict(pkg.Name(), conflict.String(), conflicts) + return nil + }) + } +} + +func (dp *depPool) checkReverseConflicts(conflicts mapStringSet) { + dp.LocalDb.PkgCache().ForEach(func(pkg alpm.Package) error { + if dp.hasPackage(pkg.Name()) { + return nil + } + + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + dp.checkReverseConflict(pkg.Name(), conflict.String(), conflicts) + return nil + }) + + return nil + }) +} + +func (dp *depPool) CheckConflicts() error { + var wg sync.WaitGroup + innerConflicts := make(mapStringSet) + conflicts := make(mapStringSet) + wg.Add(2) + + fmt.Println(bold(cyan("::") + bold(" Checking for conflicts..."))) + go func() { + dp.checkForwardConflicts(conflicts) + dp.checkReverseConflicts(conflicts) + wg.Done() + }() + + fmt.Println(bold(cyan("::") + bold(" Checking for inner conflicts..."))) + go func() { + dp.checkInnerConflicts(innerConflicts) + wg.Done() + }() + + wg.Wait() + + if len(innerConflicts) != 0 { + fmt.Println() + fmt.Println(bold(red(arrow)), bold("Inner conflicts found:")) + + for name, pkgs := range innerConflicts { + str := red(bold(smallArrow)) + " " + name + ":" + for pkg := range pkgs { + str += " " + cyan(pkg) + "," + } + str = strings.TrimSuffix(str, ",") + + fmt.Println(str) + } + + return fmt.Errorf("Unresolvable package conflicts, aborting") + } + + if len(conflicts) != 0 { + fmt.Println() + fmt.Println(bold(red(arrow)), bold("Package conflicts found:")) + for name, pkgs := range conflicts { + str := red(bold(smallArrow)) + " Installing " + cyan(name) + " will remove:" + for pkg := range pkgs { + str += " " + cyan(pkg) + "," + } + str = strings.TrimSuffix(str, ",") + + fmt.Println(str) + } + + fmt.Println() + } + + return nil +} + +type missing struct { + Good stringSet + Missing map[string][][]string +} + +func (dp *depPool) _checkMissing(dep string, stack []string, missing *missing) { + if missing.Good.get(dep) { + return + } + + if trees, ok := missing.Missing[dep]; ok { + for _, tree := range trees { + if stringSliceEqual(tree, stack) { + return + } + } + missing.Missing[dep] = append(missing.Missing[dep], stack) + return + } + + aurPkg := dp.findSatisfierAur(dep) + if aurPkg != nil { + missing.Good.set(dep) + for _, deps := range [3][]string{aurPkg.Depends, aurPkg.MakeDepends, aurPkg.CheckDepends} { + for _, aurDep := range deps { + if _, err := dp.LocalDb.PkgCache().FindSatisfier(aurDep); err == nil { + missing.Good.set(aurDep) + continue + } + + dp._checkMissing(aurDep, append(stack, aurPkg.Name), missing) + } + } + + return + } + + repoPkg := dp.findSatisfierRepo(dep) + if repoPkg != nil { + missing.Good.set(dep) + repoPkg.Depends().ForEach(func(repoDep alpm.Depend) error { + if _, err := dp.LocalDb.PkgCache().FindSatisfier(repoDep.String()); err == nil { + missing.Good.set(repoDep.String()) + return nil + } + + dp._checkMissing(repoDep.String(), append(stack, repoPkg.Name()), missing) + return nil + }) + + return + } + + missing.Missing[dep] = [][]string{stack} +} + +func (dp *depPool) CheckMissing() error { + missing := &missing{ + make(stringSet), + make(map[string][][]string), + } + + for _, target := range dp.Targets { + dp._checkMissing(target.DepString(), make([]string, 0), missing) + } + + if len(missing.Missing) == 0 { + return nil + } + + fmt.Println(bold(red(arrow+" Error: ")) + "Could not find all required packages:") + for dep, trees := range missing.Missing { + for _, tree := range trees { + + fmt.Print(" ", cyan(dep)) + + if len(tree) == 0 { + fmt.Print(" (Target") + } else { + fmt.Print(" (Wanted by: ") + for n := 0; n < len(tree)-1; n++ { + fmt.Print(cyan(tree[n]), " -> ") + } + fmt.Print(cyan(tree[len(tree)-1])) + } + + fmt.Println(")") + } + } + + return fmt.Errorf("") +} diff --git a/depOrder.go b/depOrder.go new file mode 100644 index 00000000..bb3a5273 --- /dev/null +++ b/depOrder.go @@ -0,0 +1,120 @@ +package main + +import ( + alpm "github.com/jguer/go-alpm" + rpc "github.com/mikkeloscar/aur" +) + +type depOrder struct { + Aur []*rpc.Pkg + Repo []*alpm.Package + Runtime stringSet + Bases map[string][]*rpc.Pkg +} + +func makeDepOrder() *depOrder { + return &depOrder{ + make([]*rpc.Pkg, 0), + make([]*alpm.Package, 0), + make(stringSet), + make(map[string][]*rpc.Pkg), + } +} + +func getDepOrder(dp *depPool) *depOrder { + do := makeDepOrder() + + for _, target := range dp.Targets { + dep := target.DepString() + aurPkg := dp.Aur[dep] + if aurPkg != nil && pkgSatisfies(aurPkg.Name, aurPkg.Version, dep) { + do.orderPkgAur(aurPkg, dp, true) + } + + aurPkg = dp.findSatisfierAur(dep) + if aurPkg != nil { + do.orderPkgAur(aurPkg, dp, true) + } + + repoPkg := dp.findSatisfierRepo(dep) + if repoPkg != nil { + do.orderPkgRepo(repoPkg, dp, true) + } + } + + return do +} + +func (do *depOrder) orderPkgAur(pkg *rpc.Pkg, dp *depPool, runtime bool) { + if runtime { + do.Runtime.set(pkg.Name) + } + delete(dp.Aur, pkg.Name) + + for i, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} { + for _, dep := range deps { + aurPkg := dp.findSatisfierAur(dep) + if aurPkg != nil { + do.orderPkgAur(aurPkg, dp, runtime && i == 0) + } + + repoPkg := dp.findSatisfierRepo(dep) + if repoPkg != nil { + do.orderPkgRepo(repoPkg, dp, runtime && i == 0) + } + } + } + + if _, ok := do.Bases[pkg.PackageBase]; !ok { + do.Aur = append(do.Aur, pkg) + do.Bases[pkg.PackageBase] = make([]*rpc.Pkg, 0) + } + do.Bases[pkg.PackageBase] = append(do.Bases[pkg.PackageBase], pkg) +} + +func (do *depOrder) orderPkgRepo(pkg *alpm.Package, dp *depPool, runtime bool) { + if runtime { + do.Runtime.set(pkg.Name()) + } + delete(dp.Repo, pkg.Name()) + + pkg.Depends().ForEach(func(dep alpm.Depend) (err error) { + repoPkg := dp.findSatisfierRepo(dep.String()) + if repoPkg != nil { + do.orderPkgRepo(repoPkg, dp, runtime) + } + + return nil + }) + + do.Repo = append(do.Repo, pkg) +} + +func (do *depOrder) HasMake() bool { + lenAur := 0 + for _, base := range do.Bases { + lenAur += len(base) + } + + return len(do.Runtime) != lenAur+len(do.Repo) +} + +func (do *depOrder) getMake() []string { + makeOnly := make([]string, 0, len(do.Aur)+len(do.Repo)-len(do.Runtime)) + + for _, base := range do.Bases { + for _, pkg := range base { + if !do.Runtime.get(pkg.Name) { + makeOnly = append(makeOnly, pkg.Name) + } + } + } + + for _, pkg := range do.Repo { + if !do.Runtime.get(pkg.Name()) { + makeOnly = append(makeOnly, pkg.Name()) + } + } + + return makeOnly +} diff --git a/depPool.go b/depPool.go new file mode 100644 index 00000000..ef6ee0a1 --- /dev/null +++ b/depPool.go @@ -0,0 +1,477 @@ +package main + +import ( + "sort" + "strings" + "sync" + + alpm "github.com/jguer/go-alpm" + rpc "github.com/mikkeloscar/aur" +) + +type target struct { + Db string + Name string + Mod string + Version string +} + +func toTarget(pkg string) target { + db, dep := splitDbFromName(pkg) + name, mod, version := splitDep(dep) + + return target{ + db, + name, + mod, + version, + } +} + +func (t target) DepString() string { + return t.Name + t.Mod + t.Version +} + +func (t target) String() string { + if t.Db != "" { + return t.Db + "/" + t.DepString() + } + + return t.DepString() +} + +type depPool struct { + Targets []target + Explicit stringSet + Repo map[string]*alpm.Package + Aur map[string]*rpc.Pkg + AurCache map[string]*rpc.Pkg + Groups []string + LocalDb *alpm.Db + SyncDb alpm.DbList + Warnings *aurWarnings +} + +func makeDepPool() (*depPool, error) { + localDb, err := alpmHandle.LocalDb() + if err != nil { + return nil, err + } + syncDb, err := alpmHandle.SyncDbs() + if err != nil { + return nil, err + } + + dp := &depPool{ + make([]target, 0), + make(stringSet), + make(map[string]*alpm.Package), + make(map[string]*rpc.Pkg), + make(map[string]*rpc.Pkg), + make([]string, 0), + localDb, + syncDb, + nil, + } + + return dp, nil +} + +// Includes db/ prefixes and group installs +func (dp *depPool) ResolveTargets(pkgs []string) error { + // RPC requests are slow + // Combine as many AUR package requests as possible into a single RPC + // call + aurTargets := make(stringSet) + + pkgs = removeInvalidTargets(pkgs) + + for _, pkg := range pkgs { + var err error + target := toTarget(pkg) + + // skip targets already satisfied + // even if the user enters db/pkg and aur/pkg the latter will + // still get skipped even if it's from a different database to + // the one specified + // this is how pacman behaves + if dp.hasPackage(target.DepString()) { + continue + } + + var foundPkg *alpm.Package + var singleDb *alpm.Db + + // aur/ prefix means we only check the aur + if target.Db == "aur" || mode == ModeAUR { + dp.Targets = append(dp.Targets, target) + aurTargets.set(target.DepString()) + continue + } + + // If there'ss a different priefix only look in that repo + if target.Db != "" { + singleDb, err = alpmHandle.SyncDbByName(target.Db) + if err != nil { + return err + } + foundPkg, err = singleDb.PkgCache().FindSatisfier(target.DepString()) + //otherwise find it in any repo + } else { + foundPkg, err = dp.SyncDb.FindSatisfier(target.DepString()) + } + + if err == nil { + dp.Targets = append(dp.Targets, target) + dp.Explicit.set(foundPkg.Name()) + dp.ResolveRepoDependency(foundPkg) + continue + } else { + //check for groups + //currently we don't resolve the packages in a group + //only check if the group exists + //would be better to check the groups from singleDb if + //the user specified a db but there's no easy way to do + //it without making alpm_lists so don't bother for now + //db/group is probably a rare use case + group, err := dp.SyncDb.PkgCachebyGroup(target.Name) + if err == nil { + dp.Groups = append(dp.Groups, target.String()) + group.ForEach(func(pkg alpm.Package) error { + dp.Explicit.set(pkg.Name()) + return nil + }) + continue + } + } + + //if there was no db prefix check the aur + if target.Db == "" { + aurTargets.set(target.DepString()) + } + + dp.Targets = append(dp.Targets, target) + } + + if len(aurTargets) > 0 && (mode == ModeAny || mode == ModeAUR) { + return dp.resolveAURPackages(aurTargets, true) + } + + return nil +} + +// Pseudo provides finder. +// Try to find provides by performing a search of the package name +// This effectively performs -Ss on each package +// then runs -Si on each result to cache the information. +// +// For example if you were to -S yay then yay -Ss would give: +// yay-git yay-bin yay realyog pacui pacui-git ruby-yard +// These packages will all be added to the cache in case they are needed later +// Ofcouse only the first three packages provide yay, the rest are just false +// positives. +// +// This method increases dependency resolve time +func (dp *depPool) findProvides(pkgs stringSet) error { + var mux sync.Mutex + var wg sync.WaitGroup + + doSearch := func(pkg string) { + defer wg.Done() + var err error + var results []rpc.Pkg + + // Hack for a bigger search result, if the user wants + // java-envronment we can search for just java instead and get + // more hits. + words := strings.Split(pkg, "-") + + for i := range words { + results, err = rpc.SearchByNameDesc(strings.Join(words[:i+1], "-")) + if err == nil { + break + } + } + + if err != nil { + return + } + + for _, result := range results { + mux.Lock() + if _, ok := dp.AurCache[result.Name]; !ok { + pkgs.set(result.Name) + } + mux.Unlock() + } + } + + for pkg := range pkgs { + if _, err := dp.LocalDb.PkgByName(pkg); err == nil { + continue + } + wg.Add(1) + go doSearch(pkg) + } + + wg.Wait() + + return nil +} + +func (dp *depPool) cacheAURPackages(_pkgs stringSet) error { + pkgs := _pkgs.copy() + query := make([]string, 0) + + for pkg := range pkgs { + if _, ok := dp.AurCache[pkg]; ok { + pkgs.remove(pkg) + } + } + + if len(pkgs) == 0 { + return nil + } + + if config.Provides { + err := dp.findProvides(pkgs) + if err != nil { + return err + } + } + + for pkg := range pkgs { + if _, ok := dp.AurCache[pkg]; !ok { + name, _, _ := splitDep(pkg) + query = append(query, name) + } + } + + info, err := aurInfo(query, dp.Warnings) + if err != nil { + return err + } + + for _, pkg := range info { + // Dump everything in cache just in case we need it later + dp.AurCache[pkg.Name] = pkg + } + + return nil +} + +func (dp *depPool) resolveAURPackages(pkgs stringSet, explicit bool) error { + newPackages := make(stringSet) + newAURPackages := make(stringSet) + + err := dp.cacheAURPackages(pkgs) + if err != nil { + return err + } + + if len(pkgs) == 0 { + return nil + } + + for name := range pkgs { + _, ok := dp.Aur[name] + if ok { + continue + } + + pkg := dp.findSatisfierAurCache(name) + if pkg == nil { + continue + } + + if explicit { + dp.Explicit.set(pkg.Name) + } + dp.Aur[pkg.Name] = pkg + + for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} { + for _, dep := range deps { + newPackages.set(dep) + } + } + } + + for dep := range newPackages { + if dp.hasSatisfier(dep) { + continue + } + + //has satisfier installed: skip + _, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep) + if isInstalled == nil { + continue + } + + //has satisfier in repo: fetch it + repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep) + if inRepos == nil { + dp.ResolveRepoDependency(repoPkg) + continue + } + + //assume it's in the aur + //ditch the versioning because the RPC can't handle it + newAURPackages.set(dep) + + } + + err = dp.resolveAURPackages(newAURPackages, false) + + return err +} + +func (dp *depPool) ResolveRepoDependency(pkg *alpm.Package) { + dp.Repo[pkg.Name()] = pkg + + pkg.Depends().ForEach(func(dep alpm.Depend) (err error) { + //have satisfier in dep tree: skip + if dp.hasSatisfier(dep.String()) { + return + } + + //has satisfier installed: skip + _, isInstalled := dp.LocalDb.PkgCache().FindSatisfier(dep.String()) + if isInstalled == nil { + return + } + + //has satisfier in repo: fetch it + repoPkg, inRepos := dp.SyncDb.FindSatisfier(dep.String()) + if inRepos != nil { + return + } + + dp.ResolveRepoDependency(repoPkg) + + return nil + }) +} + +func getDepPool(pkgs []string, warnings *aurWarnings) (*depPool, error) { + dp, err := makeDepPool() + if err != nil { + return nil, err + } + + dp.Warnings = warnings + err = dp.ResolveTargets(pkgs) + + return dp, err +} + +func (dp *depPool) findSatisfierAur(dep string) *rpc.Pkg { + for _, pkg := range dp.Aur { + if satisfiesAur(dep, pkg) { + return pkg + } + } + + return nil +} + +// This is mostly used to promote packages from the cache +// to the Install list +// Provide a pacman style provider menu if there's more than one candidate +// This acts slightly differently from Pacman, It will give +// a menu even if a package with a matching name exists. I believe this +// method is better because most of the time you are choosing between +// foo and foo-git. +// Using Pacman's ways trying to install foo would never give you +// a menu. +// TODO: maybe intermix repo providers in the menu +func (dp *depPool) findSatisfierAurCache(dep string) *rpc.Pkg { + depName, _, _ := splitDep(dep) + seen := make(stringSet) + providers := makeProviders(depName) + + if _, err := dp.LocalDb.PkgByName(depName); err == nil { + if pkg, ok := dp.AurCache[dep]; ok && pkgSatisfies(pkg.Name, pkg.Version, dep) { + return pkg + } + + } + + if cmdArgs.op == "Y" || cmdArgs.op == "yay" { + for _, pkg := range dp.AurCache { + if pkgSatisfies(pkg.Name, pkg.Version, dep) { + for _, target := range dp.Targets { + if target.Name == pkg.Name { + return pkg + } + } + } + } + } + + for _, pkg := range dp.AurCache { + if seen.get(pkg.Name) { + continue + } + + if pkgSatisfies(pkg.Name, pkg.Version, dep) { + providers.Pkgs = append(providers.Pkgs, pkg) + seen.set(pkg.Name) + continue + } + + for _, provide := range pkg.Provides { + if provideSatisfies(provide, dep) { + providers.Pkgs = append(providers.Pkgs, pkg) + seen.set(pkg.Name) + continue + } + } + } + + if providers.Len() == 1 { + return providers.Pkgs[0] + } + + if providers.Len() > 1 { + sort.Sort(providers) + return providerMenu(dep, providers) + } + + return nil +} + +func (dp *depPool) findSatisfierRepo(dep string) *alpm.Package { + for _, pkg := range dp.Repo { + if satisfiesRepo(dep, pkg) { + return pkg + } + } + + return nil +} + +func (dp *depPool) hasSatisfier(dep string) bool { + return dp.findSatisfierRepo(dep) != nil || dp.findSatisfierAur(dep) != nil +} + +func (dp *depPool) hasPackage(name string) bool { + for _, pkg := range dp.Repo { + if pkg.Name() == name { + return true + } + } + + for _, pkg := range dp.Aur { + if pkg.Name == name { + return true + } + } + + for _, pkg := range dp.Groups { + if pkg == name { + return true + } + } + + return false +} diff --git a/dependencies.go b/dependencies.go deleted file mode 100644 index 31bc0e07..00000000 --- a/dependencies.go +++ /dev/null @@ -1,650 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - alpm "github.com/jguer/go-alpm" - rpc "github.com/mikkeloscar/aur" - gopkg "github.com/mikkeloscar/gopkgbuild" -) - -type depTree struct { - ToProcess stringSet - Repo map[string]*alpm.Package - Aur map[string]*rpc.Pkg - Missing stringSet - Groups stringSet - Provides map[string]string - Warnings *aurWarnings -} - -type depCatagories struct { - Repo []*alpm.Package - Aur []*rpc.Pkg - MakeOnly stringSet - Bases map[string][]*rpc.Pkg -} - -func makeDepTree() *depTree { - dt := depTree{ - make(stringSet), - make(map[string]*alpm.Package), - make(map[string]*rpc.Pkg), - make(stringSet), - make(stringSet), - make(map[string]string), - &aurWarnings{}, - } - - return &dt -} - -func makeDependCatagories() *depCatagories { - dc := depCatagories{ - make([]*alpm.Package, 0), - make([]*rpc.Pkg, 0), - make(stringSet), - make(map[string][]*rpc.Pkg), - } - - return &dc -} - -// Cut the version requirement from a dependency leaving just the name. -func splitNameFromDep(dep string) (string, string) { - split := strings.FieldsFunc(dep, func(c rune) bool { - return c == '>' || c == '<' || c == '=' - }) - - if len(split) == 1 { - return split[0], "" - } - - return split[0], split[1] -} - -//split apart db/package to db and package -func splitDbFromName(pkg string) (string, string) { - split := strings.SplitN(pkg, "/", 2) - - if len(split) == 2 { - return split[0], split[1] - } - return "", split[0] -} - -func isDevelName(name string) bool { - for _, suffix := range []string{"git", "svn", "hg", "bzr", "nightly"} { - if strings.HasSuffix(name, suffix) { - return true - } - } - - return strings.Contains(name, "-always-") -} - -func getBases(pkgs map[string]*rpc.Pkg) map[string][]*rpc.Pkg { - bases := make(map[string][]*rpc.Pkg) - -nextpkg: - for _, pkg := range pkgs { - for _, base := range bases[pkg.PackageBase] { - if base == pkg { - continue nextpkg - } - } - - _, ok := bases[pkg.PackageBase] - if !ok { - bases[pkg.PackageBase] = make([]*rpc.Pkg, 0) - } - bases[pkg.PackageBase] = append(bases[pkg.PackageBase], pkg) - } - - return bases -} - -func aurFindProvider(name string, dt *depTree) (string, *rpc.Pkg) { - dep, _ := splitNameFromDep(name) - aurpkg, exists := dt.Aur[dep] - - if exists { - return dep, aurpkg - } - - dep, exists = dt.Provides[dep] - if exists { - aurpkg, exists = dt.Aur[dep] - if exists { - return dep, aurpkg - } - } - - return "", nil - -} - -func repoFindProvider(name string, dt *depTree) (string, *alpm.Package) { - dep, _ := splitNameFromDep(name) - alpmpkg, exists := dt.Repo[dep] - - if exists { - return dep, alpmpkg - } - - dep, exists = dt.Provides[dep] - if exists { - alpmpkg, exists = dt.Repo[dep] - if exists { - return dep, alpmpkg - } - } - - return "", nil - -} - -// Step two of dependency resolving. We already have all the information on the -// packages we need, now it's just about ordering them correctly. -// pkgs is a list of targets, the packages we want to install. Dependencies are -// not included. -// For each package we want we iterate down the tree until we hit the bottom. -// This is done recursively for each branch. -// The start of the tree is defined as the package we want. -// When we hit the bottom of the branch we know thats the first package -// we need to install so we add it to the start of the to install -// list (dc.Aur and dc.Repo). -// We work our way up until there is another branch to go down and do it all -// again. -// -// Here is a visual example: -// -// a -// / \ -// b c -// / \ -// d e -// -// We see a and it needs b and c -// We see b and it needs d and e -// We see d - it needs nothing so we add d to our list and move up -// We see e - it needs nothing so we add e to our list and move up -// We see c - it needs nothing so we add c to our list and move up -// -// The final install order would come out as debca -// -// There is a little more to this, handling provides, multiple packages wanting the -// same dependencies, etc. This is just the basic premise. -func getDepCatagories(pkgs []string, dt *depTree) (*depCatagories, error) { - dc := makeDependCatagories() - seen := make(stringSet) - - dc.Bases = getBases(dt.Aur) - - for _, pkg := range pkgs { - dep, alpmpkg := repoFindProvider(pkg, dt) - if alpmpkg != nil { - repoDepCatagoriesRecursive(alpmpkg, dc, dt, false) - dc.Repo = append(dc.Repo, alpmpkg) - delete(dt.Repo, dep) - } - - dep, aurpkg := aurFindProvider(pkg, dt) - if aurpkg != nil { - depCatagoriesRecursive(aurpkg, dc, dt, false, seen) - if !seen.get(aurpkg.PackageBase) { - dc.Aur = append(dc.Aur, aurpkg) - seen.set(aurpkg.PackageBase) - } - - delete(dt.Aur, dep) - } - } - - for _, base := range dc.Bases { - for _, pkg := range base { - for _, dep := range pkg.Depends { - dc.MakeOnly.remove(dep) - } - } - } - - for _, pkg := range dc.Repo { - pkg.Depends().ForEach(func(_dep alpm.Depend) error { - dep := _dep.Name - dc.MakeOnly.remove(dep) - - return nil - }) - } - - for _, pkg := range pkgs { - dc.MakeOnly.remove(pkg) - } - - dupes := make(map[*alpm.Package]struct{}) - filteredRepo := make([]*alpm.Package, 0) - - for _, pkg := range dc.Repo { - _, ok := dupes[pkg] - if ok { - continue - } - dupes[pkg] = struct{}{} - filteredRepo = append(filteredRepo, pkg) - } - - dc.Repo = filteredRepo - - return dc, nil -} - -func repoDepCatagoriesRecursive(pkg *alpm.Package, dc *depCatagories, dt *depTree, isMake bool) { - pkg.Depends().ForEach(func(_dep alpm.Depend) error { - dep, alpmpkg := repoFindProvider(_dep.Name, dt) - if alpmpkg != nil { - delete(dt.Repo, dep) - repoDepCatagoriesRecursive(alpmpkg, dc, dt, isMake) - - if isMake { - dc.MakeOnly.set(alpmpkg.Name()) - } - - dc.Repo = append(dc.Repo, alpmpkg) - } - - return nil - }) -} - -func depCatagoriesRecursive(_pkg *rpc.Pkg, dc *depCatagories, dt *depTree, isMake bool, seen stringSet) { - for _, pkg := range dc.Bases[_pkg.PackageBase] { - for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} { - for _, pkg := range deps { - dep, aurpkg := aurFindProvider(pkg, dt) - if aurpkg != nil { - delete(dt.Aur, dep) - depCatagoriesRecursive(aurpkg, dc, dt, isMake, seen) - - if !seen.get(aurpkg.PackageBase) { - dc.Aur = append(dc.Aur, aurpkg) - seen.set(aurpkg.PackageBase) - } - - if isMake { - dc.MakeOnly.set(aurpkg.Name) - } - } - - dep, alpmpkg := repoFindProvider(pkg, dt) - if alpmpkg != nil { - delete(dt.Repo, dep) - repoDepCatagoriesRecursive(alpmpkg, dc, dt, isMake) - - if isMake { - dc.MakeOnly.set(alpmpkg.Name()) - } - - dc.Repo = append(dc.Repo, alpmpkg) - } - - } - isMake = true - } - } -} - -// This is step one for dependency resolving. pkgs is a slice of the packages you -// want to resolve the dependencies for. They can be a mix of aur and repo -// dependencies. All unmet dependencies will be resolved. -// -// For Aur dependencies depends, makedepends and checkdepends are resolved but -// for repo packages only depends are resolved as they are prebuilt. -// The return will be split into three categories: Repo, Aur and Missing. -// The return is in no way ordered. This step is is just aimed at gathering the -// packages we need. -// -// This has been designed to make the least amount of rpc requests as possible. -// Web requests are probably going to be the bottleneck here so minimizing them -// provides a nice speed boost. -// -// Here is a visual expample of the request system. -// Remember only unsatisfied packages are requested, if a package is already -// installed we dont bother. -// -// a -// / \ -// b c -// / \ -// d e -// -// We see a so we send a request for a -// We see a wants b and c so we send a request for b and c -// We see d and e so we send a request for d and e -// -// Thats 5 packages in 3 requests. The amount of requests needed should always be -// the same as the height of the tree. -// The example does not really do this justice, In the real world where packages -// have 10+ dependencies each this is a very nice optimization. -func getDepTree(pkgs []string, warnings *aurWarnings) (*depTree, error) { - dt := makeDepTree() - dt.Warnings = warnings - - localDb, err := alpmHandle.LocalDb() - if err != nil { - return dt, err - } - syncDb, err := alpmHandle.SyncDbs() - if err != nil { - return dt, err - } - - for _, pkg := range pkgs { - db, name := splitDbFromName(pkg) - var foundPkg *alpm.Package - var singleDb *alpm.Db - - if db == "aur" { - dt.ToProcess.set(name) - continue - } - - // Check the repos for a matching dep - if db != "" { - singleDb, err = alpmHandle.SyncDbByName(db) - if err != nil { - return dt, err - } - foundPkg, err = singleDb.PkgCache().FindSatisfier(name) - } else { - foundPkg, err = syncDb.FindSatisfier(name) - } - - if err == nil { - repoTreeRecursive(foundPkg, dt, localDb, syncDb) - continue - } else { - //would be better to check the groups from singleDb if - //the user specified a db but theres no easy way to do - //it without making alpm_lists so dont bother for now - //db/group is probably a rare use case - _, err := syncDb.PkgCachebyGroup(name) - - if err == nil { - dt.Groups.set(pkg) - continue - } - } - - if db == "" { - dt.ToProcess.set(name) - } else { - dt.Missing.set(pkg) - } - } - - if len(dt.ToProcess) > 0 { - fmt.Println(bold(cyan("::") + bold(" Querying AUR..."))) - } - - err = depTreeRecursive(dt, localDb, syncDb, false) - if err != nil { - return dt, err - } - - if !cmdArgs.existsArg("d", "nodeps") { - err = checkVersions(dt) - } - - dt.Warnings.Print() - - return dt, err -} - -// Takes a repo package, -// gives all of the non installed deps, -// repeats on each sub dep. -func repoTreeRecursive(pkg *alpm.Package, dt *depTree, localDb *alpm.Db, syncDb alpm.DbList) (err error) { - _, exists := dt.Repo[pkg.Name()] - if exists { - return - } - - _, exists = dt.Provides[pkg.Name()] - if exists { - return - } - - dt.Repo[pkg.Name()] = pkg - (*pkg).Provides().ForEach(func(dep alpm.Depend) (err error) { - dt.Provides[dep.Name] = pkg.Name() - return nil - }) - - (*pkg).Depends().ForEach(func(dep alpm.Depend) (err error) { - _, exists := dt.Repo[dep.Name] - if exists { - return - } - - _, isInstalled := localDb.PkgCache().FindSatisfier(dep.String()) - if isInstalled == nil { - return - } - - repoPkg, inRepos := syncDb.FindSatisfier(dep.String()) - if inRepos == nil { - repoTreeRecursive(repoPkg, dt, localDb, syncDb) - return - } - - dt.Missing.set(dep.String()) - - return - }) - - return -} - -func depTreeRecursive(dt *depTree, localDb *alpm.Db, syncDb alpm.DbList, isMake bool) (err error) { - if len(dt.ToProcess) == 0 { - return - } - - nextProcess := make(stringSet) - currentProcess := make(stringSet) - // Strip version conditions - for _dep := range dt.ToProcess { - dep, _ := splitNameFromDep(_dep) - currentProcess.set(dep) - } - - // Assume toprocess only contains aur stuff we have not seen - info, err := aurInfo(currentProcess.toSlice(), dt.Warnings) - - if err != nil { - return - } - - // Cache the results - for _, pkg := range info { - dt.Aur[pkg.Name] = pkg - - for _, provide := range pkg.Provides { - name, _ := splitNameFromDep(provide) - dt.Provides[name] = pkg.Name - } - } - - // Loop through to process and check if we now have - // each packaged cached. - // If not cached, we assume it is missing. - for pkgName := range currentProcess { - pkg, exists := dt.Aur[pkgName] - - // Did not get it in the request. - if !exists { - dt.Missing.set(pkgName) - continue - } - - // for each dep and makedep - for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} { - for _, versionedDep := range deps { - dep, _ := splitNameFromDep(versionedDep) - - _, exists = dt.Aur[dep] - // We have it cached so skip. - if exists { - continue - } - - _, exists = dt.Provides[dep] - // We have it cached so skip. - if exists { - continue - } - - _, exists = dt.Repo[dep] - // We have it cached so skip. - if exists { - continue - } - - _, exists = dt.Missing[dep] - // We know it does not resolve so skip. - if exists { - continue - } - - // Check if already installed. - _, isInstalled := localDb.PkgCache().FindSatisfier(versionedDep) - if isInstalled == nil && config.ReBuild != "tree" { - continue - } - - // Check the repos for a matching dep. - repoPkg, inRepos := syncDb.FindSatisfier(versionedDep) - if inRepos == nil { - if isInstalled == nil && config.ReBuild == "tree" { - continue - } - - repoTreeRecursive(repoPkg, dt, localDb, syncDb) - continue - } - - // If all else fails add it to next search. - nextProcess.set(versionedDep) - } - } - } - - dt.ToProcess = nextProcess - depTreeRecursive(dt, localDb, syncDb, true) - - return -} - -func checkVersions(dt *depTree) error { - has := make(map[string][]string) - allDeps := make([]*gopkg.Dependency, 0) - - localDb, err := alpmHandle.LocalDb() - if err != nil { - return err - } - - for _, pkg := range dt.Aur { - for _, deps := range [3][]string{pkg.Depends, pkg.MakeDepends, pkg.CheckDepends} { - for _, dep := range deps { - _, _dep := splitNameFromDep(dep) - if _dep != "" { - deps, _ := gopkg.ParseDeps([]string{dep}) - if deps[0] != nil { - allDeps = append(allDeps, deps[0]) - } - } - } - } - - addMapStringSlice(has, pkg.Name, pkg.Version) - - if !isDevelName(pkg.Name) { - for _, name := range pkg.Provides { - _name, _ver := splitNameFromDep(name) - if _ver != "" { - addMapStringSlice(has, _name, _ver) - } else { - delete(has, _name) - } - } - } - } - - for _, pkg := range dt.Repo { - pkg.Depends().ForEach(func(dep alpm.Depend) error { - if dep.Mod != alpm.DepModAny { - deps, _ := gopkg.ParseDeps([]string{dep.String()}) - if deps[0] != nil { - allDeps = append(allDeps, deps[0]) - } - } - return nil - }) - - addMapStringSlice(has, pkg.Name(), pkg.Version()) - - pkg.Provides().ForEach(func(dep alpm.Depend) error { - if dep.Mod != alpm.DepModAny { - addMapStringSlice(has, dep.Name, dep.Version) - } else { - delete(has, dep.Name) - } - - return nil - }) - - } - - localDb.PkgCache().ForEach(func(pkg alpm.Package) error { - pkg.Provides().ForEach(func(dep alpm.Depend) error { - if dep.Mod != alpm.DepModAny { - addMapStringSlice(has, dep.Name, dep.Version) - } else { - delete(has, dep.Name) - } - - return nil - }) - - return nil - }) - - for _, dep := range allDeps { - satisfied := false - verStrs, ok := has[dep.Name] - if !ok { - continue - } - - for _, verStr := range verStrs { - version, err := gopkg.NewCompleteVersion(verStr) - if err != nil { - return err - } - - if version.Satisfies(dep) { - satisfied = true - break - } - } - - if !satisfied { - dt.Missing.set(dep.String()) - } - } - - return nil -} diff --git a/doc/yay.8 b/doc/yay.8 index da608bb0..dfd26df9 100644 --- a/doc/yay.8 +++ b/doc/yay.8 @@ -1,5 +1,5 @@ '\" t -.TH "YAY" "8" "2018-02-29" "Yay v3\&.460+" "Yay Manual" +.TH "YAY" "8" "2018\-06\-04" "Yay v6\&.784+" "Yay Manual" .nh .ad l .SH "NAME" @@ -20,29 +20,29 @@ This manpage only covers options unique to Yay\&. For other options see \fBpacman(8)\fR\&. .SH "YAY OPERATIONS" .PP -\fB\-Y, --yay\fR +\fB\-Y, \-\-yay\fR .RS 4 Perform yay specific operations\&. This is the default if no other operation is selected\&. .RE .PP -\fB\-P, --print\fR +\fB\-P, \-\-print\fR .RS 4 Perform yay specific print operations\&. .RE .PP -\fB\-G, --getpkgbuild\fR +\fB\-G, \-\-getpkgbuild\fR .RS 4 Downloads PKGBUILD from ABS or AUR\&. .RE .PP -If no arguments are provided 'yay -Syu' will be performed\&. +If no arguments are provided 'yay \-Syu' will be performed\&. .RE .PP -If no operation is selected -Y will be assumed\&. +If no operation is selected \-Y will be assumed\&. .SH "EXTENDED PACMAN OPERATIONS" .PP -\fB\-S, -Si, -Ss, -Su, -Sc, -Qu\fR +\fB\-S, \-Si, \-Ss, \-Su, \-Sc, \-Qu\fR .RS 4 These operations are extended to support both AUR and repo packages\&. .RE @@ -58,9 +58,23 @@ sources or built packages but will keep already downloaded vcs sources\&. \fB\-R\fR .RS 4 Yay will also remove cached data about devel packages\&. - .RE -.SH "YAY OPTIONS (APPLY TO -Y AND --YAY)" +.SH "NEW OPTIONS" +.PP +\fB \-\-repo\fR +.RS 4 +Assume all targets are from the repositories\&. Additionally Actions such as +sysupgrade will only act on repository packages\&. +.RE +\fB\-a \-\-aur\fR +.RS 4 +Assume all targets are from the AUR\&. Additionally Actions such as +sysupgrade will only act on AUR packages\&. + +Note that dependency resolving will still act as normal and include repository +packages\&. +.RE +.SH "YAY OPTIONS (APPLY TO \-Y AND \-\-YAY)" .PP \fB\fR .RS 4 @@ -80,7 +94,7 @@ used when migrating to Yay from another AUR helper. .RS 4 Remove unneeded dependencies\&. .RE -.SH "PRINT OPTIONS (APPLY TO -P AND --PRINT)" +.SH "PRINT OPTIONS (APPLY TO \-P AND \-\-PRINT)" \fB\-c \-\-complete\fR .RS 4 Print a list of all AUR and repo packages\&. This is to allow shell completion @@ -105,20 +119,33 @@ Print current yay configuration\&. \fB\-n \-\-numberupgrades\fR .RS 4 Print number of packages that need to be updated\&. Note this does not perform -a database refresh\&. Run \fByay -Sy\fR Before this for an up to date result\&. +a database refresh\&. Run \fByay \-Sy\fR Before this for an up to date result\&. .RE .PP \fB\-s \-\-stats\fR .RS 4 Displays information about installed packages and system health\&. If there are -orphaned, out-of-date or packages that no longer exist on the AUR warnings will +orphaned, out\-of\-date or packages that no longer exist on the AUR warnings will be displayed\&. .RE .PP \fB\-u \-\-upgrades\fR .RS 4 Print Names of packages that need to be updated\&. Note this does not perform -a database refresh\&. Run \fByay -Sy\fR Before this for an up to date result\&. +a database refresh\&. Run \fByay \-Sy\fR Before this for an up to date result\&. +.RE +.PP +\fB\-w \-\-news\fR +.RS 4 +Print new news from the Archlinux homepage\&. News is considered new if it is +newer than the build date of all native packages\&. Pass this twice to show all +available news\&. +.RE +.PP +\fB\-q \-\-quiet\fR +.RS 4 +Only show titles when printing news\&. +.RE .PP .SH "PERMANENT CONFIGURATION SETTINGS" .PP @@ -205,6 +232,13 @@ will be used instead of reading from standard input but will be treated exactly the same when parsed\&. .RE .PP +\fB\-\-answerdiff \fR +.RS 4 +Set a predetermined answer for the edit diff menu question\&. This answer +will be used instead of reading from standard input but will be treated exactly +the same when parsed\&. +.RE +.PP \fB\-\-answeredit \fR .RS 4 Set a predetermined answer for the edit pkgbuild menu question\&. This answer @@ -224,6 +258,11 @@ reading from standard input but will be treated exactly the same\&. Unset the answer for the clean build menu\&. .RE .PP +\fB\-\-noanswerdiff\fR +.RS 4 +Unset the answer for the diff menu\&. +.RE +.PP \fB\-\-noansweredit\fR .RS 4 Unset the answer for the edit pkgbuild menu\&. @@ -234,6 +273,59 @@ Unset the answer for the edit pkgbuild menu\&. Unset the answer for the upgrade menu\&. .RE .PP +\fB\-\-cleanmenu\fR +.RS 4 +Show the clean menu\&. This menu gives you the chance to fully delete the +downloaded build files from Yay's cache before redownloing a fresh copy\&. +.RE +.PP +\fB\-\-diffmenu\fR +.RS 4 +Show the diff menu\&. This menu gives you the option to view diffs from +build files before building\&. +.RE +.PP +\fB\-\-editmenu\fR +.RS 4 +Show the edit menu\&. This menu gives you the option to edit or view PKGBUILDs +before building\&. + +\fBWarning\fR: Yay resolves dependencies ahead of time via the RPC\&. It is not +recommended to edit pkgbuild variables unless you know what you are doing\&. +.RE +.PP +\fB\-\-upgrademenu\fR +.RS 4 +Show a detailed list of updates in a similar format to VerbosePkgLists\&. +Upgrades can also be skipped using numbers, number ranges or repo names\&. +Adidionally ^ can be used to invert the selection\&. + +\fBWarning\fR: It is not recommended to skip updates from the repositores as +this can lead to partial upgrades\&. This feature is intended to easily skip AUR +updates on the fly that may be broken or have a long compile time\&. Ultimately +it is up to the user what upgrades they skip\&. +.RE +.PP +\fB\-\-nocleanmenu\fR +.RS 4 +Do not show the clean menu\&. +.RE +.PP +\fB\-\-nodiffmenu\fR +.RS 4 +Do not show the diff menu\&. +.RE +.PP +\fB\-\-noeditmenu\fR +.RS 4 +Do not show the edit menu\&. +.RE +.PP +\fB\-\-noupgrademenu\fR +.RS 4 +Do not show the upgrade menu\&. +.RE +.PP \fB\-\-topdown\fR .RS 4 Display repository packages first and then AUR packages\&. @@ -258,9 +350,9 @@ Do not check for development packages updates during sysupgrade\&. \fB\-\-gitclone\fR .RS 4 Use git to download and update PKGBUILDs\&. PKGBUILDs previously downloaded -using tarball will continue to use tarballs until the package is clean built\&. -Similarly, PKGBUILDs managed with git will continue to use git until the package -is clean built.\&. +using tarball will continue to use tarballs until the package is clean +built\&. Similarly, PKGBUILDs managed with git will continue to use git until +the package is clean built.\&. .RE .PP \fB\-\-nogitclone\fR @@ -269,6 +361,18 @@ Download and update PKGBUILDs using tarballs\&. The above conditions about previously installed packages still apply\&. .RE .PP +\fB\-\-showdiffs\fR +.RS 4 +Show diffs for build files\&. Diffs are shown via \fBgit diff\fR which uses +less by default\&. This behaviour can be changed via git's config, the +\fB$GIT_PAGER\fR or \fB$PAGER\fR environment variables\&. +.RE +.PP +\fB\-\-noshowdiffs\fR +.RS 4 +Show diffs for build files\&. Files will be opened by the editor\%. +.RE +.PP \fB\-\-afterclean\fR .RS 4 Remove package sources after successful Install\&. @@ -307,6 +411,32 @@ When downloading pkgbuilds if the pkgbuild is found in cache and is equal or newer than the AUR's version use that instead of downloading a new one\&. .RE .PP +\fB\-\-provides\fR +.RS 4 +Look for matching providers when searching for AUR packages\&. When multiple +providers are found a menu will appear prompting you to pick one\&. This +increases dependency resolve time although this should not be noticeable\&. +.RE +.PP +\fB\-\-noprovides\fR +.RS 4 +Do not look for matching providers when searching for AUR packages\&. If +multiple providers happen to be found the menu will still appear\&. +.RE +.PP +\fB\-\-pgpfetch\fR +.RS 4 +Prompt to import unknown PGP keys from the \fBvalidpgpkeys\fR field of each +PKGBUILD. +.RE +.PP +\fB\-\-pgpfetch\fR +.RS 4 +Do not prompt to import unknown PGP keys\&. This is likley to cause a build +failiure unless using options such as \fB\-\-skippgpcheck\fR or a customized +gpg config\%. +.RE +.PP \fB\-\-rebuild\fR .RS 4 Always build target packages even when a copy is available in cache\&. @@ -365,37 +495,37 @@ yay \fIfoo\fR Search and install from the repos and the \fBAUR\fR\ using yogurt mode\&. .RE .PP -yay -Syu +yay \-Syu .RS 4 Update package list and upgrade all currently installed repo and \fBAUR\fR\&. .RE .PP -yay -S \fIfoo\fR +yay \-S \fIfoo\fR .RS 4 Installs package \fIfoo\fR from the repos or the \fBAUR\fR\&. .RE .PP -yay -Ss \fIfoo\fR +yay \-Ss \fIfoo\fR .RS 4 Searches for package \fIfoo\fR on the repos or the \fBAUR\fR\&. .RE .PP -yay -Si \fIfoo\fR +yay \-Si \fIfoo\fR .RS 4 Gets information about package \fIfoo\fR from the repos or the \fBAUR\fR\&. .RE .PP -yay -S \fIfoo\fR --mflags "--skipchecksums --skippgpcheck" +yay \-S \fIfoo\fR \-\-mflags "\-\-skipchecksums \-\-skippgpcheck" .RS 4 Installs \fIfoo\fR while skipping checksums and pgp checks\&. .RE .PP -yay --devel --save +yay \-\-devel \-\-save .RS 4 Sets devel to true in the config\&. .RE .PP -yay -P --stats +yay \-P \-\-stats .RS 4 Shows statistics for installed packages and system health\&. .RE @@ -435,7 +565,7 @@ and built packages from those packages\&. \fBPACMAN.CONF\fR .RS 4 Yay uses Pacman's config file to set certain pacman options either through -go-alpm or Yay itself. Options inherited include most libalpm options and +go\-alpm or Yay itself. Options inherited include most libalpm options and pacman options\&. .PP Notably \fBDatabases\fR, \fBColor\fR and \fB*Path/*Dir\fR options are used\&. diff --git a/download.go b/download.go index 79f8a30d..4b25a02f 100644 --- a/download.go +++ b/download.go @@ -21,11 +21,7 @@ func shouldUseGit(path string) bool { } _, err = os.Stat(filepath.Join(path, ".git")) - if os.IsNotExist(err) { - return false - } - - return true + return err == nil || os.IsExist(err) } func downloadFile(path string, url string) (err error) { @@ -48,25 +44,47 @@ func downloadFile(path string, url string) (err error) { return err } -func gitDownload(url string, path string, name string) error { +func gitHasDiff(path string, name string) (bool, error) { + stdout, stderr, err := passToGitCapture(filepath.Join(path, name), "rev-parse", "HEAD") + if err != nil { + return false, fmt.Errorf("%s%s", stderr, err) + } + + head := strings.TrimSpace(stdout) + + stdout, stderr, err = passToGitCapture(filepath.Join(path, name), "rev-parse", "HEAD@{upstream}") + if err != nil { + return false, fmt.Errorf("%s%s", stderr, err) + } + + upstream := strings.TrimSpace(stdout) + + return head != upstream, nil +} + +func gitDownload(url string, path string, name string) (bool, error) { _, err := os.Stat(filepath.Join(path, name, ".git")) if os.IsNotExist(err) { err = passToGit(path, "clone", url, name) if err != nil { - return fmt.Errorf("error cloning %s", name) + return false, fmt.Errorf("error cloning %s", name) } - return nil + return true, nil } else if err != nil { - return fmt.Errorf("error reading %s", filepath.Join(path, name, ".git")) + return false, fmt.Errorf("error reading %s", filepath.Join(path, name, ".git")) } err = passToGit(filepath.Join(path, name), "fetch") if err != nil { - return fmt.Errorf("error fetching %s", name) + return false, fmt.Errorf("error fetching %s", name) } - err = passToGit(filepath.Join(path, name), "reset", "--hard", "HEAD") + return false, nil +} + +func gitMerge(url string, path string, name string) error { + err := passToGit(filepath.Join(path, name), "reset", "--hard", "HEAD") if err != nil { return fmt.Errorf("error resetting %s", name) } @@ -79,15 +97,20 @@ 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, trim bool) (err error) { +func downloadAndUnpack(url string, path string) (err error) { err = os.MkdirAll(path, 0755) if err != nil { return } - tokens := strings.Split(url, "/") - fileName := tokens[len(tokens)-1] + fileName := filepath.Base(url) tarLocation := filepath.Join(path, fileName) defer os.Remove(tarLocation) @@ -97,13 +120,7 @@ func downloadAndUnpack(url string, path string, trim bool) (err error) { return } - if trim { - err = exec.Command("/bin/sh", "-c", - config.TarBin+" --strip-components 2 --include='*/"+fileName[:len(fileName)-7]+"/trunk/' -xf "+tarLocation+" -C "+path).Run() - os.Rename(path+"trunk", path+fileName[:len(fileName)-7]) // kurwa - } else { - err = exec.Command(config.TarBin, "-xf", tarLocation, "-C", path).Run() - } + err = exec.Command(config.TarBin, "-xf", tarLocation, "-C", path).Run() if err != nil { return } @@ -112,23 +129,40 @@ func downloadAndUnpack(url string, path string, trim bool) (err error) { } func getPkgbuilds(pkgs []string) error { - //possibleAurs := make([]string, 0, 0) + missing := false wd, err := os.Getwd() if err != nil { return err } - missing, err := getPkgbuildsfromABS(pkgs, wd) - if err != nil { - return err + pkgs = removeInvalidTargets(pkgs) + + aur, repo, err := packageSlices(pkgs) + + if len(repo) > 0 { + missing, err = getPkgbuildsfromABS(repo, wd) + if err != nil { + return err + } + } + + if len(aur) > 0 { + _missing, err := getPkgbuildsfromAUR(aur, wd) + if err != nil { + return err + } + missing = missing || _missing + } + + if missing { + err = fmt.Errorf("") } - err = getPkgbuildsfromAUR(missing, wd) return err } // GetPkgbuild downloads pkgbuild from the ABS. -func getPkgbuildsfromABS(pkgs []string, path string) (missing []string, err error) { +func getPkgbuildsfromABS(pkgs []string, path string) (missing bool, err error) { dbList, err := alpmHandle.SyncDbs() if err != nil { return @@ -136,8 +170,14 @@ func getPkgbuildsfromABS(pkgs []string, path string) (missing []string, err erro nextPkg: for _, pkgN := range pkgs { + pkgDb, name := splitDbFromName(pkgN) + for _, db := range dbList.Slice() { - pkg, err := db.PkgByName(pkgN) + if pkgDb != "" && db.Name() != pkgDb { + continue + } + + pkg, err := db.PkgByName(name) if err == nil { var url string name := pkg.Base() @@ -145,52 +185,84 @@ nextPkg: name = pkg.Name() } - if db.Name() == "core" || db.Name() == "extra" { - url = "https://projects.archlinux.org/svntogit/packages.git/snapshot/packages/" + name + ".tar.gz" - } else if db.Name() == "community" || db.Name() == "multilib" { - url = "https://projects.archlinux.org/svntogit/community.git/snapshot/community-packages/" + name + ".tar.gz" - } else { - fmt.Println(pkgN + " not in standard repositories") + if _, err := os.Stat(filepath.Join(path, name)); err == nil { + fmt.Println(bold(red(arrow)), bold(cyan(name)), "directory already exists") continue nextPkg } - errD := downloadAndUnpack(url, path, true) - if errD != nil { - fmt.Println(bold(red(arrow))+" "+bold(cyan(pkg.Name())), bold(red(errD.Error()))) + switch db.Name() { + case "core", "extra": + url = "https://git.archlinux.org/svntogit/packages.git/snapshot/packages/" + name + ".tar.gz" + case "community", "multilib": + url = "https://git.archlinux.org/svntogit/community.git/snapshot/packages/" + name + ".tar.gz" + default: + fmt.Println(pkgN, "not in standard repositories") + continue nextPkg + } + + errD := downloadAndUnpack(url, cacheHome) + if errD != nil { + fmt.Println(bold(red(arrow)), bold(cyan(pkg.Name())), bold(red(errD.Error()))) + } + + errD = exec.Command("mv", filepath.Join(cacheHome, "packages", name, "trunk"), filepath.Join(path, name)).Run() + if errD != nil { + fmt.Println(bold(red(arrow)), bold(cyan(pkg.Name())), bold(red(errD.Error()))) + } else { + fmt.Println(bold(yellow(arrow)), "Downloaded", cyan(pkg.Name()), "from ABS") } - fmt.Println(bold(yellow(arrow)), "Downloaded", cyan(pkg.Name()), "from ABS") continue nextPkg } } - missing = append(missing, pkgN) + fmt.Println(pkgN, "could not find package in database") + missing = true + } + + if _, err := os.Stat(filepath.Join(cacheHome, "packages")); err == nil { + os.RemoveAll(filepath.Join(cacheHome, "packages")) } return } // GetPkgbuild downloads pkgbuild from the AUR. -func getPkgbuildsfromAUR(pkgs []string, dir string) (err error) { - aq, err := aurInfoPrint(pkgs) +func getPkgbuildsfromAUR(pkgs []string, dir string) (bool, error) { + missing := false + strippedPkgs := make([]string, 0) + for _, pkg := range pkgs { + _, name := splitDbFromName(pkg) + strippedPkgs = append(strippedPkgs, name) + } + + aq, err := aurInfoPrint(strippedPkgs) if err != nil { - return err + return missing, err } for _, pkg := range aq { - var err error + if _, err := os.Stat(filepath.Join(dir, pkg.PackageBase)); err == nil { + fmt.Println(bold(red(arrow)), bold(cyan(pkg.Name)), "directory already exists") + continue + } + if shouldUseGit(filepath.Join(dir, pkg.PackageBase)) { - err = gitDownload(baseURL+"/"+pkg.PackageBase+".git", dir, pkg.PackageBase) + _, err = gitDownload(baseURL+"/"+pkg.PackageBase+".git", dir, pkg.PackageBase) } else { - err = downloadAndUnpack(baseURL+aq[0].URLPath, dir, false) + err = downloadAndUnpack(baseURL+aq[0].URLPath, dir) } if err != nil { fmt.Println(err) } else { - fmt.Println(bold(green(arrow)), bold(green("Downloaded")), bold(magenta(pkg.Name)), bold(green("from AUR"))) + fmt.Println(bold(yellow(arrow)), "Downloaded", cyan(pkg.PackageBase), "from AUR") } } - return + if len(aq) != len(pkgs) { + missing = true + } + + return missing, err } diff --git a/install.go b/install.go index 21cc90b6..b890bf6a 100644 --- a/install.go +++ b/install.go @@ -15,16 +15,14 @@ import ( // Install handles package installs func install(parser *arguments) error { - requestTargets := parser.targets.toSlice() var err error var incompatible stringSet - var dc *depCatagories - var toClean []*rpc.Pkg - var toEdit []*rpc.Pkg + var do *depOrder var aurUp upSlice var repoUp upSlice + requestTargets := parser.copy().targets warnings := &aurWarnings{} removeMake := false @@ -41,6 +39,18 @@ func install(parser *arguments) error { remoteNamesCache := sliceToStringSet(remoteNames) localNamesCache := sliceToStringSet(localNames) + //create the arguments to pass for the repo install + arguments := parser.copy() + arguments.delArg("y", "refresh") + arguments.delArg("asdeps", "asdep") + arguments.delArg("asexplicit", "asexp") + arguments.op = "S" + arguments.clearTargets() + + if mode == ModeAUR { + arguments.delArg("u", "sysupgrade") + } + //if we are doing -u also request all packages needing update if parser.existsArg("u", "sysupgrade") { aurUp, repoUp, err = upList(warnings) @@ -48,61 +58,13 @@ func install(parser *arguments) error { return err } - for _, up := range aurUp { - requestTargets = append(requestTargets, "aur/"+up.Name) - } + warnings.print() - for _, up := range repoUp { - requestTargets = append(requestTargets, up.Name) - } - - } - - //if len(aurTargets) > 0 || parser.existsArg("u", "sysupgrade") && len(remoteNames) > 0 { - // fmt.Println(bold(cyan("::") + " Querying AUR...")) - //} - dt, err := getDepTree(requestTargets, warnings) - if err != nil { - return err - } - - // Deptree will handle db/pkg prefixes. Now they can be striped from the - // targets. - for pkg := range parser.targets { - _, name := splitDbFromName(pkg) - parser.targets.remove(pkg) - parser.targets.set(name) - } - - for i, pkg := range requestTargets { - _, name := splitDbFromName(pkg) - requestTargets[i] = name - } - - if len(dt.Missing) > 0 { - str := bold(red(arrow+" Error: ")) + "Could not find all required packages:" - - for name := range dt.Missing { - str += "\n " + name - } - - return fmt.Errorf("%s", str) - } - - //create the arguments to pass for the repo install - arguments := parser.copy() - arguments.delArg("y", "refresh") - arguments.op = "S" - arguments.targets = make(stringSet) - - if parser.existsArg("u", "sysupgrade") { ignore, aurUp, err := upgradePkgs(aurUp, repoUp) if err != nil { return err } - requestTargets = parser.targets.toSlice() - for _, up := range repoUp { if !ignore.get(up.Name) { requestTargets = append(requestTargets, up.Name) @@ -114,7 +76,16 @@ func install(parser *arguments) error { requestTargets = append(requestTargets, up) } - arguments.addParam("ignore", strings.Join(ignore.toSlice(), ",")) + value, _, exists := cmdArgs.getArg("ignore") + + if len(ignore) > 0 { + ignoreStr := strings.Join(ignore.toSlice(), ",") + if exists { + ignoreStr += "," + value + } + arguments.options["ignore"] = ignoreStr + } + fmt.Println() for pkg := range aurUp { @@ -122,91 +93,148 @@ func install(parser *arguments) error { } } - hasAur := false - for pkg := range parser.targets { - _, ok := dt.Aur[pkg] - if ok { - hasAur = true - } - } + targets := sliceToStringSet(parser.targets) - if hasAur && 0 == os.Geteuid() { - return fmt.Errorf(bold(red(arrow)) + " Refusing to install AUR Packages as root, Aborting.") - } - - dc, err = getDepCatagories(requestTargets, dt) + dp, err := getDepPool(requestTargets, warnings) if err != nil { return err } - for _, pkg := range dc.Repo { + err = dp.CheckMissing() + if err != nil { + return err + } + + if len(dp.Aur) == 0 { + parser.op = "S" + parser.delArg("y", "refresh") + parser.options["ignore"] = arguments.options["ignore"] + return passToPacman(parser) + } + + if len(dp.Aur) > 0 && 0 == os.Geteuid() { + return fmt.Errorf(bold(red(arrow)) + " Refusing to install AUR Packages as root, Aborting.") + } + + err = dp.CheckConflicts() + if err != nil { + return err + } + + do = getDepOrder(dp) + if err != nil { + return err + } + + for _, pkg := range do.Repo { arguments.addTarget(pkg.DB().Name() + "/" + pkg.Name()) } - for pkg := range dt.Groups { + for _, pkg := range dp.Groups { arguments.addTarget(pkg) } - if len(dc.Aur) == 0 && len(arguments.targets) == 0 && !parser.existsArg("u", "sysupgrade") { + if len(do.Aur) == 0 && len(arguments.targets) == 0 && (!parser.existsArg("u", "sysupgrade") || mode == ModeAUR) { fmt.Println("There is nothing to do") return nil } - if hasAur { - hasAur = len(dc.Aur) != 0 + do.Print() + fmt.Println() - err = checkForAllConflicts(dc) - if err != nil { - return err + if do.HasMake() { + if !continueTask("Remove make dependencies after install?", "yY") { + removeMake = true } + } - printDepCatagories(dc) - fmt.Println() - - if len(dc.MakeOnly) > 0 { - if !continueTask("Remove make dependencies after install?", "yY") { - removeMake = true - } - } - - toClean, toEdit, err = cleanEditNumberMenu(dc.Aur, dc.Bases, remoteNamesCache) + if config.CleanMenu { + askClean := pkgbuildNumberMenu(do.Aur, do.Bases, remoteNamesCache) + toClean, err := cleanNumberMenu(do.Aur, do.Bases, remoteNamesCache, askClean) if err != nil { return err } cleanBuilds(toClean) + } - err = downloadPkgBuilds(dc.Aur, parser.targets, dc.Bases) + toSkip := pkgBuildsToSkip(do.Aur, targets) + cloned, err := downloadPkgBuilds(do.Aur, do.Bases, toSkip) + if err != nil { + return err + } + + var toDiff []*rpc.Pkg + var toEdit []*rpc.Pkg + + if config.DiffMenu { + pkgbuildNumberMenu(do.Aur, do.Bases, remoteNamesCache) + toDiff, err = diffNumberMenu(do.Aur, do.Bases, remoteNamesCache) + if err != nil { + return err + } + + if len(toDiff) > 0 { + err = showPkgBuildDiffs(toDiff, do.Bases, cloned) + if err != nil { + return err + } + } + } + + if len(toDiff) > 0 { + oldValue := config.NoConfirm + config.NoConfirm = false + fmt.Println() + if !continueTask(bold(green("Proceed with install?")), "nN") { + return fmt.Errorf("Aborting due to user") + } + config.NoConfirm = oldValue + } + + err = mergePkgBuilds(do.Aur) + if err != nil { + return err + } + + if config.EditMenu { + pkgbuildNumberMenu(do.Aur, do.Bases, remoteNamesCache) + toEdit, err = editNumberMenu(do.Aur, do.Bases, remoteNamesCache) if err != nil { return err } if len(toEdit) > 0 { - err = editPkgBuilds(toEdit) + err = editPkgBuilds(toEdit, do.Bases) if err != nil { return err } - - oldValue := config.NoConfirm - config.NoConfirm = false - if !continueTask(bold(green("Proceed with install?")), "nN") { - return fmt.Errorf("Aborting due to user") - } - config.NoConfirm = oldValue } + } - //initial srcinfo parse before pkgver() bump - err = parseSRCINFOFiles(dc.Aur, srcinfosStale, dc.Bases) - if err != nil { - return err + if len(toEdit) > 0 { + oldValue := config.NoConfirm + config.NoConfirm = false + fmt.Println() + if !continueTask(bold(green("Proceed with install?")), "nN") { + return fmt.Errorf("Aborting due to user") } + config.NoConfirm = oldValue + } - incompatible, err = getIncompatible(dc.Aur, srcinfosStale, dc.Bases) - if err != nil { - return err - } + //initial srcinfo parse before pkgver() bump + err = parseSRCINFOFiles(do.Aur, srcinfosStale, do.Bases) + if err != nil { + return err + } - err = checkPgpKeys(dc.Aur, dc.Bases, srcinfosStale) + incompatible, err = getIncompatible(do.Aur, srcinfosStale, do.Bases) + if err != nil { + return err + } + + if config.PGPFetch { + err = checkPgpKeys(do.Aur, do.Bases, srcinfosStale) if err != nil { return err } @@ -220,10 +248,19 @@ func install(parser *arguments) error { depArguments := makeArguments() depArguments.addArg("D", "asdeps") + expArguments := makeArguments() + expArguments.addArg("D", "asexplicit") - for _, pkg := range dc.Repo { - if !parser.targets.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) { + for _, pkg := range do.Repo { + if !dp.Explicit.get(pkg.Name()) && !localNamesCache.get(pkg.Name()) && !remoteNamesCache.get(pkg.Name()) { depArguments.addTarget(pkg.Name()) + continue + } + + if parser.existsArg("asdeps", "asdep") && dp.Explicit.get(pkg.Name()) { + depArguments.addTarget(pkg.Name()) + } else if parser.existsArg("asexp", "asexplicit") && dp.Explicit.get(pkg.Name()) { + expArguments.addTarget(pkg.Name()) } } @@ -233,51 +270,50 @@ func install(parser *arguments) error { return fmt.Errorf("%s%s", stderr, err) } } + + if len(expArguments.targets) > 0 { + _, stderr, err := passToPacmanCapture(expArguments) + if err != nil { + return fmt.Errorf("%s%s", stderr, err) + } + } } - if hasAur { - //conflicts have been checked so answer y for them - ask, _ := strconv.Atoi(cmdArgs.globals["ask"]) - uask := alpm.QuestionType(ask) | alpm.QuestionTypeConflictPkg - cmdArgs.globals["ask"] = fmt.Sprint(uask) + //conflicts have been checked so answer y for them + ask, _ := strconv.Atoi(cmdArgs.globals["ask"]) + uask := alpm.QuestionType(ask) | alpm.QuestionTypeConflictPkg + cmdArgs.globals["ask"] = fmt.Sprint(uask) + + err = downloadPkgBuildsSources(do.Aur, do.Bases, incompatible) + if err != nil { + return err + } + + err = buildInstallPkgBuilds(dp, do, srcinfosStale, parser, incompatible) + if err != nil { + return err + } + + if removeMake { + removeArguments := makeArguments() + removeArguments.addArg("R", "u") + + for _, pkg := range do.getMake() { + removeArguments.addTarget(pkg) + } + + oldValue := config.NoConfirm + config.NoConfirm = true + err = passToPacman(removeArguments) + config.NoConfirm = oldValue - err = downloadPkgBuildsSources(dc.Aur, dc.Bases, incompatible) if err != nil { return err } + } - err = buildInstallPkgBuilds(dc.Aur, srcinfosStale, parser.targets, parser, dc.Bases, incompatible) - if err != nil { - return err - } - - if len(dc.MakeOnly) > 0 { - if !removeMake { - return nil - } - - removeArguments := makeArguments() - removeArguments.addArg("R", "u") - - for pkg := range dc.MakeOnly { - removeArguments.addTarget(pkg) - } - - oldValue := config.NoConfirm - config.NoConfirm = true - err = passToPacman(removeArguments) - config.NoConfirm = oldValue - - if err != nil { - return err - } - } - - if config.CleanAfter { - clean(dc.Aur) - } - - return nil + if config.CleanAfter { + clean(do.Aur) } return nil @@ -318,31 +354,44 @@ nextpkg: return incompatible, nil } -func getVersionFromPkgbuild(dir string) (string, error) { +func parsePackageList(dir string) (map[string]string, string, error) { stdout, stderr, err := passToMakepkgCapture(dir, "--packagelist") if err != nil { - return "", fmt.Errorf("%s%s", stderr, err) + return nil, "", fmt.Errorf("%s%s", stderr, err) } - line := strings.Split(stdout, "\n")[0] - split := strings.Split(line, "-") + var version string + lines := strings.Split(stdout, "\n") + pkgdests := make(map[string]string) - if len(split) < 4 { - return "", fmt.Errorf("Can not parse version from: %s", split) + for _, line := range lines { + if line == "" { + continue + } + + fileName := filepath.Base(line) + split := strings.Split(fileName, "-") + + if len(split) < 4 { + return nil, "", fmt.Errorf("Can not find package name : %s", split) + } + + // pkgname-pkgver-pkgrel-arch.pkgext + // This assumes 3 dashes after the pkgname, Will cause an error + // if the PKGEXT contains a dash. Please no one do that. + pkgname := strings.Join(split[:len(split)-3], "-") + version = strings.Join(split[len(split)-3:len(split)-2], "-") + pkgdests[pkgname] = line } - //pkg-name-pkgver-pkgrel-arch: extract pkgver-pkgrel - ver := split[len(split)-3] + "-" + split[len(split)-2] - return ver, nil + + return pkgdests, version, nil } -func cleanEditNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet) ([]*rpc.Pkg, []*rpc.Pkg, error) { +func pkgbuildNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet) bool { toPrint := "" askClean := false - toClean := make([]*rpc.Pkg, 0) - toEdit := make([]*rpc.Pkg, 0) - for n, pkg := range pkgs { dir := filepath.Join(config.BuildDir, pkg.PackageBase) @@ -362,76 +411,108 @@ func cleanEditNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed fmt.Print(toPrint) - if askClean { - fmt.Println(bold(green(arrow + " Packages to cleanBuild?"))) - 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 + " "))) - cleanInput, err := getInput(config.AnswerClean) - if err != nil { - return nil, nil, err - } + return askClean +} - cInclude, cExclude, cOtherInclude, cOtherExclude := parseNumberMenu(cleanInput) - cIsInclude := len(cExclude) == 0 && len(cOtherExclude) == 0 +func cleanNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet, hasClean bool) ([]*rpc.Pkg, error) { + toClean := make([]*rpc.Pkg, 0) - if cOtherInclude.get("abort") || cOtherInclude.get("ab") { - return nil, nil, fmt.Errorf("Aborting due to user") - } + if !hasClean { + return toClean, nil + } - if !cOtherInclude.get("n") && !cOtherInclude.get("none") { - for i, pkg := range pkgs { - dir := filepath.Join(config.BuildDir, pkg.PackageBase) - if _, err := os.Stat(dir); os.IsNotExist(err) { - continue - } + fmt.Println(bold(green(arrow + " Packages to cleanBuild?"))) + 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 + " "))) + cleanInput, err := getInput(config.AnswerClean) + if err != nil { + return nil, err + } - if !cIsInclude && cExclude.get(len(pkgs)-i) { - continue - } + cInclude, cExclude, cOtherInclude, cOtherExclude := parseNumberMenu(cleanInput) + cIsInclude := len(cExclude) == 0 && len(cOtherExclude) == 0 - if installed.get(pkg.Name) && (cOtherInclude.get("i") || cOtherInclude.get("installed")) { - toClean = append(toClean, pkg) - continue - } + if cOtherInclude.get("abort") || cOtherInclude.get("ab") { + return nil, fmt.Errorf("Aborting due to user") + } - if !installed.get(pkg.Name) && (cOtherInclude.get("no") || cOtherInclude.get("notinstalled")) { - toClean = append(toClean, pkg) - continue - } + if !cOtherInclude.get("n") && !cOtherInclude.get("none") { + for i, pkg := range pkgs { + dir := filepath.Join(config.BuildDir, pkg.PackageBase) + if _, err := os.Stat(dir); os.IsNotExist(err) { + continue + } - if cOtherInclude.get("a") || cOtherInclude.get("all") { - toClean = append(toClean, pkg) - continue - } + if !cIsInclude && cExclude.get(len(pkgs)-i) { + continue + } - if cIsInclude && (cInclude.get(len(pkgs)-i) || cOtherInclude.get(pkg.PackageBase)) { - toClean = append(toClean, pkg) - continue - } + if installed.get(pkg.Name) && (cOtherInclude.get("i") || cOtherInclude.get("installed")) { + toClean = append(toClean, pkg) + continue + } - if !cIsInclude && (!cExclude.get(len(pkgs)-i) && !cOtherExclude.get(pkg.PackageBase)) { - toClean = append(toClean, pkg) - continue - } + if !installed.get(pkg.Name) && (cOtherInclude.get("no") || cOtherInclude.get("notinstalled")) { + toClean = append(toClean, pkg) + continue + } + + if cOtherInclude.get("a") || cOtherInclude.get("all") { + toClean = append(toClean, pkg) + continue + } + + if cIsInclude && (cInclude.get(len(pkgs)-i) || cOtherInclude.get(pkg.PackageBase)) { + toClean = append(toClean, pkg) + continue + } + + if !cIsInclude && (!cExclude.get(len(pkgs)-i) && !cOtherExclude.get(pkg.PackageBase)) { + toClean = append(toClean, pkg) + continue } } } - fmt.Println(bold(green(arrow + " PKGBUILDs to edit?"))) + return toClean, nil +} + +func editNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet) ([]*rpc.Pkg, error) { + return editDiffNumberMenu(pkgs, bases, installed, false) +} + +func diffNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet) ([]*rpc.Pkg, error) { + return editDiffNumberMenu(pkgs, bases, installed, true) +} + +func editDiffNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed stringSet, diff bool) ([]*rpc.Pkg, error) { + toEdit := make([]*rpc.Pkg, 0) + var editInput string + var err error + 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 + " "))) - - editInput, err := getInput(config.AnswerEdit) - if err != nil { - return nil, nil, err + if diff { + fmt.Println(bold(green(arrow + " Diffs to show?"))) + fmt.Print(bold(green(arrow + " "))) + editInput, err = getInput(config.AnswerDiff) + if err != nil { + return nil, err + } + } else { + fmt.Println(bold(green(arrow + " PKGBUILDs to edit?"))) + fmt.Print(bold(green(arrow + " "))) + editInput, err = getInput(config.AnswerEdit) + if err != nil { + return nil, err + } } eInclude, eExclude, eOtherInclude, eOtherExclude := parseNumberMenu(editInput) eIsInclude := len(eExclude) == 0 && len(eOtherExclude) == 0 if eOtherInclude.get("abort") || eOtherInclude.get("ab") { - return nil, nil, fmt.Errorf("Aborting due to user") + return nil, fmt.Errorf("Aborting due to user") } if !eOtherInclude.get("n") && !eOtherInclude.get("none") { @@ -465,7 +546,7 @@ func cleanEditNumberMenu(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, installed } } - return toClean, toEdit, nil + return toEdit, nil } func cleanBuilds(pkgs []*rpc.Pkg) { @@ -476,20 +557,67 @@ func cleanBuilds(pkgs []*rpc.Pkg) { } } -func editPkgBuilds(pkgs []*rpc.Pkg) error { +func showPkgBuildDiffs(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, cloned stringSet) error { + for _, pkg := range pkgs { + dir := filepath.Join(config.BuildDir, pkg.PackageBase) + if shouldUseGit(dir) { + start := "HEAD" + + if cloned.get(pkg.PackageBase) { + start = gitEmptyTree + } else { + hasDiff, err := gitHasDiff(config.BuildDir, pkg.PackageBase) + if err != nil { + return err + } + + if !hasDiff { + fmt.Printf("%s %s: %s\n", bold(yellow(arrow)), cyan(formatPkgbase(pkg, bases)), bold("No changes -- skipping")) + continue + } + } + + args := []string{"diff", start + "..HEAD@{upstream}", "--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) 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 @@ -530,40 +658,73 @@ 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 pkgBuildsToSkip(pkgs []*rpc.Pkg, targets stringSet) stringSet { + toSkip := make(stringSet) + + 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) if err == nil { - version, err := gopkg.NewCompleteVersion(pkg.Version) - if err == nil { - if !version.Newer(pkgbuild.Version()) { - str := bold(cyan("::") + " PKGBUILD up to date, Skipping (%d/%d): %s\n") - fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases))) - continue + versionRPC, errR := gopkg.NewCompleteVersion(pkg.Version) + versionPKG, errP := gopkg.NewCompleteVersion(pkgbuild.Version()) + if errP == nil && errR == nil { + if !versionRPC.Newer(versionPKG) { + toSkip.set(pkg.PackageBase) } } } } + } + + return toSkip +} + +func mergePkgBuilds(pkgs []*rpc.Pkg) error { + for _, pkg := range pkgs { + if shouldUseGit(filepath.Join(config.BuildDir, pkg.PackageBase)) { + err := gitMerge(baseURL+"/"+pkg.PackageBase+".git", config.BuildDir, pkg.PackageBase) + if err != nil { + return err + } + } + } + + return nil +} + +func downloadPkgBuilds(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, toSkip stringSet) (stringSet, error) { + cloned := make(stringSet) + + for k, pkg := range pkgs { + 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") fmt.Printf(str, k+1, len(pkgs), cyan(formatPkgbase(pkg, bases))) - var err error if shouldUseGit(filepath.Join(config.BuildDir, pkg.PackageBase)) { - err = gitDownload(baseURL+"/"+pkg.PackageBase+".git", config.BuildDir, pkg.PackageBase) + clone, err := gitDownload(baseURL+"/"+pkg.PackageBase+".git", config.BuildDir, pkg.PackageBase) + if err != nil { + return nil, err + } + if clone { + cloned.set(pkg.PackageBase) + } } else { - err = downloadAndUnpack(baseURL+pkg.URLPath, config.BuildDir, false) - } - if err != nil { - return err + err := downloadAndUnpack(baseURL+pkg.URLPath, config.BuildDir) + if err != nil { + return nil, err + } } } - return nil + return cloned, nil } func downloadPkgBuildsSources(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, incompatible stringSet) (err error) { @@ -584,45 +745,42 @@ func downloadPkgBuildsSources(pkgs []*rpc.Pkg, bases map[string][]*rpc.Pkg, inco return } -func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, targets stringSet, parser *arguments, bases map[string][]*rpc.Pkg, incompatible stringSet) error { - arch, err := alpmHandle.Arch() - if err != nil { - return err - } - - for _, pkg := range pkgs { +func buildInstallPkgBuilds(dp *depPool, do *depOrder, srcinfos map[string]*gopkg.PKGBUILD, parser *arguments, incompatible stringSet) error { + for _, pkg := range do.Aur { dir := filepath.Join(config.BuildDir, pkg.PackageBase) built := true srcinfo := srcinfos[pkg.PackageBase] + args := []string{"--nobuild", "-fC"} + + if incompatible.get(pkg.PackageBase) { + args = append(args, "--ignorearch") + } + //pkgver bump - err := passToMakepkg(dir, "--nobuild", "-fCc") + err := passToMakepkg(dir, args...) if err != nil { return fmt.Errorf("Error making: %s", pkg.Name) } - version, err := getVersionFromPkgbuild(dir) + pkgdests, version, err := parsePackageList(dir) if err != nil { return err } - if config.ReBuild == "no" || (config.ReBuild == "yes" && !targets.get(pkg.Name)) { - for _, split := range bases[pkg.PackageBase] { - file, err := completeFileName(dir, split.Name+"-"+version+"-"+arch+".pkg") - if err != nil { - return err + if config.ReBuild == "no" || (config.ReBuild == "yes" && !dp.Explicit.get(pkg.Name)) { + for _, split := range do.Bases[pkg.PackageBase] { + pkgdest, ok := pkgdests[split.Name] + if !ok { + return fmt.Errorf("Could not find PKGDEST for: %s", split.Name) } - if file == "" { - file, err = completeFileName(dir, split.Name+"-"+version+"-"+"any"+".pkg") - if err != nil { - return err - } - } - - if file == "" { + _, err := os.Stat(pkgdest) + if os.IsNotExist(err) { built = false + } else if err != nil { + return err } } } else { @@ -631,9 +789,9 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, if built { fmt.Println(bold(yellow(arrow)), - cyan(pkg.Name+"-"+pkg.Version)+bold(" Already made -- skipping build")) + cyan(pkg.Name+"-"+version)+bold(" Already made -- skipping build")) } else { - args := []string{"-Ccf", "--noconfirm"} + args := []string{"-cf", "--noconfirm", "--noextract", "--noprepare", "--holdver"} if incompatible.get(pkg.PackageBase) { args = append(args, "--ignorearch") @@ -646,7 +804,7 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, } arguments := parser.copy() - arguments.targets = make(stringSet) + arguments.clearTargets() arguments.op = "U" arguments.delArg("confirm") arguments.delArg("c", "clean") @@ -658,6 +816,8 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, depArguments := makeArguments() depArguments.addArg("D", "asdeps") + expArguments := makeArguments() + expArguments.addArg("D", "asexplicit") //remotenames: names of all non repo packages on the system _, _, localNames, remoteNames, err := filterPackages() @@ -670,28 +830,24 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, remoteNamesCache := sliceToStringSet(remoteNames) localNamesCache := sliceToStringSet(localNames) - for _, split := range bases[pkg.PackageBase] { - file, err := completeFileName(dir, split.Name+"-"+version+"-"+arch+".pkg") - if err != nil { - return err + for _, split := range do.Bases[pkg.PackageBase] { + pkgdest, ok := pkgdests[split.Name] + if !ok { + return fmt.Errorf("Could not find PKGDEST for: %s", split.Name) } - if file == "" { - file, err = completeFileName(dir, split.Name+"-"+version+"-"+"any"+".pkg") - if err != nil { - return err - } - } - - if file == "" { - return fmt.Errorf("Could not find built package " + split.Name + "-" + version + "-" + arch + ".pkg") - } - - arguments.addTarget(file) - //if !targets.get(split.Name) { - if !targets.get(split.Name) && !localNamesCache.get(split.Name) && !remoteNamesCache.get(split.Name) { + arguments.addTarget(pkgdest) + if !dp.Explicit.get(split.Name) && !localNamesCache.get(split.Name) && !remoteNamesCache.get(split.Name) { depArguments.addTarget(split.Name) } + + if dp.Explicit.get(split.Name) { + if parser.existsArg("asdeps", "asdep") { + depArguments.addTarget(split.Name) + } else if parser.existsArg("asexplicit", "asexp") { + expArguments.addTarget(split.Name) + } + } } oldConfirm := config.NoConfirm @@ -701,7 +857,7 @@ func buildInstallPkgBuilds(pkgs []*rpc.Pkg, srcinfos map[string]*gopkg.PKGBUILD, return err } - for _, pkg := range bases[pkg.PackageBase] { + for _, pkg := range do.Bases[pkg.PackageBase] { updateVCSData(pkg.Name, srcinfo.Source) } diff --git a/keys_test.go b/keys_test.go index f42941b3..ab8e0560 100644 --- a/keys_test.go +++ b/keys_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "fmt" "io/ioutil" "net/http" @@ -36,10 +37,6 @@ func newPkg(basename string) *rpc.Pkg { return &rpc.Pkg{Name: basename, PackageBase: basename} } -func newSplitPkg(basename, name string) *rpc.Pkg { - return &rpc.Pkg{Name: name, PackageBase: basename} -} - func getPgpKey(key string) string { var buffer bytes.Buffer @@ -73,7 +70,7 @@ func TestImportKeys(t *testing.T) { config.GpgFlags = fmt.Sprintf("--homedir %s --keyserver 127.0.0.1", keyringDir) server := startPgpKeyServer() - defer server.Shutdown(nil) + defer server.Shutdown(context.TODO()) casetests := []struct { keys []string @@ -138,7 +135,7 @@ func TestCheckPgpKeys(t *testing.T) { config.GpgFlags = fmt.Sprintf("--homedir %s --keyserver 127.0.0.1", keyringDir) server := startPgpKeyServer() - defer server.Shutdown(nil) + defer server.Shutdown(context.TODO()) casetests := []struct { pkgs []*rpc.Pkg diff --git a/main.go b/main.go index d3604cf7..17d6f819 100644 --- a/main.go +++ b/main.go @@ -10,29 +10,39 @@ import ( alpm "github.com/jguer/go-alpm" ) -func initPaths() { - if configHome = os.Getenv("XDG_CONFIG_HOME"); configHome != "" { - if info, err := os.Stat(configHome); err == nil && info.IsDir() { - configHome = configHome + "/yay" - } else { - configHome = filepath.Join(os.Getenv("HOME"), ".config/yay") +func setPaths() error { + if _configHome, set := os.LookupEnv("XDG_CONFIG_HOME"); set { + if _configHome == "" { + return fmt.Errorf("XDG_CONFIG_HOME set but empty") } + configHome = filepath.Join(_configHome, "yay") + } else if _configHome, set := os.LookupEnv("HOME"); set { + if _configHome == "" { + return fmt.Errorf("HOME set but empty") + } + configHome = filepath.Join(_configHome, ".config/yay") } else { - configHome = filepath.Join(os.Getenv("HOME"), ".config/yay") + return fmt.Errorf("XDG_CONFIG_HOME and HOME unset") } - if cacheHome = os.Getenv("XDG_CACHE_HOME"); cacheHome != "" { - if info, err := os.Stat(cacheHome); err == nil && info.IsDir() { - cacheHome = filepath.Join(cacheHome, "yay") - } else { - cacheHome = filepath.Join(os.Getenv("HOME"), ".cache/yay") + if _cacheHome, set := os.LookupEnv("XDG_CACHE_HOME"); set { + if _cacheHome == "" { + return fmt.Errorf("XDG_CACHE_HOME set but empty") } + cacheHome = filepath.Join(_cacheHome, "yay") + } else if _cacheHome, set := os.LookupEnv("HOME"); set { + if _cacheHome == "" { + return fmt.Errorf("XDG_CACHE_HOME set but empty") + } + cacheHome = filepath.Join(_cacheHome, ".cache/yay") } else { - cacheHome = filepath.Join(os.Getenv("HOME"), "/.cache/yay") + return fmt.Errorf("XDG_CACHE_HOME and HOME unset") } configFile = filepath.Join(configHome, configFileName) vcsFile = filepath.Join(cacheHome, vcsFileName) + + return nil } func initConfig() (err error) { @@ -60,6 +70,14 @@ func initConfig() (err error) { err) defaultSettings(&config) } + if _, err = os.Stat(config.BuildDir); os.IsNotExist(err) { + err = os.MkdirAll(config.BuildDir, 0755) + if err != nil { + err = fmt.Errorf("Unable to create BuildDir directory:\n%s\n"+ + "The error was:\n%s", config.BuildDir, err) + return + } + } } } @@ -176,7 +194,12 @@ func main() { goto cleanup } - initPaths() + err = setPaths() + if err != nil { + fmt.Println(err) + status = 1 + goto cleanup + } err = initConfig() if err != nil { @@ -212,7 +235,7 @@ func main() { cleanup: //cleanup - //from here on out dont exit if an error occurs + //from here on out don't exit if an error occurs //if we fail to save the configuration //at least continue on and try clean up other parts diff --git a/parser.go b/parser.go index 9957cb56..8a2ce936 100644 --- a/parser.go +++ b/parser.go @@ -1,7 +1,9 @@ package main import ( + "bytes" "fmt" + "html" "io" "os" "strconv" @@ -13,7 +15,7 @@ import ( // Other types of sets are used throughout the code but do not have // their own typedef. // String sets and sets should be used throughout the code when applicable, -// they are a lot more flexable than slices and provide easy lookup. +// they are a lot more flexible than slices and provide easy lookup. type stringSet map[string]struct{} func (set stringSet) set(v string) { @@ -39,6 +41,16 @@ func (set stringSet) toSlice() []string { return slice } +func (set stringSet) copy() stringSet { + newSet := make(stringSet) + + for str := range set { + newSet.set(str) + } + + return newSet +} + func sliceToStringSet(in []string) stringSet { set := make(stringSet) @@ -60,7 +72,7 @@ type arguments struct { options map[string]string globals map[string]string doubles stringSet // Tracks args passed twice such as -yy and -dd - targets stringSet + targets []string } func makeArguments() *arguments { @@ -69,7 +81,7 @@ func makeArguments() *arguments { make(map[string]string), make(map[string]string), make(stringSet), - make(stringSet), + make([]string, 0), } } @@ -86,9 +98,8 @@ func (parser *arguments) copy() (cp *arguments) { cp.globals[k] = v } - for k, v := range parser.targets { - cp.targets[k] = v - } + cp.targets = make([]string, len(parser.targets)) + copy(cp.targets, parser.targets) for k, v := range parser.doubles { cp.doubles[k] = v @@ -220,9 +231,12 @@ func (parser *arguments) getArg(options ...string) (arg string, double bool, exi existCount := 0 for _, option := range options { - arg, exists = parser.options[option] + var value string + + value, exists = parser.options[option] if exists { + arg = value existCount++ _, exists = parser.doubles[option] @@ -232,9 +246,10 @@ func (parser *arguments) getArg(options ...string) (arg string, double bool, exi } - arg, exists = parser.globals[option] + value, exists = parser.globals[option] if exists { + arg = value existCount++ _, exists = parser.doubles[option] @@ -252,15 +267,11 @@ func (parser *arguments) getArg(options ...string) (arg string, double bool, exi } func (parser *arguments) addTarget(targets ...string) { - for _, target := range targets { - parser.targets[target] = struct{}{} - } + parser.targets = append(parser.targets, targets...) } -func (parser *arguments) delTarget(targets ...string) { - for _, target := range targets { - delete(parser.targets, target) - } +func (parser *arguments) clearTargets() { + parser.targets = make([]string, 0) } // Multiple args acts as an OR operator @@ -275,14 +286,6 @@ func (parser *arguments) existsDouble(options ...string) bool { return false } -func (parser *arguments) formatTargets() (args []string) { - for target := range parser.targets { - args = append(args, target) - } - - return -} - func (parser *arguments) formatArgs() (args []string) { var op string @@ -297,15 +300,15 @@ func (parser *arguments) formatArgs() (args []string) { continue } - formatedOption := formatArg(option) - args = append(args, formatedOption) + formattedOption := formatArg(option) + args = append(args, formattedOption) if hasParam(option) { args = append(args, arg) } if parser.existsDouble(option) { - args = append(args, formatedOption) + args = append(args, formattedOption) } } @@ -314,15 +317,15 @@ func (parser *arguments) formatArgs() (args []string) { func (parser *arguments) formatGlobals() (args []string) { for option, arg := range parser.globals { - formatedOption := formatArg(option) - args = append(args, formatedOption) + formattedOption := formatArg(option) + args = append(args, formattedOption) if hasParam(option) { args = append(args, arg) } if parser.existsDouble(option) { - args = append(args, formatedOption) + args = append(args, formattedOption) } } @@ -466,6 +469,8 @@ func hasParam(arg string) bool { return true case "answerclean": return true + case "answerdiff": + return true case "answeredit": return true case "answerupgrade": @@ -619,8 +624,8 @@ func (parser *arguments) parseCommandLine() (err error) { //of course the implementation is up to the caller, this function mearley parses //the input and organizes it func parseNumberMenu(input string) (intRanges, intRanges, stringSet, stringSet) { - include := make(intRanges, 0, 0) - exclude := make(intRanges, 0, 0) + include := make(intRanges, 0) + exclude := make(intRanges, 0) otherInclude := make(stringSet) otherExclude := make(stringSet) @@ -669,3 +674,66 @@ func parseNumberMenu(input string) (intRanges, intRanges, stringSet, stringSet) return include, exclude, otherInclude, otherExclude } + +// Crude html parsing, good enough for the arch news +// This is only displayed in the terminal so there should be no security +// concerns +func parseNews(str string) string { + var buffer bytes.Buffer + var tagBuffer bytes.Buffer + var escapeBuffer bytes.Buffer + inTag := false + inEscape := false + + for _, char := range str { + if inTag { + if char == '>' { + inTag = false + switch tagBuffer.String() { + case "code": + buffer.WriteString(cyanCode) + case "/code": + buffer.WriteString(resetCode) + case "/p": + buffer.WriteRune('\n') + } + + continue + } + + tagBuffer.WriteRune(char) + continue + } + + if inEscape { + if char == ';' { + inEscape = false + escapeBuffer.WriteRune(char) + s := html.UnescapeString(escapeBuffer.String()) + buffer.WriteString(s) + continue + } + + escapeBuffer.WriteRune(char) + continue + } + + if char == '<' { + inTag = true + tagBuffer.Reset() + continue + } + + if char == '&' { + inEscape = true + escapeBuffer.Reset() + escapeBuffer.WriteRune(char) + continue + } + + buffer.WriteRune(char) + } + + buffer.WriteString(resetCode) + return buffer.String() +} diff --git a/parser_test.go b/parser_test.go index 47c05177..5aa57c67 100644 --- a/parser_test.go +++ b/parser_test.go @@ -19,7 +19,7 @@ func intRangesEqual(a, b intRanges) bool { r1 := a[n] r2 := b[n] - if r1.min != r1.min || r1.max != r2.max { + if r1.min != r2.min || r1.max != r2.max { return false } } diff --git a/print.go b/print.go index bb2873a8..f71fc5a7 100644 --- a/print.go +++ b/print.go @@ -1,7 +1,12 @@ package main import ( + "bufio" + "bytes" + "encoding/xml" "fmt" + "io/ioutil" + "net/http" "os" "strconv" "strings" @@ -13,7 +18,7 @@ import ( const arrow = "==>" const smallArrow = " ->" -func (warnings *aurWarnings) Print() { +func (warnings *aurWarnings) print() { if len(warnings.Missing) > 0 { fmt.Print(bold(yellow(smallArrow)) + " Missing AUR Packages:") for _, name := range warnings.Missing { @@ -153,23 +158,38 @@ func formatPkgbase(pkg *rpc.Pkg, bases map[string][]*rpc.Pkg) string { return str } +func (u upgrade) StylizedNameWithRepository() string { + return bold(colourHash(u.Repository)) + "/" + bold(u.Name) +} + // Print prints the details of the packages to upgrade. -func (u upSlice) Print(start int) { +func (u upSlice) print() { + longestName, longestVersion := 0, 0 + for _, pack := range u { + packNameLen := len(pack.StylizedNameWithRepository()) + version, _ := getVersionDiff(pack.LocalVersion, pack.RemoteVersion) + packVersionLen := len(version) + longestName = max(packNameLen, longestName) + longestVersion = max(packVersionLen, longestVersion) + } + + namePadding := fmt.Sprintf("%%-%ds ", longestName) + versionPadding := fmt.Sprintf("%%-%ds", longestVersion) + numberPadding := fmt.Sprintf("%%%dd ", len(fmt.Sprintf("%v", len(u)))) + for k, i := range u { left, right := getVersionDiff(i.LocalVersion, i.RemoteVersion) - fmt.Print(magenta(fmt.Sprintf("%3d ", len(u)+start-k-1))) - fmt.Print(bold(colourHash(i.Repository)), "/", bold(i.Name)) + fmt.Print(magenta(fmt.Sprintf(numberPadding, len(u)-k))) - w := 70 - len(i.Repository) - len(i.Name) - padding := fmt.Sprintf("%%%ds", w) - fmt.Printf(padding, left) - fmt.Printf(" -> %s\n", right) + fmt.Printf(namePadding, i.StylizedNameWithRepository()) + + fmt.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right) } } // printDownloadsFromRepo prints repository packages to be downloaded -func printDepCatagories(dc *depCatagories) { +func (do *depOrder) Print() { repo := "" repoMake := "" aur := "" @@ -180,47 +200,47 @@ func printDepCatagories(dc *depCatagories) { aurLen := 0 aurMakeLen := 0 - for _, pkg := range dc.Repo { - if dc.MakeOnly.get(pkg.Name()) { - repoMake += " " + pkg.Name() + "-" + pkg.Version() - repoMakeLen++ - } else { + for _, pkg := range do.Repo { + if do.Runtime.get(pkg.Name()) { repo += " " + pkg.Name() + "-" + pkg.Version() repoLen++ + } else { + repoMake += " " + pkg.Name() + "-" + pkg.Version() + repoMakeLen++ } } - for _, pkg := range dc.Aur { + for _, pkg := range do.Aur { pkgStr := " " + pkg.PackageBase + "-" + pkg.Version pkgStrMake := pkgStr push := false pushMake := false - if len(dc.Bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name { + if len(do.Bases[pkg.PackageBase]) > 1 || pkg.PackageBase != pkg.Name { pkgStr += " (" pkgStrMake += " (" - for _, split := range dc.Bases[pkg.PackageBase] { - if dc.MakeOnly.get(split.Name) { - pkgStrMake += split.Name + " " - aurMakeLen++ - pushMake = true - } else { + for _, split := range do.Bases[pkg.PackageBase] { + if do.Runtime.get(split.Name) { pkgStr += split.Name + " " aurLen++ push = true + } else { + pkgStrMake += split.Name + " " + aurMakeLen++ + pushMake = true } } pkgStr = pkgStr[:len(pkgStr)-1] + ")" pkgStrMake = pkgStrMake[:len(pkgStrMake)-1] + ")" - } else if dc.MakeOnly.get(pkg.Name) { - aurMakeLen++ - pushMake = true - } else { + } else if do.Runtime.get(pkg.Name) { aurLen++ push = true + } else { + aurMakeLen++ + pushMake = true } if push { @@ -247,25 +267,31 @@ func printDownloads(repoName string, length int, packages string) { fmt.Println(repoInfo + cyan(packages)) } +func printInfoValue(str, value string) { + fmt.Printf(bold("%-16s%s")+" %s\n", str, ":", value) +} + // PrintInfo prints package info like pacman -Si. func PrintInfo(a *rpc.Pkg) { - fmt.Println(bold("Repository :"), "aur") - fmt.Println(bold("Name :"), a.Name) - fmt.Println(bold("Version :"), a.Version) - fmt.Println(bold("Description :"), a.Description) - fmt.Println(bold("URL :"), a.URL) - fmt.Println(bold("Licenses :"), strings.Join(a.License, " ")) - fmt.Println(bold("Provides :"), strings.Join(a.Provides, " ")) - fmt.Println(bold("Depends On :"), strings.Join(a.Depends, " ")) - fmt.Println(bold("Make Deps :"), strings.Join(a.MakeDepends, " ")) - fmt.Println(bold("Check Deps :"), strings.Join(a.CheckDepends, " ")) - fmt.Println(bold("Optional Deps :"), strings.Join(a.OptDepends, " ")) - fmt.Println(bold("Conflicts With :"), strings.Join(a.Conflicts, " ")) - fmt.Println(bold("Maintainer :"), a.Maintainer) - fmt.Println(bold("Votes :"), a.NumVotes) - fmt.Println(bold("Popularity :"), a.Popularity) + printInfoValue("Repository", "aur") + printInfoValue("Name", a.Name) + printInfoValue("Version", a.Version) + printInfoValue("Description", a.Description) + printInfoValue("URL", a.URL) + printInfoValue("Licenses", strings.Join(a.License, " ")) + printInfoValue("Provides", strings.Join(a.Provides, " ")) + printInfoValue("Depends On", strings.Join(a.Depends, " ")) + printInfoValue("Make Deps", strings.Join(a.MakeDepends, " ")) + printInfoValue("Check Deps", strings.Join(a.CheckDepends, " ")) + printInfoValue("Optional Deps", strings.Join(a.OptDepends, " ")) + printInfoValue("Conflicts With", strings.Join(a.Conflicts, " ")) + printInfoValue("Maintainer", a.Maintainer) + printInfoValue("Votes", fmt.Sprintf("%d", a.NumVotes)) + printInfoValue("Popularity", fmt.Sprintf("%f", a.Popularity)) if a.OutOfDate != 0 { - fmt.Println(bold("Out-of-date :"), "Yes", "["+formatTime(a.OutOfDate)+"]") + printInfoValue("Out-of-date", "Yes ["+formatTime(a.OutOfDate)+"]") + } else { + printInfoValue("Out-of-date", "No") } fmt.Println() @@ -337,32 +363,37 @@ func printNumberOfUpdates() error { //TODO: Make it less hacky func printUpdateList(parser *arguments) error { + targets := sliceToStringSet(parser.targets) warnings := &aurWarnings{} old := os.Stdout // keep backup of the real stdout os.Stdout = nil _, _, localNames, remoteNames, err := filterPackages() + if err != nil { + return err + } + aurUp, repoUp, err := upList(warnings) os.Stdout = old // restoring the real stdout if err != nil { return err } - noTargets := len(parser.targets) == 0 + noTargets := len(targets) == 0 - if !parser.existsArg("m", "foreigne") { + if !parser.existsArg("m", "foreign") { for _, pkg := range repoUp { - if noTargets || parser.targets.get(pkg.Name) { + if noTargets || targets.get(pkg.Name) { fmt.Printf("%s %s -> %s\n", bold(pkg.Name), green(pkg.LocalVersion), green(pkg.RemoteVersion)) - delete(parser.targets, pkg.Name) + delete(targets, pkg.Name) } } } if !parser.existsArg("n", "native") { for _, pkg := range aurUp { - if noTargets || parser.targets.get(pkg.Name) { + if noTargets || targets.get(pkg.Name) { fmt.Printf("%s %s -> %s\n", bold(pkg.Name), green(pkg.LocalVersion), green(pkg.RemoteVersion)) - delete(parser.targets, pkg.Name) + delete(targets, pkg.Name) } } } @@ -370,7 +401,7 @@ func printUpdateList(parser *arguments) error { missing := false outer: - for pkg := range parser.targets { + for pkg := range targets { for _, name := range localNames { if name == pkg { continue outer @@ -394,10 +425,93 @@ outer: return nil } -// Formats a unix timestamp to yyyy/mm/dd +type item struct { + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + PubDate string `xml:"pubDate"` + Creator string `xml:"dc:creator"` +} + +func (item item) print(buildTime time.Time) { + var fd string + date, err := time.Parse(time.RFC1123Z, item.PubDate) + + if err != nil { + fmt.Println(err) + } else { + fd = formatTime(int(date.Unix())) + if _, double, _ := cmdArgs.getArg("news", "w"); !double && !buildTime.IsZero() { + if buildTime.After(date) { + return + } + } + } + + fmt.Println(bold(magenta(fd)), bold(strings.TrimSpace(item.Title))) + //fmt.Println(strings.TrimSpace(item.Link)) + + if !cmdArgs.existsArg("q", "quiet") { + desc := strings.TrimSpace(parseNews(item.Description)) + fmt.Println(desc) + } +} + +type channel struct { + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + Language string `xml:"language"` + Lastbuilddate string `xml:"lastbuilddate"` + Items []item `xml:"item"` +} + +type rss struct { + Channel channel `xml:"channel"` +} + +func printNewsFeed() error { + resp, err := http.Get("https://archlinux.org/feeds/news") + if err != nil { + return err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + rss := rss{} + + d := xml.NewDecoder(bytes.NewReader(body)) + err = d.Decode(&rss) + if err != nil { + return err + } + + buildTime, err := lastBuildTime() + if err != nil { + return err + } + + if config.SortMode == BottomUp { + for i := len(rss.Channel.Items) - 1; i >= 0; i-- { + rss.Channel.Items[i].print(buildTime) + } + } else { + for i := 0; i < len(rss.Channel.Items); i++ { + rss.Channel.Items[i].print(buildTime) + } + } + + return nil +} + +// Formats a unix timestamp to ISO 8601 date (yyyy-mm-dd) func formatTime(i int) string { t := time.Unix(int64(i), 0) - return fmt.Sprintf("%d/%02d/%02d", t.Year(), int(t.Month()), t.Day()) + return t.Format("2006-01-02") } const ( @@ -460,3 +574,61 @@ func colourHash(name string) (output string) { } return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name) } + +func providerMenu(dep string, providers providers) *rpc.Pkg { + size := providers.Len() + + fmt.Print(bold(cyan(":: "))) + str := bold(fmt.Sprintf(bold("There are %d providers available for %s:"), size, dep)) + + size = 1 + str += bold(cyan("\n:: ")) + bold("Repository AUR\n ") + + for _, pkg := range providers.Pkgs { + str += fmt.Sprintf("%d) %s ", size, pkg.Name) + size++ + } + + fmt.Println(str) + + for { + fmt.Print("\nEnter a number (default=1): ") + + if config.NoConfirm { + fmt.Println() + break + } + + reader := bufio.NewReader(os.Stdin) + numberBuf, overflow, err := reader.ReadLine() + + if err != nil { + fmt.Println(err) + break + } + + if overflow { + fmt.Println("Input too long") + continue + } + + if string(numberBuf) == "" { + return providers.Pkgs[0] + } + + num, err := strconv.Atoi(string(numberBuf)) + if err != nil { + fmt.Printf("%s invalid number: %s\n", red("error:"), string(numberBuf)) + continue + } + + if num < 1 || num > size { + fmt.Printf("%s invalid value: %d is not between %d and %d\n", red("error:"), num, 1, size) + continue + } + + return providers.Pkgs[num-1] + } + + return nil +} diff --git a/query.go b/query.go index 99dcfe20..8ead0b79 100644 --- a/query.go +++ b/query.go @@ -5,6 +5,7 @@ import ( "sort" "strings" "sync" + "time" alpm "github.com/jguer/go-alpm" rpc "github.com/mikkeloscar/aur" @@ -158,23 +159,41 @@ func narrowSearch(pkgS []string, sortS bool) (aurQuery, error) { // SyncSearch presents a query to the local repos and to the AUR. func syncSearch(pkgS []string) (err error) { - aq, aurErr := narrowSearch(pkgS, true) - pq, _, err := queryRepo(pkgS) - if err != nil { - return err + pkgS = removeInvalidTargets(pkgS) + var aurErr error + var repoErr error + var aq aurQuery + var pq repoQuery + + if mode == ModeAUR || mode == ModeAny { + aq, aurErr = narrowSearch(pkgS, true) + } + if mode == ModeRepo || mode == ModeAny { + pq, _, repoErr = queryRepo(pkgS) + if repoErr != nil { + return err + } } if config.SortMode == BottomUp { - aq.printSearch(1) - pq.printSearch() + if mode == ModeAUR || mode == ModeAny { + aq.printSearch(1) + } + if mode == ModeRepo || mode == ModeAny { + pq.printSearch() + } } else { - pq.printSearch() - aq.printSearch(1) + if mode == ModeRepo || mode == ModeAny { + pq.printSearch() + } + if mode == ModeAUR || mode == ModeAny { + aq.printSearch(1) + } } if aurErr != nil { fmt.Printf("Error during AUR search: %s\n", aurErr) - fmt.Println("Showing Repo packags only") + fmt.Println("Showing Repo packages only") } return nil @@ -183,6 +202,8 @@ func syncSearch(pkgS []string) (err error) { // SyncInfo serves as a pacman -Si for repo packages and AUR packages. func syncInfo(pkgS []string) (err error) { var info []*rpc.Pkg + missing := false + pkgS = removeInvalidTargets(pkgS) aurS, repoS, err := packageSlices(pkgS) if err != nil { return @@ -198,6 +219,7 @@ func syncInfo(pkgS []string) (err error) { info, err = aurInfoPrint(noDb) if err != nil { + missing = true fmt.Println(err) } } @@ -205,7 +227,8 @@ func syncInfo(pkgS []string) (err error) { // Repo always goes first if len(repoS) != 0 { arguments := cmdArgs.copy() - arguments.delTarget(aurS...) + arguments.clearTargets() + arguments.addTarget(repoS...) err = passToPacman(arguments) if err != nil { @@ -213,12 +236,20 @@ func syncInfo(pkgS []string) (err error) { } } - if len(aurS) != 0 { + if len(aurS) != len(info) { + missing = true + } + + if len(info) != 0 { for _, pkg := range info { PrintInfo(pkg) } } + if missing { + err = fmt.Errorf("") + } + return } @@ -283,10 +314,10 @@ func packageSlices(toCheck []string) (aur []string, repo []string, err error) { db, name := splitDbFromName(_pkg) found := false - if db == "aur" { + if db == "aur" || mode == ModeAUR { aur = append(aur, _pkg) continue - } else if db != "" { + } else if db != "" || mode == ModeRepo { repo = append(repo, _pkg) continue } @@ -332,7 +363,7 @@ func hangingPackages(removeOptional bool) (hanging []string, err error) { // State = 2 - Keep package and have iterated over dependencies safePackages := make(map[string]uint8) // provides stores a mapping from the provides name back to the original package name - provides := make(map[string]stringSet) + provides := make(mapStringSet) packages := localDb.PkgCache() // Mark explicit dependencies and enumerate the provides list @@ -344,7 +375,7 @@ func hangingPackages(removeOptional bool) (hanging []string, err error) { } pkg.Provides().ForEach(func(dep alpm.Depend) error { - addMapStringSet(provides, dep.Name, pkg.Name()) + provides.Add(dep.Name, pkg.Name()) return nil }) return nil @@ -353,7 +384,7 @@ func hangingPackages(removeOptional bool) (hanging []string, err error) { iterateAgain := true processDependencies := func(pkg alpm.Package) error { - if state, _ := safePackages[pkg.Name()]; state == 0 || state == 2 { + if state := safePackages[pkg.Name()]; state == 0 || state == 2 { return nil } @@ -407,6 +438,24 @@ func hangingPackages(removeOptional bool) (hanging []string, err error) { return } +func lastBuildTime() (time.Time, error) { + var time time.Time + + pkgs, _, _, _, err := filterPackages() + if err != nil { + return time, err + } + + for _, pkg := range pkgs { + thisTime := pkg.BuildDate() + if thisTime.After(time) { + time = thisTime + } + } + + return time, nil +} + // Statistics returns statistics about packages installed in system func statistics() (info struct { Totaln int @@ -516,7 +565,7 @@ func aurInfoPrint(names []string) ([]*rpc.Pkg, error) { return info, err } - warnings.Print() + warnings.print() return info, nil } diff --git a/upgrade.go b/upgrade.go index f816d728..4daac107 100644 --- a/upgrade.go +++ b/upgrade.go @@ -138,31 +138,39 @@ func upList(warnings *aurWarnings) (aurUp upSlice, repoUp upSlice, err error) { var aurErr error var develErr error - fmt.Println(bold(cyan("::") + bold(" Searching databases for updates..."))) - wg.Add(1) - go func() { - repoUp, repoErr = upRepo(local) - wg.Done() - }() + pkgdata := make(map[string]*rpc.Pkg) - fmt.Println(bold(cyan("::") + bold(" Searching AUR for updates..."))) - wg.Add(1) - go func() { - aurUp, aurErr = upAUR(remote, remoteNames, warnings) - wg.Done() - }() - - if config.Devel { - fmt.Println(bold(cyan("::") + bold(" Checking development packages..."))) + if mode == ModeAny || mode == ModeRepo { + fmt.Println(bold(cyan("::") + bold(" Searching databases for updates..."))) wg.Add(1) go func() { - develUp, develErr = upDevel(remote) + repoUp, repoErr = upRepo(local) wg.Done() }() } + if mode == ModeAny || mode == ModeAUR { + fmt.Println(bold(cyan("::") + bold(" Searching AUR for updates..."))) + wg.Add(1) + go func() { + aurUp, aurErr = upAUR(remote, remoteNames, pkgdata, warnings) + wg.Done() + }() + + if config.Devel { + fmt.Println(bold(cyan("::") + bold(" Checking development packages..."))) + wg.Add(1) + go func() { + develUp, develErr = upDevel(remote) + wg.Done() + }() + } + } + wg.Wait() + printLocalNewerThanAUR(remote, pkgdata) + errs := make([]string, 0) for _, e := range []error{repoErr, aurErr, develErr} { if e != nil { @@ -193,8 +201,8 @@ func upList(warnings *aurWarnings) (aurUp upSlice, repoUp upSlice, err error) { } func upDevel(remote []alpm.Package) (toUpgrade upSlice, err error) { - toUpdate := make([]alpm.Package, 0, 0) - toRemove := make([]string, 0, 0) + toUpdate := make([]alpm.Package, 0) + toRemove := make([]string, 0) var mux1 sync.Mutex var mux2 sync.Mutex @@ -228,9 +236,7 @@ func upDevel(remote []alpm.Package) (toUpgrade upSlice, err error) { for _, pkg := range toUpdate { if pkg.ShouldIgnore() { - left, right := getVersionDiff(pkg.Version(), "latest-commit") - fmt.Print(yellow(bold(smallArrow))) - fmt.Printf(" Ignoring package upgrade %s (%s => %s)\n", cyan(pkg.Name()), left, right) + printIgnoringPackage(pkg, "latest-commit") } else { toUpgrade = append(toUpgrade, upgrade{pkg.Name(), "devel", pkg.Version(), "latest-commit"}) } @@ -242,14 +248,16 @@ func upDevel(remote []alpm.Package) (toUpgrade upSlice, err error) { // upAUR gathers foreign packages and checks if they have new versions. // Output: Upgrade type package list. -func upAUR(remote []alpm.Package, remoteNames []string, warnings *aurWarnings) (upSlice, error) { +func upAUR( + remote []alpm.Package, remoteNames []string, + pkgdata map[string]*rpc.Pkg, warnings *aurWarnings) (upSlice, error) { + toUpgrade := make(upSlice, 0) _pkgdata, err := aurInfo(remoteNames, warnings) if err != nil { return nil, err } - pkgdata := make(map[string]*rpc.Pkg) for _, pkg := range _pkgdata { pkgdata[pkg.Name] = pkg } @@ -263,9 +271,7 @@ func upAUR(remote []alpm.Package, remoteNames []string, warnings *aurWarnings) ( if (config.TimeUpdate && (int64(aurPkg.LastModified) > pkg.BuildDate().Unix())) || (alpm.VerCmp(pkg.Version(), aurPkg.Version) < 0) { if pkg.ShouldIgnore() { - left, right := getVersionDiff(pkg.Version(), aurPkg.Version) - fmt.Print(yellow(bold(smallArrow))) - fmt.Printf(" Ignoring package upgrade: %s (%s => %s)\n", cyan(pkg.Name()), left, right) + printIgnoringPackage(pkg, aurPkg.Version) } else { toUpgrade = append(toUpgrade, upgrade{aurPkg.Name, "aur", pkg.Version(), aurPkg.Version}) } @@ -275,6 +281,35 @@ func upAUR(remote []alpm.Package, remoteNames []string, warnings *aurWarnings) ( return toUpgrade, nil } +func printIgnoringPackage(pkg alpm.Package, newPkgVersion string) { + left, right := getVersionDiff(pkg.Version(), newPkgVersion) + + fmt.Println( + yellow(bold(smallArrow)) + fmt.Sprintf( + " Ignoring package upgrade: %s (%s -> %s)", + cyan(pkg.Name()), left, right)) +} + +func printLocalNewerThanAUR( + remote []alpm.Package, pkgdata map[string]*rpc.Pkg) { + for _, pkg := range remote { + aurPkg, ok := pkgdata[pkg.Name()] + if !ok { + continue + } + + left, right := getVersionDiff(pkg.Version(), aurPkg.Version) + + if !isDevelName(pkg.Name()) && + alpm.VerCmp(pkg.Version(), aurPkg.Version) > 0 { + fmt.Println( + yellow(bold(smallArrow)) + fmt.Sprintf( + " Local package is newer than AUR: %s (%s -> %s)", + cyan(pkg.Name()), left, right)) + } + } +} + // upRepo gathers local packages and checks if they have new versions. // Output: Upgrade type package list. func upRepo(local []alpm.Package) (upSlice, error) { @@ -289,9 +324,7 @@ func upRepo(local []alpm.Package) (upSlice, error) { newPkg := pkg.NewVersion(dbList) if newPkg != nil { if pkg.ShouldIgnore() { - left, right := getVersionDiff(pkg.Version(), newPkg.Version()) - fmt.Print(yellow(bold(smallArrow))) - fmt.Printf(" Ignoring package upgrade: %s (%s => %s)\n", cyan(pkg.Name()), left, right) + printIgnoringPackage(pkg, newPkg.Version()) } else { slice = append(slice, upgrade{pkg.Name(), newPkg.DB().Name(), pkg.Version(), newPkg.Version()}) } @@ -305,15 +338,24 @@ func upgradePkgs(aurUp, repoUp upSlice) (stringSet, stringSet, error) { ignore := make(stringSet) aurNames := make(stringSet) - if len(aurUp)+len(repoUp) == 0 { + allUpLen := len(repoUp) + len(aurUp) + if allUpLen == 0 { + return ignore, aurNames, nil + } + + if !config.UpgradeMenu { + for _, pkg := range aurUp { + aurNames.set(pkg.Name) + } + return ignore, aurNames, nil } sort.Sort(repoUp) sort.Sort(aurUp) - fmt.Printf("%s"+bold(" %d ")+"%s\n", bold(cyan("::")), len(aurUp)+len(repoUp), bold("Packages to upgrade.")) - repoUp.Print(len(aurUp) + 1) - aurUp.Print(1) + allUp := append(repoUp, aurUp...) + fmt.Printf("%s"+bold(" %d ")+"%s\n", bold(cyan("::")), allUpLen, bold("Packages to upgrade.")) + allUp.print() fmt.Println(bold(green(arrow + " Packages to not upgrade: (eg: 1 2 3, 1-3, ^4 or repo name)"))) fmt.Print(bold(green(arrow + " "))) diff --git a/utils.go b/utils.go index be9f0449..c9e9017e 100644 --- a/utils.go +++ b/utils.go @@ -1,12 +1,14 @@ package main import ( - "io/ioutil" - "path/filepath" - "strings" + "fmt" "unicode" ) +const gitEmptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +type mapStringSet map[string]stringSet + type intRange struct { min int max int @@ -49,39 +51,12 @@ func max(a, b int) int { return a } -func addMapStringSet(h map[string]stringSet, n string, v string) { - _, ok := h[n] +func (mss mapStringSet) Add(n string, v string) { + _, ok := mss[n] if !ok { - h[n] = make(stringSet) + mss[n] = make(stringSet) } - h[n].set(v) -} - -func addMapStringSlice(h map[string][]string, n string, v string) { - _, ok := h[n] - if !ok { - h[n] = make([]string, 0, 1) - } - h[n] = append(h[n], v) -} - -func completeFileName(dir, name string) (string, error) { - files, err := ioutil.ReadDir(dir) - if err != nil { - return "", err - } - - for _, file := range files { - if file.IsDir() { - continue - } - - if strings.HasPrefix(file.Name(), name) { - return filepath.Join(dir, file.Name()), nil - } - } - - return "", nil + mss[n].set(v) } func lessRunes(iRunes, jRunes []rune) bool { @@ -109,3 +84,47 @@ func lessRunes(iRunes, jRunes []rune) bool { return len(iRunes) < len(jRunes) } + +func stringSliceEqual(a, b []string) bool { + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + + return true +} + +func removeInvalidTargets(targets []string) []string { + filteredTargets := make([]string, 0) + + for _, target := range targets { + db, _ := splitDbFromName(target) + + if db == "aur" && mode == ModeRepo { + fmt.Printf("%s %s %s\n", bold(yellow(arrow)), cyan(target), bold("Can't use target with option --repo -- skipping")) + continue + } + + if db != "aur" && db != "" && mode == ModeAUR { + fmt.Printf("%s %s %s\n", bold(yellow(arrow)), cyan(target), bold("Can't use target with option --aur -- skipping")) + continue + } + + filteredTargets = append(filteredTargets, target) + } + + return filteredTargets +} diff --git a/vcs.go b/vcs.go index 9b120b7d..857185d3 100644 --- a/vcs.go +++ b/vcs.go @@ -18,7 +18,7 @@ type vcsInfo map[string]shaInfos type shaInfos map[string]shaInfo type shaInfo struct { Protocols []string `json:"protocols"` - Brach string `json:"branch"` + Branch string `json:"branch"` SHA string `json:"sha"` } @@ -43,7 +43,8 @@ func createDevelDB() error { bases := getBases(infoMap) - downloadPkgBuilds(info, sliceToStringSet(remoteNames), bases) + toSkip := pkgBuildsToSkip(info, sliceToStringSet(remoteNames)) + downloadPkgBuilds(info, bases, toSkip) tryParsesrcinfosFile(info, srcinfosStale, bases) for _, pkg := range info { @@ -136,6 +137,7 @@ func getCommit(url string, branch string, protocols []string) string { cmd := exec.Command(config.GitBin, "ls-remote", protocol+"://"+url, branch) cmd.Stdout = &outbuf + cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") err := cmd.Start() if err != nil { @@ -179,7 +181,7 @@ func (infos shaInfos) needsUpdate() bool { hasUpdate := make(chan struct{}) checkHash := func(url string, info shaInfo) { - hash := getCommit(url, info.Brach, info.Protocols) + hash := getCommit(url, info.Branch, info.Protocols) if hash != "" && hash != info.SHA { hasUpdate <- struct{}{} } else { @@ -205,10 +207,6 @@ func (infos shaInfos) needsUpdate() bool { } } -func inStore(pkgName string) shaInfos { - return savedInfo[pkgName] -} - func saveVCSInfo() error { marshalledinfo, err := json.MarshalIndent(savedInfo, "", "\t") if err != nil || string(marshalledinfo) == "null" { diff --git a/vendor/github.com/jguer/go-alpm/conf.go b/vendor/github.com/jguer/go-alpm/conf.go index f449d8e2..18033239 100644 --- a/vendor/github.com/jguer/go-alpm/conf.go +++ b/vendor/github.com/jguer/go-alpm/conf.go @@ -114,18 +114,14 @@ func (rdr *confReader) ParseLine() (tok iniToken, err error) { rdr.Lineno++ line = bytes.TrimSpace(line) - - comment := bytes.IndexByte(line, '#') - if comment >= 0 { - line = line[:comment] - } - if len(line) == 0 { tok.Type = tokenComment return } - switch line[0] { + case '#': + tok.Type = tokenComment + return case '[': closing := bytes.IndexByte(line, ']') if closing < 0 { @@ -205,6 +201,7 @@ lineloop: curRepo.Servers = append(curRepo.Servers, line.Values...) continue lineloop case "Include": + conf.Include = append(conf.Include, line.Values[0]) f, err := os.Open(line.Values[0]) if err != nil { err = fmt.Errorf("error while processing Include directive at line %d: %s", diff --git a/vendor/github.com/jguer/go-alpm/db.go b/vendor/github.com/jguer/go-alpm/db.go index 90d1698c..214984de 100644 --- a/vendor/github.com/jguer/go-alpm/db.go +++ b/vendor/github.com/jguer/go-alpm/db.go @@ -89,7 +89,7 @@ func (h Handle) RegisterSyncDb(dbname string, siglevel SigLevel) (*Db, error) { cName := C.CString(dbname) defer C.free(unsafe.Pointer(cName)) - db := C.alpm_register_syncdb(h.ptr, cName, C.alpm_siglevel_t(siglevel)) + db := C.alpm_register_syncdb(h.ptr, cName, C.int(siglevel)) if db == nil { return nil, h.LastError() } diff --git a/vendor/github.com/jguer/go-alpm/enums.go b/vendor/github.com/jguer/go-alpm/enums.go index 2b1bdd3a..3c692bc1 100644 --- a/vendor/github.com/jguer/go-alpm/enums.go +++ b/vendor/github.com/jguer/go-alpm/enums.go @@ -62,7 +62,7 @@ func (mod DepMod) String() string { } // Signature checking level. -type SigLevel uint +type SigLevel int const ( SigPackage SigLevel = 1 << iota @@ -76,10 +76,10 @@ const ( SigDatabaseMarginalOk SigDatabaseUnknownOk ) -const SigUseDefault SigLevel = 1 << 31 +const SigUseDefault SigLevel = 1 << 30 // Signature status -type SigStatus uint +type SigStatus int const ( SigStatusValid SigStatus = iota diff --git a/vendor/github.com/jguer/go-alpm/handle.go b/vendor/github.com/jguer/go-alpm/handle.go index cdf4a432..d2c8ee82 100644 --- a/vendor/github.com/jguer/go-alpm/handle.go +++ b/vendor/github.com/jguer/go-alpm/handle.go @@ -534,7 +534,7 @@ func (h Handle) GetDefaultSigLevel() (SigLevel, error) { } func (h Handle) SetDefaultSigLevel(siglevel SigLevel) error { - ok := C.alpm_option_set_default_siglevel(h.ptr, C.alpm_siglevel_t(siglevel)) + ok := C.alpm_option_set_default_siglevel(h.ptr, C.int(siglevel)) if ok < 0 { return h.LastError() @@ -552,7 +552,7 @@ func (h Handle) GetLocalFileSigLevel() (SigLevel, error) { } func (h Handle) SetLocalFileSigLevel(siglevel SigLevel) error { - ok := C.alpm_option_set_local_file_siglevel(h.ptr, C.alpm_siglevel_t(siglevel)) + ok := C.alpm_option_set_local_file_siglevel(h.ptr, C.int(siglevel)) if ok < 0 { return h.LastError() @@ -570,7 +570,7 @@ func (h Handle) GetRemoteFileSigLevel() (SigLevel, error) { } func (h Handle) SetRemoteFileSigLevel(siglevel SigLevel) error { - ok := C.alpm_option_set_remote_file_siglevel(h.ptr, C.alpm_siglevel_t(siglevel)) + ok := C.alpm_option_set_remote_file_siglevel(h.ptr, C.int(siglevel)) if ok < 0 { return h.LastError() diff --git a/vendor/github.com/jguer/go-alpm/package.go b/vendor/github.com/jguer/go-alpm/package.go index ef589dd0..8873eb5b 100644 --- a/vendor/github.com/jguer/go-alpm/package.go +++ b/vendor/github.com/jguer/go-alpm/package.go @@ -159,18 +159,16 @@ func (pkg Package) OptionalDepends() DependList { } // Depends returns the package's check dependency list. -//Exists in futre alpm -/*func (pkg Package) CheckDepends() DependList { +func (pkg Package) CheckDepends() DependList { ptr := unsafe.Pointer(C.alpm_pkg_get_checkdepends(pkg.pmpkg)) return DependList{(*list)(ptr)} -}*/ +} // Depends returns the package's make dependency list. -//Exists in futre alpm -/*func (pkg Package) MakeDepends() DependList { +func (pkg Package) MakeDepends() DependList { ptr := unsafe.Pointer(C.alpm_pkg_get_makedepends(pkg.pmpkg)) return DependList{(*list)(ptr)} -}*/ +} // Description returns the package's description. func (pkg Package) Description() string { diff --git a/vendor/github.com/mikkeloscar/aur/aur.go b/vendor/github.com/mikkeloscar/aur/aur.go index 8ae7cc3b..6e0a804a 100644 --- a/vendor/github.com/mikkeloscar/aur/aur.go +++ b/vendor/github.com/mikkeloscar/aur/aur.go @@ -7,7 +7,8 @@ import ( "net/url" ) -const aurURL = "https://aur.archlinux.org/rpc.php?" +//AURURL is the base string from which the query is built +var AURURL = "https://aur.archlinux.org/rpc.php?" type response struct { Error string `json:"error"` @@ -46,7 +47,7 @@ type Pkg struct { func get(values url.Values) ([]Pkg, error) { values.Set("v", "5") - resp, err := http.Get(aurURL + values.Encode()) + resp, err := http.Get(AURURL + values.Encode()) if err != nil { return nil, err } diff --git a/vendor/github.com/mikkeloscar/gopkgbuild/lex.go b/vendor/github.com/mikkeloscar/gopkgbuild/lex.go index 12518bcb..dd2d429f 100644 --- a/vendor/github.com/mikkeloscar/gopkgbuild/lex.go +++ b/vendor/github.com/mikkeloscar/gopkgbuild/lex.go @@ -211,6 +211,8 @@ func lexEnv(l *lexer) stateFn { } case r == '\t': l.ignore() + case r == ' ': + l.ignore() case r == '#': return lexComment default: diff --git a/vendor/github.com/mikkeloscar/gopkgbuild/pkgbuild.go b/vendor/github.com/mikkeloscar/gopkgbuild/pkgbuild.go index f1f02b12..ac267f7f 100644 --- a/vendor/github.com/mikkeloscar/gopkgbuild/pkgbuild.go +++ b/vendor/github.com/mikkeloscar/gopkgbuild/pkgbuild.go @@ -616,7 +616,7 @@ func isLowerAlpha(c rune) bool { // check if c is a valid pkgname char func isValidPkgnameChar(c rune) bool { - return isLowerAlpha(c) || isDigit(c) || c == '@' || c == '.' || c == '_' || c == '+' || c == '-' + return isAlphaNumeric(c) || c == '@' || c == '.' || c == '_' || c == '+' || c == '-' } // check if c is a valid pkgver char diff --git a/vendor/github.com/mikkeloscar/gopkgbuild/version.go b/vendor/github.com/mikkeloscar/gopkgbuild/version.go index cae63a87..1695377d 100644 --- a/vendor/github.com/mikkeloscar/gopkgbuild/version.go +++ b/vendor/github.com/mikkeloscar/gopkgbuild/version.go @@ -74,33 +74,18 @@ func NewCompleteVersion(s string) (*CompleteVersion, error) { return nil, fmt.Errorf("invalid version format: %s", s) } -// Older returns true if a is older than the argument version string -func (a *CompleteVersion) Older(v string) bool { - b, err := NewCompleteVersion(v) - if err != nil { - return false - } - +// Older returns true if a is older than the argument version +func (a *CompleteVersion) Older(b *CompleteVersion) bool { return a.cmp(b) == -1 } -// Newer returns true if a is newer than the argument version string -func (a *CompleteVersion) Newer(v string) bool { - b, err := NewCompleteVersion(v) - if err != nil { - return false - } - +// Newer returns true if a is newer than the argument version +func (a *CompleteVersion) Newer(b *CompleteVersion) bool { return a.cmp(b) == 1 } -// Equal returns true if a is equal to the argument version string -func (a *CompleteVersion) Equal(v string) bool { - b, err := NewCompleteVersion(v) - if err != nil { - return false - } - +// Equal returns true if a is equal to the argument version +func (a *CompleteVersion) Equal(b *CompleteVersion) bool { return a.cmp(b) == 0 }