Repository: Jguer/yay Branch: next Commit: 90ae08862a52 Files: 214 Total size: 1.7 MB Directory structure: gitextract_b9cisaon/ ├── .dockerignore ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── builder-image.yml │ ├── multiarch-build.yml │ ├── testing-git.yml │ └── testing.yml ├── .gitignore ├── .golangci.yml ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── ci.Dockerfile ├── clean.go ├── clean_test.go ├── cmd.go ├── cmd_test.go ├── completions/ │ ├── bash │ ├── fish │ └── zsh ├── doc/ │ └── yay.8 ├── errors.go ├── get.go ├── go.mod ├── go.sum ├── local_install.go ├── local_install_test.go ├── main.go ├── pkg/ │ ├── cmd/ │ │ └── graph/ │ │ └── main.go │ ├── completion/ │ │ ├── completion.go │ │ └── completion_test.go │ ├── db/ │ │ ├── executor.go │ │ ├── ialpm/ │ │ │ ├── alpm.go │ │ │ ├── alpm_test.go │ │ │ └── high_level.go │ │ ├── mock/ │ │ │ ├── executor.go │ │ │ └── repo.go │ │ └── types.go │ ├── dep/ │ │ ├── dep.go │ │ ├── dep_graph.go │ │ ├── dep_graph_bench_test.go │ │ ├── dep_graph_rpc_test.go │ │ ├── dep_graph_test.go │ │ ├── mock/ │ │ │ └── aur.go │ │ ├── target_handler.go │ │ ├── testdata/ │ │ │ ├── android-sdk.json │ │ │ ├── aws-cli-git.json │ │ │ ├── clion.json │ │ │ ├── gstreamer-git.json │ │ │ ├── jellyfin-server.json │ │ │ ├── jellyfin-web.json │ │ │ ├── jellyfin.json │ │ │ ├── liri-desktop-git.json │ │ │ ├── mesa-git.json │ │ │ ├── nx.json │ │ │ ├── python-pydantic.json │ │ │ └── samsung-unified-driver.json │ │ └── topo/ │ │ ├── dep.go │ │ ├── dep_test.go │ │ └── errors.go │ ├── download/ │ │ ├── abs.go │ │ ├── abs_test.go │ │ ├── aur.go │ │ ├── aur_test.go │ │ ├── errors.go │ │ ├── unified.go │ │ ├── unified_integration_test.go │ │ ├── unified_test.go │ │ └── utils_test.go │ ├── intrange/ │ │ ├── intrange.go │ │ └── intrange_test.go │ ├── menus/ │ │ ├── clean_menu.go │ │ ├── diff_menu.go │ │ ├── edit_menu.go │ │ └── menu.go │ ├── multierror/ │ │ └── multierror.go │ ├── news/ │ │ ├── .snapshots/ │ │ │ ├── TestPrintNewsFeed-all-quiet │ │ │ ├── TestPrintNewsFeed-all-verbose │ │ │ ├── TestPrintNewsFeed-latest-quiet │ │ │ ├── TestPrintNewsFeed-latest-quiet-topdown │ │ │ └── TestPrintNewsFeedSameDay │ │ ├── news.go │ │ └── news_test.go │ ├── query/ │ │ ├── aur_warnings.go │ │ ├── errors.go │ │ ├── filter.go │ │ ├── metric.go │ │ ├── metric_test.go │ │ ├── query_builder.go │ │ ├── query_builder_test.go │ │ ├── source.go │ │ ├── types.go │ │ ├── version_diff.go │ │ └── version_diff_test.go │ ├── runtime/ │ │ ├── pacman.go │ │ ├── pacman_test.go │ │ ├── runtime.go │ │ └── runtime_test.go │ ├── settings/ │ │ ├── args.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── dirs.go │ │ ├── dirs_test.go │ │ ├── errors.go │ │ ├── exe/ │ │ │ ├── cmd_builder.go │ │ │ ├── exec.go │ │ │ └── mock.go │ │ ├── ini.go │ │ ├── ini_test.go │ │ ├── parser/ │ │ │ ├── parser.go │ │ │ ├── parser_test.go │ │ │ ├── rebuild_mode.go │ │ │ └── target_mode.go │ │ └── yay.conf │ ├── sync/ │ │ ├── build/ │ │ │ ├── errors.go │ │ │ ├── installer.go │ │ │ ├── installer_test.go │ │ │ ├── pkg_archive.go │ │ │ └── pkg_archive_test.go │ │ ├── srcinfo/ │ │ │ ├── pgp/ │ │ │ │ ├── keys.go │ │ │ │ ├── keys_test.go │ │ │ │ └── testdata/ │ │ │ │ ├── 11E521D646982372EB577A1F8F0871F202119294 │ │ │ │ ├── 487EACC08557AD082088DABA1EB2638FF56C0C53 │ │ │ │ ├── 647F28654894E3BD457199BE38DBBDC86092693E │ │ │ │ ├── A314827C4E4250A204CE6E13284FC34C8E4B1A25 │ │ │ │ ├── ABAF11C65A2970B130ABE3C479BE3E4300411886 │ │ │ │ ├── B6C8F98282B944E3B0D5C2530FC3042E345AD05D │ │ │ │ └── C52048C0C0748FEE227D47A2702353E0F7E48EDB │ │ │ ├── service.go │ │ │ └── service_test.go │ │ ├── sync.go │ │ └── workdir/ │ │ ├── aur_source.go │ │ ├── aur_source_test.go │ │ ├── clean.go │ │ ├── merge.go │ │ ├── preparer.go │ │ └── preparer_test.go │ ├── text/ │ │ ├── color.go │ │ ├── convert.go │ │ ├── errors.go │ │ ├── input.go │ │ ├── service.go │ │ ├── text.go │ │ ├── text_test.go │ │ └── time.go │ ├── upgrade/ │ │ ├── .snapshots/ │ │ │ ├── Test_upAUR-No_Updates │ │ │ ├── Test_upAUR-Simple_Update │ │ │ └── Test_upAUR-Time_Update │ │ ├── service.go │ │ ├── service_test.go │ │ ├── sources.go │ │ ├── sources_test.go │ │ ├── upgrade.go │ │ └── upgrade_test.go │ └── vcs/ │ ├── .snapshots/ │ │ └── TestInfoStore_Update-simple │ ├── mock.go │ ├── vcs.go │ └── vcs_test.go ├── po/ │ ├── ca.po │ ├── ca_ES.po │ ├── cs.po │ ├── de.po │ ├── en.po │ ├── es.po │ ├── eu.po │ ├── fi.po │ ├── fr.po │ ├── fr_FR.po │ ├── he.po │ ├── he_IL.po │ ├── hu.po │ ├── id.po │ ├── it_IT.po │ ├── ja.po │ ├── ko.po │ ├── nl.po │ ├── pl.po │ ├── pl_PL.po │ ├── pt.po │ ├── pt_BR.po │ ├── ru.po │ ├── ru_RU.po │ ├── sk.po │ ├── sv.po │ ├── tr.po │ ├── uk.po │ ├── vi.po │ ├── vi_VN.po │ ├── zh_CN.po │ └── zh_TW.po ├── print.go ├── print_test.go ├── query.go ├── query_test.go ├── sync.go ├── sync_test.go ├── testdata/ │ ├── cephbin/ │ │ ├── .SRCINFO │ │ └── PKGBUILD │ ├── gourou/ │ │ ├── .SRCINFO │ │ └── PKGBUILD │ ├── jfin/ │ │ ├── .SRCINFO │ │ └── PKGBUILD │ ├── libzip-git/ │ │ ├── .SRCINFO │ │ └── PKGBUILD │ └── pacman.conf ├── vcs.go └── vote.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ * !*.go !pkg !go.mod !go.sum !Makefile !po !doc !completions ================================================ FILE: .github/CODEOWNERS ================================================ * @Jguer ================================================ FILE: .github/FUNDING.yml ================================================ github: [Jguer] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Report a malfunction to help us improve title: "" labels: "Status: Triage, Type: Bug" assignees: "" --- ### Affected Version ### Describe the bug ### Reproduction Steps 1. 2. 3. ### Expected behavior ### Output ```sh ``` ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "" labels: "Type: Feature Request, Status: Discussion Open" assignees: "" --- ### Is your feature request related to a problem? Please describe. ### Describe the solution you'd like ### Describe alternatives you've considered ### Additional context ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" groups: go-all: patterns: - '*' ================================================ FILE: .github/workflows/builder-image.yml ================================================ name: Builder Image on: schedule: - cron: "0 3 * * 1" # Every Monday at 3 AM push: paths: - "ci.Dockerfile" - ".github/workflows/builder-image.yml" env: REGISTRY_IMAGE: jguer/yay-builder jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: platform: - linux/amd64 - linux/arm/v7 - linux/arm64 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 with: images: | ghcr.io/${{ env.REGISTRY_IMAGE }} tags: | type=raw,value=latest type=sha,format=long - name: Build and push by digest id: build uses: docker/build-push-action@v5 with: context: . file: ci.Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=ghcr.io/${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" echo -n "$digest" > "/tmp/digests/$(echo "${{ matrix.platform }}" | tr '/' '_')" - name: Upload digest uses: actions/upload-artifact@v4 with: name: digest-${{ matrix.platform == 'linux/amd64' && 'amd64' || matrix.platform == 'linux/arm/v7' && 'armv7' || 'arm64' }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: needs: [build] runs-on: ubuntu-latest steps: - name: Download digests uses: actions/download-artifact@v4 with: pattern: digest-* merge-multiple: true path: /tmp/digests - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 with: images: | ghcr.io/${{ env.REGISTRY_IMAGE }} tags: | type=raw,value=latest type=sha,format=short - name: Create and push manifest list env: DOCKER_CLI_EXPERIMENTAL: enabled run: | # Extract GitHub Container Registry tags GHCR_TAGS=$(echo '${{ steps.meta.outputs.tags }}' | xargs -I {} echo "-t {}") # Create a manifest list using the image digests from /tmp/digests/* DIGESTS=$(for file in /tmp/digests/*; do echo -n "ghcr.io/${{ env.REGISTRY_IMAGE }}@$(cat $file) " done) # Create the manifest list for GitHub Container Registry docker buildx imagetools create $GHCR_TAGS $DIGESTS - name: Inspect image run: | docker buildx imagetools inspect ghcr.io/${{ env.REGISTRY_IMAGE }}:latest ================================================ FILE: .github/workflows/multiarch-build.yml ================================================ name: Build Release on: push: tags: - v* jobs: build-releases: strategy: matrix: arch: ["linux/amd64 x86_64", "linux/arm/v7 armv7h", "linux/arm64 aarch64"] name: Build ${{ matrix.arch }} runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Read info id: tags run: | echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT arch="${{ matrix.arch }}" echo "PLATFORM=${arch%% *}" >> $GITHUB_OUTPUT echo "ARCH=${arch##* }" >> $GITHUB_OUTPUT - name: Build ${{ matrix.arch }} release run: | mkdir artifacts docker buildx build --platform ${{ steps.tags.outputs.platform }} \ --build-arg VERSION=${{ steps.tags.outputs.version }} \ --build-arg ARCH=${{ steps.tags.outputs.arch }} \ --build-arg PREFIX="/usr" \ -t yay:${{ steps.tags.outputs.arch }} . --load make docker-release ARCH=${{ steps.tags.outputs.arch }} VERSION=${{ steps.tags.outputs.version }} PREFIX="/usr" mv *.tar.gz artifacts - uses: actions/upload-artifact@v4 with: name: yay_${{ steps.tags.outputs.arch }} path: artifacts create_release: name: Create release from this build needs: [build-releases] runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Read info id: tags run: | echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - uses: actions/download-artifact@v4 with: pattern: yay_* merge-multiple: true - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release create ${{ steps.tags.outputs.tag }} \ --title "${{ steps.tags.outputs.tag }}" \ --generate-notes \ ./yay_${{ steps.tags.outputs.version }}_*.tar.gz - name: Release Notary Action uses: docker://aevea/release-notary:latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/testing-git.yml ================================================ name: Test against pacman-git on: pull_request: paths-ignore: - "doc/**" - "**/*.po" - "README.md" - ".gitignore" jobs: build: name: Lint and test yay (-git) runs-on: ubuntu-latest container: image: ghcr.io/jguer/yay-builder:latest steps: - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - uses: actions/cache@v3 with: path: /home/runner/work/yay/yay/pacman-git key: ${{ runner.os }}-pacman-${{ hashFiles('/home/runner/work/yay/yay/pacman-git/PKGBUILD') }} restore-keys: | ${{ runner.os }}-pacman- - name: checkout pacman-git run: | git -C ./pacman-git pull || git clone https://aur.archlinux.org/pacman-git useradd github echo 'github ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers chmod -R 777 pacman-git su github -c 'cd pacman-git; yes | makepkg -si --nocheck' - name: Run Build and Tests with pacman-git run: | make test ================================================ FILE: .github/workflows/testing.yml ================================================ name: Test against pacman on: pull_request: jobs: build: name: Lint and test yay runs-on: ubuntu-latest container: image: ghcr.io/jguer/yay-builder:latest steps: - uses: actions/checkout@v4 - uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Lint env: GOFLAGS: -buildvcs=false -tags=next run: /app/bin/golangci-lint run -v ./... - name: Run Build and Tests run: make test - name: Run Integration Tests continue-on-error: true run: | useradd -m yay && chown -R yay:yay . && cp -r ~/go/ /home/yay/go/ && chown -R yay:yay /home/yay/go/ && su yay -c "make test-integration" - name: Build yay Artifact env: GOFLAGS: -buildvcs=false -tags=next run: make - name: Upload yay Artifact uses: actions/upload-artifact@v4 with: name: yay path: ./yay if-no-files-found: error overwrite: true ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test .vscode *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* *.exe *.test *.prof yay yay_*/ *.tar.gz qemu-* .go # Locale *.mo *.pot *.po~ *.pprof node_modules/ xgotext .devcontainer/ ================================================ FILE: .golangci.yml ================================================ version: "2" run: go: "1.26" linters: default: none enable: - bodyclose - dogsled - dupl - errcheck - errorlint - gochecknoinits - gocritic - goprintffuncname - gosec - govet - ineffassign - lll - misspell - nakedret - noctx - nolintlint - staticcheck - unconvert - unparam - unused - whitespace settings: dupl: threshold: 100 funlen: lines: 100 statements: 50 goconst: min-len: 3 min-occurrences: 4 gocritic: enabled-tags: - diagnostic - experimental - opinionated - performance - style gocyclo: min-complexity: 15 lll: line-length: 140 misspell: locale: US nolintlint: require-explanation: false require-specific: false allow-unused: false exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - dupl - errcheck - errorlint - gochecknoinits - gocritic - godot - gosec - govet - lll - revive - staticcheck - wsl path: (.+)_test.go - path: (.+)\.go$ text: G204 - path: (.+)\.go$ text: G115 - path: (.+)\.go$ text: G703 - path: (.+)\.go$ text: G705 - path: (.+)\.go$ text: QF1012 paths: - third_party$ - builtin$ - examples$ formatters: enable: - gofmt - goimports settings: goimports: local-prefixes: - github.com/Jguer/yay/v12 exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ ================================================ FILE: .pre-commit-config.yaml ================================================ default_stages: [commit] repos: - repo: https://github.com/dnephin/pre-commit-golang rev: v0.5.1 hooks: - id: go-fmt - id: golangci-lint - id: go-unit-tests - id: go-build - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 # Use the sha or tag you want to point at hooks: - id: prettier - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 # Use the ref you want to point at hooks: - id: trailing-whitespace - id: check-json - id: check-yaml - id: check-added-large-files - repo: https://github.com/commitizen-tools/commitizen rev: v3.15.0 hooks: - id: commitizen stages: [commit-msg] ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to yay ## Translation [Transifex](https://www.transifex.com/yay-1/yay/) ## Quality Assurance ```sh pacman -S --needed git base-devel git clone https://aur.archlinux.org/yay-git.git cd yay-git makepkg -si ``` Installing `yay-git` and using issues to help determine what's broken is already a very big help. ## Development 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. ### Vision Yay is based on the design of [yaourt](https://github.com/archlinuxfr/yaourt), [apacman](https://github.com/oshazard/apacman) and [pacaur](https://github.com/rmarquis/pacaur). It is developed with these objectives in mind: - Provide an interface for pacman - Yaourt-style interactive search/install - Minimal dependencies - Minimize user input ### Dependencies Yay depends on: - go (make only) - git - base-devel - pacman Note: Yay also depends on a few other projects, these are pulled as go modules. ### Building Run `make` to build Yay. This command will generate a binary called `yay` in the same directory as the Makefile. #### Docker Release `make docker-release` will build the release packages for `aarch64` and for `x86_64`. For `aarch64` to run on a `x86_64` platform `qemu-user-static(-bin)` must be installed. ``` docker run --rm --privileged multiarch/qemu-user-static:register --reset ``` will register QEMU in the build agent. ARM builds tend to crash sometimes but repeated runs tend to succeed. ### Code Style All code should be formatted through `go fmt`. This tool will automatically format code for you. We recommend, however, that you write code in the proper style and use `go fmt` only to catch mistakes. Use [pre-commit](https://pre-commit.com/) to validate your commits against the various linters configured for this repository. ### Testing Run `make test` to test Yay. This command will verify that the code is formatted correctly, run the code through `go vet`, and run unit tests. ================================================ FILE: Dockerfile ================================================ FROM ghcr.io/jguer/yay-builder:latest LABEL maintainer="Jguer,docker@jguer.space" ARG VERSION ARG PREFIX ARG ARCH WORKDIR /app COPY . . RUN pacman -Syu --overwrite=* --noconfirm RUN make release VERSION=${VERSION} PREFIX=${PREFIX} ARCH=${ARCH} ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ export GO111MODULE=on GOPROXY ?= https://proxy.golang.org,direct export GOPROXY BUILD_TAG = devel ARCH ?= $(shell uname -m) BIN := yay DESTDIR := GO ?= go PKGNAME := yay PREFIX := /usr/local MAJORVERSION := 13 MINORVERSION := 0 PATCHVERSION := 0 VERSION ?= ${MAJORVERSION}.${MINORVERSION}.${PATCHVERSION} LOCALEDIR := po SYSTEMLOCALEPATH := $(PREFIX)/share/locale/ # ls -1 po | sed -e 's/\.po$//' | paste -sd " " LANGS := ca cs de en es eu fr_FR he hu id it_IT ja ko nl pl_PL pt_BR pt ru_RU ru sk sv tr uk vi vi_VN zh_CN zh_TW POTFILE := default.pot POFILES := $(addprefix $(LOCALEDIR)/,$(addsuffix .po,$(LANGS))) MOFILES := $(POFILES:.po=.mo) FLAGS ?= -trimpath -mod=readonly -modcacherw EXTRA_FLAGS ?= -buildmode=pie LDFLAGS := -X "main.yayVersion=${VERSION}" -X "main.localePath=${SYSTEMLOCALEPATH}" -linkmode=external -compressdwarf=false RELEASE_DIR := ${PKGNAME}_${VERSION}_${ARCH} PACKAGE := $(RELEASE_DIR).tar.gz SOURCES ?= $(shell find . -name "*.go" -type f) .PRECIOUS: ${LOCALEDIR}/%.po .PHONY: default default: build .PHONY: all all: | clean release .PHONY: clean clean: $(GO) clean $(FLAGS) -i ./... rm -rf $(BIN) $(PKGNAME)_* .PHONY: test_lint test_lint: test lint .PHONY: test test: $(GO) test -race -covermode=atomic $(FLAGS) ./... .PHONY: test-integration test-integration: $(GO) test -tags=integration $(FLAGS) ./... .PHONY: build build: $(BIN) .PHONY: release release: $(PACKAGE) .PHONY: docker-release-all docker-release-all: make docker-release-armv7h ARCH=armv7h make docker-release-x86_64 ARCH=x86_64 make docker-release-aarch64 ARCH=aarch64 docker-release: docker create --name yay-$(ARCH) yay:${ARCH} /bin/sh docker cp yay-$(ARCH):/app/${PACKAGE} $(PACKAGE) docker container rm yay-$(ARCH) .PHONY: docker-build docker-build: docker build -t yay-$(ARCH):${VERSION} . docker run -e="ARCH=$(ARCH)" --name yay-$(ARCH) yay-$(ARCH):${VERSION} make build VERSION=${VERSION} PREFIX=${PREFIX} docker cp yay-$(ARCH):/app/${BIN} $(BIN) docker container rm yay-$(ARCH) .PHONY: lint lint: GOFLAGS="$(FLAGS)" golangci-lint run ./... .PHONY: fmt fmt: go fmt ./... .PHONY: install install: build ${MOFILES} install -Dm755 ${BIN} $(DESTDIR)$(PREFIX)/bin/${BIN} install -Dm644 doc/${PKGNAME}.8 $(DESTDIR)$(PREFIX)/share/man/man8/${PKGNAME}.8 install -Dm644 completions/bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/${PKGNAME} install -Dm644 completions/zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_${PKGNAME} install -Dm644 completions/fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/${PKGNAME}.fish for lang in ${LANGS}; do \ install -Dm644 ${LOCALEDIR}/$${lang}.mo $(DESTDIR)$(PREFIX)/share/locale/$$lang/LC_MESSAGES/${PKGNAME}.mo; \ done .PHONY: uninstall uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/${BIN} rm -f $(DESTDIR)$(PREFIX)/share/man/man8/${PKGNAME}.8 rm -f $(DESTDIR)$(PREFIX)/share/bash-completion/completions/${PKGNAME} rm -f $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_${PKGNAME} rm -f $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/${PKGNAME}.fish for lang in ${LANGS}; do \ rm -f $(DESTDIR)$(PREFIX)/share/locale/$$lang/LC_MESSAGES/${PKGNAME}.mo; \ done $(BIN): $(SOURCES) $(GO) build $(FLAGS) -ldflags '$(LDFLAGS)' $(EXTRA_FLAGS) -o $@ $(RELEASE_DIR): mkdir $(RELEASE_DIR) $(PACKAGE): $(BIN) $(RELEASE_DIR) ${MOFILES} strip ${BIN} cp -t $(RELEASE_DIR) ${BIN} doc/${PKGNAME}.8 completions/* ${MOFILES} tar -czvf $(PACKAGE) $(RELEASE_DIR) locale: xgotext -in . -out po mv po/default.pot po/en.po for lang in ${LANGS}; do \ test -f po/$$lang.po || msginit --no-translator -l po/$$lang.po -i po/${POTFILE} -o po/$$lang.po; \ msgmerge -U po/$$lang.po po/${POTFILE}; \ touch po/$$lang.po; \ done ${LOCALEDIR}/%.mo: ${LOCALEDIR}/%.po msgfmt -o $@ $< ================================================ FILE: README.md ================================================ [![yay](https://img.shields.io/aur/version/yay?color=1793d1&label=yay&logo=arch-linux&style=for-the-badge)](https://aur.archlinux.org/packages/yay/) [![yay-bin](https://img.shields.io/aur/version/yay-bin?color=1793d1&label=yay-bin&logo=arch-linux&style=for-the-badge)](https://aur.archlinux.org/packages/yay-bin/) [![yay-git](https://img.shields.io/aur/version/yay-git?color=1793d1&label=yay-git&logo=arch-linux&style=for-the-badge)](https://aur.archlinux.org/packages/yay-git/) ![AUR votes](https://img.shields.io/aur/votes/yay?color=333333&style=for-the-badge) [![GitHub license](https://img.shields.io/github/license/jguer/yay?color=333333&style=for-the-badge)](https://github.com/Jguer/yay/blob/master/LICENSE) # yay Yet Another Yogurt - An AUR Helper Written in Go ### Help translate yay: [Transifex](https://app.transifex.com/yay-1/yay/) ## Features - Advanced dependency solving - PKGBUILD downloading from ABS or AUR - Completions for AUR packages - Query user up-front for all input (prior to starting builds) - Narrow search (`yay linux header` will first search `linux` and then narrow on `header`) - Find matching package providers during search and allow selection - Remove make dependencies at the end of the build process - Build local PKGBUILDs with AUR dependencies - Un/Vote for packages [![asciicast](https://asciinema.org/a/399431.svg)](https://asciinema.org/a/399431) [![asciicast](https://asciinema.org/a/399433.svg)](https://asciinema.org/a/399433) ## Installation If you are migrating from another AUR helper, you can simply install yay with that helper. > [!WARNING] > We are using `sudo` in these examples. You can switch that out for a different privilege escalation tool. ### Source The initial installation of yay can be done by cloning the PKGBUILD and building with makepkg: We make sure we have the `base-devel` package group installed. ```sh sudo pacman -S --needed git base-devel git clone https://aur.archlinux.org/yay.git cd yay makepkg -si ``` If you want to do all of this at once, we can chain the commands like so: ```sh sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si ``` ### Binary If you do not want to compile yay yourself you can use the builds generated by GitHub Actions. ```sh sudo pacman -S --needed git base-devel git clone https://aur.archlinux.org/yay-bin.git cd yay-bin makepkg -si ``` If you want to do all of this at once, we can chain the commands like so: ```sh sudo pacman -S --needed git base-devel && git clone https://aur.archlinux.org/yay-bin.git && cd yay-bin && makepkg -si ``` ### Other distributions If you're using Manjaro or [another distribution that packages `yay`](https://repology.org/project/yay/versions) you can simply install yay using pacman (as root): ```sh pacman -S --needed git base-devel yay ``` > [!WARNING] > distributions sometimes lag updating yay on their repositories. ## First Use #### Development packages upgrade - Use `yay -Y --gendb` to generate a development package database for `*-git` packages that were installed without yay. This command should only be run once. - `yay -Syu --devel` will then check for development package updates - Use `yay -Y --devel --save` to make development package updates permanently enabled (`yay` and `yay -Syu` will then always check dev packages) ## Examples of Custom Operations | Command | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `yay` | Alias to `yay -Syu`. | | `yay ` | Present package-installation selection menu. | | `yay -Bi ` | Install dependencies and build a local PKGBUILD. | | `yay -G ` | Download PKGBUILD from ABS or AUR. (yay v12.0+) | | `yay -Gp ` | Print to stdout PKGBUILD from ABS or AUR. | | `yay -Ps` | Print system statistics. | | `yay -Syu --devel` | Perform system upgrade, but also check for development package updates. | | `yay -Wu ` | Unvote for package (Requires setting `AUR_USERNAME` and `AUR_PASSWORD` environment variables) (yay v11.3+) | | `yay -Wv ` | Vote for package (Requires setting `AUR_USERNAME` and `AUR_PASSWORD` environment variables). (yay v11.3+) | | `yay -Y --combinedupgrade --save` | Make combined upgrade the default mode. | | `yay -Y --gendb` | Generate development package database used for devel update. | | `yay -Yc` | Clean unneeded dependencies. | ## 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` (see issue [#123](https://github.com/Jguer/yay/issues/123)). - **Sometimes diffs are printed to the terminal, and other times they are paged via less. How do I fix this?** yay uses `git diff` to display diffs, which by default tells less not to page if the output can fit into one terminal length. This behavior can be overridden by exporting your own flags (`export LESS=SRX`). - **yay is not asking me to edit PKGBUILDS, and I don't like the diff menu! What can I do?** `yay --editmenu --diffmenu=false --save` - **How can I tell yay to act only on AUR packages, or only on repo packages?** `yay -{OPERATION} --aur` `yay -{OPERATION} --repo` - **A `Flagged Out Of Date AUR Packages` message is displayed. Why doesn't yay update them?** This message does not mean that updated AUR packages are available. It means the packages have been flagged out of date on the AUR, but their maintainers have not yet updated the `PKGBUILD`s (see [outdated AUR packages](https://wiki.archlinux.org/index.php/Arch_User_Repository#Foo_in_the_AUR_is_outdated.3B_what_should_I_do.3F)). - **yay doesn't install dependencies added to a PKGBUILD during installation.** yay resolves all dependencies ahead of time. You are free to edit the PKGBUILD in any way, but any problems you cause are your own and should not be reported unless they can be reproduced with the original PKGBUILD. - **I know my `-git` package has updates but yay doesn't offer to update it** yay uses a hash cache for development packages. Normally it is updated at the end of the package install with the message `Found git repo`. If you transition between aur helpers and did not install the devel package using yay at some point, it is possible it never got added to the cache. `yay -Y --gendb` will fix the current version of every devel package and start checking from there. - **I want to help out!** Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more information. ## Support All support related to yay should be requested via GitHub issues. Since yay is not officially supported by Arch Linux, support should not be sought out on the forums, AUR comments or other official channels. A broken AUR package should be reported as a comment on the package's AUR page. A package may only be considered broken if it fails to build with makepkg. Reports should be made using makepkg and include the full output as well as any other relevant information. Never make reports using yay or any other external tools. ## Images

### Other AUR helpers/tools - [paru](https://github.com/morganamilo/paru) - [aurutils](https://github.com/AladW/aurutils) - [pikaur](https://github.com/actionless/pikaur) ================================================ FILE: SECURITY.md ================================================ # Security Policy Thank you for helping keep yay secure! ## Supported Versions We only provide security updates and support for the latest released version of yay. Please ensure you are using the most up-to-date version before reporting vulnerabilities. ## Reporting a Vulnerability If you discover a security vulnerability, please email us at [security@jguer.space](mailto:security@jguer.space). We will respond as quickly as possible and coordinate a fix. We appreciate responsible disclosure and your help in making this project safe for everyone. ================================================ FILE: ci.Dockerfile ================================================ FROM quay.io/gmanka/archlinuxarm:base-devel LABEL maintainer="Jguer,docker@jguer.space" ENV GO111MODULE=on WORKDIR /app COPY go.mod . # asciidoc, doxygen, meson needed for pacman-git RUN set -eux; \ pacman-key --init; \ sed -i 's/^#DisableSandboxFilesystem/DisableSandboxFilesystem/' /etc/pacman.conf; \ sed -i 's/^#DisableSandboxSyscalls/DisableSandboxSyscalls/' /etc/pacman.conf; \ pacman -Syu --noconfirm --needed archlinux-keyring pacman go git gcc make base-devel sudo asciidoc doxygen meson; \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v2.10.1; \ go mod download; \ rm -rf /var/lib/pacman/sync/* /var/cache/pacman/* /tmp/* /var/tmp/*; \ rm -rf /usr/share/man/* /usr/share/doc/* || true; \ yes | pacman -Scc >/dev/null 2>&1 || true ================================================ FILE: clean.go ================================================ package main import ( "context" "os" "path/filepath" "github.com/Jguer/aur" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" ) // CleanDependencies removes all dangling dependencies in system. func cleanDependencies(ctx context.Context, cfg *settings.Configuration, cmdBuilder exe.ICmdBuilder, cmdArgs *parser.Arguments, dbExecutor db.Executor, removeOptional bool, ) error { hanging := hangingPackages(removeOptional, dbExecutor) if len(hanging) != 0 { return cleanRemove(ctx, cfg, cmdBuilder, cmdArgs, hanging) } return nil } // CleanRemove sends a full removal command to pacman with the pkgName slice. func cleanRemove(ctx context.Context, cfg *settings.Configuration, cmdBuilder exe.ICmdBuilder, cmdArgs *parser.Arguments, pkgNames []string, ) error { if len(pkgNames) == 0 { return nil } arguments := cmdArgs.CopyGlobal() if err := arguments.AddArg("R", "s", "u"); err != nil { return err } arguments.AddTarget(pkgNames...) return cmdBuilder.Show( cmdBuilder.BuildPacmanCmd(ctx, arguments, cfg.Mode, settings.NoConfirm)) } func syncClean(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error { keepInstalled := false keepCurrent := false _, removeAll, _ := cmdArgs.GetArg("c", "clean") for _, v := range run.PacmanConf.CleanMethod { switch v { case "KeepInstalled": keepInstalled = true case "KeepCurrent": keepCurrent = true } } if run.Cfg.Mode.AtLeastRepo() { if err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)); err != nil { return err } } if !run.Cfg.Mode.AtLeastAUR() { return nil } var question string if removeAll { question = gotext.Get("Do you want to remove ALL AUR packages from cache?") } else { question = gotext.Get("Do you want to remove all other AUR packages from cache?") } run.Logger.Println(gotext.Get("\nBuild directory:"), run.Cfg.BuildDir) if run.Logger.ContinueTask(question, true, settings.NoConfirm) { if err := cleanAUR(ctx, run, keepInstalled, keepCurrent, removeAll, dbExecutor); err != nil { return err } } if removeAll { return nil } if run.Logger.ContinueTask(gotext.Get("Do you want to remove ALL untracked AUR files?"), true, settings.NoConfirm) { return cleanUntracked(ctx, run) } return nil } func cleanAUR(ctx context.Context, run *runtime.Runtime, keepInstalled, keepCurrent, removeAll bool, dbExecutor db.Executor, ) error { run.Logger.Println(gotext.Get("removing AUR packages from cache...")) installedBases := mapset.NewThreadUnsafeSet[string]() inAURBases := mapset.NewThreadUnsafeSet[string]() remotePackages := dbExecutor.InstalledRemotePackages() files, err := os.ReadDir(run.Cfg.BuildDir) if err != nil { return err } cachedPackages := make([]string, 0, len(files)) for _, file := range files { if !file.IsDir() { continue } cachedPackages = append(cachedPackages, file.Name()) } // 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 don't do it if we // don't need to. if keepCurrent { info, errInfo := run.AURClient.Get(ctx, &aur.Query{ Needles: cachedPackages, }) if errInfo != nil { return errInfo } for i := range info { inAURBases.Add(info[i].PackageBase) } } for _, pkg := range remotePackages { if pkg.Base() != "" { installedBases.Add(pkg.Base()) } else { installedBases.Add(pkg.Name()) } } for _, file := range files { if !file.IsDir() { continue } if !removeAll { if keepInstalled && installedBases.Contains(file.Name()) { continue } if keepCurrent && inAURBases.Contains(file.Name()) { continue } } dir := filepath.Join(run.Cfg.BuildDir, file.Name()) run.Logger.Debugln("removing", dir) if err = os.RemoveAll(dir); err != nil { run.Logger.Warnln(gotext.Get("Unable to remove %s: %s", dir, err)) } } return nil } func cleanUntracked(ctx context.Context, run *runtime.Runtime) error { run.Logger.Println(gotext.Get("removing untracked AUR files from cache...")) files, err := os.ReadDir(run.Cfg.BuildDir) if err != nil { return err } for _, file := range files { if !file.IsDir() { continue } dir := filepath.Join(run.Cfg.BuildDir, file.Name()) run.Logger.Debugln("cleaning", dir) if isGitRepository(dir) { if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fx")); err != nil { run.Logger.Warnln(gotext.Get("Unable to clean:"), dir) return err } } } return nil } func isGitRepository(dir string) bool { _, err := os.Stat(filepath.Join(dir, ".git")) return !os.IsNotExist(err) } ================================================ FILE: clean_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" alpm "github.com/Jguer/dyalpm" pacmanconf "github.com/Morganamilo/go-pacmanconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" ) func TestCleanHanging(t *testing.T) { pacmanBin := t.TempDir() + "/pacman" t.Parallel() testCases := []struct { name string args []string wantShow []string }{ { name: "clean", args: []string{"Y", "c"}, wantShow: []string{"pacman", "-R", "-s", "-u", "--config", "/etc/pacman.conf", "--", "lsp-plugins"}, }, { name: "clean double", args: []string{"Y", "c", "c"}, wantShow: []string{"pacman", "-R", "-s", "-u", "--config", "/etc/pacman.conf", "--", "lsp-plugins", "linux-headers"}, }, } dbExc := &mock.DBExecutor{ PackageOptionalDependsFn: func(i alpm.Package) []alpm.Depend { if i.Name() == "linux" { return []alpm.Depend{ { Name: "linux-headers", }, } } return []alpm.Depend{} }, PackageProvidesFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, PackageDependsFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, LocalPackagesFn: func() []mock.IPackage { return []mock.IPackage{ &mock.Package{ PReason: alpm.PkgReasonExplicit, PName: "linux", }, &mock.Package{ PReason: alpm.PkgReasonDepend, PName: "lsp-plugins", }, &mock.Package{ PReason: alpm.PkgReasonDepend, PName: "linux-headers", }, } }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mockRunner := &exe.MockRunner{ CaptureFn: func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil }, ShowFn: func(cmd *exec.Cmd) error { return nil }, } cmdBuilder := &exe.CmdBuilder{ SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } run := &runtime.Runtime{CmdBuilder: cmdBuilder, Cfg: &settings.Configuration{}} cmdArgs := parser.MakeArguments() cmdArgs.AddArg(tc.args...) err := handleCmd(context.Background(), run, cmdArgs, dbExc, ) require.NoError(t, err) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } }) } } func TestIntegrationCleanAUR(t *testing.T) { buildDir := filepath.Join(t.TempDir(), "build") yayGitDir := filepath.Join(buildDir, "yay-git") zoomDir := filepath.Join(buildDir, "zoom") t.Parallel() testCases := []struct { name string args []string wantDirs []string }{ { name: "Sync clean AUR", args: []string{"S", "c", "a"}, wantDirs: []string{"zoom"}, }, { name: "Sync clean double AUR", args: []string{"S", "c", "c", "a"}, wantDirs: []string{}, }, } dbExc := &mock.DBExecutor{ PackageOptionalDependsFn: func(i alpm.Package) []alpm.Depend { if i.Name() == "linux" { return []alpm.Depend{ { Name: "linux-headers", }, } } return []alpm.Depend{} }, PackageProvidesFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, PackageDependsFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{ "zoom": &mock.Package{ PName: "zoom", PVersion: "6.5.8-1", PBase: "zoom", PReason: alpm.PkgReasonExplicit, }, } }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mockRunner := &exe.MockRunner{} cfg := &settings.Configuration{ BuildDir: buildDir, Mode: parser.ModeAUR, } pacmanConf := &pacmanconf.Config{ // Only testing the keep installed clean method right now CleanMethod: []string{"KeepInstalled"}, } run := &runtime.Runtime{ Cfg: cfg, PacmanConf: pacmanConf, Logger: newTestLogger(), } cmdArgs := parser.MakeArguments() cmdArgs.AddArg(tc.args...) // Create the package directories to be cleaned err := os.MkdirAll(yayGitDir, 0o755) require.NoError(t, err) err = os.MkdirAll(zoomDir, 0o755) require.NoError(t, err) err = handleCmd(context.Background(), run, cmdArgs, dbExc, ) require.NoError(t, err) // This should only test AUR cleaning, so no calls to an external command should be made assert.Len(t, mockRunner.ShowCalls, 0) // Make sure the directories left after cleaning are the only ones we expect packageDirs, err := os.ReadDir(buildDir) require.NoError(t, err) var packageDirNames []string for _, dir := range packageDirs { packageDirNames = append(packageDirNames, dir.Name()) } assert.ElementsMatch(t, tc.wantDirs, packageDirNames) }) } } ================================================ FILE: cmd.go ================================================ package main import ( "context" "errors" "fmt" "net/http" "strings" alpm "github.com/Jguer/dyalpm" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/completion" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/download" "github.com/Jguer/yay/v12/pkg/intrange" "github.com/Jguer/yay/v12/pkg/news" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/upgrade" "github.com/Jguer/yay/v12/pkg/vcs" ) func usage(logger *text.Logger) { logger.Println(`Usage: yay yay [...] yay operations: yay {-h --help} yay {-V --version} yay {-D --database} yay {-F --files} [options] [package(s)] yay {-Q --query} [options] [package(s)] yay {-R --remove} [options] yay {-S --sync} [options] [package(s)] yay {-T --deptest} [options] [package(s)] yay {-U --upgrade} [options] New operations: yay {-B --build} [options] [dir] yay {-G --getpkgbuild} [options] [package(s)] yay {-P --show} [options] yay {-W --web} [options] [package(s)] yay {-Y --yay} [options] [package(s)] If no operation is specified 'yay -Syu' will be performed If no operation is specified and targets are provided, -Y will be assumed New options: -N --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 --aururl Set an alternative AUR URL --aurrpcurl Set an alternative URL for the AUR /rpc endpoint --builddir Directory used to download and run PKGBUILDS --editor Editor to use when editing PKGBUILDs --editorflags Pass arguments to editor --makepkg makepkg command to use --mflags Pass arguments to makepkg --pacman pacman command to use --git git command to use --gitflags Pass arguments to git --gpg gpg command to use --gpgflags Pass arguments to gpg --config pacman.conf file to use --makepkgconf makepkg.conf file to use --nomakepkgconf Use the default makepkg.conf --requestsplitn Max amount of packages to query per AUR request --completioninterval Time in days to refresh completion cache --sortby Sort AUR results by a specific field during search --searchby Search for packages using a specified field --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 --askremovemake Ask to remove makedepends after install --askyesremovemake Ask to remove makedepends after install("Y" as default) --removemake Remove makedepends after install --noremovemake Don't remove makedepends after install --cleanafter Remove package sources after successful install --keepsrc Keep pkg/ and src/ after building packages --bottomup Shows AUR's packages first and then repository's --topdown Shows repository's packages first and then AUR's --singlelineresults List each search result on its own line --doublelineresults List each search result on two lines, like pacman --devel Check development packages during sysupgrade --rebuild Always build target packages --rebuildall Always build all AUR packages --norebuild Skip package build if in cache and up to date --rebuildtree Always build all AUR packages even if installed --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 providers when searching for packages --pgpfetch Prompt to import PGP keys from PKGBUILDs --useask Automatically resolve conflicts using pacman's ask flag --sudo sudo command to use --sudoflags Pass arguments to sudo --sudoloop Loop sudo calls in the background to avoid timeout show specific options (used with -P): -c --complete Used for completions -d --defaultconfig Print default yay configuration -g --currentconfig Print current yay configuration -s --stats Display system package statistics -w --news Print arch news yay specific options (used with -Y): -c --clean Remove unneeded dependencies (-cc to ignore optdepends) --gendb Generates development package DB used for updating getpkgbuild specific options (used with -G): -f --force Force download for existing ABS packages -p --print Print pkgbuild of packages`) } func handleCmd(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor, ) error { if cmdArgs.ExistsArg("h", "help") { return handleHelp(ctx, run, cmdArgs) } if run.Cfg.SudoLoop && cmdArgs.NeedRoot(run.Cfg.Mode) { run.CmdBuilder.SudoLoop() } switch cmdArgs.Op { case "V", "version": handleVersion(run.Logger) return nil case "D", "database": return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) case "F", "files": return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) case "Q", "query": return handleQuery(ctx, run, cmdArgs, dbExecutor) case "R", "remove": return handleRemove(ctx, run, cmdArgs, run.VCSStore) case "S", "sync": return handleSync(ctx, run, cmdArgs, dbExecutor) case "T", "deptest": return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) case "U", "upgrade": return handleUpgrade(ctx, run, cmdArgs) case "B", "build": return handleBuild(ctx, run, dbExecutor, cmdArgs) case "G", "getpkgbuild": return handleGetpkgbuild(ctx, run, cmdArgs, dbExecutor) case "P", "show": return handlePrint(ctx, run, cmdArgs, dbExecutor) case "Y", "yay": return handleYay(ctx, run, cmdArgs, run.CmdBuilder, dbExecutor, run.QueryBuilder) case "W", "web": return handleWeb(ctx, run, cmdArgs) } return errors.New(gotext.Get("unhandled operation")) } // getFilter returns filter function which can keep packages which were only // explicitly installed or ones installed as dependencies for showing available // updates or their count. func getFilter(cmdArgs *parser.Arguments) (upgrade.Filter, error) { deps, explicit := cmdArgs.ExistsArg("d", "deps"), cmdArgs.ExistsArg("e", "explicit") switch { case deps && explicit: return nil, errors.New(gotext.Get("invalid option: '--deps' and '--explicit' may not be used together")) case deps: return func(pkg *upgrade.Upgrade) bool { return pkg.Reason == alpm.PkgReasonDepend }, nil case explicit: return func(pkg *upgrade.Upgrade) bool { return pkg.Reason == alpm.PkgReasonExplicit }, nil } return func(pkg *upgrade.Upgrade) bool { return true }, nil } func handleQuery(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error { if cmdArgs.ExistsArg("u", "upgrades") { filter, err := getFilter(cmdArgs) if err != nil { return err } return printUpdateList(ctx, run, cmdArgs, dbExecutor, cmdArgs.ExistsDouble("u", "sysupgrade"), filter) } if err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)); err != nil { if str := err.Error(); strings.Contains(str, "exit status") { // yay -Qdt should not output anything in case of error return fmt.Errorf("") } return err } return nil } func handleHelp(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error { usage(run.Logger) switch cmdArgs.Op { case "Y", "yay", "G", "getpkgbuild", "P", "show", "W", "web", "B", "build": return nil } run.Logger.Println("\npacman operation specific options:") return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) } func handleVersion(logger *text.Logger) { logger.Printf("yay v%s - libalpm v%s\n", yayVersion, alpm.Version()) } func handlePrint(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error { switch { case cmdArgs.ExistsArg("d", "defaultconfig"): tmpConfig := settings.DefaultConfig(yayVersion) run.Logger.Printf("%v", tmpConfig) return nil case cmdArgs.ExistsArg("g", "currentconfig"): run.Logger.Printf("%v", run.Cfg) return nil case cmdArgs.ExistsArg("w", "news"): double := cmdArgs.ExistsDouble("w", "news") quiet := cmdArgs.ExistsArg("q", "quiet") return news.PrintNewsFeed(ctx, run.HTTPClient, run.Logger, dbExecutor.LastBuildTime(), run.Cfg.BottomUp, double, quiet) case cmdArgs.ExistsArg("c", "complete"): return completion.Show(ctx, run.HTTPClient, dbExecutor, run.Cfg.AURURL, run.Cfg.CompletionPath, run.Cfg.CompletionInterval, cmdArgs.ExistsDouble("c", "complete"), run.Logger) case cmdArgs.ExistsArg("s", "stats"): return localStatistics(ctx, run, dbExecutor) } return nil } func handleYay(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, cmdBuilder exe.ICmdBuilder, dbExecutor db.Executor, queryBuilder query.Builder, ) error { switch { case cmdArgs.ExistsArg("gendb"): return createDevelDB(ctx, run, dbExecutor) case cmdArgs.ExistsDouble("c"): return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, true) case cmdArgs.ExistsArg("c", "clean"): return cleanDependencies(ctx, run.Cfg, cmdBuilder, cmdArgs, dbExecutor, false) case len(cmdArgs.Targets) > 0: return displayNumberMenu(ctx, run, cmdArgs.Targets, dbExecutor, queryBuilder, cmdArgs) } return nil } func handleWeb(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments) error { switch { case cmdArgs.ExistsArg("v", "vote"): return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger, run.VoteClient, true) case cmdArgs.ExistsArg("u", "unvote"): return handlePackageVote(ctx, cmdArgs.Targets, run.AURClient, run.Logger, run.VoteClient, false) } return nil } func handleGetpkgbuild(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor download.DBSearcher) error { if cmdArgs.ExistsArg("p", "print") { return printPkgbuilds(dbExecutor, run.AURClient, run.HTTPClient, run.Logger, cmdArgs.Targets, run.Cfg.Mode, run.Cfg.AURURL) } return getPkgbuilds(ctx, dbExecutor, run.AURClient, run, cmdArgs.Targets, cmdArgs.ExistsArg("f", "force")) } func handleUpgrade(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, ) error { return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) } // -B* options func handleBuild(ctx context.Context, run *runtime.Runtime, dbExecutor db.Executor, cmdArgs *parser.Arguments, ) error { return installLocalPKGBUILD(ctx, run, cmdArgs, dbExecutor) } func handleSync(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor) error { targets := cmdArgs.Targets switch { case cmdArgs.ExistsArg("s", "search"): return syncSearch(ctx, targets, dbExecutor, run.QueryBuilder, !cmdArgs.ExistsArg("q", "quiet")) case cmdArgs.ExistsArg("p", "print", "print-format"): return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) case cmdArgs.ExistsArg("c", "clean"): return syncClean(ctx, run, cmdArgs, dbExecutor) case cmdArgs.ExistsArg("l", "list"): return syncList(ctx, run, run.HTTPClient, cmdArgs, dbExecutor) case cmdArgs.ExistsArg("g", "groups"): return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) case cmdArgs.ExistsArg("i", "info"): return syncInfo(ctx, run, cmdArgs, targets, dbExecutor) case cmdArgs.ExistsArg("u", "sysupgrade") || len(cmdArgs.Targets) > 0: return syncInstall(ctx, run, cmdArgs, dbExecutor) case cmdArgs.ExistsArg("y", "refresh"): return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) } return nil } func handleRemove(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, localCache vcs.Store) error { err := run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) if err == nil { localCache.RemovePackages(cmdArgs.Targets) } return err } // NumberMenu presents a CLI for selecting packages to install. func displayNumberMenu(ctx context.Context, run *runtime.Runtime, pkgS []string, dbExecutor db.Executor, queryBuilder query.Builder, cmdArgs *parser.Arguments, ) error { queryBuilder.Execute(ctx, dbExecutor, pkgS) if err := queryBuilder.Results(dbExecutor, query.NumberMenu); err != nil { return err } if queryBuilder.Len() == 0 { // no results were found return nil } run.Logger.Infoln(gotext.Get("Packages to install (eg: 1 2 3, 1-3 or ^4)")) numberBuf, err := run.Logger.GetInput("", false) if err != nil { return err } include, exclude, _, otherExclude := intrange.ParseNumberMenu(numberBuf) targets, err := queryBuilder.GetTargets(include, exclude, otherExclude) if err != nil { return err } // modify the arguments to pass for the install cmdArgs.Targets = targets if len(cmdArgs.Targets) == 0 { run.Logger.Println(gotext.Get(" there is nothing to do")) return nil } return syncInstall(ctx, run, cmdArgs, dbExecutor) } func syncList(ctx context.Context, run *runtime.Runtime, httpClient *http.Client, cmdArgs *parser.Arguments, dbExecutor db.Executor, ) error { aur := false for i := len(cmdArgs.Targets) - 1; i >= 0; i-- { if cmdArgs.Targets[i] == "aur" && run.Cfg.Mode.AtLeastAUR() { cmdArgs.Targets = append(cmdArgs.Targets[:i], cmdArgs.Targets[i+1:]...) aur = true } } if run.Cfg.Mode.AtLeastAUR() && (len(cmdArgs.Targets) == 0 || aur) { scanner, err := download.GetPackageScanner(ctx, httpClient, run.Cfg.AURURL, run.Logger) if err != nil { return err } defer scanner.Close() for scanner.Scan() { name := scanner.Text() if cmdArgs.ExistsArg("q", "quiet") { run.Logger.Println(name) } else { run.Logger.Printf("%s %s %s", text.Magenta("aur"), text.Bold(name), text.Bold(text.Green(gotext.Get("unknown-version")))) if dbExecutor.LocalPackage(name) != nil { run.Logger.Print(text.Bold(text.Blue(gotext.Get(" [Installed]")))) } run.Logger.Println() } } } if run.Cfg.Mode.AtLeastRepo() && (len(cmdArgs.Targets) != 0 || !aur) { return run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, cmdArgs, run.Cfg.Mode, settings.NoConfirm)) } return nil } ================================================ FILE: cmd_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "fmt" "io" "os" "os/exec" "strings" "testing" "github.com/Jguer/aur" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func TestYogurtMenuAURDB(t *testing.T) { t.Skip("skip until Operation service is an interface") t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("Y") cmdArgs.AddTarget("yay") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"aur"} }, SyncPackagesFn: func(s ...string) []mock.IPackage { return []mock.IPackage{ &mock.Package{ PName: "yay", PBase: "yay", PVersion: "10.0.0", PDB: mock.NewDB("aur"), }, } }, LocalPackageFn: func(s string) mock.IPackage { return nil }, } aurCache := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "yay", PackageBase: "yay", Version: "10.0.0", }, }, nil }, } logger := text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test") run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: logger, CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, QueryBuilder: query.NewSourceQueryBuilder(aurCache, logger, "votes", parser.ModeAny, "name", true, false, true), AURClient: aurCache, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) wantCapture := []string{} wantShow := []string{ "pacman -S -y --config /etc/pacman.conf --", "pacman -S -y -u --config /etc/pacman.conf --", } require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } ================================================ FILE: completions/bash ================================================ # This file is in the public domain. _arch_compgen() { local i r COMPREPLY=($(compgen -W '$*' -- "$cur")) for ((i = 1; i < ${#COMP_WORDS[@]} - 1; i++)); do for r in ${!COMPREPLY[@]}; do if [[ ${COMP_WORDS[i]} == ${COMPREPLY[r]} ]]; then unset 'COMPREPLY[r]' break fi done done } _arch_ptr2comp() { local list= x y for x; do for y in '0 --' '1 -'; do eval 'set -- ${'$x'[${y% *}]}' list+=\ ${@/#/${y#* }} done done _arch_compgen $list } _arch_incomp() { local r="[[:space:]]-(-${1#* }[[:space:]]|[[:alnum:]_]*${1% *})" [[ $COMP_LINE =~ $r ]] } _pacman_pkg() { _arch_compgen "$( if [[ $2 ]]; then \pacman -$1 2>/dev/null | \cut -d' ' -f1 | \sort -u else \pacman -$1 2>/dev/null fi )" } _yay_pkg() { [ -z "$cur" ] && _pacman_pkg Slq && return _arch_compgen "$(yay -Pc)" } _pacman_repo_list() { _arch_compgen "$(pacman-conf --repo-list)" } _yay() { compopt -o default local common core cur database files prev query remove sync upgrade o local yays show getpkgbuild web local cur prev words cword _init_completion || return database=('asdeps asexplicit') files=('list machinereadable refresh regex' 'l x y') query=('changelog check deps explicit file foreign groups info list native owns search unrequired upgrades' 'c e g i k l m n o p s t u') remove=('cascade dbonly nodeps assume-installed nosave print recursive unneeded' 'c n p s u') sync=('asdeps asexplicit clean dbonly downloadonly overwrite groups ignore ignoregroup info list needed nodeps assume-installed print refresh recursive search sysupgrade aur repo' 'c g i l p s u w y a N') upgrade=('asdeps asexplicit overwrite needed nodeps assume-installed print recursive' 'p') core=('database files help query remove sync upgrade version' 'D F Q R S U V h') ##yay stuff common=('arch cachedir color config confirm dbpath debug gpgdir help hookdir logfile noconfirm noprogressbar noscriptlet quiet root verbose makepkg pacman git gpg gpgflags config requestsplitn sudoloop redownload noredownload redownloadall rebuild rebuildall rebuildtree norebuild sortby singlelineresults doublelineresults answerclean answerdiff answeredit answerupgrade noanswerclean noanswerdiff noansweredit noanswerupgrade cleanmenu diffmenu editmenu cleanafter keepsrc provides pgpfetch useask combinedupgrade aur repo makepkgconf nomakepkgconf askremovemake askyesremovemake removemake noremovemake completioninterval aururl aurrpcurl searchby batchinstall' 'b d h q r v') yays=('clean gendb' 'c') show=('complete defaultconfig currentconfig stats news' 'c d g s w') getpkgbuild=('force print' 'f p') web=('vote unvote' 'v u') for o in 'D database' 'F files' 'Q query' 'R remove' 'S sync' 'U upgrade' 'Y yays' 'P show' 'G getpkgbuild' 'W web'; do _arch_incomp "$o" && break done if [[ $? != 0 ]]; then _arch_ptr2comp core elif [[ ! $prev =~ ^-[[:alnum:]_]*[Vbhr] && ! $prev == --@(cachedir|color|config|dbpath|help|hookdir|gpgdir|logfile|root|version) ]]; then [[ $cur == -* ]] && _arch_ptr2comp ${o#* } common || case ${o% *} in D | R) _pacman_pkg Qq ;; F) { _arch_incomp 'l list' && _pacman_pkg Slq; } || _arch_incomp 'o owns' || compopt +o default ;; Q) { _arch_incomp 'g groups' && _pacman_pkg Qg sort; } || { _arch_incomp 'p file' && _pacman_file; } || { _arch_incomp 's search' && compopt +o default; } || { _arch_incomp 'u upgrades' && compopt +o default; } || _arch_incomp 'o owns' || _pacman_pkg Qq ;; S) { _arch_incomp 'g groups' && _pacman_pkg Sg; } || { _arch_incomp 'l list' && _pacman_repo_list; } || { _arch_incomp 's search' && compopt +o default; } || _yay_pkg ;; U) _pacman_file ;; G) _yay_pkg ;; W) _yay_pkg ;; esac fi true } _pacman_file() { compopt -o filenames _filedir 'pkg.*' } complete -F _yay yay # ex:et ts=2 sw=2 ft=sh ================================================ FILE: completions/fish ================================================ # vim:fdm=marker foldlevel=0 tabstop=2 shiftwidth=2 filetype=fish # Original Author for pacman: Giorgio Lando # Updated for yay by jguer set -l progname yay # Yay constants set -l listall "(yay -Pc)" set -l listpacman "(__fish_print_packages)" set -l yayspecific '__fish_contains_opt -s Y yay' set -l webspecific '__fish_contains_opt -s W web' set -l show '__fish_contains_opt -s P show' set -l getpkgbuild '__fish_contains_opt -s G getpkgbuild' # Pacman constants set -l listinstalled "(pacman -Q | string replace ' ' \t)" set -l listrepos "(__fish_print_pacman_repos)" set -l listgroups "(pacman -Sg)\t'Package Group'" set -l noopt 'not __fish_contains_opt -s S -s D -s Q -s R -s U -s T -s F -s Y -s W -s P -s G database query sync remove upgrade deptest files show getpkgbuild web yay' set -l database '__fish_contains_opt -s D database' set -l query '__fish_contains_opt -s Q query' set -l remove '__fish_contains_opt -s R remove' set -l sync '__fish_contains_opt -s S sync' set -l upgrade '__fish_contains_opt -s U upgrade' set -l files '__fish_contains_opt -s F files' complete -c $progname -e complete -c $progname -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" # 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 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 dependencies' complete -c $progname -s U -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 V -f -l version -d 'Display version and exit' complete -c $progname -s h -f -l help -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 b -l dbpath -d 'Alternate database location' -xa "(__fish_complete_directories)" complete -c $progname -n "not $noopt" -s r -l root -d 'Alternate installation root' -xa "(__fish_complete_directories)" complete -c $progname -n "not $noopt" -s v -l verbose -d 'Output more status messages' -f complete -c $progname -n "not $noopt" -l arch -d 'Alternate architecture' -f complete -c $progname -n "not $noopt" -l cachedir -d 'Alternate package cache location' -xa "(__fish_complete_directories)" complete -c $progname -n "not $noopt" -l color -d 'Colorize the output' -fa '{auto,always,never}' complete -c $progname -n "not $noopt" -l config -d 'Alternate config file' -rF complete -c $progname -n "not $noopt" -l confirm -d 'Always ask for confirmation' -f complete -c $progname -n "not $noopt" -l debug -d 'Display debug messages' -f complete -c $progname -n "not $noopt" -l disable-download-timeout -d 'Use relaxed timeouts for download' -f complete -c $progname -n "not $noopt" -l gpgdir -d 'Alternate home directory for GnuPG' -xa "(__fish_complete_directories)" complete -c $progname -n "not $noopt" -l hookdir -d 'Alternate hook location' -xa "(__fish_complete_directories)" complete -c $progname -n "not $noopt" -l logfile -d 'Alternate log file' complete -c $progname -n "not $noopt" -l noconfirm -d 'Bypass any confirmation' -f complete -c $progname -n "not $noopt" -l sysroot -d 'Operate on a mounted guest system (root-only)' -xa "(__fish_complete_directories)" # File, query, sync options (files, query, sync) for condition in files query sync complete -c $progname -n "$$condition" -s q -l quiet -d 'Show less information' -f end # Transaction options (sync, remove, upgrade) for condition in sync remove upgrade complete -c $progname -n "$$condition" -s d -l nodeps -d 'Skip [all] dependency checks' -f complete -c $progname -n "$$condition" -s p -l print -d 'Dry run, only print targets' -f complete -c $progname -n "$$condition" -l assume-installed -d 'Add a virtual package to satisfy dependencies' -f complete -c $progname -n "$$condition" -l dbonly -d 'Modify database entry only' -f complete -c $progname -n "$$condition" -l noprogressbar -d 'Do not display progress bar' -f complete -c $progname -n "$$condition" -l noscriptlet -d 'Do not execute install script' -f complete -c $progname -n "$$condition" -l print-format -d 'Specify printf-like format' -x end # File and query options (files, query) for condition in files query complete -c $progname -n "$$condition" -s l -l list -d 'List the files owned by PACKAGE' -f end # File and sync options (files, sync) for condition in files sync complete -c $progname -n "$$condition" -s y -l refresh -d 'Download fresh package databases [force]' -f end # Query and sync options (query, sync) for condition in query sync complete -c $progname -n "$$condition" -s g -l groups -d 'Display members of [all] package GROUP' -xa "$listgroups" end # Sync and upgrade options (sync, upgrade) for condition in sync upgrade complete -c $progname -n "$$condition" -l asdeps -d 'Install packages as non-explicitly installed' -f complete -c $progname -n "$$condition" -l asexplicit -d 'Install packages as explicitly installed' -f complete -c $progname -n "$$condition" -l ignore -d 'Ignore a package upgrade (can be used more than once)' -xa "$listall" complete -c $progname -n "$$condition" -l ignoregroup -d 'Ignore a group upgrade (can be used more than once)' -xa "$listgroups" complete -c $progname -n "$$condition" -l needed -d 'Do not reinstall up to date packages' -f complete -c $progname -n "$$condition" -l overwrite -d 'Overwrite conflicting files (can be used more than once)' -rF end # Database options set -l has_db_opt '__fish_contains_opt asdeps asexplicit check -s k' complete -c $progname -n "$database; and not $has_db_opt" -s k -l check -d 'Check database validity' complete -c $progname -n "$database" -s q -l quite -d 'Suppress output of success messages' -f complete -c $progname -n "$database; and not $has_db_opt" -l asdeps -d 'Mark PACKAGE as dependency' -x complete -c $progname -n "$database; and not $has_db_opt" -l asexplicit -d 'Mark PACKAGE as explicitly installed' -x complete -c $progname -n "$has_db_opt; and $database" -xa "$listinstalled" # File options - since pacman 5 complete -c $progname -n "$files" -s x -l regex -d 'Interpret each query as a regular expression' -f complete -c $progname -n "$files" -l machinereadable -d 'Print each match in a machine readable output format' -f complete -c $progname -n "$files" -d Package -xa "$listpacman" # Query options complete -c $progname -n "$query" -s c -l changelog -d 'View the change log of PACKAGE' -f complete -c $progname -n "$query" -s d -l deps -d 'List only non-explicit packages (dependencies)' -f complete -c $progname -n "$query" -s e -l explicit -d 'List only explicitly installed packages' -f complete -c $progname -n "$query" -s i -l info -d 'View PACKAGE [backup files] information' -f complete -c $progname -n "$query" -s k -l check -d 'Check that PACKAGE files exist' -f complete -c $progname -n "$query" -s m -l foreign -d 'List installed packages not found in sync database' -f complete -c $progname -n "$query" -s n -l native -d 'list installed packages only found in sync database' -f complete -c $progname -n "$query" -s o -l owns -d 'Query the package that owns FILE' -rF complete -c $progname -n "$query" -s p -l file -d 'Query a package file instead of the database' -rF complete -c $progname -n "$query" -s s -l search -d 'Search locally-installed packages for regexp' -f complete -c $progname -n "$query" -s t -l unrequired -d 'List only unrequired packages [and optdepends]' -f complete -c $progname -n "$query" -s u -l upgrades -d 'List only out-of-date packages' -f complete -c $progname -n "$query" -d 'Installed package' -xa "$listinstalled" # Remove options complete -c $progname -n "$remove" -s c -l cascade -d 'Also remove packages depending on PACKAGE' -f complete -c $progname -n "$remove" -s n -l nosave -d 'Ignore file backup designations' -f complete -c $progname -n "$remove" -s s -l recursive -d 'Also remove dependencies of PACKAGE' -f complete -c $progname -n "$remove" -s u -l unneeded -d 'Only remove targets not required by PACKAGE' -f complete -c $progname -n "$remove" -d 'Installed package' -xa "$listinstalled" # Sync options complete -c $progname -n "$sync" -s c -l clean -d 'Remove [all] packages from cache' -f complete -c $progname -n "$sync" -s i -l info -d 'View PACKAGE [extended] information' -f complete -c $progname -n "$sync" -s l -l list -d 'List all packages in REPOSITORY' -xa "$listrepos" complete -c $progname -n "$sync" -s s -l search -d 'Search remote repositories for regexp' -f complete -c $progname -n "$sync" -s u -l sysupgrade -d 'Upgrade all packages that are out of date' complete -c $progname -n "$sync" -s w -l downloadonly -d 'Only download the target packages' complete -c $progname -n "$sync" -xa "$listall $listgroups" # Upgrade options # Theoretically, pacman reads packages in all formats that libarchive supports # In practice, it's going to be tar.xz, tar.gz, tar.zst, or just pkg.tar (uncompressed pkg) complete -c $progname -n "$upgrade" -xa '(__fish_complete_suffix pkg.tar.zst; __fish_complete_suffix pkg.tar.xz; __fish_complete_suffix pkg.tar.gz; __fish_complete_suffix pkg.tar;)' -d 'Package file' # Yay operations complete -c $progname -s Y -f -l yay -n "$noopt" -d 'Yay specific operations' complete -c $progname -s P -f -l show -n "$noopt" -d 'Print information' complete -c $progname -s G -f -l getpkgbuild -n "$noopt" -d 'Get PKGBUILD from ABS or AUR' complete -c $progname -s W -f -l web -n "$noopt" -d 'Web operations' # Web options complete -c $progname -n "$webspecific" -s v -l vote -d 'Vote for AUR packages' -f complete -c $progname -n "$webspecific" -s u -l unvote -d 'Unvote for AUR packages' -f complete -c $progname -n "$webspecific" -xa "$listall" # New options complete -c $progname -n "not $noopt" -s a -l aur -d 'Assume targets are from the AUR' -f complete -c $progname -n "not $noopt" -s N -l repo -d 'Assume targets are from the repositories' -f # Yay options complete -c $progname -n "$yayspecific" -s c -l clean -d 'Remove unneeded dependencies' -f complete -c $progname -n "$yayspecific" -l gendb -d 'Generate development package DB' -f # Show options complete -c $progname -n "$show" -s c -l complete -d 'Print a list of all AUR and repo packages' -f #complete -c $progname -n "$show" -s f -l fish -d 'During complete adjust the output for the fish shell' -f complete -c $progname -n "$show" -s d -l defaultconfig -d 'Print default yay configuration' -f complete -c $progname -n "$show" -s g -l currentconfig -d 'Print current yay configuration' -f complete -c $progname -n "$show" -s s -l stats -d 'Display system package statistics' -f complete -c $progname -n "$show" -s w -l news -d 'Print arch news' -f complete -c $progname -n "$show" -s q -l quiet -d 'Do not print news description' -f # Getpkgbuild options complete -c $progname -n "$getpkgbuild" -s f -l force -d 'Force download for existing ABS packages' -f complete -c $progname -n "$getpkgbuild" -xa "$listall" complete -c $progname -n "$getpkgbuild" -s p -l print -d 'Print pkgbuild of packages' -f # Permanent configuration settings complete -c $progname -n "not $noopt" -l save -d 'Save current arguments to yay permanent configuration' -f complete -c $progname -n "not $noopt" -l aururl -d 'Set an alternative AUR URL' -f complete -c $progname -n "not $noopt" -l aurrpcurl -d 'Set an alternative URL for the AUR /rpc endpoint' -f complete -c $progname -n "not $noopt" -l builddir -d 'Directory to use for Building AUR Packages' -r 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 complete -c $progname -n "not $noopt" -l git -d 'Git command to use' -f complete -c $progname -n "not $noopt" -l gpg -d 'Gpg command to use' -f complete -c $progname -n "not $noopt" -l config -d 'The pacman config file to use' -r complete -c $progname -n "not $noopt" -l makepkgconf -d 'Use custom makepkg.conf location' -r complete -c $progname -n "not $noopt" -l nomakepkgconf -d 'Use default makepkg.conf' -f complete -c $progname -n "not $noopt" -l requestsplitn -d 'Max amount of packages to query per AUR request' -f complete -c $progname -n "not $noopt" -l completioninterval -d 'Refresh interval for completion cache' -f complete -c $progname -n "not $noopt" -l sortby -d 'Sort AUR results by a specific field during search' -xa "{votes,popularity,name,base,submitted,modified}" complete -c $progname -n "not $noopt" -l searchby -d 'Search for AUR packages by querying the specified field' -xa "{name,name-desc,maintainer,depends,checkdepends,makedepends,optdepends}" complete -c $progname -n "not $noopt" -l answerclean -d 'Set a predetermined answer for the clean build menu' -xa "{All,None,Installed,NotInstalled}" complete -c $progname -n "not $noopt" -l answerdiff -d 'Set a predetermined answer for the edit diff menu' -xa "{All,None,Installed,NotInstalled}" complete -c $progname -n "not $noopt" -l answeredit -d 'Set a predetermined answer for the edit pkgbuild menu' -xa "{All,None,Installed,NotInstalled}" complete -c $progname -n "not $noopt" -l answerupgrade -d 'Set a predetermined answer for the upgrade menu' -f complete -c $progname -n "not $noopt" -l noanswerclean -d 'Unset the answer for the clean build menu' -f complete -c $progname -n "not $noopt" -l noanswerdiff -d 'Unset the answer for the diff menu' -f 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 askremovemake -d 'Ask to remove make deps after install' -f complete -c $progname -n "not $noopt" -l askyesremovemake -d 'Ask to remove make deps after install(with "Y" as default)' -f complete -c $progname -n "not $noopt" -l removemake -d 'Remove make deps after install' -f complete -c $progname -n "not $noopt" -l noremovemake -d 'Do not remove make deps after install' -f complete -c $progname -n "not $noopt" -l topdown -d 'Shows repository packages first and then aur' -f complete -c $progname -n "not $noopt" -l bottomup -d 'Shows aur packages first and then repository' -f complete -c $progname -n "not $noopt" -l singlelineresults -d 'List each search result on its own line' -f complete -c $progname -n "not $noopt" -l doublelineresults -d 'List each search result on two lines, like pacman' -f complete -c $progname -n "not $noopt" -l devel -d 'Check -git/-svn/-hg development version' -f complete -c $progname -n "not $noopt" -l cleanafter -d 'Clean package sources after successful build' -f complete -c $progname -n "not $noopt" -l keepsrc -d 'Keep pkg/ and src/ after building packages' -f complete -c $progname -n "not $noopt" -l redownload -d 'Redownload PKGBUILD of package even if up-to-date' -f complete -c $progname -n "not $noopt" -l redownloadall -d 'Redownload PKGBUILD of package and deps even if up-to-date' -f complete -c $progname -n "not $noopt" -l noredownload -d 'Do not redownload up-to-date PKGBUILDs' -f complete -c $progname -n "not $noopt" -l provides -d 'Look for matching providers when searching for packages' -f complete -c $progname -n "not $noopt" -l pgpfetch -d 'Prompt to import PGP keys from PKGBUILDs' -f complete -c $progname -n "not $noopt" -l useask -d 'Automatically resolve conflicts using pacmans ask flag' -f complete -c $progname -n "not $noopt" -l combinedupgrade -d 'Refresh then perform the repo and AUR upgrade together' -f complete -c $progname -n "not $noopt" -l batchinstall -d 'Build multiple AUR packages then install them together' -f complete -c $progname -n "not $noopt" -l rebuild -d 'Always build target packages' -f complete -c $progname -n "not $noopt" -l rebuildall -d 'Always build all AUR packages' -f complete -c $progname -n "not $noopt" -l rebuildtree -d 'Always build all AUR packages even if installed' -f complete -c $progname -n "not $noopt" -l norebuild -d 'Skip package build if in cache and up to date' -f complete -c $progname -n "not $noopt" -l mflags -d 'Pass the following options to makepkg' -f complete -c $progname -n "not $noopt" -l gpgflags -d 'Pass the following options to gpg' -f complete -c $progname -n "not $noopt" -l sudoloop -d 'Loop sudo calls in the background to avoid timeout' -f ================================================ FILE: completions/zsh ================================================ #compdef yay # vim:noexpandtab tabstop=2 shiftwidth=2 filetype=zsh typeset -A opt_args setopt extendedglob # options for passing to _arguments: main pacman commands _pacman_opts_commands=( {-D,--database}'[Modify database]' {-F,--files}'[Query the files database]' {-G,--getpkgbuild}'[Get PKGBUILD from ABS or AUR]' {-Q,--query}'[Query the package database]' {-R,--remove}'[Remove a package from the system]' {-P,--show}'[Print yay information]' {-S,--sync}'[Synchronize packages]' {-T,--deptest}'[Check if dependencies are installed]' {-U,--upgrade}'[Upgrade a package]' {-Y,--yay}'[Yay specific options]' {-W,--web}'[web options]' {-V,--version}'[Display version and exit]' '(-h --help)'{-h,--help}'[Display usage]' ) # options for passing to _arguments: options common to all commands _pacman_opts_common=( '(-N --repo)'{-N,--repo}'[Assume targets are from the repositories]' '(-a --aur)'{-a,--aur}'[Assume targets are from the AUR]' '--aururl[Set an alternative AUR URL]:url' '--aurrpcurl[Set an alternative URL for the AUR /rpc endpoint]:url' '--arch[Set an alternate architecture]' '(-b --dbpath)'{-b,--dbpath}'[Alternate database location]:database_location:_files -/' '--color[colorize the output]:color options:(always never auto)' '(- *)'{-h,--help}'[Display syntax for the given operation]' '(-r --root)'{-r,--root}'[Set alternate installation root]:installation root:_files -/' '(-v --verbose)'{-v,--verbose}'[Be more verbose]' '--cachedir[Alternate package cache location]:cache_location:_files -/' '--config[An alternate configuration file]:config file:_files' '(--nomakepkgconf)--makepkgconf[makepkg.conf file to use]:config file:_files' '(--makepkgconf)--nomakepkgconf[Use the default makepkg.conf]' '--requestsplitn[Max amount of packages to query per AUR request]:number' '--completioninterval[Time in days to refresh completion cache]:number' '--confirm[Always ask for confirmation]' '--debug[Display debug messages]' '--gpgdir[Set an alternate directory for GnuPG (instead of /etc/pacman.d/gnupg)]: :_files -/' '--hookdir[Set an alternate hook location]: :_files -/' '--logfile[An alternate log file]:config file:_files' '--noconfirm[Do not ask for confirmation]' '--noprogressbar[Do not show a progress bar when downloading files]' '--noscriptlet[Do not execute the install scriptlet if one exists]' '--save[Causes config options to be saved back to the config file]' '--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' '--git[git command to use]:git:_files' '--gpg[gpg command to use]:gpg:_files' '--sortby[Sort AUR results by a specific field during search]:sortby options:(votes popularity name base submitted modified)' '--searchby[Search for packages using a specified field]:query' '(--noanswerclean)--answerclean[Set a predetermined answer for the clean build menu]:answer' '(--noanswerdiff)--answerdiff[Set a predetermined answer for the diff menu]:answer' '(--noansweredit)--answeredit[Set a predetermined answer for the edit pkgbuild menu]:answer' '(--noanswerupgrade)--answerupgrade[Set a predetermined answer for the upgrade menu]:answer' '(--answerclean)--noanswerclean[Unset the answer for the clean build menu]' '(--answerdiff)--noanswerdiff[Unset the answer for the diff menu]' '(--answeredit)--noansweredit[Unset the answer for the edit pkgbuild menu]' '(--answerupgrade)--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]' '(--askyesremovemake --removemake --noremovemake)--askremovemake[Ask to remove makedepends after install]' '(--askremovemake --removemake --noremovemake)--askyesremovemake[Ask to remove makedepends after install(with "Y" as default)]' '(--askremovemake --askyesremovemake --noremovemake)--removemake[Remove makedepends after install]' "(--askremovemake --askyesremovemake --removemake)--noremovemake[Don't remove makedepends after install]" '(--bottomup)--topdown[Show repository packages first]' '(--topdown)--bottomup[Show AUR packages first]' '(--doublelineresults)--singlelineresults[List each search result on its own line]' '(--singlelineresults)--doublelineresults[List each search result on two lines, like pacman]' '--devel[Check -git/-svn/-hg development version]' '--cleanafter[Clean package sources after successful build]' '--keepsrc[Keep pkg/ and src/ after building packages]' '--separatesources[Separate query results by source, AUR and sync]' '(--redownloadall --noredownload)--redownload[Always download pkgbuilds of targets]' '(--redownload --noredownload)--redownloadall[Always download pkgbuilds of all AUR packages]' '(--redownload --redownloadall)--noredownload[Skip pkgbuild download if in cache and up to date]' '--provides[Look for matching providers when searching for packages]' '--pgpfetch[Prompt to import PGP keys from PKGBUILDs]' "--useask[Automatically resolve conflicts using pacman's ask flag]" '--combinedupgrade[Refresh then perform the repo and AUR upgrade together]' '--batchinstall[Build multiple AUR packages then install them together]' '(--rebuildall --rebuildtree --norebuild)--rebuild[Always build target packages]' '(--rebuild --rebuildtree --norebuild)--rebuildall[Always build all AUR packages]' '(--rebuild --rebuildall --norebuild)--rebuildtree[Always build all AUR packages even if installed]' '(--rebuild --rebuildall --rebuildtree)--norebuild[Skip package build if in cache and up to date]' '--mflags[Pass arguments to makepkg]:mflags' '--gpgflags[Pass arguments to gpg]:gpgflags' '--sudo[The command to use for sudo calls]:command' '--sudoflags[Passes arguments to sudo]:flag' '--sudoloop[Loop sudo calls in the background to avoid timeout]' ) # options for passing to _arguments: options for --upgrade commands _pacman_opts_pkgfile=( '*-d[Skip dependency checks]' '*--nodeps[Skip dependency checks]' '*--assume-installed[Add virtual package to satisfy dependencies]' '--dbonly[Only remove database entry, do not remove files]' '--overwrite[Overwrite conflicting files]:file:_files -g "*"' '--needed[Do not reinstall up to date packages]' '(--asexplicit)--asdeps[mark packages as non-explicitly installed]' '(--asdeps)--asexplicit[mark packages as explicitly installed]' '(-p --print)'{-p,--print}'[Only print the targets instead of performing the operation]' '*--ignore[Ignore a package upgrade]:package: _pacman_completions_all_packages' '*--ignoregroup[Ignore a group upgrade]:package group:_pacman_completions_all_groups' '--print-format[Specify how the targets should be printed]' '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"' ) # options for passing to _arguments: subactions for --query command _pacman_opts_query_actions=( '(-Q --query)'{-Q,--query} {-g,--groups}'[View all members of a package group]:*:package groups:->query_group' {-o,--owns}'[Query the package that owns a file]:file:_files' {-p,--file}'[Package file to query]:*:package file:->query_file' {-s,--search}'[Search package names and descriptions]:*:search text:->query_search' ) # options for passing to _arguments: options for --query and subcommands _pacman_opts_query_modifiers=( '(-c --changelog)'{-c,--changelog}'[List package changelog]' '(-d --deps)'{-d,--deps}'[List packages installed as dependencies]' '(-e --explicit)'{-e,--explicit}'[List packages explicitly installed]' {\*-i,\*--info}'[View package information]' {\*-k,\*--check}'[Check package files]' '(-l --list)'{-l,--list}'[List package contents]' '(-m --foreign)'{-m,--foreign}'[List installed packages not found in sync db(s)]' '(-n --native)'{-n,--native}'[List installed packages found in sync db(s)]' '(-q --quiet)'{-q,--quiet}'[Show less information for query and search]' '(-t --unrequired)'{-t,--unrequired}'[List packages not required by any package]' '(-u --upgrades)'{-u,--upgrades}'[List packages that can be upgraded]' ) # -Y _pacman_opts_yay_modifiers=( '(-c --clean)'{-c,--clean}'[Remove unneeded dependencies]' '--gendb[Generates development package DB used for updating]' ) # -G _pacman_opts_getpkgbuild_modifiers=( '(-f --force)'{-f,--force}'[Force download for existing ABS packages]' '(-p --print)'{-p,--print}'[Print PKGBUILDs]:package:_pacman_completions_all_packages' ) # -W _pacman_opts_web_modifiers=( '(-u --unvote)'{-u,--unvote}'[Unvote AUR package]:package:_pacman_completions_all_packages' '(-v --vote)'{-v,--vote}'[Vote AUR package]:package:_pacman_completions_all_packages' ) # -P _pacman_opts_print_modifiers=( '(-c --complete)'{-c,--complete}'[Used for completions]' '(-d --defaultconfig)'{-d,--defaultconfig}'[Print default yay configuration]' '(-g --config)'{-g,--config}'[Print current yay configuration]' '(-n --numberupgrades)'{-n,--numberupgrades}'[Print number of updates]' '(-s --stats)'{-s,--stats}'[Display system package statistics]' '(-u --upgrades)'{-u,--upgrades}'[Print update list]' '(-w --news)'{-w,--news}'[Print arch news]' ) # options for passing to _arguments: options for --remove command _pacman_opts_remove=( '(-c --cascade)'{-c,--cascade}'[Remove all dependent packages]' '(-d --nodeps)'{-d,--nodeps}'[Skip dependency checks]' '*--assume-installed[Add virtual package to satisfy dependencies]' '(-n --nosave)'{-n,--nosave}'[Remove protected configuration files]' '(-p --print)'{-p,--print}'[Only print the targets instead of performing the operation]' {\*-s,\*--recursive}'[Remove dependencies not required by other packages]' '(-u --unneeded)'{-u,--unneeded}'[Remove unneeded packages]' '--dbonly[Only remove database entry, do not remove files]' '--print-format[Specify how the targets should be printed]' '*:installed package:_pacman_completions_installed_packages' ) _pacman_opts_database=( '(--asexplicit)--asdeps[mark packages as non-explicitly installed]' '(--asdeps)--asexplicit[mark packages as explicitly installed]' '*:installed package:_pacman_completions_installed_packages' ) _pacman_opts_files=( '(-l --list)'{-l,--list}'[List the files owned by the queried package]:package:_pacman_completions_all_packages' '(-x --regex)'{-x,--regex}'[Enable searching using regular expressions]:regex:' '(-y --refresh)'{-y,--refresh}'[Download fresh files databases from the server]' '--machinereadable[Produce machine-readable output]' '(-q --quiet)'{-q,--quiet}'[Show less information for query and search]' ) # options for passing to _arguments: options for --sync command _pacman_opts_sync_actions=( '(-S --sync)'{-S,--sync} {\*-c,\*--clean}'[Remove old packages from cache]:\*:clean:->sync_clean' {-g,--groups}'[View all members of a package group]:*:package groups:->sync_group' {-s,--search}'[Search package names and descriptions]:*:search text:->sync_search' '--dbonly[Only remove database entry, do not remove files]' '--needed[Do not reinstall up to date packages]' '--recursive[Reinstall all dependencies of target packages]' ) # options for passing to _arguments: options for --sync command _pacman_opts_sync_modifiers=( {\*-d,\*--nodeps}'[Skip dependency checks]' '*--assume-installed[Add virtual package to satisfy dependencies]' {\*-i,\*--info}'[View package information]' '(-l --list)'{-l,--list}'[List all packages in a repository]' '(-p --print)'{-p,--print}'[Print download URIs for each package to be installed]' '(-q --quiet)'{-q,--quiet}'[Show less information for query and search]' {\*-u,\*--sysupgrade}'[Upgrade all out-of-date packages]' '(-w --downloadonly)'{-w,--downloadonly}'[Download packages only]' {\*-y,\*--refresh}'[Download fresh package databases]' '*--ignore[Ignore a package upgrade]:package: _pacman_completions_all_packages' '*--ignoregroup[Ignore a group upgrade]:package group:_pacman_completions_all_groups' '(--asexplicit)--asdeps[Install packages as non-explicitly installed]' '(--asdeps)--asexplicit[Install packages as explicitly installed]' '--overwrite[Overwrite conflicting files]:files:_files' '--print-format[Specify how the targets should be printed]' ) # handles --help subcommand _pacman_action_help() { _arguments -s : \ "$_pacman_opts_commands[@]" } # handles cases where no subcommand has yet been given _pacman_action_none() { _arguments -s : \ "$_pacman_opts_commands[@]" } # handles --query subcommand _pacman_action_query() { local context state line typeset -A opt_args case $state in query_file) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"' ;; query_group) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:groups:_pacman_completions_installed_groups' ;; query_owner) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:file:_files' ;; query_search) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:search text: ' ;; *) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_actions[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:package:_pacman_completions_installed_packages' ;; esac } # handles --remove subcommand _pacman_action_remove() { _arguments -s : \ '(--remove -R)'{-R,--remove} \ "$_pacman_opts_common[@]" \ "$_pacman_opts_remove[@]" } # handles --database subcommand _pacman_action_database() { _arguments -s : \ '(--database -D)'{-D,--database} \ "$_pacman_opts_common[@]" \ "$_pacman_opts_database[@]" } # handles --files subcommand _pacman_action_files() { _arguments -s : \ '(--files -F)'{-F,--files} \ "$_pacman_opts_common[@]" \ "$_pacman_opts_files[@]" } _pacman_action_deptest () { _arguments -s : \ '(--deptest)-T' \ "$_pacman_opts_common[@]" \ ":packages:_pacman_all_packages" } # handles --sync subcommand _pacman_action_sync() { local context state line typeset -A opt_args if (( $+words[(r)--clean] )); then state=sync_clean elif (( $+words[(r)--groups] )); then state=sync_group elif (( $+words[(r)--search] )); then state=sync_search fi case $state in sync_clean) _arguments -s : \ {\*-c,\*--clean}'[Remove old packages from cache]' \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" ;; sync_group) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '(-g --group)'{-g,--groups} \ '*:package group:_pacman_completions_all_groups' ;; sync_search) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '*:search text: ' ;; *) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_actions[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '*:package:_pacman_completions_all_packages' ;; esac } # handles --upgrade subcommand _pacman_action_upgrade() { _arguments -s : \ '(-U --upgrade)'{-U,--upgrade} \ "$_pacman_opts_common[@]" \ "$_pacman_opts_pkgfile[@]" } # handles --version subcommand _pacman_action_version() { # no further arguments return 0 } # provides completions for package groups _pacman_completions_all_groups() { local -a cmd groups _pacman_get_command groups=( $(_call_program groups $cmd[@] -Sg) ) typeset -U groups if [[ ${words[CURRENT-1]} == '--ignoregroup' ]]; then _sequence compadd -S ',' "$@" -a groups else compadd "$@" -a groups fi } # provides completions for packages available from repositories # these can be specified as either 'package' or 'repository/package' _pacman_completions_all_packages() { local -a seq sep cmd packages repositories packages_long if [[ ${words[CURRENT-1]} == '--ignore' ]]; then seq='_sequence' sep=(-S ',') else seq= sep=() fi if compset -P1 '*/*'; then packages=( $(_call_program packages yay -Pc ${words[CURRENT]%/*}) ) typeset -U packages ${seq} _wanted repo_packages expl "repository/package" compadd ${sep[@]} ${(@)packages} else packages=( $(_call_program packages yay -Pc ) ) typeset -U packages ${seq} _wanted packages expl "packages" compadd ${sep[@]} - "${(@)packages}" repositories=($(pacman-conf --repo-list)) typeset -U repositories _wanted repo_packages expl "repository/package" compadd -S "/" $repositories fi } # provides completions for package groups _pacman_completions_installed_groups() { local -a cmd groups _pacman_get_command groups=(${(o)${(f)"$(_call_program groups $cmd[@] -Qg)"}% *}) typeset -U groups compadd "$@" -a groups } # provides completions for installed packages _pacman_completions_installed_packages() { local -a cmd packages packages_long packages_long=(/var/lib/pacman/local/*(/)) packages=( ${${packages_long#/var/lib/pacman/local/}%-*-*} ) compadd "$@" -a packages } _pacman_all_packages() { _alternative : \ 'localpkgs:local packages:_pacman_completions_installed_packages' \ 'repopkgs:repository packages:_pacman_completions_all_packages' } # provides completions for repository names _pacman_completions_repositories() { local -a cmd repositories repositories=($(pacman-conf --repo-list)) # Uniq the array typeset -U repositories compadd "$@" -a repositories } # builds command for invoking pacman in a _call_program command - extracts # relevant options already specified (config file, etc) # $cmd must be declared by calling function _pacman_get_command() { # this is mostly nicked from _perforce cmd=( "pacman" "2>/dev/null") integer i for (( i = 2; i < CURRENT - 1; i++ )); do if [[ ${words[i]} = "--config" || ${words[i]} = "--root" ]]; then cmd+=( ${words[i,i+1]} ) fi done } # main dispatcher _pacman_zsh_comp() { local -a args cmds; local tmp args=( ${${${(M)words:#-*}#-}:#-*} ) for tmp in $words; do cmds+=("${${_pacman_opts_commands[(r)*$tmp\[*]%%\[*}#*\)}") done case $args in #$words[2] in h*) if (( ${(c)#args} <= 1 && ${(w)#cmds} <= 1 )); then _pacman_action_help else _message "no more arguments" fi ;; *h*) _message "no more arguments" ;; D*) _pacman_action_database ;; F*) _pacman_action_files ;; Q*g*) # ipkg groups _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:groups:_pacman_completions_installed_groups' ;; Q*o*) # file _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:package file:_files' ;; Q*p*) # file *.pkg.tar* _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_query_modifiers[@]" \ '*:package file:_files -g "*.pkg.tar*~*.sig(.,@)"' ;; Q*) _pacman_action_query ;; P*) _arguments -s : \ '-P' \ "$_pacman_opts_print_modifiers[@]" ;; W*) _arguments -s : \ '-W' \ "$_pacman_opts_web_modifiers[@]" ;; R*) _pacman_action_remove ;; S*c*) # no completion _arguments -s : \ '(-c --clean)'{\*-c,\*--clean}'[Remove all files from the cache]' \ "$_pacman_opts_common[@]" ;; S*l*) # repos _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '*:package repo:_pacman_completions_repositories' \ ;; S*g*) # pkg groups _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '*:package group:_pacman_completions_all_groups' ;; S*s*) _arguments -s : \ "$_pacman_opts_common[@]" \ "$_pacman_opts_sync_modifiers[@]" \ '*:search text: ' ;; S*) _pacman_action_sync ;; T*) _pacman_action_deptest ;; U*) _pacman_action_upgrade ;; V*) _pacman_action_version ;; Y*) _arguments -s : \ '-Y' \ "$_pacman_opts_yay_modifiers[@]" ;; G*) _arguments -s : \ '-G' \ "$_pacman_opts_getpkgbuild_modifiers[@]" ;; *) case ${(M)words:#--*} in *--help*) if (( ${(w)#cmds} == 1 )); then _pacman_action_help else return 0; fi ;; *--sync*) _pacman_action_sync ;; *--query*) _pacman_action_query ;; *--remove*) _pacman_action_remove ;; *--deptest*) _pacman_action_deptest ;; *--database*) _pacman_action_database ;; *--files*) _pacman_action_files ;; *--version*) _pacman_action_version ;; *--upgrade*) _pacman_action_upgrade ;; *) _pacman_action_none ;; esac ;; esac } _pacman_comp() { case "$service" in yay) _pacman_zsh_comp "$@" ;; *) _message "Error" ;; esac } _pacman_comp "$@" ================================================ FILE: doc/yay.8 ================================================ .TH "YAY" "8" "2019\-10\-21" "Yay v12.0+" "Yay Manual" .nh .ad l .SH NAME yay \- AUR Helper written in go .SH SYNOPSIS \fIyay\fR [options] [targets] .sp \fIyay\fR .sp \fIyay\fR .SH DESCRIPTION Yay is a Pacman wrapper with AUR support. It passes options to Makepkg and Pacman after resolving packages to install/upgrade. This manpage only covers options unique to Yay. For other options see \fBpacman(8)\fR. .SH YAY OPERATIONS .TP .B \-Y, \-\-yay Perform yay specific operations. This is the default if no other operation is selected and targets are defined. .TP .B \-B, \-\-build Build a PKGBUILD in a given directory. .TP .B \-P, \-\-show Perform yay specific print operations. .TP .B \-G, \-\-getpkgbuild Downloads PKGBUILD from ABS or AUR. The ABS can only be used for Arch Linux repositories. .TP .B \-W, \-\-web Web related operations such as voting for AUR packages. .RE If no operation is specified 'yay \-Syu' will be performed If no operation is specified and targets are provided \-Y will be assumed .SH EXTENDED PACMAN OPERATIONS .TP .B \-S, \-Si, \-Sl, \-Ss, \-Su, \-Sc, \-Qu These operations are extended to support both AUR and repo packages. .TP .B \-Sc Yay will also clean cached AUR package and any untracked Files in the cache. Cleaning untracked files will wipe any downloaded sources or built packages but will keep already downloaded vcs sources. .TP .B \-R Yay will also remove cached data about devel packages. .SH NEW OPTIONS .TP .B \-N, \-\-repo Assume all targets are from the repositories. Additionally Actions such as sysupgrade will only act on repository packages. .TP .B \-a, \-\-aur 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 normally and include repository packages. .SH YAY OPTIONS (APPLY TO \-Y AND \-\-YAY) .TP .B Displays a list of packages matching the search terms and prompts the user on which packages to install (yogurt mode). The first search term is used to query the different sources and the following search terms are used to narrow the search results through exact matching. .TP .B \-\-gendb Generate development package database. Tracks the latest commit for each development package, when there is a new commit Yay will know to update. This is done per package whenever a package is synced. This option should only be used when migrating to Yay from another AUR helper. .TP .B \-c, \-\-clean Remove unneeded dependencies. .TP .B \-cc Remove unneeded dependencies, including packages optionally required by any other package. .SH SHOW OPTIONS (APPLY TO \-P AND \-\-show) .TP .B \-c, \-\-complete Print a list of all AUR and repo packages. This allows shell completion and is not intended to be used directly by the user. .TP .B \-d, \-\-defaultconfig Print default yay configuration. .TP .B \-g, \-\-currentconfig Print current yay configuration. .TP .B \-s, \-\-stats Displays information about installed packages and system health. If there are orphaned, or out\-of\-date packages, or packages that no longer exist on the AUR; warnings will be displayed. .TP .B \-w, \-\-news 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. .TP .B \-q, \-\-quiet Only show titles when printing news. .SH BUILD OPTIONS (APPLY TO \-B AND \-\-build) .TP .B \-i, \-\-install Build and install a PKGBUILD in a given directory .SH GETPKGBUILD OPTIONS (APPLY TO \-G AND \-\-getpkgbuild) .TP .B \-f, \-\-force Force download for ABS packages that already exist in the current directory. This ensures directories are not accidentally overwritten. .TP .B \-p, \-\-print Prints the PKGBUILD of the given packages to stdout. .SH WEB OPTIONS (APPLY TO \-W AND \-\-web) .TP Web related operations such as voting for AUR packages. Requires setting AUR_USERNAME and AUR_PASSWORD environment variables. .TP .B \-u, \-\-unvote Remove vote from AUR package(s) .TP .B \-v, \-\-vote Vote for AUR package(s) .SH PERMANENT CONFIGURATION SETTINGS .TP .B \-\-save Causes the following options to be saved back to the config file. This provides an easy way to change config options without directly editing the file. .TP .B \-\-aururl Set an alternative AUR URL. .TP .B \-\-aurrpcurl Set an alternative URL for the AUR /rpc endpoint. .TP .B \-\-builddir Directory to use for Building AUR Packages. This directory is also used as the AUR cache when deciding if Yay should skip builds. .TP .B \-\-editor Editor to use when editing PKGBUILDs. If this is not set the \fBVISUAL\fR environment variable will be checked, followed by \fBEDITOR\fR. If none of these are set Yay will prompt the user for an editor. .TP .B \-\-editorflags Passes arguments to the editor. These flags get passed to every instance where the editor is called by Yay. Arguments are split on whitespace before being passed to the editor. Multiple arguments may be passed by supplying a space separated list that is quoted by the shell. .TP .B \-\-makepkg The command to use for \fBmakepkg\fR calls. This can be a command in \fBPATH\fR or an absolute path to the file. .TP .B \-\-pacman The command to use for \fBpacman\fR calls. This can be a command in \fBPATH\fR or an absolute path to the file. .TP .B \-\-git The command to use for \fBgit\fR calls. This can be a command in \fBPATH\fR or an absolute path to the file. .TP .B \-\-gitflags Passes arguments to git. These flags get passed to every instance where git is called by yay. Arguments are split on whitespace before being passed to git. Multiple arguments may be passed by supplying a space separated list that is quoted by the shell. .TP .B \-\-gpg The command to use for \fBgpg\fR calls. This can be a command in \fBPATH\fR or an absolute path to the file. .TP .B \-\-config The pacman config file to use. .TP .B \-\-makepkgconf The config file for makepkg to use\%. If this is not set then the default config file will be used. .TP .B \-\-nomakepkgconf Reset the makepkg config file back to its default. .TP .B \-\-requestsplitn The maximum amount of packages to request per AUR query. The higher the number the faster AUR requests will be. Requesting too many packages in one AUR query will cause an error. This should only make a noticeable difference with very large requests (>500) packages. .TP .B \-\-completioninterval Time in days to refresh the completion cache. Setting this to 0 will cause the cache to be refreshed every time, while setting this to -1 will cause the cache to never be refreshed. .TP .B \-\-sortby Sort AUR results by a specific field during search. .TP .B \-\-searchby Search for AUR packages by querying the specified field. .TP .B \-\-answerclean Set a predetermined answer for the clean build menu question. This answer will be used instead of reading from standard input but will be parsed exactly the same. .TP .B \-\-answerdiff Set a predetermined answer for the edit diff menu question. This answer will be used instead of reading from standard input but will be parsed exactly the same. .TP .B \-\-answeredit Set a predetermined answer for the edit pkgbuild menu question. This answer will be used instead of reading from standard input but will be parsed exactly the same. .TP .B \-\-answerupgrade Set a predetermined answer for the upgrade menu question. Selects which package ranges or repos to omit for updates. This answer will be used instead of reading from standard input but will be treated exactly the same. .TP .B \-\-noanswerclean Unset the answer for the clean build menu. .TP .B \-\-noanswerdiff Unset the answer for the diff menu. .TP .B \-\-noansweredit Unset the answer for the edit pkgbuild menu. .TP .B \-\-noanswerupgrade Unset the answer for the upgrade menu. .TP .B \-\-cleanmenu Show the clean menu. This menu gives you the chance to fully delete the downloaded build files from Yay's cache before redownloading a fresh copy. If 'cleanmenu' is enabled in the configuration file, you can temporarily disable it by using '--cleanmenu=false' on the command line .TP .B \-\-diffmenu Show the diff menu. This menu gives you the option to view diffs from build files before building. 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. .TP .B \-\-editmenu 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. .TP .B \-\-askremovemake Ask to remove makedepends after installing packages. .TP .B \-\-askyesremovemake Ask to remove makedepends after installing packages(with "Y" as default). .TP .B \-\-removemake Remove makedepends after installing packages. .TP .B \-\-noremovemake Do not remove makedepends after installing packages. .TP .B \-\-topdown Display repository packages first and then AUR packages. .TP .B \-\-bottomup Show AUR packages first and then repository packages. .TP .B \-\-singlelineresults Override pacman's usual double-line search result format and list each result on its own line. .TP .B \-\-doublelineresults Follow pacman's double-line search result format and list each result using two lines. .TP .B \-\-devel During sysupgrade also check AUR development packages for updates. Currently only Git packages are supported. Devel checking is done using \fBgit ls-remote\fR. The newest commit hash is compared against the hash at install time. This allows devel updates to be checked almost instantly and not require the original pkgbuild to be downloaded. The slower pacaur-like devel checks can be implemented manually by piping a list of packages into yay (see \fBexamples\fR). If 'devel' is enabled in the configuration file, you can temporarily disable it by using '--devel=false' on the command line .TP .B \-\-cleanafter Remove untracked files after installation. Untracked files are removed with the exception of directories. This allows VCS packages to easily pull an update instead of having to reclone the entire repo. .TP .B \-\-keepsrc Keep pkg/ and src/ after building packages .TP .B \-\-separatesources Separate query results by source, AUR and sync .TP .B \-\-redownload Always download pkgbuilds of targets even when a copy is available in cache. .TP .B \-\-redownloadall Always download pkgbuilds of all AUR packages even when a copy is available in cache. .TP .B \-\-noredownload 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. .TP .B \-\-provides 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. .TP .B \-\-pgpfetch Prompt to import unknown PGP keys from the \fBvalidpgpkeys\fR field of each PKGBUILD. .TP .B \-\-useask Use pacman's --ask flag to automatically confirm package conflicts. Yay lists conflicts ahead of time. It is possible that Yay does not detect a conflict, causing a package to be removed without the user's confirmation. However, this is very unlikely. .TP .B \-\-combinedupgrade During sysupgrade, Yay will first perform a refresh, then show its combined menu of repo and AUR packages that will be upgraded. Then after reviewing the pkgbuilds, the repo and AUR upgrade will start with no need for manual intervention. If Yay exits for any reason After the refresh without upgrading. It is then the user's responsibility to either resolve the reason Yay exited or run a sysupgrade through pacman directly. .TP .B \-\-batchinstall When building and installing AUR packages instead of installing each package after building, queue each package for install. Then once either all packages are built or a package in the build queue is needed as a dependency to build another package, install all the packages in the install queue. .TP .B \-\-rebuild Always build target packages even when a copy is available in cache. .TP .B \-\-rebuildall Always build all AUR packages even when a copy is available in cache. .TP .B \-\-rebuildtree When installing an AUR package rebuild and reinstall all of its AUR dependencies recursively, even the ones already installed. This flag allows you to easily rebuild packages against your current system's libraries if they have become incompatible. .TP .B \-\-norebuild When building packages if the package is found in cache and is an equal version to the one wanted skip the package build and use the existing package. .TP .B \-\-mflags Passes arguments to makepkg. These flags get passed to every instance where makepkg is called by Yay. Arguments are split on whitespace before being passed to makepkg. Multiple arguments may be passed by supplying a space separated list that is quoted by the shell. .TP .B \-\-gpgflags Passes arguments to gpg. These flags get passed to every instance where gpg is called by Yay. Arguments are split on whitespace before being passed to gpg. Multiple arguments may be passed by supplying a space separated list that is quoted by the shell. .TP .B \-\-sudo The command to use for \fBsudo\fR calls. This can be a command in \fBPATH\fR or an absolute path to the file. The sudoloop is not guaranteed to work with a custom \fBsudo\fR command. .TP .B \-\-sudoflags Passes arguments to sudo. These flags get passed to every instance where sudo is called by Yay. Arguments are split on whitespace before being passed to sudo. Multiple arguments may be passed by supplying a space separated list that is quoted by the shell. .TP .B \-\-sudoloop Loop sudo calls in the background to prevent sudo from timing out during long builds. .SH EXAMPLES .TP yay \fIfoo\fR Search and install from the repos and the \fBAUR\fR\ using yogurt mode. .TP yay \-Syu Update package list and upgrade all currently installed repo and \fBAUR\fR. .TP yay \-Sua Update all currently installed \fBAUR\fR packages. .TP yay \-S \fIfoo\fR Installs package \fIfoo\fR from the repos or the \fBAUR\fR. .TP yay \-Ss \fIfoo\fR Searches for package \fIfoo\fR on the repos or the \fBAUR\fR. .TP yay \-Si \fIfoo\fR Gets information about package \fIfoo\fR from the repos or the \fBAUR\fR. .TP yay \-S \fIfoo\fR \-\-mflags "\-\-skipchecksums \-\-skippgpcheck" Installs \fIfoo\fR while skipping checksums and pgp checks. .TP yay \-\-devel \-\-save Sets devel to true in the config. .TP yay \-P \-\-stats Shows statistics for installed packages and system health. .TP pacman -Qmq | grep -Ee '-(cvs|svn|git|hg|bzr|darcs)$' | yay -S --needed - pacaur-like devel check. .SH ENVIRONMENT VARIABLES .TP .B AURDEST Can be set to configure the build directory. Overridden by \-\-builddir. .TP .B VISUAL, EDITOR When editor is not configured, use these variables to pick what editor to use when editing PKGBUILDS. .SH FILES .TP .B CONFIG DIRECTORY The config directory is \fI$XDG_CONFIG_HOME/yay/\fR. If \fB$XDG_CONFIG_HOME\fR is unset, the config directory will fall back to \fI$HOME/.config/yay\fR. \fIconfig.json\fR Is used to store all of Yay's config options. Editing this file should be done through Yay, using the options mentioned in \fBPERMANENT CONFIGURATION SETTINGS\fR. .TP .B CACHE DIRECTORY The cache directory is \fI$XDG_CACHE_HOME/yay/\fR. If \fB$XDG_CACHE_HOME\fR is unset, the cache directory will fall back to \fI$HOME/.cache/yay\fR. \fIcompletion.cache\fR holds a list of of all packages, including the AUR, for shell completion. By default the completion files are refreshed every 7 days. \fIvcs.json\fR tracks VCS packages and the latest commit of each source. If any of these commits change the package will be upgraded during a devel update. .TP .B BUILD DIRECTORY Unless otherwise set this should be the same as \fBCACHE DIRECTORY\fR. This directory is used to store downloaded AUR Packages as well as any source files and built packages from those packages. .TP .B PACMAN.CONF 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 pacman options. Notably: \fBDatabases\fR, \fBColor\fR and \fB*Path/*Dir\fR options are used. .SH SEE ALSO .BR makepkg (8), .BR makepkg.conf (5), .BR PKGBUILD (5), .BR pacman (8), .BR pacman.conf (5) See the arch wiki at https://wiki.archlinux.org/index.php/Arch_User_Repository for more info on the \fBAUR\fR. .SH BUGS Please report bugs to our GitHub page https://github.com/Jguer/yay .SH AUTHORS Jguer .br Morgan ================================================ FILE: errors.go ================================================ package main import ( "errors" "github.com/leonelquinteros/gotext" ) var ErrPackagesNotFound = errors.New(gotext.Get("could not find all required packages")) ================================================ FILE: get.go ================================================ package main import ( "context" "fmt" "net/http" "os" "strings" "github.com/Jguer/aur" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/download" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) // yay -Gp. func printPkgbuilds(dbExecutor download.DBSearcher, aurClient aur.QueryClient, httpClient *http.Client, logger *text.Logger, targets []string, mode parser.TargetMode, aurURL string, ) error { pkgbuilds, err := download.PKGBUILDs(dbExecutor, aurClient, httpClient, logger, targets, aurURL, mode) if err != nil { logger.Errorln(err) } for target, pkgbuild := range pkgbuilds { logger.Printf("\n\n# %s\n\n%s", target, string(pkgbuild)) } if len(pkgbuilds) != len(targets) { missing := []string{} for _, target := range targets { if _, ok := pkgbuilds[target]; !ok { missing = append(missing, target) } } logger.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", ")) return fmt.Errorf("") } return nil } // yay -G. func getPkgbuilds(ctx context.Context, dbExecutor download.DBSearcher, aurClient aur.QueryClient, run *runtime.Runtime, targets []string, force bool, ) error { wd, err := os.Getwd() if err != nil { return err } cloned, errD := download.PKGBUILDRepos(ctx, dbExecutor, aurClient, run.CmdBuilder, run.Logger, targets, run.Cfg.Mode, run.Cfg.AURURL, wd, force) if errD != nil { run.Logger.Errorln(errD) } if len(targets) != len(cloned) { missing := []string{} for _, target := range targets { if _, ok := cloned[target]; !ok { missing = append(missing, target) } } run.Logger.Warnln(gotext.Get("Unable to find the following packages:"), " ", strings.Join(missing, ", ")) err = fmt.Errorf("") } return err } ================================================ FILE: go.mod ================================================ module github.com/Jguer/yay/v12 require ( github.com/Jguer/aur v1.3.0 github.com/Jguer/dyalpm v0.1.1 github.com/Jguer/votar v1.0.0 github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 github.com/Morganamilo/go-srcinfo v1.0.0 github.com/adrg/strutil v0.3.1 github.com/bradleyjkemp/cupaloy v2.3.0+incompatible github.com/deckarep/golang-set/v2 v2.8.0 github.com/hashicorp/go-multierror v1.1.1 github.com/leonelquinteros/gotext v1.7.2 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.52.0 golang.org/x/sys v0.42.0 golang.org/x/term v0.41.0 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/ini.v1 v1.67.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/ebitengine/purego v0.9.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.18 // indirect github.com/itchyny/timefmt-go v0.1.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/ohler55/ojg v1.28.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) go 1.26 ================================================ FILE: go.sum ================================================ github.com/Jguer/aur v1.3.0 h1:skdjp/P9kB75TBaJmn9PKK/kCeA9QsgjdUrORZ3gldU= github.com/Jguer/aur v1.3.0/go.mod h1:F8Awo+WKzTxlXtNOO4pDQjMkePLZ+oMSbu+1fKLTTLo= github.com/Jguer/dyalpm v0.1.1 h1:38JkmJuHIGXVZedXIDGz/nhVcn8DtMk4zM+GRGipC7w= github.com/Jguer/dyalpm v0.1.1/go.mod h1:eUPJQ/zSclJKTxOPihpspulI+S8WQNsxHJoIBiBgogw= github.com/Jguer/votar v1.0.0 h1:drPYpV5Py5BeAQS8xezmT6uCEfLzotNjLf5yfmlHKTg= github.com/Jguer/votar v1.0.0/go.mod h1:rc6vgVlTqNjI4nAnPbDTbdxw/N7kXkbB8BcUDjeFbYQ= github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 h1:TMscPjkb1ThXN32LuFY5bEYIcXZx3YlwzhS1GxNpn/c= github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5/go.mod h1:Hk55m330jNiwxRodIlMCvw5iEyoRUCIY64W1p9D+tHc= github.com/Morganamilo/go-srcinfo v1.0.0 h1:Wh4nEF+HJWo+29hnxM18Q2hi+DUf0GejS13+Wg+dzmI= github.com/Morganamilo/go-srcinfo v1.0.0/go.mod h1:MP6VGY1NNpVUmYIEgoM9acix95KQqIRyqQ0hCLsyYUY= github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4= github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA= github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y= github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc= github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/ohler55/ojg v1.28.0 h1:8xClBgMIRRJGDUC9xNe7NprP4kD2C3mQMeon3wY4KXA= github.com/ohler55/ojg v1.28.0/go.mod h1:/Y5dGWkekv9ocnUixuETqiL58f+5pAsUfg5P8e7Pa2o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: local_install.go ================================================ // Experimental code for install local with dependency refactoring // Not at feature parity with install.go package main import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/sync" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/leonelquinteros/gotext" ) var ErrNoBuildFiles = errors.New(gotext.Get("cannot find PKGBUILD and .SRCINFO in directory")) func srcinfoExists(ctx context.Context, cmdBuilder exe.ICmdBuilder, targetDir string, ) error { srcInfoDir := filepath.Join(targetDir, ".SRCINFO") pkgbuildDir := filepath.Join(targetDir, "PKGBUILD") if _, err := os.Stat(srcInfoDir); err == nil { if _, err := os.Stat(pkgbuildDir); err == nil { return nil } } if _, err := os.Stat(pkgbuildDir); err == nil { // run makepkg to generate .SRCINFO srcinfo, stderr, err := cmdBuilder.Capture(cmdBuilder.BuildMakepkgCmd(ctx, targetDir, "--printsrcinfo")) if err != nil { return fmt.Errorf("unable to generate .SRCINFO: %w - %s", err, stderr) } if srcinfo == "" { return fmt.Errorf("generated .SRCINFO is empty, check your PKGBUILD for errors") } if err := os.WriteFile(srcInfoDir, []byte(srcinfo), 0o600); err != nil { return fmt.Errorf("unable to write .SRCINFO: %w", err) } return nil } return fmt.Errorf("%w: %s", ErrNoBuildFiles, targetDir) } func installLocalPKGBUILD( ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor, ) error { aurCache := run.AURClient noCheck := strings.Contains(run.Cfg.MFlags, "--nocheck") if len(cmdArgs.Targets) < 1 { return errors.New(gotext.Get("no target directories specified")) } srcInfos := map[string]*gosrc.Srcinfo{} for _, targetDir := range cmdArgs.Targets { if err := srcinfoExists(ctx, run.CmdBuilder, targetDir); err != nil { return err } pkgbuild, err := gosrc.ParseFile(filepath.Join(targetDir, ".SRCINFO")) if err != nil { return fmt.Errorf("%s: %w", gotext.Get("failed to parse .SRCINFO"), err) } srcInfos[targetDir] = pkgbuild } grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm, cmdArgs.ExistsDouble("d", "nodeps"), noCheck, cmdArgs.ExistsArg("needed"), run.Logger.Child("grapher")) graph, err := grapher.GraphFromSrcInfos(ctx, nil, srcInfos) if err != nil { return err } opService := sync.NewOperationService(ctx, dbExecutor, run) multiErr := &multierror.MultiError{} targets := graph.TopoSortedLayers(func(name string, ii *dep.InstallInfo) error { if ii.Source == dep.Missing { multiErr.Add(fmt.Errorf("%w: %s %s", ErrPackagesNotFound, name, ii.Version)) } return nil }) if err := multiErr.Return(); err != nil { return err } return opService.Run(ctx, run, cmdArgs, targets, []string{}) } ================================================ FILE: local_install_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "fmt" "io" "os" "os/exec" "path/filepath" "slices" "strings" "sync" "testing" aur "github.com/Jguer/aur" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } func TestIntegrationLocalInstall(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "pacman -S --config /etc/pacman.conf -- community/dotnet-sdk-6.0 community/dotnet-runtime-6.0", "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 dotnet-sdk-6.0", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst /testdir/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin-server jellyfin-web", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin", } wantCapture := []string{ "makepkg --packagelist", "git -C testdata/jfin git reset --hard HEAD", "git -C testdata/jfin git merge --no-edit --ff", "makepkg --packagelist", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget("testdata/jfin") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk>=6", "dotnet-sdk<7": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, LocalPackageFn: func(s string) mock.IPackage { return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalBuildOnly(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "pacman -S --config /etc/pacman.conf -- community/dotnet-sdk-6.0 community/dotnet-runtime-6.0", "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 dotnet-sdk-6.0", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", } wantCapture := []string{ "makepkg --packagelist", "git -C testdata/jfin git reset --hard HEAD", "git -C testdata/jfin git merge --no-edit --ff", "makepkg --packagelist", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddTarget("testdata/jfin") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk>=6", "dotnet-sdk<7": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, LocalPackageFn: func(s string) mock.IPackage { return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallMissingDep(t *testing.T) { wantErr := ErrPackagesNotFound makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{} wantCapture := []string{} captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget("testdata/jfin") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, LocalPackageFn: func(string) mock.IPackage { return nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{}, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.ErrorContains(t, err, wantErr.Error()) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallNeeded(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "pacman -S --config /etc/pacman.conf -- community/dotnet-sdk-6.0 community/dotnet-runtime-6.0", "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 dotnet-sdk-6.0", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", } wantCapture := []string{ "makepkg --packagelist", "git -C testdata/jfin git reset --hard HEAD", "git -C testdata/jfin git merge --no-edit --ff", "makepkg --packagelist", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddArg("needed") cmdArgs.AddTarget("testdata/jfin") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, IsCorrectVersionInstalledFn: func(s1, s2 string) bool { return true }, LocalPackageFn: func(s string) mock.IPackage { if s == "jellyfin-server" { return &mock.Package{ PName: "jellyfin-server", PBase: "jellyfin-server", PVersion: "10.8.4-1", PDB: mock.NewDB("community"), } } return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk>=6", "dotnet-sdk<7": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow), "show calls: %v", mockRunner.ShowCalls) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallGenerateSRCINFO(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) srcinfo, err := os.ReadFile("testdata/jfin/.SRCINFO") require.NoError(t, err) assert.True(t, strings.HasPrefix(string(srcinfo), "pkgbase = jellyfin"), string(srcinfo)) targetDir := t.TempDir() f, err = os.OpenFile(filepath.Join(targetDir, "PKGBUILD"), os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "pacman -S --config /etc/pacman.conf -- community/dotnet-sdk-6.0 community/dotnet-runtime-6.0", "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 dotnet-sdk-6.0", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst /testdir/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin-server jellyfin-web", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin", } wantCapture := []string{ "makepkg --printsrcinfo", "makepkg --packagelist", "git -C testdata/jfin git reset --hard HEAD", "git -C testdata/jfin git merge --no-edit --ff", "makepkg --packagelist", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { if slices.Contains(cmd.Args, "--printsrcinfo") { return string(srcinfo), "", nil } return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget(targetDir) settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk>=6", "dotnet-sdk<7": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", Debug: false, }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallMissingFiles(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) srcinfo, err := os.ReadFile("testdata/jfin/.SRCINFO") require.NoError(t, err) targetDir := t.TempDir() tars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } wantShow := []string{} wantCapture := []string{} captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { if cmd.Args[1] == "--printsrcinfo" { return string(srcinfo), "", nil } return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget(targetDir) settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk>=6", "dotnet-sdk<7", "dotnet-runtime>=6", "dotnet-runtime<7", "jellyfin-server=10.8.4", "jellyfin-web=10.8.4": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "dotnet-runtime>=6", "dotnet-runtime<7": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk>=6", "dotnet-sdk<7": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, } config := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", Debug: false, }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), config, cmdArgs, db) require.ErrorIs(t, err, ErrNoBuildFiles) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallWithDepsProvides(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tars := []string{ tmpDir + "/ceph-bin-17.2.6-2-x86_64.pkg.tar.zst", tmpDir + "/ceph-libs-bin-17.2.6-2-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/ceph-libs-bin-17.2.6-2-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- ceph-libs-bin", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/ceph-bin-17.2.6-2-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- ceph-bin", } wantCapture := []string{ "git -C testdata/cephbin git reset --hard HEAD", "git -C testdata/cephbin git merge --no-edit --ff", "makepkg --packagelist", "makepkg --packagelist", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget("testdata/cephbin") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "ceph=17.2.6-2", "ceph-libs=17.2.6-2": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, LocalPackageFn: func(s string) mock.IPackage { return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } config := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), config, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestIntegrationLocalInstallTwoSrcInfosWithDeps(t *testing.T) { makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" tmpDir1 := t.TempDir() tmpDir2 := t.TempDir() f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) pkgsTars := []string{ tmpDir1 + "/libzip-git-1.9.2.r166.gd2c47d0f-1-x86_64.pkg.tar.zst", tmpDir2 + "/gourou-0.8.1-4-x86_64.pkg.tar.zst", } wantShow := []string{ "makepkg --verifysource --skippgpcheck -f -Cc", "makepkg --verifysource --skippgpcheck -f -Cc", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir1/libzip-git-1.9.2.r166.gd2c47d0f-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- libzip-git", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir2/gourou-0.8.1-4-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- gourou", } wantCapture := []string{ "git -C testdata/gourou git reset --hard HEAD", "git -C testdata/gourou git merge --no-edit --ff", "git -C testdata/libzip-git git reset --hard HEAD", "git -C testdata/libzip-git git merge --no-edit --ff", "makepkg --packagelist", "makepkg --packagelist", } captureCounter := 0 captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { captureCounter++ switch captureCounter { case 5: return pkgsTars[0] + "\n", "", nil case 6: return pkgsTars[1] + "\n", "", nil default: return "", "", nil } } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range pkgsTars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("B") cmdArgs.AddArg("i") cmdArgs.AddTarget("testdata/gourou") cmdArgs.AddTarget("testdata/libzip-git") settings.NoConfirm = true defer func() { settings.NoConfirm = false }() db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "gourou", "libzip", "libzip-git": return false } return true }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, LocalPackageFn: func(s string) mock.IPackage { return nil }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: newTestLogger(), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir1, "/testdir1") // replace the temp dir with a static path show = strings.ReplaceAll(show, tmpDir2, "/testdir2") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } ================================================ FILE: main.go ================================================ package main // import "github.com/Jguer/yay" import ( "context" "errors" "os" "os/exec" "runtime/debug" "strings" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db/ialpm" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) var ( yayVersion = "13.0.0" // To be set by compiler. localePath = "/usr/share/locale" // To be set by compiler. ) func initGotext() { if envLocalePath := os.Getenv("LOCALE_PATH"); envLocalePath != "" { localePath = envLocalePath } if lc := os.Getenv("LANGUAGE"); lc != "" { // Split LANGUAGE by ':' and prioritize the first locale // Should fix in gotext to support this locales := strings.Split(lc, ":") if len(locales) > 0 && locales[0] != "" { gotext.Configure(localePath, locales[0], "yay") } } else if lc := os.Getenv("LC_ALL"); lc != "" { gotext.Configure(localePath, lc, "yay") } else if lc := os.Getenv("LC_MESSAGES"); lc != "" { gotext.Configure(localePath, lc, "yay") } else { gotext.Configure(localePath, os.Getenv("LANG"), "yay") } } func main() { fallbackLog := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, false, "fallback") var ( err error ctx = context.Background() ret = 0 ) defer func() { if rec := recover(); rec != nil { fallbackLog.Errorln("Panic occurred:", rec) fallbackLog.Errorln("Stack trace:", string(debug.Stack())) ret = 1 } os.Exit(ret) }() initGotext() if os.Geteuid() == 0 { fallbackLog.Warnln(gotext.Get("Avoid running yay as root/sudo.")) } configPath := settings.GetConfigPath() // Parse config cfg, err := settings.NewConfig(fallbackLog, configPath, yayVersion) if err != nil { if str := err.Error(); str != "" { fallbackLog.Errorln(str) } ret = 1 return } cmdArgs := parser.MakeArguments() // Parse command line if err = cfg.ParseCommandLine(cmdArgs); err != nil { if str := err.Error(); str != "" { fallbackLog.Errorln(str) } ret = 1 return } if cfg.SaveConfig { if errS := cfg.Save(configPath, yayVersion); errS != nil { fallbackLog.Errorln(errS) } } // Build run run, err := runtime.NewRuntime(cfg, cmdArgs, yayVersion) if err != nil { if str := err.Error(); str != "" { fallbackLog.Errorln(str) } ret = 1 return } dbExecutor, err := ialpm.NewExecutor(run.PacmanConf, run.Logger.Child("db")) if err != nil { if str := err.Error(); str != "" { fallbackLog.Errorln(str) } ret = 1 return } defer func() { if rec := recover(); rec != nil { fallbackLog.Errorln("Panic occurred in DB operation:", rec) fallbackLog.Errorln("Stack trace:", string(debug.Stack())) } dbExecutor.Cleanup() }() if err = handleCmd(ctx, run, cmdArgs, dbExecutor); err != nil { if str := err.Error(); str != "" { fallbackLog.Errorln(str) } exitError := &exec.ExitError{} if errors.As(err, &exitError) { // mirror pacman exit code when applicable ret = exitError.ExitCode() return } // fallback ret = 1 } } ================================================ FILE: pkg/cmd/graph/main.go ================================================ package main import ( "context" "errors" "fmt" "os" "path/filepath" "github.com/Jguer/yay/v12/pkg/db/ialpm" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/aur/metadata" "github.com/leonelquinteros/gotext" ) func handleCmd(logger *text.Logger) error { cfg, err := settings.NewConfig(logger, settings.GetConfigPath(), "") if err != nil { return err } cmdArgs := parser.MakeArguments() if errP := cfg.ParseCommandLine(cmdArgs); errP != nil { return errP } run, err := runtime.NewRuntime(cfg, cmdArgs, "1.0.0") if err != nil { return err } dbExecutor, err := ialpm.NewExecutor(run.PacmanConf, logger) if err != nil { return err } aurCache, err := metadata.New( metadata.WithCacheFilePath( filepath.Join(cfg.BuildDir, "aur.json"))) if err != nil { return fmt.Errorf("%s: %w", gotext.Get("failed to retrieve aur Cache"), err) } grapher := dep.NewGrapher(dbExecutor, aurCache, true, settings.NoConfirm, cmdArgs.ExistsDouble("d", "nodeps"), false, false, run.Logger.Child("grapher")) return graphPackage(context.Background(), grapher, cmdArgs.Targets) } func main() { fallbackLog := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, false, "fallback") if err := handleCmd(fallbackLog); err != nil { fallbackLog.Errorln(err) os.Exit(1) } } func graphPackage( ctx context.Context, grapher *dep.Grapher, targets []string, ) error { if len(targets) != 1 { return errors.New(gotext.Get("only one target is allowed")) } graph, err := grapher.GraphFromAUR(ctx, nil, []string{targets[0]}) if err != nil { return err } fmt.Fprintln(os.Stdout, graph.String()) fmt.Fprintln(os.Stdout, "\nlayers map\n", graph.TopoSortedLayers(nil)) return nil } ================================================ FILE: pkg/completion/completion.go ================================================ package completion import ( "context" "io" "os" "path/filepath" "strings" "time" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/download" "github.com/Jguer/yay/v12/pkg/text" ) // NeedsUpdate checks if the completion cache needs to be regenerated. // Returns true if the file doesn't exist, is older than interval days, is empty, or force is true. func NeedsUpdate(completionPath string, interval int, force bool) bool { if force { return true } info, err := os.Stat(completionPath) if os.IsNotExist(err) { return true } // If the file is empty or invalid, regenerate it if info.Size() == 0 { return true } if interval != -1 && time.Since(info.ModTime()).Hours() >= float64(interval*24) { return true } return false } type PkgSynchronizer interface { SyncPackages(...string) []db.IPackage } // Show provides completion info for shells. func Show(ctx context.Context, httpClient download.HTTPRequestDoer, dbExecutor PkgSynchronizer, aurURL, completionPath string, interval int, force bool, logger *text.Logger, ) error { if NeedsUpdate(completionPath, interval, force) { if err := UpdateCache(ctx, httpClient, dbExecutor, aurURL, completionPath, logger); err != nil { return err } } in, err := os.OpenFile(completionPath, os.O_RDWR|os.O_CREATE, 0o644) if err != nil { return err } defer in.Close() _, err = io.Copy(os.Stdout, in) return err } // UpdateCache regenerates the completion cache file unconditionally. func UpdateCache(ctx context.Context, httpClient download.HTTPRequestDoer, dbExecutor PkgSynchronizer, aurURL, completionPath string, logger *text.Logger, ) error { if err := os.MkdirAll(filepath.Dir(completionPath), 0o755); err != nil { return err } out, err := os.Create(completionPath) if err != nil { return err } defer out.Close() if err := createAURList(ctx, httpClient, aurURL, out, logger); err != nil { os.Remove(completionPath) return err } return createRepoList(dbExecutor, out) } // createAURList creates a new completion file. func createAURList(ctx context.Context, client download.HTTPRequestDoer, aurURL string, out io.Writer, logger *text.Logger) error { scanner, err := download.GetPackageScanner(ctx, client, aurURL, logger) if err != nil { return err } defer scanner.Close() scanner.Scan() for scanner.Scan() { pkgName := scanner.Text() if strings.HasPrefix(pkgName, "#") { continue } if _, err := io.WriteString(out, pkgName+"\tAUR\n"); err != nil { return err } } return nil } // createRepoList appends Repo packages to completion cache. func createRepoList(dbExecutor PkgSynchronizer, out io.Writer) error { for _, pkg := range dbExecutor.SyncPackages() { _, err := io.WriteString(out, pkg.Name()+"\t"+pkg.DB().Name()+"\n") if err != nil { return err } } return nil } ================================================ FILE: pkg/completion/completion_test.go ================================================ //go:build !integration // +build !integration package completion import ( "bytes" "compress/gzip" "context" "errors" "io" "net/http" "os" "path/filepath" "testing" "time" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const samplePackageResp = ` # AUR package list, generated on Fri, 24 Jul 2020 22:05:22 GMT cytadela bitefusion globs-svn ri-li globs-benchmarks-svn dunelegacy lumina eternallands-sound ` const expectPackageCompletion = `cytadela AUR bitefusion AUR globs-svn AUR ri-li AUR globs-benchmarks-svn AUR dunelegacy AUR lumina AUR eternallands-sound AUR ` type mockDoer struct { t *testing.T returnBody []byte returnStatusCode int returnErr error wantURL string } func (m *mockDoer) Get(url string) (*http.Response, error) { assert.Equal(m.t, m.wantURL, url) return &http.Response{ StatusCode: m.returnStatusCode, Body: io.NopCloser(bytes.NewReader(m.returnBody)), }, m.returnErr } func gzipString(s string) []byte { var buf bytes.Buffer gz := gzip.NewWriter(&buf) gz.Write([]byte(s)) gz.Close() return buf.Bytes() } func Test_createAURList(t *testing.T) { t.Parallel() doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: []byte(samplePackageResp), returnErr: nil, } out := &bytes.Buffer{} err := createAURList(context.Background(), doer, "https://aur.archlinux.org", out, nil) assert.NoError(t, err) gotOut := out.String() assert.Equal(t, expectPackageCompletion, gotOut) } func Test_createAURListGzip(t *testing.T) { t.Parallel() doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: gzipString(samplePackageResp), returnErr: nil, } out := &bytes.Buffer{} err := createAURList(context.Background(), doer, "https://aur.archlinux.org", out, nil) assert.NoError(t, err) gotOut := out.String() assert.Equal(t, expectPackageCompletion, gotOut) } func Test_createAURListHTTPError(t *testing.T) { t.Parallel() doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: []byte(samplePackageResp), returnErr: errors.New("Not available"), } out := &bytes.Buffer{} err := createAURList(context.Background(), doer, "https://aur.archlinux.org", out, nil) assert.EqualError(t, err, "Not available") } func Test_createAURListStatusError(t *testing.T) { t.Parallel() doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 503, returnBody: []byte(samplePackageResp), returnErr: nil, } out := &bytes.Buffer{} err := createAURList(context.Background(), doer, "https://aur.archlinux.org", out, nil) assert.EqualError(t, err, "invalid status code: 503") } func TestNeedsUpdate(t *testing.T) { t.Parallel() tests := []struct { name string setupFile func(t *testing.T, path string) interval int force bool expectedResult bool }{ { name: "force returns true", setupFile: nil, interval: 7, force: true, expectedResult: true, }, { name: "file does not exist returns true", setupFile: nil, interval: 7, force: false, expectedResult: true, }, { name: "fresh file returns false", setupFile: func(t *testing.T, path string) { t.Helper() err := os.WriteFile(path, []byte("test"), 0o600) require.NoError(t, err) }, interval: 7, force: false, expectedResult: false, }, { name: "interval -1 never updates", setupFile: func(t *testing.T, path string) { t.Helper() err := os.WriteFile(path, []byte("test"), 0o600) require.NoError(t, err) // Set file time to 30 days ago oldTime := time.Now().Add(-30 * 24 * time.Hour) err = os.Chtimes(path, oldTime, oldTime) require.NoError(t, err) }, interval: -1, force: false, expectedResult: false, }, { name: "old file returns true", setupFile: func(t *testing.T, path string) { t.Helper() err := os.WriteFile(path, []byte("test"), 0o600) require.NoError(t, err) // Set file time to 10 days ago oldTime := time.Now().Add(-10 * 24 * time.Hour) err = os.Chtimes(path, oldTime, oldTime) require.NoError(t, err) }, interval: 7, force: false, expectedResult: true, }, { name: "file within interval returns false", setupFile: func(t *testing.T, path string) { t.Helper() err := os.WriteFile(path, []byte("test"), 0o600) require.NoError(t, err) // Set file time to 3 days ago oldTime := time.Now().Add(-3 * 24 * time.Hour) err = os.Chtimes(path, oldTime, oldTime) require.NoError(t, err) }, interval: 7, force: false, expectedResult: false, }, { name: "empty file returns true", setupFile: func(t *testing.T, path string) { t.Helper() // Create an empty file err := os.WriteFile(path, []byte(""), 0o600) require.NoError(t, err) }, interval: 7, force: false, expectedResult: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() tmpDir := t.TempDir() completionPath := filepath.Join(tmpDir, "completion") if tt.setupFile != nil { tt.setupFile(t, completionPath) } result := NeedsUpdate(completionPath, tt.interval, tt.force) assert.Equal(t, tt.expectedResult, result) }) } } // mockPkgSynchronizer implements PkgSynchronizer for testing. type mockPkgSynchronizer struct { packages []db.IPackage } func (m *mockPkgSynchronizer) SyncPackages(...string) []db.IPackage { return m.packages } func Test_createRepoList(t *testing.T) { t.Parallel() tests := []struct { name string packages []db.IPackage expectedOutput string expectedError error }{ { name: "empty package list", packages: []db.IPackage{}, expectedOutput: "", expectedError: nil, }, { name: "single package", packages: []db.IPackage{ &mock.Package{PName: "vim", PDB: mock.NewDB("extra")}, }, expectedOutput: "vim\textra\n", expectedError: nil, }, { name: "multiple packages", packages: []db.IPackage{ &mock.Package{PName: "vim", PDB: mock.NewDB("extra")}, &mock.Package{PName: "git", PDB: mock.NewDB("extra")}, &mock.Package{PName: "linux", PDB: mock.NewDB("core")}, }, expectedOutput: "vim\textra\ngit\textra\nlinux\tcore\n", expectedError: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() dbExecutor := &mockPkgSynchronizer{packages: tt.packages} out := &bytes.Buffer{} err := createRepoList(dbExecutor, out) if tt.expectedError != nil { assert.EqualError(t, err, tt.expectedError.Error()) } else { assert.NoError(t, err) } assert.Equal(t, tt.expectedOutput, out.String()) }) } } // errorWriter is a writer that always returns an error. type errorWriter struct{} func (e *errorWriter) Write(p []byte) (n int, err error) { return 0, errors.New("write error") } func Test_createRepoListWriteError(t *testing.T) { t.Parallel() dbExecutor := &mockPkgSynchronizer{ packages: []db.IPackage{ &mock.Package{PName: "vim", PDB: mock.NewDB("extra")}, }, } err := createRepoList(dbExecutor, &errorWriter{}) assert.EqualError(t, err, "write error") } func TestUpdateCache(t *testing.T) { t.Parallel() tests := []struct { name string doer *mockDoer packages []db.IPackage expectedOutput string expectError bool }{ { name: "successful update", doer: &mockDoer{ returnStatusCode: 200, returnBody: []byte("# Comment\npkg1\npkg2\n"), returnErr: nil, }, packages: []db.IPackage{ &mock.Package{PName: "vim", PDB: mock.NewDB("extra")}, }, expectedOutput: "pkg1\tAUR\npkg2\tAUR\nvim\textra\n", expectError: false, }, { name: "AUR fetch error removes file", doer: &mockDoer{ returnStatusCode: 500, returnBody: []byte{}, returnErr: nil, }, packages: []db.IPackage{}, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() tmpDir := t.TempDir() completionPath := filepath.Join(tmpDir, "subdir", "completion") tt.doer.t = t tt.doer.wantURL = "https://aur.archlinux.org/packages.gz" dbExecutor := &mockPkgSynchronizer{packages: tt.packages} err := UpdateCache(context.Background(), tt.doer, dbExecutor, "https://aur.archlinux.org", completionPath, nil) if tt.expectError { assert.Error(t, err) // File should be removed on error _, statErr := os.Stat(completionPath) assert.True(t, os.IsNotExist(statErr)) } else { require.NoError(t, err) content, readErr := os.ReadFile(completionPath) require.NoError(t, readErr) assert.Equal(t, tt.expectedOutput, string(content)) } }) } } func TestShow(t *testing.T) { // Note: Not running in parallel because we need to capture os.Stdout tests := []struct { name string setupFile func(t *testing.T, path string) doer *mockDoer packages []db.IPackage interval int force bool expectError bool }{ { name: "existing fresh file", setupFile: func(t *testing.T, path string) { t.Helper() err := os.WriteFile(path, []byte("cached\tdata\n"), 0o600) require.NoError(t, err) }, doer: nil, // Should not be called packages: nil, interval: 7, force: false, expectError: false, }, { name: "file needs update", setupFile: nil, doer: &mockDoer{ returnStatusCode: 200, returnBody: []byte("# Comment\naur-pkg\n"), returnErr: nil, }, packages: []db.IPackage{ &mock.Package{PName: "repo-pkg", PDB: mock.NewDB("core")}, }, interval: 7, force: false, expectError: false, }, { name: "force update", setupFile: nil, doer: &mockDoer{ returnStatusCode: 200, returnBody: []byte("# Comment\nforced-pkg\n"), returnErr: nil, }, packages: []db.IPackage{}, interval: 7, force: true, expectError: false, }, { name: "update cache error", setupFile: nil, doer: &mockDoer{ returnStatusCode: 500, returnBody: []byte{}, returnErr: nil, }, packages: []db.IPackage{}, interval: 7, force: false, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Not running in parallel because we capture os.Stdout tmpDir := t.TempDir() completionPath := filepath.Join(tmpDir, "completion") if tt.setupFile != nil { tt.setupFile(t, completionPath) } if tt.doer != nil { tt.doer.t = t tt.doer.wantURL = "https://aur.archlinux.org/packages.gz" } dbExecutor := &mockPkgSynchronizer{packages: tt.packages} // Capture stdout using a pipe oldStdout := os.Stdout r, w, pipeErr := os.Pipe() require.NoError(t, pipeErr) os.Stdout = w err := Show(context.Background(), tt.doer, dbExecutor, "https://aur.archlinux.org", completionPath, tt.interval, tt.force, nil) // Close writer first, then restore stdout, then read w.Close() os.Stdout = oldStdout var buf bytes.Buffer _, copyErr := io.Copy(&buf, r) r.Close() if tt.expectError { assert.Error(t, err) } else { require.NoError(t, err) require.NoError(t, copyErr) // Verify file exists and has content content, readErr := os.ReadFile(completionPath) require.NoError(t, readErr) assert.NotEmpty(t, content) } }) } } func TestShowFileOpenError(t *testing.T) { t.Parallel() tmpDir := t.TempDir() // Use a path that can't be created (directory as file) completionPath := filepath.Join(tmpDir, "completion") // Create a directory where we expect a file - this will cause OpenFile to fail err := os.MkdirAll(completionPath, 0o755) require.NoError(t, err) doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: []byte("# Comment\npkg\n"), returnErr: nil, } dbExecutor := &mockPkgSynchronizer{packages: []db.IPackage{}} err = Show(context.Background(), doer, dbExecutor, "https://aur.archlinux.org", completionPath, 7, true, nil) assert.Error(t, err) } func TestUpdateCacheMkdirError(t *testing.T) { t.Parallel() // Create a file where we expect a directory - this will cause MkdirAll to fail tmpDir := t.TempDir() blockingFile := filepath.Join(tmpDir, "blocking") err := os.WriteFile(blockingFile, []byte("block"), 0o600) require.NoError(t, err) completionPath := filepath.Join(blockingFile, "subdir", "completion") doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: []byte("# Comment\npkg\n"), returnErr: nil, } dbExecutor := &mockPkgSynchronizer{packages: []db.IPackage{}} err = UpdateCache(context.Background(), doer, dbExecutor, "https://aur.archlinux.org", completionPath, nil) assert.Error(t, err) } func Test_createAURListWriteError(t *testing.T) { t.Parallel() doer := &mockDoer{ t: t, wantURL: "https://aur.archlinux.org/packages.gz", returnStatusCode: 200, returnBody: []byte("# Comment\npkg1\npkg2\n"), returnErr: nil, } err := createAURList(context.Background(), doer, "https://aur.archlinux.org", &errorWriter{}, nil) assert.EqualError(t, err, "write error") } ================================================ FILE: pkg/db/executor.go ================================================ package db import ( "time" alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/text" ) type ( IPackage = alpm.Package Depend = alpm.Depend ) // VerCmp performs version comparison according to Pacman conventions. Return // value is <0 if and only if v1 is older than v2. func VerCmp(v1, v2 string) int { return alpm.VerCmp(v1, v2) } type Upgrade struct { Name string Base string Repository string LocalVersion string RemoteVersion string Reason alpm.PkgReason Extra string // Extra information to be displayed } type SyncUpgrade struct { Package alpm.Package LocalVersion string Reason alpm.PkgReason } type Executor interface { AlpmArchitectures() ([]string, error) BiggestPackages() []IPackage Cleanup() InstalledRemotePackageNames() []string InstalledRemotePackages() map[string]IPackage InstalledSyncPackageNames() []string IsCorrectVersionInstalled(string, string) bool LastBuildTime() time.Time LocalPackage(string) IPackage LocalPackages() []IPackage LocalSatisfierExists(string) bool PackageDepends(IPackage) []Depend PackageGroups(IPackage) []string PackageOptionalDepends(IPackage) []Depend PackageProvides(IPackage) []Depend PackagesFromGroup(string) []IPackage PackagesFromGroupAndDB(string, string) ([]IPackage, error) RefreshHandle() error SyncUpgrades(enableDowngrade bool) ( map[string]SyncUpgrade, error) Repos() []string SatisfierFromDB(string, string) (IPackage, error) SyncPackage(string) IPackage SyncPackageFromDB(string, string) IPackage SyncPackages(...string) []IPackage SyncSatisfier(string) IPackage SyncSatisfierExists(string) bool SetLogger(logger *text.Logger) } ================================================ FILE: pkg/db/ialpm/alpm.go ================================================ package ialpm import ( "errors" "fmt" "os" "strconv" "time" alpm "github.com/Jguer/dyalpm" pacmanconf "github.com/Morganamilo/go-pacmanconf" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" ) type AlpmExecutor struct { handle alpm.Handle localDB alpm.Database syncDB []alpm.Database syncDBsCache []alpm.Database conf *pacmanconf.Config log *text.Logger installedRemotePkgNames []string installedRemotePkgMap map[string]alpm.Package installedSyncPkgNames []string } func NewExecutor(pacmanConf *pacmanconf.Config, logger *text.Logger) (*AlpmExecutor, error) { ae := &AlpmExecutor{ handle: nil, localDB: nil, syncDB: nil, syncDBsCache: []alpm.Database{}, conf: pacmanConf, log: logger, installedRemotePkgNames: nil, installedRemotePkgMap: nil, installedSyncPkgNames: nil, } if err := ae.RefreshHandle(); err != nil { return nil, err } var err error ae.localDB, err = ae.handle.LocalDB() if err != nil { return nil, err } ae.syncDB, err = ae.handle.SyncDBs() if err != nil { return nil, err } return ae, nil } func toUsage(usages []string) alpm.Usage { if len(usages) == 0 { return alpm.UsageAll } var ret alpm.Usage for _, usage := range usages { switch usage { case "Sync": ret |= alpm.UsageSync case "Search": ret |= alpm.UsageSearch case "Install": ret |= alpm.UsageInstall case "Upgrade": ret |= alpm.UsageUpgrade case "All": ret |= alpm.UsageAll } } return ret } func configureAlpm(pacmanConf *pacmanconf.Config, alpmHandle alpm.Handle) error { for _, repo := range pacmanConf.Repos { // TODO: set SigLevel alpmDB, err := alpmHandle.RegisterSyncDB(repo.Name, 0) if err != nil { return err } if err := alpmDB.SetServers(repo.Servers); err != nil { return err } if err := alpmDB.SetUsage(int(toUsage(repo.Usage))); err != nil { return err } } if err := alpmHandle.SetCacheDirs(pacmanConf.CacheDir); err != nil { return err } // add hook directories 1-by-1 to avoid overwriting the system directory for _, dir := range pacmanConf.HookDir { if err := alpmHandle.AddHookDir(dir); err != nil { return err } } if err := alpmHandle.SetGPGDir(pacmanConf.GPGDir); err != nil { return err } if err := alpmHandle.SetLogFile(pacmanConf.LogFile); err != nil { return err } if err := alpmHandle.SetIgnorePkgs(pacmanConf.IgnorePkg); err != nil { return err } if err := alpmHandle.SetIgnoreGroups(pacmanConf.IgnoreGroup); err != nil { return err } if err := alpmSetArchitecture(alpmHandle, pacmanConf.Architecture); err != nil { return err } if err := alpmHandle.SetNoUpgrades(pacmanConf.NoUpgrade); err != nil { return err } if err := alpmHandle.SetNoExtracts(pacmanConf.NoExtract); err != nil { return err } if err := alpmHandle.SetUseSyslog(pacmanConf.UseSyslog); err != nil { return err } return alpmHandle.SetCheckSpace(pacmanConf.CheckSpace) } func (ae *AlpmExecutor) logCallback() func(level alpm.LogLevel, str string) { return func(level alpm.LogLevel, str string) { switch level { case alpm.LogWarning: ae.log.Warn(str) case alpm.LogError: ae.log.Error(str) } } } func (ae *AlpmExecutor) questionCallback() func(question alpm.QuestionAny) { return func(question alpm.QuestionAny) { if qi, err := question.QuestionInstallIgnorepkg(); err == nil { qi.SetInstall(true) } qp, err := question.QuestionSelectProvider() if err != nil { return } if settings.HideMenus { return } size := 0 _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.Package) error { size++ return nil }) str := text.Bold(gotext.Get("There are %[1]d providers available for %[2]s:", size, qp.Dep())) size = 1 var dbName string _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.Package) error { thisDB := pkg.DB().Name() if dbName != thisDB { dbName = thisDB str += "\n" str += ae.log.SprintOperationInfo(gotext.Get("Repository"), " ", dbName, "\n ") } str += fmt.Sprintf("%d) %s ", size, pkg.Name()) size++ return nil }) ae.log.OperationInfoln(str) for { ae.log.Println(gotext.Get("\nEnter a number (default=1): ")) // TODO: reenable noconfirm if settings.NoConfirm { ae.log.Println() break } numberBuf, err := ae.log.GetInput("", false) if err != nil { ae.log.Errorln(err) break } if numberBuf == "" { break } num, err := strconv.Atoi(numberBuf) if err != nil { ae.log.Errorln(gotext.Get("invalid number: %s", numberBuf)) continue } if num < 1 || num > size { ae.log.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size)) continue } qp.SetUseIndex(num - 1) break } } } func (ae *AlpmExecutor) RefreshHandle() error { if ae.handle != nil { if errRelease := ae.handle.Release(); errRelease != nil { return errRelease } } alpmHandle, err := alpm.Initialize(ae.conf.RootDir, ae.conf.DBPath) if err != nil { return errors.New(gotext.Get("unable to CreateHandle: %s", err)) } if errConf := configureAlpm(ae.conf, alpmHandle); errConf != nil { return errConf } if err := alpmSetQuestionCallback(alpmHandle, ae.questionCallback()); err != nil { return err } alpmSetLogCallback(alpmHandle, ae.logCallback()) ae.handle = alpmHandle ae.syncDBsCache = nil ae.syncDB, err = alpmHandle.SyncDBs() if err != nil { return err } ae.localDB, err = alpmHandle.LocalDB() return err } func (ae *AlpmExecutor) LocalSatisfierExists(pkgName string) bool { if _, err := ae.localDB.PkgCache().FindSatisfier(pkgName); err != nil { return false } return true } func (ae *AlpmExecutor) SyncSatisfierExists(pkgName string) bool { return ae.SyncSatisfier(pkgName) != nil } func (ae *AlpmExecutor) IsCorrectVersionInstalled(pkgName, versionRequired string) bool { alpmPackage := ae.localDB.Pkg(pkgName) if alpmPackage == nil { return false } return alpmPackage.Version() == versionRequired } func (ae *AlpmExecutor) SyncSatisfier(pkgName string) alpm.Package { dbs := ae.syncDBs() if len(dbs) == 0 { return nil } // Use FindDBSatisfier across sync databases dbSlice := make([]alpm.Database, len(dbs)) copy(dbSlice, dbs) return ae.handle.FindDBSatisfier(dbSlice, pkgName) } func (ae *AlpmExecutor) PackagesFromGroup(groupName string) []alpm.Package { pkgs, err := ae.handle.FindGroupPkgs(ae.syncDBs(), groupName) if err != nil { return nil } return pkgs } func (ae *AlpmExecutor) PackagesFromGroupAndDB(groupName, dbName string) ([]alpm.Package, error) { singleDBs, err := ae.handle.SyncDBListByDBName(dbName) if err != nil { return nil, err } return ae.handle.FindGroupPkgs(singleDBs, groupName) } func (ae *AlpmExecutor) LocalPackages() []alpm.Package { localPackages := []alpm.Package{} _ = ae.localDB.PkgCache().ForEach(func(pkg alpm.Package) error { localPackages = append(localPackages, pkg) return nil }) return localPackages } // SyncPackages searches SyncDB for packages or returns all packages if no search param is given. func (ae *AlpmExecutor) SyncPackages(pkgNames ...string) []alpm.Package { repoPackages := []alpm.Package{} for _, alpmDB := range ae.syncDBs() { if len(pkgNames) == 0 { _ = alpmDB.PkgCache().ForEach(func(pkg alpm.Package) error { repoPackages = append(repoPackages, pkg) return nil }) continue } _ = alpmDB.Search(pkgNames).ForEach(func(pkg alpm.Package) error { repoPackages = append(repoPackages, pkg) return nil }) } return repoPackages } func (ae *AlpmExecutor) LocalPackage(pkgName string) alpm.Package { pkg := ae.localDB.Pkg(pkgName) if pkg == nil { return nil } return pkg } func (ae *AlpmExecutor) syncDBs() []alpm.Database { if ae.syncDBsCache == nil { ae.syncDBsCache = ae.syncDB } return ae.syncDBsCache } func (ae *AlpmExecutor) SyncPackage(pkgName string) alpm.Package { for _, db := range ae.syncDBs() { if dbPkg := db.Pkg(pkgName); dbPkg != nil { return dbPkg } } return nil } func (ae *AlpmExecutor) SyncPackageFromDB(pkgName, dbName string) alpm.Package { singleDB, err := ae.handle.SyncDBByName(dbName) if err != nil { return nil } return singleDB.Pkg(pkgName) } func (ae *AlpmExecutor) SatisfierFromDB(pkgName, dbName string) (alpm.Package, error) { singleDBs, err := ae.handle.SyncDBListByDBName(dbName) if err != nil { return nil, err } foundPkg := ae.handle.FindDBSatisfier(singleDBs, pkgName) if foundPkg == nil { return nil, nil } return foundPkg, nil } func (ae *AlpmExecutor) PackageDepends(pkg alpm.Package) []alpm.Depend { return pkg.Depends() } func (ae *AlpmExecutor) PackageOptionalDepends(pkg alpm.Package) []alpm.Depend { return pkg.OptionalDepends() } func (ae *AlpmExecutor) PackageProvides(pkg alpm.Package) []alpm.Depend { return pkg.Provides() } func (ae *AlpmExecutor) PackageGroups(pkg alpm.Package) []string { return pkg.Groups() } // upRepo gathers local packages and checks if they have new versions. // Output: Upgrade type package list. func (ae *AlpmExecutor) SyncUpgrades(enableDowngrade bool) ( map[string]db.SyncUpgrade, error, ) { ups := map[string]db.SyncUpgrade{} var errReturn error localDB, errDB := ae.handle.LocalDB() if errDB != nil { return ups, errDB } if err := ae.handle.TransInit(alpm.TransFlagNoLock); err != nil { return ups, err } defer func() { errReturn = ae.handle.TransRelease() }() if err := ae.handle.SyncSysupgrade(enableDowngrade); err != nil { return ups, err } _ = ae.handle.TransGetAdd().ForEach(func(pkg alpm.Package) error { localVer := "-" reason := alpm.PkgReasonExplicit if localPkg := localDB.Pkg(pkg.Name()); localPkg != nil { localVer = localPkg.Version() reason = localPkg.Reason() } ups[pkg.Name()] = db.SyncUpgrade{ Package: pkg, Reason: reason, LocalVersion: localVer, } return nil }) return ups, errReturn } func (ae *AlpmExecutor) BiggestPackages() []alpm.Package { return append([]alpm.Package{}, ae.localDB.PkgCache().SortBySize()...) } func (ae *AlpmExecutor) LastBuildTime() time.Time { var lastTime time.Time for _, db := range ae.syncDBs() { _ = db.PkgCache().ForEach(func(pkg alpm.Package) error { thisTime := pkg.BuildDate() if thisTime.After(lastTime) { lastTime = thisTime } return nil }) } return lastTime } func (ae *AlpmExecutor) Cleanup() { if ae.handle != nil { if err := ae.handle.Release(); err != nil { fmt.Fprintln(os.Stderr, err) } } } func (ae *AlpmExecutor) Repos() (repos []string) { for _, db := range ae.syncDBs() { repos = append(repos, db.Name()) } return } func alpmSetArchitecture(alpmHandle alpm.Handle, arch []string) error { return alpmHandle.SetArchitectures(arch) } func (ae *AlpmExecutor) AlpmArchitectures() ([]string, error) { architectures, err := ae.handle.Architectures() return architectures, err } func alpmSetLogCallback(alpmHandle alpm.Handle, cb func(alpm.LogLevel, string)) { // dyalpm uses a different callback mechanism - log callback not easily supported // due to va_list in libalpm. Skip setting log callback. _ = alpmHandle _ = cb } func alpmSetQuestionCallback(alpmHandle alpm.Handle, cb func(alpm.QuestionAny)) error { return alpmHandle.SetQuestionCallbackFunc(func(q alpm.Question) { cb(alpm.QuestionAny{Question: q}) }) } ================================================ FILE: pkg/db/ialpm/alpm_test.go ================================================ //go:build !integration // +build !integration package ialpm import ( "io" "strings" "testing" alpm "github.com/Jguer/dyalpm" "github.com/Morganamilo/go-pacmanconf" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/text" ) func TestAlpmExecutor(t *testing.T) { t.Parallel() pacmanConf := &pacmanconf.Config{ RootDir: "/", DBPath: "/var/lib/pacman/", CacheDir: []string{"/cachedir/", "/another/"}, HookDir: []string{"/hookdir/"}, GPGDir: "/gpgdir/", LogFile: "/logfile", HoldPkg: []string(nil), IgnorePkg: []string{"ignore", "this", "package"}, IgnoreGroup: []string{"ignore", "this", "group"}, Architecture: []string{"8086"}, XferCommand: "", NoUpgrade: []string{"noupgrade"}, NoExtract: []string{"noextract"}, CleanMethod: []string{"KeepInstalled"}, SigLevel: []string{"PackageOptional", "PackageTrustedOnly", "DatabaseOptional", "DatabaseTrustedOnly"}, LocalFileSigLevel: []string(nil), RemoteFileSigLevel: []string(nil), UseSyslog: false, Color: false, UseDelta: 0, TotalDownload: true, CheckSpace: true, VerbosePkgLists: true, DisableDownloadTimeout: false, Repos: []pacmanconf.Repository{ {Name: "repo1", Servers: []string{"repo1"}, SigLevel: []string(nil), Usage: []string{"All"}}, {Name: "repo2", Servers: []string{"repo2"}, SigLevel: []string(nil), Usage: []string{"All"}}, }, } aExec, err := NewExecutor(pacmanConf, text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test")) assert.NoError(t, err) assert.NotNil(t, aExec.conf) assert.EqualValues(t, pacmanConf, aExec.conf) assert.NotNil(t, aExec.localDB) assert.NotNil(t, aExec.syncDB) assert.NotNil(t, aExec.questionCallback) h := aExec.handle assert.NotNil(t, h) root := h.Root() assert.Nil(t, err) assert.Equal(t, "/", root) dbPath := h.DBPath() assert.Nil(t, err) assert.Equal(t, "/var/lib/pacman/", dbPath) cache, err := h.CacheDirs() assert.Nil(t, err) assert.Equal(t, []string{"/cachedir/", "/another/"}, cache) log := h.LogFile() assert.Nil(t, err) assert.Equal(t, "/logfile", log) gpg := h.GPGDir() assert.Nil(t, err) assert.Equal(t, "/gpgdir/", gpg) hook, err := h.HookDirs() assert.Nil(t, err) assert.Equal(t, []string{"/usr/share/libalpm/hooks/", "/hookdir/"}, hook) arch, err := alpmTestGetArch(h) assert.Nil(t, err) assert.Equal(t, []string{"8086"}, arch) ignorePkg, err := h.IgnorePkgs() assert.Nil(t, err) assert.Equal(t, []string{"ignore", "this", "package"}, ignorePkg) ignoreGroup, err := h.IgnoreGroups() assert.Nil(t, err) assert.Equal(t, []string{"ignore", "this", "group"}, ignoreGroup) noUp, err := h.NoUpgrades() assert.Nil(t, err) assert.Equal(t, []string{"noupgrade"}, noUp) noEx, err := h.NoExtracts() assert.Nil(t, err) assert.Equal(t, []string{"noextract"}, noEx) check := h.CheckSpace() assert.Nil(t, err) assert.Equal(t, true, check) } func alpmTestGetArch(h alpm.Handle) ([]string, error) { architectures, err := h.Architectures() return architectures, err } ================================================ FILE: pkg/db/ialpm/high_level.go ================================================ package ialpm import ( alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/text" ) // GetPackageNamesBySource returns package names with and without correspondence in SyncDBS respectively. func (ae *AlpmExecutor) getPackageNamesBySource() { if ae.installedRemotePkgMap == nil { ae.installedRemotePkgMap = map[string]alpm.Package{} } for _, localpkg := range ae.LocalPackages() { pkgName := localpkg.Name() if ae.SyncPackage(pkgName) != nil { ae.installedSyncPkgNames = append(ae.installedSyncPkgNames, pkgName) } else { ae.installedRemotePkgNames = append(ae.installedRemotePkgNames, pkgName) ae.installedRemotePkgMap[pkgName] = localpkg } } ae.log.Debugln("populating db executor package caches.", "sync_len", len(ae.installedSyncPkgNames), "remote_len", len(ae.installedRemotePkgNames)) } func (ae *AlpmExecutor) InstalledRemotePackages() map[string]alpm.Package { if ae.installedRemotePkgMap == nil { ae.getPackageNamesBySource() } return ae.installedRemotePkgMap } func (ae *AlpmExecutor) InstalledRemotePackageNames() []string { if ae.installedRemotePkgNames == nil { ae.getPackageNamesBySource() } return ae.installedRemotePkgNames } func (ae *AlpmExecutor) InstalledSyncPackageNames() []string { if ae.installedSyncPkgNames == nil { ae.getPackageNamesBySource() } return ae.installedSyncPkgNames } func (ae *AlpmExecutor) SetLogger(logger *text.Logger) { ae.log = logger } ================================================ FILE: pkg/db/mock/executor.go ================================================ package mock import ( "time" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/text" alpm "github.com/Jguer/dyalpm" ) type ( IPackage = alpm.Package Depend = alpm.Depend Upgrade = db.Upgrade ) type DBExecutor struct { db.Executor AlpmArchitecturesFn func() ([]string, error) InstalledRemotePackageNamesFn func() []string InstalledRemotePackagesFn func() map[string]IPackage IsCorrectVersionInstalledFn func(string, string) bool LocalPackageFn func(string) IPackage LocalPackagesFn func() []IPackage LocalSatisfierExistsFn func(string) bool PackageDependsFn func(IPackage) []Depend PackageOptionalDependsFn func(alpm.Package) []alpm.Depend PackageProvidesFn func(IPackage) []Depend PackagesFromGroupFn func(string) []IPackage PackagesFromGroupAndDBFn func(string, string) ([]IPackage, error) RefreshHandleFn func() error ReposFn func() []string SyncPackageFn func(string) IPackage SyncPackagesFn func(...string) []IPackage SyncSatisfierFn func(string) IPackage SatisfierFromDBFn func(string, string) (IPackage, error) SyncUpgradesFn func(bool) (map[string]db.SyncUpgrade, error) SetLoggerFn func(*text.Logger) } func (t *DBExecutor) InstalledRemotePackageNames() []string { if t.InstalledRemotePackageNamesFn != nil { return t.InstalledRemotePackageNamesFn() } panic("implement me") } func (t *DBExecutor) InstalledRemotePackages() map[string]IPackage { if t.InstalledRemotePackagesFn != nil { return t.InstalledRemotePackagesFn() } panic("implement me") } func (t *DBExecutor) AlpmArchitectures() ([]string, error) { if t.AlpmArchitecturesFn != nil { return t.AlpmArchitecturesFn() } panic("implement me") } func (t *DBExecutor) BiggestPackages() []IPackage { panic("implement me") } func (t *DBExecutor) Cleanup() { panic("implement me") } func (t *DBExecutor) IsCorrectVersionInstalled(s, s2 string) bool { if t.IsCorrectVersionInstalledFn != nil { return t.IsCorrectVersionInstalledFn(s, s2) } panic("implement me") } func (t *DBExecutor) LastBuildTime() time.Time { panic("implement me") } func (t *DBExecutor) LocalPackage(s string) IPackage { if t.LocalPackageFn != nil { return t.LocalPackageFn(s) } panic("implement me") } func (t *DBExecutor) LocalPackages() []IPackage { if t.LocalPackagesFn != nil { return t.LocalPackagesFn() } panic("implement me") } func (t *DBExecutor) LocalSatisfierExists(s string) bool { if t.LocalSatisfierExistsFn != nil { return t.LocalSatisfierExistsFn(s) } panic("implement me") } func (t *DBExecutor) PackageConflicts(iPackage IPackage) []Depend { panic("implement me") } func (t *DBExecutor) PackageDepends(iPackage IPackage) []Depend { if t.PackageDependsFn != nil { return t.PackageDependsFn(iPackage) } panic("implement me") } func (t *DBExecutor) PackageGroups(iPackage IPackage) []string { return []string{} } func (t *DBExecutor) PackageOptionalDepends(iPackage IPackage) []Depend { if t.PackageOptionalDependsFn != nil { return t.PackageOptionalDependsFn(iPackage) } panic("implement me") } func (t *DBExecutor) PackageProvides(iPackage IPackage) []Depend { if t.PackageProvidesFn != nil { return t.PackageProvidesFn(iPackage) } panic("implement me") } func (t *DBExecutor) PackagesFromGroup(s string) []IPackage { if t.PackagesFromGroupFn != nil { return t.PackagesFromGroupFn(s) } panic("implement me") } func (t *DBExecutor) PackagesFromGroupAndDB(s, s2 string) ([]IPackage, error) { if t.PackagesFromGroupAndDBFn != nil { return t.PackagesFromGroupAndDBFn(s, s2) } panic("implement me") } func (t *DBExecutor) RefreshHandle() error { if t.RefreshHandleFn != nil { return t.RefreshHandleFn() } panic("implement me") } func (t *DBExecutor) SyncUpgrades(b bool) (map[string]db.SyncUpgrade, error) { if t.SyncUpgradesFn != nil { return t.SyncUpgradesFn(b) } panic("implement me") } func (t *DBExecutor) Repos() []string { if t.ReposFn != nil { return t.ReposFn() } // Tests that don't care about repo ordering shouldn't need to stub this out. return nil } func (t *DBExecutor) SatisfierFromDB(s, s2 string) (IPackage, error) { if t.SatisfierFromDBFn != nil { return t.SatisfierFromDBFn(s, s2) } panic("implement me") } func (t *DBExecutor) SyncPackage(s string) IPackage { if t.SyncPackageFn != nil { return t.SyncPackageFn(s) } panic("implement me") } func (t *DBExecutor) SyncPackages(s ...string) []IPackage { if t.SyncPackagesFn != nil { return t.SyncPackagesFn(s...) } panic("implement me") } func (t *DBExecutor) SyncSatisfier(s string) IPackage { if t.SyncSatisfierFn != nil { return t.SyncSatisfierFn(s) } panic("implement me") } func (t *DBExecutor) SyncSatisfierExists(s string) bool { if t.SyncSatisfierFn != nil { return t.SyncSatisfierFn(s) != nil } panic("implement me") } func (t *DBExecutor) SetLogger(logger *text.Logger) { if t.SetLoggerFn != nil { t.SetLoggerFn(logger) return } panic("implement me") } ================================================ FILE: pkg/db/mock/repo.go ================================================ package mock import ( "io" "time" alpm "github.com/Jguer/dyalpm" ) // DependList is a lightweight helper for test fixtures. type DependList struct { Depends []alpm.Depend } type Package struct { PBase string PBuildDate time.Time PDB *DB PDescription string PISize int64 PName string PShouldIgnore bool PSize int64 PVersion string PReason alpm.PkgReason PDepends DependList PProvides DependList PArchitecture string } var _ alpm.Package = (*Package)(nil) func (p *Package) Base() string { return p.PBase } func (p *Package) BuildDate() time.Time { return p.PBuildDate } func (p *Package) DB() alpm.Database { return p.PDB } func (p *Package) Description() string { return p.PDescription } func (p *Package) ISize() int64 { return p.PISize } func (p *Package) Name() string { return p.PName } func (p *Package) ShouldIgnore() bool { return p.PShouldIgnore } func (p *Package) Size() int64 { return p.PSize } func (p *Package) Version() string { return p.PVersion } func (p *Package) Reason() alpm.PkgReason { return p.PReason } func (p *Package) FileName() string { panic("not implemented") } func (p *Package) Base64Signature() string { panic("not implemented") } func (p *Package) Validation() alpm.Validation { panic("not implemented") } // Architecture returns the package target Architecture. func (p *Package) Architecture() string { return p.PArchitecture } // Backup returns a list of package backups. func (p *Package) Backup() []alpm.Backup { panic("not implemented") } // Conflicts returns the conflicts of the package as a DependList. func (p *Package) Conflicts() []alpm.Depend { panic("not implemented") } // Depends returns the package's dependency list. func (p *Package) Depends() []alpm.Depend { return p.PDepends.Depends } // Depends returns the package's optional dependency list. func (p *Package) OptionalDepends() []alpm.Depend { panic("not implemented") } // Depends returns the package's check dependency list. func (p *Package) CheckDepends() []alpm.Depend { panic("not implemented") } // Depends returns the package's make dependency list. func (p *Package) MakeDepends() []alpm.Depend { panic("not implemented") } // Files returns the file list of the package. func (p *Package) Files() []alpm.File { panic("not implemented") } // ContainsFile checks if the path is in the package filelist. func (p *Package) ContainsFile(path string) (alpm.File, error) { panic("not implemented") } // Groups returns the groups the package belongs to. func (p *Package) Groups() []string { panic("not implemented") } // InstallDate returns the package install date. func (p *Package) InstallDate() time.Time { panic("not implemented") } // Licenses returns the package license list. func (p *Package) Licenses() []string { panic("not implemented") } // SHA256Sum returns package SHA256Sum. func (p *Package) SHA256Sum() string { panic("not implemented") } // Packager returns package packager name. func (p *Package) Packager() string { panic("not implemented") } // Provides returns DependList of packages provides by package. func (p *Package) Provides() []alpm.Depend { return p.PProvides.Depends } // Origin returns package origin. func (p *Package) Origin() alpm.PkgFrom { panic("not implemented") } // Replaces returns a DependList with the packages this package replaces. func (p *Package) Replaces() []alpm.Depend { panic("not implemented") } // URL returns the upstream URL of the package. func (p *Package) URL() string { panic("not implemented") } // ComputeRequiredBy returns the names of reverse dependencies of a package. func (p *Package) ComputeRequiredBy() ([]string, error) { panic("not implemented") } // ComputeOptionalFor returns the names of packages that optionally // require the given package. func (p *Package) ComputeOptionalFor() ([]string, error) { panic("not implemented") } // SyncNewVersion checks if there is a new version of the // package in a given DBlist. func (p *Package) SyncNewVersion(dbs []alpm.Database) alpm.Package { panic("not implemented") } func (p *Package) Type() string { panic("not implemented") } func (p *Package) CheckMD5Sum() error { panic("not implemented") } func (p *Package) CheckPGPSignature() (alpm.SigList, error) { panic("not implemented") } func (p *Package) Contains(path string) bool { panic("not implemented") } func (p *Package) Free() error { return nil } // New methods required by dyalpm refactoring func (p *Package) HasScriptlet() bool { return false } func (p *Package) DownloadSize() int64 { return 0 } func (p *Package) NativeHandle() alpm.Handle { return nil } func (p *Package) Sig() string { return "" } func (p *Package) PkgValidation() alpm.PkgValidation { return alpm.PkgValidationUnknown } func (p *Package) XData() string { return "" } func (p *Package) Changelog() (io.ReadCloser, error) { return nil, nil } func (p *Package) SyncGetNewVersion(dbsSync []alpm.Database) alpm.Package { return nil } type DB struct { alpm.Database name string } func NewDB(name string) *DB { return &DB{name: name} } func (d *DB) Name() string { return d.name } func (d *DB) Pkg(name string) alpm.Package { return nil } func (d *DB) PkgCache() alpm.PackageIterator { return alpm.PackageIterator{} } func (d *DB) Search(needles []string) alpm.PackageIterator { return alpm.PackageIterator{} } func (d *DB) SetServers(servers []string) error { return nil } func (d *DB) SetUsage(usage int) error { return nil } ================================================ FILE: pkg/db/types.go ================================================ package db import "slices" func ArchIsSupported(alpmArch []string, arch string) bool { if arch == "any" { return true } return slices.Contains(alpmArch, arch) } ================================================ FILE: pkg/dep/dep.go ================================================ package dep import ( "strings" "github.com/Jguer/yay/v12/pkg/db" aur "github.com/Jguer/yay/v12/pkg/query" ) func splitDep(dep string) (pkg, mod, ver string) { split := strings.FieldsFunc(dep, func(c rune) bool { match := c == '>' || c == '<' || c == '=' if match { mod += string(c) } return match }) if len(split) == 0 { return "", "", "" } 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, pkgVersion string) bool { depName, depMod, depVersion := splitDep(dep) provideName, provideMod, provideVersion := splitDep(provide) if provideName != depName { return false } // Unversioned provides can not satisfy a versioned dep if provideMod == "" && depMod != "" { provideVersion = pkgVersion // Example package: pagure } return verSatisfies(provideVersion, depMod, depVersion) } func verSatisfies(ver1, mod, ver2 string) bool { switch mod { case "=": return db.VerCmp(ver1, ver2) == 0 case "<": return db.VerCmp(ver1, ver2) < 0 case "<=": return db.VerCmp(ver1, ver2) <= 0 case ">": return db.VerCmp(ver1, ver2) > 0 case ">=": return db.VerCmp(ver1, ver2) >= 0 } return true } func satisfiesAur(dep string, pkg *aur.Pkg) bool { if pkgSatisfies(pkg.Name, pkg.Version, dep) { return true } for _, provide := range pkg.Provides { if provideSatisfies(provide, dep, pkg.Version) { return true } } return false } ================================================ FILE: pkg/dep/dep_graph.go ================================================ package dep import ( "context" "fmt" "strconv" "strings" aurc "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep/topo" "github.com/Jguer/yay/v12/pkg/intrange" aur "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) type InstallInfo struct { Source Source Reason Reason Version string LocalVersion string SrcinfoPath *string AURBase *string SyncDBName *string IsGroup bool Upgrade bool Devel bool } func (i *InstallInfo) String() string { return fmt.Sprintf("InstallInfo{Source: %v, Reason: %v}", i.Source, i.Reason) } type ( Reason int Source int ) func (r Reason) String() string { return ReasonNames[r] } func (s Source) String() string { return SourceNames[s] } const ( Explicit Reason = iota // 0 Dep // 1 MakeDep // 2 CheckDep // 3 ) var ReasonNames = map[Reason]string{ Explicit: gotext.Get("Explicit"), Dep: gotext.Get("Dependency"), MakeDep: gotext.Get("Make Dependency"), CheckDep: gotext.Get("Check Dependency"), } const ( AUR Source = iota Sync Local SrcInfo Missing ) var SourceNames = map[Source]string{ AUR: gotext.Get("AUR"), Sync: gotext.Get("Sync"), Local: gotext.Get("Local"), SrcInfo: gotext.Get("SRCINFO"), Missing: gotext.Get("Missing"), } var bgColorMap = map[Source]string{ AUR: "lightblue", Sync: "lemonchiffon", Local: "darkolivegreen1", Missing: "tomato", } var colorMap = map[Reason]string{ Explicit: "black", Dep: "deeppink", MakeDep: "navyblue", CheckDep: "forestgreen", } type Grapher struct { logger *text.Logger providerCache map[string][]aur.Pkg dbExecutor db.Executor aurClient aurc.QueryClient fullGraph bool // If true, the graph will include all dependencies including already installed ones or repo noConfirm bool // If true, the graph will not prompt for confirmation noDeps bool // If true, the graph will not include dependencies noCheckDeps bool // If true, the graph will not include check dependencies needed bool // If true, the graph will only include packages that are not installed } func NewGrapher(dbExecutor db.Executor, aurCache aurc.QueryClient, fullGraph, noConfirm, noDeps, noCheckDeps, needed bool, logger *text.Logger, ) *Grapher { return &Grapher{ dbExecutor: dbExecutor, aurClient: aurCache, fullGraph: fullGraph, noConfirm: noConfirm, noDeps: noDeps, noCheckDeps: noCheckDeps, needed: needed, providerCache: make(map[string][]aurc.Pkg, 5), logger: logger, } } func NewGraph() *topo.Graph[string, *InstallInfo] { return topo.New[string, *InstallInfo]() } func (g *Grapher) GraphFromTargets(ctx context.Context, graph *topo.Graph[string, *InstallInfo], targets []string, ) (*topo.Graph[string, *InstallInfo], error) { if graph == nil { graph = NewGraph() } aurTargets := make([]string, 0, len(targets)) for _, targetString := range targets { target := ToTarget(targetString) switch target.DB { case "": // unspecified db if pkg := g.dbExecutor.SyncSatisfier(target.Name); pkg != nil { g.GraphSyncPkg(ctx, graph, pkg, nil) continue } groupPackages := g.dbExecutor.PackagesFromGroup(target.Name) if len(groupPackages) > 0 { dbName := groupPackages[0].DB().Name() g.GraphSyncGroup(ctx, graph, target.Name, dbName) continue } fallthrough case "aur": aurTargets = append(aurTargets, target.Name) default: pkg, err := g.dbExecutor.SatisfierFromDB(target.Name, target.DB) if err != nil { return nil, err } if pkg != nil { g.GraphSyncPkg(ctx, graph, pkg, nil) continue } groupPackages, err := g.dbExecutor.PackagesFromGroupAndDB(target.Name, target.DB) if err != nil { return nil, err } if len(groupPackages) > 0 { g.GraphSyncGroup(ctx, graph, target.Name, target.DB) continue } g.logger.Errorln(gotext.Get("No package found for"), " ", target) } } var errA error graph, errA = g.GraphFromAUR(ctx, graph, aurTargets) if errA != nil { return nil, errA } return graph, nil } func (g *Grapher) pickSrcInfoPkgs(pkgs []*aurc.Pkg) ([]*aurc.Pkg, error) { final := make([]*aurc.Pkg, 0, len(pkgs)) for i := range pkgs { g.logger.Println(text.Magenta(strconv.Itoa(i+1)+" ") + text.Bold(pkgs[i].Name) + " " + text.Cyan(pkgs[i].Version)) g.logger.Println(" " + pkgs[i].Description) } g.logger.Infoln(gotext.Get("Packages to exclude") + " (eg: \"1 2 3\", \"1-3\", \"^4\"):") numberBuf, err := g.logger.GetInput("", g.noConfirm) if err != nil { return nil, err } include, exclude, _, otherExclude := intrange.ParseNumberMenu(numberBuf) isInclude := len(exclude) == 0 && otherExclude.Cardinality() == 0 for i := 1; i <= len(pkgs); i++ { target := i - 1 if isInclude && !include.Get(i) { final = append(final, pkgs[target]) } if !isInclude && (exclude.Get(i)) { final = append(final, pkgs[target]) } } return final, nil } func (g *Grapher) addAurPkgProvides(pkg *aurc.Pkg, graph *topo.Graph[string, *InstallInfo]) { for i := range pkg.Provides { depName, mod, version := splitDep(pkg.Provides[i]) g.logger.Debugln(pkg.String() + " provides: " + depName) graph.AddProvides(depName, &alpm.Depend{ Name: depName, Version: version, Mod: aurDepModToAlpmDep(mod), }, pkg.Name) } } func (g *Grapher) GraphFromSrcInfos(ctx context.Context, graph *topo.Graph[string, *InstallInfo], srcInfos map[string]*gosrc.Srcinfo, ) (*topo.Graph[string, *InstallInfo], error) { if graph == nil { graph = NewGraph() } aurPkgsAdded := []*aurc.Pkg{} for pkgBuildDir, pkgbuild := range srcInfos { aurPkgs, err := makeAURPKGFromSrcinfo(g.dbExecutor, pkgbuild) if err != nil { return nil, err } if len(aurPkgs) > 1 { var errPick error aurPkgs, errPick = g.pickSrcInfoPkgs(aurPkgs) if errPick != nil { return nil, errPick } } for _, pkg := range aurPkgs { reason := Explicit if pkg := g.dbExecutor.LocalPackage(pkg.Name); pkg != nil { reason = Reason(pkg.Reason()) } graph.AddNode(pkg.Name) g.addAurPkgProvides(pkg, graph) g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{ Color: colorMap[reason], Background: bgColorMap[AUR], Value: &InstallInfo{ Source: SrcInfo, Reason: reason, SrcinfoPath: &pkgBuildDir, AURBase: &pkg.PackageBase, Version: pkg.Version, }, }) } aurPkgsAdded = append(aurPkgsAdded, aurPkgs...) } g.AddDepsForPkgs(ctx, aurPkgsAdded, graph) return graph, nil } func (g *Grapher) AddDepsForPkgs(ctx context.Context, pkgs []*aur.Pkg, graph *topo.Graph[string, *InstallInfo]) { for _, pkg := range pkgs { g.addDepNodes(ctx, pkg, graph) } } func (g *Grapher) addDepNodes(ctx context.Context, pkg *aur.Pkg, graph *topo.Graph[string, *InstallInfo]) { if len(pkg.MakeDepends) > 0 { g.addNodes(ctx, graph, pkg.Name, pkg.MakeDepends, MakeDep) } if !g.noDeps && len(pkg.Depends) > 0 { g.addNodes(ctx, graph, pkg.Name, pkg.Depends, Dep) } if !g.noCheckDeps && !g.noDeps && len(pkg.CheckDepends) > 0 { g.addNodes(ctx, graph, pkg.Name, pkg.CheckDepends, CheckDep) } } func (g *Grapher) GraphSyncPkg(ctx context.Context, graph *topo.Graph[string, *InstallInfo], pkg alpm.Package, upgradeInfo *db.SyncUpgrade, ) *topo.Graph[string, *InstallInfo] { if graph == nil { graph = NewGraph() } graph.AddNode(pkg.Name()) provides := pkg.Provides() for i := range provides { p := &provides[i] g.logger.Debugln(pkg.Name() + " provides: " + p.String()) graph.AddProvides(p.Name, p, pkg.Name()) } dbName := pkg.DB().Name() info := &InstallInfo{ Source: Sync, Reason: Explicit, Version: pkg.Version(), SyncDBName: &dbName, } if upgradeInfo == nil { if localPkg := g.dbExecutor.LocalPackage(pkg.Name()); localPkg != nil { info.Reason = Reason(localPkg.Reason()) } } else { info.Upgrade = true info.Reason = Reason(upgradeInfo.Reason) info.LocalVersion = upgradeInfo.LocalVersion } g.ValidateAndSetNodeInfo(graph, pkg.Name(), &topo.NodeInfo[*InstallInfo]{ Color: colorMap[info.Reason], Background: bgColorMap[info.Source], Value: info, }) return graph } func (g *Grapher) GraphSyncGroup(ctx context.Context, graph *topo.Graph[string, *InstallInfo], groupName, dbName string, ) *topo.Graph[string, *InstallInfo] { if graph == nil { graph = NewGraph() } graph.AddNode(groupName) g.ValidateAndSetNodeInfo(graph, groupName, &topo.NodeInfo[*InstallInfo]{ Color: colorMap[Explicit], Background: bgColorMap[Sync], Value: &InstallInfo{ Source: Sync, Reason: Explicit, Version: "", SyncDBName: &dbName, IsGroup: true, }, }) return graph } func (g *Grapher) GraphAURTarget(ctx context.Context, graph *topo.Graph[string, *InstallInfo], pkg *aurc.Pkg, instalInfo *InstallInfo, ) *topo.Graph[string, *InstallInfo] { if graph == nil { graph = NewGraph() } graph.AddNode(pkg.Name) g.addAurPkgProvides(pkg, graph) g.ValidateAndSetNodeInfo(graph, pkg.Name, &topo.NodeInfo[*InstallInfo]{ Color: colorMap[instalInfo.Reason], Background: bgColorMap[AUR], Value: instalInfo, }) return graph } func (g *Grapher) GraphFromAUR(ctx context.Context, graph *topo.Graph[string, *InstallInfo], targets []string, ) (*topo.Graph[string, *InstallInfo], error) { if graph == nil { graph = NewGraph() } if len(targets) == 0 { return graph, nil } aurPkgs, errCache := g.aurClient.Get(ctx, &aurc.Query{By: aurc.Name, Needles: targets}) if errCache != nil { g.logger.Errorln(errCache) } for i := range aurPkgs { pkg := &aurPkgs[i] if _, ok := g.providerCache[pkg.Name]; !ok { g.providerCache[pkg.Name] = []aurc.Pkg{*pkg} } } aurPkgsAdded := []*aurc.Pkg{} var packagesNotFound int for _, target := range targets { if cachedProvidePkg, ok := g.providerCache[target]; ok { aurPkgs = cachedProvidePkg } else { var errA error aurPkgs, errA = g.aurClient.Get(ctx, &aurc.Query{By: aurc.Provides, Needles: []string{target}, Contains: true}) if errA != nil { g.logger.Errorln(gotext.Get("Failed to find AUR package for"), " ", target, ":", errA) } } if len(aurPkgs) == 0 { g.logger.Errorln(gotext.Get("No AUR package found for"), " ", target) packagesNotFound++ continue } aurPkg := &aurPkgs[0] if len(aurPkgs) > 1 { chosen := g.provideMenu(target, aurPkgs) aurPkg = chosen g.providerCache[target] = []aurc.Pkg{*aurPkg} } reason := Explicit if pkg := g.dbExecutor.LocalPackage(aurPkg.Name); pkg != nil { reason = Reason(pkg.Reason()) if g.needed { if db.VerCmp(pkg.Version(), aurPkg.Version) >= 0 { g.logger.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(pkg.Name()+"-"+pkg.Version()))) continue } } } graph = g.GraphAURTarget(ctx, graph, aurPkg, &InstallInfo{ AURBase: &aurPkg.PackageBase, Reason: reason, Source: AUR, Version: aurPkg.Version, }) aurPkgsAdded = append(aurPkgsAdded, aurPkg) } g.AddDepsForPkgs(ctx, aurPkgsAdded, graph) if packagesNotFound == len(targets) { return graph, &aur.ErrTargetNotFound{} } return graph, nil } // Removes found deps from the deps mapset and returns the found deps. func (g *Grapher) findDepsFromAUR(ctx context.Context, graph *topo.Graph[string, *InstallInfo], parentPkgName string, deps mapset.Set[string], ) []aurc.Pkg { pkgsToAdd := make([]aurc.Pkg, 0, deps.Cardinality()) if deps.Cardinality() == 0 { return []aurc.Pkg{} } missingNeedles := make([]string, 0, deps.Cardinality()) for _, depString := range deps.ToSlice() { if _, ok := g.providerCache[depString]; !ok { depName, _, _ := splitDep(depString) missingNeedles = append(missingNeedles, depName) } } if len(missingNeedles) != 0 { g.logger.Debugln("deps to find", missingNeedles) // provider search is more demanding than a simple search // try to find name match if possible and then try to find provides. aurPkgs, errCache := g.aurClient.Get(ctx, &aurc.Query{ By: aurc.Name, Needles: missingNeedles, Contains: false, }) if errCache != nil { g.logger.Errorln(errCache) } for i := range aurPkgs { pkg := &aurPkgs[i] // Cache by the full depString (including version) for each dep whose name matches for _, depString := range deps.ToSlice() { depName, _, _ := splitDep(depString) if depName == pkg.Name { g.providerCache[depString] = append(g.providerCache[depString], *pkg) } } for _, val := range pkg.Provides { if val == pkg.Name { continue } // Also check provides against versioned deps provideName, _, _ := splitDep(val) for _, depString := range deps.ToSlice() { depName, _, _ := splitDep(depString) if depName == provideName { g.providerCache[depString] = append(g.providerCache[depString], *pkg) } } } } } for _, depString := range deps.ToSlice() { var aurPkgs []aurc.Pkg depName, _, _ := splitDep(depString) if cachedProvidePkg, ok := g.providerCache[depString]; ok { aurPkgs = cachedProvidePkg } else { var errA error aurPkgs, errA = g.aurClient.Get(ctx, &aurc.Query{By: aurc.Provides, Needles: []string{depName}, Contains: true}) if errA != nil { g.logger.Errorln(gotext.Get("Failed to find AUR package for"), depString, ":", errA) } } // remove packages that don't satisfy the dependency satisfyingPkgs := make([]aurc.Pkg, 0, len(aurPkgs)) for i := range aurPkgs { if satisfiesAur(depString, &aurPkgs[i]) { satisfyingPkgs = append(satisfyingPkgs, aurPkgs[i]) } } aurPkgs = satisfyingPkgs if len(aurPkgs) == 0 { // set of packages that require this dependency requiredBySet := mapset.NewThreadUnsafeSet[string]() // add current parent requiredBySet.Add(parentPkgName) // if dependency is already in graph, get all packages that require it if graph.Exists(depName) { if deps := graph.Dependents(depName); deps != nil { for parent := range deps { requiredBySet.Add(parent) } } } requiredBySlice := requiredBySet.ToSlice() requiredByStr := strings.Join(requiredBySlice, ", ") g.logger.Errorln(gotext.Get("No AUR package found for"), " ", depString, " (", gotext.Get("required by"), ": ", requiredByStr, ")") continue } pkg := aurPkgs[0] if len(aurPkgs) > 1 { chosen := g.provideMenu(depString, aurPkgs) pkg = *chosen } g.providerCache[depString] = []aurc.Pkg{pkg} deps.Remove(depString) pkgsToAdd = append(pkgsToAdd, pkg) } return pkgsToAdd } func (g *Grapher) ValidateAndSetNodeInfo(graph *topo.Graph[string, *InstallInfo], node string, nodeInfo *topo.NodeInfo[*InstallInfo], ) { info := graph.GetNodeInfo(node) if info != nil && info.Value != nil { if info.Value.Reason < nodeInfo.Value.Reason { return // refuse to downgrade reason } if info.Value.Upgrade { return // refuse to overwrite an upgrade } } graph.SetNodeInfo(node, nodeInfo) } func (g *Grapher) addNodes( ctx context.Context, graph *topo.Graph[string, *InstallInfo], parentPkgName string, deps []string, depType Reason, ) { targetsToFind := mapset.NewThreadUnsafeSet(deps...) // Check if in graph already for _, depString := range targetsToFind.ToSlice() { depName, _, _ := splitDep(depString) if !graph.Exists(depName) && !graph.HasProvides(depName) { continue } if graph.Exists(depName) { if err := graph.DependOn(depName, parentPkgName); err != nil { g.logger.Warnln(depString, parentPkgName, err) } targetsToFind.Remove(depString) } if p := graph.GetProviderInfo(depName); p != nil { if provideSatisfies(p.String(), depString, p.Version) { if err := graph.DependOn(p.Provider, parentPkgName); err != nil { g.logger.Warnln(p.Provider, parentPkgName, err) } targetsToFind.Remove(depString) } } } // Check installed for _, depString := range targetsToFind.ToSlice() { depName, _, _ := splitDep(depString) if !g.dbExecutor.LocalSatisfierExists(depString) { continue } if g.fullGraph { g.ValidateAndSetNodeInfo( graph, depName, &topo.NodeInfo[*InstallInfo]{Color: colorMap[depType], Background: bgColorMap[Local]}) if err := graph.DependOn(depName, parentPkgName); err != nil { g.logger.Warnln(depName, parentPkgName, err) } } targetsToFind.Remove(depString) } // Check Sync for _, depString := range targetsToFind.ToSlice() { alpmPkg := g.dbExecutor.SyncSatisfier(depString) if alpmPkg == nil { continue } if err := graph.DependOn(alpmPkg.Name(), parentPkgName); err != nil { g.logger.Warnln("repo dep warn:", depString, parentPkgName, err) } dbName := alpmPkg.DB().Name() g.ValidateAndSetNodeInfo( graph, alpmPkg.Name(), &topo.NodeInfo[*InstallInfo]{ Color: colorMap[depType], Background: bgColorMap[Sync], Value: &InstallInfo{ Source: Sync, Reason: depType, Version: alpmPkg.Version(), SyncDBName: &dbName, }, }) if newDeps := alpmPkg.Depends(); len(newDeps) != 0 && g.fullGraph { newDepsSlice := make([]string, 0, len(newDeps)) for _, newDep := range newDeps { newDepsSlice = append(newDepsSlice, newDep.Name) } g.addNodes(ctx, graph, alpmPkg.Name(), newDepsSlice, Dep) } targetsToFind.Remove(depString) } // Check AUR pkgsToAdd := g.findDepsFromAUR(ctx, graph, parentPkgName, targetsToFind) for i := range pkgsToAdd { aurPkg := &pkgsToAdd[i] if err := graph.DependOn(aurPkg.Name, parentPkgName); err != nil { g.logger.Warnln("aur dep warn:", aurPkg.Name, parentPkgName, err) } graph.SetNodeInfo( aurPkg.Name, &topo.NodeInfo[*InstallInfo]{ Color: colorMap[depType], Background: bgColorMap[AUR], Value: &InstallInfo{ Source: AUR, Reason: depType, AURBase: &aurPkg.PackageBase, Version: aurPkg.Version, }, }) g.addDepNodes(ctx, aurPkg, graph) } // Add missing to graph for _, depString := range targetsToFind.ToSlice() { depName, mod, ver := splitDep(depString) // no dep found. add as missing if err := graph.DependOn(depName, parentPkgName); err != nil { g.logger.Warnln("missing dep warn:", depString, parentPkgName, err) } graph.SetNodeInfo(depName, &topo.NodeInfo[*InstallInfo]{ Color: colorMap[depType], Background: bgColorMap[Missing], Value: &InstallInfo{ Source: Missing, Reason: depType, Version: fmt.Sprintf("%s%s", mod, ver), }, }) } } func (g *Grapher) provideMenu(dep string, options []aur.Pkg) *aur.Pkg { size := len(options) if size == 1 { return &options[0] } str := text.Bold(gotext.Get("There are %[1]d providers available for %[2]s:", size, dep)) str += "\n" size = 1 str += g.logger.SprintOperationInfo(gotext.Get("Repository AUR"), "\n ") for i := range options { str += fmt.Sprintf("%d) %s ", size, options[i].Name) size++ } g.logger.OperationInfoln(str) for { g.logger.Println(gotext.Get("\nEnter a number (default=1): ")) if g.noConfirm { g.logger.Println("1") return &options[0] } numberBuf, err := g.logger.GetInput("", false) if err != nil { g.logger.Errorln(err) return &options[0] } if numberBuf == "" { return &options[0] } num, err := strconv.Atoi(numberBuf) if err != nil { g.logger.Errorln(gotext.Get("invalid number: %s", numberBuf)) continue } if num < 1 || num >= size { g.logger.Errorln(gotext.Get("invalid value: %d is not between %d and %d", num, 1, size-1)) continue } return &options[num-1] } } func makeAURPKGFromSrcinfo(dbExecutor db.Executor, srcInfo *gosrc.Srcinfo) ([]*aur.Pkg, error) { pkgs := make([]*aur.Pkg, 0, 1) alpmArch, err := dbExecutor.AlpmArchitectures() if err != nil { return nil, err } alpmArch = append(alpmArch, "") // srcinfo assumes no value as "" getDesc := func(pkg *gosrc.Package) string { if pkg.Pkgdesc != "" { return pkg.Pkgdesc } return srcInfo.Pkgdesc } for i := range srcInfo.Packages { pkg := &srcInfo.Packages[i] pkgs = append(pkgs, &aur.Pkg{ ID: 0, Name: pkg.Pkgname, PackageBaseID: 0, PackageBase: srcInfo.Pkgbase, Version: srcInfo.Version(), Description: getDesc(pkg), URL: pkg.URL, Depends: append(archStringToString(alpmArch, pkg.Depends), archStringToString(alpmArch, srcInfo.Depends)...), MakeDepends: archStringToString(alpmArch, srcInfo.MakeDepends), CheckDepends: archStringToString(alpmArch, srcInfo.CheckDepends), Conflicts: append(archStringToString(alpmArch, pkg.Conflicts), archStringToString(alpmArch, srcInfo.Conflicts)...), Provides: append(archStringToString(alpmArch, pkg.Provides), archStringToString(alpmArch, srcInfo.Provides)...), Replaces: append(archStringToString(alpmArch, pkg.Replaces), archStringToString(alpmArch, srcInfo.Replaces)...), OptDepends: append(archStringToString(alpmArch, pkg.OptDepends), archStringToString(alpmArch, srcInfo.OptDepends)...), Groups: pkg.Groups, License: pkg.License, Keywords: []string{}, }) } return pkgs, nil } func archStringToString(alpmArches []string, archString []gosrc.ArchString) []string { pkgs := make([]string, 0, len(archString)) for _, arch := range archString { if db.ArchIsSupported(alpmArches, arch.Arch) { pkgs = append(pkgs, arch.Value) } } return pkgs } func aurDepModToAlpmDep(mod string) alpm.DepMod { switch mod { case "=": return alpm.DepModEQ case ">=": return alpm.DepModGE case "<=": return alpm.DepModLE case ">": return alpm.DepModGT case "<": return alpm.DepModLT } return alpm.DepModAny } ================================================ FILE: pkg/dep/dep_graph_bench_test.go ================================================ //go:build !integration // +build !integration package dep import ( "context" "io" "os" "testing" aurc "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" aur "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) // benchCase represents a single benchmark scenario with expected results for validation. type benchCase struct { name string targets []string expectedLayers []map[string]*InstallInfo noDeps bool noCheckDeps bool } // newBenchMockDB creates a mock DB executor for benchmarking gstreamer-git scenarios. func newBenchMockDB() *mock.DBExecutor { return &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git", "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git=1.24.0.r37-1": return nil case "libxml2": return &mock.Package{ PName: "libxml2", PVersion: "2.12.0-1", PDB: mock.NewDB("core"), } case "glib2": return &mock.Package{ PName: "glib2", PVersion: "2.78.0-1", PDB: mock.NewDB("core"), } case "orc": return &mock.Package{ PName: "orc", PVersion: "0.4.34-1", PDB: mock.NewDB("extra"), } case "libxv": return &mock.Package{ PName: "libxv", PVersion: "1.0.12-1", PDB: mock.NewDB("extra"), } case "iso-codes": return &mock.Package{ PName: "iso-codes", PVersion: "4.15.0-1", PDB: mock.NewDB("extra"), } case "libpulse": return &mock.Package{ PName: "libpulse", PVersion: "16.1-1", PDB: mock.NewDB("extra"), } case "wavpack": return &mock.Package{ PName: "wavpack", PVersion: "5.6.0-1", PDB: mock.NewDB("extra"), } } return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "gstreamer-git", "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git", "gst-plugins-base-libs-git=1.24.0.r37-1", "gst-plugins-good-git": return false case "libxml2", "glib2", "orc", "libxv", "iso-codes", "libpulse", "wavpack", "git", "meson", "ninja", "llvm", "clang": return true } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } } // newBenchMockAUR creates a mock AUR client for benchmarking gstreamer-git scenarios. func newBenchMockAUR(t testing.TB) *mockaur.MockAUR { return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 { for _, needle := range query.Needles { if needle == "gstreamer-git" || needle == "gst-plugins-base-libs-git" || needle == "gst-plugins-good-git" { gstFn := getFromFile(t, "testdata/gstreamer-git.json") return gstFn(ctx, query) } } } return []aur.Pkg{}, nil }} } // newJellyfinMockDB creates a mock DB for jellyfin scenarios. func newJellyfinMockDB() *mock.DBExecutor { return &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "jellyfin": return nil case "dotnet-runtime-6.0": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk-6.0": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk-6.0", "dotnet-runtime-6.0", "jellyfin-server=10.8.8", "jellyfin-web=10.8.8": return false } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } } // newJellyfinMockAUR creates a mock AUR for jellyfin scenarios. func newJellyfinMockAUR(t testing.TB) *mockaur.MockAUR { return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) == 0 { return []aur.Pkg{}, nil } switch query.Needles[0] { case "jellyfin": return getFromFile(t, "testdata/jellyfin.json")(ctx, query) case "jellyfin-web": return getFromFile(t, "testdata/jellyfin-web.json")(ctx, query) case "jellyfin-server": return getFromFile(t, "testdata/jellyfin-server.json")(ctx, query) } return []aur.Pkg{}, nil }} } // newCephMockDB creates a mock DB for ceph scenarios with providers. func newCephMockDB() *mock.DBExecutor { return &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "ceph-bin", "ceph-libs-bin", "ceph", "ceph-libs", "ceph-libs=17.2.6-2": return nil } return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "ceph-libs", "ceph-libs=17.2.6-2": return false case "dep1", "dep2", "dep3", "makedep1", "makedep2", "checkdep1": return true } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } } // newCephMockAUR creates a mock AUR for ceph scenarios. func newCephMockAUR() *mockaur.MockAUR { mockPkgs := map[string]aur.Pkg{ "ceph-bin": { Name: "ceph-bin", PackageBase: "ceph-bin", Version: "17.2.6-2", Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, Provides: []string{"ceph=17.2.6-2"}, }, "ceph-libs-bin": { Name: "ceph-libs-bin", PackageBase: "ceph-bin", Version: "17.2.6-2", Depends: []string{"dep1", "dep2"}, Provides: []string{"ceph-libs=17.2.6-2"}, }, "ceph": { Name: "ceph", PackageBase: "ceph", Version: "17.2.6-2", Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, MakeDepends: []string{"makedep1"}, CheckDepends: []string{"checkdep1"}, Provides: []string{"ceph=17.2.6-2"}, }, "ceph-libs": { Name: "ceph-libs", PackageBase: "ceph", Version: "17.2.6-2", Depends: []string{"dep1", "dep2", "dep3"}, MakeDepends: []string{"makedep1", "makedep2"}, CheckDepends: []string{"checkdep1"}, Provides: []string{"ceph-libs=17.2.6-2"}, }, } return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { pkgs := []aur.Pkg{} for _, needle := range query.Needles { if pkg, ok := mockPkgs[needle]; ok { pkgs = append(pkgs, pkg) } } return pkgs, nil }} } // newAndroidSDKMockDB creates a mock DB for android-sdk scenarios. func newAndroidSDKMockDB() *mock.DBExecutor { return &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "android-sdk": return nil case "jdk11-openjdk": return &mock.Package{ PName: "jdk11-openjdk", PVersion: "11.0.12.u7-1", PDB: mock.NewDB("community"), PProvides: mock.DependList{ Depends: []alpm.Depend{ {Name: "java-environment", Version: "11", Mod: alpm.DepModEQ}, {Name: "java-environment-openjdk", Version: "11", Mod: alpm.DepModEQ}, {Name: "jdk11-openjdk", Version: "11.0.19.u7-1", Mod: alpm.DepModEQ}, }, }, } } return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "java-environment": return false case "libxtst", "fontconfig", "freetype2", "lib32-gcc-libs", "lib32-glibc", "libx11", "libxext", "libxrender", "zlib", "gcc-libs": return true } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } } // newAndroidSDKMockAUR creates a mock AUR for android-sdk scenarios. func newAndroidSDKMockAUR(t testing.TB) *mockaur.MockAUR { return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 && query.Needles[0] == "android-sdk" { return getFromFile(t, "testdata/android-sdk.json")(ctx, query) } return []aur.Pkg{}, nil }} } // verifyLayers checks that the actual layers match expected layers. func verifyLayers(t testing.TB, expected, actual []map[string]*InstallInfo) { t.Helper() require.Equal(t, len(expected), len(actual), "layer count mismatch") for i := range expected { require.Equal(t, len(expected[i]), len(actual[i]), "layer %d package count mismatch", i) for name, expectedInfo := range expected[i] { actualInfo, ok := actual[i][name] require.True(t, ok, "missing package %s in layer %d", name, i) require.Equal(t, expectedInfo.Source, actualInfo.Source, "source mismatch for %s", name) require.Equal(t, expectedInfo.Reason, actualInfo.Reason, "reason mismatch for %s", name) require.Equal(t, expectedInfo.Version, actualInfo.Version, "version mismatch for %s", name) if expectedInfo.AURBase != nil { require.NotNil(t, actualInfo.AURBase, "AURBase should not be nil for %s", name) require.Equal(t, *expectedInfo.AURBase, *actualInfo.AURBase, "AURBase mismatch for %s", name) } if expectedInfo.SyncDBName != nil { require.NotNil(t, actualInfo.SyncDBName, "SyncDBName should not be nil for %s", name) require.Equal(t, *expectedInfo.SyncDBName, *actualInfo.SyncDBName, "SyncDBName mismatch for %s", name) } } } } // BenchmarkGraphFromTargets_GstreamerGit benchmarks dependency graph construction // for the gstreamer-git split package scenario. func BenchmarkGraphFromTargets_GstreamerGit(b *testing.B) { cases := []benchCase{ { name: "SingleTarget", targets: []string{"gst-plugins-good-git"}, expectedLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gst-plugins-base-libs-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gstreamer-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, }, }, { name: "TwoTargets", targets: []string{"gstreamer-git", "gst-plugins-good-git"}, expectedLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gst-plugins-base-libs-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gstreamer-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, }, }, { name: "AllThreeExplicit", targets: []string{"gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git"}, expectedLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gst-plugins-base-libs-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, {"gstreamer-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, }, }, } for _, tc := range cases { b.Run(tc.name, func(b *testing.B) { mockDB := newBenchMockDB() mockAUR := newBenchMockAUR(b) logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) // Verify correctness once before benchmarking graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) require.NoError(b, err) layers := graph.TopoSortedLayers(nil) verifyLayers(b, tc.expectedLayers, layers) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) } }) } } // BenchmarkGraphFromTargets_Jellyfin benchmarks dependency graph construction // for the jellyfin package with mixed AUR/sync dependencies. func BenchmarkGraphFromTargets_Jellyfin(b *testing.B) { cases := []benchCase{ { name: "NoDeps", targets: []string{"jellyfin"}, noDeps: true, noCheckDeps: false, expectedLayers: []map[string]*InstallInfo{ {"jellyfin": {Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}}, {"dotnet-sdk-6.0": {Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community")}}, }, }, { name: "WithDeps", targets: []string{"jellyfin"}, noDeps: false, noCheckDeps: false, expectedLayers: []map[string]*InstallInfo{ {"jellyfin": {Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}}, { "jellyfin-web": {Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}, "jellyfin-server": {Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}, }, { "dotnet-sdk-6.0": {Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community")}, "dotnet-runtime-6.0": {Source: Sync, Reason: Dep, Version: "6.0.100-1", SyncDBName: ptrString("community")}, }, }, }, } for _, tc := range cases { b.Run(tc.name, func(b *testing.B) { mockDB := newJellyfinMockDB() mockAUR := newJellyfinMockAUR(b) logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) // Verify correctness once before benchmarking graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) require.NoError(b, err) layers := graph.TopoSortedLayers(nil) verifyLayers(b, tc.expectedLayers, layers) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) } }) } } // BenchmarkGraphFromTargets_CephProvides benchmarks dependency graph construction // for packages with virtual provides (ceph-bin provides ceph-libs). func BenchmarkGraphFromTargets_CephProvides(b *testing.B) { cases := []benchCase{ { name: "CephBinWithLibsBin", targets: []string{"ceph-bin", "ceph-libs-bin"}, expectedLayers: []map[string]*InstallInfo{ {"ceph-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, {"ceph-libs-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, }, }, { name: "CephOnly", targets: []string{"ceph"}, expectedLayers: []map[string]*InstallInfo{ {"ceph": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, {"ceph-libs": {Source: AUR, Reason: Dep, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, }, }, { name: "CephBinOnly", targets: []string{"ceph-bin"}, expectedLayers: []map[string]*InstallInfo{ {"ceph-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, {"ceph-libs": {Source: AUR, Reason: Dep, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, }, }, } for _, tc := range cases { b.Run(tc.name, func(b *testing.B) { mockDB := newCephMockDB() mockAUR := newCephMockAUR() logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) // Verify correctness once before benchmarking graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) require.NoError(b, err) layers := graph.TopoSortedLayers(nil) verifyLayers(b, tc.expectedLayers, layers) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) } }) } } // BenchmarkGraphFromTargets_AndroidSDK benchmarks dependency graph construction // for packages with explicit sync dependencies providing virtual packages. func BenchmarkGraphFromTargets_AndroidSDK(b *testing.B) { tc := benchCase{ name: "WithJDK", targets: []string{"android-sdk", "jdk11-openjdk"}, expectedLayers: []map[string]*InstallInfo{ {"android-sdk": {Source: AUR, Reason: Explicit, Version: "26.1.1-2", AURBase: ptrString("android-sdk")}}, {"jdk11-openjdk": {Source: Sync, Reason: Explicit, Version: "11.0.12.u7-1", SyncDBName: ptrString("community")}}, }, } mockDB := newAndroidSDKMockDB() mockAUR := newAndroidSDKMockAUR(b) logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) // Verify correctness once before benchmarking graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) require.NoError(b, err) layers := graph.TopoSortedLayers(nil) verifyLayers(b, tc.expectedLayers, layers) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) } } // BenchmarkTopoSortedLayers benchmarks the topological sort operation on pre-built graphs. func BenchmarkTopoSortedLayers(b *testing.B) { b.Run("GstreamerGit", func(b *testing.B) { mockDB := newBenchMockDB() mockAUR := newBenchMockAUR(b) logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, logger) graph, err := g.GraphFromTargets(context.Background(), nil, []string{"gst-plugins-good-git"}) require.NoError(b, err) // Verify correctness layers := graph.TopoSortedLayers(nil) require.Len(b, layers, 3) b.ResetTimer() for i := 0; i < b.N; i++ { _ = graph.TopoSortedLayers(nil) } }) b.Run("Jellyfin", func(b *testing.B) { mockDB := newJellyfinMockDB() mockAUR := newJellyfinMockAUR(b) logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, logger) graph, err := g.GraphFromTargets(context.Background(), nil, []string{"jellyfin"}) require.NoError(b, err) // Verify correctness layers := graph.TopoSortedLayers(nil) require.Len(b, layers, 3) b.ResetTimer() for i := 0; i < b.N; i++ { _ = graph.TopoSortedLayers(nil) } }) } // BenchmarkNewGraph benchmarks graph creation overhead. func BenchmarkNewGraph(b *testing.B) { for i := 0; i < b.N; i++ { _ = NewGraph() } } // BenchmarkGraphDependOn benchmarks adding dependency edges. func BenchmarkGraphDependOn(b *testing.B) { b.Run("SmallGraph", func(b *testing.B) { for i := 0; i < b.N; i++ { graph := NewGraph() _ = graph.DependOn("pkg1", "pkg2") _ = graph.DependOn("pkg2", "pkg3") _ = graph.DependOn("pkg3", "pkg4") } }) b.Run("MediumGraph", func(b *testing.B) { for i := 0; i < b.N; i++ { graph := NewGraph() // Create a chain of 20 dependencies for j := 0; j < 20; j++ { _ = graph.DependOn( "pkg"+string(rune('A'+j)), "pkg"+string(rune('A'+j+1)), ) } } }) } ================================================ FILE: pkg/dep/dep_graph_rpc_test.go ================================================ //go:build !integration // +build !integration // Package dep provides tests for tree resolution and parsing using RPC/.SRCINFO metadata // instead of PKGBUILD parsing. These tests validate: // - Reliable parser: Ability to handle complex packages using provided metadata // - Reliable solver: Ability to correctly solve and build complex dependency chains // - Split packages: Ability to correctly build and install split packages package dep import ( "context" "fmt" "io" "os" "testing" aurc "github.com/Jguer/aur" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" aur "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) // TestGrapher_ReliableParser_AWSCliGit tests the reliable parsing capability // for complex packages like aws-cli-git that have many dependencies. // This validates that the RPC metadata is correctly parsed and dependencies // are properly resolved without needing PKGBUILD parsing. func TestGrapher_ReliableParser_AWSCliGit(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "aws-cli-git": return nil case "python": return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")} case "python-botocore>=1.19.35", "python-botocore": return &mock.Package{PName: "python-botocore", PVersion: "1.29.0-1", PDB: mock.NewDB("extra")} case "python-docutils>=0.10", "python-docutils": return &mock.Package{PName: "python-docutils", PVersion: "0.19-1", PDB: mock.NewDB("extra")} case "python-rsa>=3.1.2", "python-rsa": return &mock.Package{PName: "python-rsa", PVersion: "4.9-1", PDB: mock.NewDB("extra")} case "python-s3transfer>=0.3.0", "python-s3transfer": return &mock.Package{PName: "python-s3transfer", PVersion: "0.6.0-1", PDB: mock.NewDB("extra")} case "python-yaml>=3.10", "python-yaml": return &mock.Package{PName: "python-yaml", PVersion: "6.0-1", PDB: mock.NewDB("extra")} case "python-colorama>=0.2.5", "python-colorama": return &mock.Package{PName: "python-colorama", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")} case "python-tox>=2.3.1", "python-tox": return &mock.Package{PName: "python-tox", PVersion: "4.0.0-1", PDB: mock.NewDB("extra")} case "python-nose>=1.3.7", "python-nose": return &mock.Package{PName: "python-nose", PVersion: "1.3.7-1", PDB: mock.NewDB("extra")} case "python-mock>=1.3.0", "python-mock": return &mock.Package{PName: "python-mock", PVersion: "5.0.0-1", PDB: mock.NewDB("extra")} case "python-wheel>=0.24.0", "python-wheel": return &mock.Package{PName: "python-wheel", PVersion: "0.38.0-1", PDB: mock.NewDB("extra")} case "python-dateutil>=2.1", "python-dateutil": return &mock.Package{PName: "python-dateutil", PVersion: "2.8.2-1", PDB: mock.NewDB("extra")} case "python-sphinx>=1.1.3", "python-sphinx": return &mock.Package{PName: "python-sphinx", PVersion: "6.0.0-1", PDB: mock.NewDB("extra")} case "python-distribute": return &mock.Package{PName: "python-distribute", PVersion: "0.7.3-1", PDB: mock.NewDB("extra")} case "git": return &mock.Package{PName: "git", PVersion: "2.39.0-1", PDB: mock.NewDB("extra")} } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "aws-cli-git": return false case "python", "python-botocore>=1.19.35", "python-docutils>=0.10", "python-rsa>=3.1.2", "python-s3transfer>=0.3.0", "python-yaml>=3.10", "python-colorama>=0.2.5", "python-tox>=2.3.1", "python-nose>=1.3.7", "python-mock>=1.3.0", "python-wheel>=0.24.0", "python-dateutil>=2.1", "python-sphinx>=1.1.3", "python-distribute", "git": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 && query.Needles[0] == "aws-cli-git" { awsFn := getFromFile(t, "testdata/aws-cli-git.json") return awsFn(ctx, query) } return []aur.Pkg{}, nil }} t.Run("parses aws-cli-git with all its dependencies", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"aws-cli-git"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.NotEmpty(t, layers) require.Contains(t, layers[0], "aws-cli-git") require.Equal(t, "1.27.145.r11217.g5885ee4dc-1", layers[0]["aws-cli-git"].Version) require.Equal(t, "aws-cli-git", *layers[0]["aws-cli-git"].AURBase) require.Equal(t, Explicit, layers[0]["aws-cli-git"].Reason) require.Equal(t, AUR, layers[0]["aws-cli-git"].Source) }) t.Run("validates provides field for aws-cli", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"aws-cli-git"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.NotEmpty(t, layers) require.Contains(t, layers[0], "aws-cli-git") }) } // TestGrapher_ReliableSolver_LiriDesktopGit tests the dependency solver // with complex dependency chains like liri-desktop-git metapackage. func TestGrapher_ReliableSolver_LiriDesktopGit(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "liri-desktop-git", "liri-shell-git", "liri-settings-git", "libliri-git", "fluid-git", "liri-cmake-shared-git": return nil case "qt5-declarative": return &mock.Package{PName: "qt5-declarative", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "qt5-quickcontrols2": return &mock.Package{PName: "qt5-quickcontrols2", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "qt5-svg": return &mock.Package{PName: "qt5-svg", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "qt5-graphicaleffects": return &mock.Package{PName: "qt5-graphicaleffects", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "qt5-wayland": return &mock.Package{PName: "qt5-wayland", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "wayland": return &mock.Package{PName: "wayland", PVersion: "1.22.0-1", PDB: mock.NewDB("extra")} case "cmake": return &mock.Package{PName: "cmake", PVersion: "3.28.0-1", PDB: mock.NewDB("extra")} case "qt5-tools": return &mock.Package{PName: "qt5-tools", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} case "git": return &mock.Package{PName: "git", PVersion: "2.43.0-1", PDB: mock.NewDB("extra")} } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "liri-desktop-git", "liri-shell-git", "liri-settings-git", "libliri-git", "fluid-git": return false case "liri-cmake-shared-git": // makedepend from AUR return false case "qt5-declarative", "qt5-quickcontrols2", "qt5-svg", "qt5-graphicaleffects", "qt5-wayland", "wayland", "cmake", "qt5-tools", "git": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "liri-desktop-git", "liri-shell-git", "liri-settings-git", "libliri-git", "fluid-git", "liri-cmake-shared-git": liriFn := getFromFile(t, "testdata/liri-desktop-git.json") return liriFn(ctx, query) } } return []aur.Pkg{}, nil }} t.Run("liri-desktop-git pulls all dependencies", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"liri-desktop-git"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) totalPkgs := 0 for _, layer := range layers { totalPkgs += len(layer) } // 6 packages: liri-desktop-git + 4 deps + liri-cmake-shared-git (makedep) require.Equal(t, 6, totalPkgs) require.Contains(t, layers[0], "liri-desktop-git") require.Equal(t, Explicit, layers[0]["liri-desktop-git"].Reason) }) t.Run("complex dependency chain resolves in correct order", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"liri-desktop-git"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.Contains(t, layers[0], "liri-desktop-git") allPkgs := make(map[string]bool) for _, layer := range layers { for pkg := range layer { allPkgs[pkg] = true } } require.True(t, allPkgs["liri-desktop-git"]) require.True(t, allPkgs["liri-shell-git"]) require.True(t, allPkgs["liri-settings-git"]) require.True(t, allPkgs["fluid-git"]) require.True(t, allPkgs["libliri-git"]) require.True(t, allPkgs["liri-cmake-shared-git"]) // makedepend }) } // TestGrapher_SplitPackages_Clion tests split packages where multiple packages // come from the same package base, ensuring no rebuilding or reinstalling multiple times. func TestGrapher_SplitPackages_Clion(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": return nil case "libdbusmenu-glib": return &mock.Package{PName: "libdbusmenu-glib", PVersion: "16.04.0-5", PDB: mock.NewDB("extra")} case "rsync": return &mock.Package{PName: "rsync", PVersion: "3.2.7-1", PDB: mock.NewDB("extra")} case "glibc": return &mock.Package{PName: "glibc", PVersion: "2.38-1", PDB: mock.NewDB("core")} case "gcc-libs": return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")} case "python": return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")} } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": return false case "libdbusmenu-glib", "rsync", "glibc", "gcc-libs", "python": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": clionFn := getFromFile(t, "testdata/clion.json") return clionFn(ctx, query) } } return []aur.Pkg{}, nil }} installInfos := map[string]*InstallInfo{ "clion exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, "clion-jre exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, "clion-cmake exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, "clion-gdb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, "clion-lldb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo }{ { name: "single clion package", targets: []string{"clion"}, wantLayers: []map[string]*InstallInfo{ {"clion": installInfos["clion exp"]}, }, }, { name: "clion with clion-jre", targets: []string{"clion", "clion-jre"}, wantLayers: []map[string]*InstallInfo{ {"clion": installInfos["clion exp"], "clion-jre": installInfos["clion-jre exp"]}, }, }, { name: "all clion packages from same base", targets: []string{"clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb"}, wantLayers: []map[string]*InstallInfo{ { "clion": installInfos["clion exp"], "clion-jre": installInfos["clion-jre exp"], "clion-cmake": installInfos["clion-cmake exp"], "clion-gdb": installInfos["clion-gdb exp"], "clion-lldb": installInfos["clion-lldb exp"], }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } t.Run("packages from same base share AURBase", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"clion", "clion-jre", "clion-cmake"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.Len(t, layers, 1) for _, info := range layers[0] { require.NotNil(t, info.AURBase) require.Equal(t, "clion", *info.AURBase) } }) } // TestGrapher_SplitPackages_SamsungUnifiedDriver tests split packages where // packages depend on another package from the same package base. func TestGrapher_SplitPackages_SamsungUnifiedDriver(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": return nil case "cups": return &mock.Package{PName: "cups", PVersion: "2.4.2-1", PDB: mock.NewDB("extra")} case "ghostscript": return &mock.Package{PName: "ghostscript", PVersion: "10.02.0-1", PDB: mock.NewDB("extra")} case "libxml2-legacy": return &mock.Package{PName: "libxml2-legacy", PVersion: "2.10.3-1", PDB: mock.NewDB("extra")} case "libusb-compat": return &mock.Package{PName: "libusb-compat", PVersion: "0.1.8-1", PDB: mock.NewDB("extra")} case "sane": return &mock.Package{PName: "sane", PVersion: "1.2.1-1", PDB: mock.NewDB("extra")} } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": return false case "cups", "ghostscript", "libxml2-legacy", "libusb-compat", "sane": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json") return samsungFn(ctx, query) } } return []aur.Pkg{}, nil }} tests := []struct { name string targets []string wantPkgCount int wantContains []string wantNotContain []string }{ { name: "metapackage samsung-unified-driver pulls printer and scanner", targets: []string{"samsung-unified-driver"}, wantPkgCount: 4, wantContains: []string{ "samsung-unified-driver", "samsung-unified-driver-printer", "samsung-unified-driver-scanner", "samsung-unified-driver-common", }, }, { name: "printer alone pulls common", targets: []string{"samsung-unified-driver-printer"}, wantPkgCount: 2, wantContains: []string{"samsung-unified-driver-printer", "samsung-unified-driver-common"}, wantNotContain: []string{"samsung-unified-driver-scanner"}, }, { name: "scanner alone pulls common", targets: []string{"samsung-unified-driver-scanner"}, wantPkgCount: 2, wantContains: []string{"samsung-unified-driver-scanner", "samsung-unified-driver-common"}, wantNotContain: []string{"samsung-unified-driver-printer"}, }, { name: "common alone", targets: []string{"samsung-unified-driver-common"}, wantPkgCount: 1, wantContains: []string{"samsung-unified-driver-common"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) allPkgs := make(map[string]bool) for _, layer := range layers { for pkg := range layer { allPkgs[pkg] = true } } require.Equal(t, tt.wantPkgCount, len(allPkgs)) for _, pkg := range tt.wantContains { require.True(t, allPkgs[pkg], "expected package %s not found", pkg) } for _, pkg := range tt.wantNotContain { require.False(t, allPkgs[pkg], "unexpected package %s found", pkg) } }) } t.Run("split package internal deps resolved correctly", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"samsung-unified-driver"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) lastLayer := layers[len(layers)-1] require.Contains(t, lastLayer, "samsung-unified-driver-common") for _, layer := range layers { for _, info := range layer { require.NotNil(t, info.AURBase) require.Equal(t, "samsung-unified-driver", *info.AURBase) } } }) } // TestGrapher_SplitPackages_NX tests independent split packages like nxproxy and nxagent. func TestGrapher_SplitPackages_NX(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "nxproxy", "nxagent", "nx-x11", "libxcomp": return nil case "libjpeg-turbo": return &mock.Package{PName: "libjpeg-turbo", PVersion: "2.1.5-1", PDB: mock.NewDB("extra")} case "libpng": return &mock.Package{PName: "libpng", PVersion: "1.6.39-1", PDB: mock.NewDB("extra")} case "gcc-libs": return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")} case "libxml2": return &mock.Package{PName: "libxml2", PVersion: "2.11.0-1", PDB: mock.NewDB("extra")} case "xkeyboard-config": return &mock.Package{PName: "xkeyboard-config", PVersion: "2.38-1", PDB: mock.NewDB("extra")} case "xorg-xkbcomp": return &mock.Package{PName: "xorg-xkbcomp", PVersion: "1.4.6-1", PDB: mock.NewDB("extra")} case "libxfont2": return &mock.Package{PName: "libxfont2", PVersion: "2.0.6-1", PDB: mock.NewDB("extra")} case "libxinerama": return &mock.Package{PName: "libxinerama", PVersion: "1.1.5-1", PDB: mock.NewDB("extra")} case "xorg-font-util": return &mock.Package{PName: "xorg-font-util", PVersion: "1.4.0-1", PDB: mock.NewDB("extra")} case "pixman": return &mock.Package{PName: "pixman", PVersion: "0.42.2-1", PDB: mock.NewDB("extra")} case "libxrandr": return &mock.Package{PName: "libxrandr", PVersion: "1.5.3-1", PDB: mock.NewDB("extra")} case "libxtst": return &mock.Package{PName: "libxtst", PVersion: "1.2.4-1", PDB: mock.NewDB("extra")} case "libxcomposite": return &mock.Package{PName: "libxcomposite", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")} case "libxpm": return &mock.Package{PName: "libxpm", PVersion: "3.5.16-1", PDB: mock.NewDB("extra")} case "libxdamage": return &mock.Package{PName: "libxdamage", PVersion: "1.1.6-1", PDB: mock.NewDB("extra")} case "libtirpc": return &mock.Package{PName: "libtirpc", PVersion: "1.3.3-1", PDB: mock.NewDB("extra")} case "xorgproto": return &mock.Package{PName: "xorgproto", PVersion: "2023.2-1", PDB: mock.NewDB("extra")} case "imake": return &mock.Package{PName: "imake", PVersion: "1.0.9-1", PDB: mock.NewDB("extra")} } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "nxproxy", "nxagent", "nx-x11", "libxcomp": return false case "libjpeg-turbo", "libpng", "gcc-libs", "libxml2", "xkeyboard-config", "xorg-xkbcomp", "libxfont2", "libxinerama", "xorg-font-util", "pixman", "libxrandr", "libxtst", "libxcomposite", "libxpm", "libxdamage", "libtirpc", "xorgproto", "imake": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "nxproxy", "nxagent", "nx-x11", "libxcomp": nxFn := getFromFile(t, "testdata/nx.json") return nxFn(ctx, query) } } return []aur.Pkg{}, nil }} installInfos := map[string]*InstallInfo{ "nxproxy exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, "nxagent exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, "nx-x11 dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, "libxcomp dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, "libxcomp exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo }{ { name: "nxproxy independently", targets: []string{"nxproxy"}, wantLayers: []map[string]*InstallInfo{ {"nxproxy": installInfos["nxproxy exp"]}, {"libxcomp": installInfos["libxcomp dep"]}, }, }, { name: "nxagent independently - has more deps", targets: []string{"nxagent"}, wantLayers: []map[string]*InstallInfo{ {"nxagent": installInfos["nxagent exp"]}, {"nx-x11": installInfos["nx-x11 dep"]}, {"libxcomp": installInfos["libxcomp dep"]}, }, }, { name: "both nxproxy and nxagent", targets: []string{"nxproxy", "nxagent"}, wantLayers: []map[string]*InstallInfo{ {"nxproxy": installInfos["nxproxy exp"], "nxagent": installInfos["nxagent exp"]}, {"nx-x11": installInfos["nx-x11 dep"]}, {"libxcomp": installInfos["libxcomp dep"]}, }, }, { name: "libxcomp independently", targets: []string{"libxcomp"}, wantLayers: []map[string]*InstallInfo{ {"libxcomp": installInfos["libxcomp exp"]}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } t.Run("split packages share AURBase but can be installed independently", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got1, err := g.GraphFromTargets(context.Background(), nil, []string{"nxproxy"}) require.NoError(t, err) layers1 := got1.TopoSortedLayers(nil) got2, err := g.GraphFromTargets(context.Background(), nil, []string{"nxagent"}) require.NoError(t, err) layers2 := got2.TopoSortedLayers(nil) require.Equal(t, "nx", *layers1[0]["nxproxy"].AURBase) require.Equal(t, "nx", *layers2[0]["nxagent"].AURBase) totalPkgs1 := 0 for _, layer := range layers1 { totalPkgs1 += len(layer) } totalPkgs2 := 0 for _, layer := range layers2 { totalPkgs2 += len(layer) } require.Equal(t, 2, totalPkgs1) require.Equal(t, 3, totalPkgs2) }) } // TestGrapher_SplitPackages_ReversedOrder tests that split packages resolve // correctly regardless of the order they are specified in. func TestGrapher_SplitPackages_ReversedOrder(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "nxproxy", "nxagent", "nx-x11", "libxcomp": return nil default: return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")} } }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "nxproxy", "nxagent", "nx-x11", "libxcomp": return false default: return true } }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "nxproxy", "nxagent", "nx-x11", "libxcomp": nxFn := getFromFile(t, "testdata/nx.json") return nxFn(ctx, query) } } return []aur.Pkg{}, nil }} t.Run("nxproxy nxagent order", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"nxproxy", "nxagent"}) require.NoError(t, err) layers1 := got.TopoSortedLayers(nil) require.Contains(t, layers1[0], "nxproxy") require.Contains(t, layers1[0], "nxagent") }) t.Run("nxagent nxproxy reversed order", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"nxagent", "nxproxy"}) require.NoError(t, err) layers2 := got.TopoSortedLayers(nil) require.Contains(t, layers2[0], "nxproxy") require.Contains(t, layers2[0], "nxagent") }) } // TestGrapher_MultipleInstallInfo ensures that when the same package appears as // both explicit target and dependency, the explicit reason takes precedence. func TestGrapher_MultipleInstallInfo(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": return nil default: return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")} } }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": return false default: return true } }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { for _, needle := range query.Needles { switch needle { case "samsung-unified-driver", "samsung-unified-driver-common", "samsung-unified-driver-printer", "samsung-unified-driver-scanner": samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json") return samsungFn(ctx, query) } } return []aur.Pkg{}, nil }} t.Run("explicit target takes precedence over dependency", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"samsung-unified-driver", "samsung-unified-driver-common"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) found := false for _, layer := range layers { if info, ok := layer["samsung-unified-driver-common"]; ok { found = true require.Equal(t, Explicit, info.Reason, "explicit target should have Explicit reason, not Dep") } } require.True(t, found, "samsung-unified-driver-common should be in layers") }) } // TestGrapher_VersionedDependencies tests proper handling of versioned dependencies. func TestGrapher_VersionedDependencies(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0": return nil } panic(fmt.Sprintf("implement me: %s", s)) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0": return false } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { mockPkgs := map[string]aur.Pkg{ "versioned-pkg": { Name: "versioned-pkg", PackageBase: "versioned-pkg", Version: "1.0.0-1", Depends: []string{"dep-pkg>=2.0.0"}, }, "dep-pkg": { Name: "dep-pkg", PackageBase: "dep-pkg", Version: "2.5.0-1", }, } pkgs := []aur.Pkg{} for _, needle := range query.Needles { if pkg, ok := mockPkgs[needle]; ok { pkgs = append(pkgs, pkg) } } return pkgs, nil }} t.Run("versioned dependency satisfied by higher version", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"versioned-pkg"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.Len(t, layers, 2) require.Contains(t, layers[0], "versioned-pkg") require.Contains(t, layers[1], "dep-pkg") require.Equal(t, "2.5.0-1", layers[1]["dep-pkg"].Version) }) } ================================================ FILE: pkg/dep/dep_graph_test.go ================================================ //go:build !integration // +build !integration package dep import ( "bytes" "context" "encoding/json" "fmt" "io" "os" "strings" "testing" aurc "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" aur "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) func ptrString(s string) *string { return &s } func getFromFile(t testing.TB, filePath string) mockaur.GetFunc { t.Helper() f, err := os.Open(filePath) require.NoError(t, err) fBytes, err := io.ReadAll(f) require.NoError(t, err) pkgs := []aur.Pkg{} err = json.Unmarshal(fBytes, &pkgs) require.NoError(t, err) return func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { return pkgs, nil } } func TestGrapher_findDepsFromAUR_logsRequiredByForMissingDep(t *testing.T) { mockDB := &mock.DBExecutor{} mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }} var stderr bytes.Buffer logger := text.NewLogger(io.Discard, &stderr, strings.NewReader(""), true, "test") g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, logger) graph := NewGraph() depString := "missingdep>=1.0" depName := "missingdep" require.NoError(t, graph.DependOn("existingNeeds", depName)) toFind := mapset.NewThreadUnsafeSet(depString) _ = g.findDepsFromAUR(context.Background(), graph, "currentNeeds", toFind) out := stderr.String() require.Contains(t, out, "No AUR package found for "+depString+" (required by:") require.Contains(t, out, "currentNeeds") require.Contains(t, out, "existingNeeds") } func TestGrapher_GraphFromTargets_jellyfin(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "jellyfin": return nil case "dotnet-runtime-6.0": return &mock.Package{ PName: "dotnet-runtime-6.0", PBase: "dotnet-runtime-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } case "dotnet-sdk-6.0": return &mock.Package{ PName: "dotnet-sdk-6.0", PBase: "dotnet-sdk-6.0", PVersion: "6.0.100-1", PDB: mock.NewDB("community"), } } return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "dotnet-sdk-6.0", "dotnet-runtime-6.0", "jellyfin-server=10.8.8", "jellyfin-web=10.8.8": return false } return true }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if query.Needles[0] == "jellyfin" { jfinFn := getFromFile(t, "testdata/jellyfin.json") return jfinFn(ctx, query) } if query.Needles[0] == "jellyfin-web" { jfinWebFn := getFromFile(t, "testdata/jellyfin-web.json") return jfinWebFn(ctx, query) } if query.Needles[0] == "jellyfin-server" { jfinServerFn := getFromFile(t, "testdata/jellyfin-server.json") return jfinServerFn(ctx, query) } panic(fmt.Sprintf("implement me %v", query.Needles)) }} type fields struct { dbExecutor db.Executor aurCache aurc.QueryClient noDeps bool noCheckDeps bool } type args struct { targets []string } tests := []struct { name string fields fields args args want []map[string]*InstallInfo wantErr bool }{ { name: "noDeps", fields: fields{ dbExecutor: mockDB, aurCache: mockAUR, noDeps: true, noCheckDeps: false, }, args: args{ targets: []string{"jellyfin"}, }, want: []map[string]*InstallInfo{ { "jellyfin": { Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin"), }, }, { "dotnet-sdk-6.0": { Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community"), }, }, }, wantErr: false, }, { name: "deps", fields: fields{ dbExecutor: mockDB, aurCache: mockAUR, noDeps: false, noCheckDeps: false, }, args: args{ targets: []string{"jellyfin"}, }, want: []map[string]*InstallInfo{ { "jellyfin": { Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin"), }, }, { "jellyfin-web": { Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin"), }, "jellyfin-server": { Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin"), }, }, { "dotnet-sdk-6.0": { Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community"), }, "dotnet-runtime-6.0": { Source: Sync, Reason: Dep, Version: "6.0.100-1", SyncDBName: ptrString("community"), }, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(tt.fields.dbExecutor, tt.fields.aurCache, false, true, tt.fields.noDeps, tt.fields.noCheckDeps, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.args.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.want, layers, layers) }) } } func TestGrapher_GraphProvides_androidsdk(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "android-sdk": return nil case "jdk11-openjdk": return &mock.Package{ PName: "jdk11-openjdk", PVersion: "11.0.12.u7-1", PDB: mock.NewDB("community"), PProvides: mock.DependList{ Depends: []alpm.Depend{ {Name: "java-environment", Version: "11", Mod: alpm.DepModEQ}, {Name: "java-environment-openjdk", Version: "11", Mod: alpm.DepModEQ}, {Name: "jdk11-openjdk", Version: "11.0.19.u7-1", Mod: alpm.DepModEQ}, }, }, } case "java-environment": panic("not supposed to be called") } panic("implement me " + s) }, PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "java-environment": return false } switch s { case "libxtst", "fontconfig", "freetype2", "lib32-gcc-libs", "lib32-glibc", "libx11", "libxext", "libxrender", "zlib", "gcc-libs": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if query.Needles[0] == "android-sdk" { jfinFn := getFromFile(t, "testdata/android-sdk.json") return jfinFn(ctx, query) } panic(fmt.Sprintf("implement me %v", query.Needles)) }} type fields struct { dbExecutor db.Executor aurCache aurc.QueryClient noDeps bool noCheckDeps bool } type args struct { targets []string } tests := []struct { name string fields fields args args want []map[string]*InstallInfo wantErr bool }{ { name: "explicit dep", fields: fields{ dbExecutor: mockDB, aurCache: mockAUR, noDeps: false, noCheckDeps: false, }, args: args{ targets: []string{"android-sdk", "jdk11-openjdk"}, }, want: []map[string]*InstallInfo{ { "android-sdk": { Source: AUR, Reason: Explicit, Version: "26.1.1-2", AURBase: ptrString("android-sdk"), }, }, { "jdk11-openjdk": { Source: Sync, Reason: Explicit, Version: "11.0.12.u7-1", SyncDBName: ptrString("community"), }, }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(tt.fields.dbExecutor, tt.fields.aurCache, false, true, tt.fields.noDeps, tt.fields.noCheckDeps, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.args.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.want, layers, layers) }) } } func TestGrapher_GraphFromAUR_Deps_ceph_bin(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "ceph-bin", "ceph-libs-bin": return nil case "ceph", "ceph-libs", "ceph-libs=17.2.6-2": return nil } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "ceph-libs", "ceph-libs=17.2.6-2": return false case "dep1", "dep2", "dep3", "makedep1", "makedep2", "checkdep1": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { mockPkgs := map[string]aur.Pkg{ "ceph-bin": { Name: "ceph-bin", PackageBase: "ceph-bin", Version: "17.2.6-2", Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, Provides: []string{"ceph=17.2.6-2"}, }, "ceph-libs-bin": { Name: "ceph-libs-bin", PackageBase: "ceph-bin", Version: "17.2.6-2", Depends: []string{"dep1", "dep2"}, Provides: []string{"ceph-libs=17.2.6-2"}, }, "ceph": { Name: "ceph", PackageBase: "ceph", Version: "17.2.6-2", Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, MakeDepends: []string{"makedep1"}, CheckDepends: []string{"checkdep1"}, Provides: []string{"ceph=17.2.6-2"}, }, "ceph-libs": { Name: "ceph-libs", PackageBase: "ceph", Version: "17.2.6-2", Depends: []string{"dep1", "dep2", "dep3"}, MakeDepends: []string{"makedep1", "makedep2"}, CheckDepends: []string{"checkdep1"}, Provides: []string{"ceph-libs=17.2.6-2"}, }, } pkgs := []aur.Pkg{} for _, needle := range query.Needles { if pkg, ok := mockPkgs[needle]; ok { pkgs = append(pkgs, pkg) } else { panic(fmt.Sprintf("implement me %v", needle)) } } return pkgs, nil }} installInfos := map[string]*InstallInfo{ "ceph-bin exp": { Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin"), }, "ceph-libs-bin exp": { Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin"), }, "ceph exp": { Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph"), }, "ceph-libs exp": { Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph"), }, "ceph-libs dep": { Source: AUR, Reason: Dep, Version: "17.2.6-2", AURBase: ptrString("ceph"), }, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo wantErr bool }{ { name: "ceph-bin ceph-libs-bin", targets: []string{"ceph-bin", "ceph-libs-bin"}, wantLayers: []map[string]*InstallInfo{ {"ceph-bin": installInfos["ceph-bin exp"]}, {"ceph-libs-bin": installInfos["ceph-libs-bin exp"]}, }, wantErr: false, }, { name: "ceph-libs-bin ceph-bin (reversed order)", targets: []string{"ceph-libs-bin", "ceph-bin"}, wantLayers: []map[string]*InstallInfo{ {"ceph-bin": installInfos["ceph-bin exp"]}, {"ceph-libs-bin": installInfos["ceph-libs-bin exp"]}, }, wantErr: false, }, { name: "ceph", targets: []string{"ceph"}, wantLayers: []map[string]*InstallInfo{ {"ceph": installInfos["ceph exp"]}, {"ceph-libs": installInfos["ceph-libs dep"]}, }, wantErr: false, }, { name: "ceph-bin", targets: []string{"ceph-bin"}, wantLayers: []map[string]*InstallInfo{ {"ceph-bin": installInfos["ceph-bin exp"]}, {"ceph-libs": installInfos["ceph-libs dep"]}, }, wantErr: false, }, { name: "ceph-bin ceph-libs", targets: []string{"ceph-bin", "ceph-libs"}, wantLayers: []map[string]*InstallInfo{ {"ceph-bin": installInfos["ceph-bin exp"]}, {"ceph-libs": installInfos["ceph-libs exp"]}, }, wantErr: false, }, { name: "ceph-libs ceph-bin (reversed order)", targets: []string{"ceph-libs", "ceph-bin"}, wantLayers: []map[string]*InstallInfo{ {"ceph-bin": installInfos["ceph-bin exp"]}, {"ceph-libs": installInfos["ceph-libs exp"]}, }, wantErr: false, }, { name: "ceph ceph-libs-bin", targets: []string{"ceph", "ceph-libs-bin"}, wantLayers: []map[string]*InstallInfo{ {"ceph": installInfos["ceph exp"]}, {"ceph-libs-bin": installInfos["ceph-libs-bin exp"]}, }, wantErr: false, }, { name: "ceph-libs-bin ceph (reversed order)", targets: []string{"ceph-libs-bin", "ceph"}, wantLayers: []map[string]*InstallInfo{ {"ceph": installInfos["ceph exp"]}, {"ceph-libs-bin": installInfos["ceph-libs-bin exp"]}, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } } func TestGrapher_GraphFromAUR_Deps_gourou(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "gourou", "libzip-git": return nil case "libzip": return &mock.Package{ PName: "libzip", PVersion: "1.9.2-1", PDB: mock.NewDB("extra"), } } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "gourou", "libzip", "libzip-git": return false case "dep1", "dep2": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { mockPkgs := map[string]aur.Pkg{ "gourou": { Name: "gourou", PackageBase: "gourou", Version: "0.8.1", Depends: []string{"libzip"}, }, "libzip-git": { Name: "libzip-git", PackageBase: "libzip-git", Version: "1.9.2.r159.gb3ac716c-1", Depends: []string{"dep1", "dep2"}, Provides: []string{"libzip=1.9.2.r159.gb3ac716c"}, }, } pkgs := []aur.Pkg{} for _, needle := range query.Needles { if pkg, ok := mockPkgs[needle]; ok { pkgs = append(pkgs, pkg) } else { panic(fmt.Sprintf("implement me %v", needle)) } } return pkgs, nil }} installInfos := map[string]*InstallInfo{ "gourou exp": { Source: AUR, Reason: Explicit, Version: "0.8.1", AURBase: ptrString("gourou"), }, "libzip dep": { Source: Sync, Reason: Dep, Version: "1.9.2-1", SyncDBName: ptrString("extra"), }, "libzip exp": { Source: Sync, Reason: Explicit, Version: "1.9.2-1", SyncDBName: ptrString("extra"), }, "libzip-git exp": { Source: AUR, Reason: Explicit, Version: "1.9.2.r159.gb3ac716c-1", AURBase: ptrString("libzip-git"), }, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo wantErr bool }{ { name: "gourou", targets: []string{"gourou"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou exp"]}, {"libzip": installInfos["libzip dep"]}, }, wantErr: false, }, { name: "gourou libzip", targets: []string{"gourou", "libzip"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou exp"]}, {"libzip": installInfos["libzip exp"]}, }, wantErr: false, }, { name: "gourou libzip-git", targets: []string{"gourou", "libzip-git"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou exp"]}, {"libzip-git": installInfos["libzip-git exp"]}, }, wantErr: false, }, { name: "libzip-git gourou (reversed order)", targets: []string{"libzip-git", "gourou"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou exp"]}, {"libzip-git": installInfos["libzip-git exp"]}, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } } func TestGrapher_GraphFromTargets_ReinstalledDeps(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "gourou": return nil case "libzip": return &mock.Package{ PName: "libzip", PVersion: "1.9.2-1", PDB: mock.NewDB("extra"), } } panic("implement me " + s) }, SatisfierFromDBFn: func(s, s2 string) (mock.IPackage, error) { if s2 == "extra" { switch s { case "libzip": return &mock.Package{ PName: "libzip", PVersion: "1.9.2-1", PDB: mock.NewDB("extra"), }, nil } } panic("implement me " + s2 + "/" + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "gourou", "libzip": return true } panic("implement me " + s) }, LocalPackageFn: func(s string) mock.IPackage { switch s { case "libzip": return &mock.Package{ PName: "libzip", PVersion: "1.9.2-1", PDB: mock.NewDB("extra"), PReason: alpm.PkgReasonDepend, } case "gourou": return &mock.Package{ PName: "gourou", PVersion: "0.8.1", PDB: mock.NewDB("aur"), PReason: alpm.PkgReasonDepend, } } return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { mockPkgs := map[string]aur.Pkg{ "gourou": { Name: "gourou", PackageBase: "gourou", Version: "0.8.1", Depends: []string{"libzip"}, }, } pkgs := []aur.Pkg{} for _, needle := range query.Needles { if pkg, ok := mockPkgs[needle]; ok { pkgs = append(pkgs, pkg) } else { panic(fmt.Sprintf("implement me %v", needle)) } } return pkgs, nil }} installInfos := map[string]*InstallInfo{ "gourou dep": { Source: AUR, Reason: Dep, Version: "0.8.1", AURBase: ptrString("gourou"), }, "libzip dep": { Source: Sync, Reason: Dep, Version: "1.9.2-1", SyncDBName: ptrString("extra"), }, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo wantErr bool }{ { name: "gourou libzip", targets: []string{"gourou", "libzip"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou dep"]}, {"libzip": installInfos["libzip dep"]}, }, wantErr: false, }, { name: "aur/gourou extra/libzip", targets: []string{"aur/gourou", "extra/libzip"}, wantLayers: []map[string]*InstallInfo{ {"gourou": installInfos["gourou dep"]}, {"libzip": installInfos["libzip dep"]}, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } } func TestGrapher_GraphFromTargets_TargetNotFound(t *testing.T) { mockDB := &mock.DBExecutor{ SyncSatisfierFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, LocalPackageFn: func(string) mock.IPackage { return nil }, LocalSatisfierExistsFn: func(string) bool { return false }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { ok := aur.Pkg{ Name: "okpkg", PackageBase: "okpkg", Version: "1.0.0", } switch query.By { case aurc.Name: // Return only packages that exist. pkgs := make([]aur.Pkg, 0, len(query.Needles)) for _, needle := range query.Needles { if needle == ok.Name { pkgs = append(pkgs, ok) } } return pkgs, nil case aurc.Provides: // Provider lookup is done per-target. if len(query.Needles) > 0 && query.Needles[0] == ok.Name { return []aur.Pkg{ok}, nil } return []aur.Pkg{}, nil default: return []aur.Pkg{}, nil } }} g := NewGrapher(mockDB, mockAUR, false, true, true, true, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) t.Run("returns error when all targets are missing", func(t *testing.T) { _, err := g.GraphFromTargets(context.Background(), nil, []string{"missing1", "missing2"}) require.Error(t, err) var targetNotFound *aur.ErrTargetNotFound require.ErrorAs(t, err, &targetNotFound) }) t.Run("does not error when at least one target is found", func(t *testing.T) { got, err := g.GraphFromTargets(context.Background(), nil, []string{"missing1", "okpkg"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, []map[string]*InstallInfo{ { "okpkg": { Source: AUR, Reason: Explicit, Version: "1.0.0", AURBase: ptrString("okpkg"), }, }, }, layers, layers) }) } // TestGrapher_GraphFromAUR_SplitPkgInternalDeps tests split packages where // packages from the same base depend on each other (like gstreamer-git). func TestGrapher_GraphFromAUR_SplitPkgInternalDeps(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { // AUR packages and versioned AUR deps return nil case "gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git", "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git=1.24.0.r37-1": return nil case "libxml2": return &mock.Package{ PName: "libxml2", PVersion: "2.12.0-1", PDB: mock.NewDB("core"), } case "glib2": return &mock.Package{ PName: "glib2", PVersion: "2.78.0-1", PDB: mock.NewDB("core"), } case "orc": return &mock.Package{ PName: "orc", PVersion: "0.4.34-1", PDB: mock.NewDB("extra"), } case "libxv": return &mock.Package{ PName: "libxv", PVersion: "1.0.12-1", PDB: mock.NewDB("extra"), } case "iso-codes": return &mock.Package{ PName: "iso-codes", PVersion: "4.15.0-1", PDB: mock.NewDB("extra"), } case "libpulse": return &mock.Package{ PName: "libpulse", PVersion: "16.1-1", PDB: mock.NewDB("extra"), } case "wavpack": return &mock.Package{ PName: "wavpack", PVersion: "5.6.0-1", PDB: mock.NewDB("extra"), } } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "gstreamer-git", "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git", "gst-plugins-base-libs-git=1.24.0.r37-1", "gst-plugins-good-git": return false case "libxml2", "glib2", "orc", "libxv", "iso-codes", "libpulse", "wavpack", "git", "meson", "ninja": // makedepends return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 { for _, needle := range query.Needles { if needle == "gstreamer-git" || needle == "gst-plugins-base-libs-git" || needle == "gst-plugins-good-git" { gstFn := getFromFile(t, "testdata/gstreamer-git.json") return gstFn(ctx, query) } } } return []aur.Pkg{}, nil // Return empty for unknown packages }} installInfos := map[string]*InstallInfo{ "gstreamer-git exp": { Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git"), }, "gstreamer-git dep": { Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git"), }, "gst-plugins-base-libs-git exp": { Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git"), }, "gst-plugins-base-libs-git dep": { Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git"), }, "gst-plugins-good-git exp": { Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git"), }, } tests := []struct { name string targets []string wantLayers []map[string]*InstallInfo wantErr bool }{ { name: "gst-plugins-good-git pulls in base libs and gstreamer", targets: []string{"gst-plugins-good-git"}, wantLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git dep"]}, {"gstreamer-git": installInfos["gstreamer-git dep"]}, }, wantErr: false, }, { name: "gst-plugins-base-libs-git pulls in gstreamer", targets: []string{"gst-plugins-base-libs-git"}, wantLayers: []map[string]*InstallInfo{ {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git exp"]}, {"gstreamer-git": installInfos["gstreamer-git dep"]}, }, wantErr: false, }, { name: "explicit gstreamer-git with gst-plugins-good-git", targets: []string{"gstreamer-git", "gst-plugins-good-git"}, wantLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git dep"]}, {"gstreamer-git": installInfos["gstreamer-git exp"]}, }, wantErr: false, }, { name: "all three packages explicitly", targets: []string{"gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git"}, wantLayers: []map[string]*InstallInfo{ {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git exp"]}, {"gstreamer-git": installInfos["gstreamer-git exp"]}, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.EqualValues(t, tt.wantLayers, layers, layers) }) } } // TestGrapher_GraphFromAUR_CheckDeps tests packages with CheckDepends. func TestGrapher_GraphFromAUR_CheckDeps(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "python-pydantic": return nil case "python": return &mock.Package{ PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core"), } case "python-typing-extensions": return &mock.Package{ PName: "python-typing-extensions", PVersion: "4.8.0-1", PDB: mock.NewDB("extra"), } case "python-build": return &mock.Package{ PName: "python-build", PVersion: "1.0.0-1", PDB: mock.NewDB("extra"), } case "python-installer": return &mock.Package{ PName: "python-installer", PVersion: "0.7.0-1", PDB: mock.NewDB("extra"), } case "python-pytest": return &mock.Package{ PName: "python-pytest", PVersion: "7.4.0-1", PDB: mock.NewDB("extra"), } case "python-pytest-mock": return &mock.Package{ PName: "python-pytest-mock", PVersion: "3.11.0-1", PDB: mock.NewDB("extra"), } } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "python-pydantic", "python-pytest", "python-pytest-mock": // check deps not installed return false case "python", "python-typing-extensions", "python-build", "python-installer": return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 && query.Needles[0] == "python-pydantic" { pydanticFn := getFromFile(t, "testdata/python-pydantic.json") return pydanticFn(ctx, query) } return []aur.Pkg{}, nil // Return empty for unknown packages }} t.Run("with check deps enabled", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"python-pydantic"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) // Should have the main package and its check deps require.Len(t, layers, 2) require.Contains(t, layers[0], "python-pydantic") // Check deps should be in the second layer require.Contains(t, layers[1], "python-pytest") require.Contains(t, layers[1], "python-pytest-mock") }) t.Run("with check deps disabled", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, true, false, // noCheckDeps = true text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"python-pydantic"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) // Should only have the main package (no check deps) require.Len(t, layers, 1) require.Contains(t, layers[0], "python-pydantic") }) } // TestGrapher_GraphFromAUR_VirtualProvides tests packages that provide virtual packages // (like mesa-git providing vulkan-driver, opengl-driver). func TestGrapher_GraphFromAUR_VirtualProvides(t *testing.T) { mockDB := &mock.DBExecutor{ SyncPackageFn: func(string) mock.IPackage { return nil }, PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, SyncSatisfierFn: func(s string) mock.IPackage { switch s { case "mesa-git": return nil case "libdrm": return &mock.Package{ PName: "libdrm", PVersion: "2.4.117-1", PDB: mock.NewDB("core"), } case "vulkan-icd-loader": return &mock.Package{ PName: "vulkan-icd-loader", PVersion: "1.3.268-1", PDB: mock.NewDB("extra"), } case "vulkan-radeon": return &mock.Package{ PName: "vulkan-radeon", PVersion: "23.3.0-1", PDB: mock.NewDB("extra"), PProvides: mock.DependList{ Depends: []alpm.Depend{ {Name: "vulkan-driver", Version: "", Mod: alpm.DepModAny}, }, }, } } // Most mesa deps are already installed switch s { case "libxxf86vm", "libxdamage", "libxshmfence", "libelf", "libunwind", "libglvnd", "wayland", "lm_sensors", "zstd", "expat": return &mock.Package{ PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra"), } } panic("implement me " + s) }, LocalSatisfierExistsFn: func(s string) bool { switch s { case "mesa-git", "vulkan-driver", "opengl-driver": return false case "libdrm", "libxxf86vm", "libxdamage", "libxshmfence", "libelf", "libunwind", "libglvnd", "wayland", "lm_sensors", "vulkan-icd-loader", "zstd", "expat", "git", "meson", "ninja", "llvm", "clang": // makedepends return true } panic("implement me " + s) }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { if len(query.Needles) > 0 && query.Needles[0] == "mesa-git" { mesaFn := getFromFile(t, "testdata/mesa-git.json") return mesaFn(ctx, query) } return []aur.Pkg{}, nil // Return empty for unknown packages }} t.Run("mesa-git provides vulkan-driver and opengl-driver", func(t *testing.T) { g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) got, err := g.GraphFromTargets(context.Background(), nil, []string{"mesa-git"}) require.NoError(t, err) layers := got.TopoSortedLayers(nil) require.Len(t, layers, 1) require.Contains(t, layers[0], "mesa-git") require.Equal(t, "24.0.0.r1234-1", layers[0]["mesa-git"].Version) require.Equal(t, "mesa-git", *layers[0]["mesa-git"].AURBase) }) } ================================================ FILE: pkg/dep/mock/aur.go ================================================ package mock import ( "context" "github.com/Jguer/aur" ) type GetFunc func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) type MockAUR struct { GetFn GetFunc } func (m *MockAUR) Get(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { if m.GetFn != nil { return m.GetFn(ctx, query) } panic("implement me") } ================================================ FILE: pkg/dep/target_handler.go ================================================ package dep import "github.com/Jguer/yay/v12/pkg/text" type Target struct { DB string Name string Mod string Version string } func ToTarget(pkg string) Target { dbName, depString := text.SplitDBFromName(pkg) name, mod, depVersion := splitDep(depString) return Target{ DB: dbName, Name: name, Mod: mod, Version: depVersion, } } 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() } ================================================ FILE: pkg/dep/testdata/android-sdk.json ================================================ [ {"ID":1055234,"Name":"android-sdk","PackageBaseID":13751,"PackageBase":"android-sdk","Version":"26.1.1-2","Description":"Google Android SDK","URL":"https://developer.android.com/studio/releases/sdk-tools.html","NumVotes":1487,"Popularity":0.802316,"OutOfDate":null,"Maintainer":"dreamingincode","Submitter":null,"FirstSubmitted":1194895596,"LastModified":1647982720,"URLPath":"/cgit/aur.git/snapshot/android-sdk.tar.gz","Depends":["java-environment","libxtst","fontconfig","freetype2","lib32-gcc-libs","lib32-glibc","libx11","libxext","libxrender","zlib","gcc-libs"],"OptDepends":["android-emulator","android-sdk-platform-tools","android-udev"],"License":["custom"],"Keywords":["android","development"]} ] ================================================ FILE: pkg/dep/testdata/aws-cli-git.json ================================================ [ {"ID":1266542,"Name":"aws-cli-git","PackageBaseID":75029,"PackageBase":"aws-cli-git","Version":"1.27.145.r11217.g5885ee4dc-1","Description":"Universal Command Line Interface for Amazon Web Services awscli","URL":"https://github.com/aws/aws-cli","NumVotes":8,"Popularity":0.0,"OutOfDate":1710077383,"Maintainer":null,"Submitter":"flaccid","FirstSubmitted":1383449501,"LastModified":1685666227,"URLPath":"/cgit/aur.git/snapshot/aws-cli-git.tar.gz","Depends":["python","python-botocore>=1.19.35","python-docutils>=0.10","python-rsa>=3.1.2","python-s3transfer>=0.3.0","python-yaml>=3.10","python-colorama>=0.2.5","python-tox>=2.3.1","python-nose>=1.3.7","python-mock>=1.3.0","python-wheel>=0.24.0","python-dateutil>=2.1","python-sphinx>=1.1.3"],"MakeDepends":["python","python-distribute","git"],"Conflicts":["python2-aws-cli","python-aws-cli","aws-cli"],"Provides":["aws-cli=1.27.145"],"License":["Apache"]} ] ================================================ FILE: pkg/dep/testdata/clion.json ================================================ [ {"ID":1937213,"Name":"clion","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"Cross-platform IDE for C and C++ from JetBrains.","URL":"http://www.jetbrains.com/clion","NumVotes":239,"Popularity":1.760009,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["libdbusmenu-glib"],"MakeDepends":["rsync"],"OptDepends":["clion-jre","clion-cmake","clion-gdb","clion-lldb","java-runtime","cmake","gdb","lldb","gcc","clang","gtest","python","doxygen"],"License":["custom"]}, {"ID":1937214,"Name":"clion-jre","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"JBR (JetBrains Runtime) for CLion - aass/JVM bundle based on OpenJDK","URL":"http://www.jetbrains.com/clion","NumVotes":50,"Popularity":0.5,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","gcc-libs"],"MakeDepends":["rsync"],"Provides":["java-runtime"],"License":["custom"]}, {"ID":1937215,"Name":"clion-cmake","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"CMake for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":30,"Popularity":0.3,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc"],"MakeDepends":["rsync"],"Provides":["cmake"],"Conflicts":["cmake"],"License":["custom"]}, {"ID":1937216,"Name":"clion-gdb","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"GDB for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":25,"Popularity":0.25,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","python"],"MakeDepends":["rsync"],"Provides":["gdb"],"Conflicts":["gdb"],"License":["custom"]}, {"ID":1937217,"Name":"clion-lldb","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"LLDB for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":20,"Popularity":0.2,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","python"],"MakeDepends":["rsync"],"Provides":["lldb"],"Conflicts":["lldb"],"License":["custom"]} ] ================================================ FILE: pkg/dep/testdata/gstreamer-git.json ================================================ [ {"ID":1000001,"Name":"gstreamer-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer open-source multimedia framework core library (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["libxml2","glib2"],"MakeDepends":["git","meson","ninja"],"Provides":["gstreamer=1.24.0"],"Conflicts":["gstreamer"],"License":["LGPL"]}, {"ID":1000002,"Name":"gst-plugins-base-libs-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer base plugins libraries (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["gstreamer-git=1.24.0.r37-1","orc","libxv","iso-codes"],"MakeDepends":["git","meson","ninja"],"Provides":["gst-plugins-base-libs=1.24.0"],"Conflicts":["gst-plugins-base-libs"],"License":["LGPL"]}, {"ID":1000003,"Name":"gst-plugins-good-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer good plugins (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["gst-plugins-base-libs-git=1.24.0.r37-1","libpulse","wavpack"],"MakeDepends":["git","meson","ninja"],"Provides":["gst-plugins-good=1.24.0"],"Conflicts":["gst-plugins-good"],"License":["LGPL"]} ] ================================================ FILE: pkg/dep/testdata/jellyfin-server.json ================================================ [ {"ID":1176791,"Name":"jellyfin-server","PackageBaseID":138631,"PackageBase":"jellyfin","Version":"10.8.8-1","Description":"Jellyfin server component","URL":"https://github.com/jellyfin/jellyfin","NumVotes":84,"Popularity":1.272964,"OutOfDate":null,"Maintainer":"z3ntu","Submitter":"z3ntu","FirstSubmitted":1547053171,"LastModified":1669830147,"URLPath":"/cgit/aur.git/snapshot/jellyfin-server.tar.gz","Depends":["dotnet-runtime-6.0","aspnet-runtime-6.0","ffmpeg","sqlite"],"MakeDepends":["dotnet-sdk-6.0","nodejs","npm","git"],"License":["GPL2"]} ] ================================================ FILE: pkg/dep/testdata/jellyfin-web.json ================================================ [ {"ID":1176790,"Name":"jellyfin-web","PackageBaseID":138631,"PackageBase":"jellyfin","Version":"10.8.8-1","Description":"Jellyfin web client","URL":"https://github.com/jellyfin/jellyfin","NumVotes":84,"Popularity":1.272964,"OutOfDate":null,"Maintainer":"z3ntu","Submitter":"z3ntu","FirstSubmitted":1547053171,"LastModified":1669830147,"URLPath":"/cgit/aur.git/snapshot/jellyfin-web.tar.gz","MakeDepends":["dotnet-sdk-6.0","nodejs","npm","git"],"License":["GPL2"]} ] ================================================ FILE: pkg/dep/testdata/jellyfin.json ================================================ [ {"ID":1176789,"Name":"jellyfin","PackageBaseID":138631,"PackageBase":"jellyfin","Version":"10.8.8-1","Description":"The Free Software Media System","URL":"https://github.com/jellyfin/jellyfin","NumVotes":84,"Popularity":1.272964,"OutOfDate":null,"Maintainer":"z3ntu","Submitter":"z3ntu","FirstSubmitted":1547053171,"LastModified":1669830147,"URLPath":"/cgit/aur.git/snapshot/jellyfin.tar.gz","Depends":["jellyfin-web=10.8.8","jellyfin-server=10.8.8"],"MakeDepends":["dotnet-sdk-6.0","nodejs","npm","git"],"License":["GPL2"]} ] ================================================ FILE: pkg/dep/testdata/liri-desktop-git.json ================================================ [ {"ID":2000001,"Name":"liri-desktop-git","PackageBaseID":200001,"PackageBase":"liri-desktop-git","Version":"0.1.0.r50-1","Description":"Liri Desktop metapackage","URL":"https://liri.io","NumVotes":10,"Popularity":0.1,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-desktop-git.tar.gz","Depends":["liri-shell-git","liri-settings-git","fluid-git","libliri-git"],"MakeDepends":["git"],"License":["GPL3"]}, {"ID":2000002,"Name":"liri-shell-git","PackageBaseID":200002,"PackageBase":"liri-shell-git","Version":"0.9.0.r100-1","Description":"Liri Shell - Wayland compositor","URL":"https://liri.io","NumVotes":8,"Popularity":0.08,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-shell-git.tar.gz","Depends":["fluid-git","libliri-git","qt5-wayland","wayland"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"License":["GPL3"]}, {"ID":2000003,"Name":"liri-settings-git","PackageBaseID":200003,"PackageBase":"liri-settings-git","Version":"0.9.0.r50-1","Description":"Liri Settings","URL":"https://liri.io","NumVotes":6,"Popularity":0.06,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-settings-git.tar.gz","Depends":["fluid-git","libliri-git","qt5-declarative"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"License":["GPL3"]}, {"ID":1191231,"Name":"libliri-git","PackageBaseID":122343,"PackageBase":"libliri-git","Version":"r183.5ebe982-1","Description":"Utilities for Liri Quick applications","URL":"https://liri.io","NumVotes":3,"Popularity":0.0,"OutOfDate":1751932023,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/libliri-git.tar.gz","Depends":["qt5-declarative"],"MakeDepends":["git","liri-cmake-shared-git"],"Conflicts":["libliri"],"Provides":["libliri"],"Replaces":["libliri"],"License":["LGPL3"]}, {"ID":2000004,"Name":"fluid-git","PackageBaseID":200004,"PackageBase":"fluid-git","Version":"1.3.0.r50-1","Description":"Fluid - Material Design for Qt Quick","URL":"https://liri.io","NumVotes":15,"Popularity":0.15,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/fluid-git.tar.gz","Depends":["qt5-declarative","qt5-quickcontrols2","qt5-svg","qt5-graphicaleffects"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"Conflicts":["fluid"],"Provides":["fluid"],"License":["MPL2"]}, {"ID":2000005,"Name":"liri-cmake-shared-git","PackageBaseID":200005,"PackageBase":"liri-cmake-shared-git","Version":"1.0.0.r20-1","Description":"Shared CMake functions and macros for Liri","URL":"https://liri.io","NumVotes":5,"Popularity":0.05,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-cmake-shared-git.tar.gz","Depends":["cmake"],"MakeDepends":["git"],"Conflicts":["liri-cmake-shared"],"Provides":["liri-cmake-shared"],"License":["BSD"]} ] ================================================ FILE: pkg/dep/testdata/mesa-git.json ================================================ [ {"ID":3000001,"Name":"mesa-git","PackageBaseID":300001,"PackageBase":"mesa-git","Version":"24.0.0.r1234-1","Description":"Open-source implementation of the OpenGL specification (git version)","URL":"https://www.mesa3d.org/","NumVotes":100,"Popularity":3.0,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/mesa-git.tar.gz","Depends":["libdrm","libxxf86vm","libxdamage","libxshmfence","libelf","libunwind","libglvnd","wayland","lm_sensors","vulkan-icd-loader","zstd","expat"],"MakeDepends":["git","meson","ninja","llvm","clang"],"Provides":["mesa=24.0.0","vulkan-intel=24.0.0","vulkan-radeon=24.0.0","vulkan-driver","opengl-driver","mesa-libgl"],"Conflicts":["mesa","vulkan-intel","vulkan-radeon","mesa-libgl"],"License":["MIT"]} ] ================================================ FILE: pkg/dep/testdata/nx.json ================================================ [ {"ID":1313798,"Name":"libxcomp","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX X compression library","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libjpeg-turbo","libpng","gcc-libs"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, {"ID":1313799,"Name":"nxproxy","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX proxy","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libxcomp"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, {"ID":1313800,"Name":"nx-x11","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX-X11 lib for the NX framework","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libxcomp"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, {"ID":1313801,"Name":"nxagent","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX X server based on Xnest","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["nx-x11","libxcomp","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","libtirpc"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"Conflicts":["nx-xcompext"],"License":["GPL"]} ] ================================================ FILE: pkg/dep/testdata/python-pydantic.json ================================================ [ {"ID":2000001,"Name":"python-pydantic","PackageBaseID":200001,"PackageBase":"python-pydantic","Version":"2.5.0-1","Description":"Data validation using Python type hints","URL":"https://github.com/pydantic/pydantic","NumVotes":50,"Popularity":2.0,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/python-pydantic.tar.gz","Depends":["python","python-typing-extensions"],"MakeDepends":["python-build","python-installer"],"CheckDepends":["python-pytest","python-pytest-mock"],"License":["MIT"]} ] ================================================ FILE: pkg/dep/testdata/samsung-unified-driver.json ================================================ [ {"ID":1734381,"Name":"samsung-unified-driver","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for printers and scanners.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver.tar.gz","Depends":["samsung-unified-driver-printer","samsung-unified-driver-scanner"],"License":["custom:samsung"]}, {"ID":1734378,"Name":"samsung-unified-driver-common","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver (common files)","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-common.tar.gz","License":["custom:samsung"]}, {"ID":1734379,"Name":"samsung-unified-driver-printer","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for printers.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-printer.tar.gz","Depends":["samsung-unified-driver-common","cups","ghostscript"],"License":["custom:samsung"]}, {"ID":1734380,"Name":"samsung-unified-driver-scanner","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for scanners.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-scanner.tar.gz","Depends":["samsung-unified-driver-common","libxml2-legacy","libusb-compat","sane"],"License":["custom:samsung"]} ] ================================================ FILE: pkg/dep/topo/dep.go ================================================ package topo import ( "fmt" "maps" "strings" alpm "github.com/Jguer/dyalpm" ) type ( // NodeSet is a set of nodes represented as a map for O(1) membership checks. // The boolean value is not meaningful; presence of the key indicates membership. NodeSet[T comparable] map[T]bool // ProvidesMap maps a "provides" key (an alias/satisfier name) to information about the // node that provides it. ProvidesMap[T comparable] map[T]*DependencyInfo[T] // DepMap maps a node to a set of nodes. In Graph this is used for adjacency: // - dependencies: node -> its direct dependencies // - dependents: node -> its direct dependents DepMap[T comparable] map[T]NodeSet[T] ) // Slice returns the set contents as a slice in unspecified order. func (n NodeSet[T]) Slice() []T { var slice []T for node := range n { slice = append(slice, node) } return slice } // NodeInfo carries optional rendering metadata (Color/Background) plus the node's value. type NodeInfo[V any] struct { Color string Background string Value V } // DependencyInfo describes which node provides a given dependency/satisfier, along with the // original dependency metadata (alpm.Depend). type DependencyInfo[T comparable] struct { Provider T alpm.Depend } // CheckFn is a callback used by traversal helpers. It receives the node id plus the node's value. type CheckFn[T comparable, V any] func(T, V) error // Graph is a directed dependency graph. // // Edge direction: // - An edge is added with DependOn(child, parent), meaning "child depends on parent". // - Internally, dependencies maps child -> parents (direct dependencies). // - Internally, dependents maps parent -> children (direct dependents). type Graph[T comparable, V any] struct { nodes NodeSet[T] // node info map nodeInfo map[T]*NodeInfo[V] // `provides` tracks provides -> node. provides ProvidesMap[T] // `dependencies` tracks child -> parents. dependencies DepMap[T] // `dependents` tracks parent -> children. dependents DepMap[T] } // New returns an empty Graph. func New[T comparable, V any]() *Graph[T, V] { return &Graph[T, V]{ nodes: make(NodeSet[T]), dependencies: make(DepMap[T]), dependents: make(DepMap[T]), nodeInfo: make(map[T]*NodeInfo[V]), provides: make(ProvidesMap[T]), } } // Len returns the number of nodes currently present in the graph. func (g *Graph[T, V]) Len() int { return len(g.nodes) } // Exists reports whether node exists in the graph's node set. func (g *Graph[T, V]) Exists(node T) bool { _, ok := g.nodes[node] return ok } // AddNode adds node to the graph. It is safe to call multiple times. func (g *Graph[T, V]) AddNode(node T) { g.nodes[node] = true } // HasProvides reports whether the given provides key is registered. func (g *Graph[T, V]) HasProvides(provides T) bool { _, ok := g.provides[provides] return ok } // GetProviderInfo returns the dependency info for a provider. func (g *Graph[T, V]) GetProviderInfo(provides T) *DependencyInfo[T] { return g.provides[provides] } // AddProvides registers that node provides the given provides key. // // Note: despite the "Add" name, this is a single mapping; calling it again with the same // provides key overwrites the previous entry. func (g *Graph[T, V]) AddProvides(provides T, depInfo *alpm.Depend, node T) { g.provides[provides] = &DependencyInfo[T]{ Provider: node, Depend: *depInfo, } } // ForEach calls f for every node in the graph. // // The value passed to f is the node's NodeInfo.Value if set via SetNodeInfo; otherwise it is // the zero value of V. func (g *Graph[T, V]) ForEach(f CheckFn[T, V]) error { for node := range g.nodes { var v V if info := g.nodeInfo[node]; info != nil { v = info.Value } if err := f(node, v); err != nil { return err } } return nil } // SetNodeInfo sets metadata and value for node. Node does not need to already exist in the graph. func (g *Graph[T, V]) SetNodeInfo(node T, nodeInfo *NodeInfo[V]) { g.nodeInfo[node] = nodeInfo } // GetNodeInfo returns metadata/value for node, or nil if none was set. func (g *Graph[T, V]) GetNodeInfo(node T) *NodeInfo[V] { return g.nodeInfo[node] } // DependOn adds an edge meaning "child depends on parent". // // This ensures both nodes exist in the graph and rejects: // - self edges (ErrSelfReferential) // - edges that would introduce a cycle (ErrCircular) func (g *Graph[T, V]) DependOn(child, parent T) error { if child == parent { return ErrSelfReferential } if g.DependsOn(parent, child) { return ErrCircular } g.AddNode(parent) g.AddNode(child) // Add edges. g.dependents.add(parent, child) g.dependencies.add(child, parent) return nil } // String renders the graph in GraphViz DOT format. // // Nodes are emitted as `"node"` entries with optional color metadata from NodeInfo. // Edges are emitted in the direction: dependent -> dependency (child -> parent). func (g *Graph[T, V]) String() string { var sb strings.Builder sb.WriteString("digraph {\n") sb.WriteString("compound=true;\n") sb.WriteString("concentrate=true;\n") sb.WriteString("node [shape = record, ordering=out];\n") for node := range g.nodes { extra := "" if info, ok := g.nodeInfo[node]; ok { if info.Background != "" || info.Color != "" { extra = fmt.Sprintf("[color = %s, style = filled, fillcolor = %s]", info.Color, info.Background) } } sb.WriteString(fmt.Sprintf("\t\"%v\"%s;\n", node, extra)) } for parent, children := range g.dependencies { for child := range children { sb.WriteString(fmt.Sprintf("\t\"%v\" -> \"%v\";\n", parent, child)) } } sb.WriteString("}") return sb.String() } // DependsOn reports whether child depends (transitively) on parent. func (g *Graph[T, V]) DependsOn(child, parent T) bool { deps := g.Dependencies(child) _, ok := deps[parent] return ok } // HasDependent reports whether parent has dependent as a (transitive) dependent. func (g *Graph[T, V]) HasDependent(parent, dependent T) bool { deps := g.Dependents(parent) _, ok := deps[dependent] return ok } // leavesMap returns a map of leaves with the node as key and the node info value as value. func (g *Graph[T, V]) leavesMap() map[T]V { leaves := make(map[T]V, 0) for node := range g.nodes { if _, ok := g.dependencies[node]; !ok { nodeInfo := g.GetNodeInfo(node) if nodeInfo == nil { nodeInfo = &NodeInfo[V]{} } leaves[node] = nodeInfo.Value } } return leaves } // TopoSortedLayers returns a slice of all of the graph nodes in topological sort order with their node info. // // The returned slice is layered: each element is a "layer" of nodes that have no remaining // dependencies at that stage of the process. // // Practical meaning with this graph's edge direction (DependOn(child, parent)): // - Earlier layers contain nodes with fewer/zero dependencies (i.e. dependencies-first order). // - A node appears only after all of its dependencies have appeared in earlier layers. // // If checkFn is non-nil, it is called once per node when it is emitted in a layer. Returning an // error causes TopoSortedLayers to return nil. func (g *Graph[T, V]) TopoSortedLayers(checkFn CheckFn[T, V]) []map[T]V { layers := []map[T]V{} // Copy the graph shrinkingGraph := g.clone() for { leaves := shrinkingGraph.leavesMap() if len(leaves) == 0 { break } layers = append(layers, leaves) for leafNode := range leaves { if checkFn != nil { if err := checkFn(leafNode, leaves[leafNode]); err != nil { return nil } } shrinkingGraph.remove(leafNode) } } return layers } // returns if it was the last func (dm DepMap[T]) remove(key, node T) bool { if nodes := dm[key]; len(nodes) == 1 { // The only element in the nodeset must be `node`, so we // can delete the entry entirely. delete(dm, key) return true } else { // Otherwise, remove the single node from the nodeset. delete(nodes, node) return false } } // Prune removes the node, // its dependencies if there are no other dependents // and its dependents // // It returns the list of nodes that were removed (including node). The returned order is based // on recursive traversal and is not guaranteed to be stable. func (g *Graph[T, V]) Prune(node T) []T { pruned := []T{node} // Remove edges from things that depend on `node`. for dependent := range g.dependents[node] { last := g.dependencies.remove(dependent, node) if last { pruned = append(pruned, g.Prune(dependent)...) } } delete(g.dependents, node) // Remove all edges from node to the things it depends on. for dependency := range g.dependencies[node] { last := g.dependents.remove(dependency, node) if last { pruned = append(pruned, g.Prune(dependency)...) } } delete(g.dependencies, node) // Finally, remove the node itself. delete(g.nodes, node) return pruned } func (g *Graph[T, V]) remove(node T) { // Remove edges from things that depend on `node`. for dependent := range g.dependents[node] { g.dependencies.remove(dependent, node) } delete(g.dependents, node) // Remove all edges from node to the things it depends on. for dependency := range g.dependencies[node] { g.dependents.remove(dependency, node) } delete(g.dependencies, node) // Finally, remove the node itself. delete(g.nodes, node) } // Dependencies returns all transitive dependencies of child (excluding child itself). // The returned set is nil if child is not present in the graph. func (g *Graph[T, V]) Dependencies(child T) NodeSet[T] { return g.buildTransitive(child, g.ImmediateDependencies) } // ImmediateDependencies returns the direct dependencies of node. // The returned set is nil if node has no direct dependencies (or is not present). func (g *Graph[T, V]) ImmediateDependencies(node T) NodeSet[T] { return g.dependencies[node] } // Dependents returns all transitive dependents of parent (excluding parent itself). // The returned set is nil if parent is not present in the graph. func (g *Graph[T, V]) Dependents(parent T) NodeSet[T] { return g.buildTransitive(parent, g.ImmediateDependents) } // ImmediateDependents returns the direct dependents of node. // The returned set is nil if node has no direct dependents (or is not present). func (g *Graph[T, V]) ImmediateDependents(node T) NodeSet[T] { return g.dependents[node] } func (g *Graph[T, V]) clone() *Graph[T, V] { return &Graph[T, V]{ dependencies: g.dependencies.copy(), dependents: g.dependents.copy(), nodes: g.nodes.copy(), nodeInfo: g.nodeInfo, // not copied, as it is not modified } } // buildTransitive starts at `root` and continues calling `nextFn` to keep discovering more nodes until // the graph cannot produce any more. It returns the set of all discovered nodes. func (g *Graph[T, V]) buildTransitive(root T, nextFn func(T) NodeSet[T]) NodeSet[T] { if _, ok := g.nodes[root]; !ok { return nil } out := make(NodeSet[T]) searchNext := []T{root} for len(searchNext) > 0 { // List of new nodes from this layer of the dependency graph. This is // assigned to `searchNext` at the end of the outer "discovery" loop. discovered := []T{} for _, node := range searchNext { // For each node to discover, find the next nodes. for nextNode := range nextFn(node) { // If we have not seen the node before, add it to the output as well // as the list of nodes to traverse in the next iteration. if _, ok := out[nextNode]; !ok { out[nextNode] = true discovered = append(discovered, nextNode) } } } searchNext = discovered } return out } func (s NodeSet[T]) copy() NodeSet[T] { out := make(NodeSet[T], len(s)) maps.Copy(out, s) return out } func (dm DepMap[T]) copy() DepMap[T] { out := make(DepMap[T], len(dm)) for k := range dm { out[k] = dm[k].copy() } return out } func (dm DepMap[T]) add(key, node T) { nodes, ok := dm[key] if !ok { nodes = make(NodeSet[T]) dm[key] = nodes } nodes[node] = true } ================================================ FILE: pkg/dep/topo/dep_test.go ================================================ package topo import ( "testing" "github.com/stretchr/testify/require" ) func TestGraph_DependenciesAndDependents_Direct(t *testing.T) { g := New[string, struct{}]() // yay depends on go require.NoError(t, g.DependOn("yay", "go")) // Dependencies("yay") => {"go"} depsYay := g.Dependencies("yay") require.NotNil(t, depsYay) require.Len(t, depsYay, 1) require.True(t, depsYay["go"]) // Dependencies("go") => {} (empty set, because "go" is in the graph but has no deps) depsGo := g.Dependencies("go") require.NotNil(t, depsGo) require.Len(t, depsGo, 0) // Dependents("go") => {"yay"} dependentsGo := g.Dependents("go") require.NotNil(t, dependentsGo) require.Len(t, dependentsGo, 1) require.True(t, dependentsGo["yay"]) // Dependents("yay") => {} (empty set, because nothing depends on yay) dependentsYay := g.Dependents("yay") require.NotNil(t, dependentsYay) require.Len(t, dependentsYay, 0) } func TestGraph_DependenciesAndDependents_Transitive(t *testing.T) { g := New[string, struct{}]() // yay depends on go; foo depends on yay require.NoError(t, g.DependOn("yay", "go")) require.NoError(t, g.DependOn("foo", "yay")) // Dependencies("foo") => {"yay", "go"} depsFoo := g.Dependencies("foo") require.NotNil(t, depsFoo) require.Len(t, depsFoo, 2) require.True(t, depsFoo["yay"]) require.True(t, depsFoo["go"]) // Dependents("go") => {"yay", "foo"} (transitive) dependentsGo := g.Dependents("go") require.NotNil(t, dependentsGo) require.Len(t, dependentsGo, 2) require.True(t, dependentsGo["yay"]) require.True(t, dependentsGo["foo"]) // Dependents("yay") => {"foo"} dependentsYay := g.Dependents("yay") require.NotNil(t, dependentsYay) require.Len(t, dependentsYay, 1) require.True(t, dependentsYay["foo"]) } func TestGraph_DependenciesAndDependents_MissingNodeReturnsNil(t *testing.T) { g := New[string, struct{}]() // For nodes not present in the graph, transitive queries return nil. require.Nil(t, g.Dependencies("missing")) require.Nil(t, g.Dependents("missing")) // Adding edges adds nodes; existing nodes with no deps/dependents return an empty set (non-nil). require.NoError(t, g.DependOn("yay", "go")) require.NotNil(t, g.Dependencies("go")) require.Len(t, g.Dependencies("go"), 0) require.NotNil(t, g.Dependents("yay")) require.Len(t, g.Dependents("yay"), 0) } ================================================ FILE: pkg/dep/topo/errors.go ================================================ package topo import "errors" var ( // ErrSelfReferential is returned when attempting to create an edge where a node depends on itself. ErrSelfReferential = errors.New(" self-referential dependencies not allowed") // ErrConflictingAlias is reserved for when a "provides" alias is defined more than once. // Note: current Graph APIs overwrite provides entries; this error is not currently returned. ErrConflictingAlias = errors.New(" alias already defined") // ErrCircular is returned when attempting to create an edge that would introduce a cycle. ErrCircular = errors.New(" circular dependencies not allowed") ) ================================================ FILE: pkg/download/abs.go ================================================ package download import ( "context" "errors" "fmt" "io" "net/http" "regexp" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings/exe" ) const ( MaxConcurrentFetch = 20 absPackageURL = "https://gitlab.archlinux.org/archlinux/packaging/packages" ) var ( ErrInvalidRepository = errors.New(gotext.Get("invalid repository")) ErrABSPackageNotFound = errors.New(gotext.Get("package not found in repos")) ) type regexReplace struct { repl string match *regexp.Regexp } // regex replacements for Gitlab URLs // info: https://gitlab.archlinux.org/archlinux/devtools/-/blob/6ce666a1669235749c17d5c44d8a24dea4a135da/src/lib/api/gitlab.sh#L84 var gitlabRepl = []regexReplace{ {repl: `$1-$2`, match: regexp.MustCompile(`([a-zA-Z0-9]+)\+([a-zA-Z]+)`)}, {repl: `plus`, match: regexp.MustCompile(`\+`)}, {repl: `-`, match: regexp.MustCompile(`[^a-zA-Z0-9_\-.]`)}, {repl: `-`, match: regexp.MustCompile(`[_\-]{2,}`)}, {repl: `unix-tree`, match: regexp.MustCompile(`^tree$`)}, } // Return format for pkgbuild // https://gitlab.archlinux.org/archlinux/packaging/packages/0ad/-/raw/main/PKGBUILD func getPackagePKGBUILDURL(pkgName string) string { return fmt.Sprintf("%s/%s/-/raw/main/PKGBUILD", absPackageURL, convertPkgNameForURL(pkgName)) } // Return format for pkgbuild repo // https://gitlab.archlinux.org/archlinux/packaging/packages/0ad.git func getPackageRepoURL(pkgName string) string { return fmt.Sprintf("%s/%s.git", absPackageURL, convertPkgNameForURL(pkgName)) } // convert pkgName for Gitlab URL path (repo name) func convertPkgNameForURL(pkgName string) string { for _, regex := range gitlabRepl { pkgName = regex.match.ReplaceAllString(pkgName, regex.repl) } return pkgName } // ABSPKGBUILD retrieves the PKGBUILD file to a dest directory. func ABSPKGBUILD(httpClient HTTPRequestDoer, dbName, pkgName string) ([]byte, error) { packageURL := getPackagePKGBUILDURL(pkgName) resp, err := httpClient.Get(packageURL) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, ErrABSPackageNotFound } defer resp.Body.Close() pkgBuild, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return pkgBuild, nil } // ABSPKGBUILDRepo retrieves the PKGBUILD repository to a dest directory. func ABSPKGBUILDRepo(ctx context.Context, cmdBuilder exe.GitCmdBuilder, dbName, pkgName, dest string, force bool, ) (bool, error) { pkgURL := getPackageRepoURL(pkgName) return downloadGitRepo(ctx, cmdBuilder, pkgURL, pkgName, dest, force, "--single-branch") } ================================================ FILE: pkg/download/abs_test.go ================================================ //go:build !integration // +build !integration package download import ( "context" "fmt" "os" "os/exec" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/settings/exe" ) const gitExtrasPKGBUILD = `pkgname=git-extras pkgver=6.1.0 pkgrel=1 pkgdesc="GIT utilities -- repo summary, commit counting, repl, changelog population and more" arch=('any') url="https://github.com/tj/${pkgname}" license=('MIT') depends=('git') source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/${pkgver}.tar.gz") sha256sums=('7be0b15ee803d76d2c2e8036f5d9db6677f2232bb8d2c4976691ff7ae026a22f') b2sums=('3450edecb3116e19ffcf918b118aee04f025c06d812e29e8701f35a3c466b13d2578d41c8e1ee93327743d0019bf98bb3f397189e19435f89e3a259ff1b82747') package() { cd "${srcdir}/${pkgname}-${pkgver}" # avoid annoying interactive prompts if an alias is in your gitconfig export GIT_CONFIG=/dev/null make DESTDIR="${pkgdir}" PREFIX=/usr SYSCONFDIR=/etc install install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" }` func Test_getPackageURL(t *testing.T) { t.Parallel() type args struct { db string pkgName string } tests := []struct { name string args args want string wantErr bool }{ { name: "extra package", args: args{ db: "extra", pkgName: "kitty", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/kitty/-/raw/main/PKGBUILD", wantErr: false, }, { name: "core package", args: args{ db: "core", pkgName: "linux", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/raw/main/PKGBUILD", wantErr: false, }, { name: "personal repo package", args: args{ db: "sweswe", pkgName: "zabix", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/zabix/-/raw/main/PKGBUILD", wantErr: false, }, { name: "special name +", args: args{ db: "core", pkgName: "my+package", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package/-/raw/main/PKGBUILD", wantErr: false, }, { name: "special name %", args: args{ db: "core", pkgName: "my%package", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package/-/raw/main/PKGBUILD", wantErr: false, }, { name: "special name _-", args: args{ db: "core", pkgName: "my_-package", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package/-/raw/main/PKGBUILD", wantErr: false, }, { name: "special name ++", args: args{ db: "core", pkgName: "my++package", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/mypluspluspackage/-/raw/main/PKGBUILD", wantErr: false, }, { name: "special name tree", args: args{ db: "sweswe", pkgName: "tree", }, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/unix-tree/-/raw/main/PKGBUILD", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := getPackagePKGBUILDURL(tt.args.pkgName) assert.Equal(t, tt.want, got) }) } } func TestGetABSPkgbuild(t *testing.T) { t.Parallel() type args struct { dbName string body string status int pkgName string wantURL string } tests := []struct { name string args args want string wantErr bool }{ { name: "found package", args: args{ dbName: "core", body: gitExtrasPKGBUILD, status: 200, pkgName: "git-extras", wantURL: "https://gitlab.archlinux.org/archlinux/packaging/packages/git-extras/-/raw/main/PKGBUILD", }, want: gitExtrasPKGBUILD, wantErr: false, }, { name: "not found package", args: args{ dbName: "core", body: "", status: 404, pkgName: "git-git", wantURL: "https://gitlab.archlinux.org/archlinux/packaging/packages/git-git/-/raw/main/PKGBUILD", }, want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() httpClient := &testClient{ t: t, wantURL: tt.args.wantURL, body: tt.args.body, status: tt.args.status, } got, err := ABSPKGBUILD(httpClient, tt.args.dbName, tt.args.pkgName) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, tt.want, string(got)) }) } } func Test_getPackageRepoURL(t *testing.T) { t.Parallel() type args struct { pkgName string } tests := []struct { name string args args want string wantErr bool }{ { name: "extra package", args: args{pkgName: "zoxide"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/zoxide.git", wantErr: false, }, { name: "core package", args: args{pkgName: "linux"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/linux.git", wantErr: false, }, { name: "personal repo package", args: args{pkgName: "sweswe"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/sweswe.git", wantErr: false, }, { name: "special name +", args: args{pkgName: "my+package"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package.git", wantErr: false, }, { name: "special name %", args: args{pkgName: "my%package"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package.git", wantErr: false, }, { name: "special name _-", args: args{pkgName: "my_-package"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/my-package.git", wantErr: false, }, { name: "special name ++", args: args{pkgName: "my++package"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/mypluspluspackage.git", wantErr: false, }, { name: "special name tree", args: args{pkgName: "tree"}, want: "https://gitlab.archlinux.org/archlinux/packaging/packages/unix-tree.git", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := getPackageRepoURL(tt.args.pkgName) assert.Equal(t, tt.want, got) }) } } // GIVEN no previous existing folder // WHEN ABSPKGBUILDRepo is called // THEN a clone command should be formed func TestABSPKGBUILDRepo(t *testing.T) { t.Parallel() cmdRunner := &testRunner{} want := "/usr/local/bin/git --no-replace-objects -C /tmp/doesnt-exist clone --no-progress --single-branch https://gitlab.archlinux.org/archlinux/packaging/packages/linux.git linux" if os.Getuid() == 0 { ld := "systemd-run" if path, _ := exec.LookPath(ld); path != "" { ld = path } want = fmt.Sprintf("%s --service-type=oneshot --pipe --wait --pty --quiet -p DynamicUser=yes -p CacheDirectory=yay -E HOME=/tmp --no-replace-objects -C /tmp/doesnt-exist clone --no-progress --single-branch https://gitlab.archlinux.org/archlinux/packaging/packages/linux.git linux", ld) } cmdBuilder := &testGitBuilder{ index: 0, test: t, want: want, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{"--no-replace-objects"}, }, } newClone, err := ABSPKGBUILDRepo(context.Background(), cmdBuilder, "core", "linux", "/tmp/doesnt-exist", false) assert.NoError(t, err) assert.Equal(t, true, newClone) } // GIVEN a previous existing folder with permissions // WHEN ABSPKGBUILDRepo is called // THEN a pull command should be formed func TestABSPKGBUILDRepoExistsPerms(t *testing.T) { t.Parallel() dir := t.TempDir() os.MkdirAll(filepath.Join(dir, "linux", ".git"), 0o777) want := fmt.Sprintf("/usr/local/bin/git --no-replace-objects -C %s/linux pull --rebase --autostash", dir) if os.Getuid() == 0 { ld := "systemd-run" if path, _ := exec.LookPath(ld); path != "" { ld = path } want = fmt.Sprintf("%s --service-type=oneshot --pipe --wait --pty --quiet -p DynamicUser=yes -p CacheDirectory=yay -E HOME=/tmp --no-replace-objects -C %s/linux pull --rebase --autostash", ld, dir) } cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, want: want, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{"--no-replace-objects"}, }, } newClone, err := ABSPKGBUILDRepo(context.Background(), cmdBuilder, "core", "linux", dir, false) assert.NoError(t, err) assert.Equal(t, false, newClone) } ================================================ FILE: pkg/download/aur.go ================================================ package download import ( "bufio" "bytes" "compress/gzip" "context" "fmt" "io" "net/http" "net/url" "path" "sync" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) func AURPKGBUILD(httpClient HTTPRequestDoer, pkgName, aurURL string) ([]byte, error) { values := url.Values{} values.Set("h", pkgName) pkgURL := aurURL + "/cgit/aur.git/plain/PKGBUILD?" + values.Encode() resp, err := httpClient.Get(pkgURL) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, ErrAURPackageNotFound{pkgName: pkgName} } defer resp.Body.Close() pkgBuild, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return pkgBuild, nil } // AURPkgbuildRepo retrieves the PKGBUILD repository to a dest directory. func AURPKGBUILDRepo(ctx context.Context, cmdBuilder exe.GitCmdBuilder, aurURL, pkgName, dest string, force bool) (bool, error) { pkgURL := fmt.Sprintf("%s/%s.git", aurURL, pkgName) return downloadGitRepo(ctx, cmdBuilder, pkgURL, pkgName, dest, force) } func AURPKGBUILDRepos( ctx context.Context, cmdBuilder exe.GitCmdBuilder, logger *text.Logger, targets []string, aurURL, dest string, force bool, ) (map[string]bool, error) { cloned := make(map[string]bool, len(targets)) var ( mux sync.Mutex errs multierror.MultiError wg sync.WaitGroup ) sem := make(chan uint8, MaxConcurrentFetch) for _, target := range targets { sem <- 1 wg.Add(1) go func(target string) { defer func() { <-sem wg.Done() }() newClone, err := AURPKGBUILDRepo(ctx, cmdBuilder, aurURL, target, dest, force) mux.Lock() progress := len(cloned) if err != nil { errs.Add(err) mux.Unlock() logger.OperationInfoln( gotext.Get("(%d/%d) Failed to download PKGBUILD: %s", progress, len(targets), text.Cyan(target))) return } cloned[target] = newClone progress = len(cloned) mux.Unlock() logger.OperationInfoln( gotext.Get("(%d/%d) Downloaded PKGBUILD: %s", progress, len(targets), text.Cyan(target))) }(target) } wg.Wait() return cloned, errs.Return() } // ScannerCloser combines a bufio.Scanner with a Close method. type ScannerCloser struct { *bufio.Scanner closer io.Closer } // Close closes the underlying gzip reader if present. func (s *ScannerCloser) Close() error { if s.closer != nil { return s.closer.Close() } return nil } // GetPackageScanner fetches the AUR packages.gz file and returns a scanner for reading its contents. // The caller must call Close() on the returned ScannerCloser when done to properly release resources. func GetPackageScanner(ctx context.Context, client HTTPRequestDoer, aurURL string, logger *text.Logger) (*ScannerCloser, error) { u, err := url.Parse(aurURL) if err != nil { return nil, err } u.Path = path.Join(u.Path, "packages.gz") packagesURL := u.String() resp, err := client.Get(packagesURL) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode) } // Read the entire body to allow trying gzip decompression body, err := io.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } // Try to decompress as gzip; if that fails, use raw body var reader io.Reader var closer io.Closer gzReader, gzErr := gzip.NewReader(bytes.NewReader(body)) if gzErr == nil { reader = gzReader closer = gzReader } else { if logger != nil { logger.Debugln("gzip decompression not needed, using raw response body") } reader = bytes.NewReader(body) } scanner := bufio.NewScanner(reader) return &ScannerCloser{ Scanner: scanner, closer: closer, }, nil } ================================================ FILE: pkg/download/aur_test.go ================================================ //go:build !integration // +build !integration package download import ( "context" "fmt" "os" "os/exec" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/settings/exe" ) func TestGetAURPkgbuild(t *testing.T) { t.Parallel() type args struct { body string status int pkgName string wantURL string } tests := []struct { name string args args want string wantErr bool }{ { name: "found package", args: args{ body: gitExtrasPKGBUILD, status: 200, pkgName: "git-extras", wantURL: "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=git-extras", }, want: gitExtrasPKGBUILD, wantErr: false, }, { name: "not found package", args: args{ body: "", status: 404, pkgName: "git-git", wantURL: "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=git-git", }, want: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() httpClient := &testClient{ t: t, wantURL: tt.args.wantURL, body: tt.args.body, status: tt.args.status, } got, err := AURPKGBUILD(httpClient, tt.args.pkgName, "https://aur.archlinux.org") if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } assert.Equal(t, tt.want, string(got)) }) } } // GIVEN no previous existing folder // WHEN AURPKGBUILDRepo is called // THEN a clone command should be formed func TestAURPKGBUILDRepo(t *testing.T) { t.Parallel() want := "/usr/local/bin/git --no-replace-objects -C /tmp/doesnt-exist clone --no-progress https://aur.archlinux.org/yay-bin.git yay-bin" if os.Getuid() == 0 { ld := "systemd-run" if path, _ := exec.LookPath(ld); path != "" { ld = path } want = fmt.Sprintf("%s --service-type=oneshot --pipe --wait --pty --quiet -p DynamicUser=yes -p CacheDirectory=yay -E HOME=/tmp --no-replace-objects -C /tmp/doesnt-exist clone --no-progress https://aur.archlinux.org/yay-bin.git yay-bin", ld) } cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, want: want, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{"--no-replace-objects"}, }, } newCloned, err := AURPKGBUILDRepo(context.Background(), cmdBuilder, "https://aur.archlinux.org", "yay-bin", "/tmp/doesnt-exist", false) assert.NoError(t, err) assert.Equal(t, true, newCloned) } // GIVEN a previous existing folder with permissions // WHEN AURPKGBUILDRepo is called // THEN a pull command should be formed func TestAURPKGBUILDRepoExistsPerms(t *testing.T) { t.Parallel() dir := t.TempDir() os.MkdirAll(filepath.Join(dir, "yay-bin", ".git"), 0o777) want := fmt.Sprintf("/usr/local/bin/git --no-replace-objects -C %s/yay-bin pull --rebase --autostash", dir) if os.Getuid() == 0 { ld := "systemd-run" if path, _ := exec.LookPath(ld); path != "" { ld = path } want = fmt.Sprintf("%s --service-type=oneshot --pipe --wait --pty --quiet -p DynamicUser=yes -p CacheDirectory=yay -E HOME=/tmp --no-replace-objects -C %s/yay-bin pull --rebase --autostash", ld, dir) } cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, want: want, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{"--no-replace-objects"}, }, } cloned, err := AURPKGBUILDRepo(context.Background(), cmdBuilder, "https://aur.archlinux.org", "yay-bin", dir, false) assert.NoError(t, err) assert.Equal(t, false, cloned) } func TestAURPKGBUILDRepos(t *testing.T) { t.Parallel() dir := t.TempDir() os.MkdirAll(filepath.Join(dir, "yay-bin", ".git"), 0o777) targets := []string{"yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, want: "", parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } cloned, err := AURPKGBUILDRepos(context.Background(), cmdBuilder, newTestLogger(), targets, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"yay": true, "yay-bin": false, "yay-git": true}, cloned) } ================================================ FILE: pkg/download/errors.go ================================================ package download import ( "fmt" "github.com/leonelquinteros/gotext" ) // ErrAURPackageNotFound means that package was not found in AUR. type ErrAURPackageNotFound struct { pkgName string } func (e ErrAURPackageNotFound) Error() string { return fmt.Sprintln(gotext.Get("package not found in AUR"), ":", e.pkgName) } type ErrGetPKGBUILDRepo struct { inner error pkgName string errOut string } func (e ErrGetPKGBUILDRepo) Error() string { return fmt.Sprintln(gotext.Get("error fetching %s: %s", e.pkgName, e.errOut), "\n\t context:", e.inner.Error()) } func (e *ErrGetPKGBUILDRepo) Unwrap() error { return e.inner } ================================================ FILE: pkg/download/unified.go ================================================ package download import ( "context" "net/http" "os" "path/filepath" "sync" "github.com/leonelquinteros/gotext" "github.com/Jguer/aur" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) // HTTPRequestDoer is an interface for HTTP clients that can perform GET requests. type HTTPRequestDoer interface { Get(string) (*http.Response, error) } type DBSearcher interface { SyncPackage(string) db.IPackage SyncPackageFromDB(string, string) db.IPackage } func downloadGitRepo(ctx context.Context, cmdBuilder exe.GitCmdBuilder, pkgURL, pkgName, dest string, force bool, gitArgs ...string, ) (bool, error) { finalDir := filepath.Join(dest, pkgName) newClone := true switch _, err := os.Stat(filepath.Join(finalDir, ".git")); { case os.IsNotExist(err) || (err == nil && force): if _, errD := os.Stat(finalDir); force && errD == nil { if errR := os.RemoveAll(finalDir); errR != nil { return false, ErrGetPKGBUILDRepo{inner: errR, pkgName: pkgName, errOut: ""} } } gitArgs = append(gitArgs, pkgURL, pkgName) cloneArgs := make([]string, 0, len(gitArgs)+4) cloneArgs = append(cloneArgs, "clone", "--no-progress") cloneArgs = append(cloneArgs, gitArgs...) cmd := cmdBuilder.BuildGitCmd(ctx, dest, cloneArgs...) _, stderr, errCapture := cmdBuilder.Capture(cmd) if errCapture != nil { return false, ErrGetPKGBUILDRepo{inner: errCapture, pkgName: pkgName, errOut: stderr} } case err != nil: return false, ErrGetPKGBUILDRepo{ inner: err, pkgName: pkgName, errOut: gotext.Get("error reading %s", filepath.Join(dest, pkgName, ".git")), } default: cmd := cmdBuilder.BuildGitCmd(ctx, filepath.Join(dest, pkgName), "pull", "--rebase", "--autostash") _, stderr, errCmd := cmdBuilder.Capture(cmd) if errCmd != nil { return false, ErrGetPKGBUILDRepo{inner: errCmd, pkgName: pkgName, errOut: stderr} } newClone = false } return newClone, nil } func getURLName(pkg db.IPackage) string { name := pkg.Base() if name == "" { name = pkg.Name() } return name } func PKGBUILDs(dbExecutor DBSearcher, aurClient aur.QueryClient, httpClient *http.Client, logger *text.Logger, targets []string, aurURL string, mode parser.TargetMode, ) (map[string][]byte, error) { pkgbuilds := make(map[string][]byte, len(targets)) var ( mux sync.Mutex errs multierror.MultiError wg sync.WaitGroup ) sem := make(chan uint8, MaxConcurrentFetch) for _, target := range targets { // Probably replaceable by something in query. dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, logger, target, mode) if toSkip { continue } sem <- 1 wg.Add(1) go func(target, dbName, pkgName string, aur bool) { var ( err error pkgbuild []byte ) if aur { pkgbuild, err = AURPKGBUILD(httpClient, pkgName, aurURL) } else { pkgbuild, err = ABSPKGBUILD(httpClient, dbName, pkgName) } if err == nil { mux.Lock() pkgbuilds[target] = pkgbuild mux.Unlock() } else { errs.Add(err) } <-sem wg.Done() }(target, dbName, name, isAUR) } wg.Wait() return pkgbuilds, errs.Return() } func PKGBUILDRepos(ctx context.Context, dbExecutor DBSearcher, aurClient aur.QueryClient, cmdBuilder exe.GitCmdBuilder, logger *text.Logger, targets []string, mode parser.TargetMode, aurURL, dest string, force bool, ) (map[string]bool, error) { cloned := make(map[string]bool, len(targets)) var ( mux sync.Mutex errs multierror.MultiError wg sync.WaitGroup ) sem := make(chan uint8, MaxConcurrentFetch) for _, target := range targets { // Probably replaceable by something in query. dbName, name, isAUR, toSkip := getPackageUsableName(dbExecutor, aurClient, logger, target, mode) if toSkip { continue } sem <- 1 wg.Add(1) go func(target, dbName, pkgName string, aur bool) { var ( err error newClone bool ) if aur { newClone, err = AURPKGBUILDRepo(ctx, cmdBuilder, aurURL, pkgName, dest, force) } else { newClone, err = ABSPKGBUILDRepo(ctx, cmdBuilder, dbName, pkgName, dest, force) } progress := 0 if err != nil { errs.Add(err) } else { mux.Lock() cloned[target] = newClone progress = len(cloned) mux.Unlock() } if aur { logger.OperationInfoln( gotext.Get("(%d/%d) Downloaded PKGBUILD: %s", progress, len(targets), text.Cyan(pkgName))) } else { logger.OperationInfoln( gotext.Get("(%d/%d) Downloaded PKGBUILD from ABS: %s", progress, len(targets), text.Cyan(pkgName))) } <-sem wg.Done() }(target, dbName, name, isAUR) } wg.Wait() return cloned, errs.Return() } func getPackageUsableName(dbExecutor DBSearcher, aurClient aur.QueryClient, logger *text.Logger, target string, mode parser.TargetMode, ) (dbname, pkgname string, isAUR, toSkip bool) { dbName, name := text.SplitDBFromName(target) if dbName != "aur" && mode.AtLeastRepo() { var pkg db.IPackage if dbName != "" { pkg = dbExecutor.SyncPackageFromDB(name, dbName) } else { pkg = dbExecutor.SyncPackage(name) } if pkg != nil { return pkg.DB().Name(), getURLName(pkg), false, false } // Package not found in specified DB - skip it if dbName != "" { return dbName, name, true, true } } if mode == parser.ModeRepo { return dbName, name, true, true } pkgs, err := aurClient.Get(context.Background(), &aur.Query{ By: aur.Name, Contains: false, Needles: []string{name}, }) if err != nil { logger.Warnln(err) return dbName, name, true, true } if len(pkgs) == 0 { return dbName, name, true, true } return "aur", name, true, false } ================================================ FILE: pkg/download/unified_integration_test.go ================================================ //go:build integration // +build integration package download import ( "context" "net/http" "os" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/Jguer/aur" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) func TestIntegrationPKGBUILDReposDefinedDBClone(t *testing.T) { dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } targets := []string{"core/linux", "yay-bin", "yay-git"} testLogger := text.NewLogger(os.Stdout, os.Stderr, strings.NewReader(""), true, "test") cmdRunner := &exe.OSRunner{Log: testLogger} cmdBuilder := &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "git", GitFlags: []string{}, Log: testLogger, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"linux": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, testLogger.Child("test"), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"core/linux": true, "yay-bin": true, "yay-git": true}, cloned) } func TestIntegrationPKGBUILDReposNotExist(t *testing.T) { dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } targets := []string{"core/yay", "yay-bin", "yay-git"} testLogger := text.NewLogger(os.Stdout, os.Stderr, strings.NewReader(""), true, "test") cmdRunner := &exe.OSRunner{Log: testLogger} cmdBuilder := &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "git", GitFlags: []string{}, Log: testLogger, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, testLogger.Child("test"), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.Error(t, err) assert.EqualValues(t, map[string]bool{"yay-bin": true, "yay-git": true}, cloned) } // GIVEN 2 aur packages and 1 in repo // WHEN defining as specified targets // THEN all aur be found and cloned func TestIntegrationPKGBUILDFull(t *testing.T) { mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil }, } testLogger := text.NewLogger(os.Stdout, os.Stderr, strings.NewReader(""), true, "test") targets := []string{"core/linux", "aur/yay-bin", "yay-git"} searcher := &testDBSearcher{ absPackagesDB: map[string]string{"linux": "core"}, } fetched, err := PKGBUILDs(searcher, mockClient, &http.Client{}, testLogger.Child("test"), targets, "https://aur.archlinux.org", parser.ModeAny) assert.NoError(t, err) for _, target := range targets { assert.Contains(t, fetched, target) assert.NotEmpty(t, fetched[target]) } } ================================================ FILE: pkg/download/unified_test.go ================================================ //go:build !integration // +build !integration package download import ( "context" "io" "net/http" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "gopkg.in/h2non/gock.v1" "github.com/Jguer/aur" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } // GIVEN 2 aur packages and 1 in repo // GIVEN package in repo is already present // WHEN defining package db as a target // THEN all should be found and cloned, except the repo one func TestPKGBUILDReposDefinedDBPull(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } testLogger := text.NewLogger(os.Stdout, os.Stderr, strings.NewReader(""), true, "test") os.MkdirAll(filepath.Join(dir, "yay", ".git"), 0o777) targets := []string{"core/yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, Log: testLogger, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"core/yay": false, "yay-bin": true, "yay-git": true}, cloned) } // GIVEN 2 aur packages and 1 in repo // WHEN defining package db as a target // THEN all should be found and cloned func TestPKGBUILDReposDefinedDBClone(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } targets := []string{"core/yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"core/yay": true, "yay-bin": true, "yay-git": true}, cloned) } // GIVEN 2 aur packages and 1 in repo // WHEN defining as non specified targets // THEN all should be found and cloned func TestPKGBUILDReposClone(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } targets := []string{"yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"yay": true, "yay-bin": true, "yay-git": true}, cloned) } // GIVEN 2 aur packages and 1 in repo but wrong db // WHEN defining as non specified targets // THEN all aur be found and cloned func TestPKGBUILDReposNotFound(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil // fakes a package found for all }, } targets := []string{"extra/yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"yay-bin": true, "yay-git": true}, cloned) } // GIVEN 2 aur packages and 1 in repo // WHEN defining as non specified targets in repo mode // THEN only repo should be cloned func TestPKGBUILDReposRepoMode(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil // fakes a package found for all }, } targets := []string{"yay", "yay-bin", "yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeRepo, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"yay": true}, cloned) } // GIVEN 2 aur packages and 1 in repo // WHEN defining as specified targets // THEN all aur be found and cloned func TestPKGBUILDFull(t *testing.T) { t.Parallel() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{{}}, nil }, } gock.New("https://aur.archlinux.org"). Get("/cgit/aur.git/plain/PKGBUILD").MatchParam("h", "yay-git"). Reply(200). BodyString("example_yay-git") gock.New("https://aur.archlinux.org"). Get("/cgit/aur.git/plain/PKGBUILD").MatchParam("h", "yay-bin"). Reply(200). BodyString("example_yay-bin") gock.New("https://gitlab.archlinux.org/"). Get("archlinux/packaging/packages/yay/-/raw/main/PKGBUILD"). Reply(200). BodyString("example_yay") defer gock.Off() targets := []string{"core/yay", "aur/yay-bin", "yay-git"} searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } fetched, err := PKGBUILDs(searcher, mockClient, &http.Client{}, newTestLogger(), targets, "https://aur.archlinux.org", parser.ModeAny) assert.NoError(t, err) assert.EqualValues(t, map[string][]byte{ "core/yay": []byte("example_yay"), "aur/yay-bin": []byte("example_yay-bin"), "yay-git": []byte("example_yay-git"), }, fetched) } // GIVEN 2 aur packages and 1 in repo // WHEN aur packages are not found // only repo should be cloned func TestPKGBUILDReposMissingAUR(t *testing.T) { t.Parallel() dir := t.TempDir() mockClient := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil // fakes a package found for all }, } targets := []string{"core/yay", "aur/yay-bin", "aur/yay-git"} cmdRunner := &testRunner{} cmdBuilder := &testGitBuilder{ index: 0, test: t, parentBuilder: &exe.CmdBuilder{ Runner: cmdRunner, GitBin: "/usr/local/bin/git", GitFlags: []string{}, }, } searcher := &testDBSearcher{ absPackagesDB: map[string]string{"yay": "core"}, } cloned, err := PKGBUILDRepos(context.Background(), searcher, mockClient, cmdBuilder, newTestLogger(), targets, parser.ModeAny, "https://aur.archlinux.org", dir, false) assert.NoError(t, err) assert.EqualValues(t, map[string]bool{"core/yay": true}, cloned) } ================================================ FILE: pkg/download/utils_test.go ================================================ package download import ( "context" "io" "net/http" "os/exec" "strings" "testing" "github.com/stretchr/testify/assert" alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/settings/exe" ) type testRunner struct{} func (t *testRunner) Capture(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } func (t *testRunner) Show(cmd *exec.Cmd) error { return nil } type testGitBuilder struct { index int test *testing.T want string parentBuilder *exe.CmdBuilder } func (t *testGitBuilder) BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { cmd := t.parentBuilder.BuildGitCmd(ctx, dir, extraArgs...) if t.want != "" { assert.Equal(t.test, t.want, cmd.String()) } return cmd } func (c *testGitBuilder) Show(cmd *exec.Cmd) error { return c.parentBuilder.Show(cmd) } func (c *testGitBuilder) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { return c.parentBuilder.Capture(cmd) } type ( testDB struct { alpm.Database name string } testPackage struct { *mock.Package db *testDB } testDBSearcher struct { absPackagesDB map[string]string } testClient struct { t *testing.T wantURL string body string status int } ) func (d *testDB) Name() string { return d.name } func (p *testPackage) DB() alpm.Database { return p.db } func (d *testDBSearcher) SyncPackage(name string) db.IPackage { if v, ok := d.absPackagesDB[name]; ok { return &testPackage{ Package: &mock.Package{ PName: name, PBase: name, }, db: &testDB{name: v}, } } return nil } func (d *testDBSearcher) SyncPackageFromDB(name string, db string) db.IPackage { if v, ok := d.absPackagesDB[name]; ok && v == db { return &testPackage{ Package: &mock.Package{ PName: name, PBase: name, }, db: &testDB{name: v}, } } return nil } func (t *testClient) Get(url string) (*http.Response, error) { assert.Equal(t.t, t.wantURL, url) return &http.Response{StatusCode: t.status, Body: io.NopCloser(strings.NewReader(t.body))}, nil } ================================================ FILE: pkg/intrange/intrange.go ================================================ package intrange import ( "strconv" "strings" "unicode" mapset "github.com/deckarep/golang-set/v2" ) // IntRange stores a max and min amount for range. type IntRange struct { min int max int } // IntRanges is a slice of IntRange. type IntRanges []IntRange func makeIntRange(minVal, maxVal int) IntRange { return IntRange{ min: minVal, max: maxVal, } } // Get returns true if the argument n is included in the closed range // between min and max. func (r IntRange) Get(n int) bool { return n >= r.min && n <= r.max } // Get returns true if the argument n is included in the closed range // between min and max of any of the provided IntRanges. func (rs IntRanges) Get(n int) bool { for _, r := range rs { if r.Get(n) { return true } } return false } // ParseNumberMenu parses input for number menus split by spaces or commas // supports individual selection: 1 2 3 4 // supports range selections: 1-4 10-20 // supports negation: ^1 ^1-4 // // include and excule holds numbers that should be added and should not be added // respectively. other holds anything that can't be parsed as an int. This is // intended to allow words inside of number menus. e.g. 'all' 'none' 'abort' // of course the implementation is up to the caller, this function mearley parses // the input and organizes it. func ParseNumberMenu(input string) (include, exclude IntRanges, otherInclude, otherExclude mapset.Set[string], ) { include = make(IntRanges, 0) exclude = make(IntRanges, 0) otherInclude = mapset.NewThreadUnsafeSet[string]() otherExclude = mapset.NewThreadUnsafeSet[string]() words := strings.FieldsFunc(input, func(c rune) bool { return unicode.IsSpace(c) || c == ',' }) for _, word := range words { var ( num1 int num2 int err error ) invert := false other := otherInclude if word[0] == '^' { invert = true other = otherExclude word = word[1:] } ranges := strings.SplitN(word, "-", 2) num1, err = strconv.Atoi(ranges[0]) if err != nil { other.Add(strings.ToLower(word)) continue } if len(ranges) == 2 { num2, err = strconv.Atoi(ranges[1]) if err != nil { other.Add(strings.ToLower(word)) continue } } else { num2 = num1 } mi := min(num1, num2) ma := max(num1, num2) if !invert { include = append(include, makeIntRange(mi, ma)) } else { exclude = append(exclude, makeIntRange(mi, ma)) } } return include, exclude, otherInclude, otherExclude } ================================================ FILE: pkg/intrange/intrange_test.go ================================================ //go:build !integration // +build !integration package intrange import ( "testing" mapset "github.com/deckarep/golang-set/v2" "github.com/stretchr/testify/assert" ) func TestParseNumberMenu(t *testing.T) { t.Parallel() type result struct { Include IntRanges Exclude IntRanges OtherInclude mapset.Set[string] OtherExclude mapset.Set[string] } inputs := []string{ "1 2 3 4 5", "1-10 5-15", "10-5 90-85", "1 ^2 ^10-5 99 ^40-38 ^123 60-62", "abort all none", "a-b ^a-b ^abort", "-9223372036854775809-9223372036854775809", "1\t2 3 4\t\t \t 5", "1 2,3, 4, 5,6 ,7 ,8", "", " \t ", "A B C D E", } expected := []result{ {IntRanges{ makeIntRange(1, 1), makeIntRange(2, 2), makeIntRange(3, 3), makeIntRange(4, 4), makeIntRange(5, 5), }, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{ makeIntRange(1, 10), makeIntRange(5, 15), }, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{ makeIntRange(5, 10), makeIntRange(85, 90), }, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, { IntRanges{ makeIntRange(1, 1), makeIntRange(99, 99), makeIntRange(60, 62), }, IntRanges{ makeIntRange(2, 2), makeIntRange(5, 10), makeIntRange(38, 40), makeIntRange(123, 123), }, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string](), }, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet("abort", "all", "none"), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet("a-b"), mapset.NewThreadUnsafeSet("abort", "a-b")}, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet("-9223372036854775809-9223372036854775809"), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{ makeIntRange(1, 1), makeIntRange(2, 2), makeIntRange(3, 3), makeIntRange(4, 4), makeIntRange(5, 5), }, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{ makeIntRange(1, 1), makeIntRange(2, 2), makeIntRange(3, 3), makeIntRange(4, 4), makeIntRange(5, 5), makeIntRange(6, 6), makeIntRange(7, 7), makeIntRange(8, 8), }, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]()}, {IntRanges{}, IntRanges{}, mapset.NewThreadUnsafeSet("a", "b", "c", "d", "e"), mapset.NewThreadUnsafeSet[string]()}, } for n, in := range inputs { res := expected[n] include, exclude, otherInclude, otherExclude := ParseNumberMenu(in) assert.True(t, intRangesEqual(include, res.Include), "Test %d Failed: Expected: include=%+v got include=%+v", n+1, res.Include, include) assert.True(t, intRangesEqual(exclude, res.Exclude), "Test %d Failed: Expected: exclude=%+v got exclude=%+v", n+1, res.Exclude, exclude) assert.True(t, otherInclude.Equal(res.OtherInclude), "Test %d Failed: Expected: otherInclude=%+v got otherInclude=%+v", n+1, res.OtherInclude, otherInclude) assert.True(t, otherExclude.Equal(res.OtherExclude), "Test %d Failed: Expected: otherExclude=%+v got otherExclude=%+v", n+1, res.OtherExclude, otherExclude) } } func TestIntRange_Get(t *testing.T) { t.Parallel() type fields struct { min int max int } type args struct { n int } tests := []struct { name string fields fields args args want bool }{ {name: "normal range true", fields: fields{0, 10}, args: args{5}, want: true}, {name: "normal start range true", fields: fields{0, 10}, args: args{0}, want: true}, {name: "normal end range true", fields: fields{0, 10}, args: args{10}, want: true}, {name: "small range true", fields: fields{1, 1}, args: args{1}, want: true}, {name: "normal start range false", fields: fields{1, 2}, args: args{0}, want: false}, {name: "normal end range false", fields: fields{1, 2}, args: args{3}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() r := IntRange{ min: tt.fields.min, max: tt.fields.max, } if got := r.Get(tt.args.n); got != tt.want { t.Errorf("IntRange.Get() = %v, want %v", got, tt.want) } }) } } func intRangesEqual(a, b IntRanges) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } if len(a) != len(b) { return false } for n := range a { r1 := a[n] r2 := b[n] if r1.min != r2.min || r1.max != r2.max { return false } } return true } func TestIntRanges_Get(t *testing.T) { t.Parallel() type args struct { n int } tests := []struct { name string rs IntRanges args args want bool }{ {name: "normal range true", rs: IntRanges{{0, 10}}, args: args{5}, want: true}, {name: "normal ranges in between true", rs: IntRanges{{0, 4}, {5, 10}}, args: args{5}, want: true}, {name: "normal ranges in between false", rs: IntRanges{{0, 4}, {6, 10}}, args: args{5}, want: false}, {name: "normal start range true", rs: IntRanges{{0, 10}}, args: args{0}, want: true}, {name: "normal end range true", rs: IntRanges{{0, 10}}, args: args{10}, want: true}, {name: "small range true", rs: IntRanges{{1, 1}, {3, 3}}, args: args{1}, want: true}, {name: "normal start range false", rs: IntRanges{{1, 2}}, args: args{0}, want: false}, {name: "normal end range false", rs: IntRanges{{1, 2}}, args: args{3}, want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := tt.rs.Get(tt.args.n); got != tt.want { t.Errorf("IntRanges.Get() = %v, want %v", got, tt.want) } }) } } ================================================ FILE: pkg/menus/clean_menu.go ================================================ // Clean Build Menu functions package menus import ( "context" "io" "os" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" ) func anyExistInCache(pkgbuildDirs map[string]string) bool { for _, dir := range pkgbuildDirs { if _, err := os.Stat(dir); !os.IsNotExist(err) { return true } } return false } func CleanFn(ctx context.Context, run *runtime.Runtime, w io.Writer, pkgbuildDirsByBase map[string]string, installed mapset.Set[string], ) error { if len(pkgbuildDirsByBase) == 0 { return nil // no work to do } if !anyExistInCache(pkgbuildDirsByBase) { return nil } skipFunc := func(pkg string) bool { dir := pkgbuildDirsByBase[pkg] // TOFIX: new install engine dir will always exist, check if unclean instead if _, err := os.Stat(dir); os.IsNotExist(err) { return true } return false } bases := make([]string, 0, len(pkgbuildDirsByBase)) for pkg := range pkgbuildDirsByBase { bases = append(bases, pkg) } toClean, errClean := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed, gotext.Get("Packages to cleanBuild?"), settings.NoConfirm, run.Cfg.AnswerClean, skipFunc) if errClean != nil { return errClean } for i, base := range toClean { dir := pkgbuildDirsByBase[base] run.Logger.OperationInfoln(gotext.Get("Deleting (%d/%d): %s", i+1, len(toClean), text.Cyan(dir))) if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "reset", "--hard", "origin/HEAD")); err != nil { run.Logger.Warnln(gotext.Get("Unable to clean:"), dir) return err } if err := run.CmdBuilder.Show(run.CmdBuilder.BuildGitCmd(ctx, dir, "clean", "-fdx")); err != nil { run.Logger.Warnln(gotext.Get("Unable to clean:"), dir) return err } } return nil } ================================================ FILE: pkg/menus/diff_menu.go ================================================ // file dedicated to diff menu package menus import ( "context" "fmt" "io" "strings" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) const ( gitEmptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" gitDiffRefName = "AUR_SEEN" ) func showPkgbuildDiffs(ctx context.Context, cmdBuilder exe.ICmdBuilder, logger *text.Logger, pkgbuildDirs map[string]string, bases []string, ) error { var errMulti multierror.MultiError for _, pkg := range bases { dir := pkgbuildDirs[pkg] start, err := getLastSeenHash(ctx, cmdBuilder, dir) if err != nil { errMulti.Add(err) continue } if start != gitEmptyTree { hasDiff, err := gitHasDiff(ctx, cmdBuilder, dir) if err != nil { errMulti.Add(err) continue } if !hasDiff { logger.Warnln(gotext.Get("%s: No changes -- skipping", text.Cyan(pkg))) continue } } args := []string{ "diff", start + "..HEAD@{upstream}", "--src-prefix", dir + "/", "--dst-prefix", dir + "/", "--", ".", ":(exclude).SRCINFO", } if text.UseColor { args = append(args, "--color=always") } else { args = append(args, "--color=never") } _ = cmdBuilder.Show(cmdBuilder.BuildGitCmd(ctx, dir, args...)) } return errMulti.Return() } // Check whether or not a diff exists between the last reviewed diff and // HEAD@{upstream}. func gitHasDiff(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) (bool, error) { if gitHasLastSeenRef(ctx, cmdBuilder, dir) { stdout, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "rev-parse", gitDiffRefName, "HEAD@{upstream}")) if err != nil { return false, fmt.Errorf("%s%w", stderr, err) } lines := strings.Split(stdout, "\n") lastseen := lines[0] upstream := lines[1] return lastseen != upstream, nil } // If AUR_SEEN does not exists, we have never reviewed a diff for this package // and should display it. return true, nil } // Return whether or not we have reviewed a diff yet. It checks for the existence of // AUR_SEEN in the git ref-list. func gitHasLastSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) bool { _, _, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "rev-parse", "--quiet", "--verify", gitDiffRefName)) return err == nil } // Returns the last reviewed hash. If AUR_SEEN exists it will return this hash. // If it does not it will return empty tree as no diff have been reviewed yet. func getLastSeenHash(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) (string, error) { if gitHasLastSeenRef(ctx, cmdBuilder, dir) { stdout, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "rev-parse", gitDiffRefName)) if err != nil { return "", fmt.Errorf("%s %w", stderr, err) } lines := strings.Split(stdout, "\n") return lines[0], nil } return gitEmptyTree, nil } // Update the AUR_SEEN ref to HEAD. We use this ref to determine which diff were // reviewed by the user. func gitUpdateSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) error { _, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "update-ref", gitDiffRefName, "HEAD")) if err != nil { return fmt.Errorf("%s %w", stderr, err) } return nil } func updatePkgbuildSeenRef(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string, bases []string) error { var errMulti multierror.MultiError for _, pkg := range bases { dir := pkgbuildDirs[pkg] if err := gitUpdateSeenRef(ctx, cmdBuilder, dir); err != nil { errMulti.Add(err) } } return errMulti.Return() } func DiffFn(ctx context.Context, run *runtime.Runtime, w io.Writer, pkgbuildDirsByBase map[string]string, installed mapset.Set[string], ) error { if len(pkgbuildDirsByBase) == 0 { return nil // no work to do } bases := make([]string, 0, len(pkgbuildDirsByBase)) for base := range pkgbuildDirsByBase { bases = append(bases, base) } toDiff, errMenu := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed, gotext.Get("Diffs to show?"), settings.NoConfirm, run.Cfg.AnswerDiff, nil) if errMenu != nil || len(toDiff) == 0 { return errMenu } if errD := showPkgbuildDiffs(ctx, run.CmdBuilder, run.Logger, pkgbuildDirsByBase, toDiff); errD != nil { return errD } run.Logger.Println() if !run.Logger.ContinueTask(gotext.Get("Proceed with install?"), true, false) { return settings.ErrUserAbort{} } if errUpd := updatePkgbuildSeenRef(ctx, run.CmdBuilder, pkgbuildDirsByBase, toDiff); errUpd != nil { return errUpd } return nil } ================================================ FILE: pkg/menus/edit_menu.go ================================================ // edit menu package menus import ( "context" "errors" "io" "os" "os/exec" "path/filepath" "strings" gosrc "github.com/Morganamilo/go-srcinfo" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" ) // Editor returns the preferred system editor. func editor(log *text.Logger, editorConfig, editorFlags string, noConfirm bool) (editor string, args []string) { switch { case editorConfig != "": editor, err := exec.LookPath(editorConfig) if err != nil { log.Errorln(err) } else { return editor, strings.Fields(editorFlags) } fallthrough case os.Getenv("VISUAL") != "": if editorArgs := strings.Fields(os.Getenv("VISUAL")); len(editorArgs) != 0 { editor, err := exec.LookPath(editorArgs[0]) if err != nil { log.Errorln(err) } else { return editor, editorArgs[1:] } } fallthrough case os.Getenv("EDITOR") != "": if editorArgs := strings.Fields(os.Getenv("EDITOR")); len(editorArgs) != 0 { editor, err := exec.LookPath(editorArgs[0]) if err != nil { log.Errorln(err) } else { return editor, editorArgs[1:] } } fallthrough default: log.Errorln("\n", gotext.Get("%s is not set", text.Bold(text.Cyan("$EDITOR")))) log.Warnln(gotext.Get("Add %s or %s to your environment variables", text.Bold(text.Cyan("$EDITOR")), text.Bold(text.Cyan("$VISUAL")))) for { log.Infoln(gotext.Get("Edit PKGBUILD with?")) editorInput, err := log.GetInput("", noConfirm) if err != nil { log.Errorln(err) continue } editorArgs := strings.Fields(editorInput) if len(editorArgs) == 0 { continue } editor, err := exec.LookPath(editorArgs[0]) if err != nil { log.Errorln(err) continue } return editor, editorArgs[1:] } } } func editPkgbuilds(log *text.Logger, pkgbuildDirs map[string]string, bases []string, editorConfig, editorFlags string, srcinfos map[string]*gosrc.Srcinfo, noConfirm bool, ) error { pkgbuilds := make([]string, 0, len(bases)) for _, pkg := range bases { dir := pkgbuildDirs[pkg] pkgbuilds = append(pkgbuilds, filepath.Join(dir, "PKGBUILD")) if srcinfos != nil { for _, splitPkg := range srcinfos[pkg].SplitPackages() { if splitPkg.Install != "" { pkgbuilds = append(pkgbuilds, filepath.Join(dir, splitPkg.Install)) } } } } if len(pkgbuilds) > 0 { editor, editorArgs := editor(log, editorConfig, editorFlags, noConfirm) editorArgs = append(editorArgs, pkgbuilds...) editcmd := exec.CommandContext(context.Background(), editor, editorArgs...) editcmd.Stdin, editcmd.Stdout, editcmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := editcmd.Run(); err != nil { return errors.New(gotext.Get("editor did not exit successfully, aborting: %s", err)) } } return nil } func EditFn(ctx context.Context, run *runtime.Runtime, w io.Writer, pkgbuildDirsByBase map[string]string, installed mapset.Set[string], ) error { if len(pkgbuildDirsByBase) == 0 { return nil // no work to do } bases := make([]string, 0, len(pkgbuildDirsByBase)) for pkg := range pkgbuildDirsByBase { bases = append(bases, pkg) } toEdit, errMenu := selectionMenu(run.Logger, pkgbuildDirsByBase, bases, installed, gotext.Get("PKGBUILDs to edit?"), settings.NoConfirm, run.Cfg.AnswerEdit, nil) if errMenu != nil || len(toEdit) == 0 { return errMenu } // TOFIX: remove or use srcinfo data if errEdit := editPkgbuilds(run.Logger, pkgbuildDirsByBase, toEdit, run.Cfg.Editor, run.Cfg.EditorFlags, nil, settings.NoConfirm); errEdit != nil { return errEdit } run.Logger.Println() if !run.Logger.ContinueTask(gotext.Get("Proceed with install?"), true, false) { return settings.ErrUserAbort{} } return nil } ================================================ FILE: pkg/menus/menu.go ================================================ package menus import ( "fmt" "os" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/intrange" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" mapset "github.com/deckarep/golang-set/v2" ) func pkgbuildNumberMenu(logger *text.Logger, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string], ) { toPrint := "" for n, pkgBase := range bases { dir := pkgbuildDirs[pkgBase] toPrint += fmt.Sprintf(text.Magenta("%3d")+" %-40s", len(pkgbuildDirs)-n, text.Bold(pkgBase)) if installed.Contains(pkgBase) { toPrint += text.Bold(text.Green(gotext.Get(" (Installed)"))) } // TODO: remove or refactor to check if git dir is unclean if _, err := os.Stat(dir); !os.IsNotExist(err) { toPrint += text.Bold(text.Green(gotext.Get(" (Build Files Exist)"))) } toPrint += "\n" } logger.Print(toPrint) } func selectionMenu(logger *text.Logger, pkgbuildDirs map[string]string, bases []string, installed mapset.Set[string], message string, noConfirm bool, defaultAnswer string, skipFunc func(string) bool, ) ([]string, error) { selected := make([]string, 0) pkgbuildNumberMenu(logger, pkgbuildDirs, bases, installed) logger.Infoln(message) logger.Infoln(gotext.Get("%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)", text.Cyan(gotext.Get("[N]one")))) selectInput, err := logger.GetInput(defaultAnswer, noConfirm) if err != nil { return nil, err } eInclude, eExclude, eOtherInclude, eOtherExclude := intrange.ParseNumberMenu(selectInput) eIsInclude := len(eExclude) == 0 && eOtherExclude.Cardinality() == 0 if eOtherInclude.Contains("abort") || eOtherInclude.Contains("ab") { return nil, settings.ErrUserAbort{} } if eOtherInclude.Contains("n") || eOtherInclude.Contains("none") { return selected, nil } for i, pkgBase := range bases { if skipFunc != nil && skipFunc(pkgBase) { continue } anyInstalled := installed.Contains(pkgBase) if !eIsInclude && eExclude.Get(len(bases)-i) { continue } if anyInstalled && (eOtherInclude.Contains("i") || eOtherInclude.Contains("installed")) { selected = append(selected, pkgBase) continue } if !anyInstalled && (eOtherInclude.Contains("no") || eOtherInclude.Contains("notinstalled")) { selected = append(selected, pkgBase) continue } if eOtherInclude.Contains("a") || eOtherInclude.Contains("all") { selected = append(selected, pkgBase) continue } if eIsInclude && (eInclude.Get(len(bases)-i) || eOtherInclude.Contains(pkgBase)) { selected = append(selected, pkgBase) } if !eIsInclude && (!eExclude.Get(len(bases)-i) && !eOtherExclude.Contains(pkgBase)) { selected = append(selected, pkgBase) } } return selected, nil } ================================================ FILE: pkg/multierror/multierror.go ================================================ package multierror import "sync" // MultiError type handles error accumulation from goroutines. type MultiError struct { Errors []error mux sync.Mutex } // Error turns the MultiError structure into a string. func (err *MultiError) Error() string { str := "" for _, e := range err.Errors { str += e.Error() + "\n" } return str[:len(str)-1] } // Add adds an error to the Multierror structure. func (err *MultiError) Add(e error) { if e == nil { return } err.mux.Lock() err.Errors = append(err.Errors, e) err.mux.Unlock() } // Return is used as a wrapper on return on whether to return the // MultiError Structure if errors exist or nil instead of delivering an empty structure. func (err *MultiError) Return() error { if len(err.Errors) > 0 { return err } return nil } ================================================ FILE: pkg/news/.snapshots/TestPrintNewsFeed-all-quiet ================================================ 2019-12-20 Xorg cleanup requires manual intervention 2020-01-04 Now using Zstandard instead of xz for package compression 2020-01-15 rsync compatibility 2020-02-17 sshd needs restarting after upgrading to openssh-8.2p1 2020-02-22 Planet Arch Linux migration 2020-02-24 The Future of the Arch Linux Project Leader 2020-03-01 firewalld>=0.8.1-2 update requires manual intervention 2020-03-19 hplip 3.20.3-2 update requires manual intervention 2020-04-13 nss>=3.51.1-1 and lib32-nss>=3.51.1-1 updates require manual intervention 2020-04-14 zn_poly 0.9.2-2 update requires manual intervention ================================================ FILE: pkg/news/.snapshots/TestPrintNewsFeed-all-verbose ================================================ 2019-12-20 Xorg cleanup requires manual intervention In the process of Xorg cleanup the update requires manual intervention when you hit this message: :: installing xorgproto (2019.2-2) breaks dependency 'inputproto' required by lib32-libxi :: installing xorgproto (2019.2-2) breaks dependency 'dmxproto' required by libdmx :: installing xorgproto (2019.2-2) breaks dependency 'xf86dgaproto' required by libxxf86dga :: installing xorgproto (2019.2-2) breaks dependency 'xf86miscproto' required by libxxf86misc  when updating, use: pacman -Rdd libdmx libxxf86dga libxxf86misc && pacman -Syu to perform the upgrade.  2020-01-04 Now using Zstandard instead of xz for package compression As announced on the mailing list, on Friday, Dec 27 2019, our package compression scheme has changed from xz (.pkg.tar.xz) to zstd (.pkg.tar.zst). zstd and xz trade blows in their compression ratio. Recompressing all packages to zstd with our options yields a total ~0.8% increase in package size on all of our packages combined, but the decompression time for all packages saw a ~1300% speedup. We already have more than 545 zstd-compressed packages in our repositories, and as packages get updated more will keep rolling in. We have not found any user-facing issues as of yet, so things appear to be working. As a packager, you will automatically start building .pkg.tar.zst packages if you are using the latest version of devtools (>= 20191227). As an end-user no manual intervention is required, assuming that you have read and followed the news post from late last year. If you nevertheless haven't updated libarchive since 2018, all hope is not lost! Binary builds of pacman-static are available from Eli Schwartz' personal repository (or direct link to binary), signed with their Trusted User keys, with which you can perform the update.  2020-01-15 rsync compatibility Our rsync package was shipped with bundled zlib to provide compatibility with the old-style --compress option up to version 3.1.0. Version 3.1.1 was released on 2014-06-22 and is shipped by all major distributions now. So we decided to finally drop the bundled library and ship a package with system zlib. This also fixes security issues, actual ones and in future. Go and blame those running old versions if you encounter errors with rsync 3.1.3-3.  2020-02-17 sshd needs restarting after upgrading to openssh-8.2p1 After upgrading to openssh-8.2p1, the existing SSH daemon will be unable to accept new connections. (See FS#65517.) When upgrading remote hosts, please make sure to restart the SSH daemon using systemctl restart sshd right after running pacman -Syu. If you are upgrading to openssh-8.2p1-3 or higher, this restart will happen automatically.  2020-02-22 Planet Arch Linux migration The software behind planet.archlinux.org was implemented in Python 2 and is no longer maintained upstream. This functionality has now been implemented in archlinux.org's archweb backend which is actively maintained but offers a slightly different experience. The most notable changes are the offered feeds and the feed location. Archweb only offers an Atom feed which is located at here.  2020-02-24 The Future of the Arch Linux Project Leader Hello everyone, Some of you may know me from the days when I was much more involved in Arch, but most of you probably just know me as a name on the website. I’ve been with Arch for some time, taking the leadership of this beast over from Judd back in 2007. But, as these things often go, my involvement has slid down to minimal levels over time. It’s high time that changes. Arch Linux needs involved leadership to make hard decisions and direct the project where it needs to go. And I am not in a position to do this. In a team effort, the Arch Linux staff devised a new process for determining future leaders. From now on, leaders will be elected by the staff for a term length of two years. Details of this new process can be found here In the first official vote with Levente Polyak (anthraxx), Gaetan Bisson (vesath), Giancarlo Razzolini (grazzolini), and Sven-Hendrik Haase (svenstaro) as candidates, and through 58 verified votes, a winner was chosen: Levente Polyak (anthraxx) will be taking over the reins of this ship. Congratulations! Thanks for everything over all these years, Aaron Griffin (phrakture)  2020-03-01 firewalld>=0.8.1-2 update requires manual intervention The firewalld package prior to version 0.8.1-2 was missing the compiled python modules. This has been fixed in 0.8.1-2, so the upgrade will need to overwrite the untracked pyc files created. If you get errors like these firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/__init__.cpython-38.pyc exists in filesystem firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/client.cpython-38.pyc exists in filesystem firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/dbus_utils.cpython-38.pyc exists in filesystem ...many more...  when updating, use pacman -Suy --overwrite /usr/lib/python3.8/site-packages/firewall/\*  to perform the upgrade.  2020-03-19 hplip 3.20.3-2 update requires manual intervention The hplip package prior to version 3.20.3-2 was missing the compiled python modules. This has been fixed in 3.20.3-2, so the upgrade will need to overwrite the untracked pyc files that were created. If you get errors such as these hplip: /usr/share/hplip/base/__pycache__/__init__.cpython-38.pyc exists in filesystem hplip: /usr/share/hplip/base/__pycache__/avahi.cpython-38.pyc exists in filesystem hplip: /usr/share/hplip/base/__pycache__/codes.cpython-38.pyc exists in filesystem ...many more...  when updating, use pacman -Suy --overwrite /usr/share/hplip/\*  to perform the upgrade.  2020-04-13 nss>=3.51.1-1 and lib32-nss>=3.51.1-1 updates require manual intervention The nss and lib32-nss packages prior to version 3.51.1-1 were missing a soname link each. This has been fixed in 3.51.1-1, so the upgrade will need to overwrite the untracked files created by ldconfig. If you get any of these errors nss: /usr/lib/p11-kit-trust.so exists in filesystem lib32-nss: /usr/lib32/p11-kit-trust.so exists in filesystem  when updating, use pacman -Syu --overwrite /usr/lib\*/p11-kit-trust.so  to perform the upgrade.  2020-04-14 zn_poly 0.9.2-2 update requires manual intervention The zn_poly package prior to version 0.9.2-2 was missing a soname link. This has been fixed in 0.9.2-2, so the upgrade will need to overwrite the untracked files created by ldconfig. If you get an error zn_poly: /usr/lib/libzn_poly-0.9.so exists in filesystem  when updating, use pacman -Syu --overwrite usr/lib/libzn_poly-0.9.so  to perform the upgrade.  ================================================ FILE: pkg/news/.snapshots/TestPrintNewsFeed-latest-quiet ================================================ 2020-04-13 nss>=3.51.1-1 and lib32-nss>=3.51.1-1 updates require manual intervention 2020-04-14 zn_poly 0.9.2-2 update requires manual intervention ================================================ FILE: pkg/news/.snapshots/TestPrintNewsFeed-latest-quiet-topdown ================================================ 2020-04-14 zn_poly 0.9.2-2 update requires manual intervention 2020-04-13 nss>=3.51.1-1 and lib32-nss>=3.51.1-1 updates require manual intervention ================================================ FILE: pkg/news/.snapshots/TestPrintNewsFeedSameDay ================================================ 2020-04-14 zn_poly 0.9.2-2 update requires manual intervention The zn_poly package prior to version 0.9.2-2 was missing a soname link. ================================================ FILE: pkg/news/news.go ================================================ package news import ( "bytes" "context" "encoding/xml" "html" "io" "net/http" "strings" "time" "github.com/Jguer/yay/v12/pkg/text" ) 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) printNews(logger *text.Logger, buildTime time.Time, all, quiet bool) { var fd string date, err := time.Parse(time.RFC1123Z, item.PubDate) if err != nil { logger.Errorln(err) } else { fd = text.FormatTime(int(date.Unix())) if !all && !buildTime.IsZero() { if buildTime.After(date) { return } } } logger.Println(text.Bold(text.Magenta(fd)), text.Bold(strings.TrimSpace(item.Title))) if !quiet { desc := strings.TrimSpace(parseNews(item.Description)) logger.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(ctx context.Context, client *http.Client, logger *text.Logger, cutOffDate time.Time, bottomUp, all, quiet bool, ) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://archlinux.org/feeds/news", http.NoBody) if err != nil { return err } resp, err := client.Do(req) // #nosec G704 if err != nil { return err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return err } rssGot := rss{} d := xml.NewDecoder(bytes.NewReader(body)) if err := d.Decode(&rssGot); err != nil { return err } if bottomUp { for i := len(rssGot.Channel.Items) - 1; i >= 0; i-- { rssGot.Channel.Items[i].printNews(logger, cutOffDate, all, quiet) } } else { for i := 0; i < len(rssGot.Channel.Items); i++ { rssGot.Channel.Items[i].printNews(logger, cutOffDate, all, quiet) } } return nil } // 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 tagBuffer bytes.Buffer escapeBuffer bytes.Buffer inTag = false inEscape = false ) for _, char := range str { if inTag { if char == '>' { inTag = false switch tagBuffer.String() { case "code": buffer.WriteString(text.CyanCode) case "/code": buffer.WriteString(text.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(text.ResetCode) return buffer.String() } ================================================ FILE: pkg/news/news_test.go ================================================ //go:build !integration // +build !integration package news import ( "context" "io" "net/http" "os" "strings" "testing" "time" "github.com/bradleyjkemp/cupaloy" "github.com/stretchr/testify/assert" "gopkg.in/h2non/gock.v1" "github.com/Jguer/yay/v12/pkg/text" ) const lastNews = ` Arch Linux: Recent news updates https://www.archlinux.org/news/ The latest and greatest news from the Arch Linux distribution. en-us Tue, 14 Apr 2020 16:30:32 +0000 zn_poly 0.9.2-2 update requires manual intervention https://www.archlinux.org/news/zn_poly-092-2-update-requires-manual-intervention/ <p>The zn_poly package prior to version 0.9.2-2 was missing a soname link. Antonio Rojas Tue, 14 Apr 2020 16:30:30 +0000 tag:www.archlinux.org,2020-04-14:/news/zn_poly-092-2-update-requires-manual-intervention/ ` const sampleNews = ` Arch Linux: Recent news updateshttps://www.archlinux.org/news/The latest and greatest news from the Arch Linux distribution.en-usTue, 14 Apr 2020 16:30:32 +0000zn_poly 0.9.2-2 update requires manual interventionhttps://www.archlinux.org/news/zn_poly-092-2-update-requires-manual-intervention/<p>The zn_poly package prior to version 0.9.2-2 was missing a soname link. This has been fixed in 0.9.2-2, so the upgrade will need to overwrite the untracked files created by ldconfig. If you get an error</p> <pre><code>zn_poly: /usr/lib/libzn_poly-0.9.so exists in filesystem </code></pre> <p>when updating, use</p> <pre><code>pacman -Syu --overwrite usr/lib/libzn_poly-0.9.so </code></pre> <p>to perform the upgrade.</p>Antonio RojasTue, 14 Apr 2020 16:30:30 +0000tag:www.archlinux.org,2020-04-14:/news/zn_poly-092-2-update-requires-manual-intervention/nss>=3.51.1-1 and lib32-nss>=3.51.1-1 updates require manual interventionhttps://www.archlinux.org/news/nss3511-1-and-lib32-nss3511-1-updates-require-manual-intervention/<p>The nss and lib32-nss packages prior to version 3.51.1-1 were missing a soname link each. This has been fixed in 3.51.1-1, so the upgrade will need to overwrite the untracked files created by ldconfig. If you get any of these errors</p> <pre><code>nss: /usr/lib/p11-kit-trust.so exists in filesystem lib32-nss: /usr/lib32/p11-kit-trust.so exists in filesystem </code></pre> <p>when updating, use</p> <pre><code>pacman -Syu --overwrite /usr/lib\*/p11-kit-trust.so </code></pre> <p>to perform the upgrade.</p>Jan Alexander SteffensMon, 13 Apr 2020 00:35:58 +0000tag:www.archlinux.org,2020-04-13:/news/nss3511-1-and-lib32-nss3511-1-updates-require-manual-intervention/hplip 3.20.3-2 update requires manual interventionhttps://www.archlinux.org/news/hplip-3203-2-update-requires-manual-intervention/<p>The hplip package prior to version 3.20.3-2 was missing the compiled python modules. This has been fixed in 3.20.3-2, so the upgrade will need to overwrite the untracked pyc files that were created. If you get errors such as these</p> <pre><code>hplip: /usr/share/hplip/base/__pycache__/__init__.cpython-38.pyc exists in filesystem hplip: /usr/share/hplip/base/__pycache__/avahi.cpython-38.pyc exists in filesystem hplip: /usr/share/hplip/base/__pycache__/codes.cpython-38.pyc exists in filesystem ...many more... </code></pre> <p>when updating, use</p> <pre><code>pacman -Suy --overwrite /usr/share/hplip/\* </code></pre> <p>to perform the upgrade.</p>Andreas RadkeThu, 19 Mar 2020 06:53:30 +0000tag:www.archlinux.org,2020-03-19:/news/hplip-3203-2-update-requires-manual-intervention/firewalld>=0.8.1-2 update requires manual interventionhttps://www.archlinux.org/news/firewalld081-2-update-requires-manual-intervention/<p>The firewalld package prior to version 0.8.1-2 was missing the compiled python modules. This has been fixed in 0.8.1-2, so the upgrade will need to overwrite the untracked pyc files created. If you get errors like these</p> <pre><code>firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/__init__.cpython-38.pyc exists in filesystem firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/client.cpython-38.pyc exists in filesystem firewalld: /usr/lib/python3.8/site-packages/firewall/__pycache__/dbus_utils.cpython-38.pyc exists in filesystem ...many more... </code></pre> <p>when updating, use</p> <pre><code>pacman -Suy --overwrite /usr/lib/python3.8/site-packages/firewall/\* </code></pre> <p>to perform the upgrade.</p>Jan Alexander SteffensSun, 01 Mar 2020 16:36:48 +0000tag:www.archlinux.org,2020-03-01:/news/firewalld081-2-update-requires-manual-intervention/The Future of the Arch Linux Project Leaderhttps://www.archlinux.org/news/the-future-of-the-arch-linux-project-leader/<p>Hello everyone,</p> <p>Some of you may know me from the days when I was much more involved in Arch, but most of you probably just know me as a name on the website. I’ve been with Arch for some time, taking the leadership of this beast over from Judd back in 2007. But, as these things often go, my involvement has slid down to minimal levels over time. It’s high time that changes.</p> <p>Arch Linux needs involved leadership to make hard decisions and direct the project where it needs to go. And I am not in a position to do this.</p> <p>In a team effort, the Arch Linux staff devised a new process for determining future leaders. From now on, leaders will be elected by the staff for a term length of two years. Details of this new process can be found <a href="https://wiki.archlinux.org/index.php/DeveloperWiki:Project_Leader">here</a></p> <p>In the first official vote with Levente Polyak (anthraxx), Gaetan Bisson (vesath), Giancarlo Razzolini (grazzolini), and Sven-Hendrik Haase (svenstaro) as candidates, and through 58 verified votes, a winner was chosen:</p> <p><strong>Levente Polyak (anthraxx) will be taking over the reins of this ship. Congratulations!</strong></p> <p><em>Thanks for everything over all these years,<br /> Aaron Griffin (phrakture)</em></p>Aaron GriffinMon, 24 Feb 2020 15:56:28 +0000tag:www.archlinux.org,2020-02-24:/news/the-future-of-the-arch-linux-project-leader/Planet Arch Linux migrationhttps://www.archlinux.org/news/planet-arch-linux-migration/<p>The software behind planet.archlinux.org was implemented in Python 2 and is no longer maintained upstream. This functionality has now been implemented in archlinux.org's archweb backend which is actively maintained but offers a slightly different experience.</p> <p>The most notable changes are the offered feeds and the feed location. Archweb only offers an Atom feed which is located at <a href="https://archlinux.org/feeds/planet">here</a>.</p>Jelle van der WaaSat, 22 Feb 2020 22:43:00 +0000tag:www.archlinux.org,2020-02-22:/news/planet-arch-linux-migration/sshd needs restarting after upgrading to openssh-8.2p1https://www.archlinux.org/news/sshd-needs-restarting-after-upgrading-to-openssh-82p1/<p>After upgrading to openssh-8.2p1, the existing SSH daemon will be unable to accept new connections. (See <a href="https://bugs.archlinux.org/task/65517">FS#65517</a>.) When upgrading remote hosts, please make sure to restart the SSH daemon using <code>systemctl restart sshd</code> right after running <code>pacman -Syu</code>. If you are upgrading to openssh-8.2p1-3 or higher, this restart will happen automatically.</p>Gaetan BissonMon, 17 Feb 2020 01:35:04 +0000tag:www.archlinux.org,2020-02-17:/news/sshd-needs-restarting-after-upgrading-to-openssh-82p1/rsync compatibilityhttps://www.archlinux.org/news/rsync-compatibility/<p>Our <code>rsync</code> package was shipped with bundled <code>zlib</code> to provide compatibility with the old-style <code>--compress</code> option up to version 3.1.0. Version 3.1.1 was released on 2014-06-22 and is shipped by all major distributions now.</p> <p>So we decided to finally drop the bundled library and ship a package with system <code>zlib</code>. This also fixes security issues, actual ones and in future. Go and blame those running old versions if you encounter errors with <code>rsync 3.1.3-3</code>.</p>Christian HesseWed, 15 Jan 2020 20:14:43 +0000tag:www.archlinux.org,2020-01-15:/news/rsync-compatibility/Now using Zstandard instead of xz for package compressionhttps://www.archlinux.org/news/now-using-zstandard-instead-of-xz-for-package-compression/<p>As announced on the <a href="https://lists.archlinux.org/pipermail/arch-dev-public/2019-December/029752.html">mailing list</a>, on Friday, Dec 27 2019, our package compression scheme has changed from xz (.pkg.tar.xz) to <a href="https://lists.archlinux.org/pipermail/arch-dev-public/2019-December/029778.html">zstd (.pkg.tar.zst)</a>.</p> <p>zstd and xz trade blows in their compression ratio. Recompressing all packages to zstd with our options yields a total ~0.8% increase in package size on all of our packages combined, but the decompression time for all packages saw a ~1300% speedup.</p> <p>We already have more than 545 zstd-compressed packages in our repositories, and as packages get updated more will keep rolling in. We have not found any user-facing issues as of yet, so things appear to be working.</p> <p>As a packager, you will automatically start building .pkg.tar.zst packages if you are using the latest version of devtools (&gt;= 20191227).<br /> As an end-user no manual intervention is required, assuming that you have read and followed the news post <a href="https://www.archlinux.org/news/required-update-to-recent-libarchive/">from late last year</a>.</p> <p>If you nevertheless haven't updated libarchive since 2018, all hope is not lost! Binary builds of pacman-static are available from Eli Schwartz' <a href="https://wiki.archlinux.org/index.php/Unofficial_user_repositories#eschwartz">personal repository</a> (or direct link to <a href="https://pkgbuild.com/~eschwartz/repo/x86_64-extracted/">binary</a>), signed with their Trusted User keys, with which you can perform the update.</p>Robin BrodaSat, 04 Jan 2020 20:35:55 +0000tag:www.archlinux.org,2020-01-04:/news/now-using-zstandard-instead-of-xz-for-package-compression/Xorg cleanup requires manual interventionhttps://www.archlinux.org/news/xorg-cleanup-requires-manual-intervention/<p>In the process of <a href="https://bugs.archlinux.org/task/64892">Xorg cleanup</a> the update requires manual intervention when you hit this message:</p> <pre><code>:: installing xorgproto (2019.2-2) breaks dependency 'inputproto' required by lib32-libxi :: installing xorgproto (2019.2-2) breaks dependency 'dmxproto' required by libdmx :: installing xorgproto (2019.2-2) breaks dependency 'xf86dgaproto' required by libxxf86dga :: installing xorgproto (2019.2-2) breaks dependency 'xf86miscproto' required by libxxf86misc </code></pre> <p>when updating, use: <code>pacman -Rdd libdmx libxxf86dga libxxf86misc &amp;&amp; pacman -Syu</code> to perform the upgrade.</p>Andreas RadkeFri, 20 Dec 2019 13:37:40 +0000tag:www.archlinux.org,2019-12-20:/news/xorg-cleanup-requires-manual-intervention/ ` func TestPrintNewsFeed(t *testing.T) { layout := "2006-01-02" str := "2020-04-13" lastNewsTime, _ := time.Parse(layout, str) type args struct { cutOffDate time.Time bottomUp bool all bool quiet bool } tests := []struct { name string args args wantErr bool }{ {name: "all-verbose", args: args{bottomUp: true, cutOffDate: time.Now(), all: true, quiet: false}, wantErr: false}, {name: "all-quiet", args: args{bottomUp: true, cutOffDate: lastNewsTime, all: true, quiet: true}, wantErr: false}, {name: "latest-quiet", args: args{bottomUp: true, cutOffDate: lastNewsTime, all: false, quiet: true}, wantErr: false}, {name: "latest-quiet-topdown", args: args{bottomUp: false, cutOffDate: lastNewsTime, all: false, quiet: true}, wantErr: false}, } t.Setenv("TZ", "UTC") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gock.New("https://archlinux.org"). Get("/feeds/news"). Reply(200). BodyString(sampleNews) defer gock.Off() r, w, _ := os.Pipe() logger := text.NewLogger(w, w, strings.NewReader(""), false, "logger") err := PrintNewsFeed(context.Background(), &http.Client{}, logger, tt.args.cutOffDate, tt.args.bottomUp, tt.args.all, tt.args.quiet) assert.NoError(t, err) w.Close() out, _ := io.ReadAll(r) cupaloy.SnapshotT(t, out) }) } } // GIVEN last build time at 13h00 // WHEN there's a news posted at 18h00 // THEN it should still be printed func TestPrintNewsFeedSameDay(t *testing.T) { str := "2020-04-14T13:04:05Z" lastNewsTime, _ := time.Parse(time.RFC3339, str) gock.New("https://archlinux.org"). Get("/feeds/news"). Reply(200). BodyString(lastNews) defer gock.Off() r, w, _ := os.Pipe() logger := text.NewLogger(w, w, strings.NewReader(""), false, "logger") err := PrintNewsFeed(context.Background(), &http.Client{}, logger, lastNewsTime, true, false, false) assert.NoError(t, err) w.Close() out, _ := io.ReadAll(r) cupaloy.SnapshotT(t, out) } ================================================ FILE: pkg/query/aur_warnings.go ================================================ package query import ( "strings" "github.com/leonelquinteros/gotext" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/text" ) type AURWarnings struct { Orphans []string OutOfDate []string Missing []string LocalNewer []string log *text.Logger } func NewWarnings(logger *text.Logger) *AURWarnings { return &AURWarnings{log: logger} } func (warnings *AURWarnings) AddToWarnings(remote map[string]alpm.Package, aurPkg *aur.Pkg) { name := aurPkg.Name pkg, ok := remote[name] if !ok { return } if aurPkg.Maintainer == "" && !pkg.ShouldIgnore() { warnings.Orphans = append(warnings.Orphans, name) } if aurPkg.OutOfDate != 0 && !pkg.ShouldIgnore() { warnings.OutOfDate = append(warnings.OutOfDate, name) } if !pkg.ShouldIgnore() && !isDevelPackage(pkg) && db.VerCmp(pkg.Version(), aurPkg.Version) > 0 { left, right := GetVersionDiff(pkg.Version(), aurPkg.Version) newerMsg := gotext.Get("%s: local (%s) is newer than AUR (%s)", text.Cyan(name), left, right, ) warnings.LocalNewer = append(warnings.LocalNewer, newerMsg) } } func (warnings *AURWarnings) CalculateMissing(remoteNames []string, remote map[string]alpm.Package, aurData map[string]*aur.Pkg, ) { for _, name := range remoteNames { if _, ok := aurData[name]; !ok && !remote[name].ShouldIgnore() { if _, ok := aurData[strings.TrimSuffix(name, "-debug")]; !ok { warnings.Missing = append(warnings.Missing, name) } } } } func (warnings *AURWarnings) Print() { normalMissing, debugMissing := filterDebugPkgs(warnings.Missing) if len(normalMissing) > 0 { warnings.log.Warnln(gotext.Get("Packages not in AUR:"), formatNames(normalMissing)) } if len(debugMissing) > 0 { warnings.log.Warnln(gotext.Get("Missing AUR Debug Packages:"), formatNames(debugMissing)) } if len(warnings.Orphans) > 0 { warnings.log.Warnln(gotext.Get("Orphan (unmaintained) AUR Packages:"), formatNames(warnings.Orphans)) } if len(warnings.OutOfDate) > 0 { warnings.log.Warnln(gotext.Get("Flagged Out Of Date AUR Packages:"), formatNames(warnings.OutOfDate)) } if len(warnings.LocalNewer) > 0 { for _, newer := range warnings.LocalNewer { warnings.log.Warnln(newer) } } } func filterDebugPkgs(names []string) (normal, debug []string) { normal = make([]string, 0, len(names)) debug = make([]string, 0, len(names)) for _, name := range names { if strings.HasSuffix(name, "-debug") { debug = append(debug, name) } else { normal = append(normal, name) } } return } func formatNames(names []string) string { return " " + text.Cyan(strings.Join(names, " ")) } ================================================ FILE: pkg/query/errors.go ================================================ package query import ( "github.com/leonelquinteros/gotext" ) // ErrAURSearch means that it was not possible to connect to the AUR. type ErrAURSearch struct { inner error } func (e ErrAURSearch) Error() string { return gotext.Get("Error during AUR search: %s\n", e.inner.Error()) } // ErrNoQuery means that query was not executed. type ErrNoQuery struct{} func (e ErrNoQuery) Error() string { return gotext.Get("no query was executed") } type ErrTargetNotFound struct{} func (e ErrTargetNotFound) Error() string { return gotext.Get("no package found for targets") } ================================================ FILE: pkg/query/filter.go ================================================ package query import ( "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) func RemoveInvalidTargets(logger *text.Logger, targets []string, mode parser.TargetMode) []string { filteredTargets := make([]string, 0) for _, target := range targets { dbName, _ := text.SplitDBFromName(target) if dbName == "aur" && !mode.AtLeastAUR() { logger.Warnln(gotext.Get("%s: can't use target with option --repo -- skipping", text.Cyan(target))) continue } if dbName != "aur" && dbName != "" && !mode.AtLeastRepo() { logger.Warnln(gotext.Get("%s: can't use target with option --aur -- skipping", text.Cyan(target))) continue } filteredTargets = append(filteredTargets, target) } return filteredTargets } ================================================ FILE: pkg/query/metric.go ================================================ package query import ( "strings" "github.com/adrg/strutil" ) const minVotes = 30 const minPopularity = 0.5 const ( separateSourceMax = 45.0 separateSourceMin = 5.0 ) func (a *abstractResults) aurSortByMetric(pkg *abstractResult) float64 { votesScore := 1 - (minVotes / (minVotes + float64(pkg.votes))) if pkg.popularity <= 0 { return votesScore } popularityScore := 1 - (minPopularity / (minPopularity + pkg.popularity)) return (votesScore + popularityScore) / 2 } func (a *abstractResults) GetMetric(pkg *abstractResult) float64 { if v, ok := a.distanceCache[pkg.name]; ok { return v } if strings.EqualFold(pkg.name, a.search) { return 1.0 } sim := strutil.Similarity(pkg.name, a.search, a.metric) for _, prov := range pkg.provides { // If the package provides search, it's a perfect match // AUR packages don't populate provides candidate := strutil.Similarity(prov, a.search, a.metric) * 0.80 if candidate > sim { sim = candidate } } simDesc := strutil.Similarity(pkg.description, a.search, a.metric) // slightly overweight sync sources by always giving them max popularity popularity := 1.0 if pkg.source == "aur" { popularity = a.aurSortByMetric(pkg) } sim = sim*0.5 + simDesc*0.2 + popularity*0.3 a.distanceCache[pkg.name] = sim return sim } func (a *abstractResults) separateSourceScore(source string, score float64) float64 { if !a.separateSources { return 0 } if score == 1.0 { return 50 } if v, ok := a.separateSourceCache[source]; ok { return v } // AUR is always lowest priority if source == "aur" { return 0 } // Score sync repositories based on pacman.conf order (as reflected by dbExecutor.Repos()). // First repo gets max, last repo gets min, evenly distributed across the range. for i, repo := range a.repoOrder { if repo != source { continue } n := len(a.repoOrder) if n == 1 { a.separateSourceCache[source] = separateSourceMax return separateSourceMax } step := (separateSourceMax - separateSourceMin) / float64(n-1) sourceScore := separateSourceMax - (float64(i) * step) a.separateSourceCache[source] = sourceScore return sourceScore } return 0 } func (a *abstractResults) calculateMetric(pkg *abstractResult) float64 { score := a.GetMetric(pkg) return a.separateSourceScore(pkg.source, score) + score } ================================================ FILE: pkg/query/metric_test.go ================================================ //go:build !integration // +build !integration package query import ( "testing" "github.com/stretchr/testify/assert" ) func TestSeparateSourceScore_UsesRepoOrderEvenlyDistributed(t *testing.T) { t.Parallel() // Any non-1.0 score avoids the special-case 50 return. const sim = 0.5 const delta = 1e-1 t.Run("arch repos (core/extra/community/multilib)", func(t *testing.T) { a := &abstractResults{ separateSources: true, repoOrder: []string{"core", "extra", "community", "multilib"}, separateSourceCache: map[string]float64{}, } assert.InDelta(t, 45.0, a.separateSourceScore("core", sim), delta) assert.InDelta(t, 31.6, a.separateSourceScore("extra", sim), delta) assert.InDelta(t, 18.3, a.separateSourceScore("community", sim), delta) assert.InDelta(t, 5.0, a.separateSourceScore("multilib", sim), delta) assert.Equal(t, 0.0, a.separateSourceScore("aur", sim)) }) t.Run("arch arm repos (core/extra/alarm/aur)", func(t *testing.T) { a := &abstractResults{ separateSources: true, repoOrder: []string{"core", "extra", "alarm", "aur"}, separateSourceCache: map[string]float64{}, } // Note: AUR is not a sync repository; it is always lowest priority (0) regardless of repo order. assert.InDelta(t, 45.0, a.separateSourceScore("core", sim), delta) assert.InDelta(t, 31.6, a.separateSourceScore("extra", sim), delta) assert.InDelta(t, 18.3, a.separateSourceScore("alarm", sim), delta) assert.Equal(t, 0.0, a.separateSourceScore("aur", sim)) }) } ================================================ FILE: pkg/query/query_builder.go ================================================ package query import ( "cmp" "context" "sort" "strconv" "strings" "unicode" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/adrg/strutil" "github.com/adrg/strutil/metrics" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/intrange" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) type SearchVerbosity int // Verbosity settings for search. const ( NumberMenu SearchVerbosity = iota Detailed Minimal ) type Builder interface { Len() int Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error GetTargets(include, exclude intrange.IntRanges, otherExclude mapset.Set[string]) ([]string, error) } type SortFunc func(pkgA, pkgB abstractResult) int type SourceQueryBuilder struct { results []abstractResult sortBy string searchBy string targetMode parser.TargetMode queryMap map[string]map[string]any bottomUp bool singleLineResults bool separateSources bool aurClient aur.QueryClient logger *text.Logger } func NewSourceQueryBuilder( aurClient aur.QueryClient, logger *text.Logger, sortBy string, targetMode parser.TargetMode, searchBy string, bottomUp, singleLineResults bool, separateSources bool, ) *SourceQueryBuilder { return &SourceQueryBuilder{ aurClient: aurClient, logger: logger, bottomUp: bottomUp, sortBy: sortBy, targetMode: targetMode, searchBy: searchBy, singleLineResults: singleLineResults, separateSources: separateSources, queryMap: map[string]map[string]any{}, results: make([]abstractResult, 0, 100), } } type abstractResult struct { source string name string description string packageBase string votes int popularity float64 firstSubmitted int lastModified int provides []string } type abstractResults struct { results []abstractResult search string metric strutil.StringMetric separateSources bool sortByFunc SortFunc repoOrder []string distanceCache map[string]float64 separateSourceCache map[string]float64 } func (a *abstractResults) Len() int { return len(a.results) } func (a *abstractResults) Swap(i, j int) { a.results[i], a.results[j] = a.results[j], a.results[i] } func (a *abstractResults) Less(i, j int) bool { pkgA := a.results[i] pkgB := a.results[j] // Sort in descending order by default return a.sortByFunc(pkgA, pkgB) > 0 } func (a *abstractResults) GetSortFunc(sortBy string, bottomUp bool) SortFunc { var sortFunc SortFunc // Primary sort switch sortBy { case "base": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.packageBase, pkgB.packageBase) } case "modified": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.lastModified, pkgB.lastModified) } case "name": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.name, pkgB.name) } case "popularity": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.popularity, pkgB.popularity) } case "submitted": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.firstSubmitted, pkgB.firstSubmitted) } case "votes": sortFunc = func(pkgA, pkgB abstractResult) int { return cmp.Compare(pkgA.votes, pkgB.votes) } default: sortFunc = func(pkgA, pkgB abstractResult) int { return 0 } } // Sort by metric as a tie-breaker. Also handle separating sources when not a tie { originalSortFunc := sortFunc sortFunc = func(pkgA, pkgB abstractResult) int { if cmpResult := originalSortFunc(pkgA, pkgB); cmpResult != 0 { if a.separateSources { if cmpSources := strings.Compare(pkgA.source, pkgB.source); cmpSources != 0 { return cmpSources } } return cmpResult } metricA := a.calculateMetric(&pkgA) metricB := a.calculateMetric(&pkgB) return cmp.Compare(metricA, metricB) } } if bottomUp { // Invert sort for bottom-up sorting originalSortFunc := sortFunc sortFunc = func(pkgA, pkgB abstractResult) int { return -originalSortFunc(pkgA, pkgB) } } return sortFunc } func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) { var aurErr error pkgS = RemoveInvalidTargets(s.logger, pkgS, s.targetMode) metric := &metrics.Hamming{ CaseSensitive: false, } sortableResults := &abstractResults{ results: []abstractResult{}, search: strings.Join(pkgS, ""), metric: metric, separateSources: s.separateSources, repoOrder: dbExecutor.Repos(), distanceCache: map[string]float64{}, separateSourceCache: map[string]float64{}, } sortableResults.sortByFunc = sortableResults.GetSortFunc(s.sortBy, s.bottomUp) var repoResults []alpm.Package if s.targetMode.AtLeastRepo() { repoResults = dbExecutor.SyncPackages(pkgS...) for i := range repoResults { dbName := repoResults[i].DB().Name() if s.queryMap[dbName] == nil { s.queryMap[dbName] = map[string]any{} } s.queryMap[dbName][repoResults[i].Name()] = repoResults[i] rawProvides := repoResults[i].Provides() provides := make([]string, len(rawProvides)) for j := range rawProvides { provides[j] = rawProvides[j].Name } sortableResults.results = append(sortableResults.results, abstractResult{ source: repoResults[i].DB().Name(), name: repoResults[i].Name(), description: repoResults[i].Description(), packageBase: repoResults[i].Base(), votes: -1, popularity: -1, firstSubmitted: -1, lastModified: -1, provides: provides, }) } } if s.targetMode.AtLeastAUR() { var aurResults []aur.Pkg aurResults, aurErr = queryAUR(ctx, s.aurClient, pkgS, s.searchBy) dbName := "aur" for i := range aurResults { if s.queryMap[dbName] == nil { s.queryMap[dbName] = map[string]any{} } by := getSearchBy(s.searchBy) if (by == aur.NameDesc || by == aur.None || by == aur.Name) && !matchesSearch(&aurResults[i], pkgS) { continue } s.queryMap[dbName][aurResults[i].Name] = aurResults[i] sortableResults.results = append(sortableResults.results, abstractResult{ source: dbName, name: aurResults[i].Name, description: aurResults[i].Description, packageBase: aurResults[i].PackageBase, votes: aurResults[i].NumVotes, popularity: aurResults[i].Popularity, firstSubmitted: aurResults[i].FirstSubmitted, lastModified: aurResults[i].LastModified, provides: aurResults[i].Provides, }) } } sort.Sort(sortableResults) s.results = sortableResults.results if aurErr != nil { s.logger.Errorln(ErrAURSearch{inner: aurErr}) if len(repoResults) != 0 { s.logger.Warnln(gotext.Get("Showing repo packages only")) } } } func (s *SourceQueryBuilder) Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error { for i := range s.results { if verboseSearch == Minimal { s.logger.Println(s.results[i].name) continue } var toPrint string if verboseSearch == NumberMenu { if s.bottomUp { toPrint += text.Magenta(strconv.Itoa(len(s.results)-i)) + " " } else { toPrint += text.Magenta(strconv.Itoa(i+1)) + " " } } pkg := s.queryMap[s.results[i].source][s.results[i].name] switch pPkg := pkg.(type) { case aur.Pkg: toPrint += aurPkgSearchString(&pPkg, dbExecutor, s.singleLineResults) case alpm.Package: toPrint += syncPkgSearchString(pPkg, dbExecutor, s.singleLineResults) } s.logger.Println(toPrint) } return nil } func (s *SourceQueryBuilder) Len() int { return len(s.results) } func (s *SourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges, otherExclude mapset.Set[string], ) ([]string, error) { var ( isInclude = len(exclude) == 0 && otherExclude.Cardinality() == 0 targets []string lenRes = len(s.results) ) for i := 1; i <= s.Len(); i++ { target := i - 1 if s.bottomUp { target = lenRes - i } if (isInclude && include.Get(i)) || (!isInclude && !exclude.Get(i)) { targets = append(targets, s.results[target].source+"/"+s.results[target].name) } } return targets, nil } func matchesSearch(pkg *aur.Pkg, terms []string) bool { if len(terms) <= 1 { return true } for _, pkgN := range terms { if strings.IndexFunc(pkgN, unicode.IsSymbol) != -1 { return true } name := strings.ToLower(pkg.Name) desc := strings.ToLower(pkg.Description) targ := strings.ToLower(pkgN) if !strings.Contains(name, targ) && !strings.Contains(desc, targ) { return false } } return true } ================================================ FILE: pkg/query/query_builder_test.go ================================================ //go:build !integration // +build !integration package query import ( "context" "io" "strings" "testing" "github.com/Jguer/aur" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/stretchr/testify/assert" ) func TestSourceQueryBuilder(t *testing.T) { t.Parallel() type testCase struct { desc string search []string bottomUp bool separateSources bool sortBy string verbosity SearchVerbosity targetMode parser.TargetMode singleLineResults bool searchBy string wantResults []string wantOutput []string } testCases := []testCase{ { desc: "sort-by-metric bottomup separatesources", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "", verbosity: Detailed, wantResults: []string{"linux-ck", "linux-zen", "linux"}, wantOutput: []string{ "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", }, }, { desc: "sort-by-metric topdown separatesources", search: []string{"linux"}, bottomUp: false, separateSources: true, sortBy: "", verbosity: Detailed, wantResults: []string{"linux", "linux-zen", "linux-ck"}, wantOutput: []string{ "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, { desc: "sort-by-metric bottomup noseparatesources", search: []string{"linux"}, bottomUp: true, separateSources: false, sortBy: "", verbosity: Detailed, wantResults: []string{"linux-ck", "linux-zen", "linux"}, wantOutput: []string{ "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", }, }, { desc: "sort-by-metric topdown noseparatesources", search: []string{"linux"}, bottomUp: false, separateSources: false, sortBy: "", verbosity: Detailed, wantResults: []string{"linux", "linux-zen", "linux-ck"}, wantOutput: []string{ "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, { desc: "sort-by-metric bottomup separatesources number-menu", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "", verbosity: NumberMenu, wantResults: []string{"linux-ck", "linux-zen", "linux"}, wantOutput: []string{ "\x1b[35m3\x1b[0m \x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", "\x1b[35m2\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b[35m1\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", }, }, { desc: "sort-by-metric topdown separatesources number-menu", search: []string{"linux"}, bottomUp: false, separateSources: true, sortBy: "", verbosity: NumberMenu, wantResults: []string{"linux", "linux-zen", "linux-ck"}, wantOutput: []string{ "\x1b[35m1\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", "\x1b[35m2\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b[35m3\x1b[0m \x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, { desc: "sort-by-name bottomup separatesources number-menu", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "name", verbosity: NumberMenu, wantResults: []string{"linux-ck", "linux", "linux-zen"}, wantOutput: []string{ "\x1b[35m3\x1b[0m \x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", "\x1b[35m2\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", "\x1b[35m1\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", }, }, { desc: "sort-by-name topdown separatesources number-menu", search: []string{"linux"}, bottomUp: false, separateSources: true, sortBy: "name", verbosity: NumberMenu, wantResults: []string{"linux-zen", "linux", "linux-ck"}, wantOutput: []string{ "\x1b[35m1\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux ZEN kernel and modules\n", "\x1b[35m2\x1b[0m \x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n The Linux kernel and modules\n", "\x1b[35m3\x1b[0m \x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, { desc: "sort-by-name bottomup noseparatesources minimal", search: []string{"linux"}, bottomUp: true, separateSources: false, sortBy: "name", verbosity: Minimal, wantResults: []string{"linux", "linux-ck", "linux-zen"}, wantOutput: []string{ "linux\n", "linux-ck\n", "linux-zen\n", }, }, { desc: "only-aur minimal", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "name", verbosity: Minimal, targetMode: parser.ModeAUR, wantResults: []string{"linux-ck"}, wantOutput: []string{ "linux-ck\n", }, }, { desc: "only-repo minimal", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "name", verbosity: Minimal, targetMode: parser.ModeRepo, wantResults: []string{"linux", "linux-zen"}, wantOutput: []string{ "linux\n", "linux-zen\n", }, }, { desc: "sort-by-name singleline", search: []string{"linux"}, bottomUp: true, separateSources: true, sortBy: "name", verbosity: Detailed, singleLineResults: true, wantResults: []string{"linux-ck", "linux", "linux-zen"}, wantOutput: []string{ "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\tThe Linux-ck kernel and modules with ck's hrtimer patches\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\tThe Linux kernel and modules\n", "\x1b]8;;https://archlinux.org/packages/core/x86_64/linux-zen\x1b\\\x1b[1m\x1b[33mcore\x1b[0m\x1b[0m/\x1b[1mlinux-zen\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\tThe Linux ZEN kernel and modules\n", }, }, { desc: "sort-by-name search-by-name", search: []string{"linux-ck"}, bottomUp: true, separateSources: true, sortBy: "name", verbosity: Detailed, searchBy: "name", targetMode: parser.ModeAUR, wantResults: []string{"linux-ck"}, wantOutput: []string{ "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, { desc: "only-aur search-by-several-terms", search: []string{"linux-ck", "hrtimer"}, bottomUp: true, separateSources: true, verbosity: Detailed, targetMode: parser.ModeAUR, wantResults: []string{"linux-ck"}, wantOutput: []string{ "\x1b]8;;https://aur.archlinux.org/packages/linux-ck\x1b\\\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mlinux-ck\x1b[0m\x1b]8;;\x1b\\ \x1b[36m5.16.12-1\x1b[0m\x1b[1m (+450\x1b[0m \x1b[1m1.51) \x1b[0m\n The Linux-ck kernel and modules with ck's hrtimer patches\n", }, }, } mockDB := &mock.DBExecutor{ ReposFn: func() []string { // Match pacman.conf parsing order for source separation. return []string{"core"} }, SyncPackagesFn: func(pkgs ...string) []mock.IPackage { mockDB := mock.NewDB("core") return []mock.IPackage{ &mock.Package{ PBase: "linux", PName: "linux", PVersion: "5.16.0", PDescription: "The Linux kernel and modules", PSize: 1, PISize: 1, PDB: mockDB, PArchitecture: "x86_64", }, &mock.Package{ PBase: "linux-zen", PName: "linux-zen", PVersion: "5.16.0", PDescription: "The Linux ZEN kernel and modules", PSize: 1, PISize: 1, PDB: mockDB, PArchitecture: "x86_64", }, } }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Description: "The Linux-ck kernel and modules with ck's hrtimer patches", FirstSubmitted: 1311346274, ID: 1045311, LastModified: 1646250901, Maintainer: "graysky", Name: "linux-ck", NumVotes: 450, OutOfDate: 0, PackageBase: "linux-ck", PackageBaseID: 50911, Popularity: 1.511141, URL: "https://wiki.archlinux.org/index.php/Linux-ck", URLPath: "/cgit/aur.git/snapshot/linux-ck.tar.gz", Version: "5.16.12-1", }, }, nil }, } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { w := &strings.Builder{} queryBuilder := NewSourceQueryBuilder(mockAUR, text.NewLogger(w, io.Discard, strings.NewReader(""), false, "test"), tc.sortBy, tc.targetMode, tc.searchBy, tc.bottomUp, tc.singleLineResults, tc.separateSources) queryBuilder.Execute(context.Background(), mockDB, tc.search) assert.Len(t, queryBuilder.results, len(tc.wantResults)) assert.Equal(t, len(tc.wantResults), queryBuilder.Len()) for i, name := range tc.wantResults { assert.Equal(t, name, queryBuilder.results[i].name) } queryBuilder.Results(mockDB, tc.verbosity) assert.Equal(t, strings.Join(tc.wantOutput, ""), w.String()) }) } } func TestSourceQueryBuilderTieSortsByRepoOrder(t *testing.T) { t.Parallel() type testCase struct { desc string bottomUp bool repoOrder []string wantNames []string } testCases := []testCase{ { desc: "sort-by-metric topdown repo-order-core-extra", bottomUp: false, repoOrder: []string{"core", "extra"}, wantNames: []string{"systemd", "systemd-libs", "python-systemd", "systemd-git"}, }, { desc: "sort-by-metric topdown repo-order-extra-core", bottomUp: false, repoOrder: []string{"extra", "core"}, wantNames: []string{"systemd", "python-systemd", "systemd-libs", "systemd-git"}, }, { desc: "sort-by-metric bottomup repo-order-core-extra", bottomUp: true, repoOrder: []string{"core", "extra"}, wantNames: []string{"systemd-git", "python-systemd", "systemd-libs", "systemd"}, }, { desc: "sort-by-metric bottomup repo-order-extra-core", bottomUp: true, repoOrder: []string{"extra", "core"}, wantNames: []string{"systemd-git", "systemd-libs", "python-systemd", "systemd"}, }, } mockDB := &mock.DBExecutor{ SyncPackagesFn: func(pkgs ...string) []mock.IPackage { return []mock.IPackage{ &mock.Package{ PBase: "systemd", PName: "systemd", PVersion: "259-1", PDescription: "system and service manager", PSize: 1, PISize: 1, PDB: mock.NewDB("core"), PArchitecture: "x86_64", }, &mock.Package{ PBase: "systemd", PName: "systemd-libs", PVersion: "259-1", PDescription: "systemd client libraries", PSize: 1, PISize: 1, PDB: mock.NewDB("core"), PArchitecture: "x86_64", }, &mock.Package{ PBase: "python-systemd", PName: "python-systemd", PVersion: "235-4", PDescription: "Python bindings for systemd", PSize: 1, PISize: 1, PDB: mock.NewDB("extra"), PArchitecture: "x86_64", }, } }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Description: "system and service manager (git version)", FirstSubmitted: 1445633397, ID: 1909597, LastModified: 1765571424, Maintainer: "Atsutane", Name: "systemd-git", NumVotes: 11, OutOfDate: 0, PackageBase: "systemd-git", PackageBaseID: 102323, Popularity: 0.005618, URL: "https://www.github.com/systemd/systemd", URLPath: "/cgit/aur.git/snapshot/systemd-git.tar.gz", Version: "259.rc3.r85286.7524671f74c-1", }, }, nil }, } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { w := &strings.Builder{} mockDB.ReposFn = func() []string { return tc.repoOrder } queryBuilder := NewSourceQueryBuilder(mockAUR, text.NewLogger(w, io.Discard, strings.NewReader(""), false, "test"), "", parser.ModeAny, "", tc.bottomUp, false, true) queryBuilder.Execute(context.Background(), mockDB, []string{"systemd"}) gotNames := make([]string, len(queryBuilder.results)) for i, result := range queryBuilder.results { gotNames[i] = result.name } assert.Equal(t, tc.wantNames, gotNames) }) } } func TestSourceQueryBuilderTieDoesNotSeparateSources(t *testing.T) { t.Parallel() type testCase struct { desc string bottomUp bool wantNames []string } testCases := []testCase{ { desc: "sort-by-metric topdown", bottomUp: false, wantNames: []string{"yay", "ruby-yard", "yay-git"}, }, { desc: "sort-by-metric bottomup", bottomUp: true, wantNames: []string{"yay-git", "ruby-yard", "yay"}, }, } mockDB, mockAUR := newYayQueryBuilderMocks() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { w := &strings.Builder{} queryBuilder := NewSourceQueryBuilder(mockAUR, text.NewLogger(w, io.Discard, strings.NewReader(""), false, "test"), "", parser.ModeAny, "", tc.bottomUp, false, true) queryBuilder.Execute(context.Background(), mockDB, []string{"yay"}) gotNames := make([]string, len(queryBuilder.results)) for i, result := range queryBuilder.results { gotNames[i] = result.name } assert.Equal(t, tc.wantNames, gotNames) }) } } func TestSourceQueryBuilderSortByFields(t *testing.T) { t.Parallel() type testCase struct { desc string sortBy string bottomUp bool wantNames []string } testCases := []testCase{ { desc: "sort-by-base topdown", sortBy: "base", bottomUp: false, wantNames: []string{"yay-git", "yay", "ruby-yard"}, }, { desc: "sort-by-base bottomup", sortBy: "base", bottomUp: true, wantNames: []string{"ruby-yard", "yay", "yay-git"}, }, { desc: "sort-by-modified topdown", sortBy: "modified", bottomUp: false, wantNames: []string{"yay-git", "yay", "ruby-yard"}, }, { desc: "sort-by-modified bottomup", sortBy: "modified", bottomUp: true, wantNames: []string{"ruby-yard", "yay", "yay-git"}, }, { desc: "sort-by-name topdown", sortBy: "name", bottomUp: false, wantNames: []string{"yay-git", "yay", "ruby-yard"}, }, { desc: "sort-by-name bottomup", sortBy: "name", bottomUp: true, wantNames: []string{"ruby-yard", "yay", "yay-git"}, }, { desc: "sort-by-popularity topdown", sortBy: "popularity", bottomUp: false, wantNames: []string{"yay", "yay-git", "ruby-yard"}, }, { desc: "sort-by-popularity bottomup", sortBy: "popularity", bottomUp: true, wantNames: []string{"ruby-yard", "yay-git", "yay"}, }, { desc: "sort-by-votes topdown", sortBy: "votes", bottomUp: false, wantNames: []string{"yay", "yay-git", "ruby-yard"}, }, { desc: "sort-by-votes bottomup", sortBy: "votes", bottomUp: true, wantNames: []string{"ruby-yard", "yay-git", "yay"}, }, { desc: "sort-by-submitted topdown", sortBy: "submitted", bottomUp: false, wantNames: []string{"yay-git", "yay", "ruby-yard"}, }, { desc: "sort-by-submitted bottomup", sortBy: "submitted", bottomUp: true, wantNames: []string{"ruby-yard", "yay", "yay-git"}, }, { desc: "sort-by-metric topdown", sortBy: "", bottomUp: false, wantNames: []string{"yay", "yay-git", "ruby-yard"}, }, { desc: "sort-by-metric bottomup", sortBy: "", bottomUp: true, wantNames: []string{"ruby-yard", "yay-git", "yay"}, }, } mockDB, mockAUR := newYayQueryBuilderMocks() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { w := &strings.Builder{} queryBuilder := NewSourceQueryBuilder(mockAUR, text.NewLogger(w, io.Discard, strings.NewReader(""), false, "test"), tc.sortBy, parser.ModeAny, "", tc.bottomUp, false, false) queryBuilder.Execute(context.Background(), mockDB, []string{"yay"}) gotNames := make([]string, len(queryBuilder.results)) for i, result := range queryBuilder.results { gotNames[i] = result.name } assert.Equal(t, tc.wantNames, gotNames) }) } } func newYayQueryBuilderMocks() (*mock.DBExecutor, *mockaur.MockAUR) { mockDB := &mock.DBExecutor{ ReposFn: func() []string { // Match pacman.conf parsing order for source separation. return []string{"core", "extra"} }, SyncPackagesFn: func(pkgs ...string) []mock.IPackage { mockDB := mock.NewDB("extra") return []mock.IPackage{ &mock.Package{ PBase: "ruby-yard", PName: "ruby-yard", PVersion: "0.9.34-5", PDescription: "YARD is a Ruby Documentation tool. The Y stands for \"Yay!\"", PSize: 1, PISize: 1, PDB: mockDB, PArchitecture: "x86_64", }, } }, LocalPackageFn: func(string) mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Description: "Yet another yogurt. Pacman wrapper and AUR helper written in go.", FirstSubmitted: 1475688004, ID: 1911141, LastModified: 1765742501, Maintainer: "jguer", Name: "yay", NumVotes: 2461, OutOfDate: 0, PackageBase: "yay", PackageBaseID: 115973, Popularity: 34.903162, URL: "https://github.com/Jguer/yay", URLPath: "/cgit/aur.git/snapshot/yay.tar.gz", Version: "12.5.7-1", }, { Description: "Yet another yogurt. Pacman wrapper and AUR helper written in go. (development version)", FirstSubmitted: 1517205142, ID: 1911143, LastModified: 1765742519, Maintainer: "jguer", Name: "yay-git", NumVotes: 55, OutOfDate: 0, PackageBase: "yay-git", PackageBaseID: 129573, Popularity: 1.850171, URL: "https://github.com/Jguer/yay", URLPath: "/cgit/aur.git/snapshot/yay-git.tar.gz", Version: "12.5.7.r0.g44dfda05-1", }, }, nil }, } return mockDB, mockAUR } ================================================ FILE: pkg/query/source.go ================================================ package query import ( "context" "github.com/Jguer/aur" "github.com/hashicorp/go-multierror" ) // queryAUR searches AUR and narrows based on subarguments. func queryAUR(ctx context.Context, aurClient aur.QueryClient, pkgS []string, searchBy string, ) ([]aur.Pkg, error) { var ( err error by = getSearchBy(searchBy) ) for _, word := range pkgS { r, errM := aurClient.Get(ctx, &aur.Query{ Needles: []string{word}, By: by, Contains: true, }) if errM == nil { return r, nil } err = multierror.Append(err, errM) } return nil, err } ================================================ FILE: pkg/query/types.go ================================================ package query import ( "fmt" "strconv" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/text" ) type Pkg = aur.Pkg func getSearchBy(value string) aur.By { switch value { case "name": return aur.Name case "maintainer": return aur.Maintainer case "submitter": return aur.Submitter case "depends": return aur.Depends case "makedepends": return aur.MakeDepends case "optdepends": return aur.OptDepends case "checkdepends": return aur.CheckDepends case "provides": return aur.Provides case "conflicts": return aur.Conflicts case "replaces": return aur.Replaces case "groups": return aur.Groups case "keywords": return aur.Keywords case "comaintainers": return aur.CoMaintainers default: return aur.NameDesc } } func aurPkgSearchString( pkg *aur.Pkg, dbExecutor db.Executor, singleLineResults bool, ) string { linkText := text.Bold(text.ColorHash("aur")) + "/" + text.Bold(pkg.Name) toPrint := text.CreateRepoLink("aur", "", pkg.Name, linkText) + " " + text.Cyan(pkg.Version) + text.Bold(" (+"+strconv.Itoa(pkg.NumVotes)) + " " + text.Bold(strconv.FormatFloat(pkg.Popularity, 'f', 2, 64)+") ") if pkg.Maintainer == "" { toPrint += text.Bold(text.Red(gotext.Get("(Orphaned)"))) + " " } if pkg.OutOfDate != 0 { toPrint += text.Bold(text.Red(gotext.Get("(Out-of-date: %s)", text.FormatTime(pkg.OutOfDate)))) + " " } if localPkg := dbExecutor.LocalPackage(pkg.Name); localPkg != nil { if localPkg.Version() != pkg.Version { toPrint += text.Bold(text.Green(gotext.Get("(Installed: %s)", localPkg.Version()))) } else { toPrint += text.Bold(text.Green(gotext.Get("(Installed)"))) } } if singleLineResults { toPrint += "\t" } else { toPrint += "\n " } toPrint += pkg.Description return toPrint } // PrintSearch receives a RepoSearch type and outputs pretty text. func syncPkgSearchString(pkg alpm.Package, dbExecutor db.Executor, singleLineResults bool) string { linkText := text.Bold(text.ColorHash(pkg.DB().Name())) + "/" + text.Bold(pkg.Name()) toPrint := text.CreateRepoLink(pkg.DB().Name(), pkg.Architecture(), pkg.Name(), linkText) + " " + text.Cyan(pkg.Version()) + text.Bold(" ("+text.Human(pkg.Size())+ " "+text.Human(pkg.ISize())+") ") packageGroups := dbExecutor.PackageGroups(pkg) if len(packageGroups) != 0 { toPrint += fmt.Sprint(packageGroups, " ") } if localPkg := dbExecutor.LocalPackage(pkg.Name()); localPkg != nil { if localPkg.Version() != pkg.Version() { toPrint += text.Bold(text.Green(gotext.Get("(Installed: %s)", localPkg.Version()))) } else { toPrint += text.Bold(text.Green(gotext.Get("(Installed)"))) } } if singleLineResults { toPrint += "\t" } else { toPrint += "\n " } toPrint += pkg.Description() return toPrint } ================================================ FILE: pkg/query/version_diff.go ================================================ package query import ( "strings" "unicode" "github.com/Jguer/yay/v12/pkg/text" alpm "github.com/Jguer/dyalpm" ) func GetVersionDiff(oldVersion, newVersion string) (left, right string) { if oldVersion == newVersion { return oldVersion + text.Red(""), newVersion + text.Green("") } diffPosition := 0 checkWords := func(str string, index int, words ...string) bool { // Make sure the word is not part of a longer word ongoingWord := unicode.IsLetter(rune(str[index])) if ongoingWord { return false } for _, word := range words { wordLength := len(word) nextIndex := index + 1 if (index < len(str)-wordLength) && (str[nextIndex:(nextIndex+wordLength)] == word) { return true } } return false } for index, char := range oldVersion { charIsSpecial := !unicode.IsLetter(char) && !unicode.IsNumber(char) if (index >= len(newVersion)) || (char != rune(newVersion[index])) { if charIsSpecial { diffPosition = index } break } if charIsSpecial || (((index == len(oldVersion)-1) || (index == len(newVersion)-1)) && ((len(oldVersion) != len(newVersion)) || (oldVersion[index] == newVersion[index]))) || checkWords(oldVersion, index, "rc", "pre", "alpha", "beta") { diffPosition = index + 1 } } samePart := oldVersion[0:diffPosition] left = samePart + text.Red(oldVersion[diffPosition:]) right = samePart + text.Green(newVersion[diffPosition:]) return left, right } func isDevelName(name string) bool { for _, suffix := range []string{"git", "svn", "hg", "bzr", "nightly", "insiders-bin"} { if strings.HasSuffix(name, "-"+suffix) { return true } } return strings.Contains(name, "-always-") } func isDevelPackage(pkg alpm.Package) bool { return isDevelName(pkg.Name()) || isDevelName(pkg.Base()) } ================================================ FILE: pkg/query/version_diff_test.go ================================================ //go:build !integration // +build !integration package query import ( "testing" "github.com/Jguer/yay/v12/pkg/text" ) func TestVersionDiff(t *testing.T) { testCases := []struct { name string a string b string wantDiff string }{ { name: "1.0.0-1 -> 1.0.0-2", a: "1.0.0-1", b: "1.0.0-2", wantDiff: "1.0.0-" + text.Red("1") + " " + "1.0.0-" + text.Green("2"), }, { name: "1.0.0-1 -> 1.0.1-1", a: "1.0.0-1", b: "1.0.1-1", wantDiff: "1.0." + text.Red("0-1") + " " + "1.0." + text.Green("1-1"), }, { name: "3.0.0~alpha7-3 -> 3.0.0~alpha7-4", a: "3.0.0~alpha7-3", b: "3.0.0~alpha7-4", wantDiff: "3.0.0~alpha7-" + text.Red("3") + " " + "3.0.0~alpha7-" + text.Green("4"), }, { name: "3.0.0~beta7-3 -> 3.0.0~beta8-3", a: "3.0.0~beta7-3", b: "3.0.0~beta8-3", wantDiff: "3.0.0~" + text.Red("beta7-3") + " " + "3.0.0~" + text.Green("beta8-3"), }, { name: "23.04.r131.b1bfe05-1 -> 23.04.r131.b1bfe07-1", a: "23.04.r131.b1bfe05-1", b: "23.04.r131.b1bfe07-1", wantDiff: "23.04.r131." + text.Red("b1bfe05-1") + " " + "23.04.r131." + text.Green("b1bfe07-1"), }, { name: "1.0.arch0-1 -> 1.0.arch1-2", a: "1.0.arch0-1", b: "1.0.arch1-2", wantDiff: "1.0." + text.Red("arch0-1") + " " + "1.0." + text.Green("arch1-2"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { originalUseColor := text.UseColor text.UseColor = true left, right := GetVersionDiff(tc.a, tc.b) gotDiff := left + " " + right if gotDiff != tc.wantDiff { t.Errorf("VersionDiff(%s, %s) = %s, want %s", tc.a, tc.b, gotDiff, tc.wantDiff) } text.UseColor = originalUseColor }) } } ================================================ FILE: pkg/runtime/pacman.go ================================================ package runtime import ( "fmt" "os" "github.com/Jguer/yay/v12/pkg/settings/parser" pacmanconf "github.com/Morganamilo/go-pacmanconf" "golang.org/x/term" ) func retrievePacmanConfig(cmdArgs *parser.Arguments, pacmanConfigPath string) (*pacmanconf.Config, bool, error) { root := "/" if value, _, exists := cmdArgs.GetArg("root", "r"); exists { root = value } pacmanConf, stderr, err := pacmanconf.PacmanConf("--config", pacmanConfigPath, "--root", root) if err != nil { cmdErr := err if stderr != "" { cmdErr = fmt.Errorf("%w\n%s", err, stderr) } return nil, false, cmdErr } if dbPath, _, exists := cmdArgs.GetArg("dbpath", "b"); exists { pacmanConf.DBPath = dbPath } if arch := cmdArgs.GetArgs("arch"); arch != nil { pacmanConf.Architecture = append(pacmanConf.Architecture, arch...) } if ignoreArray := cmdArgs.GetArgs("ignore"); ignoreArray != nil { pacmanConf.IgnorePkg = append(pacmanConf.IgnorePkg, ignoreArray...) } if ignoreGroupsArray := cmdArgs.GetArgs("ignoregroup"); ignoreGroupsArray != nil { pacmanConf.IgnoreGroup = append(pacmanConf.IgnoreGroup, ignoreGroupsArray...) } if cacheArray := cmdArgs.GetArgs("cachedir"); cacheArray != nil { pacmanConf.CacheDir = cacheArray } if gpgDir, _, exists := cmdArgs.GetArg("gpgdir"); exists { pacmanConf.GPGDir = gpgDir } useColor := pacmanConf.Color && term.IsTerminal(int(os.Stdout.Fd())) switch value, _, _ := cmdArgs.GetArg("color"); value { case "always": useColor = true case "auto": useColor = term.IsTerminal(int(os.Stdout.Fd())) case "never": useColor = false } return pacmanConf, useColor, nil } ================================================ FILE: pkg/runtime/pacman_test.go ================================================ //go:build !integration // +build !integration package runtime import ( "path/filepath" "runtime" "strings" "testing" "github.com/Morganamilo/go-pacmanconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/settings/parser" ) // normalizePath removes trailing slashes from paths (except for root "/"). func normalizePath(p string) string { if p == "/" { return p } return strings.TrimSuffix(p, "/") } // normalizePaths normalizes a slice of paths. func normalizePaths(paths []string) []string { result := make([]string, len(paths)) for i, p := range paths { result[i] = normalizePath(p) } return result } // normalizePacmanConf normalizes directory paths in a pacmanconf.Config // to handle differences between pacman versions (with/without trailing slashes). func normalizePacmanConf(conf *pacmanconf.Config) { conf.RootDir = normalizePath(conf.RootDir) conf.DBPath = normalizePath(conf.DBPath) conf.GPGDir = normalizePath(conf.GPGDir) conf.CacheDir = normalizePaths(conf.CacheDir) conf.HookDir = normalizePaths(conf.HookDir) } func TestPacmanConf(t *testing.T) { t.Parallel() path := "../../testdata/pacman.conf" absPath, err := filepath.Abs(path) require.NoError(t, err) // detect the architecture of the system expectedArch := []string{"x86_64"} if runtime.GOARCH == "arm64" { expectedArch = []string{"aarch64"} } expectedPacmanConf := &pacmanconf.Config{ RootDir: "/", DBPath: "/var/lib/pacman", CacheDir: []string{"/var/cache/pacman/pkg"}, HookDir: []string{"/etc/pacman.d/hooks"}, GPGDir: "/etc/pacman.d/gnupg", LogFile: "/var/log/pacman.log", HoldPkg: []string{"pacman", "glibc"}, IgnorePkg: []string{"xorm"}, IgnoreGroup: []string{"yorm"}, Architecture: expectedArch, XferCommand: "/usr/bin/wget --passive-ftp -c -O %o %u", NoUpgrade: []string(nil), NoExtract: []string(nil), CleanMethod: []string{"KeepInstalled"}, SigLevel: []string{"PackageRequired", "PackageTrustedOnly", "DatabaseOptional", "DatabaseTrustedOnly"}, LocalFileSigLevel: []string{"PackageOptional", "PackageTrustedOnly"}, RemoteFileSigLevel: []string{"PackageRequired", "PackageTrustedOnly"}, UseSyslog: true, Color: true, UseDelta: 0, TotalDownload: false, CheckSpace: true, VerbosePkgLists: true, DisableDownloadTimeout: false, Repos: []pacmanconf.Repository{ { Name: "core", Servers: []string{"Core"}, SigLevel: []string(nil), Usage: []string{"All"}, }, { Name: "extra", Servers: []string{"Extra"}, SigLevel: []string(nil), Usage: []string{"All"}, }, { Name: "multilib", Servers: []string{"repo3", "multilib"}, SigLevel: []string(nil), Usage: []string{"All"}, }, }, } pacmanConf, color, err := retrievePacmanConfig(parser.MakeArguments(), absPath) assert.Nil(t, err) assert.NotNil(t, pacmanConf) assert.Equal(t, color, false) // Normalize paths to handle differences between pacman versions // (some versions include trailing slashes, some don't) normalizePacmanConf(pacmanConf) assert.EqualValues(t, expectedPacmanConf, pacmanConf) } ================================================ FILE: pkg/runtime/runtime.go ================================================ package runtime import ( "context" "fmt" "net/http" "os" "path/filepath" "time" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" "github.com/Jguer/aur" "github.com/Jguer/aur/metadata" "github.com/Jguer/aur/rpc" "github.com/Jguer/votar/pkg/vote" "github.com/Morganamilo/go-pacmanconf" "golang.org/x/net/proxy" ) type Runtime struct { Cfg *settings.Configuration QueryBuilder query.Builder PacmanConf *pacmanconf.Config VCSStore vcs.Store CmdBuilder exe.ICmdBuilder HTTPClient *http.Client VoteClient *vote.Client AURClient aur.QueryClient Logger *text.Logger } func NewRuntime(cfg *settings.Configuration, cmdArgs *parser.Arguments, version string) (*Runtime, error) { logger := text.NewLogger(os.Stdout, os.Stderr, os.Stdin, cfg.Debug, "runtime") runner := exe.NewOSRunner(logger.Child("runner")) transport := http.DefaultTransport.(*http.Transport).Clone() transport.IdleConnTimeout = 90 * time.Second transport.TLSHandshakeTimeout = 30 * time.Second transport.ResponseHeaderTimeout = 30 * time.Second transport.MaxIdleConns = 100 transport.MaxIdleConnsPerHost = 10 if socks5Proxy := os.Getenv("SOCKS5_PROXY"); socks5Proxy != "" { dialer, err := proxy.SOCKS5("tcp", socks5Proxy, nil, proxy.Direct) if err != nil { return nil, err } contextDialer, ok := dialer.(proxy.ContextDialer) if !ok { return nil, fmt.Errorf("SOCKS5 dialer does not support DialContext") } transport.DialContext = contextDialer.DialContext } httpClient := &http.Client{ Timeout: 30 * time.Second, Transport: transport, } userAgent := fmt.Sprintf("Yay/%s", version) voteClient, errVote := vote.NewClient(vote.WithUserAgent(userAgent), vote.WithHTTPClient(httpClient)) if errVote != nil { return nil, errVote } voteClient.SetCredentials( os.Getenv("AUR_USERNAME"), os.Getenv("AUR_PASSWORD")) userAgentFn := func(ctx context.Context, req *http.Request) error { req.Header.Set("User-Agent", userAgent) return nil } var aurCache aur.QueryClient aurCache, errAURCache := metadata.New( metadata.WithHTTPClient(httpClient), metadata.WithCacheFilePath(filepath.Join(cfg.BuildDir, "aur.json")), metadata.WithRequestEditorFn(userAgentFn), metadata.WithBaseURL(cfg.AURURL), metadata.WithDebugLogger(logger.Debugln), ) if errAURCache != nil { return nil, fmt.Errorf(gotext.Get("failed to retrieve aur Cache")+": %w", errAURCache) } aurClient, errAUR := rpc.NewClient( rpc.WithHTTPClient(httpClient), rpc.WithBaseURL(cfg.AURRPCURL), rpc.WithRequestEditorFn(userAgentFn), rpc.WithLogFn(logger.Debugln)) if errAUR != nil { return nil, errAUR } if cfg.UseRPC { aurCache = aurClient } pacmanConf, useColor, err := retrievePacmanConfig(cmdArgs, cfg.PacmanConf) if err != nil { return nil, err } // FIXME: get rid of global text.UseColor = useColor cmdBuilder := exe.NewCmdBuilder(cfg, runner, logger.Child("cmdbuilder"), pacmanConf.DBPath) vcsStore := vcs.NewInfoStore( cfg.VCSFilePath, cmdBuilder, logger.Child("vcs")) if err := vcsStore.Load(); err != nil { return nil, err } queryBuilder := query.NewSourceQueryBuilder( aurClient, logger.Child("mixed.querybuilder"), cfg.SortBy, cfg.Mode, cfg.SearchBy, cfg.BottomUp, cfg.SingleLineResults, cfg.SeparateSources) run := &Runtime{ Cfg: cfg, QueryBuilder: queryBuilder, PacmanConf: pacmanConf, VCSStore: vcsStore, CmdBuilder: cmdBuilder, HTTPClient: httpClient, VoteClient: voteClient, AURClient: aurCache, Logger: logger, } return run, nil } ================================================ FILE: pkg/runtime/runtime_test.go ================================================ //go:build !integration // +build !integration package runtime_test import ( "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" ) func TestBuildRuntime(t *testing.T) { t.Parallel() path := "../../testdata/pacman.conf" absPath, err := filepath.Abs(path) require.NoError(t, err) // Prepare test inputs cfg := &settings.Configuration{ Debug: true, UseRPC: false, AURURL: "https://aur.archlinux.org", AURRPCURL: "https://aur.archlinux.org/rpc", BuildDir: "/tmp", VCSFilePath: "", PacmanConf: absPath, } cmdArgs := parser.MakeArguments() version := "1.0.0" // Call the function being tested run, err := runtime.NewRuntime(cfg, cmdArgs, version) require.NoError(t, err) // Assert the function's output assert.NotNil(t, run) assert.NotNil(t, run.QueryBuilder) assert.NotNil(t, run.PacmanConf) assert.NotNil(t, run.VCSStore) assert.NotNil(t, run.CmdBuilder) assert.NotNil(t, run.HTTPClient) assert.NotNil(t, run.VoteClient) assert.NotNil(t, run.AURClient) assert.NotNil(t, run.Logger) } ================================================ FILE: pkg/settings/args.go ================================================ package settings import ( "strconv" "strings" "github.com/Jguer/yay/v12/pkg/settings/parser" ) func (c *Configuration) ParseCommandLine(a *parser.Arguments) error { if err := a.Parse(); err != nil { return err } c.extractYayOptions(a) return nil } func (c *Configuration) extractYayOptions(a *parser.Arguments) { for option, value := range a.Options { if c.handleOption(option, value.First()) { a.DelArg(option) } } c.AURURL = strings.TrimRight(c.AURURL, "/") // if AurRPCURL is set, use that for /rpc calls if c.AURRPCURL == "" { c.AURRPCURL = c.AURURL + "/rpc?" return } if !strings.HasSuffix(c.AURRPCURL, "?") { if strings.HasSuffix(c.AURRPCURL, "/rpc") { c.AURRPCURL += "?" } else { c.AURRPCURL = strings.TrimRight(c.AURRPCURL, "/") + "/rpc?" } } } func (c *Configuration) handleOption(option, value string) bool { boolValue, err := strconv.ParseBool(value) if err != nil { boolValue = true } switch option { case "aururl": c.AURURL = value case "aurrpcurl": c.AURRPCURL = value case "save": c.SaveConfig = boolValue case "afterclean", "cleanafter": c.CleanAfter = boolValue case "keepsrc": c.KeepSrc = boolValue case "debug": c.Debug = boolValue return !boolValue case "devel": c.Devel = boolValue case "topdown": c.BottomUp = false case "bottomup": c.BottomUp = true case "singlelineresults": c.SingleLineResults = true case "doublelineresults": c.SingleLineResults = false case "completioninterval": n, err := strconv.Atoi(value) if err == nil { c.CompletionInterval = n } case "sortby": c.SortBy = value case "searchby": c.SearchBy = value case "noconfirm": NoConfirm = boolValue case "config": c.PacmanConf = value case "redownload": c.ReDownload = "yes" case "redownloadall": c.ReDownload = "all" case "noredownload": c.ReDownload = "no" case "rebuild": c.ReBuild = parser.RebuildModeYes case "rebuildall": c.ReBuild = parser.RebuildModeAll case "rebuildtree": c.ReBuild = parser.RebuildModeTree case "norebuild": c.ReBuild = parser.RebuildModeNo case "batchinstall": c.BatchInstall = boolValue case "answerclean": c.AnswerClean = value case "noanswerclean": c.AnswerClean = "" case "answerdiff": c.AnswerDiff = value case "noanswerdiff": c.AnswerDiff = "" case "answeredit": c.AnswerEdit = value case "noansweredit": c.AnswerEdit = "" case "answerupgrade": c.AnswerUpgrade = value case "noanswerupgrade": c.AnswerUpgrade = "" case "gpgflags": c.GpgFlags = value case "mflags": c.MFlags = value case "gitflags": c.GitFlags = value case "builddir": c.BuildDir = value case "editor": c.Editor = value case "editorflags": c.EditorFlags = value case "makepkg": c.MakepkgBin = value case "makepkgconf": c.MakepkgConf = value case "nomakepkgconf": c.MakepkgConf = "" case "pacman": c.PacmanBin = value case "git": c.GitBin = value case "gpg": c.GpgBin = value case "sudo": c.SudoBin = value case "sudoflags": c.SudoFlags = value case "requestsplitn": n, err := strconv.Atoi(value) if err == nil && n > 0 { c.RequestSplitN = n } case "sudoloop": c.SudoLoop = boolValue case "provides": c.Provides = boolValue case "pgpfetch": c.PGPFetch = boolValue case "cleanmenu": c.CleanMenu = boolValue case "diffmenu": c.DiffMenu = boolValue case "editmenu": c.EditMenu = boolValue case "useask": c.UseAsk = boolValue case "combinedupgrade": c.CombinedUpgrade = boolValue case "a", "aur": c.Mode = parser.ModeAUR case "N", "repo": c.Mode = parser.ModeRepo case "removemake": c.RemoveMake = "yes" case "noremovemake": c.RemoveMake = "no" case "askremovemake": c.RemoveMake = "ask" case "askyesremovemake": c.RemoveMake = "askyes" case "separatesources": c.SeparateSources = boolValue default: return false } return true } ================================================ FILE: pkg/settings/config.go ================================================ package settings import ( "bytes" _ "embed" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/leonelquinteros/gotext" "gopkg.in/ini.v1" ) //go:embed yay.conf var defaultsINI []byte // HideMenus indicates if pacman's provider menus must be hidden. var HideMenus = false // NoConfirm indicates if user input should be skipped. var NoConfirm = false // Configuration stores yay's config. type Configuration struct { AURURL string `json:"aururl" ini:"AurUrl"` AURRPCURL string `json:"aurrpcurl" ini:"AurRpcUrl"` BuildDir string `json:"buildDir" ini:"BuildDir"` Editor string `json:"editor" ini:"Editor"` EditorFlags string `json:"editorflags" ini:"EditorFlags"` MakepkgBin string `json:"makepkgbin" ini:"MakepkgBin"` MakepkgConf string `json:"makepkgconf" ini:"MakepkgConf"` PacmanBin string `json:"pacmanbin" ini:"PacmanBin"` PacmanConf string `json:"pacmanconf" ini:"PacmanConf"` ReDownload string `json:"redownload" ini:"ReDownload"` AnswerClean string `json:"answerclean" ini:"AnswerClean"` AnswerDiff string `json:"answerdiff" ini:"AnswerDiff"` AnswerEdit string `json:"answeredit" ini:"AnswerEdit"` AnswerUpgrade string `json:"answerupgrade" ini:"AnswerUpgrade"` GitBin string `json:"gitbin" ini:"GitBin"` GpgBin string `json:"gpgbin" ini:"GpgBin"` GpgFlags string `json:"gpgflags" ini:"GpgFlags"` MFlags string `json:"mflags" ini:"MFlags"` SortBy string `json:"sortby" ini:"SortBy"` SearchBy string `json:"searchby" ini:"SearchBy"` GitFlags string `json:"gitflags" ini:"GitFlags"` RemoveMake string `json:"removemake" ini:"RemoveMake"` SudoBin string `json:"sudobin" ini:"SudoBin"` SudoFlags string `json:"sudoflags" ini:"SudoFlags"` Version string `json:"version" ini:"-"` RequestSplitN int `json:"requestsplitn" ini:"RequestSplitN"` CompletionInterval int `json:"completionrefreshtime" ini:"CompletionInterval"` MaxConcurrentDownloads int `json:"maxconcurrentdownloads" ini:"MaxConcurrentDownloads"` BottomUp bool `json:"bottomup" ini:"BottomUp"` SudoLoop bool `json:"sudoloop" ini:"SudoLoop"` Devel bool `json:"devel" ini:"Devel"` CleanAfter bool `json:"cleanAfter" ini:"CleanAfter"` KeepSrc bool `json:"keepSrc" ini:"KeepSrc"` Provides bool `json:"provides" ini:"Provides"` PGPFetch bool `json:"pgpfetch" ini:"PgpFetch"` CleanMenu bool `json:"cleanmenu" ini:"CleanMenu"` DiffMenu bool `json:"diffmenu" ini:"DiffMenu"` EditMenu bool `json:"editmenu" ini:"EditMenu"` CombinedUpgrade bool `json:"combinedupgrade" ini:"CombinedUpgrade"` UseAsk bool `json:"useask" ini:"UseAsk"` BatchInstall bool `json:"batchinstall" ini:"BatchInstall"` SingleLineResults bool `json:"singlelineresults" ini:"SingleLineResults"` SeparateSources bool `json:"separatesources" ini:"SeparateSources"` Debug bool `json:"debug" ini:"Debug"` UseRPC bool `json:"rpc" ini:"Rpc"` DoubleConfirm bool `json:"doubleconfirm" ini:"DoubleConfirm"` // confirm install before and after build CompletionPath string `json:"-" ini:"-"` VCSFilePath string `json:"-" ini:"-"` SaveConfig bool `json:"-" ini:"-"` Mode parser.TargetMode `json:"-" ini:"-"` ReBuild parser.RebuildMode `json:"rebuild" ini:"ReBuild"` } // Save writes yay config to INI file. func (c *Configuration) Save(configPath, version string) error { // Use INI config path instead of JSON iniPath := GetINIConfigPath() if iniPath == "" { return fmt.Errorf("unable to determine config path") } return c.SaveINI(iniPath) } func (c *Configuration) expandEnv() { c.AURURL = os.ExpandEnv(c.AURURL) c.AURRPCURL = os.ExpandEnv(c.AURRPCURL) c.BuildDir = expandEnvOrHome(c.BuildDir) c.Editor = expandEnvOrHome(c.Editor) c.EditorFlags = os.ExpandEnv(c.EditorFlags) c.MakepkgBin = expandEnvOrHome(c.MakepkgBin) c.MakepkgConf = expandEnvOrHome(c.MakepkgConf) c.PacmanBin = expandEnvOrHome(c.PacmanBin) c.PacmanConf = expandEnvOrHome(c.PacmanConf) c.GpgFlags = os.ExpandEnv(c.GpgFlags) c.MFlags = os.ExpandEnv(c.MFlags) c.GitFlags = os.ExpandEnv(c.GitFlags) c.SortBy = os.ExpandEnv(c.SortBy) c.SearchBy = os.ExpandEnv(c.SearchBy) c.GitBin = expandEnvOrHome(c.GitBin) c.GpgBin = expandEnvOrHome(c.GpgBin) c.SudoBin = expandEnvOrHome(c.SudoBin) c.SudoFlags = os.ExpandEnv(c.SudoFlags) c.ReDownload = os.ExpandEnv(c.ReDownload) c.ReBuild = parser.RebuildMode(os.ExpandEnv(string(c.ReBuild))) c.AnswerClean = os.ExpandEnv(c.AnswerClean) c.AnswerDiff = os.ExpandEnv(c.AnswerDiff) c.AnswerEdit = os.ExpandEnv(c.AnswerEdit) c.AnswerUpgrade = os.ExpandEnv(c.AnswerUpgrade) c.RemoveMake = os.ExpandEnv(c.RemoveMake) } func expandEnvOrHome(path string) string { path = os.ExpandEnv(path) if strings.HasPrefix(path, "~/") { path = filepath.Join(os.Getenv("HOME"), path[2:]) } return path } func (c *Configuration) String() string { var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetIndent("", "\t") if err := enc.Encode(c); err != nil { fmt.Fprintln(os.Stderr, err) } return buf.String() } // check privilege elevator exists otherwise try to find another one. func (c *Configuration) setPrivilegeElevator() error { if auth := os.Getenv("PACMAN_AUTH"); auth != "" { c.SudoBin = auth if auth != "sudo" { c.SudoFlags = "" c.SudoLoop = false } } for _, bin := range [...]string{c.SudoBin, "sudo"} { if _, err := exec.LookPath(bin); err == nil { c.SudoBin = bin return nil // wrapper or sudo command existing. Retrocompatiblity } } c.SudoFlags = "" c.SudoLoop = false for _, bin := range [...]string{"run0", "doas", "pkexec", "su"} { if _, err := exec.LookPath(bin); err == nil { c.SudoBin = bin return nil // command existing } } return &ErrPrivilegeElevatorNotFound{confValue: c.SudoBin} } func DefaultConfig(version string) *Configuration { cfg := &Configuration{ Version: version, Mode: parser.ModeAny, } // Load defaults from embedded INI iniCfg, err := ini.LoadSources(ini.LoadOptions{ AllowBooleanKeys: true, Insensitive: true, InsensitiveSections: true, IgnoreInlineComment: true, }, defaultsINI) if err != nil { // Fallback to minimal defaults if embedded config fails cfg.AURURL = "https://aur.archlinux.org" cfg.BuildDir = os.ExpandEnv("$HOME/.cache/yay") return cfg } // Map the default section _ = iniCfg.Section("").MapTo(cfg) // Also map [options] section if present if iniCfg.HasSection("options") { _ = iniCfg.Section("options").MapTo(cfg) } return cfg } func NewConfig(logger *text.Logger, configPath, version string) (*Configuration, error) { newConfig := DefaultConfig(version) cacheHome, errCache := getCacheHome() if errCache != nil && logger != nil { logger.Errorln(errCache) } newConfig.BuildDir = cacheHome newConfig.CompletionPath = filepath.Join(cacheHome, completionFileName) newConfig.VCSFilePath = filepath.Join(cacheHome, vcsFileName) // Load system-wide INI config first (silently ignored if not present) if err := newConfig.loadINI(SystemConfigPath); err != nil && logger != nil { logger.Errorln(err) } // Load user JSON config (legacy, overrides system config) newConfig.load(configPath) // Load user INI config (takes priority over JSON when both exist) userINIPath := GetINIConfigPath() if userINIPath != "" { if err := newConfig.loadINI(userINIPath); err != nil && logger != nil { logger.Errorln(err) } } if aurdest := os.Getenv("AURDEST"); aurdest != "" { newConfig.BuildDir = aurdest } newConfig.expandEnv() if newConfig.BuildDir != systemdCache { errBuildDir := initDir(newConfig.BuildDir) if errBuildDir != nil { return nil, errBuildDir } } if errPE := newConfig.setPrivilegeElevator(); errPE != nil { return nil, errPE } return newConfig, nil } func (c *Configuration) load(configPath string) { cfile, err := os.Open(configPath) if !os.IsNotExist(err) && err != nil { fmt.Fprintln(os.Stderr, gotext.Get("failed to open config file '%s': %s", configPath, err)) return } defer cfile.Close() if !os.IsNotExist(err) { decoder := json.NewDecoder(cfile) if err = decoder.Decode(c); err != nil { fmt.Fprintln(os.Stderr, gotext.Get("failed to read config file '%s': %s", configPath, err)) } } } ================================================ FILE: pkg/settings/config_test.go ================================================ //go:build !integration // +build !integration package settings import ( "encoding/json" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // GIVEN a non existing build dir in the config // WHEN the config is loaded // THEN the directory should be created func TestNewConfig(t *testing.T) { configDir := t.TempDir() err := os.MkdirAll(filepath.Join(configDir, "yay"), 0o755) assert.NoError(t, err) t.Setenv("XDG_CONFIG_HOME", configDir) cacheDir := t.TempDir() config := map[string]string{"BuildDir": filepath.Join(cacheDir, "test-build-dir")} f, err := os.Create(filepath.Join(configDir, "yay", "config.json")) assert.NoError(t, err) defer f.Close() configJSON, _ := json.Marshal(config) _, err = f.WriteString(string(configJSON)) assert.NoError(t, err) newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0") assert.NoError(t, err) assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir) _, err = os.Stat(filepath.Join(cacheDir, "test-build-dir")) assert.NoError(t, err) } // GIVEN a non existing build dir in the config and AURDEST set to a non-existing folder // WHEN the config is loaded // THEN the directory of AURDEST should be created and selected func TestNewConfigAURDEST(t *testing.T) { configDir := t.TempDir() err := os.MkdirAll(filepath.Join(configDir, "yay"), 0o755) assert.NoError(t, err) t.Setenv("XDG_CONFIG_HOME", configDir) cacheDir := t.TempDir() config := map[string]string{"BuildDir": filepath.Join(cacheDir, "test-other-dir")} t.Setenv("AURDEST", filepath.Join(cacheDir, "test-build-dir")) f, err := os.Create(filepath.Join(configDir, "yay", "config.json")) assert.NoError(t, err) defer f.Close() configJSON, _ := json.Marshal(config) _, err = f.WriteString(string(configJSON)) assert.NoError(t, err) newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0") assert.NoError(t, err) assert.Equal(t, filepath.Join(cacheDir, "test-build-dir"), newConfig.BuildDir) _, err = os.Stat(filepath.Join(cacheDir, "test-build-dir")) assert.NoError(t, err) } // Test tilde expansion in AURDEST func TestNewConfigAURDESTTildeExpansion(t *testing.T) { configDir := t.TempDir() err := os.MkdirAll(filepath.Join(configDir, "yay"), 0o755) assert.NoError(t, err) t.Setenv("XDG_CONFIG_HOME", configDir) homeDir := t.TempDir() cacheDir := t.TempDir() config := map[string]string{"BuildDir": filepath.Join(cacheDir, "test-other-dir")} t.Setenv("AURDEST", "~/test-build-dir") t.Setenv("HOME", homeDir) f, err := os.Create(filepath.Join(configDir, "yay", "config.json")) assert.NoError(t, err) defer f.Close() configJSON, _ := json.Marshal(config) _, err = f.WriteString(string(configJSON)) assert.NoError(t, err) newConfig, err := NewConfig(nil, GetConfigPath(), "v1.0.0") assert.NoError(t, err) assert.Equal(t, filepath.Join(homeDir, "test-build-dir"), newConfig.BuildDir) _, err = os.Stat(filepath.Join(homeDir, "test-build-dir")) assert.NoError(t, err) } // GIVEN default config // WHEN setPrivilegeElevator gets called // THEN sudobin should stay as "sudo" (given sudo exists) func TestConfiguration_setPrivilegeElevator(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "sudo") _, err := os.Create(doas) os.Chmod(doas, 0o755) assert.NoError(t, err) config := DefaultConfig("test") config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "sudo", config.SudoBin) assert.Equal(t, "-v", config.SudoFlags) assert.True(t, config.SudoLoop) } // GIVEN default config and sudo loop enabled // GIVEN only su in path // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "su" func TestConfiguration_setPrivilegeElevator_su(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "su") _, err := os.Create(doas) os.Chmod(doas, 0o755) assert.NoError(t, err) config := DefaultConfig("test") config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "su", config.SudoBin) assert.Equal(t, "", config.SudoFlags) assert.False(t, config.SudoLoop) } // GIVEN default config and sudo loop enabled // GIVEN no sudo in path // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "su" func TestConfiguration_setPrivilegeElevator_no_path(t *testing.T) { t.Setenv("PATH", "") config := DefaultConfig("test") config.SudoLoop = true config.SudoFlags = "-v" err := config.setPrivilegeElevator() assert.Error(t, err) assert.Equal(t, "sudo", config.SudoBin) assert.Equal(t, "", config.SudoFlags) assert.False(t, config.SudoLoop) } // GIVEN default config and sudo loop enabled // GIVEN doas in path // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "doas" func TestConfiguration_setPrivilegeElevator_doas(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "doas") _, err := os.Create(doas) os.Chmod(doas, 0o755) assert.NoError(t, err) config := DefaultConfig("test") config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "doas", config.SudoBin) assert.Equal(t, "", config.SudoFlags) assert.False(t, config.SudoLoop) } // GIVEN default config and sudo loop enabled // GIVEN run0 in path // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "run0" func TestConfiguration_setPrivilegeElevator_run0(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "run0") _, err := os.Create(doas) os.Chmod(doas, 0o755) assert.NoError(t, err) config := DefaultConfig("test") config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "run0", config.SudoBin) assert.Equal(t, "", config.SudoFlags) assert.False(t, config.SudoLoop) } // GIVEN config with wrapper and sudo loop enabled // GIVEN wrapper is in path // WHEN setPrivilegeElevator gets called // THEN sudobin should be kept as the wrapper func TestConfiguration_setPrivilegeElevator_custom_script(t *testing.T) { path := t.TempDir() wrapper := filepath.Join(path, "custom-wrapper") _, err := os.Create(wrapper) os.Chmod(wrapper, 0o755) assert.NoError(t, err) config := DefaultConfig("test") config.SudoLoop = true config.SudoBin = wrapper config.SudoFlags = "-v" t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, wrapper, config.SudoBin) assert.Equal(t, "-v", config.SudoFlags) assert.True(t, config.SudoLoop) } // GIVEN default config and sudo loop enabled // GIVEN doas as PACMAN_AUTH env variable // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "doas" func TestConfiguration_setPrivilegeElevator_pacman_auth_doas(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "doas") _, err := os.Create(doas) os.Chmod(doas, 0o755) require.NoError(t, err) sudo := filepath.Join(path, "sudo") _, err = os.Create(sudo) os.Chmod(sudo, 0o755) require.NoError(t, err) config := DefaultConfig("test") config.SudoBin = "sudo" config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PACMAN_AUTH", "doas") t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "doas", config.SudoBin) assert.Equal(t, "", config.SudoFlags) assert.False(t, config.SudoLoop) } // GIVEN config with doas configed and sudo loop enabled // GIVEN sudo as PACMAN_AUTH env variable // WHEN setPrivilegeElevator gets called // THEN sudobin should be changed to "sudo" func TestConfiguration_setPrivilegeElevator_pacman_auth_sudo(t *testing.T) { path := t.TempDir() doas := filepath.Join(path, "doas") _, err := os.Create(doas) os.Chmod(doas, 0o755) require.NoError(t, err) sudo := filepath.Join(path, "sudo") _, err = os.Create(sudo) os.Chmod(sudo, 0o755) require.NoError(t, err) config := DefaultConfig("test") config.SudoBin = "doas" config.SudoLoop = true config.SudoFlags = "-v" t.Setenv("PACMAN_AUTH", "sudo") t.Setenv("PATH", path) err = config.setPrivilegeElevator() assert.NoError(t, err) assert.Equal(t, "sudo", config.SudoBin) assert.Equal(t, "-v", config.SudoFlags) assert.True(t, config.SudoLoop) } ================================================ FILE: pkg/settings/dirs.go ================================================ package settings import ( "os" "path/filepath" ) const ( configFileName string = "config.json" // configFileName holds the name of the config file. iniConfigFileName string = "yay.conf" // iniConfigFileName holds the name of the INI config file. vcsFileName string = "vcs.json" // vcsFileName holds the name of the vcs file. completionFileName string = "completion.cache" systemdCache string = "/var/cache/yay" // systemd should handle cache creation ) func GetConfigPath() string { if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" { configDir := filepath.Join(configHome, "yay") if err := initDir(configDir); err == nil { return filepath.Join(configDir, configFileName) } } if configHome := os.Getenv("HOME"); configHome != "" { configDir := filepath.Join(configHome, ".config", "yay") if err := initDir(configDir); err == nil { return filepath.Join(configDir, configFileName) } } return "" } // GetINIConfigPath returns the path to the user's INI config file (yay.conf). // This is used for both loading (with priority over JSON) and saving. func GetINIConfigPath() string { if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" { configDir := filepath.Join(configHome, "yay") if err := initDir(configDir); err == nil { return filepath.Join(configDir, iniConfigFileName) } } if configHome := os.Getenv("HOME"); configHome != "" { configDir := filepath.Join(configHome, ".config", "yay") if err := initDir(configDir); err == nil { return filepath.Join(configDir, iniConfigFileName) } } return "" } func getCacheHome() (string, error) { uid := os.Geteuid() if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" && uid != 0 { cacheDir := filepath.Join(cacheHome, "yay") if err := initDir(cacheDir); err == nil { return cacheDir, nil } } if cacheHome := os.Getenv("HOME"); cacheHome != "" && uid != 0 { cacheDir := filepath.Join(cacheHome, ".cache", "yay") if err := initDir(cacheDir); err == nil { return cacheDir, nil } } if uid == 0 && os.Getenv("SUDO_USER") == "" && os.Getenv("DOAS_USER") == "" { return systemdCache, nil // Don't create directory if systemd-run takes care of it } tmpDir := filepath.Join(os.TempDir(), "yay") return tmpDir, initDir(tmpDir) } func initDir(dir string) error { if _, err := os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0o755); err != nil { return &ErrRuntimeDir{inner: err, dir: dir} } } else if err != nil { return err } return nil } ================================================ FILE: pkg/settings/dirs_test.go ================================================ //go:build !integration // +build !integration package settings import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // GIVEN no user directories and sudo user // WHEN cache home is selected // THEN the selected cache home should be in the tmp dir func Test_getCacheHome(t *testing.T) { dir := t.TempDir() require.NoError(t, os.Unsetenv("XDG_CACHE_HOME")) require.NoError(t, os.Unsetenv("HOME")) t.Setenv("SUDO_USER", "test") t.Setenv("TMPDIR", dir) got, err := getCacheHome() require.NoError(t, err) assert.Equal(t, filepath.Join(dir, "yay"), got) require.NoError(t, os.Unsetenv("TMPDIR")) require.NoError(t, os.Unsetenv("SUDO_USER")) } ================================================ FILE: pkg/settings/errors.go ================================================ package settings import ( "fmt" "github.com/leonelquinteros/gotext" ) type ErrPrivilegeElevatorNotFound struct { confValue string } func (e *ErrPrivilegeElevatorNotFound) Error() string { return fmt.Sprintf("unable to find a privilege elevator, config value: %s", e.confValue) } type ErrRuntimeDir struct { inner error dir string } func (e *ErrRuntimeDir) Error() string { return gotext.Get("failed to create directory '%s': %s", e.dir, e.inner) } type ErrUserAbort struct{} func (e ErrUserAbort) Error() string { return gotext.Get("aborting due to user") } ================================================ FILE: pkg/settings/exe/cmd_builder.go ================================================ package exe import ( "context" "fmt" "os" "os/exec" "os/user" "path/filepath" "strconv" "strings" "syscall" "time" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) const SudoLoopDuration = 241 var gitDenyList = mapset.NewThreadUnsafeSet( "GIT_WORK_TREE", "GIT_DIR", ) type GitCmdBuilder interface { Runner BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd } type ICmdBuilder interface { Runner BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd AddMakepkgFlag(string) GetKeepSrc() bool SudoLoop() } type CmdBuilder struct { GitBin string GitFlags []string GPGBin string GPGFlags []string MakepkgFlags []string MakepkgConfPath string MakepkgBin string SudoBin string SudoFlags []string SudoLoopEnabled bool PacmanBin string PacmanConfigPath string PacmanDBPath string KeepSrc bool Runner Runner Log *text.Logger } func NewCmdBuilder(cfg *settings.Configuration, runner Runner, logger *text.Logger, dbPath string) *CmdBuilder { return &CmdBuilder{ GitBin: cfg.GitBin, GitFlags: strings.Fields(cfg.GitFlags), GPGBin: cfg.GpgBin, GPGFlags: strings.Fields(cfg.GpgFlags), MakepkgFlags: strings.Fields(cfg.MFlags), MakepkgConfPath: cfg.MakepkgConf, MakepkgBin: cfg.MakepkgBin, SudoBin: cfg.SudoBin, SudoFlags: strings.Fields(cfg.SudoFlags), SudoLoopEnabled: cfg.SudoLoop, PacmanBin: cfg.PacmanBin, PacmanConfigPath: cfg.PacmanConf, PacmanDBPath: dbPath, KeepSrc: cfg.KeepSrc, Runner: runner, Log: logger, } } func (c *CmdBuilder) BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd { args := make([]string, len(c.GPGFlags), len(c.GPGFlags)+len(extraArgs)) copy(args, c.GPGFlags) if len(extraArgs) > 0 { args = append(args, extraArgs...) } cmd := exec.CommandContext(ctx, c.GPGBin, args...) cmd = c.deElevateCommand(ctx, cmd) return cmd } func gitFilteredEnv() []string { var env []string for _, envVar := range os.Environ() { envKey := strings.SplitN(envVar, "=", 2)[0] if !gitDenyList.Contains(envKey) { env = append(env, envVar) } } env = append(env, "GIT_TERMINAL_PROMPT=0") return env } func (c *CmdBuilder) BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { args := make([]string, len(c.GitFlags), len(c.GitFlags)+len(extraArgs)) copy(args, c.GitFlags) if dir != "" { args = append(args, "-C", dir) } if len(extraArgs) > 0 { args = append(args, extraArgs...) } cmd := exec.CommandContext(ctx, c.GitBin, args...) cmd.Env = gitFilteredEnv() cmd = c.deElevateCommand(ctx, cmd) return cmd } func (c *CmdBuilder) AddMakepkgFlag(flag string) { c.MakepkgFlags = append(c.MakepkgFlags, flag) } func (c *CmdBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { args := make([]string, len(c.MakepkgFlags), len(c.MakepkgFlags)+len(extraArgs)) copy(args, c.MakepkgFlags) if c.MakepkgConfPath != "" { args = append(args, "--config", c.MakepkgConfPath) } if len(extraArgs) > 0 { args = append(args, extraArgs...) } cmd := exec.CommandContext(ctx, c.MakepkgBin, args...) cmd.Dir = dir cmd = c.deElevateCommand(ctx, cmd) return cmd } // deElevateCommand, `systemd-run` code based on pikaur. func (c *CmdBuilder) deElevateCommand(ctx context.Context, cmd *exec.Cmd) *exec.Cmd { if os.Geteuid() != 0 { return cmd } ogCaller := "" if caller := os.Getenv("SUDO_USER"); caller != "" { ogCaller = caller } else if caller := os.Getenv("DOAS_USER"); caller != "" { ogCaller = caller } if userFound, err := user.Lookup(ogCaller); err == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} uid64, errUid := strconv.ParseUint(userFound.Uid, 10, 32) gid64, errGid := strconv.ParseUint(userFound.Gid, 10, 32) if errUid == nil && errGid == nil { cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid64), Gid: uint32(gid64)} return cmd } } cmdArgs := []string{ "--service-type=oneshot", "--pipe", "--wait", "--pty", "--quiet", "-p", "DynamicUser=yes", "-p", "CacheDirectory=yay", "-E", "HOME=/tmp", } if cmd.Dir != "" { cmdArgs = append(cmdArgs, "-p", fmt.Sprintf("WorkingDirectory=%s", cmd.Dir)) } for _, envVarName := range [...]string{"http_proxy", "https_proxy", "ftp_proxy"} { if env := os.Getenv(envVarName); env != "" { cmdArgs = append(cmdArgs, "-E", fmt.Sprintf("%s=%s", envVarName, env)) } } path, _ := exec.LookPath(cmd.Args[0]) cmdArgs = append(cmdArgs, path) cmdArgs = append(cmdArgs, cmd.Args[1:]...) systemdCmd := exec.CommandContext(ctx, "systemd-run", cmdArgs...) // #nosec G702 systemdCmd.Dir = cmd.Dir return systemdCmd } func (c *CmdBuilder) buildPrivilegeElevatorCommand(ctx context.Context, ogArgs []string) *exec.Cmd { if c.SudoBin == "su" { return exec.CommandContext(ctx, c.SudoBin, "-c", strings.Join(ogArgs, " ")) } argArr := make([]string, 0, len(c.SudoFlags)+len(ogArgs)) argArr = append(argArr, c.SudoFlags...) argArr = append(argArr, ogArgs...) return exec.CommandContext(ctx, c.SudoBin, argArr...) } func (c *CmdBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd { argArr := make([]string, 0, 32) needsRoot := args.NeedRoot(mode) argArr = append(argArr, c.PacmanBin) argArr = append(argArr, args.FormatGlobals()...) argArr = append(argArr, args.FormatArgs()...) if noConfirm { argArr = append(argArr, "--noconfirm") } argArr = append(argArr, "--config", c.PacmanConfigPath, "--") argArr = append(argArr, args.Targets...) if needsRoot { c.waitLock(c.PacmanDBPath) if os.Geteuid() != 0 { return c.buildPrivilegeElevatorCommand(ctx, argArr) } } return exec.CommandContext(ctx, argArr[0], argArr[1:]...) } // waitLock will lock yay checking the status of db.lck until it does not exist. func (c *CmdBuilder) waitLock(dbPath string) { lockDBPath := filepath.Join(dbPath, "db.lck") if _, err := os.Stat(lockDBPath); err != nil { return } c.Log.Warnln(gotext.Get("%s is present.", lockDBPath)) c.Log.Warn(gotext.Get("There may be another Pacman instance running. Waiting...")) for { time.Sleep(3 * time.Second) if _, err := os.Stat(lockDBPath); err != nil { c.Log.Println() return } } } func (c *CmdBuilder) SudoLoop() { c.updateSudo() go c.sudoLoopBackground() } func (c *CmdBuilder) sudoLoopBackground() { for { c.updateSudo() time.Sleep(SudoLoopDuration * time.Second) } } func (c *CmdBuilder) updateSudo() { for { err := c.Show(exec.CommandContext(context.Background(), c.SudoBin, "-v")) if err != nil { c.Log.Errorln(err) } else { break } } } func (c *CmdBuilder) Show(cmd *exec.Cmd) error { return c.Runner.Show(cmd) } func (c *CmdBuilder) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { return c.Runner.Capture(cmd) } func (c *CmdBuilder) GetKeepSrc() bool { return c.KeepSrc } ================================================ FILE: pkg/settings/exe/exec.go ================================================ package exe import ( "errors" "os" "os/exec" "strings" "syscall" "github.com/Jguer/yay/v12/pkg/text" ) type Runner interface { Capture(cmd *exec.Cmd) (stdout string, stderr string, err error) Show(cmd *exec.Cmd) error } type OSRunner struct { Log *text.Logger } func NewOSRunner(log *text.Logger) *OSRunner { return &OSRunner{log} } func (r *OSRunner) Show(cmd *exec.Cmd) error { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM, } r.Log.Debugln("running", cmd.String()) return cmd.Run() } func (r *OSRunner) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { r.Log.Debugln("capturing", cmd.String()) cmd.SysProcAttr = &syscall.SysProcAttr{ Pdeathsig: syscall.SIGTERM, } outbuf, err := cmd.Output() stdout = strings.TrimSpace(string(outbuf)) if err != nil { exitErr := &exec.ExitError{} if errors.As(err, &exitErr) { stderr = strings.TrimSpace(string(exitErr.Stderr)) } } return stdout, stderr, err } ================================================ FILE: pkg/settings/exe/mock.go ================================================ package exe import ( "context" "fmt" "os/exec" "sync" "github.com/Jguer/yay/v12/pkg/settings/parser" ) type Call struct { Res []any Args []any Dir string } func (c *Call) String() string { return fmt.Sprintf("%+v", c.Args) } type MockBuilder struct { Runner Runner BuildMakepkgCmdCallsMu sync.Mutex BuildMakepkgCmdCalls []Call BuildMakepkgCmdFn func(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd BuildPacmanCmdFn func(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd GetKeepSrcFn func() bool } type MockRunner struct { ShowCallsMu sync.Mutex ShowCalls []Call CaptureCallsMu sync.Mutex CaptureCalls []Call ShowFn func(cmd *exec.Cmd) error CaptureFn func(cmd *exec.Cmd) (stdout string, stderr string, err error) } func (m *MockBuilder) BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd { return exec.CommandContext(ctx, "gpg", extraArgs...) } func (m *MockBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { var res *exec.Cmd if m.BuildMakepkgCmdFn != nil { res = m.BuildMakepkgCmdFn(ctx, dir, extraArgs...) } else { res = exec.CommandContext(ctx, "makepkg", extraArgs...) } m.BuildMakepkgCmdCallsMu.Lock() m.BuildMakepkgCmdCalls = append(m.BuildMakepkgCmdCalls, Call{ Res: []any{res}, Args: []any{ ctx, dir, extraArgs, }, }) m.BuildMakepkgCmdCallsMu.Unlock() return res } func (m *MockBuilder) AddMakepkgFlag(flag string) { } func (m *MockBuilder) BuildGitCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { return exec.CommandContext(ctx, "git", extraArgs...) } func (m *MockBuilder) BuildPacmanCmd(ctx context.Context, args *parser.Arguments, mode parser.TargetMode, noConfirm bool) *exec.Cmd { var res *exec.Cmd if m.BuildPacmanCmdFn != nil { res = m.BuildPacmanCmdFn(ctx, args, mode, noConfirm) } else { res = exec.CommandContext(ctx, "pacman") } return res } func (m *MockBuilder) SetPacmanDBPath(path string) { } func (m *MockBuilder) SudoLoop() { } func (m *MockBuilder) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { return m.Runner.Capture(cmd) } func (m *MockBuilder) Show(cmd *exec.Cmd) error { return m.Runner.Show(cmd) } func (m *MockBuilder) GetKeepSrc() bool { return false } func (m *MockRunner) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { m.CaptureCallsMu.Lock() m.CaptureCalls = append(m.CaptureCalls, Call{ Args: []any{ cmd, }, Dir: cmd.Dir, }) m.CaptureCallsMu.Unlock() if m.CaptureFn != nil { return m.CaptureFn(cmd) } return "", "", nil } func (m *MockRunner) Show(cmd *exec.Cmd) error { var err error if m.ShowFn != nil { err = m.ShowFn(cmd) } m.ShowCallsMu.Lock() m.ShowCalls = append(m.ShowCalls, Call{ Args: []any{ cmd, }, Dir: cmd.Dir, }) m.ShowCallsMu.Unlock() return err } ================================================ FILE: pkg/settings/ini.go ================================================ package settings import ( "fmt" "os" "path/filepath" "gopkg.in/ini.v1" ) // SystemConfigPath is the path to the system-wide INI configuration file. const SystemConfigPath = "/etc/yay.conf" // loadINI parses an INI configuration file and applies values to the Configuration. // It silently returns nil if the file doesn't exist. // Uses struct tags for mapping (e.g., `ini:"AurUrl"`). func (c *Configuration) loadINI(path string) error { if _, err := os.Stat(path); os.IsNotExist(err) { return nil } cfg, err := ini.LoadSources(ini.LoadOptions{ AllowBooleanKeys: true, Insensitive: true, InsensitiveSections: true, IgnoreInlineComment: true, }, path) if err != nil { return fmt.Errorf("failed to load INI config file '%s': %w", path, err) } // Map the default section to the config struct if err := cfg.Section("").MapTo(c); err != nil { return fmt.Errorf("failed to map INI config '%s': %w", path, err) } // Also map [options] section if present (for compatibility) if cfg.HasSection("options") { if err := cfg.Section("options").MapTo(c); err != nil { return fmt.Errorf("failed to map INI [options] section '%s': %w", path, err) } } return nil } // SaveINI writes the configuration to an INI file at the specified path. func (c *Configuration) SaveINI(path string) error { cfg := ini.Empty(ini.LoadOptions{ AllowBooleanKeys: true, }) // Use [options] section for compatibility with system config section, err := cfg.NewSection("options") if err != nil { return fmt.Errorf("failed to create INI section: %w", err) } if err := section.ReflectFrom(c); err != nil { return fmt.Errorf("failed to reflect config to INI: %w", err) } // Ensure parent directory exists if dir := filepath.Dir(path); dir != "" { if _, err := os.Stat(dir); os.IsNotExist(err) { if mkErr := os.MkdirAll(dir, 0o755); mkErr != nil { return fmt.Errorf("failed to create config directory: %w", mkErr) } } } return cfg.SaveTo(path) } ================================================ FILE: pkg/settings/ini_test.go ================================================ package settings import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestConfigurationLoadINI(t *testing.T) { t.Parallel() t.Run("load nonexistent file returns nil", func(t *testing.T) { t.Parallel() cfg := DefaultConfig("test") err := cfg.loadINI("/nonexistent/path/yay.conf") assert.NoError(t, err) }) t.Run("load valid INI file", func(t *testing.T) { t.Parallel() content := `# System-wide yay configuration [options] AurUrl = https://custom.aur.org BuildDir = /var/cache/yay Editor = vim Devel = true SudoLoop = yes RequestSplitN = 200 BottomUp = false ; This is also a comment CleanAfter = 1 ` tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") require.NoError(t, os.WriteFile(iniPath, []byte(content), 0o644)) cfg := DefaultConfig("test") err := cfg.loadINI(iniPath) require.NoError(t, err) assert.Equal(t, "https://custom.aur.org", cfg.AURURL) assert.Equal(t, "/var/cache/yay", cfg.BuildDir) assert.Equal(t, "vim", cfg.Editor) assert.True(t, cfg.Devel) assert.True(t, cfg.SudoLoop) assert.Equal(t, 200, cfg.RequestSplitN) assert.False(t, cfg.BottomUp) assert.True(t, cfg.CleanAfter) }) t.Run("load INI file with all option types", func(t *testing.T) { t.Parallel() content := ` # String options AurUrl = https://aur.example.com AurRpcUrl = https://aur.example.com/rpc BuildDir = /tmp/build Editor = nvim EditorFlags = -p MakepkgBin = /usr/bin/makepkg MakepkgConf = /etc/makepkg.conf PacmanBin = /usr/bin/pacman PacmanConf = /etc/pacman.conf GitBin = /usr/bin/git GpgBin = /usr/bin/gpg GpgFlags = --keyserver-options MFlags = -s SortBy = votes SearchBy = name GitFlags = --depth=1 RemoveMake = yes SudoBin = doas SudoFlags = -n ReDownload = all AnswerClean = All AnswerDiff = None AnswerEdit = None AnswerUpgrade = None ReBuild = all # Integer options RequestSplitN = 100 CompletionInterval = 3 MaxConcurrentDownloads = 4 # Boolean options BottomUp = true SudoLoop = false Devel = no CleanAfter = true KeepSrc = false Provides = true PgpFetch = false CleanMenu = yes DiffMenu = no EditMenu = true CombinedUpgrade = false UseAsk = true BatchInstall = false SingleLineResults = true SeparateSources = false Debug = no Rpc = yes DoubleConfirm = false ` tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") require.NoError(t, os.WriteFile(iniPath, []byte(content), 0o644)) cfg := DefaultConfig("test") err := cfg.loadINI(iniPath) require.NoError(t, err) // String options assert.Equal(t, "https://aur.example.com", cfg.AURURL) assert.Equal(t, "https://aur.example.com/rpc", cfg.AURRPCURL) assert.Equal(t, "/tmp/build", cfg.BuildDir) assert.Equal(t, "nvim", cfg.Editor) assert.Equal(t, "-p", cfg.EditorFlags) assert.Equal(t, "/usr/bin/makepkg", cfg.MakepkgBin) assert.Equal(t, "/etc/makepkg.conf", cfg.MakepkgConf) assert.Equal(t, "/usr/bin/pacman", cfg.PacmanBin) assert.Equal(t, "/etc/pacman.conf", cfg.PacmanConf) assert.Equal(t, "/usr/bin/git", cfg.GitBin) assert.Equal(t, "/usr/bin/gpg", cfg.GpgBin) assert.Equal(t, "--keyserver-options", cfg.GpgFlags) assert.Equal(t, "-s", cfg.MFlags) assert.Equal(t, "votes", cfg.SortBy) assert.Equal(t, "name", cfg.SearchBy) assert.Equal(t, "--depth=1", cfg.GitFlags) assert.Equal(t, "yes", cfg.RemoveMake) assert.Equal(t, "doas", cfg.SudoBin) assert.Equal(t, "-n", cfg.SudoFlags) assert.Equal(t, "all", cfg.ReDownload) assert.Equal(t, "All", cfg.AnswerClean) assert.Equal(t, "None", cfg.AnswerDiff) assert.Equal(t, "None", cfg.AnswerEdit) assert.Equal(t, "None", cfg.AnswerUpgrade) assert.Equal(t, "all", string(cfg.ReBuild)) // Integer options assert.Equal(t, 100, cfg.RequestSplitN) assert.Equal(t, 3, cfg.CompletionInterval) assert.Equal(t, 4, cfg.MaxConcurrentDownloads) // Boolean options assert.True(t, cfg.BottomUp) assert.False(t, cfg.SudoLoop) assert.False(t, cfg.Devel) assert.True(t, cfg.CleanAfter) assert.False(t, cfg.KeepSrc) assert.True(t, cfg.Provides) assert.False(t, cfg.PGPFetch) assert.True(t, cfg.CleanMenu) assert.False(t, cfg.DiffMenu) assert.True(t, cfg.EditMenu) assert.False(t, cfg.CombinedUpgrade) assert.True(t, cfg.UseAsk) assert.False(t, cfg.BatchInstall) assert.True(t, cfg.SingleLineResults) assert.False(t, cfg.SeparateSources) assert.False(t, cfg.Debug) assert.True(t, cfg.UseRPC) assert.False(t, cfg.DoubleConfirm) }) t.Run("load INI without section header", func(t *testing.T) { t.Parallel() content := `# Config without section header AurUrl = https://custom.aur.org Devel = true RequestSplitN = 250 ` tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") require.NoError(t, os.WriteFile(iniPath, []byte(content), 0o644)) cfg := DefaultConfig("test") err := cfg.loadINI(iniPath) require.NoError(t, err) assert.Equal(t, "https://custom.aur.org", cfg.AURURL) assert.True(t, cfg.Devel) assert.Equal(t, 250, cfg.RequestSplitN) }) t.Run("load INI with boolean keys (no value)", func(t *testing.T) { t.Parallel() content := `# Boolean keys without values are treated as true Devel SudoLoop CleanAfter ` tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") require.NoError(t, os.WriteFile(iniPath, []byte(content), 0o644)) cfg := DefaultConfig("test") cfg.Devel = false cfg.SudoLoop = false cfg.CleanAfter = false err := cfg.loadINI(iniPath) require.NoError(t, err) assert.True(t, cfg.Devel) assert.True(t, cfg.SudoLoop) assert.True(t, cfg.CleanAfter) }) t.Run("unknown options are ignored", func(t *testing.T) { t.Parallel() content := `AurUrl = https://custom.aur.org unknownoption = somevalue anotherunknown = 123 ` tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") require.NoError(t, os.WriteFile(iniPath, []byte(content), 0o644)) cfg := DefaultConfig("test") err := cfg.loadINI(iniPath) require.NoError(t, err) assert.Equal(t, "https://custom.aur.org", cfg.AURURL) }) } func TestSystemConfigPath(t *testing.T) { t.Parallel() assert.Equal(t, "/etc/yay.conf", SystemConfigPath) } func TestConfigurationSaveINI(t *testing.T) { t.Parallel() t.Run("save and reload config", func(t *testing.T) { t.Parallel() cfg := DefaultConfig("test") cfg.AURURL = "https://custom.aur.org" cfg.BuildDir = "/custom/build" cfg.Editor = "nvim" cfg.Devel = true cfg.SudoLoop = true cfg.RequestSplitN = 150 cfg.BottomUp = true cfg.CleanAfter = false tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") err := cfg.SaveINI(iniPath) require.NoError(t, err) // Verify file was created _, err = os.Stat(iniPath) require.NoError(t, err) // Load into new config and verify values cfg2 := DefaultConfig("test") err = cfg2.loadINI(iniPath) require.NoError(t, err) assert.Equal(t, "https://custom.aur.org", cfg2.AURURL) assert.Equal(t, "/custom/build", cfg2.BuildDir) assert.Equal(t, "nvim", cfg2.Editor) assert.True(t, cfg2.Devel) assert.True(t, cfg2.SudoLoop) assert.Equal(t, 150, cfg2.RequestSplitN) assert.True(t, cfg2.BottomUp) assert.False(t, cfg2.CleanAfter) }) t.Run("save creates directory if not exists", func(t *testing.T) { t.Parallel() cfg := DefaultConfig("test") cfg.AURURL = "https://test.aur.org" tmpDir := t.TempDir() nestedPath := filepath.Join(tmpDir, "nested", "dir", "yay.conf") err := cfg.SaveINI(nestedPath) require.NoError(t, err) // Verify file was created _, err = os.Stat(nestedPath) require.NoError(t, err) }) t.Run("roundtrip preserves all field types", func(t *testing.T) { t.Parallel() cfg := DefaultConfig("test") // String fields cfg.AURURL = "https://roundtrip.aur.org" cfg.AURRPCURL = "https://roundtrip.aur.org/rpc" cfg.BuildDir = "/roundtrip/build" cfg.Editor = "emacs" cfg.EditorFlags = "-nw" cfg.SudoBin = "doas" cfg.SudoFlags = "-n" cfg.ReDownload = "all" cfg.ReBuild = "tree" // Integer fields cfg.RequestSplitN = 75 cfg.CompletionInterval = 5 cfg.MaxConcurrentDownloads = 8 // Boolean fields cfg.BottomUp = true cfg.SudoLoop = false cfg.Devel = true cfg.CleanAfter = false cfg.UseRPC = true cfg.BatchInstall = true tmpDir := t.TempDir() iniPath := filepath.Join(tmpDir, "yay.conf") err := cfg.SaveINI(iniPath) require.NoError(t, err) cfg2 := DefaultConfig("test") err = cfg2.loadINI(iniPath) require.NoError(t, err) // Verify all fields assert.Equal(t, cfg.AURURL, cfg2.AURURL) assert.Equal(t, cfg.AURRPCURL, cfg2.AURRPCURL) assert.Equal(t, cfg.BuildDir, cfg2.BuildDir) assert.Equal(t, cfg.Editor, cfg2.Editor) assert.Equal(t, cfg.EditorFlags, cfg2.EditorFlags) assert.Equal(t, cfg.SudoBin, cfg2.SudoBin) assert.Equal(t, cfg.SudoFlags, cfg2.SudoFlags) assert.Equal(t, cfg.ReDownload, cfg2.ReDownload) assert.Equal(t, cfg.ReBuild, cfg2.ReBuild) assert.Equal(t, cfg.RequestSplitN, cfg2.RequestSplitN) assert.Equal(t, cfg.CompletionInterval, cfg2.CompletionInterval) assert.Equal(t, cfg.MaxConcurrentDownloads, cfg2.MaxConcurrentDownloads) assert.Equal(t, cfg.BottomUp, cfg2.BottomUp) assert.Equal(t, cfg.SudoLoop, cfg2.SudoLoop) assert.Equal(t, cfg.Devel, cfg2.Devel) assert.Equal(t, cfg.CleanAfter, cfg2.CleanAfter) assert.Equal(t, cfg.UseRPC, cfg2.UseRPC) assert.Equal(t, cfg.BatchInstall, cfg2.BatchInstall) }) } ================================================ FILE: pkg/settings/parser/parser.go ================================================ package parser import ( "bufio" "errors" "fmt" "maps" "os" "strings" "github.com/leonelquinteros/gotext" ) type Option struct { Global bool Args []string } func (o *Option) Add(args ...string) { if o.Args == nil { o.Args = args return } o.Args = append(o.Args, args...) } func (o *Option) First() string { if len(o.Args) == 0 { return "" } return o.Args[0] } func (o *Option) Set(arg string) { o.Args = []string{arg} } func (o *Option) String() string { return fmt.Sprintf("Global:%v Args:%v", o.Global, o.Args) } // Arguments Parses command line arguments in a way we can interact with programmatically but // also in a way that can easily be passed to pacman later on. type Arguments struct { Op string Options map[string]*Option Targets []string } func (a *Arguments) String() string { return fmt.Sprintf("Op:%v Options:%+v Targets: %v", a.Op, a.Options, a.Targets) } func (a *Arguments) CreateOrAppendOption(option string, values ...string) { if a.Options[option] == nil { a.Options[option] = &Option{ Args: values, } } else { a.Options[option].Add(values...) } } func MakeArguments() *Arguments { return &Arguments{ "", make(map[string]*Option), make([]string, 0), } } func (a *Arguments) CopyGlobal() *Arguments { cp := MakeArguments() for k, v := range a.Options { if v.Global { cp.Options[k] = v } } return cp } func (a *Arguments) Copy() (cp *Arguments) { cp = MakeArguments() cp.Op = a.Op maps.Copy(cp.Options, a.Options) cp.Targets = make([]string, len(a.Targets)) copy(cp.Targets, a.Targets) return } func (a *Arguments) DelArg(options ...string) { for _, option := range options { delete(a.Options, option) } } func (a *Arguments) NeedRoot(mode TargetMode) bool { if a.ExistsArg("h", "help") { return false } switch a.Op { case "D", "database": if a.ExistsArg("k", "check") { return false } return true case "F", "files": if a.ExistsArg("y", "refresh") { return true } return false case "Q", "query": if a.ExistsArg("k", "check") { return true } return false case "R", "remove": if a.ExistsArg("p", "print", "print-format") { return false } return true case "S", "sync": switch { case a.ExistsArg("y", "refresh"): return true case a.ExistsArg("p", "print", "print-format"): return false case a.ExistsArg("s", "search"): return false case a.ExistsArg("l", "list"): return false case a.ExistsArg("g", "groups"): return false case a.ExistsArg("i", "info"): return false case a.ExistsArg("c", "clean") && mode == ModeAUR: return false } return true case "U", "upgrade": return true default: return false } } func (a *Arguments) addOP(op string) error { if a.Op != "" { return errors.New(gotext.Get("only one operation may be used at a time")) } a.Op = op return nil } func (a *Arguments) addParam(option, arg string) error { if !isArg(option) { return errors.New(gotext.Get("invalid option '%s'", option)) } if isOp(option) { return a.addOP(option) } a.CreateOrAppendOption(option, strings.Split(arg, ",")...) if isGlobal(option) { a.Options[option].Global = true } return nil } func (a *Arguments) AddArg(options ...string) error { for _, option := range options { err := a.addParam(option, "") if err != nil { return err } } return nil } // Multiple args acts as an OR operator. func (a *Arguments) ExistsArg(options ...string) bool { for _, option := range options { if _, exists := a.Options[option]; exists { return true } } return false } func (a *Arguments) GetArg(options ...string) (arg string, double, exists bool) { for _, option := range options { value, exists := a.Options[option] if exists { return value.First(), len(value.Args) >= 2, len(value.Args) >= 1 } } return arg, false, false } func (a *Arguments) GetArgs(option string) (args []string) { value, exists := a.Options[option] if exists { return value.Args } return nil } func (a *Arguments) AddTarget(targets ...string) { a.Targets = append(a.Targets, targets...) } func (a *Arguments) ClearTargets() { a.Targets = make([]string, 0) } // Multiple args acts as an OR operator. func (a *Arguments) ExistsDouble(options ...string) bool { for _, option := range options { if value, exists := a.Options[option]; exists { return len(value.Args) >= 2 } } return false } func (a *Arguments) FormatArgs() (args []string) { if a.Op != "" { args = append(args, formatArg(a.Op)) } for option, arg := range a.Options { if arg.Global || option == "--" { continue } formattedOption := formatArg(option) for _, value := range arg.Args { args = append(args, formattedOption) if hasParam(option) { args = append(args, value) } } } return args } func (a *Arguments) FormatGlobals() (args []string) { for option, arg := range a.Options { if !arg.Global { continue } formattedOption := formatArg(option) for _, value := range arg.Args { args = append(args, formattedOption) if hasParam(option) { args = append(args, value) } } } return args } func formatArg(arg string) string { if len(arg) > 1 { arg = "--" + arg } else { arg = "-" + arg } return arg } func isArg(arg string) bool { switch arg { case "-", "--": case "ask": case "D", "database": case "Q", "query": case "R", "remove": case "S", "sync": case "T", "deptest": case "U", "upgrade": case "F", "files": case "V", "version": case "h", "help": case "Y", "yay": case "W", "web": case "P", "show": case "B", "build": case "G", "getpkgbuild": case "b", "dbpath": case "r", "root": case "v", "verbose": case "arch": case "cachedir": case "color": case "config": case "debug": case "gpgdir": case "hookdir": case "logfile": case "noconfirm": case "confirm": case "disable-download-timeout": case "sysroot": case "d", "nodeps": case "assume-installed": case "dbonly": case "noprogressbar": case "numberupgrades": case "noscriptlet": case "p", "print": case "print-format": case "asdeps": case "asexplicit": case "ignore": case "ignoregroup": case "needed": case "overwrite": case "f", "force": case "c", "changelog": case "deps": case "e", "explicit": case "g", "groups": case "i", "info": case "k", "check": case "l", "list": case "m", "foreign": case "n", "native": case "o", "owns": case "file": case "q", "quiet": case "s", "search": case "t", "unrequired": case "u", "upgrades": case "cascade": case "nosave": case "recursive": case "unneeded": case "clean": case "sysupgrade": case "w", "downloadonly": case "y", "refresh": case "x", "regex": case "machinereadable": case "disable-sandbox": // yay options case "aururl": case "aurrpcurl": case "save": case "afterclean", "cleanafter": case "keepsrc": case "devel": case "topdown": case "bottomup": case "completioninterval": case "sortby": case "searchby": case "redownload": case "redownloadall": case "noredownload": case "rebuild": case "rebuildall": case "rebuildtree": case "norebuild": case "batchinstall": case "answerclean": case "noanswerclean": case "answerdiff": case "noanswerdiff": case "answeredit": case "noansweredit": case "answerupgrade": case "noanswerupgrade": case "gpgflags": case "mflags": case "gitflags": case "builddir": case "editor": case "editorflags": case "makepkg": case "makepkgconf": case "nomakepkgconf": case "pacman": case "git": case "gpg": case "sudo": case "sudoflags": case "requestsplitn": case "sudoloop": case "provides": case "pgpfetch": case "cleanmenu": case "diffmenu": case "editmenu": case "useask": case "combinedupgrade": case "a", "aur": case "N", "repo": case "removemake": case "noremovemake": case "askremovemake": case "askyesremovemake": case "complete": case "stats": case "news": case "gendb": case "currentconfig": case "defaultconfig": case "singlelineresults": case "doublelineresults": case "separatesources": default: return false } return true } func isOp(op string) bool { switch op { case "V", "version": case "D", "database": case "F", "files": case "Q", "query": case "R", "remove": case "S", "sync": case "T", "deptest": case "U", "upgrade": // yay specific case "Y", "yay": case "W", "web": case "B", "build": case "P", "show": case "G", "getpkgbuild": default: return false } return true } func isGlobal(op string) bool { switch op { case "b", "dbpath": case "r", "root": case "v", "verbose": case "arch": case "cachedir": case "color": case "config": case "debug": case "gpgdir": case "hookdir": case "logfile": case "noconfirm": case "confirm": default: return false } return true } func hasParam(arg string) bool { switch arg { case "dbpath", "b": case "root", "r": case "sysroot": case "config": case "ignore": case "assume-installed": case "overwrite": case "ask": case "cachedir": case "hookdir": case "logfile": case "ignoregroup": case "arch": case "print-format": case "gpgdir": case "color": // yay params case "aururl": case "aurrpcurl": case "mflags": case "gpgflags": case "gitflags": case "builddir": case "editor": case "editorflags": case "makepkg": case "makepkgconf": case "pacman": case "git": case "gpg": case "sudo": case "sudoflags": case "requestsplitn": case "answerclean": case "answerdiff": case "answeredit": case "answerupgrade": case "completioninterval": case "sortby": case "searchby": default: return false } return true } // Parses short hand options such as: // -Syu -b /some/path -. func (a *Arguments) parseShortOption(arg, param string) (usedNext bool, err error) { if arg == "-" { err = a.AddArg("-") return } arg = arg[1:] for k, _char := range arg { char := string(_char) if hasParam(char) { if k < len(arg)-1 { err = a.addParam(char, arg[k+1:]) } else { usedNext = true err = a.addParam(char, param) } break } else { err = a.AddArg(char) if err != nil { return } } } return } // Parses full length options such as: // --sync --refresh --sysupgrade --dbpath /some/path --. func (a *Arguments) parseLongOption(arg, param string) (usedNext bool, err error) { if arg == "--" { err = a.AddArg(arg) return } arg = arg[2:] switch split := strings.SplitN(arg, "=", 2); { case len(split) == 2: err = a.addParam(split[0], split[1]) case hasParam(arg): err = a.addParam(arg, param) usedNext = true default: err = a.AddArg(arg) } return } func (a *Arguments) parseStdin() error { fi, err := os.Stdin.Stat() if err != nil { return err } // Ensure data is piped if (fi.Mode() & os.ModeCharDevice) != 0 { return errors.New(gotext.Get("argument '-' specified without input on stdin")) } scanner := bufio.NewScanner(os.Stdin) scanner.Split(bufio.ScanLines) for scanner.Scan() { a.AddTarget(scanner.Text()) } return os.Stdin.Close() } func (a *Arguments) Parse() error { args := os.Args[1:] usedNext := false for k, arg := range args { var nextArg string if usedNext { usedNext = false continue } if k+1 < len(args) { nextArg = args[k+1] } var err error switch { case a.ExistsArg("--"): a.AddTarget(arg) case strings.HasPrefix(arg, "--"): usedNext, err = a.parseLongOption(arg, nextArg) case strings.HasPrefix(arg, "-"): usedNext, err = a.parseShortOption(arg, nextArg) default: a.AddTarget(arg) } if err != nil { return err } } if a.Op == "" { if len(a.Targets) > 0 { a.Op = "Y" } else { // Handle args incompatible with -Syu if a.ExistsArg("c", "clean") { fmt.Println(gotext.Get("the clean command requires an operation to be specified")) fmt.Println(gotext.Get("did you mean yay -Scc (clean caches) or yay -Ycc (clean orphaned packages)?")) return errors.New(gotext.Get("invalid option")) } if _, err := a.parseShortOption("-Syu", ""); err != nil { return err } } } if a.ExistsArg("-") { if err := a.parseStdin(); err != nil { return err } a.DelArg("-") file, err := os.Open("/dev/tty") if err != nil { return err } os.Stdin = file } return nil } ================================================ FILE: pkg/settings/parser/parser_test.go ================================================ //go:build !integration // +build !integration package parser import ( "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOption_Add(t *testing.T) { t.Parallel() type fields struct { Args []string } type args struct { arg string } tests := []struct { name string fields fields args args want []string }{ {name: "simple add", fields: fields{ Args: []string{"a", "b"}, }, args: args{ arg: "c", }, want: []string{"a", "b", "c"}}, {name: "null add", fields: fields{ Args: nil, }, args: args{ arg: "c", }, want: []string{"c"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() o := &Option{ Args: tt.fields.Args, } o.Add(tt.args.arg) assert.ElementsMatch(t, tt.want, o.Args) }) } } func TestOption_Set(t *testing.T) { t.Parallel() type fields struct { Args []string } type args struct { arg string } tests := []struct { name string fields fields args args want []string }{ {name: "simple set", fields: fields{ Args: []string{"a", "b"}, }, args: args{ arg: "c", }, want: []string{"c"}}, {name: "null set", fields: fields{ Args: nil, }, args: args{ arg: "c", }, want: []string{"c"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() o := &Option{ Args: tt.fields.Args, } o.Set(tt.args.arg) assert.ElementsMatch(t, tt.want, o.Args) }) } } func TestOption_First(t *testing.T) { t.Parallel() type fields struct { Args []string } tests := []struct { name string fields fields want string }{ {name: "simple first", fields: fields{ Args: []string{"a", "b"}, }, want: "a"}, {name: "null first", fields: fields{ Args: nil, }, want: ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() o := &Option{ Args: tt.fields.Args, } assert.Equal(t, tt.want, o.First()) }) } } func TestMakeArguments(t *testing.T) { t.Parallel() args := MakeArguments() assert.NotNil(t, args) assert.Equal(t, "", args.Op) assert.Empty(t, args.Options) assert.Empty(t, args.Targets) } func TestArguments_CopyGlobal(t *testing.T) { t.Parallel() type fields struct { Op string Options map[string]*Option Targets []string } tests := []struct { name string fields fields want *Arguments }{ {name: "simple", fields: fields{ Op: "Q", Options: map[string]*Option{ "a": {}, "arch": { Global: true, Args: []string{"x86_x64"}, }, "boo": {Global: true, Args: []string{"a", "b"}}, }, Targets: []string{"a", "b"}, }, want: &Arguments{ Op: "", Options: map[string]*Option{ "arch": { Global: true, Args: []string{"x86_x64"}, }, "boo": {Global: true, Args: []string{"a", "b"}}, }, Targets: []string{}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cmdArgs := &Arguments{ Op: tt.fields.Op, Options: tt.fields.Options, Targets: tt.fields.Targets, } got := cmdArgs.CopyGlobal() assert.NotEqualValues(t, tt.fields.Options, got.Options) assert.NotEqualValues(t, tt.fields.Targets, got.Targets) assert.NotEqual(t, tt.fields.Op, got.Op) assert.Equal(t, tt.want, got) }) } } func TestArguments_Copy(t *testing.T) { t.Parallel() type fields struct { Op string Options map[string]*Option Targets []string } tests := []struct { name string fields fields want *Arguments }{ {name: "simple", fields: fields{ Op: "Q", Options: map[string]*Option{ "a": {}, "arch": { Args: []string{"x86_x64"}, Global: true, }, "boo": {Args: []string{"a", "b"}, Global: true}, }, Targets: []string{"a", "b"}, }, want: &Arguments{ Op: "Q", Options: map[string]*Option{ "a": {}, "arch": { Global: true, Args: []string{"x86_x64"}, }, "boo": {Args: []string{"a", "b"}, Global: true}, }, Targets: []string{"a", "b"}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cmdArgs := &Arguments{ Op: tt.fields.Op, Options: tt.fields.Options, Targets: tt.fields.Targets, } got := cmdArgs.Copy() assert.Equal(t, cmdArgs, got) assert.Equal(t, tt.want, got) }) } } func TestArguments_DelArg(t *testing.T) { t.Parallel() args := MakeArguments() args.addParam("arch", "arg") args.addParam("ask", "arg") args.DelArg("arch", "ask") assert.Empty(t, args.Options) } func TestArguments_FormatArgs(t *testing.T) { t.Parallel() type fields struct { Op string Options map[string]*Option Targets []string } tests := []struct { name string fields fields wantArgs []string }{ {name: "simple", fields: fields{ Op: "S", Options: map[string]*Option{}, Targets: []string{"yay", "yay-bin", "yay-git"}, }, wantArgs: []string{"-S"}}, {name: "only global", fields: fields{ Op: "Y", Options: map[string]*Option{"noconfirm": {Global: true, Args: []string{""}}}, Targets: []string{"yay", "yay-bin", "yay-git"}, }, wantArgs: []string{"-Y"}}, {name: "options single", fields: fields{ Op: "Y", Options: map[string]*Option{"overwrite": {Args: []string{"/tmp/a"}}, "useask": {Args: []string{""}}}, Targets: []string{}, }, wantArgs: []string{"-Y", "--overwrite", "/tmp/a", "--useask"}}, {name: "options doubles", fields: fields{ Op: "Y", Options: map[string]*Option{"overwrite": {Args: []string{"/tmp/a", "/tmp/b", "/tmp/c"}}, "needed": {Args: []string{""}}}, Targets: []string{}, }, wantArgs: []string{"-Y", "--overwrite", "/tmp/a", "--overwrite", "/tmp/b", "--overwrite", "/tmp/c", "--needed"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cmdArgs := &Arguments{ Op: tt.fields.Op, Options: tt.fields.Options, Targets: tt.fields.Targets, } gotArgs := cmdArgs.FormatArgs() assert.ElementsMatch(t, gotArgs, tt.wantArgs) }) } } func TestArguments_FormatGlobalArgs(t *testing.T) { t.Parallel() type fields struct { Op string Options map[string]*Option Targets []string } tests := []struct { name string fields fields wantArgs []string }{ {name: "simple", fields: fields{ Op: "S", Options: map[string]*Option{"dbpath": {Global: true, Args: []string{"/tmp/a", "/tmp/b"}}}, Targets: []string{"yay", "yay-bin", "yay-git"}, }, wantArgs: []string{"--dbpath", "/tmp/a", "--dbpath", "/tmp/b"}}, {name: "only global", fields: fields{ Op: "Y", Options: map[string]*Option{"noconfirm": {Global: true, Args: []string{""}}}, Targets: []string{"yay", "yay-bin", "yay-git"}, }, wantArgs: []string{"--noconfirm"}}, {name: "options single", fields: fields{ Op: "Y", Options: map[string]*Option{"overwrite": {Args: []string{"/tmp/a"}}, "useask": {Args: []string{""}}}, Targets: []string{}, }, wantArgs: []string(nil)}, {name: "options doubles", fields: fields{ Op: "Y", Options: map[string]*Option{"overwrite": {Args: []string{"/tmp/a", "/tmp/b", "/tmp/c"}}, "needed": {Args: []string{""}}}, Targets: []string{}, }, wantArgs: []string(nil)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cmdArgs := &Arguments{ Op: tt.fields.Op, Options: tt.fields.Options, Targets: tt.fields.Targets, } gotArgs := cmdArgs.FormatGlobals() assert.ElementsMatch(t, tt.wantArgs, gotArgs) }) } } func Test_isArg(t *testing.T) { t.Parallel() got := isArg("zorg") assert.False(t, got) got = isArg("dbpath") assert.True(t, got) } func TestArguments_ParseStdin(t *testing.T) { input := []byte("yay") r, w, err := os.Pipe() require.NoError(t, err) _, err = w.Write(input) require.NoError(t, err) w.Close() // Restore stdin after the test. defer func(o *os.File) { os.Stdin = o }(os.Stdin) os.Stdin = r args := MakeArguments() err = args.parseStdin() assert.NoError(t, err) expectedTargets := []string{string(input)} assert.ElementsMatch(t, args.Targets, expectedTargets) } func TestArguments_ParseStdin_broken_pipe(t *testing.T) { r, _, err := os.Pipe() require.NoError(t, err) r.Close() // Close early to break pipe // Restore stdin after the test. defer func(o *os.File) { os.Stdin = o }(os.Stdin) os.Stdin = r args := MakeArguments() err = args.parseStdin() assert.Error(t, err) } ================================================ FILE: pkg/settings/parser/rebuild_mode.go ================================================ package parser type RebuildMode string const ( RebuildModeNo RebuildMode = "no" RebuildModeYes RebuildMode = "yes" RebuildModeTree RebuildMode = "tree" RebuildModeAll RebuildMode = "all" ) ================================================ FILE: pkg/settings/parser/target_mode.go ================================================ package parser type TargetMode int const ( ModeAny TargetMode = iota ModeAUR ModeRepo ) func (mode TargetMode) AtLeastAUR() bool { return mode == ModeAny || mode == ModeAUR } func (mode TargetMode) AtLeastRepo() bool { return mode == ModeAny || mode == ModeRepo } ================================================ FILE: pkg/settings/yay.conf ================================================ # /etc/yay.conf - System-wide yay configuration [options] # AUR Settings AurUrl = https://aur.archlinux.org #AurRpcUrl = https://aur.archlinux.org/rpc # Directories BuildDir = ~/.cache/yay # Binaries #Editor = vim #EditorFlags = MakepkgBin = makepkg #MakepkgConf = PacmanBin = pacman PacmanConf = /etc/pacman.conf GitBin = git GpgBin = gpg SudoBin = sudo # Flags # GpgFlags = # MFlags = # GitFlags = # SudoFlags = # Search/Display BottomUp # SingleLineResults SeparateSources # Sorting #SortBy = SearchBy = name-desc # Build options #Devel #CleanAfter #KeepSrc #BatchInstall RemoveMake = ask # Download options ReDownload = no ReBuild = no PgpFetch # Menus CleanMenu DiffMenu # EditMenu # Prompts #AnswerClean = #AnswerDiff = #AnswerEdit = #AnswerUpgrade = # Behavior CombinedUpgrade #SudoLoop #Provides #UseAsk DoubleConfirm Rpc #Debug # Limits RequestSplitN = 150 CompletionInterval = 7 MaxConcurrentDownloads = 1 ================================================ FILE: pkg/sync/build/errors.go ================================================ package build import ( "errors" "strings" "github.com/leonelquinteros/gotext" ) var ErrInstallRepoPkgs = errors.New(gotext.Get("error installing repo packages")) type FailedIgnoredPkgError struct { pkgErrors map[string]error } func (e *FailedIgnoredPkgError) Error() string { var sb strings.Builder sb.WriteString(gotext.Get("Failed to install the following packages. Manual intervention is required:")) for pkg, err := range e.pkgErrors { sb.WriteString("\n") sb.WriteString(pkg) sb.WriteString(" - ") sb.WriteString(err.Error()) } return sb.String() } type PkgDestNotInListError struct { name string } func (e *PkgDestNotInListError) Error() string { return gotext.Get("could not find PKGDEST for: %s", e.name) } type FindPkgDestError struct { name, pkgDest string } func (e *FindPkgDestError) Error() string { return gotext.Get( "the PKGDEST for %s is listed by makepkg but does not exist: %s", e.name, e.pkgDest) } type SetPkgReasonError struct { exp bool // explicit } func (e *SetPkgReasonError) Error() string { reason := gotext.Get("explicit") if !e.exp { reason = gotext.Get("dependency") } return gotext.Get("error updating package install reason to %s", reason) } type NoPkgDestsFoundError struct { dir string } func (e *NoPkgDestsFoundError) Error() string { return gotext.Get("could not find any package archives listed in %s", e.dir) } ================================================ FILE: pkg/sync/build/installer.go ================================================ package build import ( "context" "fmt" "maps" "os" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" ) type ( PostInstallHookFunc func(ctx context.Context) error Installer struct { dbExecutor db.Executor postInstallHooks []PostInstallHookFunc failedAndIgnored map[string]error exeCmd exe.ICmdBuilder vcsStore vcs.Store targetMode parser.TargetMode rebuildMode parser.RebuildMode origTargets mapset.Set[string] downloadOnly bool installBuiltPackages bool log *text.Logger manualConfirmRequired bool } ) func NewInstaller(dbExecutor db.Executor, exeCmd exe.ICmdBuilder, vcsStore vcs.Store, targetMode parser.TargetMode, rebuildMode parser.RebuildMode, downloadOnly bool, logger *text.Logger, ) *Installer { return &Installer{ dbExecutor: dbExecutor, postInstallHooks: []PostInstallHookFunc{}, failedAndIgnored: map[string]error{}, exeCmd: exeCmd, vcsStore: vcsStore, targetMode: targetMode, rebuildMode: rebuildMode, downloadOnly: downloadOnly, installBuiltPackages: true, log: logger, manualConfirmRequired: true, } } func (installer *Installer) SetInstallBuiltPackages(install bool) { installer.installBuiltPackages = install } func (installer *Installer) CompileFailedAndIgnored() (map[string]error, error) { if len(installer.failedAndIgnored) == 0 { return installer.failedAndIgnored, nil } return installer.failedAndIgnored, &FailedIgnoredPkgError{ pkgErrors: installer.failedAndIgnored, } } func (installer *Installer) AddPostInstallHook(hook PostInstallHookFunc) { if hook == nil { return } installer.postInstallHooks = append(installer.postInstallHooks, hook) } func (installer *Installer) RunPostInstallHooks(ctx context.Context) error { var errMulti multierror.MultiError for _, hook := range installer.postInstallHooks { if err := hook(ctx); err != nil { errMulti.Add(err) } } return errMulti.Return() } func (installer *Installer) Install(ctx context.Context, cmdArgs *parser.Arguments, targets []map[string]*dep.InstallInfo, pkgBuildDirs map[string]string, excluded []string, manualConfirmRequired bool, ) error { installer.log.Debugln("manualConfirmRequired:", manualConfirmRequired) installer.manualConfirmRequired = manualConfirmRequired installer.origTargets = mapset.NewThreadUnsafeSet[string]() for _, targetString := range cmdArgs.Targets { installer.origTargets.Add(dep.ToTarget(targetString).Name) } installer.log.Debugln("origTargets:", installer.origTargets) // Reorganize targets into layers of dependencies var errMulti multierror.MultiError for i := len(targets) - 1; i >= 0; i-- { lastLayer := i == 0 errI := installer.handleLayer(ctx, cmdArgs, targets[i], pkgBuildDirs, lastLayer, excluded) if errI == nil && lastLayer { // success after rollups return nil } if errI != nil { errMulti.Add(errI) if lastLayer { break } // rollup installer.log.Warnln(gotext.Get("Failed to install layer, rolling up to next layer."), "error:", errI) targets[i-1] = mergeLayers(targets[i-1], targets[i]) } } return errMulti.Return() } func mergeLayers(layer1, layer2 map[string]*dep.InstallInfo) map[string]*dep.InstallInfo { maps.Copy(layer1, layer2) return layer1 } func (installer *Installer) appendNoConfirm() bool { return !installer.manualConfirmRequired || settings.NoConfirm } func (installer *Installer) handleLayer(ctx context.Context, cmdArgs *parser.Arguments, layer map[string]*dep.InstallInfo, pkgBuildDirs map[string]string, lastLayer bool, excluded []string, ) error { // Install layer nameToBaseMap := make(map[string]string, len(layer)) syncDeps, syncExp, syncGroups := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]() aurDeps, aurExp, aurOrigTargetBases := mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string](), mapset.NewThreadUnsafeSet[string]() upgradeSync := false for name, info := range layer { switch info.Source { case dep.AUR, dep.SrcInfo: nameToBaseMap[name] = *info.AURBase if installer.origTargets.Contains(name) { aurOrigTargetBases.Add(*info.AURBase) } switch info.Reason { case dep.Explicit: if cmdArgs.ExistsArg("asdeps", "asdep") { aurDeps.Add(name) } else { aurExp.Add(name) } case dep.Dep, dep.MakeDep, dep.CheckDep: // Only apply --asexplicit to packages that are explicitly targeted if cmdArgs.ExistsArg("asexplicit", "asexp") && installer.origTargets.Contains(name) { aurExp.Add(name) } else { aurDeps.Add(name) } } case dep.Sync: if info.Upgrade { upgradeSync = true continue // do not add to targets, let pacman handle it } compositePkgName := fmt.Sprintf("%s/%s", *info.SyncDBName, name) if info.IsGroup { syncGroups.Add(compositePkgName) continue } switch info.Reason { case dep.Explicit: if cmdArgs.ExistsArg("asdeps", "asdep") { syncDeps.Add(compositePkgName) } else { syncExp.Add(compositePkgName) } case dep.Dep, dep.MakeDep, dep.CheckDep: // Only apply --asexplicit to packages that are explicitly targeted if cmdArgs.ExistsArg("asexplicit", "asexp") && installer.origTargets.Contains(name) { syncExp.Add(compositePkgName) } else { syncDeps.Add(compositePkgName) } } } } installer.log.Debugln("syncDeps", syncDeps, "SyncExp", syncExp, "aurDeps", aurDeps, "aurExp", aurExp, "upgrade", upgradeSync) errShow := installer.installSyncPackages(ctx, cmdArgs, syncDeps, syncExp, syncGroups, excluded, upgradeSync, installer.appendNoConfirm()) if errShow != nil { return ErrInstallRepoPkgs } errAur := installer.installAURPackages(ctx, cmdArgs, aurDeps, aurExp, aurOrigTargetBases, nameToBaseMap, pkgBuildDirs, true, lastLayer, installer.appendNoConfirm()) return errAur } func (installer *Installer) installAURPackages(ctx context.Context, cmdArgs *parser.Arguments, aurDepNames, aurExpNames, aurOrigTargetBases mapset.Set[string], nameToBase, pkgBuildDirsByBase map[string]string, installIncompatible bool, lastLayer bool, noConfirm bool, ) error { all := aurDepNames.Union(aurExpNames).ToSlice() if len(all) == 0 { return nil } builtPkgDests := make(map[string]map[string]string) deps := make([]string, 0, aurDepNames.Cardinality()) exps := make([]string, 0, aurExpNames.Cardinality()) pkgArchives := make([]string, 0, len(all)) for _, name := range all { base := nameToBase[name] dir := pkgBuildDirsByBase[base] pkgdests, ok := builtPkgDests[base] if ok { installer.log.Debugln("skipping built pkgbase", base, "package", name) } else { var errMake error installer.log.Debugln("building pkgbase", base, "package", name) pkgdests, errMake = installer.buildPkg(ctx, dir, base, installIncompatible, cmdArgs.ExistsArg("needed"), aurOrigTargetBases.Contains(base)) builtPkgDests[base] = pkgdests if errMake != nil { if !lastLayer { return fmt.Errorf("%s - %w", gotext.Get("error making: %s", base), errMake) } installer.failedAndIgnored[name] = errMake installer.log.Errorln(gotext.Get("error making: %s", base), "-", errMake) continue } } if len(pkgdests) == 0 { installer.log.Warnln(gotext.Get("nothing to install for %s", text.Cyan(base))) continue } newPKGArchives, hasDebug, err := installer.getNewTargets(pkgdests, name) if err != nil { return err } pkgArchives = append(pkgArchives, newPKGArchives...) if isDep := installer.isDep(cmdArgs, aurExpNames, name); isDep { deps = append(deps, name) } else { exps = append(exps, name) } if hasDebug { deps = append(deps, name+"-debug") } } if len(pkgArchives) == 0 || !installer.installBuiltPackages { return nil } if err := installPkgArchive(ctx, installer.exeCmd, installer.targetMode, installer.vcsStore, cmdArgs, pkgArchives, noConfirm); err != nil { return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err) } if err := setInstallReason(ctx, installer.exeCmd, installer.targetMode, cmdArgs, deps, exps); err != nil { return fmt.Errorf("%s - %w", fmt.Sprintf(gotext.Get("error installing:")+" %v", pkgArchives), err) } return nil } func (installer *Installer) buildPkg(ctx context.Context, dir, base string, installIncompatible, needed, isTarget bool, ) (map[string]string, error) { args := []string{"--nobuild", "-f"} if !installer.exeCmd.GetKeepSrc() { args = append(args, "-C") } if installIncompatible { args = append(args, "--ignorearch") } // pkgver bump if err := installer.exeCmd.Show( installer.exeCmd.BuildMakepkgCmd(ctx, dir, args...)); err != nil { return nil, err } pkgdests, pkgVersion, errList := parsePackageList(ctx, installer.exeCmd, dir) if errList != nil { return nil, errList } switch { case needed && installer.pkgsAreAlreadyInstalled(pkgdests, pkgVersion) || installer.downloadOnly: args = []string{"--nobuild", "--noextract", "--ignorearch"} pkgdests = map[string]string{} installer.log.Warnln(gotext.Get("%s is up to date -- skipping", text.Cyan(base+"-"+pkgVersion))) case installer.skipAlreadyBuiltPkg(isTarget, pkgdests): args = []string{"--nobuild", "--noextract", "--ignorearch"} installer.log.Warnln(gotext.Get("%s already made -- skipping build", text.Cyan(base+"-"+pkgVersion))) default: args = []string{"-f", "--noconfirm", "--noextract", "--noprepare", "--holdver"} if installIncompatible { args = append(args, "--ignorearch") } } if !installer.exeCmd.GetKeepSrc() { args = append(args, "-c") } errMake := installer.exeCmd.Show( installer.exeCmd.BuildMakepkgCmd(ctx, dir, args...)) if errMake != nil { return nil, errMake } if installer.downloadOnly { return map[string]string{}, nil } return pkgdests, nil } func (installer *Installer) pkgsAreAlreadyInstalled(pkgdests map[string]string, pkgVersion string) bool { for pkgName := range pkgdests { if !installer.dbExecutor.IsCorrectVersionInstalled(pkgName, pkgVersion) { return false } } return true } func pkgsAreBuilt(logger *text.Logger, pkgdests map[string]string) bool { for _, pkgdest := range pkgdests { if _, err := os.Stat(pkgdest); err != nil { logger.Debugln("pkgIsBuilt:", pkgdest, "does not exist") return false } } return true } func (installer *Installer) skipAlreadyBuiltPkg(isTarget bool, pkgdests map[string]string) bool { switch installer.rebuildMode { case parser.RebuildModeNo: return pkgsAreBuilt(installer.log, pkgdests) case parser.RebuildModeYes: return !isTarget && pkgsAreBuilt(installer.log, pkgdests) // case parser.RebuildModeTree: // TODO // case parser.RebuildModeAll: // TODO default: // same as RebuildModeNo return pkgsAreBuilt(installer.log, pkgdests) } } func (*Installer) isDep(cmdArgs *parser.Arguments, aurExpNames mapset.Set[string], name string) bool { switch { case cmdArgs.ExistsArg("asdeps", "asdep"): return true case cmdArgs.ExistsArg("asexplicit", "asexp"): return false case aurExpNames.Contains(name): return false } return true } func (installer *Installer) getNewTargets(pkgdests map[string]string, name string, ) (archives []string, good bool, err error) { pkgdest, ok := pkgdests[name] if !ok { return nil, false, &PkgDestNotInListError{name: name} } pkgArchives := make([]string, 0, 2) if _, errStat := os.Stat(pkgdest); os.IsNotExist(errStat) { return nil, false, &FindPkgDestError{name: name, pkgDest: pkgdest} } pkgArchives = append(pkgArchives, pkgdest) debugName := name + "-debug" pkgdestDebug, ok := pkgdests[debugName] if ok { if _, errStat := os.Stat(pkgdestDebug); errStat == nil { pkgArchives = append(pkgArchives, pkgdestDebug) } else { ok = false } } return pkgArchives, ok, nil } func (installer *Installer) installSyncPackages(ctx context.Context, cmdArgs *parser.Arguments, syncDeps, // repo targets that are deps syncExp mapset.Set[string], // repo targets that are exp syncGroups mapset.Set[string], // repo targets that are groups excluded []string, upgrade bool, // run even without targets noConfirm bool, ) error { repoTargets := syncDeps.Union(syncExp).Union(syncGroups).ToSlice() if len(repoTargets) == 0 && !upgrade { return nil } arguments := cmdArgs.Copy() arguments.DelArg("asdeps", "asdep") arguments.DelArg("asexplicit", "asexp") arguments.DelArg("i", "install") arguments.Op = "S" arguments.ClearTargets() arguments.AddTarget(repoTargets...) // Don't upgrade all repo packages if only AUR upgrades are specified if installer.targetMode == parser.ModeAUR { arguments.DelArg("u", "upgrades") } if len(excluded) > 0 { arguments.CreateOrAppendOption("ignore", excluded...) } errShow := installer.exeCmd.Show(installer.exeCmd.BuildPacmanCmd(ctx, arguments, installer.targetMode, noConfirm)) if errShow != nil { return errShow } if errD := asdeps(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncDeps.ToSlice()); errD != nil { return errD } if errE := asexp(ctx, installer.exeCmd, installer.targetMode, cmdArgs, syncExp.ToSlice()); errE != nil { return errE } return nil } ================================================ FILE: pkg/sync/build/installer_test.go ================================================ package build import ( "context" "errors" "fmt" "io" "os" "os/exec" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } func ptrString(s string) *string { return &s } func TestInstaller_InstallNeeded(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string isInstalled bool isBuilt bool wantShow []string wantCapture []string } testCases := []testCase{ { desc: "not installed and not built", isInstalled: false, isBuilt: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --needed --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, }, { desc: "not installed and built", isInstalled: false, isBuilt: true, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --needed --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, }, { desc: "installed", isInstalled: true, isBuilt: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", }, wantCapture: []string{"makepkg --packagelist"}, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { tmpDir := td.TempDir() pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } i := 0 showOverride := func(cmd *exec.Cmd) error { i++ if i == 2 { if !tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } } return nil } // create a mock file if tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } isCorrectInstalledOverride := func(string, string) bool { return tc.isInstalled } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddArg("needed") cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } targets := []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, } errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_BuildOnlySkipsInstall(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tmpDir := t.TempDir() pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } i := 0 showOverride := func(cmd *exec.Cmd) error { i++ if i == 2 { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } return nil } mockDB := &mock.DBExecutor{} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) installer.SetInstallBuiltPackages(false) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } targets := []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, } err = installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs, []string{}, false) require.NoError(t, err) wantShow := []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", } require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, 1) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), show) assert.NotContains(t, show, "pacman -U") } for _, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(t, strings.Split(capture, " "), strings.Split("makepkg --packagelist", " "), capture) } } func TestInstaller_InstallMixedSourcesAndLayers(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string targets []map[string]*dep.InstallInfo wantShow []string wantCapture []string } tmpDir := t.TempDir() tmpDirJfin := t.TempDir() testCases := []testCase{ { desc: "same layer -- different sources", wantShow: []string{ "pacman -S --config /etc/pacman.conf -- core/linux", "pacman -D -q --asdeps --config /etc/pacman.conf -- linux", "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, "linux": { Source: dep.Sync, Reason: dep.Dep, Version: "17.0.0-1", SyncDBName: ptrString("core"), }, }, }, }, { desc: "different layer -- different sources", wantShow: []string{ "pacman -S --config /etc/pacman.conf -- core/linux", "pacman -D -q --asdeps --config /etc/pacman.conf -- linux", "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, { "linux": { Source: dep.Sync, Reason: dep.Dep, Version: "17.0.0-1", SyncDBName: ptrString("core"), }, }, }, }, { desc: "same layer -- sync", wantShow: []string{ "pacman -S --config /etc/pacman.conf -- extra/linux-zen core/linux", "pacman -D -q --asexplicit --config /etc/pacman.conf -- linux-zen linux", }, wantCapture: []string{}, targets: []map[string]*dep.InstallInfo{ { "linux-zen": { Source: dep.Sync, Reason: dep.Explicit, Version: "18.0.0-1", SyncDBName: ptrString("extra"), }, "linux": { Source: dep.Sync, Reason: dep.Explicit, Version: "17.0.0-1", SyncDBName: ptrString("core"), }, }, }, }, { desc: "same layer -- aur", wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay", }, wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, "jellyfin-server": { Source: dep.AUR, Reason: dep.Explicit, Version: "10.8.8-1", SrcinfoPath: ptrString(tmpDirJfin + "/.SRCINFO"), AURBase: ptrString("jellyfin"), }, }, }, }, { desc: "different layer -- aur", wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-server-10.8.8-1-x86_64.pkg.tar.zst", "pacman -D -q --asdeps --config /etc/pacman.conf -- jellyfin-server", "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- pacman -U --config /etc/pacman.conf -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- yay", }, wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, { "jellyfin-server": { Source: dep.AUR, Reason: dep.MakeDep, Version: "10.8.8-1", SrcinfoPath: ptrString(tmpDirJfin + "/.SRCINFO"), AURBase: ptrString("jellyfin"), }, }, }, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" jfinPkgTar := tmpDirJfin + "/jellyfin-server-10.8.8-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { if cmd.Dir == tmpDirJfin { return jfinPkgTar, "", nil } if cmd.Dir == tmpDir { return pkgTar, "", nil } return "", "", fmt.Errorf("unexpected command: %s - %s", cmd.String(), cmd.Dir) } showOverride := func(cmd *exec.Cmd) error { if strings.Contains(cmd.String(), "makepkg -f --noconfirm") && cmd.Dir == tmpDir { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } if strings.Contains(cmd.String(), "makepkg -f --noconfirm") && cmd.Dir == tmpDirJfin { f, err := os.OpenFile(jfinPkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } return nil } defer os.Remove(pkgTar) defer os.Remove(jfinPkgTar) isCorrectInstalledOverride := func(string, string) bool { return false } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, "jellyfin": tmpDirJfin, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, tmpDirJfin, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, tmpDirJfin, "/testdir") capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_RunPostHooks(t *testing.T) { mockDB := &mock.DBExecutor{} mockRunner := &exe.MockRunner{} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: "makepkg", SudoBin: "su", PacmanBin: "pacman", PacmanConfigPath: "/etc/pacman.conf", Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) called := false hook := func(ctx context.Context) error { called = true return nil } installer.AddPostInstallHook(hook) installer.RunPostInstallHooks(context.Background()) assert.True(t, called) } func TestInstaller_CompileFailed(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string targets []map[string]*dep.InstallInfo wantErrInstall bool wantErrCompile bool failBuild bool failPkgInstall bool } tmpDir := t.TempDir() testCases := []testCase{ { desc: "one layer", wantErrInstall: false, wantErrCompile: true, failBuild: true, failPkgInstall: false, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, { desc: "one layer -- fail install", wantErrInstall: true, wantErrCompile: false, failBuild: false, failPkgInstall: true, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, { desc: "two layers", wantErrInstall: false, wantErrCompile: true, failBuild: true, failPkgInstall: false, targets: []map[string]*dep.InstallInfo{ {"bob": { AURBase: ptrString("yay"), }}, { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } showOverride := func(cmd *exec.Cmd) error { if tc.failBuild && strings.Contains(cmd.String(), "makepkg -f --noconfirm") && cmd.Dir == tmpDir { return errors.New("makepkg failed") } return nil } isCorrectInstalledOverride := func(string, string) bool { return false } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddArg("needed") cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) if tc.wantErrInstall { require.Error(td, errI) } else { require.NoError(td, errI) } failed, err := installer.CompileFailedAndIgnored() if tc.wantErrCompile { require.Error(td, err) for key := range failed { assert.ErrorContains(td, err, key) } uniqueBases := make(map[string]struct{}) for _, layer := range tc.targets { for _, info := range layer { if info.AURBase != nil { uniqueBases[*info.AURBase] = struct{}{} } } } require.Len(td, failed, len(uniqueBases)) } else { require.NoError(td, err) } }) } } func TestInstaller_InstallSplitPackage(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string wantShow []string wantCapture []string targets []map[string]*dep.InstallInfo } tmpDir := t.TempDir() testCases := []testCase{ { desc: "jellyfin", targets: []map[string]*dep.InstallInfo{ {"jellyfin": { Source: dep.AUR, Reason: dep.Explicit, Version: "10.8.4-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("jellyfin"), }}, { "jellyfin-server": { Source: dep.AUR, Reason: dep.Dep, Version: "10.8.4-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("jellyfin"), }, "jellyfin-web": { Source: dep.AUR, Reason: dep.Dep, Version: "10.8.4-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("jellyfin"), }, }, { "dotnet-runtime-6.0": { Source: dep.Sync, Reason: dep.Dep, Version: "6.0.12.sdk112-1", SyncDBName: ptrString("community"), }, "aspnet-runtime": { Source: dep.Sync, Reason: dep.Dep, Version: "6.0.12.sdk112-1", SyncDBName: ptrString("community"), }, "dotnet-sdk-6.0": { Source: dep.Sync, Reason: dep.MakeDep, Version: "6.0.12.sdk112-1", SyncDBName: ptrString("community"), }, }, }, wantShow: []string{ "pacman -S --config /etc/pacman.conf -- community/dotnet-runtime-6.0 community/aspnet-runtime community/dotnet-sdk-6.0", "pacman -D -q --asdeps --config /etc/pacman.conf -- dotnet-runtime-6.0 aspnet-runtime dotnet-sdk-6.0", "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst /testdir/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asdeps --config /etc/pacman.conf -- jellyfin-server jellyfin-web", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config /etc/pacman.conf -- jellyfin", }, wantCapture: []string{"makepkg --packagelist", "makepkg --packagelist"}, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { pkgTars := []string{ tmpDir + "/jellyfin-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-web-10.8.4-1-x86_64.pkg.tar.zst", tmpDir + "/jellyfin-server-10.8.4-1-x86_64.pkg.tar.zst", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(pkgTars, "\n"), "", nil } i := 0 showOverride := func(cmd *exec.Cmd) error { i++ if i == 4 { for _, pkgTar := range pkgTars { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } } return nil } isCorrectInstalledOverride := func(string, string) bool { return false } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("jellyfin") pkgBuildDirs := map[string]string{ "jellyfin": tmpDir, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), fmt.Sprintf("got at %d: %s \n", i, show)) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_InstallDownloadOnly(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string isInstalled bool isBuilt bool wantShow []string wantCapture []string } testCases := []testCase{ { desc: "not installed and not built", isInstalled: false, isBuilt: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", }, wantCapture: []string{"makepkg --packagelist"}, }, { desc: "not installed and built", isInstalled: false, isBuilt: true, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", }, wantCapture: []string{"makepkg --packagelist"}, }, { desc: "installed", isInstalled: true, isBuilt: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", }, wantCapture: []string{"makepkg --packagelist"}, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { tmpDir := td.TempDir() pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } i := 0 showOverride := func(cmd *exec.Cmd) error { i++ if i == 2 { if !tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } } return nil } // create a mock file if tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } isCorrectInstalledOverride := func(string, string) bool { return tc.isInstalled } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, true, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } targets := []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, } errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) require.Empty(td, installer.failedAndIgnored) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_InstallGroup(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string wantShow []string wantCapture []string } testCases := []testCase{ { desc: "group", wantShow: []string{ "pacman -S --noconfirm --config -- community/kubernetes-tools", }, wantCapture: []string{}, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { tmpDir := td.TempDir() captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockDB := &mock.DBExecutor{} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, true, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("kubernetes-tools") pkgBuildDirs := map[string]string{} targets := []map[string]*dep.InstallInfo{ { "kubernetes-tools": { Source: dep.Sync, Reason: dep.Explicit, Version: "", IsGroup: true, SyncDBName: ptrString("community"), }, }, } errI := installer.Install(context.Background(), cmdArgs, targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) require.Empty(td, installer.failedAndIgnored) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_InstallRebuild(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string rebuildOption parser.RebuildMode isInstalled bool isBuilt bool wantShow []string wantCapture []string targets []map[string]*dep.InstallInfo } tmpDir := t.TempDir() testCases := []testCase{ { desc: "--norebuild(default) when built and not installed", rebuildOption: parser.RebuildModeNo, isBuilt: true, isInstalled: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, { desc: "--rebuild when built and not installed", rebuildOption: parser.RebuildModeYes, isBuilt: true, isInstalled: false, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, { desc: "--rebuild when built and installed", rebuildOption: parser.RebuildModeYes, isInstalled: true, isBuilt: true, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, { desc: "--rebuild when built and installed previously as dep", rebuildOption: parser.RebuildModeYes, isInstalled: true, isBuilt: true, wantShow: []string{ "makepkg --nobuild -f -C --ignorearch", "makepkg -f -c --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asdeps --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Dep, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { tmpDir := td.TempDir() pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } i := 0 showOverride := func(cmd *exec.Cmd) error { i++ if i == 2 { if !tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } } return nil } // create a mock file if tc.isBuilt { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } isCorrectInstalledOverride := func(string, string) bool { return tc.isInstalled } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } cmdBuilder.Runner = mockRunner installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, tc.rebuildOption, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") // replace the temp dir with a static path capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } func TestInstaller_InstallUpgrade(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string targetMode parser.TargetMode } tmpDir := t.TempDir() testCases := []testCase{ { desc: "target any", targetMode: parser.ModeAny, }, { desc: "target repo", targetMode: parser.ModeRepo, }, { desc: "target aur", targetMode: parser.ModeAUR, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { mockDB := &mock.DBExecutor{} mockRunner := &exe.MockRunner{} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, Runner: mockRunner, SudoLoopEnabled: false, } installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, tc.targetMode, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddArg("u", "upgrades") // Make sure both args are removed targets := []map[string]*dep.InstallInfo{ { "linux": { Source: dep.Sync, Reason: dep.Dep, Version: "17.0.0-1", SyncDBName: ptrString("core"), }, }, } errI := installer.Install(context.Background(), cmdArgs, targets, map[string]string{}, []string{}, false) require.NoError(td, errI) require.NotEmpty(td, mockRunner.ShowCalls) // The first call is the only call being test call := mockRunner.ShowCalls[0] show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") if tc.targetMode == parser.ModeAUR { assert.NotContains(td, show, "--upgrades") assert.NotContains(td, show, "-u") } else { assert.Contains(td, show, "--upgrades") assert.Contains(td, show, "-u") } }) } } func TestInstaller_KeepSrc(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) type testCase struct { desc string wantShow []string targets []map[string]*dep.InstallInfo } tmpDir := t.TempDir() testCases := []testCase{ { desc: "--keepsrc", wantShow: []string{ "makepkg --nobuild -f --ignorearch", "makepkg --nobuild --noextract --ignorearch", "pacman -U --config -- /testdir/yay-92.0.0-1-x86_64.pkg.tar.zst", "pacman -D -q --asexplicit --config -- yay", }, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Explicit, Version: "92.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { tmpDir := td.TempDir() pkgTar := tmpDir + "/yay-92.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } // create a mock file f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) mockDB := &mock.DBExecutor{} mockRunner := &exe.MockRunner{CaptureFn: captureOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, KeepSrc: true, Runner: mockRunner, SudoLoopEnabled: false, } installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := parser.MakeArguments() cmdArgs.AddTarget("yay") pkgBuildDirs := map[string]string{ "yay": tmpDir, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) // Only assert makepkg commands don't have clean arguments if strings.HasPrefix(show, "makepkg") { assert.NotContains(td, show, "-c") assert.NotContains(td, show, "-C") } } }) } } // TestInstaller_InstallAsExplicit tests that --asexplicit flag only affects // targeted packages, not their dependencies. This is a regression test for // the bug where --asexplicit was ignored when reinstalling packages previously // installed as dependencies. func TestInstaller_InstallAsExplicit(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) tmpDir := t.TempDir() type testCase struct { desc string cmdArgs func() *parser.Arguments targets []map[string]*dep.InstallInfo wantShow []string wantCapture []string } testCases := []testCase{ { desc: "reinstall dep as explicit with --asexplicit (AUR)", cmdArgs: func() *parser.Arguments { args := parser.MakeArguments() args.AddArg("asexplicit") args.AddTarget("yay") return args }, targets: []map[string]*dep.InstallInfo{ { "yay": { Source: dep.AUR, Reason: dep.Dep, Version: "91.0.0-1", SrcinfoPath: ptrString(tmpDir + "/.SRCINFO"), AURBase: ptrString("yay"), }, }, }, wantShow: []string{ "makepkg --nobuild --ignorearch", "makepkg --noconfirm --noextract --noprepare --holdver --ignorearch", "pacman -U --config -- /testdir/yay-91.0.0-1-x86_64.pkg.tar.zst", "pacman -D --asexplicit -q --config -- yay", }, wantCapture: []string{"makepkg --packagelist"}, }, { desc: "reinstall dep as explicit with --asexplicit (Sync)", cmdArgs: func() *parser.Arguments { args := parser.MakeArguments() args.AddArg("asexplicit") args.AddTarget("linux") return args }, targets: []map[string]*dep.InstallInfo{ { "linux": { Source: dep.Sync, Reason: dep.Dep, Version: "17.0.0-1", SyncDBName: ptrString("core"), }, }, }, wantShow: []string{ "pacman -S --config /etc/pacman.conf -- core/linux", "pacman -D -q --asexplicit --config /etc/pacman.conf -- linux", }, wantCapture: []string{}, }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { pkgTar := tmpDir + "/yay-91.0.0-1-x86_64.pkg.tar.zst" captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return pkgTar, "", nil } showOverride := func(cmd *exec.Cmd) error { if strings.Contains(cmd.String(), "makepkg -f --noconfirm") && cmd.Dir == tmpDir { f, err := os.OpenFile(pkgTar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(td, err) require.NoError(td, f.Close()) } return nil } isCorrectInstalledOverride := func(string, string) bool { return false } mockDB := &mock.DBExecutor{IsCorrectVersionInstalledFn: isCorrectInstalledOverride} mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", Runner: mockRunner, SudoLoopEnabled: false, } installer := NewInstaller(mockDB, cmdBuilder, &vcs.Mock{}, parser.ModeAny, parser.RebuildModeNo, false, newTestLogger()) cmdArgs := tc.cmdArgs() pkgBuildDirs := map[string]string{ "yay": tmpDir, } errI := installer.Install(context.Background(), cmdArgs, tc.targets, pkgBuildDirs, []string{}, false) require.NoError(td, errI) require.Len(td, mockRunner.ShowCalls, len(tc.wantShow)) require.Len(td, mockRunner.CaptureCalls, len(tc.wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") assert.Subset(td, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, tmpDir, "/testdir") capture = strings.ReplaceAll(capture, makepkgBin, "makepkg") capture = strings.ReplaceAll(capture, pacmanBin, "pacman") assert.Subset(td, strings.Split(capture, " "), strings.Split(tc.wantCapture[i], " "), capture) } }) } } ================================================ FILE: pkg/sync/build/pkg_archive.go ================================================ package build import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/vcs" ) func installPkgArchive(ctx context.Context, cmdBuilder exe.ICmdBuilder, mode parser.TargetMode, vcsStore vcs.Store, cmdArgs *parser.Arguments, pkgArchives []string, noConfirm bool, ) error { if len(pkgArchives) == 0 { return nil } arguments := cmdArgs.Copy() arguments.ClearTargets() arguments.Op = "U" arguments.DelArg("confirm") arguments.DelArg("noconfirm") arguments.DelArg("c", "clean") arguments.DelArg("i", "install") arguments.DelArg("q", "quiet") arguments.DelArg("y", "refresh") arguments.DelArg("u", "sysupgrade") arguments.DelArg("w", "downloadonly") arguments.DelArg("asdeps", "asdep") arguments.DelArg("asexplicit", "asexp") arguments.AddTarget(pkgArchives...) if errShow := cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx, arguments, mode, noConfirm)); errShow != nil { return errShow } if errStore := vcsStore.Save(); errStore != nil { fmt.Fprintln(os.Stderr, errStore) } return nil } func setInstallReason(ctx context.Context, cmdBuilder exe.ICmdBuilder, mode parser.TargetMode, cmdArgs *parser.Arguments, deps, exps []string, ) error { if len(deps)+len(exps) == 0 { return nil } if errDeps := asdeps(ctx, cmdBuilder, mode, cmdArgs, deps); errDeps != nil { return errDeps } return asexp(ctx, cmdBuilder, mode, cmdArgs, exps) } func setPkgReason(ctx context.Context, cmdBuilder exe.ICmdBuilder, mode parser.TargetMode, cmdArgs *parser.Arguments, pkgs []string, exp bool, ) error { if len(pkgs) == 0 { return nil } cmdArgs = cmdArgs.CopyGlobal() if exp { if err := cmdArgs.AddArg("q", "D", "asexplicit"); err != nil { return err } } else { if err := cmdArgs.AddArg("q", "D", "asdeps"); err != nil { return err } } for _, compositePkgName := range pkgs { pkgSplit := strings.Split(compositePkgName, "/") pkgName := pkgSplit[0] if len(pkgSplit) > 1 { pkgName = pkgSplit[1] } cmdArgs.AddTarget(pkgName) } if err := cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx, cmdArgs, mode, settings.NoConfirm)); err != nil { return &SetPkgReasonError{exp: exp} } return nil } func asdeps(ctx context.Context, cmdBuilder exe.ICmdBuilder, mode parser.TargetMode, cmdArgs *parser.Arguments, pkgs []string, ) error { return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, false) } func asexp(ctx context.Context, cmdBuilder exe.ICmdBuilder, mode parser.TargetMode, cmdArgs *parser.Arguments, pkgs []string, ) error { return setPkgReason(ctx, cmdBuilder, mode, cmdArgs, pkgs, true) } func parsePackageList(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string, ) (pkgdests map[string]string, pkgVersion string, err error) { args := []string{"--packagelist", "--ignorearch"} stdout, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildMakepkgCmd(ctx, dir, args...)) if err != nil { return nil, "", fmt.Errorf("%s %w", stderr, err) } lines := strings.Split(stdout, "\n") pkgdests = make(map[string]string, len(lines)) for _, line := range lines { if line == "" { continue } fileName := filepath.Base(line) split := strings.Split(fileName, "-") if len(split) < 4 { return nil, "", errors.New(gotext.Get("cannot find package name: %v", 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], "-") pkgVersion = strings.Join(split[len(split)-3:len(split)-1], "-") pkgdests[pkgName] = line } if len(pkgdests) == 0 { return nil, "", &NoPkgDestsFoundError{dir} } return pkgdests, pkgVersion, nil } ================================================ FILE: pkg/sync/build/pkg_archive_test.go ================================================ package build import ( "context" "fmt" "os/exec" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/settings/exe" ) func TestParsePackageList(t *testing.T) { t.Parallel() type testCase struct { desc string mockStdout string mockStderr string mockErr error wantPkgDests map[string]string wantPkgVersion string wantErr bool wantErrText string // Optional: specific error text to check } testCases := []testCase{ { desc: "Standard package", mockStdout: "/path/to/package-1.2.3-4-x86_64.pkg.tar.zst\n", wantPkgDests: map[string]string{ "package": "/path/to/package-1.2.3-4-x86_64.pkg.tar.zst", }, wantPkgVersion: "1.2.3-4", wantErr: false, }, { desc: "Package with dash in name", mockStdout: "/path/to/package-name-with-dash-1.0.0-1-any.pkg.tar.gz\n", wantPkgDests: map[string]string{ "package-name-with-dash": "/path/to/package-name-with-dash-1.0.0-1-any.pkg.tar.gz", }, wantPkgVersion: "1.0.0-1", wantErr: false, // This should fail with current logic but pass with regex }, { desc: "Multiple packages", mockStdout: "/path/to/pkg1-1.0-1-x86_64.pkg.tar.zst\n/other/path/pkg2-2.5-3-any.pkg.tar.xz\n", wantPkgDests: map[string]string{ "pkg1": "/path/to/pkg1-1.0-1-x86_64.pkg.tar.zst", "pkg2": "/other/path/pkg2-2.5-3-any.pkg.tar.xz", }, wantPkgVersion: "2.5-3", // Version of the last package processed wantErr: false, }, { desc: "Empty input", mockStdout: "", wantErr: true, // Expect NoPkgDestsFoundError }, { desc: "Input with only newline", mockStdout: "\n", wantErr: true, // Expect NoPkgDestsFoundError }, { desc: "Makepkg error", mockStderr: "makepkg failed", mockErr: fmt.Errorf("exit status 1"), wantErr: true, }, { desc: "Malformed filename (too few dashes)", mockStdout: "/path/to/malformed-package.pkg.tar.zst\n", wantErr: true, // Expect "cannot find package name" error }, { desc: "Package with epoch", mockStdout: "/path/to/epochpkg-1:2.0.0-1-x86_64.pkg.tar.zst\n", wantPkgDests: map[string]string{ "epochpkg": "/path/to/epochpkg-1:2.0.0-1-x86_64.pkg.tar.zst", }, wantPkgVersion: "1:2.0.0-1", wantErr: false, // This might fail with current logic }, { desc: "Package with .zst extension", mockStdout: "/path/to/zstdpkg-3.3-1-any.pkg.tar.zst\n", wantPkgDests: map[string]string{ "zstdpkg": "/path/to/zstdpkg-3.3-1-any.pkg.tar.zst", }, wantPkgVersion: "3.3-1", wantErr: false, }, { desc: "Package with .gz extension", mockStdout: "/path/to/gzpkg-3.3-1-any.pkg.tar.gz\n", wantPkgDests: map[string]string{ "gzpkg": "/path/to/gzpkg-3.3-1-any.pkg.tar.gz", }, wantPkgVersion: "3.3-1", wantErr: false, }, { desc: "Package with .xz extension", mockStdout: "/path/to/xzpkg-3.3-1-any.pkg.tar.xz\n", wantPkgDests: map[string]string{ "xzpkg": "/path/to/xzpkg-3.3-1-any.pkg.tar.xz", }, wantPkgVersion: "3.3-1", wantErr: false, }, { desc: "Package with .bz2 extension", mockStdout: "/path/to/bz2pkg-3.3-1-any.pkg.tar.bz2\n", wantPkgDests: map[string]string{ "bz2pkg": "/path/to/bz2pkg-3.3-1-any.pkg.tar.bz2", }, wantPkgVersion: "3.3-1", wantErr: false, }, { desc: "Package with .tar extension (uncompressed)", mockStdout: "/path/to/tarpkg-3.3-1-any.pkg.tar\n", wantPkgDests: map[string]string{ "tarpkg": "/path/to/tarpkg-3.3-1-any.pkg.tar", }, wantPkgVersion: "3.3-1", wantErr: false, }, } for _, tc := range testCases { // capture range variable t.Run(tc.desc, func(t *testing.T) { t.Parallel() mockRunner := &exe.MockRunner{ CaptureFn: func(cmd *exec.Cmd) (string, string, error) { // Basic check to ensure the command looks right require.Contains(t, cmd.String(), "--packagelist") return tc.mockStdout, tc.mockStderr, tc.mockErr }, } cmdBuilder := &exe.CmdBuilder{Runner: mockRunner} // Simplified for this test pkgdests, pkgVersion, err := parsePackageList(context.Background(), cmdBuilder, "/fake/dir") if tc.wantErr { assert.Error(t, err) if tc.wantErrText != "" { assert.Contains(t, err.Error(), tc.wantErrText) } // Check for specific error types if needed if tc.desc == "Empty input" || tc.desc == "Input with only newline" { assert.IsType(t, &NoPkgDestsFoundError{}, err) } } else { assert.NoError(t, err) assert.Equal(t, tc.wantPkgDests, pkgdests) assert.Equal(t, tc.wantPkgVersion, pkgVersion) } }) } } ================================================ FILE: pkg/sync/srcinfo/pgp/keys.go ================================================ package pgp import ( "bytes" "context" "errors" "os/exec" "strings" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) // pgpKeySet maps a PGP key with a list of PKGBUILDs that require it. // This is similar to stringSet, used throughout the code. type pgpKeySet map[string][]string func (set pgpKeySet) toSlice() []string { slice := make([]string, 0, len(set)) for v := range set { slice = append(slice, v) } return slice } func (set pgpKeySet) set(key, p string) { // Using ToUpper to make sure keys with a different case will be // considered the same. upperKey := strings.ToUpper(key) set[upperKey] = append(set[upperKey], p) } func (set pgpKeySet) get(key string) bool { upperKey := strings.ToUpper(key) _, exists := set[upperKey] return exists } type GPGCmdBuilder interface { exe.Runner BuildGPGCmd(ctx context.Context, extraArgs ...string) *exec.Cmd } // CheckPgpKeys iterates through the keys listed in the PKGBUILDs and if needed, // asks the user whether yay should try to import them. func CheckPgpKeys(ctx context.Context, logger *text.Logger, pkgbuildDirsByBase map[string]string, srcinfos map[string]*gosrc.Srcinfo, cmdBuilder GPGCmdBuilder, noConfirm bool, ) ([]string, error) { // Let's check the keys individually, and then we can offer to import // the problematic ones. problematic := make(pgpKeySet) // Mapping all the keys. for pkg := range pkgbuildDirsByBase { srcinfo := srcinfos[pkg] for _, key := range srcinfo.ValidPGPKeys { // If key already marked as problematic, indicate the current // PKGBUILD requires it. if problematic.get(key) { problematic.set(key, pkg) continue } if err := cmdBuilder.Show(cmdBuilder.BuildGPGCmd(ctx, "--list-keys", key)); err != nil { problematic.set(key, pkg) } } } // No key issues! if len(problematic) == 0 { return []string{}, nil } str, err := formatKeysToImport(logger, problematic) if err != nil { return nil, err } logger.Println("\n", str) if logger.ContinueTask(gotext.Get("Import?"), true, noConfirm) { return problematic.toSlice(), importKeys(ctx, logger, cmdBuilder, problematic.toSlice()) } return problematic.toSlice(), nil } // importKeys tries to import the list of keys specified in its argument. func importKeys(ctx context.Context, logger *text.Logger, cmdBuilder GPGCmdBuilder, keys []string) error { logger.OperationInfoln(gotext.Get("Importing keys with gpg...")) if err := cmdBuilder.Show(cmdBuilder.BuildGPGCmd(ctx, append([]string{"--recv-keys"}, keys...)...)); err != nil { return errors.New(gotext.Get("problem importing keys")) } return nil } // formatKeysToImport receives a set of keys and returns a string containing the // question asking the user wants to import the problematic keys. func formatKeysToImport(logger *text.Logger, keys pgpKeySet) (string, error) { if len(keys) == 0 { return "", errors.New(gotext.Get("no keys to import")) } var buffer bytes.Buffer buffer.WriteString(logger.SprintOperationInfo(gotext.Get("PGP keys need importing:"))) for key, bases := range keys { pkglist := "" for _, base := range bases { pkglist += base + " " } pkglist = strings.TrimRight(pkglist, " ") buffer.WriteString("\n" + logger.SprintWarn(gotext.Get("%s, required by: %s", text.Cyan(key), text.Cyan(pkglist)))) } return buffer.String(), nil } ================================================ FILE: pkg/sync/srcinfo/pgp/keys_test.go ================================================ //go:build !integration // +build !integration package pgp import ( "context" "fmt" "io" "os" "os/exec" "sort" "strings" "testing" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } func makeSrcinfo(pkgbase string, pgpkeys ...string) *gosrc.Srcinfo { srcinfo := gosrc.Srcinfo{} srcinfo.Pkgbase = pkgbase srcinfo.ValidPGPKeys = pgpkeys return &srcinfo } func TestCheckPgpKeys(t *testing.T) { gpgBin := t.TempDir() + "/gpg" f, err := os.OpenFile(gpgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) testcases := []struct { name string pkgs map[string]string srcinfos map[string]*gosrc.Srcinfo wantError bool wantShow []string wantCapture []string showFn func(cmd *exec.Cmd) error expected []string }{ // cower: single package, one valid key not yet in the keyring. // 487EACC08557AD082088DABA1EB2638FF56C0C53: Dave Reisner. { name: " one valid key not yet in the keyring", pkgs: map[string]string{"cower": ""}, srcinfos: map[string]*gosrc.Srcinfo{"cower": makeSrcinfo("cower", "487EACC08557AD082088DABA1EB2638FF56C0C53")}, wantError: false, wantShow: []string{ "gpg --homedir /tmp --list-keys 487EACC08557AD082088DABA1EB2638FF56C0C53", "gpg --homedir /tmp --recv-keys 487EACC08557AD082088DABA1EB2638FF56C0C53", }, wantCapture: []string{}, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") { return fmt.Errorf("key not found") } return nil }, expected: []string{"487EACC08557AD082088DABA1EB2638FF56C0C53"}, }, // libc++: single package, two valid keys not yet in the keyring. // 11E521D646982372EB577A1F8F0871F202119294: Tom Stellard. // B6C8F98282B944E3B0D5C2530FC3042E345AD05D: Hans Wennborg. { name: "two valid keys not yet in the keyring", pkgs: map[string]string{"libc++": ""}, srcinfos: map[string]*gosrc.Srcinfo{"libc++": makeSrcinfo("libc++", "11E521D646982372EB577A1F8F0871F202119294", "B6C8F98282B944E3B0D5C2530FC3042E345AD05D")}, wantError: false, wantShow: []string{ "gpg --homedir /tmp --list-keys 11E521D646982372EB577A1F8F0871F202119294", "gpg --homedir /tmp --list-keys B6C8F98282B944E3B0D5C2530FC3042E345AD05D", "gpg --homedir /tmp --recv-keys 11E521D646982372EB577A1F8F0871F202119294 B6C8F98282B944E3B0D5C2530FC3042E345AD05D", }, wantCapture: []string{}, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") { return fmt.Errorf("key not found") } return nil }, expected: []string{"11E521D646982372EB577A1F8F0871F202119294", "B6C8F98282B944E3B0D5C2530FC3042E345AD05D"}, }, { name: "Two dummy packages requiring the same key", pkgs: map[string]string{"dummy-1": "", "dummy-2": ""}, srcinfos: map[string]*gosrc.Srcinfo{ "dummy-1": makeSrcinfo("dummy-1", "ABAF11C65A2970B130ABE3C479BE3E4300411886"), "dummy-2": makeSrcinfo("dummy-2", "ABAF11C65A2970B130ABE3C479BE3E4300411886"), }, wantError: false, expected: []string{"ABAF11C65A2970B130ABE3C479BE3E4300411886"}, wantCapture: []string{}, wantShow: []string{ "gpg --homedir /tmp --list-keys ABAF11C65A2970B130ABE3C479BE3E4300411886", "gpg --homedir /tmp --recv-keys ABAF11C65A2970B130ABE3C479BE3E4300411886", }, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") { return fmt.Errorf("key not found") } return nil }, }, // dummy package: single package, two valid keys, one of them already // in the keyring. // 11E521D646982372EB577A1F8F0871F202119294: Tom Stellard. // C52048C0C0748FEE227D47A2702353E0F7E48EDB: Thomas Dickey. { name: "one already in keyring", pkgs: map[string]string{"dummy-3": ""}, srcinfos: map[string]*gosrc.Srcinfo{ "dummy-3": makeSrcinfo("dummy-3", "11E521D646982372EB577A1F8F0871F202119294", "C52048C0C0748FEE227D47A2702353E0F7E48EDB"), }, wantError: false, expected: []string{"C52048C0C0748FEE227D47A2702353E0F7E48EDB"}, wantCapture: []string{}, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") && !strings.Contains(s, "11E521D646982372EB577A1F8F0871F202119294") { return fmt.Errorf("key not found") } return nil }, wantShow: []string{ "gpg --homedir /tmp --list-keys 11E521D646982372EB577A1F8F0871F202119294", "gpg --homedir /tmp --list-keys C52048C0C0748FEE227D47A2702353E0F7E48EDB", "gpg --homedir /tmp --recv-keys C52048C0C0748FEE227D47A2702353E0F7E48EDB", }, }, // Two dummy packages with existing keys. { name: "two existing", pkgs: map[string]string{"dummy-4": "", "dummy-5": ""}, srcinfos: map[string]*gosrc.Srcinfo{ "dummy-4": makeSrcinfo("dummy-4", "11E521D646982372EB577A1F8F0871F202119294"), "dummy-5": makeSrcinfo("dummy-5", "C52048C0C0748FEE227D47A2702353E0F7E48EDB"), }, wantError: false, expected: []string{}, wantCapture: []string{}, showFn: func(cmd *exec.Cmd) error { return nil }, wantShow: []string{ "gpg --homedir /tmp --list-keys 11E521D646982372EB577A1F8F0871F202119294", "gpg --homedir /tmp --list-keys C52048C0C0748FEE227D47A2702353E0F7E48EDB", }, }, // Dummy package with invalid key, should fail. { name: "one invalid", pkgs: map[string]string{"dummy-7": ""}, srcinfos: map[string]*gosrc.Srcinfo{"dummy-7": makeSrcinfo("dummy-7", "THIS-SHOULD-FAIL")}, wantError: true, wantCapture: []string{}, wantShow: []string{ "gpg --homedir /tmp --list-keys THIS-SHOULD-FAIL", "gpg --homedir /tmp --recv-keys THIS-SHOULD-FAIL", }, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") { return fmt.Errorf("key not found") } if strings.Contains(s, "--recv-keys") { return fmt.Errorf("invalid key") } return nil }, }, // Dummy package with both an invalid an another valid key, should fail. // A314827C4E4250A204CE6E13284FC34C8E4B1A25: Thomas Bächler. { name: "one invalid, one valid", pkgs: map[string]string{"dummy-8": ""}, srcinfos: map[string]*gosrc.Srcinfo{"dummy-8": makeSrcinfo("dummy-8", "A314827C4E4250A204CE6E13284FC34C8E4B1A25", "THIS-SHOULD-FAIL")}, wantError: true, expected: []string{}, wantCapture: []string{}, showFn: func(cmd *exec.Cmd) error { s := cmd.String() if strings.Contains(s, "--list-keys") { return fmt.Errorf("key not found") } if strings.Contains(s, "--recv-keys") { return fmt.Errorf("invalid key") } return nil }, wantShow: []string{ "gpg --homedir /tmp --list-keys A314827C4E4250A204CE6E13284FC34C8E4B1A25", "gpg --homedir /tmp --list-keys THIS-SHOULD-FAIL", "gpg --homedir /tmp --recv-keys A314827C4E4250A204CE6E13284FC34C8E4B1A25 THIS-SHOULD-FAIL", }, }, } for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { mockRunner := &exe.MockRunner{ ShowFn: tt.showFn, CaptureFn: func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil }, } cmdBuilder := exe.CmdBuilder{ GPGBin: gpgBin, GPGFlags: []string{"--homedir /tmp"}, Runner: mockRunner, } problematic, err := CheckPgpKeys(context.Background(), newTestLogger(), tt.pkgs, tt.srcinfos, &cmdBuilder, true) require.Len(t, mockRunner.ShowCalls, len(tt.wantShow)) require.Len(t, mockRunner.CaptureCalls, len(tt.wantCapture)) sort.SliceStable(mockRunner.ShowCalls, func(i, j int) bool { return mockRunner.ShowCalls[i].Args[0].(*exec.Cmd).String() < mockRunner.ShowCalls[j].Args[0].(*exec.Cmd).String() }) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, gpgBin, "gpg") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(tt.wantShow[i], " "), show) } for i, call := range mockRunner.CaptureCalls { capture := call.Args[0].(*exec.Cmd).String() capture = strings.ReplaceAll(capture, gpgBin, "gpg") assert.Subset(t, strings.Split(capture, " "), strings.Split(tt.wantCapture[i], " "), capture) } if tt.wantError { require.Error(t, err) return } require.NoError(t, err) assert.ElementsMatch(t, tt.expected, problematic, fmt.Sprintf("%#v", problematic)) }) } } ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/11E521D646982372EB577A1F8F0871F202119294 ================================================ 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 =/J5n ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/487EACC08557AD082088DABA1EB2638FF56C0C53 ================================================ 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 v5Wb1miAZnGDGEfyuYnSAw== =bVs6 ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/647F28654894E3BD457199BE38DBBDC86092693E ================================================ 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 HKc5w55soziCllQ= =PQVe ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/A314827C4E4250A204CE6E13284FC34C8E4B1A25 ================================================ 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 oSf3hW01pA== =cv82 ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/ABAF11C65A2970B130ABE3C479BE3E4300411886 ================================================ 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 8jMWgR7U4vQMGuOReoECpO8gNUWr2yAhHelm+A/8L1Un8q7S6eJafBeCsg8WD1eRApvD76cC ViDfbyoeSU/hWxitB1OdV1FzdbGa7/dQuYUBmi8MtFAZ4okCHAQQAQgABgUCTnplUgAKCRDT b3abwRgE8JZYD/0WH4k/timxBCGW3ERN6cCyOOzqj52cdZBXwyzaobeA6m7/d5+GzpmvVqHG e3Arb4PoQH6L462iH1iCCbKvK7lhHKCmtVXM6orcbkfZTi1IYy2lOATsRX1OKWL3O9U/wKhU tw62pQEBt0FIFZLd00Jamh5iT3MxUhcrT4ZWlXjC4cfJ89vzOjvpLs1hnhqx9t/HfPDwMsbG e40PxGrLzq2pLpwS/L3M703JGTCblrHTLbVmGWfLpMBsp2g3L5V5+JSHnI0wgYrF9S+lTkOS bFmIbmYuwaJHCaGtVTd3Mx3MUJspZuT9Oj4MeB0a/X++8YbSXA7uKkzqLFv0Yv////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////4kCHAQQAQgABgUCTqblgAAK CRDnv8jslYYRCd8mEACQnqydgXN+myvn0uZZeic7wSqbHOXLcG9z2fS40pujAvmTEeO0994O BrvfM95BRcLBch9ZpHK5asvwoYy9nnXQ0Rn16ldKahYd9TpZ+eJUohIRI5gKkIiwD5nmot+F NYMQLD0SOn5WHPuINIg72TK7oUKobCP7Tbb5Kv7rf2VmqpcCcYwNXsQ1lMPnR3msZlNuidlr ZhAS7RMvb08eUYAso3KHR0srQoqGkYWZdjQnnj4Ver8Y4q9REEpDw67r4lxp5LBCgBGY78c4 tbTVZa7Efa5zA282+v7bkzoaMgJxqDns61de3Af8snUEpCFTBWkqCMGkWiNx70ZNfX3Z1z3c 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 LddgYrylZH+MwNDlB5u3I138 =6ybs ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/B6C8F98282B944E3B0D5C2530FC3042E345AD05D ================================================ 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 ix4eSNK5nln0jUq167H2IH//2sf8dw== =pTiW ================================================ FILE: pkg/sync/srcinfo/pgp/testdata/C52048C0C0748FEE227D47A2702353E0F7E48EDB ================================================ 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 =Nbc4 ================================================ FILE: pkg/sync/srcinfo/service.go ================================================ package srcinfo import ( "context" "errors" "fmt" "path/filepath" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/sync/srcinfo/pgp" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) type Service struct { dbExecutor db.Executor cfg *settings.Configuration cmdBuilder pgp.GPGCmdBuilder vcsStore vcs.Store log *text.Logger pkgBuildDirs map[string]string srcInfos map[string]*gosrc.Srcinfo } func NewService(dbExecutor db.Executor, cfg *settings.Configuration, logger *text.Logger, cmdBuilder exe.ICmdBuilder, vcsStore vcs.Store, pkgBuildDirs map[string]string, ) (*Service, error) { srcinfos, err := ParseSrcinfoFilesByBase(logger, pkgBuildDirs, true) if err != nil { return nil, err } return &Service{ dbExecutor: dbExecutor, cfg: cfg, cmdBuilder: cmdBuilder, vcsStore: vcsStore, pkgBuildDirs: pkgBuildDirs, srcInfos: srcinfos, log: logger, }, nil } func (s *Service) IncompatiblePkgs(ctx context.Context) ([]string, error) { incompatible := []string{} alpmArch, err := s.dbExecutor.AlpmArchitectures() if err != nil { return nil, err } nextpkg: for base, srcinfo := range s.srcInfos { for _, arch := range srcinfo.Arch { if db.ArchIsSupported(alpmArch, arch) { continue nextpkg } } incompatible = append(incompatible, base) } return incompatible, nil } func (s *Service) CheckPGPKeys(ctx context.Context) error { _, errCPK := pgp.CheckPgpKeys(ctx, s.log.Child("pgp"), s.pkgBuildDirs, s.srcInfos, s.cmdBuilder, settings.NoConfirm) return errCPK } func (s *Service) UpdateVCSStore(ctx context.Context, targets []map[string]*dep.InstallInfo, ignore map[string]error, ) error { for _, srcinfo := range s.srcInfos { if srcinfo.Source == nil { continue } // TODO: high complexity - refactor for i := range srcinfo.Packages { for j := range targets { if _, ok := targets[j][srcinfo.Packages[i].Pkgname]; !ok { s.log.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "not in targets") continue } if _, ok := ignore[srcinfo.Packages[i].Pkgname]; ok { s.log.Debugln("skipping VCS update for", srcinfo.Packages[i].Pkgname, "due to install error") continue } s.log.Debugln("checking VCS entry for", srcinfo.Packages[i].Pkgname, fmt.Sprintf("source: %v", srcinfo.Source)) s.vcsStore.Update(ctx, srcinfo.Packages[i].Pkgname, srcinfo.Source) } } } return nil } func ParseSrcinfoFilesByBase(logger *text.Logger, pkgBuildDirs map[string]string, errIsFatal bool) (map[string]*gosrc.Srcinfo, error) { srcinfos := make(map[string]*gosrc.Srcinfo) k := 0 for base, dir := range pkgBuildDirs { logger.OperationInfoln(gotext.Get("(%d/%d) Parsing SRCINFO: %s", k+1, len(pkgBuildDirs), text.Cyan(base))) pkgbuild, err := gosrc.ParseFile(filepath.Join(dir, ".SRCINFO")) if err != nil { if !errIsFatal { logger.Warnln(gotext.Get("failed to parse %s -- skipping: %s", base, err)) continue } return nil, errors.New(gotext.Get("failed to parse %s: %s", base, err)) } srcinfos[base] = pkgbuild k++ } return srcinfos, nil } ================================================ FILE: pkg/sync/srcinfo/service_test.go ================================================ package srcinfo import ( "context" "io" "strings" "testing" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } func TestNewService(t *testing.T) { dbExecutor := &mock.DBExecutor{} cfg := &settings.Configuration{} cmdBuilder := &exe.MockBuilder{} vcsStore := &vcs.Mock{} pkgBuildDirs := map[string]string{ "jellyfin": "../../../testdata/jfin", "cephbin": "../../../testdata/cephbin", } srv, err := NewService(dbExecutor, cfg, newTestLogger(), cmdBuilder, vcsStore, pkgBuildDirs) assert.NoError(t, err) assert.NotNil(t, srv) assert.Equal(t, dbExecutor, srv.dbExecutor) assert.Equal(t, cfg, srv.cfg) assert.Equal(t, cmdBuilder, srv.cmdBuilder) assert.Equal(t, vcsStore, srv.vcsStore) assert.Equal(t, pkgBuildDirs, srv.pkgBuildDirs) assert.NotNil(t, srv.srcInfos) } func TestService_IncompatiblePkgs(t *testing.T) { srv := &Service{ dbExecutor: &mock.DBExecutor{AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }}, srcInfos: map[string]*gosrc.Srcinfo{ "pkg1": { Package: gosrc.Package{ Arch: []string{"x86_64", "any"}, }, }, "pkg2": { Package: gosrc.Package{ Arch: []string{"any"}, }, }, "pkg3": { Package: gosrc.Package{ Arch: []string{"armv7h"}, }, }, "pkg4": { Package: gosrc.Package{ Arch: []string{"i683", "x86_64"}, }, }, }, } incompatible, err := srv.IncompatiblePkgs(context.Background()) assert.NoError(t, err) assert.ElementsMatch(t, []string{"pkg3"}, incompatible) } func TestService_CheckPGPKeys(t *testing.T) { srv := &Service{ log: newTestLogger(), pkgBuildDirs: map[string]string{ "pkg1": "/path/to/pkg1", "pkg2": "/path/to/pkg2", }, srcInfos: map[string]*gosrc.Srcinfo{ "pkg1": { Packages: []gosrc.Package{ {Pkgname: "pkg1"}, }, }, "pkg2": { Packages: []gosrc.Package{ {Pkgname: "pkg2"}, }, }, }, } err := srv.CheckPGPKeys(context.Background()) assert.NoError(t, err) } func TestService_UpdateVCSStore(t *testing.T) { srv := &Service{ srcInfos: map[string]*gosrc.Srcinfo{ "pkg1": { Packages: []gosrc.Package{ {Pkgname: "pkg1"}, }, }, "pkg2": { Packages: []gosrc.Package{ {Pkgname: "pkg2"}, }, }, }, vcsStore: &vcs.Mock{}, } targets := []map[string]*dep.InstallInfo{ { "pkg1": {}, "pkg2": {}, }, } ignore := map[string]error{} err := srv.UpdateVCSStore(context.Background(), targets, ignore) assert.NoError(t, err) } ================================================ FILE: pkg/sync/sync.go ================================================ package sync import ( "context" "github.com/Jguer/yay/v12/pkg/completion" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/sync/build" "github.com/Jguer/yay/v12/pkg/sync/srcinfo" "github.com/Jguer/yay/v12/pkg/sync/workdir" "github.com/Jguer/yay/v12/pkg/text" "github.com/leonelquinteros/gotext" ) type OperationService struct { ctx context.Context cfg *settings.Configuration dbExecutor db.Executor logger *text.Logger } func NewOperationService(ctx context.Context, dbExecutor db.Executor, run *runtime.Runtime, ) *OperationService { return &OperationService{ ctx: ctx, cfg: run.Cfg, dbExecutor: dbExecutor, logger: run.Logger.Child("operation"), } } func (o *OperationService) Run(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, targets []map[string]*dep.InstallInfo, excluded []string, ) error { if len(targets) == 0 { o.logger.Println("", gotext.Get("there is nothing to do")) return nil } preparer := workdir.NewPreparer(o.dbExecutor, run.CmdBuilder, o.cfg, o.logger.Child("workdir")) installer := build.NewInstaller(o.dbExecutor, run.CmdBuilder, run.VCSStore, o.cfg.Mode, o.cfg.ReBuild, cmdArgs.ExistsArg("w", "downloadonly"), run.Logger.Child("installer")) shouldInstall := !cmdArgs.ExistsArg("w", "downloadonly") if cmdArgs.Op == "B" && !cmdArgs.ExistsArg("i", "install") { shouldInstall = false } installer.SetInstallBuiltPackages(shouldInstall) pkgBuildDirs, errInstall := preparer.Run(ctx, run, targets) if errInstall != nil { return errInstall } if cleanFunc := preparer.ShouldCleanMakeDeps(run, cmdArgs); cleanFunc != nil { installer.AddPostInstallHook(cleanFunc) } if cleanAURDirsFunc := preparer.ShouldCleanAURDirs(run, pkgBuildDirs); cleanAURDirsFunc != nil { installer.AddPostInstallHook(cleanAURDirsFunc) } if completion.NeedsUpdate(o.cfg.CompletionPath, o.cfg.CompletionInterval, false) { go func() { errComp := completion.UpdateCache(ctx, run.HTTPClient, o.dbExecutor, o.cfg.AURURL, o.cfg.CompletionPath, o.logger) if errComp != nil { o.logger.Warnln(errComp) } }() } srcInfo, errInstall := srcinfo.NewService(o.dbExecutor, o.cfg, o.logger.Child("srcinfo"), run.CmdBuilder, run.VCSStore, pkgBuildDirs) if errInstall != nil { return errInstall } incompatible, errInstall := srcInfo.IncompatiblePkgs(ctx) if errInstall != nil { return errInstall } if errIncompatible := confirmIncompatible(o.logger, incompatible); errIncompatible != nil { return errIncompatible } if errPGP := srcInfo.CheckPGPKeys(ctx); errPGP != nil { return errPGP } if errInstall := installer.Install(ctx, cmdArgs, targets, pkgBuildDirs, excluded, o.manualConfirmRequired(cmdArgs)); errInstall != nil { return errInstall } var multiErr multierror.MultiError failedAndIgnored, err := installer.CompileFailedAndIgnored() if err != nil { multiErr.Add(err) } if !cmdArgs.ExistsArg("w", "downloadonly") { if err := srcInfo.UpdateVCSStore(ctx, targets, failedAndIgnored); err != nil { o.logger.Warnln(err) } } if err := installer.RunPostInstallHooks(ctx); err != nil { multiErr.Add(err) } return multiErr.Return() } func (o *OperationService) manualConfirmRequired(cmdArgs *parser.Arguments) bool { return (!cmdArgs.ExistsArg("u", "sysupgrade") && cmdArgs.Op != "Y") || o.cfg.DoubleConfirm } func confirmIncompatible(logger *text.Logger, incompatible []string) error { if len(incompatible) > 0 { logger.Warnln(gotext.Get("The following packages are not compatible with your architecture:")) for _, pkg := range incompatible { logger.Print(" " + text.Cyan(pkg)) } logger.Println() if !logger.ContinueTask(gotext.Get("Try to build them anyway?"), true, settings.NoConfirm) { return &settings.ErrUserAbort{} } } return nil } ================================================ FILE: pkg/sync/workdir/aur_source.go ================================================ package workdir import ( "context" "fmt" "runtime" "sync" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) type ErrDownloadSource struct { inner error pkgName string errOut string } func (e ErrDownloadSource) Error() string { return fmt.Sprintln(gotext.Get("error downloading sources: %s", text.Cyan(e.pkgName)), "\n\t context:", e.inner.Error(), "\n\t", e.errOut) } func (e *ErrDownloadSource) Unwrap() error { return e.inner } func downloadPKGBUILDSource(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgBuildDir string, installIncompatible bool, ) error { args := []string{"--verifysource", "--skippgpcheck", "-f"} if !cmdBuilder.GetKeepSrc() { args = append(args, "-Cc") } if installIncompatible { args = append(args, "--ignorearch") } err := cmdBuilder.Show( cmdBuilder.BuildMakepkgCmd(ctx, pkgBuildDir, args...)) if err != nil { return ErrDownloadSource{inner: err, pkgName: pkgBuildDir} } return nil } func downloadPKGBUILDSourceWorker(ctx context.Context, wg *sync.WaitGroup, dirChannel <-chan string, valOut chan<- string, errOut chan<- error, cmdBuilder exe.ICmdBuilder, incompatible bool, ) { for pkgBuildDir := range dirChannel { err := downloadPKGBUILDSource(ctx, cmdBuilder, pkgBuildDir, incompatible) if err != nil { errOut <- ErrDownloadSource{inner: err, pkgName: pkgBuildDir, errOut: ""} } else { valOut <- pkgBuildDir } } wg.Done() } func downloadPKGBUILDSourceFanout(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgBuildDirs map[string]string, incompatible bool, maxConcurrentDownloads int, ) error { if len(pkgBuildDirs) == 0 { return nil // no work to do } if len(pkgBuildDirs) == 1 { for _, pkgBuildDir := range pkgBuildDirs { return downloadPKGBUILDSource(ctx, cmdBuilder, pkgBuildDir, incompatible) } } var ( numOfWorkers = runtime.NumCPU() wg = &sync.WaitGroup{} c = make(chan string) fanInChanValues = make(chan string) fanInChanErrors = make(chan error) ) if maxConcurrentDownloads != 0 { numOfWorkers = maxConcurrentDownloads } dedupSet := mapset.NewThreadUnsafeSet[string]() go func() { for _, pkgbuildDir := range pkgBuildDirs { if !dedupSet.Contains(pkgbuildDir) { c <- pkgbuildDir dedupSet.Add(pkgbuildDir) } } close(c) }() // Launch Workers wg.Add(numOfWorkers) for s := 0; s < numOfWorkers; s++ { go downloadPKGBUILDSourceWorker(ctx, wg, c, fanInChanValues, fanInChanErrors, cmdBuilder, incompatible) } go func() { wg.Wait() close(fanInChanValues) close(fanInChanErrors) }() returnErr := multierror.MultiError{} receiver: for { select { case _, ok := <-fanInChanValues: if !ok { break receiver } case err, ok := <-fanInChanErrors: if !ok { break receiver } returnErr.Add(err) } } return returnErr.Return() } ================================================ FILE: pkg/sync/workdir/aur_source_test.go ================================================ //go:build !integration // +build !integration package workdir import ( "context" "fmt" "os/exec" "path/filepath" "sync/atomic" "testing" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/settings/exe" ) type TestMakepkgBuilder struct { exe.ICmdBuilder parentBuilder *exe.CmdBuilder test *testing.T passes uint32 want string wantDir string showError error } func (z *TestMakepkgBuilder) BuildMakepkgCmd(ctx context.Context, dir string, extraArgs ...string) *exec.Cmd { cmd := z.parentBuilder.BuildMakepkgCmd(ctx, dir, extraArgs...) if z.want != "" { assert.Contains(z.test, cmd.String(), z.want) } if z.GetKeepSrc() { assert.NotContains(z.test, cmd.String(), "-Cc") } if z.wantDir != "" { assert.Equal(z.test, z.wantDir, cmd.Dir) } atomic.AddUint32(&z.passes, 1) return cmd } func (z *TestMakepkgBuilder) Show(cmd *exec.Cmd) error { return z.showError } func (z *TestMakepkgBuilder) GetKeepSrc() bool { return z.parentBuilder.KeepSrc } // GIVEN 1 package // WHEN downloadPKGBUILDSource is called // THEN 1 call should be made to makepkg with the specified parameters and dir func Test_downloadPKGBUILDSource(t *testing.T) { t.Parallel() type testCase struct { desc string keepSrc bool want string } testCases := []testCase{ { desc: "keepsrc", keepSrc: true, want: "makepkg --nocheck --config /etc/not.conf --verifysource --skippgpcheck -f", }, { desc: "nokeepsrc", keepSrc: false, want: "makepkg --nocheck --config /etc/not.conf --verifysource --skippgpcheck -f -Cc", }, } for _, tc := range testCases { t.Run(tc.desc, func(td *testing.T) { cmdBuilder := &TestMakepkgBuilder{ parentBuilder: &exe.CmdBuilder{ MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg", KeepSrc: tc.keepSrc, }, test: t, want: tc.want, wantDir: "/tmp/yay-bin", } err := downloadPKGBUILDSource(context.Background(), cmdBuilder, filepath.Join("/tmp", "yay-bin"), false) assert.NoError(t, err) assert.Equal(t, 1, int(cmdBuilder.passes)) }) } } // GIVEN 1 package // WHEN downloadPKGBUILDSource is called // THEN 1 call should be made to makepkg which should return error func Test_downloadPKGBUILDSourceError(t *testing.T) { t.Parallel() cmdBuilder := &TestMakepkgBuilder{ parentBuilder: &exe.CmdBuilder{MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg"}, test: t, want: "makepkg --nocheck --config /etc/not.conf --verifysource --skippgpcheck -f -Cc", wantDir: "/tmp/yay-bin", showError: &exec.ExitError{}, } err := downloadPKGBUILDSource(context.Background(), cmdBuilder, filepath.Join("/tmp", "yay-bin"), false) assert.Error(t, err) assert.EqualError(t, err, "error downloading sources: \x1b[36m/tmp/yay-bin\x1b[0m \n\t context: \n\t \n") } // GIVEN 5 packages // WHEN downloadPKGBUILDSourceFanout is called // THEN 5 calls should be made to makepkg func Test_downloadPKGBUILDSourceFanout(t *testing.T) { t.Parallel() pkgBuildDirs := map[string]string{ "yay": "/tmp/yay", "yay-bin": "/tmp/yay-bin", "yay-git": "/tmp/yay-git", "yay-v11": "/tmp/yay-v11", "yay-v12": "/tmp/yay-v12", } for _, maxConcurrentDownloads := range []int{0, 3} { t.Run(fmt.Sprintf("maxconcurrentdownloads set to %d", maxConcurrentDownloads), func(t *testing.T) { cmdBuilder := &TestMakepkgBuilder{ parentBuilder: &exe.CmdBuilder{ MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg", }, test: t, } err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, true, maxConcurrentDownloads) assert.NoError(t, err) assert.Equal(t, 5, int(cmdBuilder.passes)) }) } } // GIVEN 1 package // WHEN downloadPKGBUILDSourceFanout is called // THEN 1 calls should be made to makepkg without concurrency func Test_downloadPKGBUILDSourceFanoutNoCC(t *testing.T) { t.Parallel() cmdBuilder := &TestMakepkgBuilder{ parentBuilder: &exe.CmdBuilder{ MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg", }, test: t, } pkgBuildDirs := map[string]string{"yay": "/tmp/yay"} err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, false, 0) assert.NoError(t, err) assert.Equal(t, 1, int(cmdBuilder.passes)) } // GIVEN 5 packages // WHEN downloadPKGBUILDSourceFanout is called // THEN 5 calls should be made to makepkg func Test_downloadPKGBUILDSourceFanoutError(t *testing.T) { t.Parallel() cmdBuilder := &TestMakepkgBuilder{ parentBuilder: &exe.CmdBuilder{ MakepkgConfPath: "/etc/not.conf", MakepkgFlags: []string{"--nocheck"}, MakepkgBin: "makepkg", }, test: t, showError: &exec.ExitError{}, } pkgBuildDirs := map[string]string{ "yay": "/tmp/yay", "yay-bin": "/tmp/yay-bin", "yay-git": "/tmp/yay-git", "yay-v11": "/tmp/yay-v11", "yay-v12": "/tmp/yay-v12", } err := downloadPKGBUILDSourceFanout(context.Background(), cmdBuilder, pkgBuildDirs, false, 0) assert.Error(t, err) assert.Equal(t, 5, int(cmdBuilder.passes)) assert.Len(t, err.(*multierror.MultiError).Errors, 5) } ================================================ FILE: pkg/sync/workdir/clean.go ================================================ package workdir import ( "context" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) func removeMake(ctx context.Context, config *settings.Configuration, cmdBuilder exe.ICmdBuilder, makeDeps []string, cmdArgs *parser.Arguments, ) error { removeArguments := cmdArgs.CopyGlobal() err := removeArguments.AddArg("R", "s", "u") if err != nil { return err } for _, pkg := range makeDeps { removeArguments.AddTarget(pkg) } oldValue := settings.NoConfirm settings.NoConfirm = true err = cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx, removeArguments, config.Mode, settings.NoConfirm)) settings.NoConfirm = oldValue return err } func cleanAfter(ctx context.Context, run *runtime.Runtime, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string, ) { run.Logger.Println(gotext.Get("removing untracked AUR files from cache...")) i := 0 for _, dir := range pkgbuildDirs { run.Logger.OperationInfoln(gotext.Get("Cleaning (%d/%d): %s", i+1, len(pkgbuildDirs), text.Cyan(dir))) _, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd( ctx, dir, "reset", "--hard", "HEAD")) if err != nil { run.Logger.Errorln(gotext.Get("error resetting %s: %s", dir, stderr)) } if err := run.CmdBuilder.Show( run.CmdBuilder.BuildGitCmd( ctx, dir, "clean", "-fx", "--exclude", "*.pkg.*")); err != nil { run.Logger.Errorln(err) } i++ } } ================================================ FILE: pkg/sync/workdir/merge.go ================================================ package workdir import ( "context" "errors" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings/exe" ) func gitMerge(ctx context.Context, cmdBuilder exe.ICmdBuilder, dir string) error { _, stderr, err := cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "reset", "--hard", "HEAD")) if err != nil { return errors.New(gotext.Get("error resetting %s: %s", dir, stderr)) } _, stderr, err = cmdBuilder.Capture( cmdBuilder.BuildGitCmd(ctx, dir, "merge", "--no-edit", "--ff")) if err != nil { return errors.New(gotext.Get("error merging %s: %s", dir, stderr)) } return nil } func mergePkgbuilds(ctx context.Context, cmdBuilder exe.ICmdBuilder, pkgbuildDirs map[string]string) error { for _, dir := range pkgbuildDirs { err := gitMerge(ctx, cmdBuilder, dir) if err != nil { return err } } return nil } ================================================ FILE: pkg/sync/workdir/preparer.go ================================================ package workdir import ( "context" "fmt" "io" "os" "path/filepath" "strings" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/download" "github.com/Jguer/yay/v12/pkg/menus" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/sync/build" "github.com/Jguer/yay/v12/pkg/text" gosrc "github.com/Morganamilo/go-srcinfo" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" ) type HookType string const ( // PreDownloadSourcesHook is called before sourcing a package PreDownloadSourcesHook HookType = "pre-download-sources" ) type HookFn func(ctx context.Context, run *runtime.Runtime, w io.Writer, pkgbuildDirsByBase map[string]string, installed mapset.Set[string], ) error type Hook struct { Name string Hookfn HookFn Type HookType } type Preparer struct { dbExecutor db.Executor cmdBuilder exe.ICmdBuilder cfg *settings.Configuration hooks []Hook downloadSources bool log *text.Logger makeDeps []string } func NewPreparerWithoutHooks(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder, cfg *settings.Configuration, logger *text.Logger, downloadSources bool, ) *Preparer { return &Preparer{ dbExecutor: dbExecutor, cmdBuilder: cmdBuilder, cfg: cfg, hooks: []Hook{}, downloadSources: downloadSources, log: logger, } } func NewPreparer(dbExecutor db.Executor, cmdBuilder exe.ICmdBuilder, cfg *settings.Configuration, logger *text.Logger, ) *Preparer { preper := NewPreparerWithoutHooks(dbExecutor, cmdBuilder, cfg, logger, true) if cfg.CleanMenu { preper.hooks = append(preper.hooks, Hook{ Name: "clean", Hookfn: menus.CleanFn, Type: PreDownloadSourcesHook, }) } if cfg.DiffMenu { preper.hooks = append(preper.hooks, Hook{ Name: "diff", Hookfn: menus.DiffFn, Type: PreDownloadSourcesHook, }) } if cfg.EditMenu { preper.hooks = append(preper.hooks, Hook{ Name: "edit", Hookfn: menus.EditFn, Type: PreDownloadSourcesHook, }) } return preper } func (preper *Preparer) ShouldCleanAURDirs(run *runtime.Runtime, pkgBuildDirs map[string]string) build.PostInstallHookFunc { if !preper.cfg.CleanAfter || len(pkgBuildDirs) == 0 { return nil } preper.log.Debugln("added post install hook to clean up AUR dirs", pkgBuildDirs) return func(ctx context.Context) error { cleanAfter(ctx, run, run.CmdBuilder, pkgBuildDirs) return nil } } func (preper *Preparer) ShouldCleanMakeDeps(run *runtime.Runtime, cmdArgs *parser.Arguments) build.PostInstallHookFunc { if len(preper.makeDeps) == 0 { return nil } switch preper.cfg.RemoveMake { case "yes": break case "no": return nil default: isYesDefault := preper.cfg.RemoveMake == "askyes" if !preper.log.ContinueTask(gotext.Get("Remove make dependencies after install?"), isYesDefault, settings.NoConfirm) { return nil } } preper.log.Debugln("added post install hook to clean up AUR makedeps", preper.makeDeps) return func(ctx context.Context) error { return removeMake(ctx, preper.cfg, run.CmdBuilder, preper.makeDeps, cmdArgs) } } func (preper *Preparer) Run(ctx context.Context, run *runtime.Runtime, targets []map[string]*dep.InstallInfo, ) (pkgbuildDirsByBase map[string]string, err error) { preper.Present(targets) pkgBuildDirs, err := preper.PrepareWorkspace(ctx, run, targets) if err != nil { return nil, err } return pkgBuildDirs, nil } func (preper *Preparer) Present(targets []map[string]*dep.InstallInfo) { pkgsBySourceAndReason := map[string]map[string][]string{} for _, layer := range targets { for pkgName, info := range layer { source := dep.SourceNames[info.Source] reason := dep.ReasonNames[info.Reason] var pkgStr string if info.Version != "" { pkgStr = text.Cyan(fmt.Sprintf("%s-%s", pkgName, info.Version)) } else { pkgStr = text.Cyan(pkgName) } if _, ok := pkgsBySourceAndReason[source]; !ok { pkgsBySourceAndReason[source] = map[string][]string{} } pkgsBySourceAndReason[source][reason] = append(pkgsBySourceAndReason[source][reason], pkgStr) if info.Reason == dep.MakeDep { preper.makeDeps = append(preper.makeDeps, pkgName) } } } for source, pkgsByReason := range pkgsBySourceAndReason { for reason, pkgs := range pkgsByReason { preper.log.Printf(text.Bold("%s %s (%d):")+" %s\n", source, reason, len(pkgs), strings.Join(pkgs, ", ")) } } } func (preper *Preparer) PrepareWorkspace(ctx context.Context, run *runtime.Runtime, targets []map[string]*dep.InstallInfo, ) (map[string]string, error) { aurBasesToClone := mapset.NewThreadUnsafeSet[string]() pkgBuildDirsByBase := make(map[string]string, len(targets)) for _, layer := range targets { for _, info := range layer { switch info.Source { case dep.AUR: pkgBase := *info.AURBase pkgBuildDir := filepath.Join(preper.cfg.BuildDir, pkgBase) if preper.needToCloneAURBase(info, pkgBuildDir) { aurBasesToClone.Add(pkgBase) } pkgBuildDirsByBase[pkgBase] = pkgBuildDir case dep.SrcInfo: pkgBase := *info.AURBase pkgBuildDirsByBase[pkgBase] = *info.SrcinfoPath } } } if _, errA := download.AURPKGBUILDRepos(ctx, preper.cmdBuilder, preper.log.Child("download"), aurBasesToClone.ToSlice(), preper.cfg.AURURL, preper.cfg.BuildDir, false); errA != nil { return nil, errA } if !preper.downloadSources { return pkgBuildDirsByBase, nil } if err := mergePkgbuilds(ctx, preper.cmdBuilder, pkgBuildDirsByBase); err != nil { return nil, err } remoteNames := preper.dbExecutor.InstalledRemotePackageNames() remoteNamesCache := mapset.NewThreadUnsafeSet(remoteNames...) for _, hookFn := range preper.hooks { if hookFn.Type == PreDownloadSourcesHook { if err := hookFn.Hookfn(ctx, run, os.Stdout, pkgBuildDirsByBase, remoteNamesCache); err != nil { return nil, err } } } if errP := downloadPKGBUILDSourceFanout(ctx, preper.cmdBuilder, pkgBuildDirsByBase, false, preper.cfg.MaxConcurrentDownloads); errP != nil { preper.log.Errorln(errP) } return pkgBuildDirsByBase, nil } func (preper *Preparer) needToCloneAURBase(installInfo *dep.InstallInfo, pkgbuildDir string) bool { if preper.cfg.ReDownload == "all" { return true } srcinfoFile := filepath.Join(pkgbuildDir, ".SRCINFO") if pkgbuild, err := gosrc.ParseFile(srcinfoFile); err == nil { if db.VerCmp(pkgbuild.Version(), installInfo.Version) >= 0 { preper.log.OperationInfoln( gotext.Get("PKGBUILD up to date, skipping download: %s", text.Cyan(*installInfo.AURBase))) return false } } return true } ================================================ FILE: pkg/sync/workdir/preparer_test.go ================================================ //go:build !integration // +build !integration package workdir import ( "io" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } // Test order of pre-download-sources hooks func TestPreDownloadSourcesHooks(t *testing.T) { testCases := []struct { name string cfg *settings.Configuration wantHook []string }{ { name: "clean, diff, edit", cfg: &settings.Configuration{ CleanMenu: true, DiffMenu: true, EditMenu: true, }, wantHook: []string{"clean", "diff", "edit"}, }, { name: "clean, edit", cfg: &settings.Configuration{ CleanMenu: true, DiffMenu: false, EditMenu: true, }, wantHook: []string{"clean", "edit"}, }, { name: "clean, diff", cfg: &settings.Configuration{ CleanMenu: true, DiffMenu: true, EditMenu: false, }, wantHook: []string{"clean", "diff"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { preper := NewPreparer(nil, nil, tc.cfg, newTestLogger()) assert.Len(t, preper.hooks, len(tc.wantHook)) got := make([]string, 0, len(preper.hooks)) for _, hook := range preper.hooks { got = append(got, hook.Name) } assert.Equal(t, tc.wantHook, got) }) } } ================================================ FILE: pkg/text/color.go ================================================ package text import "fmt" const ( redCode = "\x1b[31m" greenCode = "\x1b[32m" yellowCode = "\x1b[33m" blueCode = "\x1b[34m" magentaCode = "\x1b[35m" CyanCode = "\x1b[36m" boldCode = "\x1b[1m" ResetCode = "\x1b[0m" ) // UseColor determines if package will emit colors. var UseColor = true func stylize(startCode, in string) string { if UseColor { return startCode + in + ResetCode } return in } func Red(in string) string { return stylize(redCode, in) } func Green(in string) string { return stylize(greenCode, in) } func yellow(in string) string { return stylize(yellowCode, in) } func Cyan(in string) string { return stylize(CyanCode, in) } func Magenta(in string) string { return stylize(magentaCode, in) } func Blue(in string) string { return stylize(blueCode, in) } func Bold(in string) string { return stylize(boldCode, in) } // ColorHash Colors text using a hashing algorithm. The same text will always produce the // same color while different text will produce a different color. func ColorHash(name string) (output string) { if !UseColor { return name } var hash uint = 5381 for i := 0; i < len(name); i++ { hash = uint(name[i]) + ((hash << 5) + (hash)) } return fmt.Sprintf("\x1b[%dm%s\x1b[0m", hash%6+31, name) } ================================================ FILE: pkg/text/convert.go ================================================ package text import ( "fmt" ) // Human method returns results in human readable format. func Human(size int64) string { floatsize := float32(size) units := [...]string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} for _, unit := range units { if floatsize < 1024 { return fmt.Sprintf("%.1f %sB", floatsize, unit) } floatsize /= 1024 } return fmt.Sprintf("%d%s", size, "B") } ================================================ FILE: pkg/text/errors.go ================================================ package text import "github.com/leonelquinteros/gotext" type ErrInputOverflow struct{} func (e ErrInputOverflow) Error() string { return gotext.Get("input too long") } ================================================ FILE: pkg/text/input.go ================================================ package text import ( "bufio" "fmt" "strings" "unicode" "unicode/utf8" "github.com/leonelquinteros/gotext" ) func (l *Logger) GetInput(defaultValue string, noConfirm bool) (string, error) { l.Info() if defaultValue != "" || noConfirm { l.Println(defaultValue) return defaultValue, nil } reader := bufio.NewReader(l.r) buf, overflow, err := reader.ReadLine() if err != nil { return "", err } if overflow { return "", ErrInputOverflow{} } return string(buf), nil } // ContinueTask prompts if user wants to continue task. // If NoConfirm is set the action will continue without user input. func (l *Logger) ContinueTask(s string, preset, noConfirm bool) bool { if noConfirm { return preset } var ( response string postFix string n string y string yes = gotext.Get("yes") no = gotext.Get("no") ) // Only use localized "y" and "n" if they are latin characters. if nRune, _ := utf8.DecodeRuneInString(no); unicode.Is(unicode.Latin, nRune) { n = string(nRune) } else { n = nDefault } if yRune, _ := utf8.DecodeRuneInString(yes); unicode.Is(unicode.Latin, yRune) { y = string(yRune) } else { y = yDefault } if preset { // If default behavior is true, use y as default. postFix = fmt.Sprintf(" [%s/%s] ", strings.ToUpper(y), n) } else { // If default behavior is anything else, use n as default. postFix = fmt.Sprintf(" [%s/%s] ", y, strings.ToUpper(n)) } l.OperationInfo(Bold(s), Bold(postFix)) if _, err := fmt.Fscanln(l.r, &response); err != nil { return preset } return strings.EqualFold(response, yes) || strings.EqualFold(response, y) || (!strings.EqualFold(yDefault, n) && strings.EqualFold(response, yDefault)) } ================================================ FILE: pkg/text/service.go ================================================ package text import ( "fmt" "io" ) const ( arrow = "==>" smallArrow = " ->" opSymbol = "::" ) type Logger struct { name string Debug bool stdout io.Writer stderr io.Writer r io.Reader } func NewLogger(stdout, stderr io.Writer, r io.Reader, debug bool, name string) *Logger { return &Logger{ Debug: debug, name: name, r: r, stderr: stderr, stdout: stdout, } } func (l *Logger) Child(name string) *Logger { return NewLogger(l.stdout, l.stderr, l.r, l.Debug, name) } func (l *Logger) Debugln(a ...any) { if !l.Debug { return } l.Println(append([]any{ Bold(yellow(fmt.Sprintf("[DEBUG:%s]", l.name))), }, a...)...) } func (l *Logger) OperationInfoln(a ...any) { l.Println(l.SprintOperationInfo(a...)) } func (l *Logger) OperationInfo(a ...any) { l.Print(l.SprintOperationInfo(a...)) } func (l *Logger) SprintOperationInfo(a ...any) string { return fmt.Sprint(append([]any{Bold(Cyan(opSymbol + " ")), boldCode}, a...)...) + ResetCode } func (l *Logger) Info(a ...any) { l.Print(append([]any{Bold(Green(arrow + " "))}, a...)...) } func (l *Logger) Infoln(a ...any) { l.Println(append([]any{Bold(Green(arrow))}, a...)...) } func (l *Logger) Warn(a ...any) { l.Print(l.SprintWarn(a...)) } func (l *Logger) Warnln(a ...any) { l.Println(l.SprintWarn(a...)) } func (l *Logger) SprintWarn(a ...any) string { return fmt.Sprint(append([]any{Bold(yellow(smallArrow + " "))}, a...)...) } func (l *Logger) Error(a ...any) { fmt.Fprint(l.stderr, l.SprintError(a...)) } func (l *Logger) Errorln(a ...any) { fmt.Fprintln(l.stderr, l.SprintError(a...)) } func (l *Logger) SprintError(a ...any) string { return fmt.Sprint(append([]any{Bold(Red(smallArrow + " "))}, a...)...) } func (l *Logger) Printf(format string, a ...any) { fmt.Fprintf(l.stdout, format, a...) } func (l *Logger) Println(a ...any) { fmt.Fprintln(l.stdout, a...) } func (l *Logger) Print(a ...any) { fmt.Fprint(l.stdout, a...) } ================================================ FILE: pkg/text/text.go ================================================ package text import ( "strings" "unicode" ) const ( yDefault = "y" nDefault = "n" ) // SplitDBFromName split apart db/package to db and package. func SplitDBFromName(pkg string) (db, name string) { split := strings.SplitN(pkg, "/", 2) if len(split) == 2 { return split[0], split[1] } return "", split[0] } // LessRunes compares two rune values, and returns true if the first argument is lexicographicaly smaller. func LessRunes(iRunes, jRunes []rune) bool { maxLen := min(len(iRunes), len(jRunes)) for idx := 0; idx < maxLen; idx++ { ir := iRunes[idx] jr := jRunes[idx] lir := unicode.ToLower(ir) ljr := unicode.ToLower(jr) if lir != ljr { return lir < ljr } // the lowercase runes are the same, so compare the original if ir != jr { return ir < jr } } return len(iRunes) < len(jRunes) } var RepoUrls = map[string]string{ "core": "https://archlinux.org/packages/core", "core-testing": "https://archlinux.org/packages/core-testing", "extra": "https://archlinux.org/packages/extra", "extra-testing": "https://archlinux.org/packages/extra-testing", "gnome-unstable": "https://archlinux.org/packages/gnome-unstable", "kde-unstable": "https://archlinux.org/packages/kde-unstable", "multilib": "https://archlinux.org/packages/multilib", "multilib-testing": "https://archlinux.org/packages/multilib-testing", "testing": "https://archlinux.org/packages/testing", "aur": "https://aur.archlinux.org/packages", "devel": "https://aur.archlinux.org/packages", } func CreateOSC8Link(url, text string) string { osc8Start := "\033]8;;" + url + "\033\\" osc8End := "\033]8;;\033\\" return osc8Start + text + osc8End } func CreateRepoLink(repo, arch, pkgName, text string) string { if !UseColor { return text } urlBase, ok := RepoUrls[repo] if !ok { return text } var url string if repo == "aur" || repo == "devel" { url = urlBase + "/" + pkgName } else { url = urlBase + "/" + arch + "/" + pkgName } return CreateOSC8Link(url, text) } ================================================ FILE: pkg/text/text_test.go ================================================ //go:build !integration // +build !integration package text import ( "io" "os" "path" "strings" "testing" "github.com/leonelquinteros/gotext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestLessRunes(t *testing.T) { t.Parallel() type args struct { iRunes []rune jRunes []rune } tests := []struct { name string args args want bool }{ {name: "nilslices", args: args{iRunes: nil, jRunes: nil}, want: false}, {name: "emptyslices", args: args{iRunes: []rune{}, jRunes: []rune{}}, want: false}, {name: "simpleslice a,b", args: args{iRunes: []rune{'a'}, jRunes: []rune{'b'}}, want: true}, {name: "simpleslice b,a", args: args{iRunes: []rune{'b'}, jRunes: []rune{'a'}}, want: false}, {name: "equalslice", args: args{iRunes: []rune{'a', 'a', 'a'}, jRunes: []rune{'a', 'a', 'a'}}, want: false}, {name: "uppercase", args: args{iRunes: []rune{'a'}, jRunes: []rune{'A'}}, want: false}, {name: "longerFirstArg", args: args{iRunes: []rune{'a', 'b'}, jRunes: []rune{'a'}}, want: false}, {name: "longerSecondArg", args: args{iRunes: []rune{'a'}, jRunes: []rune{'a', 'b'}}, want: true}, {name: "utf8 less", args: args{iRunes: []rune{'世', '2', '0'}, jRunes: []rune{'世', '界', '3'}}, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := LessRunes(tt.args.iRunes, tt.args.jRunes) assert.Equal(t, tt.want, got) }) } } func TestContinueTask(t *testing.T) { t.Parallel() type args struct { s string preset bool noConfirm bool input string } tests := []struct { name string args args want bool }{ {name: "noconfirm-true", args: args{s: "", preset: true, noConfirm: true}, want: true}, {name: "noconfirm-false", args: args{s: "", preset: false, noConfirm: true}, want: false}, {name: "noinput-false", args: args{s: "", preset: false, noConfirm: false}, want: false}, {name: "noinput-true", args: args{s: "", preset: true, noConfirm: false}, want: true}, {name: "input-false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false}, {name: "input-true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true}, {name: "input-false-complete", args: args{s: "", input: "no", preset: true, noConfirm: false}, want: false}, {name: "input-true-complete", args: args{s: "", input: "yes", preset: false, noConfirm: false}, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // create io.Reader with value of input in := strings.NewReader(tt.args.input) logger := NewLogger(io.Discard, io.Discard, in, false, "test") got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm) require.Equal(t, tt.want, got) }) } } func TestContinueTaskRU(t *testing.T) { strCustom := ` msgid "yes" msgstr "да" ` // Create Locales directory and files on temp location tmpDir := t.TempDir() dirname := path.Join(tmpDir, "en_US") err := os.MkdirAll(dirname, os.ModePerm) require.NoError(t, err) fDefault, err := os.Create(path.Join(dirname, "yay.po")) require.NoError(t, err) defer fDefault.Close() _, err = fDefault.WriteString(strCustom) require.NoError(t, err) gotext.Configure(tmpDir, "en_US", "yay") require.Equal(t, "да", gotext.Get("yes")) type args struct { s string preset bool noConfirm bool input string } tests := []struct { name string args args want bool }{ {name: "default input false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false}, {name: "default input true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { in := strings.NewReader(tt.args.input) logger := NewLogger(io.Discard, io.Discard, in, false, "test") got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm) require.Equal(t, tt.want, got) }) } gotext.SetLanguage("") } func TestContinueTaskDE(t *testing.T) { strCustom := ` msgid "yes" msgstr "ja" ` // Create Locales directory and files on temp location tmpDir := t.TempDir() dirname := path.Join(tmpDir, "en_US") err := os.MkdirAll(dirname, os.ModePerm) require.NoError(t, err) fDefault, err := os.Create(path.Join(dirname, "yay.po")) require.NoError(t, err) defer fDefault.Close() _, err = fDefault.WriteString(strCustom) require.NoError(t, err) gotext.Configure(tmpDir, "en_US", "yay") require.Equal(t, "ja", gotext.Get("yes")) type args struct { s string preset bool noConfirm bool input string } tests := []struct { name string args args want bool }{ {name: "default input false", args: args{s: "", input: "n", preset: true, noConfirm: false}, want: false}, {name: "default input true", args: args{s: "", input: "y", preset: false, noConfirm: false}, want: true}, {name: "custom input true", args: args{s: "", input: "j", preset: false, noConfirm: false}, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { in := strings.NewReader(tt.args.input) logger := NewLogger(io.Discard, io.Discard, in, false, "test") got := logger.ContinueTask(tt.args.s, tt.args.preset, tt.args.noConfirm) require.Equal(t, tt.want, got) }) } gotext.SetLanguage("") } func TestCreateRepoLink(t *testing.T) { tests := []struct { name string useColor bool repo string arch string pkgName string text string want string }{ { name: "color disabled returns text", useColor: false, repo: "core", arch: "x86_64", pkgName: "linux", text: "core/linux", want: "core/linux", }, { name: "unknown repo returns text", useColor: true, repo: "unknown", arch: "x86_64", pkgName: "linux", text: "core/linux", want: "core/linux", }, { name: "aur repo uses package url", useColor: true, repo: "aur", arch: "any", pkgName: "yay", text: "aur/yay", want: "\033]8;;https://aur.archlinux.org/packages/yay\033\\aur/yay\033]8;;\033\\", }, { name: "core repo uses arch in url", useColor: true, repo: "core", arch: "x86_64", pkgName: "linux", text: "core/linux", want: "\033]8;;https://archlinux.org/packages/core/x86_64/linux\033\\core/linux\033]8;;\033\\", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { original := UseColor UseColor = tt.useColor t.Cleanup(func() { UseColor = original }) got := CreateRepoLink(tt.repo, tt.arch, tt.pkgName, tt.text) assert.Equal(t, tt.want, got) }) } } ================================================ FILE: pkg/text/time.go ================================================ package text import "time" // Formats a unix timestamp to ISO 8601 date (yyyy-mm-dd). func FormatTime(i int) string { t := time.Unix(int64(i), 0) return t.Format("2006-01-02") } // Formats a unix timestamp to ISO 8601 date (Mon 02 Jan 2006 03:04:05 PM MST). func FormatTimeQuery(i int) string { t := time.Unix(int64(i), 0) return t.Format("Mon 02 Jan 2006 03:04:05 PM MST") } ================================================ FILE: pkg/upgrade/.snapshots/Test_upAUR-No_Updates ================================================  -> ignored: ignoring package upgrade (1.0.0 => 2.0.0) ================================================ FILE: pkg/upgrade/.snapshots/Test_upAUR-Simple_Update ================================================ ================================================ FILE: pkg/upgrade/.snapshots/Test_upAUR-Time_Update ================================================ ================================================ FILE: pkg/upgrade/service.go ================================================ package upgrade import ( "context" "fmt" "sort" "strings" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/dep/topo" "github.com/Jguer/yay/v12/pkg/intrange" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) const cutOffExtra = 2 type UpgradeService struct { grapher *dep.Grapher aurCache aur.QueryClient dbExecutor db.Executor vcsStore vcs.Store cfg *settings.Configuration log *text.Logger noConfirm bool AURWarnings *query.AURWarnings } func NewUpgradeService(grapher *dep.Grapher, aurCache aur.QueryClient, dbExecutor db.Executor, vcsStore vcs.Store, cfg *settings.Configuration, noConfirm bool, logger *text.Logger, ) *UpgradeService { return &UpgradeService{ grapher: grapher, aurCache: aurCache, dbExecutor: dbExecutor, vcsStore: vcsStore, cfg: cfg, noConfirm: noConfirm, log: logger, AURWarnings: query.NewWarnings(logger.Child("warnings")), } } // upGraph adds packages to upgrade to the graph. func (u *UpgradeService) upGraph(ctx context.Context, graph *topo.Graph[string, *dep.InstallInfo], enableDowngrade bool, filter Filter, ) (err error) { var ( develUp UpSlice errs multierror.MultiError aurdata = make(map[string]*aur.Pkg) aurUp UpSlice ) remote := u.dbExecutor.InstalledRemotePackages() remoteNames := u.dbExecutor.InstalledRemotePackageNames() if u.cfg.Mode.AtLeastAUR() { u.log.OperationInfoln(gotext.Get("Searching AUR for updates...")) _aurdata, err := u.aurCache.Get(ctx, &aur.Query{Needles: remoteNames, By: aur.Name}) errs.Add(err) if err == nil { for i := range _aurdata { pkg := &_aurdata[i] aurdata[pkg.Name] = pkg u.AURWarnings.AddToWarnings(remote, pkg) } u.AURWarnings.CalculateMissing(remoteNames, remote, aurdata) aurUp = UpAUR(u.log, remote, aurdata, enableDowngrade) if u.cfg.Devel { u.log.OperationInfoln(gotext.Get("Checking development packages...")) develUp = UpDevel(ctx, u.log, remote, aurdata, u.vcsStore) u.vcsStore.CleanOrphans(remote) } } } aurPkgsAdded := []*aur.Pkg{} names := mapset.NewThreadUnsafeSet[string]() for i := range develUp.Up { up := &develUp.Up[i] // check if deps are satisfied for aur packages reason := dep.Explicit if up.Reason == alpm.PkgReasonDepend { reason = dep.Dep } if filter != nil && !filter(up) { continue } aurPkg := aurdata[up.Name] graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{ Reason: reason, Source: dep.AUR, AURBase: &aurPkg.PackageBase, Upgrade: true, Devel: true, LocalVersion: up.LocalVersion, Version: up.RemoteVersion, }) names.Add(up.Name) aurPkgsAdded = append(aurPkgsAdded, aurPkg) } for i := range aurUp.Up { up := &aurUp.Up[i] // add devel packages if they are not already in the list if names.Contains(up.Name) { continue } // check if deps are satisfied for aur packages reason := dep.Explicit if up.Reason == alpm.PkgReasonDepend { reason = dep.Dep } if filter != nil && !filter(up) { continue } aurPkg := aurdata[up.Name] graph = u.grapher.GraphAURTarget(ctx, graph, aurPkg, &dep.InstallInfo{ Reason: reason, Source: dep.AUR, AURBase: &aurPkg.PackageBase, Upgrade: true, Version: up.RemoteVersion, LocalVersion: up.LocalVersion, }) aurPkgsAdded = append(aurPkgsAdded, aurPkg) } u.grapher.AddDepsForPkgs(ctx, aurPkgsAdded, graph) if u.cfg.Mode.AtLeastRepo() { u.log.OperationInfoln(gotext.Get("Searching databases for updates...")) syncUpgrades, err := u.dbExecutor.SyncUpgrades(enableDowngrade) for _, up := range syncUpgrades { if filter != nil && !filter(&db.Upgrade{ Name: up.Package.Name(), RemoteVersion: up.Package.Version(), Repository: up.Package.DB().Name(), Base: up.Package.Base(), LocalVersion: up.LocalVersion, Reason: up.Reason, }) { continue } upgradeInfo := up graph = u.grapher.GraphSyncPkg(ctx, graph, up.Package, &upgradeInfo) } errs.Add(err) } return errs.Return() } func (u *UpgradeService) graphToUpSlice(graph *topo.Graph[string, *dep.InstallInfo]) (aurUp, repoUp UpSlice) { aurUp = UpSlice{Up: make([]Upgrade, 0, graph.Len())} repoUp = UpSlice{Up: make([]Upgrade, 0, graph.Len()), Repos: u.dbExecutor.Repos()} _ = graph.ForEach(func(name string, info *dep.InstallInfo) error { alpmReason := alpm.PkgReasonDepend if info.Reason == dep.Explicit { alpmReason = alpm.PkgReasonExplicit } parents := graph.ImmediateDependencies(name) extra := "" if len(parents) > 0 && !info.Upgrade && info.Reason == dep.MakeDep { reducedParents := parents.Slice()[:min(cutOffExtra, len(parents))] if len(parents) > cutOffExtra { reducedParents = append(reducedParents, "...") } extra = fmt.Sprintf(" (%s of %s)", dep.ReasonNames[info.Reason], strings.Join(reducedParents, ", ")) } switch info.Source { case dep.AUR: aurRepo := "aur" if info.Devel { aurRepo = "devel" } aurUp.Up = append(aurUp.Up, Upgrade{ Name: name, RemoteVersion: info.Version, Repository: aurRepo, Base: *info.AURBase, LocalVersion: info.LocalVersion, Reason: alpmReason, Extra: extra, }) case dep.Sync: repoUp.Up = append(repoUp.Up, Upgrade{ Name: name, RemoteVersion: info.Version, Repository: *info.SyncDBName, Base: "", LocalVersion: info.LocalVersion, Reason: alpmReason, Extra: extra, }) } return nil }) return aurUp, repoUp } func (u *UpgradeService) GraphUpgrades(ctx context.Context, graph *topo.Graph[string, *dep.InstallInfo], enableDowngrade bool, filter Filter, ) (*topo.Graph[string, *dep.InstallInfo], error) { if graph == nil { graph = dep.NewGraph() } err := u.upGraph(ctx, graph, enableDowngrade, filter) if err != nil { return graph, err } return graph, nil } // userExcludeUpgrades asks the user which packages to exclude from the upgrade and // removes them from the graph func (u *UpgradeService) UserExcludeUpgrades(graph *topo.Graph[string, *dep.InstallInfo]) ([]string, error) { if graph.Len() == 0 { return []string{}, nil } aurUp, repoUp := u.graphToUpSlice(graph) sort.Sort(repoUp) sort.Sort(aurUp) allUp := UpSlice{Repos: append(repoUp.Repos, aurUp.Repos...)} for _, up := range repoUp.Up { if up.LocalVersion == "" && up.Reason != alpm.PkgReasonExplicit { allUp.PulledDeps = append(allUp.PulledDeps, up) } else { allUp.Up = append(allUp.Up, up) } } for _, up := range aurUp.Up { if up.LocalVersion == "" && up.Reason != alpm.PkgReasonExplicit { allUp.PulledDeps = append(allUp.PulledDeps, up) } else { allUp.Up = append(allUp.Up, up) } } if len(allUp.PulledDeps) > 0 { u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), len(allUp.PulledDeps), text.Bold(gotext.Get("%s will also be installed for this operation.", gotext.GetN("dependency", "dependencies", len(allUp.PulledDeps))))) allUp.PrintDeps(u.log) } u.log.Printf("%s"+text.Bold(" %d ")+"%s\n", text.Bold(text.Cyan("::")), len(allUp.Up), text.Bold(gotext.Get("%s to upgrade/install.", gotext.GetN("package", "packages", len(allUp.Up))))) allUp.Print(u.log) u.log.Infoln(gotext.Get("Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)")) u.log.Warnln(gotext.Get("Excluding packages may cause partial upgrades and break systems")) numbers, err := u.log.GetInput(u.cfg.AnswerUpgrade, settings.NoConfirm) if err != nil { return nil, err } // upgrade menu asks you which packages to NOT upgrade so in this case // exclude and include are kind of swapped exclude, include, otherExclude, otherInclude := intrange.ParseNumberMenu(numbers) // true if user doesn't want to include specific repositories/packages noIncludes := len(include) == 0 && otherInclude.Cardinality() == 0 // No exclusions or inclusions specified, return early if noIncludes && len(exclude) == 0 && otherExclude.Cardinality() == 0 { return []string{}, nil } excluded := make([]string, 0) for i := range allUp.Up { up := &allUp.Up[i] upgradeID := len(allUp.Up) - i // check if user wants to exclude specific things (true) or include specific things if noIncludes { // exclude repositories mentioned by the user if otherExclude.Contains(up.Repository) { u.log.Debugln("pruning", up.Name) excluded = append(excluded, graph.Prune(up.Name)...) } // exclude packages mentioned by the user if exclude.Get(upgradeID) { u.log.Debugln("pruning", up.Name) excluded = append(excluded, graph.Prune(up.Name)...) } // If the user explicitly wants to include a package/repository, exclude everything else } else if !include.Get(upgradeID) && !otherInclude.Contains(up.Repository) { u.log.Debugln("pruning", up.Name) excluded = append(excluded, graph.Prune(up.Name)...) } } return excluded, nil } ================================================ FILE: pkg/upgrade/service_test.go ================================================ //go:build !integration // +build !integration package upgrade import ( "context" "io" "os" "strings" "testing" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/dep/topo" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" ) func ptrString(s string) *string { return &s } func TestUpgradeService_GraphUpgrades(t *testing.T) { t.Parallel() linuxDepInfo := &dep.InstallInfo{ Reason: dep.Explicit, Source: dep.Sync, AURBase: nil, LocalVersion: "4.5.0-1", Version: "5.0.0-1", SyncDBName: ptrString("core"), Upgrade: true, Devel: false, } exampleDepInfoDevel := &dep.InstallInfo{ Source: dep.AUR, Reason: dep.Dep, AURBase: ptrString("example"), LocalVersion: "2.2.1.r32.41baa362-1", Version: "latest-commit", Upgrade: true, Devel: true, } newDepInfo := &dep.InstallInfo{ Source: dep.Sync, Reason: dep.Dep, SyncDBName: ptrString("core"), Version: "3.0.1-2", LocalVersion: "", Upgrade: true, Devel: false, } exampleDepInfoAUR := &dep.InstallInfo{ Source: dep.AUR, Reason: dep.Dep, AURBase: ptrString("example"), LocalVersion: "2.2.1.r32.41baa362-1", Version: "2.2.1.r69.g8a10460-1", Upgrade: true, Devel: false, } yayDepInfo := &dep.InstallInfo{ Reason: dep.Explicit, Source: dep.AUR, AURBase: ptrString("yay"), LocalVersion: "10.2.3", Version: "10.2.4", Upgrade: true, Devel: false, } coreDB := mock.NewDB("core") dbExe := &mock.DBExecutor{ InstalledRemotePackageNamesFn: func() []string { return []string{"yay", "example-git"} }, InstalledRemotePackagesFn: func() map[string]mock.IPackage { mapRemote := make(map[string]mock.IPackage) mapRemote["yay"] = &mock.Package{ PName: "yay", PBase: "yay", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["example-git"] = &mock.Package{ PName: "example-git", PBase: "example", PVersion: "2.2.1.r32.41baa362-1", PReason: alpm.PkgReasonDepend, } return mapRemote }, LocalSatisfierExistsFn: func(string) bool { return false }, SyncSatisfierFn: func(s string) mock.IPackage { return &mock.Package{ PName: "new-dep", PVersion: "3.0.1-2", PDB: coreDB, } }, SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { mapUpgrades := make(map[string]db.SyncUpgrade) mapUpgrades["linux"] = db.SyncUpgrade{ Package: &mock.Package{ PName: "linux", PVersion: "5.0.0-1", PReason: alpm.PkgReasonDepend, PDB: coreDB, PDepends: mock.DependList{Depends: []alpm.Depend{ {Name: "new-dep", Version: "3.0.1"}, }}, }, LocalVersion: "4.5.0-1", Reason: alpm.PkgReasonExplicit, } mapUpgrades["new-dep"] = db.SyncUpgrade{ Package: &mock.Package{ PName: "new-dep", PVersion: "3.0.1-2", PReason: alpm.PkgReasonDepend, PDB: coreDB, }, LocalVersion: "", Reason: alpm.PkgReasonDepend, } return mapUpgrades, nil }, ReposFn: func() []string { return []string{"core"} }, } vcsStore := &vcs.Mock{ ToUpgradeReturn: []string{"example-git"}, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ {Name: "yay", Version: "10.2.4", PackageBase: "yay"}, { Name: "example-git", Version: "2.2.1.r69.g8a10460-1", PackageBase: "example", Depends: []string{"new-dep"}, }, }, nil }, } type fields struct { input io.Reader output io.Writer noConfirm bool devel bool } type args struct { graph *topo.Graph[string, *dep.InstallInfo] enableDowngrade bool } tests := []struct { name string fields fields args args mustExist map[string]*dep.InstallInfo mustNotExist map[string]bool wantExclude []string wantErr bool }{ { name: "no input", fields: fields{ input: strings.NewReader("\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, "linux": linuxDepInfo, "example-git": exampleDepInfoAUR, "new-dep": newDepInfo, }, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, { name: "no input devel", fields: fields{ input: strings.NewReader("\n"), output: io.Discard, noConfirm: false, devel: true, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, "linux": linuxDepInfo, "example-git": exampleDepInfoDevel, }, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, { name: "exclude example-git", fields: fields{ input: strings.NewReader("2\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, "linux": linuxDepInfo, }, mustNotExist: map[string]bool{"example-git": true, "new-dep": true}, wantErr: false, wantExclude: []string{"example-git", "new-dep"}, }, { name: "exclude new-dep should have no effect", fields: fields{ input: strings.NewReader("1 3 4\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "example-git": exampleDepInfoAUR, "new-dep": newDepInfo, }, mustNotExist: map[string]bool{"linux": true, "yay": true}, wantErr: false, wantExclude: []string{"linux", "yay"}, }, { name: "exclude yay", fields: fields{ input: strings.NewReader("1\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "linux": linuxDepInfo, "example-git": exampleDepInfoAUR, }, mustNotExist: map[string]bool{"yay": true}, wantErr: false, wantExclude: []string{"yay"}, }, { name: "exclude linux", fields: fields{ input: strings.NewReader("3\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, "example-git": exampleDepInfoAUR, "new-dep": newDepInfo, }, mustNotExist: map[string]bool{"linux": true}, wantErr: false, wantExclude: []string{"linux"}, }, { name: "only linux", fields: fields{ input: strings.NewReader("^3\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "linux": linuxDepInfo, }, mustNotExist: map[string]bool{"yay": true, "example-git": true}, wantErr: false, wantExclude: []string{"yay", "example-git", "new-dep"}, }, { name: "exclude all", fields: fields{ input: strings.NewReader("1-4\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{}, mustNotExist: map[string]bool{"yay": true, "example-git": true, "linux": true}, wantErr: false, wantExclude: []string{"yay", "example-git", "linux", "new-dep"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test")) cfg := &settings.Configuration{ Devel: tt.fields.devel, Mode: parser.ModeAny, } logger := text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test") u := &UpgradeService{ log: logger, grapher: grapher, aurCache: mockAUR, dbExecutor: dbExe, vcsStore: vcsStore, cfg: cfg, noConfirm: tt.fields.noConfirm, AURWarnings: query.NewWarnings(logger), } got, err := u.GraphUpgrades(context.Background(), tt.args.graph, tt.args.enableDowngrade, func(*Upgrade) bool { return true }) if (err != nil) != tt.wantErr { t.Errorf("UpgradeService.GraphUpgrades() error = %v, wantErr %v", err, tt.wantErr) return } excluded, err := u.UserExcludeUpgrades(got) require.NoError(t, err) for node, info := range tt.mustExist { assert.True(t, got.Exists(node), node) assert.Equal(t, info, got.GetNodeInfo(node).Value) } for node := range tt.mustNotExist { assert.False(t, got.Exists(node), node) } assert.ElementsMatch(t, tt.wantExclude, excluded) }) } } func TestUpgradeService_GraphUpgradesMissingDep(t *testing.T) { t.Parallel() newDepMissingInfo := &dep.InstallInfo{ Source: dep.Missing, Reason: dep.Dep, Version: "", } exampleDepInfoAUR := &dep.InstallInfo{ Source: dep.AUR, Reason: dep.Dep, AURBase: ptrString("example"), LocalVersion: "2.2.1.r32.41baa362-1", Version: "2.2.1.r69.g8a10460-1", Upgrade: true, Devel: false, } yayDepInfo := &dep.InstallInfo{ Reason: dep.Explicit, Source: dep.AUR, AURBase: ptrString("yay"), LocalVersion: "10.2.3", Version: "10.2.4", Upgrade: true, Devel: false, } dbExe := &mock.DBExecutor{ InstalledRemotePackageNamesFn: func() []string { return []string{"yay", "example-git"} }, InstalledRemotePackagesFn: func() map[string]mock.IPackage { mapRemote := make(map[string]mock.IPackage) mapRemote["yay"] = &mock.Package{ PName: "yay", PBase: "yay", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["example-git"] = &mock.Package{ PName: "example-git", PBase: "example", PVersion: "2.2.1.r32.41baa362-1", PReason: alpm.PkgReasonDepend, } return mapRemote }, LocalSatisfierExistsFn: func(string) bool { return false }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{}, nil }, ReposFn: func() []string { return nil }, } vcsStore := &vcs.Mock{ ToUpgradeReturn: []string{"example-git"}, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "yay", Version: "10.2.4", PackageBase: "yay", }, { Name: "example-git", Version: "2.2.1.r69.g8a10460-1", PackageBase: "example", Depends: []string{"new-dep-missing"}, }, }, nil }, } type fields struct { input io.Reader output io.Writer noConfirm bool devel bool } type args struct { graph *topo.Graph[string, *dep.InstallInfo] enableDowngrade bool } tests := []struct { name string fields fields args args mustExist map[string]*dep.InstallInfo mustNotExist map[string]bool wantExclude []string wantErr bool }{ { name: "no input", fields: fields{ input: strings.NewReader("\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, "example-git": exampleDepInfoAUR, "new-dep-missing": newDepMissingInfo, }, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, { name: "exclude example-git(with missing dep)", fields: fields{ input: strings.NewReader("2\n"), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "yay": yayDepInfo, }, mustNotExist: map[string]bool{"example-git": true, "new-dep-missing": true}, wantErr: false, wantExclude: []string{"example-git", "new-dep-missing"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test")) cfg := &settings.Configuration{ Devel: tt.fields.devel, Mode: parser.ModeAny, } logger := text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test") u := &UpgradeService{ log: logger, grapher: grapher, aurCache: mockAUR, dbExecutor: dbExe, vcsStore: vcsStore, cfg: cfg, noConfirm: tt.fields.noConfirm, AURWarnings: query.NewWarnings(logger), } got, err := u.GraphUpgrades(context.Background(), tt.args.graph, tt.args.enableDowngrade, func(*Upgrade) bool { return true }) if (err != nil) != tt.wantErr { t.Errorf("UpgradeService.GraphUpgrades() error = %v, wantErr %v", err, tt.wantErr) return } excluded, err := u.UserExcludeUpgrades(got) require.NoError(t, err) for node, info := range tt.mustExist { assert.True(t, got.Exists(node), node) assert.Equal(t, info, got.GetNodeInfo(node).Value) } for node := range tt.mustNotExist { assert.False(t, got.Exists(node), node) } assert.ElementsMatch(t, tt.wantExclude, excluded) }) } } func TestUpgradeService_GraphUpgradesNoUpdates(t *testing.T) { t.Parallel() dbExe := &mock.DBExecutor{ InstalledRemotePackageNamesFn: func() []string { return []string{"yay", "example-git"} }, InstalledRemotePackagesFn: func() map[string]mock.IPackage { mapRemote := make(map[string]mock.IPackage) mapRemote["yay"] = &mock.Package{ PName: "yay", PBase: "yay", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["example-git"] = &mock.Package{ PName: "example-git", PBase: "example", PVersion: "2.2.1.r32.41baa362-1", PReason: alpm.PkgReasonDepend, } return mapRemote }, SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { mapUpgrades := make(map[string]db.SyncUpgrade) return mapUpgrades, nil }, ReposFn: func() []string { return []string{"core"} }, } vcsStore := &vcs.Mock{ ToUpgradeReturn: []string{}, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, } type fields struct { input io.Reader output io.Writer noConfirm bool devel bool } type args struct { graph *topo.Graph[string, *dep.InstallInfo] enableDowngrade bool } tests := []struct { name string fields fields args args mustExist map[string]*dep.InstallInfo mustNotExist map[string]bool wantExclude []string wantErr bool }{ { name: "no input", fields: fields{ input: strings.NewReader(""), output: io.Discard, noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{}, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test")) cfg := &settings.Configuration{ Devel: tt.fields.devel, Mode: parser.ModeAny, } logger := text.NewLogger(tt.fields.output, os.Stderr, tt.fields.input, true, "test") u := &UpgradeService{ log: logger, grapher: grapher, aurCache: mockAUR, dbExecutor: dbExe, vcsStore: vcsStore, cfg: cfg, noConfirm: tt.fields.noConfirm, AURWarnings: query.NewWarnings(logger), } got, err := u.GraphUpgrades(context.Background(), tt.args.graph, tt.args.enableDowngrade, func(*Upgrade) bool { return true }) if (err != nil) != tt.wantErr { t.Errorf("UpgradeService.GraphUpgrades() error = %v, wantErr %v", err, tt.wantErr) return } excluded, err := u.UserExcludeUpgrades(got) require.NoError(t, err) for node, info := range tt.mustExist { assert.True(t, got.Exists(node), node) assert.Equal(t, info, got.GetNodeInfo(node).Value) } for node := range tt.mustNotExist { assert.False(t, got.Exists(node), node) } assert.ElementsMatch(t, tt.wantExclude, excluded) }) } } func TestUpgradeService_Warnings(t *testing.T) { t.Parallel() dbExe := &mock.DBExecutor{ InstalledRemotePackageNamesFn: func() []string { return []string{"orphan", "outdated", "missing", "orphan-ignored"} }, InstalledRemotePackagesFn: func() map[string]mock.IPackage { mapRemote := make(map[string]mock.IPackage) mapRemote["orphan"] = &mock.Package{ PName: "orphan", PBase: "orphan", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["outdated"] = &mock.Package{ PName: "outdated", PBase: "outdated", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["missing"] = &mock.Package{ PName: "missing", PBase: "missing", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, } mapRemote["orphan-ignored"] = &mock.Package{ PName: "orphan-ignored", PBase: "orphan-ignored", PVersion: "10.2.3", PReason: alpm.PkgReasonExplicit, PShouldIgnore: true, } return mapRemote }, LocalSatisfierExistsFn: func(string) bool { return false }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { mapUpgrades := make(map[string]db.SyncUpgrade) return mapUpgrades, nil }, ReposFn: func() []string { return []string{"core"} }, } vcsStore := &vcs.Mock{ ToUpgradeReturn: []string{}, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "outdated", Version: "10.2.4", PackageBase: "orphan", OutOfDate: 100, Maintainer: "bob", }, { Name: "orphan", Version: "10.2.4", PackageBase: "orphan", Maintainer: "", }, }, nil }, } logger := text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n"), true, "test") grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, logger) cfg := &settings.Configuration{ Devel: false, Mode: parser.ModeAUR, } u := &UpgradeService{ log: logger, grapher: grapher, aurCache: mockAUR, dbExecutor: dbExe, vcsStore: vcsStore, cfg: cfg, noConfirm: true, AURWarnings: query.NewWarnings(logger), } _, err := u.GraphUpgrades(context.Background(), nil, false, func(*Upgrade) bool { return true }) require.NoError(t, err) assert.Equal(t, []string{"missing"}, u.AURWarnings.Missing) assert.Equal(t, []string{"outdated"}, u.AURWarnings.OutOfDate) assert.Equal(t, []string{"orphan"}, u.AURWarnings.Orphans) } func TestUpgradeService_GraphUpgrades_zfs_dkms(t *testing.T) { t.Parallel() zfsDKMSInfo := &dep.InstallInfo{ Reason: dep.Explicit, Source: dep.AUR, AURBase: ptrString("zfs-dkms"), LocalVersion: "2.1.10-1", Version: "2.1.11-1", Upgrade: true, Devel: false, } zfsUtilsInfo := &dep.InstallInfo{ Reason: dep.Dep, Source: dep.AUR, AURBase: ptrString("zfs-utils"), LocalVersion: "2.1.10-1", Version: "2.1.11-1", Upgrade: true, Devel: false, } vcsStore := &vcs.Mock{ToUpgradeReturn: []string{}} mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { if len(query.Needles) == 2 { return []aur.Pkg{ { Name: "zfs-dkms", Version: "2.1.11-1", PackageBase: "zfs-dkms", Depends: []string{"zfs-utils=2.1.11"}, }, {Name: "zfs-utils", Version: "2.1.11-1", PackageBase: "zfs-utils"}, }, nil } if len(query.Needles) == 1 { return []aur.Pkg{ {Name: "zfs-utils", Version: "2.1.11-1", PackageBase: "zfs-utils"}, }, nil } panic("not implemented") }, } type fields struct { input io.Reader noConfirm bool devel bool } type args struct { graph *topo.Graph[string, *dep.InstallInfo] enableDowngrade bool } tests := []struct { name string fields fields args args mustExist map[string]*dep.InstallInfo mustNotExist map[string]bool wantExclude []string wantErr bool remotePackages []string }{ { name: "no input", fields: fields{ input: strings.NewReader("\n"), noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "zfs-dkms": zfsDKMSInfo, "zfs-utils": zfsUtilsInfo, }, remotePackages: []string{"zfs-utils", "zfs-dkms"}, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, { name: "no input - inverted order", fields: fields{ input: strings.NewReader("\n"), noConfirm: false, }, args: args{ graph: nil, enableDowngrade: false, }, mustExist: map[string]*dep.InstallInfo{ "zfs-dkms": zfsDKMSInfo, "zfs-utils": zfsUtilsInfo, }, remotePackages: []string{"zfs-dkms", "zfs-utils"}, mustNotExist: map[string]bool{}, wantErr: false, wantExclude: []string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() dbExe := &mock.DBExecutor{ InstalledRemotePackageNamesFn: func() []string { return tt.remotePackages }, InstalledRemotePackagesFn: func() map[string]mock.IPackage { mapRemote := make(map[string]mock.IPackage) mapRemote["zfs-dkms"] = &mock.Package{ PName: "zfs-dkms", PBase: "zfs-dkms", PVersion: "2.1.10-1", PReason: alpm.PkgReasonExplicit, PDepends: mock.DependList{Depends: []alpm.Depend{ {Name: "zfs-utils", Version: "2.1.10-1"}, }}, } mapRemote["zfs-utils"] = &mock.Package{ PName: "zfs-utils", PBase: "zfs-utils", PVersion: "2.1.10-1", PReason: alpm.PkgReasonDepend, } return mapRemote }, LocalSatisfierExistsFn: func(string) bool { return false }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, SyncUpgradesFn: func(bool) (map[string]db.SyncUpgrade, error) { mapUpgrades := make(map[string]db.SyncUpgrade) return mapUpgrades, nil }, ReposFn: func() []string { return []string{"core"} }, } logger := text.NewLogger(io.Discard, os.Stderr, tt.fields.input, true, "test") grapher := dep.NewGrapher(dbExe, mockAUR, false, true, false, false, false, logger) cfg := &settings.Configuration{ Devel: tt.fields.devel, Mode: parser.ModeAny, } u := &UpgradeService{ log: logger, grapher: grapher, aurCache: mockAUR, dbExecutor: dbExe, vcsStore: vcsStore, cfg: cfg, noConfirm: tt.fields.noConfirm, AURWarnings: query.NewWarnings(logger), } got, err := u.GraphUpgrades(context.Background(), tt.args.graph, tt.args.enableDowngrade, func(*Upgrade) bool { return true }) if (err != nil) != tt.wantErr { t.Errorf("UpgradeService.GraphUpgrades() error = %v, wantErr %v", err, tt.wantErr) return } excluded, err := u.UserExcludeUpgrades(got) require.NoError(t, err) for node, info := range tt.mustExist { assert.True(t, got.Exists(node), node) assert.Equal(t, info, got.GetNodeInfo(node).Value) } for node := range tt.mustNotExist { assert.False(t, got.Exists(node), node) } assert.ElementsMatch(t, tt.wantExclude, excluded) }) } } ================================================ FILE: pkg/upgrade/sources.go ================================================ package upgrade import ( "context" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func UpDevel( ctx context.Context, log *text.Logger, remote map[string]db.IPackage, aurdata map[string]*query.Pkg, localCache vcs.Store, ) UpSlice { toRemove := make([]string, 0) toUpgrade := UpSlice{Up: make([]Upgrade, 0), Repos: []string{"devel"}} for pkgName, pkg := range remote { if localCache.ToUpgrade(ctx, pkgName) { if _, ok := aurdata[pkgName]; !ok { log.Warnln(gotext.Get("ignoring package devel upgrade (no AUR info found):"), pkgName) continue } if pkg.ShouldIgnore() { printIgnoringPackage(log, pkg, "latest-commit") continue } toUpgrade.Up = append(toUpgrade.Up, Upgrade{ Name: pkg.Name(), Base: pkg.Base(), Repository: "devel", LocalVersion: pkg.Version(), RemoteVersion: "latest-commit", Reason: pkg.Reason(), }) } } localCache.RemovePackages(toRemove) return toUpgrade } func printIgnoringPackage(log *text.Logger, pkg db.IPackage, newPkgVersion string) { left, right := query.GetVersionDiff(pkg.Version(), newPkgVersion) pkgName := pkg.Name() log.Warnln(gotext.Get("%s: ignoring package upgrade (%s => %s)", text.Cyan(pkgName), left, right, )) } // UpAUR gathers foreign packages and checks if they have new versions. // Output: Upgrade type package list. func UpAUR(log *text.Logger, remote map[string]db.IPackage, aurdata map[string]*query.Pkg, enableDowngrade bool, ) UpSlice { toUpgrade := UpSlice{Up: make([]Upgrade, 0), Repos: []string{"aur"}} for name, pkg := range remote { aurPkg, ok := aurdata[name] if !ok { continue } if (db.VerCmp(pkg.Version(), aurPkg.Version) < 0) || (enableDowngrade && (db.VerCmp(pkg.Version(), aurPkg.Version) > 0)) { if pkg.ShouldIgnore() { printIgnoringPackage(log, pkg, aurPkg.Version) } else { toUpgrade.Up = append(toUpgrade.Up, Upgrade{ Name: aurPkg.Name, Base: aurPkg.PackageBase, Repository: "aur", LocalVersion: pkg.Version(), RemoteVersion: aurPkg.Version, Reason: pkg.Reason(), }) } } } return toUpgrade } ================================================ FILE: pkg/upgrade/sources_test.go ================================================ //go:build !integration // +build !integration package upgrade import ( "context" "io" "os" "strings" "testing" aur "github.com/Jguer/aur" "github.com/stretchr/testify/assert" alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func Test_upAUR(t *testing.T) { t.Parallel() type args struct { remote map[string]alpm.Package aurdata map[string]*aur.Pkg enableDowngrade bool } tests := []struct { name string args args want UpSlice }{ { name: "No Updates", args: args{ remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "local_pkg": &mock.Package{PName: "local_pkg", PVersion: "1.1.0"}, "ignored": &mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}, }, aurdata: map[string]*aur.Pkg{ "hello": {Version: "2.0.0", Name: "hello"}, "ignored": {Version: "2.0.0", Name: "ignored"}, }, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{}}, }, { name: "Simple Update", args: args{ remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.1.0", Name: "hello"}}, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.1.0"}}}, }, { name: "Downgrade", args: args{ remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "1.0.0", Name: "hello"}}, enableDowngrade: true, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{{Name: "hello", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "1.0.0"}}}, }, { name: "Downgrade Disabled", args: args{ remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "1.0.0", Name: "hello"}}, enableDowngrade: false, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{}}, }, { name: "Mixed Updates Downgrades", args: args{ enableDowngrade: true, remote: map[string]alpm.Package{ "up": &mock.Package{PName: "up", PVersion: "2.0.0"}, "same": &mock.Package{PName: "same", PVersion: "3.0.0"}, "down": &mock.Package{PName: "down", PVersion: "1.1.0"}, "ignored": &mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}, }, aurdata: map[string]*aur.Pkg{ "up": {Version: "2.1.0", Name: "up"}, "same": {Version: "3.0.0", Name: "same"}, "down": {Version: "1.0.0", Name: "down"}, "ignored": {Version: "2.0.0", Name: "ignored"}, }, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{ {Name: "up", Repository: "aur", LocalVersion: "2.0.0", RemoteVersion: "2.1.0"}, {Name: "down", Repository: "aur", LocalVersion: "1.1.0", RemoteVersion: "1.0.0"}, }}, }, { name: "Ignore LastModified When Version Is Unchanged", args: args{ remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello", LastModified: 9999999999}}, }, want: UpSlice{Repos: []string{"aur"}, Up: []Upgrade{}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := UpAUR(text.NewLogger(io.Discard, os.Stderr, strings.NewReader(""), false, "test"), tt.args.remote, tt.args.aurdata, tt.args.enableDowngrade) assert.ElementsMatch(t, tt.want.Repos, got.Repos) assert.ElementsMatch(t, tt.want.Up, got.Up) assert.Equal(t, tt.want.Len(), got.Len()) }) } } func Test_upDevel(t *testing.T) { t.Parallel() type args struct { remote map[string]alpm.Package aurdata map[string]*aur.Pkg cached vcs.Store } tests := []struct { name string args args want UpSlice finalLen int }{ { name: "No Updates", args: args{ cached: &vcs.Mock{}, remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "local_pkg": &mock.Package{PName: "local_pkg", PVersion: "1.1.0"}, "ignored": &mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}, }, aurdata: map[string]*aur.Pkg{ "hello": {Version: "2.0.0", Name: "hello"}, "ignored": {Version: "2.0.0", Name: "ignored"}, }, }, want: UpSlice{Repos: []string{"devel"}}, }, { name: "Simple Update", finalLen: 3, args: args{ cached: &vcs.Mock{ ToUpgradeReturn: []string{"hello", "hello4"}, }, remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "hello2": &mock.Package{PName: "hello2", PVersion: "3.0.0"}, "hello4": &mock.Package{PName: "hello4", PVersion: "4.0.0"}, }, aurdata: map[string]*aur.Pkg{ "hello": {Version: "2.0.0", Name: "hello"}, "hello2": {Version: "2.0.0", Name: "hello2"}, "hello4": {Version: "2.0.0", Name: "hello4"}, }, }, want: UpSlice{ Repos: []string{"devel"}, Up: []Upgrade{ { Name: "hello", Repository: "devel", LocalVersion: "2.0.0", RemoteVersion: "latest-commit", }, { Name: "hello4", Repository: "devel", LocalVersion: "4.0.0", RemoteVersion: "latest-commit", }, }, }, }, { name: "No update returned", finalLen: 1, args: args{ cached: &vcs.Mock{ToUpgradeReturn: []string{}}, remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello"}}, }, want: UpSlice{Repos: []string{"devel"}}, }, { name: "No update returned - ignored", finalLen: 1, args: args{ cached: &vcs.Mock{ ToUpgradeReturn: []string{"hello"}, }, remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0", PShouldIgnore: true}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello"}}, }, want: UpSlice{Repos: []string{"devel"}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := UpDevel(context.Background(), text.NewLogger(io.Discard, os.Stderr, strings.NewReader(""), false, "test"), tt.args.remote, tt.args.aurdata, tt.args.cached) assert.ElementsMatch(t, tt.want.Up, got.Up) }) } } ================================================ FILE: pkg/upgrade/upgrade.go ================================================ package upgrade import ( "fmt" "strings" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) // Filter decides if specific package should be included in the results. type Filter func(*Upgrade) bool // Upgrade type describes a system upgrade. type Upgrade = db.Upgrade func StylizedNameWithRepository(u *Upgrade) string { return text.Bold(text.ColorHash(u.Repository)) + "/" + text.Bold(u.Name) } // upSlice is a slice of Upgrades. type UpSlice struct { Up []Upgrade Repos []string PulledDeps []Upgrade } func (u UpSlice) Len() int { return len(u.Up) } func (u UpSlice) Swap(i, j int) { u.Up[i], u.Up[j] = u.Up[j], u.Up[i] } func (u UpSlice) Less(i, j int) bool { if u.Up[i].Repository == u.Up[j].Repository { iRunes := []rune(u.Up[i].Name) jRunes := []rune(u.Up[j].Name) return text.LessRunes(iRunes, jRunes) } for _, db := range u.Repos { switch db { case u.Up[i].Repository: return true case u.Up[j].Repository: return false } } iRunes := []rune(u.Up[i].Repository) jRunes := []rune(u.Up[j].Repository) return text.LessRunes(iRunes, jRunes) } // calculateFormatting calculates formatting parameters for printing upgrades func calculateFormatting(upgrades []Upgrade) (longestName, longestVersion, longestNumber int) { for i := range upgrades { upgrade := &upgrades[i] packNameLen := len(StylizedNameWithRepository(upgrade)) packVersion, _ := query.GetVersionDiff(upgrade.LocalVersion, upgrade.RemoteVersion) packVersionLen := len(packVersion) longestName = max(packNameLen, longestName) longestVersion = max(packVersionLen, longestVersion) } lenUp := len(upgrades) longestNumber = len(fmt.Sprintf("%v", lenUp)) return } // Print prints the details of the packages to upgrade. func (u UpSlice) Print(logger *text.Logger) { longestName, longestVersion, longestNumber := calculateFormatting(u.Up) namePadding := fmt.Sprintf("%%-%ds ", longestName) versionPadding := fmt.Sprintf("%%-%ds", longestVersion) numberPadding := fmt.Sprintf("%%%dd ", longestNumber) for k := range u.Up { upgrade := &u.Up[k] left, right := query.GetVersionDiff(upgrade.LocalVersion, upgrade.RemoteVersion) logger.Print(text.Magenta(fmt.Sprintf(numberPadding, len(u.Up)-k))) logger.Print(fmt.Sprintf(namePadding, StylizedNameWithRepository(upgrade))) logger.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right) if upgrade.Extra != "" { logger.Println(strings.Repeat(" ", longestNumber), upgrade.Extra) } } } func (u UpSlice) PrintDeps(logger *text.Logger) { longestName, longestVersion, longestNumber := calculateFormatting(u.PulledDeps) namePadding := fmt.Sprintf(" %s%%-%ds ", strings.Repeat(" ", longestNumber), longestName) versionPadding := fmt.Sprintf("%%-%ds", longestVersion) for k := range u.PulledDeps { upgrade := &u.PulledDeps[k] left, right := query.GetVersionDiff(upgrade.LocalVersion, upgrade.RemoteVersion) logger.Printf("%s", fmt.Sprintf(namePadding, StylizedNameWithRepository(upgrade))) logger.Printf("%s -> %s\n", fmt.Sprintf(versionPadding, left), right) if upgrade.Extra != "" { logger.Println(strings.Repeat(" ", longestNumber), strings.ToLower(upgrade.Extra)) } } logger.Println() } ================================================ FILE: pkg/upgrade/upgrade_test.go ================================================ //go:build !integration // +build !integration package upgrade import ( "testing" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/text" ) func TestGetVersionDiff(t *testing.T) { text.UseColor = true type versionPair struct { Old string New string } in := []versionPair{ {"1-1", "1-1"}, {"1-1", "2-1"}, {"2-1", "1-1"}, {"1-1", "1-2"}, {"1-2", "1-1"}, {"1.2.3-1", "1.2.4-1"}, {"1.8rc1+6+g0f377f94-1", "1.8rc1+1+g7e949283-1"}, {"1.8rc1+6+g0f377f94-1", "1.8rc2+1+g7e949283-1"}, {"1.8rc2", "1.9rc1"}, {"2.99.917+812+g75795523-1", "2.99.917+823+gd9bf46e4-1"}, {"1.2.9-1", "1.2.10-1"}, {"1.2.10-1", "1.2.9-1"}, {"1.2-1", "1.2.1-1"}, {"1.2.1-1", "1.2-1"}, {"0.7-4", "0.7+4+gd8d8c67-1"}, {"1.0.2_r0-1", "1.0.2_r0-2"}, {"1.0.2_r0-1", "1.0.2_r1-1"}, {"1.0.2_r0-1", "1.0.3_r0-1"}, } out := []versionPair{ {"1-1" + text.Red(""), "1-1" + text.Green("")}, {text.Red("1-1"), text.Green("2-1")}, {text.Red("2-1"), text.Green("1-1")}, {"1-" + text.Red("1"), "1-" + text.Green("2")}, {"1-" + text.Red("2"), "1-" + text.Green("1")}, {"1.2." + text.Red("3-1"), "1.2." + text.Green("4-1")}, {"1.8rc1+" + text.Red("6+g0f377f94-1"), "1.8rc1+" + text.Green("1+g7e949283-1")}, {"1.8" + text.Red("rc1+6+g0f377f94-1"), "1.8" + text.Green("rc2+1+g7e949283-1")}, {"1." + text.Red("8rc2"), "1." + text.Green("9rc1")}, {"2.99.917+" + text.Red("812+g75795523-1"), "2.99.917+" + text.Green("823+gd9bf46e4-1")}, {"1.2." + text.Red("9-1"), "1.2." + text.Green("10-1")}, {"1.2." + text.Red("10-1"), "1.2." + text.Green("9-1")}, {"1.2" + text.Red("-1"), "1.2" + text.Green(".1-1")}, {"1.2" + text.Red(".1-1"), "1.2" + text.Green("-1")}, {"0.7" + text.Red("-4"), "0.7" + text.Green("+4+gd8d8c67-1")}, {"1.0.2_r0-" + text.Red("1"), "1.0.2_r0-" + text.Green("2")}, {"1.0.2_" + text.Red("r0-1"), "1.0.2_" + text.Green("r1-1")}, {"1.0." + text.Red("2_r0-1"), "1.0." + text.Green("3_r0-1")}, } for i, pair := range in { o, n := query.GetVersionDiff(pair.Old, pair.New) if o != out[i].Old || n != out[i].New { t.Errorf("Test %-2d failed for update: expected (%s => %s) got (%s => %s) %d %d %d %d", i+1, out[i].Old, out[i].New, o, n, len(out[i].Old), len(out[i].New), len(o), len(n)) } } } ================================================ FILE: pkg/vcs/.snapshots/TestInfoStore_Update-simple ================================================ { "hello": { "github.com/jguer/yay.git": { "protocols": [ "git" ], "branch": "master", "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } } } ================================================ FILE: pkg/vcs/mock.go ================================================ package vcs import ( "context" "slices" alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" ) type Mock struct { OriginsByPackage map[string]OriginInfoByURL ToUpgradeReturn []string } func (m *Mock) ToUpgrade(ctx context.Context, pkgName string) bool { return slices.Contains(m.ToUpgradeReturn, pkgName) } func (m *Mock) Update(ctx context.Context, pkgName string, sources []gosrc.ArchString) { } func (m *Mock) Save() error { return nil } func (m *Mock) RemovePackages(pkgs []string) { } func (m *Mock) Load() error { return nil } func (m *Mock) CleanOrphans(pkgs map[string]alpm.Package) { } ================================================ FILE: pkg/vcs/vcs.go ================================================ package vcs import ( "context" "encoding/json" "errors" "fmt" "os" "os/exec" "slices" "strings" "sync" "time" alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) const defaultTimeout = 15 * time.Second type Store interface { // ToUpgrade returns true if the package needs to be updated. ToUpgrade(ctx context.Context, pkgName string) bool // Update updates the VCS info of a package. Update(ctx context.Context, pkgName string, sources []gosrc.ArchString) // RemovePackages removes the VCS info of the packages given as arg if they exist. RemovePackages(pkgs []string) // Clean orphaned VCS info. CleanOrphans(pkgs map[string]alpm.Package) // Load loads the VCS info from disk. Load() error // Save saves the VCS info to disk. Save() error } // InfoStore is a collection of OriginInfoByURL by Package. // Containing a map of last commit SHAs of a repo. type InfoStore struct { OriginsByPackage map[string]OriginInfoByURL FilePath string CmdBuilder exe.GitCmdBuilder mux sync.Mutex logger *text.Logger } // OriginInfoByURL stores the OriginInfo of each origin URL provided. type OriginInfoByURL map[string]OriginInfo // OriginInfo contains the last commit sha of a repo // Example: // // "github.com/Jguer/yay.git": { // "protocols": [ // "https" // ], // "branch": "next", // "sha": "c1171d41467c68ffd3c46748182a16366aaaf87b" // }. type OriginInfo struct { Protocols []string `json:"protocols"` Branch string `json:"branch"` SHA string `json:"sha"` } func NewInfoStore(filePath string, cmdBuilder exe.GitCmdBuilder, logger *text.Logger, ) *InfoStore { infoStore := &InfoStore{ CmdBuilder: cmdBuilder, FilePath: filePath, OriginsByPackage: map[string]OriginInfoByURL{}, mux: sync.Mutex{}, logger: logger, } return infoStore } // GetCommit parses HEAD commit from url and branch. func (v *InfoStore) getCommit(ctx context.Context, url, branch string, protocols []string) string { if len(protocols) > 0 { protocol := protocols[len(protocols)-1] ctxTimeout, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() cmd := v.CmdBuilder.BuildGitCmd(ctxTimeout, "", "ls-remote", protocol+"://"+url, branch) stdout, stderr, err := v.CmdBuilder.Capture(cmd) if err != nil { exitError := &exec.ExitError{} if ok := errors.As(err, &exitError); ok && exitError.ExitCode() == 128 { v.logger.Warnln(gotext.Get("devel check for package failed: '%s' encountered an error", cmd.String()), ": ", stderr) return "" } v.logger.Warnln(gotext.Get("devel check for package failed: '%s' encountered an error", cmd.String()), ": ", err) return "" } split := strings.Fields(stdout) if len(split) < 2 { return "" } commit := split[0] return commit } return "" } func (v *InfoStore) Update(ctx context.Context, pkgName string, sources []gosrc.ArchString) { var wg sync.WaitGroup info := make(OriginInfoByURL) checkSource := func(source gosrc.ArchString) { defer wg.Done() url, branch, protocols := parseSource(source.Value) if url == "" || branch == "" { return } commit := v.getCommit(ctx, url, branch, protocols) if commit == "" { return } v.mux.Lock() info[url] = OriginInfo{ protocols, branch, commit, } v.OriginsByPackage[pkgName] = info v.logger.Debugln(gotext.Get("Found git repo: %s", text.Cyan(url))) if err := v.Save(); err != nil { fmt.Fprintln(os.Stderr, err) } v.mux.Unlock() } for _, source := range sources { wg.Add(1) go checkSource(source) } wg.Wait() } // parseSource returns the git url, default branch and protocols it supports. func parseSource(source string) (url, branch string, protocols []string) { split := strings.Split(source, "::") source = split[len(split)-1] split = strings.SplitN(source, "://", 2) if len(split) != 2 { return "", "", nil } protocols = strings.SplitN(split[0], "+", 2) git := slices.Contains(protocols, "git") protocols = protocols[len(protocols)-1:] if !git { return "", "", nil } split = strings.SplitN(split[1], "#", 2) if len(split) == 2 { secondSplit := strings.SplitN(split[1], "=", 2) if secondSplit[0] != "branch" { // source has #commit= or #tag= which makes them not vcs // packages because they reference a specific point return "", "", nil } if len(secondSplit) == 2 { url = split[0] branch = secondSplit[1] } } else { url = split[0] branch = "HEAD" } url = strings.Split(url, "?")[0] branch = strings.Split(branch, "?")[0] return url, branch, protocols } func (v *InfoStore) ToUpgrade(ctx context.Context, pkgName string) bool { if infos, ok := v.OriginsByPackage[pkgName]; ok { return v.needsUpdate(ctx, infos) } return false } func (v *InfoStore) needsUpdate(ctx context.Context, infos OriginInfoByURL) bool { // used to signal we have gone through all sources and found nothing finished := make(chan struct{}) alive := 0 // if we find an update we use this to exit early and return true hasUpdate := make(chan struct{}) closed := make(chan struct{}) defer close(closed) checkHash := func(url string, info OriginInfo) { hash := v.getCommit(ctx, url, info.Branch, info.Protocols) var sendTo chan<- struct{} if hash != "" && hash != info.SHA { sendTo = hasUpdate } else { sendTo = finished } select { case sendTo <- struct{}{}: case <-closed: } } for url, info := range infos { alive++ go checkHash(url, info) } for { select { case <-hasUpdate: return true case <-finished: alive-- if alive == 0 { return false } } } } func (v *InfoStore) Save() error { marshalledinfo, err := json.MarshalIndent(v.OriginsByPackage, "", "\t") if err != nil || string(marshalledinfo) == "null" { return err } in, err := os.OpenFile(v.FilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { return err } defer in.Close() if _, errM := in.Write(marshalledinfo); errM != nil { return errM } return in.Sync() } // RemovePackage removes package from VCS information. func (v *InfoStore) RemovePackages(pkgs []string) { updated := false for _, pkgName := range pkgs { if _, ok := v.OriginsByPackage[pkgName]; ok { delete(v.OriginsByPackage, pkgName) updated = true } } if updated { if err := v.Save(); err != nil { fmt.Fprintln(os.Stderr, err) } } } // LoadStore reads a json file and populates a InfoStore structure. func (v *InfoStore) Load() error { vfile, err := os.Open(v.FilePath) if !os.IsNotExist(err) && err != nil { return fmt.Errorf("failed to open vcs file '%s': %w", v.FilePath, err) } defer vfile.Close() if !os.IsNotExist(err) { decoder := json.NewDecoder(vfile) if err = decoder.Decode(&v.OriginsByPackage); err != nil { return fmt.Errorf("failed to read vcs '%s': %w", v.FilePath, err) } } return nil } func (v *InfoStore) CleanOrphans(pkgs map[string]alpm.Package) { missing := make([]string, 0) for pkgName := range v.OriginsByPackage { if _, ok := pkgs[pkgName]; !ok { v.logger.Debugln("removing orphaned vcs package:", pkgName) missing = append(missing, pkgName) } } v.RemovePackages(missing) } ================================================ FILE: pkg/vcs/vcs_test.go ================================================ //go:build !integration // +build !integration package vcs import ( "context" "encoding/json" "errors" "io" "os" "os/exec" "strings" "testing" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/bradleyjkemp/cupaloy" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/text" ) func newTestLogger() *text.Logger { return text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), true, "test") } func TestParsing(t *testing.T) { t.Parallel() type source struct { URL string Branch string Protocols []string } urls := []string{ "git+https://github.com/neovim/neovim.git", "git://github.com/jguer/yay.git#branch=master", "git://github.com/davidgiven/ack", "git://github.com/jguer/yay.git#tag=v3.440", "git://github.com/jguer/yay.git#commit=e5470c88c6e2f9e0f97deb4728659ffa70ef5d0c", "a+b+c+d+e+f://github.com/jguer/yay.git#branch=foo", } sources := []source{ {"github.com/neovim/neovim.git", "HEAD", []string{"https"}}, {"github.com/jguer/yay.git", "master", []string{"git"}}, {"github.com/davidgiven/ack", "HEAD", []string{"git"}}, {"", "", nil}, {"", "", nil}, {"", "", nil}, } for n, url := range urls { url, branch, protocols := parseSource(url) compare := sources[n] assert.Equal(t, compare.URL, url) assert.Equal(t, compare.Branch, branch) assert.Equal(t, compare.Protocols, protocols) } } func TestNewInfoStore(t *testing.T) { t.Parallel() type args struct { filePath string cmdBuilder *exe.CmdBuilder } tests := []struct { name string args args }{ { name: "normal", args: args{ "/tmp/a.json", &exe.CmdBuilder{GitBin: "git", GitFlags: []string{"--a", "--b"}, Runner: &exe.OSRunner{ Log: text.NewLogger(io.Discard, os.Stderr, strings.NewReader(""), true, "test"), }}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := NewInfoStore(tt.args.filePath, tt.args.cmdBuilder, text.NewLogger(io.Discard, os.Stderr, strings.NewReader(""), true, "test")) assert.NotNil(t, got) assert.Equal(t, []string{"--a", "--b"}, got.CmdBuilder.(*exe.CmdBuilder).GitFlags) assert.Equal(t, tt.args.cmdBuilder, got.CmdBuilder) assert.Equal(t, "/tmp/a.json", got.FilePath) }) } } type MockRunner struct { Returned []string Index int } func (r *MockRunner) Show(cmd *exec.Cmd) error { return nil } func (r *MockRunner) Capture(cmd *exec.Cmd) (stdout, stderr string, err error) { stdout = r.Returned[r.Index] if r.Returned[0] == "error" { err = errors.New("possible error") } return stdout, stderr, err } func TestInfoStoreToUpgrade(t *testing.T) { t.Parallel() type fields struct { CmdBuilder *exe.CmdBuilder } type args struct { infos OriginInfoByURL } tests := []struct { name string fields fields args args want bool }{ { name: "simple-has_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD"}, }}, }, want: true, }, { name: "double-has_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, "github.com/Jguer/a.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD", }, }}, }, want: true, }, { name: "simple-no_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"991c5b4146fd27f4aacf4e3111258a848934aaa1 HEAD"}, }}, }, want: false, }, { name: "simple-no_split", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }}, }, want: false, }, { name: "simple-error", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{ GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"error"}, }, }, }, want: false, }, { name: "simple-no protocol", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }}, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() v := &InfoStore{ logger: newTestLogger(), CmdBuilder: tt.fields.CmdBuilder, OriginsByPackage: map[string]OriginInfoByURL{ "yay": tt.args.infos, }, } got := v.ToUpgrade(context.Background(), "yay") assert.Equal(t, tt.want, got) }) } } func TestInfoStore_NeedsUpdate(t *testing.T) { t.Parallel() type fields struct { CmdBuilder *exe.CmdBuilder } type args struct { infos OriginInfoByURL } tests := []struct { name string fields fields args args want bool }{ { name: "simple-has_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD"}, }}, }, want: true, }, { name: "double-has_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, "github.com/Jguer/a.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD", }, }}, }, want: true, }, { name: "simple-no_update", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"991c5b4146fd27f4aacf4e3111258a848934aaa1 HEAD"}, }}, }, want: false, }, { name: "simple-no_split", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }}, }, want: false, }, { name: "simple-error", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{"https"}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{ GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"error"}, }, }, }, want: false, }, { name: "simple-no protocol", args: args{infos: OriginInfoByURL{ "github.com/Jguer/z.git": OriginInfo{ Protocols: []string{}, Branch: "0", SHA: "991c5b4146fd27f4aacf4e3111258a848934aaa1", }, }}, fields: fields{ CmdBuilder: &exe.CmdBuilder{GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{ Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, }}, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() v := &InfoStore{ logger: newTestLogger(), CmdBuilder: tt.fields.CmdBuilder, } got := v.needsUpdate(context.Background(), tt.args.infos) assert.Equal(t, tt.want, got) }) } } func TestInfoStore_Update(t *testing.T) { t.Parallel() type fields struct { OriginsByPackage map[string]OriginInfoByURL CmdBuilder *exe.CmdBuilder } type args struct { pkgName string sources []gosrc.ArchString } tests := []struct { name string fields fields args args }{ { name: "simple", args: args{ pkgName: "hello", sources: []gosrc.ArchString{{Value: "git://github.com/jguer/yay.git#branch=master"}}, }, fields: fields{ OriginsByPackage: make(map[string]OriginInfoByURL), CmdBuilder: &exe.CmdBuilder{ GitBin: "git", GitFlags: []string{""}, Runner: &MockRunner{Returned: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HEAD"}}, }, }, }, } file, err := os.CreateTemp("/tmp", "yay-infostore-*-test") filePath := file.Name() require.NoError(t, err) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() v := &InfoStore{ OriginsByPackage: tt.fields.OriginsByPackage, logger: newTestLogger(), FilePath: filePath, CmdBuilder: tt.fields.CmdBuilder, } v.Update(context.Background(), tt.args.pkgName, tt.args.sources) assert.Len(t, tt.fields.OriginsByPackage, 1) marshalledinfo, err := json.MarshalIndent(tt.fields.OriginsByPackage, "", "\t") assert.NoError(t, err) cupaloy.SnapshotT(t, marshalledinfo) v.Load() assert.Len(t, tt.fields.OriginsByPackage, 1) marshalledinfo, err = json.MarshalIndent(tt.fields.OriginsByPackage, "", "\t") assert.NoError(t, err) cupaloy.SnapshotT(t, marshalledinfo) }) } require.NoError(t, os.Remove(filePath)) } func TestInfoStore_Remove(t *testing.T) { t.Parallel() type fields struct { OriginsByPackage map[string]OriginInfoByURL } type args struct { pkgs []string } tests := []struct { name string fields fields args args }{ { name: "simple", args: args{pkgs: []string{"a", "c"}}, fields: fields{ OriginsByPackage: map[string]OriginInfoByURL{ "a": {}, "b": {}, "c": {}, "d": {}, }, }, }, } file, err := os.CreateTemp("/tmp", "yay-vcs-*-test") filePath := file.Name() require.NoError(t, err) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() v := &InfoStore{ OriginsByPackage: tt.fields.OriginsByPackage, logger: newTestLogger(), FilePath: filePath, } v.RemovePackages(tt.args.pkgs) assert.Len(t, tt.fields.OriginsByPackage, 2) }) } require.NoError(t, os.Remove(filePath)) } func TestInfoStore_CleanOrphans(t *testing.T) { t.Parallel() type fields struct { OriginsByPackage map[string]OriginInfoByURL } type args struct { pkgs map[string]db.IPackage } tests := []struct { name string fields fields args args }{ { name: "simple", args: args{pkgs: map[string]db.IPackage{"a": nil, "b": nil, "d": nil}}, fields: fields{ OriginsByPackage: map[string]OriginInfoByURL{ "a": {}, "b": {}, "c": {}, "d": {}, }, }, }, } file, err := os.CreateTemp("/tmp", "yay-vcs-*-test") filePath := file.Name() require.NoError(t, err) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() v := &InfoStore{ OriginsByPackage: tt.fields.OriginsByPackage, FilePath: filePath, logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader(""), false, "test"), } v.CleanOrphans(tt.args.pkgs) assert.Len(t, tt.fields.OriginsByPackage, 3) }) } require.NoError(t, os.Remove(filePath)) } ================================================ FILE: po/ca.po ================================================ # # Translators: # Davidmp , 2025 # msgid "" msgstr "" "Last-Translator: Davidmp , 2025\n" "Language-Team: Catalan (https://app.transifex.com/yay-1/teams/123732/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Directori de construcció:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Introduïu un número (per defecte = 1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Els fitxers de compilació ja existeixen)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Instal·lat)" #: cmd.go:453 msgid " [Installed]" msgstr "[Instal·lat]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "No hi ha res per fer." #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [T]ot [Av]orta [I]nstal·lat [No] instal·lat o (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s ja fet: se n'omet la construcció." #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s no està establert." #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s és present." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s està al dia: s'omet." #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s per actualitzar / instal·lar." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s també s'instal·larà per a aquesta operació." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, requerit per %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: sense canvis, s'omet." #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: no es pot usar l'objectiu amb l'opció --aur, s'omet." #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: no es pot usar l'objectiu amb l'opció --repo, s'omet." #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: s'ignora l'actualització del paquet (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: el paquet local (%s) és més nou que el de l'AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: establiu les variables d'entorn AUR_NOMDUSUARI i AUR_CONTRASENYA per " "votar." #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD baixat de l'ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD baixat: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Ha fallat baixar el PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) S'analitza SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Instal·lat)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Instal·lat: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orfes)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Obsolet: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL de l'AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Afegiu %s o %s a les variables d'entorn." #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Eviteu executar el yay com a root / sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Comprova'n la dependència" #: print.go:41 msgid "Check Deps" msgstr "Comprova les dependències" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Es comproven els paquets de desenvolupament..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Es neteja (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Té conflicte amb" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Se suprimeix (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dependència" #: print.go:38 msgid "Depends On" msgstr "Depèn de" #: print.go:33 msgid "Description" msgstr "Descripció" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Diferències per mostrar?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Desactiva la configuració de \"proporciona\" per defecte." #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Voleu suprimir TOTS els paquets de l'AUR de la memòria cau?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Voleu suprimir TOTS els fitxers de l'AUR sense seguiment?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Voleu suprimir tots els altres paquets de l'AUR de la memòria cau?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Amb què voleu editar el PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Error durant la cerca a l'AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "L'exclusió de paquets pot provocar actualitzacions parcials i trencar els " "sistemes." #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explícit" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paquets instal·lats explícitament: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "No s'ha pogut trobar el paquet de l'AUR per a" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Ha fallat instal·lar la capa. Es passa a la següent." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Ha fallat instal·lar els paquets següents. Cal intervenció manual:" #: print.go:45 msgid "First Submitted" msgstr "Enviat primer" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paquets de l'AUR obsolets marcats:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Paquets forans instal·lats: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "S'ha trobat el repositori git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB ha acabat. No s'ha instal·lat cap paquet." #: print.go:36 msgid "Groups" msgstr "Grups" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Ho importo?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "S'importen claus amb gpg..." #: print.go:46 msgid "Keywords" msgstr "Paraules clau" #: print.go:47 msgid "Last Modified" msgstr "Darrera modificació" #: print.go:35 msgid "Licenses" msgstr "Llicències" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Mantenidor" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Dependència de construcció" #: print.go:40 msgid "Make Deps" msgstr "Dependències de construcció" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Manca" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Manquen paquets de depuració de l'AUR:" #: print.go:31 msgid "Name" msgstr "Nom" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "No s'ha trobat cap paquet d'AUR per a" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "requerit per " #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "No s'ha trobat cap paquet per" #: print.go:225 msgid "None" msgstr "Cap" #: print.go:39 msgid "Optional Deps" msgstr "Dependències opcionals" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paquets d'AUR orfes (no mantinguts):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Obsolet" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Cal importar claus PGP:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD actualitzat, s'omet la baixada: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs per editar?" #: print.go:61 msgid "Package Base" msgstr "Base de paquets" #: print.go:60 msgid "Package Base ID" msgstr "ID de la base de paquets" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paquets no a l'AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paquets per a la neteja de la construcció?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paquets per excloure" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Paquets per excloure: (p. ex.: \"1 2 3\", \"1-3\", \"^4\" o nom del repositori)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paquets per instal·lar (p. ex.: 1 2 3, 1-3 o ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularitat" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Voleu continuar la instal·lació?" #: print.go:37 msgid "Provides" msgstr "Proporciona" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "" "Suprimeixo les dependències de construcció després de la instal·lació?" #: print.go:43 msgid "Replaces" msgstr "Reemplaça" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositori" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositori de l'AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Se cerquen actualitzacions a l'AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Se cerquen actualitzacions a les bases de dades..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Es mostren només paquets dels repositoris." #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Mida de la memòria cau del pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Mida de la memòria cau del yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL de la instantània" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronització" #: print.go:100 msgid "Ten biggest packages:" msgstr "Els deu paquets més grossos:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Els paquets següents no són compatibles amb la vostra arquitectura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Hi ha %[1]d proveïdors disponibles per a %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Pot ser que hi hagi una altra instància del Pacman en execució. S'espera..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Mida total ocupada pels paquets: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Total de paquets instal·lats: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Intento construir-los tanmateix?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "No es pot netejar:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "No s'han pogut trobar els paquets següents:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "No es pot gestionar el vot del paquet per a %s. Error: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "No es pot suprimir %s: %s" #: print.go:32 msgid "Version" msgstr "Versió" #: print.go:50 msgid "Votes" msgstr "Vots" #: print.go:87 msgid "Yay version v%s" msgstr "Versió del yay: v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "Ca[p]" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "s'avorta a causa de l'usuari" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' especificat sense entrada a stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "no es pot trobar PKGBUILD i SRCINFO al directori" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "no es pot trobar el nom del paquet: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "no s'ha pogut trobar PKGDEST per a %s" #: errors.go:9 msgid "could not find all required packages" msgstr "no s'han pogut trobar tots els paquets necessaris" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "no s'ha pogut trobar cap arxiu de paquets llistat a %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dependència" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "la comprovació del paquet ha fallat: %s ha trobat un error" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "l'editor no ha sortit correctament, s'avorta: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "error en baixar les fonts: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "error en obtenir %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "error en instal·lar paquets dels repositoris" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "error d'instal·lació:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "error de construcció: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "error en combinar %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "error en llegir %s" #: sync.go:36 msgid "error refreshing databases" msgstr "error en actualitzar les bases de dades" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "error en restablir %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "error en actualitzar el motiu d'instal·lació del paquet a %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explícit" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "ha fallat crear el directori %s: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "ha fallat obrir el fitxer de configuració %s: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "ha fallat analitzar %s, s'omet: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "no s'ha pogut analitzar %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "ha fallat analitzar .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "ha fallat llegir el fitxer de configuració %s: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "ha fallat obtenir la cau de l'AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "s'ignora l'actualització del paquet de desenvolupament (no se n'ha trobat " "cap informació a l'AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "entrada massa llarga" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "número no vàlid: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opció no vàlida: %s" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "opció no vàlida: \"--deps\" i \"--explicit\" no es poden usar juntes" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositori no vàlid" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valor no vàlid: %d no és entre %d i %d" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "sense claus per importar" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "no s'ha executat cap consulta" #: local_install.go:66 msgid "no target directories specified" msgstr "no s'han especificat directoris de destinació" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "no hi ha res per instal·lar per %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "només es pot usar una operació alhora" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "només es permet una destinació" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paquet" msgstr[1] "paquets" #: print.go:187 msgid "package '%s' was not found" msgstr "no s'ha trobat el paquet %s" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paquet no trobat a l'AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paquet no trobat als repositoris" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema d'importació de claus" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "se suprimeixen paquets de l'AUR de la memòria cau..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "" "se suprimeixen els fitxers de l'AUR sense seguiment de la memòria cau..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "el PKGDEST per a %s està llistat per makepkg però no existeix:%s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "No hi ha res per fer." #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "no se'n pot crear el maneig: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operació no manejada" #: cmd.go:450 msgid "unknown-version" msgstr "versió desconeguda" #: pkg/text/input.go:47 msgid "yes" msgstr "sí" ================================================ FILE: po/ca_ES.po ================================================ # # Translators: # Davidmp , 2025 # msgid "" msgstr "" "Last-Translator: Davidmp , 2025\n" "Language-Team: Catalan (Spain) (https://app.transifex.com/yay-1/teams/123732/ca_ES/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca_ES\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Directori de construcció:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Introduïu un número (per defecte = 1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Els fitxers de compilació ja existeixen)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Instal·lat)" #: cmd.go:453 msgid " [Installed]" msgstr "[Instal·lat]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "No hi ha res per fer." #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [T]ot [Av]orta [I]nstal·lat [No] instal·lat o (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s ja fet: se n'omet la construcció." #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s no està establert." #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s és present." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s està al dia: s'omet." #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s per actualitzar / instal·lar." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s també s'instal·larà per a aquesta operació." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, requerit per %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: sense canvis, s'omet." #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: no es pot usar l'objectiu amb l'opció --aur, s'omet." #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: no es pot usar l'objectiu amb l'opció --repo, s'omet." #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: s'ignora l'actualització del paquet (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: el paquet local (%s) és més nou que el de l'AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: establiu les variables d'entorn AUR_NOMDUSUARI i AUR_CONTRASENYA per " "votar." #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD baixat de l'ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD baixat: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Ha fallat baixar el PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) S'analitza SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Instal·lat)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Instal·lat: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orfes)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Obsolet: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL de l'AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Afegiu %s o %s a les variables d'entorn." #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Eviteu executar el yay com a root / sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Comprova'n la dependència" #: print.go:41 msgid "Check Deps" msgstr "Comprova les dependències" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Es comproven els paquets de desenvolupament..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Es neteja (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Té conflicte amb" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Se suprimeix (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dependència" #: print.go:38 msgid "Depends On" msgstr "Depèn de" #: print.go:33 msgid "Description" msgstr "Descripció" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Diferències per mostrar?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Desactiva la configuració de \"proporciona\" per defecte." #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Voleu suprimir TOTS els paquets de l'AUR de la memòria cau?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Voleu suprimir TOTS els fitxers de l'AUR sense seguiment?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Voleu suprimir tots els altres paquets de l'AUR de la memòria cau?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Amb què voleu editar el PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Error durant la cerca a l'AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "L'exclusió de paquets pot provocar actualitzacions parcials i trencar els " "sistemes." #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explícit" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paquets instal·lats explícitament: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "No s'ha pogut trobar el paquet de l'AUR per a" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Ha fallat instal·lar la capa. Es passa a la següent." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Ha fallat instal·lar els paquets següents. Cal intervenció manual:" #: print.go:45 msgid "First Submitted" msgstr "Enviat primer" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paquets de l'AUR obsolets marcats:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Paquets forans instal·lats: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "S'ha trobat el repositori git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB ha acabat. No s'ha instal·lat cap paquet." #: print.go:36 msgid "Groups" msgstr "Grups" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Ho importo?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "S'importen claus amb gpg..." #: print.go:46 msgid "Keywords" msgstr "Paraules clau" #: print.go:47 msgid "Last Modified" msgstr "Darrera modificació" #: print.go:35 msgid "Licenses" msgstr "Llicències" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Mantenidor" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Dependència de construcció" #: print.go:40 msgid "Make Deps" msgstr "Dependències de construcció" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Manca" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Manquen paquets de depuració de l'AUR:" #: print.go:31 msgid "Name" msgstr "Nom" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "No s'ha trobat cap paquet d'AUR per a" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "requerit per " #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "No s'ha trobat cap paquet per" #: print.go:225 msgid "None" msgstr "Cap" #: print.go:39 msgid "Optional Deps" msgstr "Dependències opcionals" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paquets d'AUR orfes (no mantinguts):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Obsolet" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Cal importar claus PGP:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD actualitzat, s'omet la baixada: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs per editar?" #: print.go:61 msgid "Package Base" msgstr "Base de paquets" #: print.go:60 msgid "Package Base ID" msgstr "ID de la base de paquets" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paquets no a l'AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paquets per a la neteja de la construcció?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paquets per excloure" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Paquets per excloure: (p. ex.: \"1 2 3\", \"1-3\", \"^4\" o nom del repositori)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paquets per instal·lar (p. ex.: 1 2 3, 1-3 o ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularitat" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Voleu continuar la instal·lació?" #: print.go:37 msgid "Provides" msgstr "Proporciona" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "" "Suprimeixo les dependències de construcció després de la instal·lació?" #: print.go:43 msgid "Replaces" msgstr "Reemplaça" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositori" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositori de l'AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Se cerquen actualitzacions a l'AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Se cerquen actualitzacions a les bases de dades..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Es mostren només paquets dels repositoris." #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Mida de la memòria cau del pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Mida de la memòria cau del yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL de la instantània" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronització" #: print.go:100 msgid "Ten biggest packages:" msgstr "Els deu paquets més grossos:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Els paquets següents no són compatibles amb la vostra arquitectura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Hi ha %[1]d proveïdors disponibles per a %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Pot ser que hi hagi una altra instància del Pacman en execució. S'espera..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Mida total ocupada pels paquets: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Total de paquets instal·lats: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Intento construir-los tanmateix?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "No es pot netejar:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "No s'han pogut trobar els paquets següents:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "No es pot gestionar el vot del paquet per a %s. Error: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "No es pot suprimir %s: %s" #: print.go:32 msgid "Version" msgstr "Versió" #: print.go:50 msgid "Votes" msgstr "Vots" #: print.go:87 msgid "Yay version v%s" msgstr "Versió del yay: v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "Ca[p]" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "s'avorta a causa de l'usuari" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' especificat sense entrada a stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "no es pot trobar PKGBUILD i SRCINFO al directori" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "no es pot trobar el nom del paquet: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "no s'ha pogut trobar PKGDEST per a %s" #: errors.go:9 msgid "could not find all required packages" msgstr "no s'han pogut trobar tots els paquets necessaris" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "no s'ha pogut trobar cap arxiu de paquets llistat a %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dependència" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "la comprovació del paquet ha fallat: %s ha trobat un error" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "l'editor no ha sortit correctament, s'avorta: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "error en baixar les fonts: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "error en obtenir %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "error en instal·lar paquets dels repositoris" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "error d'instal·lació:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "error de construcció: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "error en combinar %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "error en llegir %s" #: sync.go:36 msgid "error refreshing databases" msgstr "error en actualitzar les bases de dades" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "error en restablir %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "error en actualitzar el motiu d'instal·lació del paquet a %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explícit" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "ha fallat crear el directori %s: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "ha fallat obrir el fitxer de configuració %s: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "ha fallat analitzar %s, s'omet: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "no s'ha pogut analitzar %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "ha fallat analitzar .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "ha fallat llegir el fitxer de configuració %s: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "ha fallat obtenir la cau de l'AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "s'ignora l'actualització del paquet de desenvolupament (no se n'ha trobat " "cap informació a l'AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "entrada massa llarga" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "número no vàlid: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opció no vàlida: %s" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "opció no vàlida: \"--deps\" i \"--explicit\" no es poden usar juntes" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositori no vàlid" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valor no vàlid: %d no és entre %d i %d" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "sense claus per importar" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "no s'ha executat cap consulta" #: local_install.go:66 msgid "no target directories specified" msgstr "no s'han especificat directoris de destinació" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "no hi ha res per instal·lar per %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "només es pot usar una operació alhora" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "només es permet una destinació" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paquet" msgstr[1] "paquets" #: print.go:187 msgid "package '%s' was not found" msgstr "no s'ha trobat el paquet %s" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paquet no trobat a l'AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paquet no trobat als repositoris" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema d'importació de claus" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "se suprimeixen paquets de l'AUR de la memòria cau..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "" "se suprimeixen els fitxers de l'AUR sense seguiment de la memòria cau..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "el PKGDEST per a %s està llistat per makepkg però no existeix:%s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "No hi ha res per fer." #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "no se'n pot crear el maneig: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operació no manejada" #: cmd.go:450 msgid "unknown-version" msgstr "versió desconeguda" #: pkg/text/input.go:47 msgid "yes" msgstr "sí" ================================================ FILE: po/cs.po ================================================ # # Translators: # Fjuro Fjuro, 2022 # a30a45d5047eb877c4dc397ded846f36_1de9be1, 2023 # Matyáš Černý, 2023 # walken, 2024 # msgid "" msgstr "" "Last-Translator: walken, 2024\n" "Language-Team: Czech (https://app.transifex.com/yay-1/teams/123732/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Soubory sestavení existují)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Nainstalováno)" #: cmd.go:453 msgid " [Installed]" msgstr "[Nainstalováno]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "není co dělat" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Vše [Ab]Zrušit [I]Nainstalováno [No]Nenainstalováno nebo (1 2 3, 1-3, " "^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%sjiž existuje -- přeskakuji sestavení" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s není nastaven" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s je přítomný" #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s je aktualizován -- přeskakuji" #: pkg/upgrade/service.go:292 msgid "%s to upgrade/install." msgstr " %s k povýšení/instalaci." #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "%s bude také nainstalován pro tuto operaci. " #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, vyžadován balíčkem %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Žádné změny -- přeskakuji" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: nelze použít cíl s možností --aur -- přeskakuji" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: nelze použít cíl s možností --repo -- přeskakuji" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ignoruji aktualizaci balíčku (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: místní (%s) je novější než AUR (%s) " #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "prosím nastavte proměnné prostředí AUR_USERNAME a AUR_PASSWORD pro hodnocení" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Stažen PKGBUILD z ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Stažen PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Nepodařilo se stáhnout PKGBUILD\\: %s " #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Parsování SRCINFO: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(Nainstalováno)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(Nainstalováno: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Osamocené)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Zastaralé: %s)" #: print.go:44 msgid "AUR URL" msgstr "URL AUR" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Přidejte %s nebo %s do svých proměnných prostředí " #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Vyhněte se spuštění yay jako root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Zkontrolovat závislost" #: print.go:41 msgid "Check Deps" msgstr "Zkontrolovat závislosti" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "Kontrola vývojových balíčků..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Čištění (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Konflikt s" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Mazání (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Závislost" #: print.go:38 msgid "Depends On" msgstr "Závisí na" #: print.go:33 msgid "Description" msgstr "Popis" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Zobrazit rozdíly?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Zákaz nastavení 'provides' jako výchozí" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Chcete odstranit VŠECHNY balíčky AUR z mezipaměti?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Chcete odstranit VŠECHNY nesledované soubory AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Chcete odstranit všechny ostatní balíčky AUR z mezipaměti?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Upravit PKGBUILD pomocí?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Chyby při hledání v AUR: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Vyjímání balíčků může způsobovat částečné aktualizace a rozbíjet systémy." #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explicitní" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Explicitně nainstalované balíčky: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Chyba hledání balíčku AUR:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Chyba instalace vrstvy, zabaluji k další vrstvě." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Selhala instalace těchto balíčků. Ruční zásah je vyžadován." #: print.go:45 msgid "First Submitted" msgstr "První odeslané" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "Označené zastaralé balíčky AUR:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Neznámé nainstalované balíčky: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Nalezen repozitář git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB dokončena. Nebyly nainstalovány žádné balíčky" #: print.go:36 msgid "Groups" msgstr "Skupiny" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importovat?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importování klíčů pomocí gpg..." #: print.go:46 msgid "Keywords" msgstr "Klíčová slova" #: print.go:47 msgid "Last Modified" msgstr "Naposledy upraveno" #: print.go:35 msgid "Licenses" msgstr "Licence" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Místní" #: print.go:48 msgid "Maintainer" msgstr "Správce" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Závislosti pro sestavení" #: print.go:40 msgid "Make Deps" msgstr "Vytvořit závislosti" #: pkg/query/aur_warnings.go:71 msgid "Missing AUR Debug Packages:" msgstr "Chybějící ladicí balíčky AUR:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Chybějící" #: print.go:31 msgid "Name" msgstr "Název" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "AUR balíček nenalezen pro" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nebyly nalezeny žádné balíčky pro" #: print.go:225 msgid "None" msgstr "Žádné" #: print.go:39 msgid "Optional Deps" msgstr "Volitelné závislosti" #: pkg/query/aur_warnings.go:75 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Osamocené (neudržované) balíčky AUR:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Zastaralé" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Je třeba importovat klíče PGP:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD je aktuální, přeskakuji: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDy k upravení?" #: print.go:60 msgid "Package Base ID" msgstr "Základní ID aplikace" #: print.go:61 msgid "Package Base" msgstr "Základ aplikace" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "Balíčky mimo AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Balíčky ke cleanBuild?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Balíčky k vyjmutí." #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Balíčky k vyloučení: (např.: \"1 2 3\", \"1-3\", \"^4\" nebo název repozitáře)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Balíčky k instalaci (např.: 1 2 3, 1-3 nebo ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularita" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Pokračovat v instalaci?" #: print.go:37 msgid "Provides" msgstr "Poskytuje" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Odstranit vytvořené závislosti po instalaci?" #: print.go:43 msgid "Replaces" msgstr "Nahrazuje" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repozitář AUR" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "Repozitář" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "Prohledávání AUR pro aktualizace..." #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "Prohledávání databází k aktualizaci..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Zobrazování pouze balíčků repozitáře" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Velikost mezipaměti pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Velikost mezipaměti yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL snapshotu" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchronizace" #: print.go:100 msgid "Ten biggest packages:" msgstr "Deset největších balíčků" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Následující balíčky nejsou kompatibilní s vaší architekturou:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Pro %[2]s je dostupných %[1]d poskytovatelů:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Možná běží další instance Pacman. Čekání..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Celková velikost zabraná balíčky: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Celkem nainstalovaných balíčků: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Pokusit se je přesto sestavit?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Nepodařilo se vyčistit:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Nepodařilo se nalézt následující balíčky:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Nelze hlasovat pro balíček: %s. chyba: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Nelze odebrat %s:%s" #: print.go:32 msgid "Version" msgstr "Verze" #: print.go:50 msgid "Votes" msgstr "Hlasy" #: print.go:87 msgid "Yay version v%s" msgstr "Verze yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Žádné" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Adresář sestavení:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Zadejte číslo (výchozí=1):" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "rušení kvůli uživateli" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' použit bez vstupu na stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "PKGBUILD a .SRCINFO nebyly nalezeny ve složce" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nepodařilo se nalézt název balíčku: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "nepodařilo se nalézt PKGDEST pro: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "Nepodařilo se nalézt všechny požadované balíčky" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "nenalezeny žádné archivy balíčků v %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 msgid "dependency" msgstr "závislost" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "kontrola devel balíčku selhala: '%s' narazil na chybu" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editor nebyl úspěšně ukončen, rušení: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "chyba při stahování zdrojů: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "chyba při načítání %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "chyba při instalaci balíčků repozitáře" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "chyba při instalaci:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "chyba při vytváření: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "chyba při slučování %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "chyba při čtení %s" #: sync.go:36 msgid "error refreshing databases" msgstr "chyba při obnovování databází" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "chyba při resetování %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "chyba při aktualizaci důvodu instalace balíčku na" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explicitní" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "nepodařilo se vytvořit adresář '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "nepodařilo se otevřít konfigurační soubor '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "nepodařilo se parsovat %s -- přeskakuji: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "nepodařilo se parsovat %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "nepodařilo se parsovat .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "nepodařilo se přečíst konfigurační soubor '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "nepodařilo se získat aur Cache" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignoruji vývojovou aktualizaci balíčku (AUR informace nenalezeny)" #: pkg/text/errors.go:8 msgid "input too long" msgstr "vstup příliš dlouhý" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "neplatné číslo: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "neplatná možnost '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "neplatná možnost: '--deps' a '--explicit' nemohou být použity současně" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "neplatný repozitář" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "neplatná hodnota: %d není mezi %d a %d" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "žádné klíče k importování" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nebyl vykonán žádný dotaz" #: local_install.go:66 msgid "no target directories specified" msgstr "nebyly specifikovány cílové adresáře" #: pkg/text/input.go:48 msgid "no" msgstr "ne" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nic k nainstalování pro %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "naráz může být použita pouze jedna operace" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "je dovolený pouze jeden cíl" #: print.go:187 msgid "package '%s' was not found" msgstr "balíček '%s' nebyl nalezen" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "balíček nenalezen v AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "balíček nenalezen v repozitářích" #: pkg/upgrade/service.go:292 msgid "package" msgid_plural "packages" msgstr[0] "balíček" msgstr[1] "balíčky" msgstr[2] "balíčky" msgstr[3] "balíčky" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problém při importování klíčů" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "odstraňování balíčků AUR z mezipaměti..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "odstraňování nesledovaných souborů AUR z mezipaměti..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST pro %s je v seznamu makepkg ale neexistuje: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "není co dělat" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "nepodařilo se CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "neznámá operace" #: cmd.go:450 msgid "unknown-version" msgstr "neznámá-verze" #: pkg/text/input.go:47 msgid "yes" msgstr "ano" ================================================ FILE: po/de.po ================================================ # # Translators: # Julian Neupert , 2021 # Chris Boesch, 2021 # Oliver Conzen, 2021 # J G, 2022 # Andre Bouillet, 2022 # cf9fef41dd781c154ed35b0d2df25c28_1d7ee01, 2022 # Lukas Müller (Lukas), 2022 # Arne Brücher, 2023 # Ananas 77, 2023 # 452f4d2d2d15be3f170375e4fa84f9da_7bd63b3, 2023 # Lukas Esc, 2023 # Manuel Schneider, 2023 # Marethyu _, 2023 # Severin Hamader , 2025 # 0x3C50 No, 2026 # msgid "" msgstr "" "Last-Translator: 0x3C50 No, 2026\n" "Language-Team: German (https://app.transifex.com/yay-1/teams/123732/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Build-Verzeichnis:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Geben Sie eine Zahl ein (Vorgabe=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Build-Dateien sind vorhanden)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Installiert)" #: cmd.go:453 msgid " [Installed]" msgstr "[Installiert]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " es gibt nichts zu tun" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]lle [Ab]brechen [I]nstalliert [No]nicht installiert oder (1 2 3, 1-3, " "^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s Bereits erledigt -- Build wird übersprungen" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s ist nicht definiert" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s ist vorhanden." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s ist bereits aktuell -- wird übersprungen" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s zu upgraden/installieren." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s wird für diesen Vorgang ebenfalls installiert." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, benötigt von: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Keine Änderungen -- wird übersprungen" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: Argument --aur nicht möglich -- wird übersprungen" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: Argument --repo nicht möglich -- wird übersprungen" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: Ignoriere Paketaktualisierung (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: lokales (%s) ist neuer als das AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "Bitte AUR_USERNAME und AUR_PASSWORD Umgebungsvariablen setzen um abzustimmen" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD von ABS heruntergeladen: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD heruntergeladen: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Es wurde nicht geschaft PKGBUILD zu aktualisieren: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO geparst: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Installiert)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Installiert: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Verwaist)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Veraltet: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR-URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "%s oder %s zu den Umgebungsvariablen hinzufügen" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Vermeide es yay als root/sudo zu nutzen!" #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Überprüfe Abhängigkeiten" #: print.go:41 msgid "Check Deps" msgstr "Prüfe Abhängigkeiten" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Prüfe Entwicklungspakete..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Säubere (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Ist in Konflikt mit" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Lösche (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Abhängigkeit" #: print.go:38 msgid "Depends On" msgstr "Ist abhängig von" #: print.go:33 msgid "Description" msgstr "Beschreibung" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Unterschiede zeigen?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "'provides' Einstellung automatisch abschalten" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Wollen Sie ALLE AUR-Pakete aus dem Cache entfernen?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Wollen Sie ALLE nicht überwachten AUR-Dateien entfernen?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Wollen Sie alle anderen AUR-Pakete aus dem Cache entfernen?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "PKGBUILD editieren mit?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Fehler während AUR-Suche: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Das Ausschließen von Paketen kann zu teilweisen Aktualisierungen führen und " "Systeme beschädigen." #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explizit" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Explizit installierte Pakete: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "AUR-Paket nicht gefunden für" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" "Die Installationsschicht ist fehlgeschlagen, es wird zur nächsten Schicht " "übergegangen." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Die folgenden Pakete konnten nicht installiert werden. Ein manueller " "Eingriff ist erforderlich:" #: print.go:45 msgid "First Submitted" msgstr "Erstmals eingereicht" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Als nicht aktuell markierte AUR-Pakete:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Installierte Fremdpakete: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Git-Repo gefunden: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB beendet. Es wurden keine Pakete installiert" #: print.go:36 msgid "Groups" msgstr "Gruppen" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importieren?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importiere Schlüssel mit gpg..." #: print.go:46 msgid "Keywords" msgstr "Stichworte" #: print.go:47 msgid "Last Modified" msgstr "Zuletzt geändert" #: print.go:35 msgid "Licenses" msgstr "Lizenzen" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokal" #: print.go:48 msgid "Maintainer" msgstr "Betreuer" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Erstelle Abhängigkeit." #: print.go:40 msgid "Make Deps" msgstr "Abhängigkeiten herstellen" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Fehlend" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Fehlende AUR Debug-Pakete: " #: print.go:31 msgid "Name" msgstr "Name" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Kein AUR-Paket gefunden für" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "benötigt von" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Kein gefunden für" #: print.go:225 msgid "None" msgstr "Keine" #: print.go:39 msgid "Optional Deps" msgstr "Optionale Abhängigkeiten" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Verwaiste (nicht gepflegte) AUR-Pakete:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Veraltet" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP-Schlüssel müssen importiert werden: " #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD ist auf dem neuesten Stand, überspringe Herunterladen: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Welche PKGBUILDs editieren?" #: print.go:61 msgid "Package Base" msgstr "Paketbasis" #: print.go:60 msgid "Package Base ID" msgstr "Paket-Basis-ID" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pakete nicht im AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Pakete neu erstellen?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Auszuschließende Pakete" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Pakete zum Ausschließen: (z.B. \"1 2 3\", \"1-3\", \"^4\" oder Repo-Name)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Zu installierende Pakete: (z.B. 1 2 3, 1-3 oder ^4)" #: print.go:49 msgid "Popularity" msgstr "Beliebtheit" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Mit der Installation fortfahren?" #: print.go:37 msgid "Provides" msgstr "Bietet" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Abhängigkeiten nach der Installation entfernen?" #: print.go:43 msgid "Replaces" msgstr "Ersetzt" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repository" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repository AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Durchsuche AUR nach Updates..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Durchsuche Datenbanken nach Updates..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Zeige nur Repo-Pakete" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Größe des pacman-Cache %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Größe des yay-Cache %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Snapshot-URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchronisieren" #: print.go:100 msgid "Ten biggest packages:" msgstr "Die zehn größten Pakete:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Die folgenden Pakete sind mit Ihrer Architektur inkompatibel:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Es sind %[1]d Anbieter verfügbar für %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Eine andere Instanz von Pacman ist aktiv. Warte..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Gesamtgröße der installierten Pakete: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Insgesamt installierte Pakete: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Build trotzdem versuchen?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Kann nicht entfernen:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Konnte die folgenden Pakete nicht finden: " #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Paketabstimmung nicht möglich für:%s. Fehler: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Kann nicht entfernt werden %s: %s" #: print.go:32 msgid "Version" msgstr "Version" #: print.go:50 msgid "Votes" msgstr "Stimmen" #: print.go:87 msgid "Yay version v%s" msgstr "Yay-Version v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N] Keine" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "Abbruch durch den Benutzer" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "Argument '-' ohne Eingabe in stdin angegeben" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "kann PKGBUILD und .SRCINFO im Verzeichnis nicht finden" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "Kann Paketname nicht finden: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "Kann PKGDEST nicht finden für: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "konnte nicht alle erforderlichen Pakete finden" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "konnte keine Paketarchive finden ist nicht aufgeführt in %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "Abhängigkeit" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "Entwicklungstest fehlgeschlagen für Paket: '%s' Fehler gefunden." #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "Editor wurde nicht erfogreich beendet, breche ab: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "Fehler beim Herunterladen der Quellen: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "Fehler beim Abrufen von %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "Fehler bei der Installation der Repo-Pakete" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "Fehler bei der Installation: " #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "Fehler beim Erstellen: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "Fehler beim Zusammenführen von %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "Fehler beim Lesen von %s" #: sync.go:36 msgid "error refreshing databases" msgstr "Fehler beim Aktualisieren der Datenbanken" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "Fehler beim Zurücksetzen von %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "Fehler bei der Aktualisierung der Paket-Installation aufgrund von %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explizit" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "Fehler beim Erstellen von Verzeichnis '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "Fehler beim Öffnen der Konfigurationsdatei '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "Fehler beim Parsen von %s -- überspringe: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "Fehler beim Parsen von: %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "Fehler von Parsen von .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "Fehler beim Lesen der Konfigurationsdatei '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "Erhalten des AUR Caches ist fehlgeschlagen" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignoriere devel Paket Aktualisierung (keine AUR Info gefunden):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "Eingabe zu lang" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "Ungültige Nummer: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "Ungültige Option '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "Ungültige Option: '--deps' und '--explicit' können nicht zusammen genutzt " "werden" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "Ungültiges Repository" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "Ungültiger Wert: %d ist nicht zwischen %d und %d" #: pkg/text/input.go:48 msgid "no" msgstr "nein" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "Keine Schlüssel zu importieren" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "Es wurde keine Abfrage ausgeführt" #: local_install.go:66 msgid "no target directories specified" msgstr "Kein Ziel Verzeichnis angegeben" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nichts zu installieren für %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "Nur eine Operation kann gleichzeitig benutzt werden" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "Nur ein Ziel ist erlaubt" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "Paket" msgstr[1] "Pakete" #: print.go:187 msgid "package '%s' was not found" msgstr "Paket '%s' konnte nicht gefunden werden" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "Paket nicht im AUR gefunden" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "Paket nicht in den Repos gefunden" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "Problem beim Importieren der Schlüssel" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "Entferne AUR-Pakete aus dem Cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "Entferne nicht überwachte AUR-Dateien aus dem Cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST für %s ist in MAKEPKG aufgeführt, existiert aber nicht: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "es gibt nichts zu tun" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "Kann CreateHandle nicht erstellen: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "Unbehandelte Operation" #: cmd.go:450 msgid "unknown-version" msgstr "Unbekannte Version" #: pkg/text/input.go:47 msgid "yes" msgstr "ja" ================================================ FILE: po/en.po ================================================ msgid "" msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: \n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" #: pkg/db/ialpm/alpm.go:201 #: pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "" #: cmd.go:453 msgid " [Installed]" msgstr "" #: cmd.go:410 #: vote.go:36 msgid " there is nothing to do" msgstr "" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "" #: pkg/dep/dep_graph.go:460 #: pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "" #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "" #: vote.go:51 msgid "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for voting" msgstr "" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "" #: pkg/download/aur.go:92 #: pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "" #: pkg/query/types.go:103 #: pkg/query/types.go:72 msgid "(Installed)" msgstr "" #: pkg/query/types.go:101 #: pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "" #: print.go:44 msgid "AUR URL" msgstr "" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "" #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "" #: print.go:41 msgid "Check Deps" msgstr "" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "" #: print.go:42 msgid "Conflicts With" msgstr "" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "" #: print.go:38 msgid "Depends On" msgstr "" #: print.go:33 msgid "Description" msgstr "" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "" #: pkg/dep/dep_graph.go:437 #: pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" #: pkg/sync/build/errors.go:16 msgid "Failed to install the following packages. Manual intervention is required:" msgstr "" #: print.go:45 msgid "First Submitted" msgstr "" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "" #: print.go:36 msgid "Groups" msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "" #: print.go:46 msgid "Keywords" msgstr "" #: print.go:47 msgid "Last Modified" msgstr "" #: print.go:35 msgid "Licenses" msgstr "" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "" #: print.go:48 msgid "Maintainer" msgstr "" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "" #: print.go:40 msgid "Make Deps" msgstr "" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "" #: print.go:31 msgid "Name" msgstr "" #: pkg/dep/dep_graph.go:442 #: pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "" #: print.go:225 msgid "None" msgstr "" #: print.go:39 msgid "Optional Deps" msgstr "" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "" #: print.go:53 #: print.go:55 msgid "Out-of-date" msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "" #: print.go:61 msgid "Package Base" msgstr "" #: print.go:60 msgid "Package Base ID" msgstr "" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "" #: print.go:49 msgid "Popularity" msgstr "" #: pkg/menus/diff_menu.go:172 #: pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "" #: print.go:37 msgid "Provides" msgstr "" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "" #: print.go:43 msgid "Replaces" msgstr "" #: pkg/db/ialpm/alpm.go:191 #: print.go:30 msgid "Repository" msgstr "" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "" #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "" #: print.go:62 msgid "Snapshot URL" msgstr "" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "" #: print.go:100 msgid "Ten biggest packages:" msgstr "" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "" #: pkg/db/ialpm/alpm.go:179 #: pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "" #: print.go:89 msgid "Total installed packages: %s" msgstr "" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "" #: print.go:34 msgid "URL" msgstr "" #: clean.go:194 #: pkg/menus/clean_menu.go:65 #: pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "" #: get.go:42 #: get.go:74 msgid "Unable to find the following packages:" msgstr "" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "" #: print.go:32 msgid "Version" msgstr "" #: print.go:50 msgid "Votes" msgstr "" #: print.go:87 msgid "Yay version v%s" msgstr "" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "" #: errors.go:9 msgid "could not find all required packages" msgstr "" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "" #: pkg/sync/build/errors.go:50 #: pkg/upgrade/service.go:286 msgid "dependency" msgstr "" #: pkg/vcs/vcs.go:100 #: pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "" #: pkg/sync/build/installer.go:266 #: pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "" #: pkg/sync/build/installer.go:233 #: pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "" #: sync.go:36 msgid "error refreshing databases" msgstr "" #: pkg/sync/workdir/clean.go:51 #: pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "" #: pkg/cmd/graph/main.go:46 #: pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" #: pkg/text/errors.go:8 msgid "input too long" msgstr "" #: pkg/db/ialpm/alpm.go:222 #: pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "" #: pkg/db/ialpm/alpm.go:227 #: pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "" #: pkg/text/input.go:48 msgid "no" msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "" #: local_install.go:66 msgid "no target directories specified" msgstr "" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "" msgstr[1] "" #: print.go:187 msgid "package '%s' was not found" msgstr "" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "" #: clean.go:178 #: pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "" #: cmd.go:186 msgid "unhandled operation" msgstr "" #: cmd.go:450 msgid "unknown-version" msgstr "" #: pkg/text/input.go:47 msgid "yes" msgstr "" ================================================ FILE: po/es.po ================================================ # # Translators: # J G, 2021 # Ivan Garcia, 2021 # Percy De La Rosa, 2022 # Brighton Saldaña, 2022 # Antonio Alvarado-Hernández, 2023 # brandon galvis, 2023 # Angel López, 2023 # C C, 2023 # Claudio Yanez, 2025 # Pablo Lezaeta Reyes , 2025 # msgid "" msgstr "" "Last-Translator: Pablo Lezaeta Reyes , 2025\n" "Language-Team: Spanish (https://app.transifex.com/yay-1/teams/123732/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Carpeta de compilación:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Introduzca un número (por omisión=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Archivos de compilación existen)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Instalado)" #: cmd.go:453 msgid " [Installed]" msgstr " [Instalado]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " no hay nada que hacer" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]Todos [Ab]ortar [I]nstalados [No]Instalados o (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s ya creado -- omitiendo compilación" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s no está definido" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s está presente." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s está actualizado -- ignorando" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s por actualizar/instalar." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s también será instalado para esta operación." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, necesario para: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Sin cambios -- ignorando" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "" "%s: no es posible utilizar el objetivo con la opción --aur -- ignorando" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "" "%s: no es posible utilizar el objetivo con la opción --repo -- ignorando" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: actualización del paquete ignorada (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "" "%s: paquete local (%s) es más nuevo que el paquete disponible en el AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: por favor, configure las variables de entorno AUR_USERNAME y " "AUR_PASSWORD para votar" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD descargado de ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD Descargado : %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) No se pudo descargar el PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analizando SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Instalado)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Instalado: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Huérfano)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Desactualizado: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL del AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Añade %s o %s a tus variables de entorno" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Evite ejecutar yay como superusuario. root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Verificar dependencia" #: print.go:41 msgid "Check Deps" msgstr "Dependencias de verificación" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Verificando paquetes de desarrollo..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Limpiando (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Conflictos con" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Borrando (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dependencia" #: print.go:38 msgid "Depends On" msgstr "Depende de" #: print.go:33 msgid "Description" msgstr "Descripción" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "¿Diffs a mostrar?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Deshabilitar ajuste «provee» por omisión " #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "¿Quieres borrar TODOS los paquetes del AUR de la caché?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "¿Quieres borrar TODOS los archivos del AUR sin rastrear?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "¿Quieres borrar los otros paquetes del AUR de la caché?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "¿Editar PKGBUILD con?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Error al buscar en el AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Excluir paquetes puede causar actualizaciones parciales y rupturas de " "sistemas" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explícito" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paquetes explícitamente instalados: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "No se pudo encontrar el paquete en el AUR para" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "No se pudo instalar la capa, rodando hacia la siguiente capa." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "No se pudieron instalar los siguientes paquetes. Se requiere intervención " "manual:" #: print.go:45 msgid "First Submitted" msgstr "Primera vez subido" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paquetes del AUR marcados como desactualizados:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Paquetes exteriores instalados: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Repositorio git encontrado: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB finalizó. Ningún paquete fue instalado" #: print.go:36 msgid "Groups" msgstr "Grupos" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "¿Importar?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importando llaves con gpg..." #: print.go:46 msgid "Keywords" msgstr "Palabras clave" #: print.go:47 msgid "Last Modified" msgstr "Última vez modificado" #: print.go:35 msgid "Licenses" msgstr "Licencias" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Responsable" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Crear dependencia" #: print.go:40 msgid "Make Deps" msgstr "Dependencias de compilación" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Ausente" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Paquetes de depuración del AUR faltantes:" #: print.go:31 msgid "Name" msgstr "Nombre" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Ningún paquete del AUR para" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "No se encontró un paquete para" #: print.go:225 msgid "None" msgstr "Ninguno" #: print.go:39 msgid "Optional Deps" msgstr "Dependencias opcionales" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paquetes del AUR huérfanos (no mantenidos): " #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Desactualizado" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Llaves PGP a importar:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD actualizado, omitiendo descarga: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "¿PKGBUILDs a editar?" #: print.go:61 msgid "Package Base" msgstr "Paquete base" #: print.go:60 msgid "Package Base ID" msgstr "ID de paquete base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paquetes que no están en el AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "¿Paquetes a limpiar antes de compilar?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paquetes a excluir" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Paquetes a excluir: (ej.: «1 2 3», «1-3», «^4» o nombre del repositorio)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paquetes a instalar (ej..: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularidad" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "¿Proceder con la instalación?" #: print.go:37 msgid "Provides" msgstr "Provee" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "¿Borrar dependencias de instalación después de instalar?" #: print.go:43 msgid "Replaces" msgstr "Remplaza" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositorio" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositorio AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Buscando actualizaciones en el AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Buscando actualizaciones en los repositorios..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Mostrando solamente paquetes de los repositorios" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Tamaño de la cache de pacman %s:%s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Tamaño de la cache de yay %s:%s" #: print.go:62 msgid "Snapshot URL" msgstr "URL de snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronizar" #: print.go:100 msgid "Ten biggest packages:" msgstr "Diez paquetes más grandes:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Los siguientes paquetes no son compatibles con su arquitectura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Existen %[1]d paquetes que proveen %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Es posible que exista otra instancia de Pacman en ejecución. Esperando..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Tamaño total ocupado por los paquetes: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Número de paquetes instalados: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "¿Intentar compilarlos de todas formas?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "No es posible limpiar:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "No es posible encontrar los siguientes paquetes:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "No se ha podido gestionar el voto del paquete para: %s. error: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "No se puede eliminar %s: %s" #: print.go:32 msgid "Version" msgstr "Versión" #: print.go:50 msgid "Votes" msgstr "Votos" #: print.go:87 msgid "Yay version v%s" msgstr "Versión de yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]inguno" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "abortando por el usuario" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argumento «-» especificado sin entrada en stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "no se pudo encontrar PKGBUILD y .SRCINFO en el directorio" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "no es posible encontrar el paquete: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "no fue posible encontrar PKGDEST para: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "no se han encontrado todos los paquetes necesarios" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "no pudo encontrar ningún archivo de paquetes listado en %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dependencia" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" "la verificación de desarrollo para el paquete ha fallado: «%s» se encontró " "un error" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "el editor no terminó correctamente, abortando: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "error descargando fuentes: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "error descargando %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "error instalando paquetes del repositorio" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "error al instalar:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "error al compilar: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "error al fusionar %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "error leyendo %s" #: sync.go:36 msgid "error refreshing databases" msgstr "error refrescando las bases de datos" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "error al reiniciar en %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "error al actualizar el paquete motivo de instalación para %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explícito" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "no se pudo crear directorio «%s»: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "no se pudo al abrir archivo de configuración «%s»: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "no se pudo analizar %s -- ignorando: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "no se pudo al analizar %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "no se pudo analizar .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "no se pudo al leer archivo de configuración «%s»: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "no se pudo recuperar el aur Cache" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "ignorando la actualización del paquete de desarollo (no se ha encontrado " "información en el AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "entrada demasiado larga" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "número no válido: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opción no válida «%s»" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "opción inválida: «--deps» y «--explicit» no deben usarse a la vez" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositorio inválido " #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valor no válido: %d no está entre %d y %d" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "ninguna llave por importar" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "no se ha realizado ninguna consulta" #: local_install.go:66 msgid "no target directories specified" msgstr "no se han especificado directorios de destino" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nada que instalar para %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "sólo una operación se puede usar a la vez" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "sólo se permite un objetivo" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paquete" msgstr[1] "paquetes" msgstr[2] "paquetes" #: print.go:187 msgid "package '%s' was not found" msgstr "paquete «%s» no fue encontrado" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paquete no encontrado en el AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paquete no encontrado en los repositorios " #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema al importar llaves" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "borrando paquetes del AUR de la caché..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "borrando archivos del AUR no rastreados de la caché..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "el PKGDEST para %s está listado por makepkg pero no existe: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "no hay nada que hacer" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "no fue posible ejecutar CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operación no implementada" #: cmd.go:450 msgid "unknown-version" msgstr "versión-desconocida" #: pkg/text/input.go:47 msgid "yes" msgstr "sí" ================================================ FILE: po/eu.po ================================================ # # Translators: # J G , 2021 # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "PO-Revision-Date: 2021-08-13 22:55+0000\n" "Last-Translator: J G , 2021\n" "Language-Team: Basque (https://www.transifex.com/yay-1/teams/123732/eu/)\n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Konpilazio fitxategiak existitzen dira)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Instalatuta)" #: cmd.go:453 msgid " [Installed]" msgstr " [Instalatuta]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " ez dago ezer egiteko" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s Guzti[A]k [Ab]ortatu [I]nstalatuak [No]Ez instalatuak edo (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s eginda dago jada -- konpilazioa alde batera uzten" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s ezarri gabe dago" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s presente dago." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s eguneratuta dago - alde batera uzten" #: pkg/upgrade/service.go:292 #, fuzzy msgid "%s to upgrade/install." msgstr "eguneratzeko pakete." #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, honek beharrezkoa du: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Aldaketarik ez -- alde batera uzten" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: ezin da helburua --aur aukerarekin erabili -- alde batera uzten" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: ezin da helburua --repo aukerarekin erabili -- alde batera uzten" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: paketearen eguneraketa alde batera uzten (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: pakete lokala (%s) AURekoa (%s) baino berriagoa da" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for voting" msgstr "" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD ABStik deskargatuta: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 #, fuzzy msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD ABStik deskargatuta: %s" #: pkg/download/aur.go:82 #, fuzzy msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD ABStik deskargatuta: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO aztertzen: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(Instalatuta)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(Instalatuta: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Umezurtz)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Zaharkituta: %s)" #: print.go:44 msgid "AUR URL" msgstr "AUReko URLa" #: pkg/dep/dep_graph.go:75 #, fuzzy msgid "AUR" msgstr "URLa" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Gehitu %s edo %s zure ingurune-aldagaietara" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Saihestu yay root/sudo bezala exekutatzea." #: pkg/dep/dep_graph.go:63 #, fuzzy msgid "Check Dependency" msgstr "Egiaztapen menpekotasunak" #: print.go:41 msgid "Check Deps" msgstr "Egiaztapen menpekotasunak" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "Garapen paketeak egiaztatzen..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "(%d/%d) garbitzen: %s" #: print.go:42 msgid "Conflicts With" msgstr "Gatazkak hauekin" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "(%d/%d) ezabatzen: %s" #: pkg/dep/dep_graph.go:61 #, fuzzy msgid "Dependency" msgstr "Behar du" #: print.go:38 msgid "Depends On" msgstr "Behar du" #: print.go:33 msgid "Description" msgstr "Deskribapena" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Aldaketak erakutsi?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Cachean dauden AUR pakete GUZTIAK kendu nahi dituzu?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Jarraipen gabeko AUR fitxategi GUZTIAK kendu nahi dituzu?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Cachean dauden beste AUR paketeak kendu nahi dituzu?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "PKGBUILD zerekin editatu?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Errorea AURen bilatzerakoan: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Eskuz instalatutako paketeak: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 #, fuzzy msgid "Failed to find AUR package for" msgstr "Zaharkitu gisa markatutako AUR paketeak:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" #: print.go:45 msgid "First Submitted" msgstr "Lehenengo bidalita" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "Zaharkitu gisa markatutako AUR paketeak:" #: print.go:90 #, fuzzy msgid "Foreign installed packages: %s" msgstr "Kanpotik instalatu diren paketeak: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Git biltegia aurkitu da: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB amaitu da. Ez da paketerik instalatu" #: print.go:36 msgid "Groups" msgstr "Taldeak" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Inportatu?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Gakoak GPGrekin inportatzen..." #: print.go:46 msgid "Keywords" msgstr "Gako-hitzak" #: print.go:47 msgid "Last Modified" msgstr "Azken aldaketa" #: print.go:35 msgid "Licenses" msgstr "Lizentziak" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "" #: print.go:48 msgid "Maintainer" msgstr "Arduraduna" #: pkg/dep/dep_graph.go:62 #, fuzzy msgid "Make Dependency" msgstr "Menpekotasunak burutzea" #: print.go:40 msgid "Make Deps" msgstr "Menpekotasunak burutzea" #: pkg/query/aur_warnings.go:71 #, fuzzy msgid "Missing AUR Debug Packages:" msgstr "Faltan dauden AUR paketeak:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "" #: print.go:31 msgid "Name" msgstr "Izena" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "" #: print.go:225 msgid "None" msgstr "Deus ez" #: print.go:39 msgid "Optional Deps" msgstr "Hautazko menpekotasunak" #: pkg/query/aur_warnings.go:75 #, fuzzy msgid "Orphan (unmaintained) AUR Packages:" msgstr "Umezurtz geratu diren AUR paketeak:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Zaharkituta" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP gakoek inportatu behar dute:" #: pkg/sync/workdir/preparer.go:252 #, fuzzy msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD eguneratuta, alde batera uzten (%d/%d): %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Editatzeko PKGBUILDak?" #: print.go:60 msgid "Package Base ID" msgstr "Pakete-oinarriaren IDa" #: print.go:61 msgid "Package Base" msgstr "Pakete-oinarria" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Konpilatu aurretik garbitzeko paketeak?" #: pkg/dep/dep_graph.go:202 #, fuzzy msgid "Packages to exclude" msgstr "eguneratzeko pakete." #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Kanpoan uzteko paketeak: (adb: \"1 2 3\", \"1-3\", \"^4\" edo biltegiaren " "izena)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Instalatzeko paketeak (eg.: 1 2 3, 1-3 edo ^4)" #: print.go:49 msgid "Popularity" msgstr "Ospea" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Instalatu?" #: print.go:37 msgid "Provides" msgstr "Hornitzen du" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Konpilatzeko menpekotasunak ezabatu instalatu ostean?" #: print.go:43 msgid "Replaces" msgstr "" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR biltegia" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "Biltegia" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "Eguneraketak AURen bilatzen..." #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "Datu-baseetan eguneraketak bilatzen..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Biltegietako paketeak soilik erakusten" #: print.go:95 #, fuzzy msgid "Size of pacman cache %s: %s" msgstr "%s analizatzeak huts egin du: %s" #: print.go:98 #, fuzzy msgid "Size of yay cache %s: %s" msgstr "%s analizatzeak huts egin du: %s" #: print.go:62 msgid "Snapshot URL" msgstr "PKGBUILDaren argazkiaren URLa" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "" #: print.go:100 msgid "Ten biggest packages:" msgstr "Hamar pakete handienak:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Ondorengo paketeak ez dira zure arkitekturarekin bateragarriak:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 #, fuzzy msgid "There are %[1]d providers available for %[2]s:" msgstr "%[1]d hornitzaile eskuragarri %[2]s paketearentzat:\n" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Baliteke beste Pacman instantzia bat exekutzan aritzea. Zain..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Pakete guztiek erabilitako biltegiratzea: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Instalatu diren paketeak: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Saiatu konpilatzen hala ere?" #: print.go:34 msgid "URL" msgstr "URLa" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 #, fuzzy msgid "Unable to clean:" msgstr "ezin da CreateHandle burutu: %s" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "" #: clean.go:170 #, fuzzy msgid "Unable to remove %s: %s" msgstr "%s analizatzeak huts egin du: %s" #: print.go:32 msgid "Version" msgstr "Bertsioa" #: print.go:50 msgid "Votes" msgstr "Botoak" #: print.go:87 msgid "Yay version v%s" msgstr "Yay bertsioa v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "I[N]or ez" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Konpilazio direktorioa:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Idatzi zenbaki bat (lehenetsia=1): " #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "erabiltzailearengatik bertan behera uzten" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "ezin da paketearen izena aurkitu: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "ezin izan da aurkitu PKGDEST %s paketearentzat" #: errors.go:9 #, fuzzy msgid "could not find all required packages" msgstr "Ez izan dira aurkitu beharrezko pakete guztiak:" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 #, fuzzy msgid "dependency" msgstr "Behar du" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editorea ez da behar bezala irten, bertan behera uzten: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "errorea iturriak deskargatzerakoan: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "errorea %s lortzerakoan: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "errorea biltegitako paketeak instalatzerakoan" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 #, fuzzy msgid "error installing:" msgstr "errorea biltegitako paketeak instalatzerakoan" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "konpilazio akatsa: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "errorea %s bateratzerakoan: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "errorea %s irakurtzerakoan" #: sync.go:36 msgid "error refreshing databases" msgstr "errorea datu-baseak freskatzerakoan" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "errorea %s berrezartzerakoan: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "'%s' konfigurazio direktorioa sortzeak huts egin du: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "'%s' konfigurazio fitxategia irekitzeak huts egin du: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "%s analizatzeak huts egin du -- alde batera uzten: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "%s analizatzeak huts egin du: %s" #: local_install.go:77 #, fuzzy msgid "failed to parse .SRCINFO" msgstr "%s analizatzeak huts egin du: %s" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "'%s' konfigurazio fitxategia irakurtzeak huts egin du: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "" #: pkg/upgrade/sources.go:27 #, fuzzy msgid "ignoring package devel upgrade (no AUR info found):" msgstr "%s: paketearen eguneraketa alde batera uzten (%s => %s)" #: pkg/text/errors.go:8 msgid "input too long" msgstr "sarrera luzeegia" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "zenbaki baliogabea: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "'%s' aukera baliogabea" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" #: pkg/download/abs.go:22 #, fuzzy msgid "invalid repository" msgstr "Biltegia" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "balio baliogabea: %d ez dago %d eta %d artean" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "inportatzeko gakorik ez" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "" #: local_install.go:66 msgid "no target directories specified" msgstr "" #: pkg/text/input.go:48 msgid "no" msgstr "ez" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "aldi berean operazio bakarra erabil daiteke" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "" #: print.go:187 msgid "package '%s' was not found" msgstr "ez da aurkitu '%s' paketea" #: pkg/download/errors.go:15 #, fuzzy msgid "package not found in AUR" msgstr "ez da aurkitu '%s' paketea" #: pkg/download/abs.go:23 #, fuzzy msgid "package not found in repos" msgstr "ez da aurkitu '%s' paketea" #: pkg/upgrade/service.go:292 #, fuzzy msgid "package" msgid_plural "packages" msgstr[0] "Pakete-oinarria" msgstr[1] "Pakete-oinarria" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "arazoa gakoak inportatzerakoan" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "AUR paketeak cachetik kentzen..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "jarraipen gabeko AUR fitxategiak cachetik kentzen..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "" "%s(e)rako PKGDESTa makepkg-ek zerrendatzen du, baina ez da existitzen: %s" #: pkg/sync/sync.go:45 #, fuzzy msgid "there is nothing to do" msgstr " ez dago ezer egiteko" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "ezin da CreateHandle burutu: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "sostengurik gabeko eragiketa" #: cmd.go:450 msgid "unknown-version" msgstr "bertsio ezezaguna" #: pkg/text/input.go:47 msgid "yes" msgstr "bai" #~ msgid " (Target" #~ msgstr " (Helburua" #~ msgid " (Wanted by: " #~ msgstr " (Honek behar du: " #~ msgid " Input too long" #~ msgstr " Sarrera luzeegia" #~ msgid "Installing %s will remove:" #~ msgstr "%s instalatzeak ezabatuko du:" #~ msgid "%s already downloaded -- use -f to overwrite" #~ msgstr "%s deskargatuta dago -- erabili -f gainidazteko" #~ msgid "%s and %s unset" #~ msgstr "%s eta %s ezarri gabe" #~ msgid "%s not satisfied, flushing install queue" #~ msgstr "%s ez da betetzen, instalazio ilara ezabatzen" #~ msgid "Checking for conflicts..." #~ msgstr "Gatazkak bilatzen..." #~ msgid "Checking for inner conflicts..." #~ msgstr "Barneko gatazkak bilatzen..." #~ msgid "Conflicting packages will have to be confirmed manually" #~ msgstr "Pakete gatazkatsuak eskuz egiaztatu beharko dira" #~ msgid "Downloaded PKGBUILD (%d/%d): %s" #~ msgstr "(%d/%d) PKGBUILDak deskargatuta: %s" #~ msgid "Missing ABS packages:" #~ msgstr "Faltan dauden ABS paketeak:" #~ msgid "Querying AUR..." #~ msgstr "AURen bilatzen..." #~ msgid "" #~ "\n" #~ "Inner conflicts found:" #~ msgstr "" #~ "\n" #~ "Barne gatazkak aurkitu dira:" #~ msgid "" #~ "\n" #~ "Package conflicts found:" #~ msgstr "" #~ "\n" #~ "Pakete arteko gaztakak aurkitu dira:" #~ msgid "error cloning %s: %s" #~ msgstr "errorea %s klonatzerakoan: %s" #~ msgid "error during AUR search: %s" #~ msgstr "errorea AURen bilatzerakoan: %s" #~ msgid "failed to create BuildDir directory '%s': %s" #~ msgstr "'%s' BuildDir direktorioa sortzeak huts egin du: %s" #~ msgid "failed to get pkgbuild: %s: %s" #~ msgstr "PKGBUILD lortzeak huts egin du: %s: %s" #~ msgid "failed to link %s: %s" #~ msgstr "%s estekatzeak huts egin du: %s" #~ msgid "failed to open vcs file '%s': %s" #~ msgstr "'%s' VCS fitxategia irekitzeak huts egin du: %s" #~ msgid "failed to read vcs file '%s': %s" #~ msgstr "'%s' VCS fitxategia irakurtzeak huts egin du: %s" #~ msgid "invalid sort mode. Fix with yay -Y --bottomup --save" #~ msgstr "" #~ "ordenatzeko modu baliogabea. Konpondu 'yay -Y --bottomup --save' " #~ "exekutatzen" #~ msgid "no packages match search" #~ msgstr "paketerik ez dator bat bilaketarekin" #~ msgid "package conflicts can not be resolved with noconfirm, aborting" #~ msgstr "" #~ "pakete arteko gatazkak ezin dira noconfirm-ekin konpondu, bertan behera " #~ "uzten" #~ msgid "refusing to install AUR packages as root, aborting" #~ msgstr "" #~ "AUR paketeak root gisa instalatzeari uko egiten, bertan behera uzten" #~ msgid "failed to create cache directory '%s': %s" #~ msgstr "'%s' cache direktorioa sortzeak huts egin du: %s" ================================================ FILE: po/fi.po ================================================ # Translators: # Joonas Miettinen, 2026 # msgid "" msgstr "" "Last-Translator: Joonas Miettinen, 2026\n" "Language-Team: Finnish (https://app.transifex.com/yay-1/teams/123732/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Kääntöhakemisto:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Anna numero (oletusarvo=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Kääntötiedostot ovat olemassa)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Asennettu)" #: cmd.go:453 msgid " [Installed]" msgstr "[Asennettu]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "ei tehtävää" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Kaikki [Ab]Keskeytä [I]Asennettu [No]Ei asennettu tai (1 2 3, 1-3, ^4)" " " #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s on jo käännetty --- ei käännetä" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "asetusta %s ei ole asetettu" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s on olemassa." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "Asennuskohdetta %s ei tarvitse päivittää --- jatketaan" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s päivittääksesi tai asentaaksesi." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "Myös %s asennetaan tätä toimenpidettä varten." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, tarpeellinen riippuvuus seuraavalle: %s " #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Ei muutoksia -- jatketaan" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: kohdetta ei voi käyttää valinnalla --aur -- jätetään huomiotta" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: kohdetta ei voi käyttää valinnalla --repo -- jätetään huomiotta" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: pakettia ei päivitetä (%s => %s) " #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: paikallinen (%s) on uudempi kuin AUR:ssa (%s) " #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: aseta ympäristömuuttujat AUR_USERNAME ja AUR_PASSWORD äänestääksesi " #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD ladattu ABS:stä: %s " #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD ladattu: %s " #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) PKGBUILDin lataaminen epäonnistui: %s " #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Jäsennetään SRCINFO: %s " #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Asennettu)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Asennettu: %s) " #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orvoksi jäänyt) " #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Vanhentunut: %s) " #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR " #: print.go:44 msgid "AUR URL" msgstr "AUR URL " #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Lisää %s tai %s ympäristömuuttujiisi" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "" "yay:ia ei suositella ajettavan pääkäyttäjänä tai sudo-komennon kanssa." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Tarkista riippuvuus" #: print.go:41 msgid "Check Deps" msgstr "Tarkista riippuvuudet" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Tarkistetaan kehityspaketit..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Puhdistetaan (%d/%d): %s " #: print.go:42 msgid "Conflicts With" msgstr "Ei sovi seuraavan kanssa yhteen" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Poistetaan (%d/%d): %s " #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Riippuvuus" #: print.go:38 msgid "Depends On" msgstr "Riippuu seuraavasta" #: print.go:33 msgid "Description" msgstr "Kuvaus" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Näytä erot?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Poista asetus 'provides' käytöstä oletusarvoisesti" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Haluatko poistaa KAIKKI AUR-paketit välimuistista?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Haluatko poistaa KAIKKI seuraamattomat AUR-tiedostot?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Haluatko poistaa kaikki muut AUR-paketit välimuistista?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Millä haluat muokata PKGBUILD-tiedostoa?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Virhe AUR-haun aikana: %s \n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Pakettien jättäminen pois voi kohtaa osittaiseen päivitykseen ja voi rikkoa " "järjestelmiä" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Nimellä pyydetty" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Asennetut nimellä pyydetyt paketit: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "AUR-paketteja ei löytynyt seuraavalle(ille)" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Kerroksen asennus epäonnistui, siirrytään seuraavaan kerrokseen." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Seuraavien pakettien asennus epäonnistui. Käyttäjältä edellytetään " "toimenpiteitä:" #: print.go:45 msgid "First Submitted" msgstr "Ladattiin palveluun ensimmäistä kertaa" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Vanhentuneeksi merkityt AUR-paketit: " #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Vieraat asennetut paketit: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Seuraava git-tietovarasto löytyi: %s " #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB valmistui. Yhtään pakettia ei asennettu" #: print.go:36 msgid "Groups" msgstr "Ryhmät" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Tuo?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Tuodaan avaimet gpg:in avulla..." #: print.go:46 msgid "Keywords" msgstr "Avainsanat" #: print.go:47 msgid "Last Modified" msgstr "Viimeksi muokattu" #: print.go:35 msgid "Licenses" msgstr "Lisenssit" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Paikallinen" #: print.go:48 msgid "Maintainer" msgstr "Ylläpitäjä" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Kääntöriippuvuus" #: print.go:40 msgid "Make Deps" msgstr "Kääntöriippuvuudet" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Puuttuva(vat)" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Puuttuvat AUR-virheenkorjauspaketit:" #: print.go:31 msgid "Name" msgstr "Nimi" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Seuraavalle(ville) ei löytynyt yhtään AUR-pakettia" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "on seuraavan edellyttämä: " #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Yhtään pakettia ei löytynyt seuraavalle(ville)" #: print.go:225 msgid "None" msgstr "Ei mitään/yhtään" #: print.go:39 msgid "Optional Deps" msgstr "Valinnaiset riippuvuudet" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Orvot (ei ylläpitäjää) AUR-paketit:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Vanhentunut" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP-avaimet täytyy tuoda:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD on uusin, jätetään lataamatta: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Muokattavat PKGBUILD-tiedostot?" #: print.go:61 msgid "Package Base" msgstr "Paketin perusta" #: print.go:60 msgid "Package Base ID" msgstr "Paketin perustan tunniste" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paketit joita ei löydy AUR:ista:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paketit jotka haluat asentaa puhtaasti (cleanBuild)?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pois jätettävät paketit" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Pois jätettävät paketit (esim. \"1 2 3\", \"1-3\", \"^4\" tai tietovarasto nimi)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Asennettavat paketit (esim. 1 2 3, 1-3 tai ^4)" #: print.go:49 msgid "Popularity" msgstr "Suosio" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Jatka asennukseen?" #: print.go:37 msgid "Provides" msgstr "Mahdollistaa" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Poista kääntöriippuvuudet asennuksen jälkeen?" #: print.go:43 msgid "Replaces" msgstr "Korvaa" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Tietovarasto" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Tietovarasto AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Etsitään päivityksitä AUR:ista..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Etsitään päivityksiä tietokannoista..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Vain tietovarastopaketit näytetään" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "pacman-välimuistin koko %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "yay-välimuistin koko %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Tilannekuvan URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synkronoi" #: print.go:100 msgid "Ten biggest packages:" msgstr "Kymmenen suurinta pakettia:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "" "Seuraavat paketit eivät ole yhteensopivia tietokonearkkitehtuurisi kanssa:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%s[1]d tarjoajaa saatavilla %[2]:lle:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Toinen pacman-prosessi saattaa olla käynnissä. Odotetaan..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Paketit vievät muistia yhteensä: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Yhteensä asennettuja paketteja: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Yritä silti kääntää ne?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Ei voida puhdistaa:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Seuraavia paketteja ei löytynyt:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Ääntä paketille %s ei voitu käsitellä. Virheviesti: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Pakettia %s ei voitu poistaa: %s" #: print.go:32 msgid "Version" msgstr "Versio" #: print.go:50 msgid "Votes" msgstr "Äänet" #: print.go:87 msgid "Yay version v%s" msgstr "yay-versio v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Ei yhtään" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "keskeytetään käyttäjän pyynnöstä" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argumentti '-' annettu ilman stdin-syötettä" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "hakemistosta ei löydy PKGBUILD- ja .SRCINFO-tiedostoa" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "paketin nimeä ei löydy: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "PKGDEST ei löytynyt paketille %s" #: errors.go:9 msgid "could not find all required packages" msgstr "kaikkia vaadittuja paketteja ei löytynyt" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "yhtään pakettiarkistoa ei löytynyt listasta %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "riippuvuus" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "kehitystarkistus paketille '%s' kohtasi virheen" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "" "muokkausohjelma kaatui tai se suljettiin virheellisesti, keskeytetään: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "lähteiden lataaminen epäonnistui: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "kohteen %s noutaminen epäonnistui: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "tietovarastopakettien asentaminen epäonnistui" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "seuraavan(ien) asentaminen epäonnistui:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "seuraavan(ien) kääntäminen epäonnistui: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "kohteiden %s yhdistäminen epäonnistui: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "kohdetta %s ei voinut lukea" #: sync.go:36 msgid "error refreshing databases" msgstr "tietokantojen päivittäminen epäonnistui" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "kohteen %s nollaaminen epäonnistui: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "paketin päivittäminen epäonnistui; riippuvuus/nimellä pyydetty: %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "nimellä pyydetty" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "hakemistoa '%s' ei voitu luoda: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "asetustiedostoa '%s' ei voitu lukea: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "kohteen '%s' jäsentäminen epäonnistui ja se jätetään välistä: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "kohteen '%s' jäsentäminen epäonnistui: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr ".SRCINFOn jäsentäminen epäonnistui" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "asetustiedostoa '%s' ei voitu lukea: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "AUR-välimuistia ei voitu noutaa" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "paketin kehityspäivitys jätetään huomiotta (AUR-tietoja ei löytynyt):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "syöte on liian pitkä" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "numero ei kelpaa: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "valinta ei kelpaa: '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "valinta ei kelpaa: valintoja '--deps' ja '--explicit' ei voi tehdä samaan " "aikaan" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "tietovarasto ei kelpaa" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "arvo ei kelpaa: %d ei ole %d:n ja %d:n välillä" #: pkg/text/input.go:48 msgid "no" msgstr "ei" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "ei tuotavia avaimia" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "yhtään kyselyä ei toteutettu" #: local_install.go:66 msgid "no target directories specified" msgstr "yhtään kohdehakemistoa ei määritetty" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "ei asennettavaa valinnalla(oilla) %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "toimenpiteitä voi käyttää vain yhtä kerrallaan" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "vain yksi kohde sallitaan" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paketit" msgstr[1] "paketit" #: print.go:187 msgid "package '%s' was not found" msgstr "pakettia '%s' ei löytynyt" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "pakettia ei löytynyt AUR:ista" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "pakettia ei löytynyt tietovarastoista" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "avainten tuomisessa esiintyi ongelma" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "poistetaan AUR-paketit välimuistista..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "poistetaan seuraamattomat AUR-tiedostot välimuistista..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "" "paketin %s PKGDEST on makepkg:n listalla mutta sitä ei ole olemassa: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "ei tehtävää" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "käsittelyn luonti (CreateHandle) epäonnistui: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "käsittelemätön toimenpide" #: cmd.go:450 msgid "unknown-version" msgstr "tuntematon versio" #: pkg/text/input.go:47 msgid "yes" msgstr "kyllä" ================================================ FILE: po/fr.po ================================================ # Translators: # Benjamin Chenebault, 2023 # anthony tonitch , 2024 # Edgar Fournival, 2024 # J G, 2024 # Sylvain Bx, 2024 # Mathias Brugger, 2024 # Léane GRASSER, 2024 # ariasuni , 2026 # msgid "" msgstr "" "Last-Translator: ariasuni , 2026\n" "Language-Team: French (https://app.transifex.com/yay-1/teams/123732/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Répertoire de compilation :" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Saisissez un nombre (valeur par défaut : 1) :" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Les fichiers de compilation existent)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Installé)" #: cmd.go:453 msgid " [Installed]" msgstr " [Installé]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " il n'y a rien à faire" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]Tous [Ab]Annuler [I]nstallés [No]nInstallés ou (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s déjà créé -- compilation ignorée" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s n'est pas définie" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s existe." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s est à jour -- ignoré" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s à mettre à jour ou à installer." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s à installer pour effectuer cette opération." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, nécessaire pour : %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s : Aucun changement -- ignoré" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s : la cible est incompatible avec l'option --aur -- ignoré" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s : la cible est incompatible avec l'option --repo -- ignoré" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s : mise à jour du paquet ignorée (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "" "%s : le paquet local (%s) est plus récent que le paquet disponible sur l'AUR" " (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s : définissez les variables d'environnement AUR_USERNAME et AUR_PASSWORD " "avant de pouvoir voter" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD téléchargé depuis l'ABS : %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD téléchargé : %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Échec du téléchargement du PKGBUILD : %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analyse du SRCINFO : %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Installé)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Installé : %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orphelin)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Obsolète : %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL sur l'AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Ajoutez %s ou %s à vos variables d'environnement" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Évitez d'exécuter yay en tant que root ou via sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Dépendance de vérification" #: print.go:41 msgid "Check Deps" msgstr "Dépendances vérif." #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Vérification des paquets de développement…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Nettoyage (%d/%d) : %s" #: print.go:42 msgid "Conflicts With" msgstr "Est en conflit avec" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Suppression (%d/%d) : %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dépendance" #: print.go:38 msgid "Depends On" msgstr "Dépend de" #: print.go:33 msgid "Description" msgstr "Description" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Diffs à afficher ?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Désactiver par défaut le paramètre \"provides\"" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Voulez-vous supprimer TOUS les paquets de l'AUR dans le cache ?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "" "Voulez-vous supprimer TOUS les fichiers non suivis des paquets de l'AUR ?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" "Voulez-vous supprimer tous les autres paquets de l'AUR dans le cache ?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Modifier le PKGBUILD avec ?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Erreur lors de la recherche dans l'AUR : %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "L'exclusion de paquets peut mener à une mise à jour partielle de votre " "système et pourrait le casser" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explicite" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paquets installés explicitement : %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Impossible de trouver un paquet sur l'AUR correspondant à" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Échec de l'installation de la couche, passage à la couche suivante." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Échec de l'installation des paquets suivants. Une intervention manuelle est " "requise :" #: print.go:45 msgid "First Submitted" msgstr "Envoyé le" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paquets de l'AUR marqués comme obsolètes :" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Paquets étrangers installés : %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Dépôt Git trouvé : %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB terminé. Aucun paquet n'a été installé" #: print.go:36 msgid "Groups" msgstr "Groupes" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importer ?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importation des clés avec gpg…" #: print.go:46 msgid "Keywords" msgstr "Mots-clés" #: print.go:47 msgid "Last Modified" msgstr "Modifié le" #: print.go:35 msgid "Licenses" msgstr "Licences" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Mainteneur" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Dépendance de création" #: print.go:40 msgid "Make Deps" msgstr "Dépendances créat." #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Manquant" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Paquets de débogage de l'AUR manquants :" #: print.go:31 msgid "Name" msgstr "Nom" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Aucun paquet trouvé sur l'AUR correspondant à" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "requis par" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Aucun paquet trouvé correspondant à" #: print.go:225 msgid "None" msgstr "--" #: print.go:39 msgid "Optional Deps" msgstr "Dépendances opt." #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paquets de l'AUR orphelins (non maintenus) :" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Obsolète" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Des clés PGP doivent être importées :" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD à jour, téléchargement ignoré : %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs à modifier ?" #: print.go:61 msgid "Package Base" msgstr "Paquet base" #: print.go:60 msgid "Package Base ID" msgstr "ID du paquet base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paquets absents de l'AUR :" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paquets à cleanBuild ?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paquets à exclure" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Paquets à exclure : (ex. « 1 2 3 », « 1-3 », « ^4 » ou noms de dépôt)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paquets à installer (ex. 1 2 3, 1-3 ou ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularité" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Procéder à l'installation ?" #: print.go:37 msgid "Provides" msgstr "Fournit" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Supprimer les dépendances de création après l'installation ?" #: print.go:43 msgid "Replaces" msgstr "Remplace" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Dépôt" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Dépôt AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Recherche des mises à jour sur l'AUR…" #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Recherche des mises à jour dans les bases de données…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Affichage uniquement des paquets provenant des dépôts" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Taille du cache de pacman %s : %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Taille du cache de yay %s : %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL de l'instantané" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sync" #: print.go:100 msgid "Ten biggest packages:" msgstr "Les dix paquets les plus lourds :" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Les paquets suivants sont incompatibles avec votre architecture :" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[1]d paquets fournissant %[2]s sont disponibles :" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Il semblerait qu'une autre instance de Pacman soit en cours d'exécution. En " "attente…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Taille totale occupée par les paquets : %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Nombre de paquets installés : %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Essayer quand même de les compiler ?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Impossible de nettoyer :" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Impossible de trouver les paquets suivants :" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Impossible de gérer le vote pour : %s. Erreur : %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Impossible de supprimer %s : %s" #: print.go:32 msgid "Version" msgstr "Version" #: print.go:50 msgid "Votes" msgstr "Votes" #: print.go:87 msgid "Yay version v%s" msgstr "Yay version v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Aucun" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "abandon dû à l'utilisateur" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' spécifié et aucune entrée dans stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "" "impossible de trouver les fichiers PKGBUILD et .SRCINFO dans le répertoire" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "impossible de trouver le nom de paquet : %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "impossible de trouver le PKGDEST pour : %s" #: errors.go:9 msgid "could not find all required packages" msgstr "impossible de trouver tous les paquets requis" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "aucune archive de paquet trouvée parmi celles listées dans %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dépendance" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" "échec de la vérification du paquet de développement : '%s' a rencontré une " "erreur" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "l'éditeur ne s'est pas terminé avec succès, abandon : %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "erreur lors du téléchargement des sources : %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "erreur lors de la récupération de %s : %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "erreur lors de l'installation des paquets provenant des dépôts" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "erreur lors de l'installation de :" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "erreur lors de la compilation de : %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "erreur lors de la fusion (%s) : %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "erreur lors de la lecture de %s" #: sync.go:36 msgid "error refreshing databases" msgstr "erreur lors de l'actualisation des bases de données" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "erreur lors de la réinitialisation de %s : %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" "erreur lors de la modification du motif d'installation du paquet en « %s »" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explicite" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "échec de la création du répertoire '%s' : %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "échec de l'ouverture du fichier de configuration '%s' : %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "échec de l'analyse de %s -- ignoré : %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "échec de l'analyse de %s : %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "échec de l'analyse du .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "échec de la lecture du fichier de configuration '%s' : %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "échec de la récupération du cache AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "mise à jour du paquet de développement ignorée (infos AUR introuvables) :" #: pkg/text/errors.go:8 msgid "input too long" msgstr "entrée trop longue" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "nombre invalide : %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "option '%s' invalide" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "option invalide : '--deps' et '--explicit' ne peuvent pas être utilisés " "ensemble" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "dépôt invalide" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valeur invalide : %d n'est pas entre %d et %d" #: pkg/text/input.go:48 msgid "no" msgstr "non" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "aucune clé à importer" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "aucune requête n'a été exécutée" #: local_install.go:66 msgid "no target directories specified" msgstr "aucun répertoire cible spécifié" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "rien à installer pour %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "une seule opération peut être réalisée à la fois" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "une seule cible est autorisée" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paquet" msgstr[1] "paquets" msgstr[2] "paquets" #: print.go:187 msgid "package '%s' was not found" msgstr "le paquet '%s' est introuvable" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paquet introuvable dans l'AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paquet introuvable dans les dépôts" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "erreur lors de l'importation des clés" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "suppression des paquets de l'AUR du cache…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "" "suppression des fichiers non suivis dans les paquets de l'AUR en cache…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "le PKGDEST pour %s est listé par makepkg mais n'existe pas : %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "il n'y a rien à faire" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "impossible de CreateHandle : %s" #: cmd.go:186 msgid "unhandled operation" msgstr "opération non gérée" #: cmd.go:450 msgid "unknown-version" msgstr "version-inconnue" #: pkg/text/input.go:47 msgid "yes" msgstr "oui" ================================================ FILE: po/fr_FR.po ================================================ # Translators: # J G, 2021 # Mr Strik3, 2022 # Maxime Demolin, 2023 # Mathias Brugger, 2023 # Sylvain Bx, 2023 # Bertrand Junqua, 2024 # Barsanuphe, 2024 # Léane GRASSER, 2024 # ariasuni , 2026 # msgid "" msgstr "" "Last-Translator: ariasuni , 2026\n" "Language-Team: French (France) (https://app.transifex.com/yay-1/teams/123732/fr_FR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr_FR\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Répertoire de compilation :" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Saisissez un nombre (valeur par défaut : 1) :" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Les fichiers de compilation existent)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Installé)" #: cmd.go:453 msgid " [Installed]" msgstr " [Installé]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " il n'y a rien à faire" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]Tous [Ab]Annuler [I]nstallés [No]nInstallés ou (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s déjà créé -- compilation ignorée" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s n'est pas définie" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s existe." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s est à jour -- ignoré" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s à mettre à jour ou à installer." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s à installer pour effectuer cette opération." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, nécessaire pour : %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s : Aucun changement -- ignoré" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s : la cible est incompatible avec l'option --aur -- ignoré" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s : la cible est incompatible avec l'option --repo -- ignoré" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s : mise à jour du paquet ignorée (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "" "%s : le paquet local (%s) est plus récent que le paquet disponible sur l'AUR" " (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s : définissez les variables d'environnement AUR_USERNAME et AUR_PASSWORD " "avant de pouvoir voter" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD téléchargé depuis l'ABS : %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD téléchargé : %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Échec du téléchargement du PKGBUILD : %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analyse du SRCINFO : %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Installé)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Installé : %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orphelin)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Obsolète : %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL sur l'AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Ajoutez %s ou %s à vos variables d'environnement" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Évitez d'exécuter yay en tant que root ou via sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Dépendance de vérification" #: print.go:41 msgid "Check Deps" msgstr "Dépendances vérif." #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Vérification des paquets de développement…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Nettoyage (%d/%d) : %s" #: print.go:42 msgid "Conflicts With" msgstr "Est en conflit avec" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Suppression (%d/%d) : %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dépendance" #: print.go:38 msgid "Depends On" msgstr "Dépend de" #: print.go:33 msgid "Description" msgstr "Description" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Diffs à afficher ?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Désactiver par défaut le paramètre \"provides\"" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Voulez-vous supprimer TOUS les paquets de l'AUR dans le cache ?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "" "Voulez-vous supprimer TOUS les fichiers non suivis des paquets de l'AUR ?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" "Voulez-vous supprimer tous les autres paquets de l'AUR dans le cache ?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Modifier le PKGBUILD avec ?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Erreur lors de la recherche dans l'AUR : %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "L'exclusion de paquets peut mener à une mise à jour partielle de votre " "système et pourrait le casser" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explicite" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paquets installés explicitement : %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Impossible de trouver un paquet sur l'AUR correspondant à" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Échec de l'installation de la couche, passage à la couche suivante." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Échec de l'installation des paquets suivants. Une intervention manuelle est " "requise :" #: print.go:45 msgid "First Submitted" msgstr "Envoyé le" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paquets de l'AUR marqués comme obsolètes :" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Paquets étrangers installés : %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Dépôt Git trouvé : %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB terminé. Aucun paquet n'a été installé" #: print.go:36 msgid "Groups" msgstr "Groupes" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importer ?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importation des clés avec gpg…" #: print.go:46 msgid "Keywords" msgstr "Mots-clés" #: print.go:47 msgid "Last Modified" msgstr "Modifié le" #: print.go:35 msgid "Licenses" msgstr "Licences" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Mainteneur" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Dépendance de création" #: print.go:40 msgid "Make Deps" msgstr "Dépendances créat." #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Manquant" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Paquets de débogage de l'AUR manquants :" #: print.go:31 msgid "Name" msgstr "Nom" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Aucun paquet trouvé sur l'AUR correspondant à" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "requis par" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Aucun paquet trouvé correspondant à" #: print.go:225 msgid "None" msgstr "--" #: print.go:39 msgid "Optional Deps" msgstr "Dépendances opt." #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paquets de l'AUR orphelins (non maintenus) :" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Obsolète" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Des clés PGP doivent être importées :" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD à jour, téléchargement ignoré : %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs à modifier ?" #: print.go:61 msgid "Package Base" msgstr "Paquet base" #: print.go:60 msgid "Package Base ID" msgstr "ID du paquet base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paquets absents de l'AUR :" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paquets à cleanBuild ?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paquets à exclure" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Paquets à exclure : (ex. « 1 2 3 », « 1-3 », « ^4 » ou noms de dépôt)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paquets à installer (ex. 1 2 3, 1-3 ou ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularité" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Procéder à l'installation ?" #: print.go:37 msgid "Provides" msgstr "Fournit" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Supprimer les dépendances de création après l'installation ?" #: print.go:43 msgid "Replaces" msgstr "Remplace" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Dépôt" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Dépôt AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Recherche des mises à jour sur l'AUR…" #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Recherche des mises à jour dans les bases de données…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Affichage uniquement des paquets provenant des dépôts" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Taille du cache de pacman %s : %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Taille du cache de yay %s : %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL de l'instantané" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sync" #: print.go:100 msgid "Ten biggest packages:" msgstr "Les dix paquets les plus lourds :" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Les paquets suivants sont incompatibles avec votre architecture :" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[1]d paquets fournissant %[2]s sont disponibles :" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Il semblerait qu'une autre instance de Pacman soit en cours d'exécution. En " "attente…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Taille totale occupée par les paquets : %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Nombre de paquets installés : %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Essayer quand même de les compiler ?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Impossible de nettoyer :" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Impossible de trouver les paquets suivants :" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Impossible de gérer le vote pour : %s. Erreur : %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Impossible de supprimer %s : %s" #: print.go:32 msgid "Version" msgstr "Version" #: print.go:50 msgid "Votes" msgstr "Votes" #: print.go:87 msgid "Yay version v%s" msgstr "Yay version v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Aucun" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "abandon dû à l'utilisateur" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' spécifié et aucune entrée dans stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "" "impossible de trouver les fichiers PKGBUILD et .SRCINFO dans le répertoire" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "impossible de trouver le nom de paquet : %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "impossible de trouver le PKGDEST pour : %s" #: errors.go:9 msgid "could not find all required packages" msgstr "impossible de trouver tous les paquets requis" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "aucune archive de paquet trouvée parmi celles listées dans %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dépendance" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" "échec de la vérification du paquet de développement : '%s' a rencontré une " "erreur" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "l'éditeur ne s'est pas terminé avec succès, abandon : %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "erreur lors du téléchargement des sources : %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "erreur lors de la récupération de %s : %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "erreur lors de l'installation des paquets provenant des dépôts" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "erreur lors de l'installation de :" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "erreur lors de la compilation de : %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "erreur lors de la fusion (%s) : %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "erreur lors de la lecture de %s" #: sync.go:36 msgid "error refreshing databases" msgstr "erreur lors de l'actualisation des bases de données" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "erreur lors de la réinitialisation de %s : %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" "erreur lors de la modification du motif d'installation du paquet en « %s »" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explicite" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "échec de la création du répertoire '%s' : %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "échec de l'ouverture du fichier de configuration '%s' : %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "échec de l'analyse de %s -- ignoré : %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "échec de l'analyse de %s : %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "échec de l'analyse du .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "échec de la lecture du fichier de configuration '%s' : %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "échec de la récupération du cache AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "mise à jour du paquet de développement ignorée (infos AUR introuvables) :" #: pkg/text/errors.go:8 msgid "input too long" msgstr "entrée trop longue" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "nombre invalide : %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "option '%s' invalide" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "option invalide : '--deps' et '--explicit' ne peuvent pas être utilisés " "ensemble" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "dépôt invalide" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valeur invalide : %d n'est pas entre %d et %d" #: pkg/text/input.go:48 msgid "no" msgstr "non" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "aucune clé à importer" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "aucune requête n'a été exécutée" #: local_install.go:66 msgid "no target directories specified" msgstr "aucun répertoire cible spécifié" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "rien à installer pour %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "une seule opération peut être réalisée à la fois" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "une seule cible est autorisée" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paquet" msgstr[1] "paquets" msgstr[2] "paquets" #: print.go:187 msgid "package '%s' was not found" msgstr "le paquet '%s' est introuvable" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paquet introuvable dans l'AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paquet introuvable dans les dépôts" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "erreur lors de l'importation des clés" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "suppression des paquets de l'AUR du cache…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "" "suppression des fichiers non suivis dans les paquets de l'AUR en cache…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "le PKGDEST pour %s est listé par makepkg mais n'existe pas : %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "il n'y a rien à faire" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "impossible de CreateHandle : %s" #: cmd.go:186 msgid "unhandled operation" msgstr "opération non gérée" #: cmd.go:450 msgid "unknown-version" msgstr "version-inconnue" #: pkg/text/input.go:47 msgid "yes" msgstr "oui" ================================================ FILE: po/he.po ================================================ # # Translators: # lavi landa, 2024 # Yaron Shahrabani , 2024 # msgid "" msgstr "" "Last-Translator: Yaron Shahrabani , 2024\n" "Language-Team: Hebrew (https://app.transifex.com/yay-1/teams/123732/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" "Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (קיימים קובצי בנייה)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (מותקנת)" #: cmd.go:453 msgid " [Installed]" msgstr " [מותקנת]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "אין מטרות לביצוע" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]-הכול [Ab]-ביטול [I]-מותקנות [No]-לא מותקנות או (1 2 3, 1-3, ‎^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s כבר נבנתה -- לא תיבנה שוב" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s לא מוגדר" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s קיימת." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s עדכנית -- מדלגים" #: pkg/upgrade/service.go:292 msgid "%s to upgrade/install." msgstr "%s לשדרוג/התקנה." #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "גם %s תותקן לשם הפעולה הזאת." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, נדרשה ע״י: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: אין שינויים -- מדלגים" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: אי אפשר להשתמש ביעד עם האפשרות ‎--aur -- מדלגים" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: אי אפשר להשתמש ביעד עם האפשרות ‎--repo -- מדלגים" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: מתעלמים משדרוג חבילה (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: מקומית (%s) חדשה יותר מ־AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: נא להגדיר את משתני הסביבה AUR_USERNAME ו־AUR_PASSWORD כדי להצביע" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD ירד מ־ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD שירד: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) הורדת PKGBUILD נכשלה: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) מתבצע פענוח של SRCINFO: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(מותקנת)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(מותקנת: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(יתומה)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(לא עדכנית: %s)" #: print.go:44 msgid "AUR URL" msgstr "כתובת AUR" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "יש להוסיף %s או %s למשתני הסביבה שלך" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "כדאי להימנע מהפעלה תחת root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "בדיקת תלות" #: print.go:41 msgid "Check Deps" msgstr "בדיקת תלויות" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "חבילות הפיתוח נבדקות…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "מתבצע ניקיון (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "סותר את" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "מתבצעת מחיקה (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "תלות" #: print.go:38 msgid "Depends On" msgstr "תלויה ב־" #: print.go:33 msgid "Description" msgstr "תיאור" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "הבדלים להצגה?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "להשבית את הגדרת ‚provides’ (מספקת) כברירת מחדל" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "להסיר את כל חבילות ה־AUR מהמטמון?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "להסיר את כל קובצי ה־AUR שאינם במעקב?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "להסיר את כל חבילות ה־AUR האחרות מהמטמון?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "לערוך את PKGBUILD עם?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "שגיאה בחיפוש AUR: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "החרגת חבילות עשויה להוביל לשדרוג חלקי ולפגוע במערכות" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "מפורש" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "חבילות שהותקנו מפורשות: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "נכשל האיתור בחבילת AUR עבור" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "התקנת השכבה נכשלה, מתבצעת התקדמות לשכבה הבאה." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "התקנת החבילות הבאות נכשלה. נדרשת התערבות ידנית:" #: print.go:45 msgid "First Submitted" msgstr "הוגשה לראשונה" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "חבילות AUR שסומנו כלא עדכניות:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "חבילות זרות מותקנות: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "נמצא מאגר git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB הסתיים. לא הותקנו חבילות" #: print.go:36 msgid "Groups" msgstr "קבוצות" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "לייבא?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "מתבצע ייבוא מפתחות עם gpg…" #: print.go:46 msgid "Keywords" msgstr "מילות מפתח" #: print.go:47 msgid "Last Modified" msgstr "שינוי אחרון" #: print.go:35 msgid "Licenses" msgstr "רישיונות" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "מקומי" #: print.go:48 msgid "Maintainer" msgstr "תחזוקה" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "תלות בנייה" #: print.go:40 msgid "Make Deps" msgstr "תלויות בנייה" #: pkg/query/aur_warnings.go:71 msgid "Missing AUR Debug Packages:" msgstr "חבילות ניפוי שגיאות חסרות ב־AUR:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "חסרות" #: print.go:31 msgid "Name" msgstr "שם" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "לא נמצאה חבילת AUR עבור" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "לא נמצאה חבילה עבור" #: print.go:225 msgid "None" msgstr "ללא" #: print.go:39 msgid "Optional Deps" msgstr "תלויות רשות" #: pkg/query/aur_warnings.go:75 msgid "Orphan (unmaintained) AUR Packages:" msgstr "חבילות AUR יתומות (לא מתוחזקות):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "לא עדכניות" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "מפתחות PGP שיש לייבא:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD עדכני, לא תתבצע הורדה: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "קובצי PKGBUILD לעריכה?" #: print.go:60 msgid "Package Base ID" msgstr "מזהה בסיס החבילה" #: print.go:61 msgid "Package Base" msgstr "בסיס חבילה" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "חבילות שאינן ב־AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "חבילות לבנייה מההתחלה (cleanBuild)?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "חבילות להחרגה" #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "חבילות שיוחרגו: (למשל: „1 2 3”, „1-3”, „‎^4” או שם המאגר)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "חבילות להתקנה (למשל: 1 2 3, 1-3 או ‎^4)" #: print.go:49 msgid "Popularity" msgstr "פופולריות" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "להמשיך בהתקנה?" #: print.go:37 msgid "Provides" msgstr "מספקת" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "להסיר תלויות בנייה לאחר ההתקנה?" #: print.go:43 msgid "Replaces" msgstr "מחליפה" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "מאגר AUR" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "מאגר" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "מתבצע חיפוש אחר עדכונים ב־AUR…" #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "מתבצע חיפוש אחר עדכונים במסדי הנתונים…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "מוצגות חבילות מאגר בלבד" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "גודל המטמון של pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "גודל המטמון של yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "כתובת לכידה" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "סנכרון" #: print.go:100 msgid "Ten biggest packages:" msgstr "עשרת החבילות הגדולות:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "החבילות הבאות אינן תואמות לארכיטקטורת המעבד שלך:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "יש %[1]d ספקים זמינים עבור %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "יכול להיות שעוד מופע של Pacman פעיל. ממתינים…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "הגודל הכולל שתופסות החבילות: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "סך כל החבילות המותקנות: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "לנסות לבנות אותן בכל מקרה?" #: print.go:34 msgid "URL" msgstr "כתובת" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "לא ניתן לפנות:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "לא ניתן למצוא את החבילות הבאות:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "לא ניתן לטפל בהצבעות חבילה עבור: %s. שגיאה: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "לא ניתן להסיר את %s: %s" #: print.go:32 msgid "Version" msgstr "גרסה" #: print.go:50 msgid "Votes" msgstr "הצבעות" #: print.go:87 msgid "Yay version v%s" msgstr "Yay גרסה ‎v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]-כלום" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "תיקיית בנייה:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "נא להקליד מספר (ברירת מחדל=1): " #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "בוטל לבקשת המשתמש" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "סופק המשתנה ‚-’ ללא שום קלט ל־stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "לא ניתן לאתר PKGBUILD ו־‎.SRCINFO בתיקייה" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "לא ניתן למצוא את שם החבילה: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "לא ניתן למצוא PKGDEST עבור: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "לא ניתן למצוא את כל החבילות הנחוצות" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "לא ניתן למצוא ארכיוני חבילות כלשהם שמופיעים תחת %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 msgid "dependency" msgstr "תלות" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "בדיקת פיתוח לחבילה נכשלה: ‚%s’ נתקל בשגיאה" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "העורך לא יצא כמו שצריך, הפעולה מבוטלת: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "שגיאה בהורדת קוד מקור: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "שגיאה במשיכת %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "שגיאה בהתקנת חבילות מאגר" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "שגיאה בהתקנה:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "שגיאה בבנייה: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "שגיאה במיזוג %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "שגיאה בקריאת %s" #: sync.go:36 msgid "error refreshing databases" msgstr "שגיאה ברענון מסדי נתונים" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "שגיאה באיפוס %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "שגיאה בעדכון סיבת התקנת החבילה לכדי %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "מפורש" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "יצירת התיקייה ‚%s’ נכשלה: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "פתיחת קובץ ההגדרות ‚%s’ נכשלה: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "הפענוח של %s נכשל -- מדלגים: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "הפענוח של %s נכשל: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "פענוח ה־‎.SRCINFO נכשל" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "קריאת קובץ ההגדרות ‚%s’ נכשלה: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "משיכת המטמון של ה־aur נכשלה" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "התעלמות משדרוג חבילת פיתוח (devel - לא נמצאו פרטים ב־AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "הקלט ארוך מדי" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "מס׳ שגוי: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "האפשרות ‚%s’ שגויה" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "אפשרות שגויה: אסור להשתמש ב־‚‎--deps' וב־‚‎--explicit’ יחד" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "מאגר שגוי" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "ערך שגוי: %d לא בין %d לבין %d" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "אין מפתחות לייבוא" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "לא הופעלה שאילתה" #: local_install.go:66 msgid "no target directories specified" msgstr "לא צוינו תיקיות יעד" #: pkg/text/input.go:48 msgid "no" msgstr "לא" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "אין מה להתקין עבור %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "אפשר להשתמש בפעולה אחת בכל רגע נתון" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "מותר רק יעד אחד" #: print.go:187 msgid "package '%s' was not found" msgstr "החבילה ‚%s’ נכשלה" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "החבילה לא נמצאה ב־AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "החבילה לא נמצאה במאגרים" #: pkg/upgrade/service.go:292 msgid "package" msgid_plural "packages" msgstr[0] "חבילה" msgstr[1] "חבילות" msgstr[2] "חבילות" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "תקלה בייבוא מפתחות" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "חבילות AUR נמחקות מהמטמון…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "קובצי AUR שאינם במעקב נמחקים מהמטמון…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "ה־PKGDEST עבור %s מוצג על ידי makepkg אך אינו קיים: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "אין מטרות לביצוע" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "לא ניתן ליצור כינוי (CreateHandle): %s" #: cmd.go:186 msgid "unhandled operation" msgstr "פעולה שלא טופלה" #: cmd.go:450 msgid "unknown-version" msgstr "גרסה-לא-ידועה" #: pkg/text/input.go:47 msgid "yes" msgstr "כן" ================================================ FILE: po/he_IL.po ================================================ # # Translators: # Yaron Shahrabani , 2024 # lavi landa, 2024 # msgid "" msgstr "" "Last-Translator: lavi landa, 2024\n" "Language-Team: Hebrew (Israel) (https://app.transifex.com/yay-1/teams/123732/he_IL/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he_IL\n" "Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (קיימים קובצי בנייה)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (מותקנת)" #: cmd.go:453 msgid " [Installed]" msgstr " [מותקנת]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "אין מטרות לביצוע" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]-הכול [Ab]-ביטול [I]-מותקנות [No]-לא מותקנות או (1 2 3, 1-3, ‎^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s כבר נבנתה -- לא תיבנה שוב" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s לא מוגדר" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s קיימת." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s עדכנית -- מדלגים" #: pkg/upgrade/service.go:292 msgid "%s to upgrade/install." msgstr "%s לשדרוג/התקנה." #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "גם %s תותקן לשם הפעולה הזאת." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, נדרשה ע״י: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: אין שינויים -- מדלגים" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: אי אפשר להשתמש ביעד עם האפשרות ‎--aur -- מדלגים" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: אי אפשר להשתמש ביעד עם האפשרות ‎--repo -- מדלגים" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: מתעלמים משדרוג חבילה (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: מקומית (%s) חדשה יותר מ־AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: נא להגדיר את משתני הסביבה AUR_USERNAME ו־AUR_PASSWORD כדי להצביע" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD ירד מ־ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD שירד: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) לא הצליח להוריד PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) מתבצע פענוח של SRCINFO: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(מותקנת)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(מותקנת: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(יתומה)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(לא עדכנית: %s)" #: print.go:44 msgid "AUR URL" msgstr "כתובת AUR" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "יש להוסיף %s או %s למשתני הסביבה שלך" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "כדאי להימנע מהפעלה תחת root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "בדיקת תלות" #: print.go:41 msgid "Check Deps" msgstr "בדיקת תלויות" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "חבילות הפיתוח נבדקות…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "מתבצע ניקיון (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "סותר את" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "מתבצעת מחיקה (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "תלות" #: print.go:38 msgid "Depends On" msgstr "תלויה ב־" #: print.go:33 msgid "Description" msgstr "תיאור" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "הבדלים להצגה?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "להשבית את הגדרת ‚provides’ (מספקת) כברירת מחדל" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "להסיר את כל חבילות ה־AUR מהמטמון?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "להסיר את כל קובצי ה־AUR שאינם במעקב?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "להסיר את כל חבילות ה־AUR האחרות מהמטמון?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "לערוך את PKGBUILD עם?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "שגיאה בחיפוש AUR: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "החרגת חבילות עשויה להוביל לשדרוג חלקי ולפגוע במערכות" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "מפורש" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "חבילות שהותקנו מפורשות: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "נכשל האיתור בחבילת AUR עבור" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "התקנת השכבה נכשלה, מתבצעת התקדמות לשכבה הבאה." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "התקנת החבילות הבאות נכשלה. נדרשת התערבות ידנית:" #: print.go:45 msgid "First Submitted" msgstr "הוגשה לראשונה" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "חבילות AUR שסומנו כלא עדכניות:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "חבילות זרות מותקנות: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "נמצא מאגר git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "יצירת מסד הנתונים (GenDB) הסתיימה. לא הותקנו חבילות" #: print.go:36 msgid "Groups" msgstr "קבוצות" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "לייבא?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "מתבצע ייבוא מפתחות עם gpg…" #: print.go:46 msgid "Keywords" msgstr "מילות מפתח" #: print.go:47 msgid "Last Modified" msgstr "שינוי אחרון" #: print.go:35 msgid "Licenses" msgstr "רישיונות" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "מקומי" #: print.go:48 msgid "Maintainer" msgstr "תחזוקה" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "תלות בנייה" #: print.go:40 msgid "Make Deps" msgstr "תלויות בנייה" #: pkg/query/aur_warnings.go:71 msgid "Missing AUR Debug Packages:" msgstr "חבילות ניפוי שגיאות חסרות ב־AUR:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "חסרות" #: print.go:31 msgid "Name" msgstr "שם" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "לא נמצאה חבילת AUR עבור" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "לא נמצאה חבילה עבור" #: print.go:225 msgid "None" msgstr "ללא" #: print.go:39 msgid "Optional Deps" msgstr "תלויות רשות" #: pkg/query/aur_warnings.go:75 msgid "Orphan (unmaintained) AUR Packages:" msgstr "חבילות AUR יתומות (לא מתוחזקות):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "לא עדכניות" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "מפתחות PGP שיש לייבא:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD עדכני, לא תתבצע הורדה: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "קובצי PKGBUILD לעריכה?" #: print.go:60 msgid "Package Base ID" msgstr "מזהה בסיס החבילה" #: print.go:61 msgid "Package Base" msgstr "בסיס חבילה" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "חבילות שאינן ב־AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "חבילות לבנייה מההתחלה (cleanBuild)?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "חבילות להחרגה" #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "חבילות שיוחרגו: (למשל: „1 2 3”, „1-3”, „‎^4” או שם המאגר)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "חבילות להתקנה (למשל: 1 2 3, 1-3 או ‎^4)" #: print.go:49 msgid "Popularity" msgstr "פופולריות" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "להמשיך בהתקנה?" #: print.go:37 msgid "Provides" msgstr "מספקת" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "להסיר תלויות בנייה לאחר ההתקנה?" #: print.go:43 msgid "Replaces" msgstr "מחליף" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "מאגר AUR" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "מאגר" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "מתבצע חיפוש אחר עדכונים ב־AUR…" #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "מתבצע חיפוש אחר עדכונים במסדי הנתונים…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "מוצגות חבילות מאגר בלבד" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "גודל המטמון של pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "גודל המטמון של yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "כתובת לכידה" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "סנכרון" #: print.go:100 msgid "Ten biggest packages:" msgstr "עשרת החבילות הגדולות:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "החבילות הבאות אינן תואמות לארכיטקטורת המעבד שלך:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "יש %[1]d ספקים זמינים עבור %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "יכול להיות שעוד מופע של Pacman פעיל. ממתינים…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "הגודל הכולל שתופסות החבילות: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "סך כל החבילות המותקנות: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "לנסות לבנות אותן בכל מקרה?" #: print.go:34 msgid "URL" msgstr "כתובת" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "לא ניתן לפנות:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "לא ניתן למצוא את החבילות הבאות:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "לא ניתן לטפל בהצבעות חבילה עבור: %s. שגיאה: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "לא ניתן להסיר את %s: %s" #: print.go:32 msgid "Version" msgstr "גרסה" #: print.go:50 msgid "Votes" msgstr "הצבעות" #: print.go:87 msgid "Yay version v%s" msgstr "Yay גרסה ‎v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]-כלום" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "תיקיית בנייה:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "נא להקליד מספר (ברירת מחדל=1): " #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "בוטל לבקשת המשתמש" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "סופק המשתנה ‚-’ ללא שום קלט ל־stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "לא ניתן לאתר PKGBUILD ו־‎.SRCINFO בתיקייה" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "לא ניתן למצוא את שם החבילה: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "לא ניתן למצוא PKGDEST עבור: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "לא ניתן למצוא את כל החבילות הנחוצות" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "לא ניתן למצוא ארכיוני חבילות כלשהם שמופיעים תחת %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 msgid "dependency" msgstr "תלות" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "בדיקת פיתוח לחבילה נכשלה: ‚%s’ נתקל בשגיאה" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "העורך לא יצא כמו שצריך, הפעולה מבוטלת: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "שגיאה בהורדת קוד מקור: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "שגיאה במשיכת %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "שגיאה בהתקנת חבילות מאגר" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "שגיאה בהתקנה:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "שגיאה בבנייה: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "שגיאה במיזוג %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "שגיאה בקריאת %s" #: sync.go:36 msgid "error refreshing databases" msgstr "שגיאה ברענון מסדי נתונים" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "שגיאה באיפוס %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "שגיאה בעדכון סיבת התקנת החבילה לכדי %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "מפורש" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "יצירת התיקייה ‚%s’ נכשלה: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "פתיחת קובץ ההגדרות ‚%s’ נכשלה: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "הפענוח של %s נכשל -- מדלגים: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "הפענוח של %s נכשל: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "פענוח ה־‎.SRCINFO נכשל" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "קריאת קובץ ההגדרות ‚%s’ נכשלה: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "משיכת המטמון של ה־aur נכשלה" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "התעלמות משדרוג חבילת פיתוח (devel - לא נמצאו פרטים ב־AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "הקלט ארוך מדי" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "מס׳ שגוי: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "האפשרות ‚%s’ שגויה" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "אפשרות שגויה: אסור להשתמש ב־‚‎--deps' וב־‚‎--explicit’ יחד" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "מאגר שגוי" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "ערך שגוי: %d לא בין %d לבין %d" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "אין מפתחות לייבוא" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "לא הופעלה שאילתה" #: local_install.go:66 msgid "no target directories specified" msgstr "לא צוינו תיקיות יעד" #: pkg/text/input.go:48 msgid "no" msgstr "לא" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "אין מה להתקין עבור %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "אפשר להשתמש בפעולה אחת בכל רגע נתון" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "מותר רק יעד אחד" #: print.go:187 msgid "package '%s' was not found" msgstr "החבילה ‚%s’ נכשלה" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "החבילה לא נמצאה ב־AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "החבילה לא נמצאה במאגרים" #: pkg/upgrade/service.go:292 msgid "package" msgid_plural "packages" msgstr[0] "חבילה" msgstr[1] "חבילות" msgstr[2] "חבילות" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "תקלה בייבוא מפתחות" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "חבילות AUR נמחקות מהמטמון…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "קובצי AUR שאינם במעקב נמחקים מהמטמון…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "ה־PKGDEST עבור %s מוצג על ידי makepkg אך אינו קיים: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "אין מטרות לביצוע" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "לא ניתן ליצור כינוי (CreateHandle): %s" #: cmd.go:186 msgid "unhandled operation" msgstr "פעולה שלא טופלה" #: cmd.go:450 msgid "unknown-version" msgstr "גרסה-לא-ידועה" #: pkg/text/input.go:47 msgid "yes" msgstr "כן" ================================================ FILE: po/hu.po ================================================ # # Translators: # Szigeti Péter, 2021 # summoner , 2025 # msgid "" msgstr "" "Last-Translator: summoner , 2025\n" "Language-Team: Hungarian (https://app.transifex.com/yay-1/teams/123732/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Összeállítási könyvtár:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Írjon be egy számot (alapértelmezett=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Összeállítási fájlok léteznek)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Telepítve)" #: cmd.go:453 msgid " [Installed]" msgstr " [Telepítve]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " nincs tennivaló" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Összes [Ab]Megszakítás [I]Telepített [No]Nem telepített, vagy (1 2 3, " "1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s már kész -- összeállítás kihagyása" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s nincs beállítva" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s létezik." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s naprakész -- kihagyás" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s a frissítéshez/telepítéshez." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s is telepítve lesz ehhez a művelethez." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, szükséges a következő(k)höz: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Nincs változtatás -- kihagyás" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: nem használható a cél az --aur kapcsolóval -- kihagyás" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: nem használható a cél a --repo kapcsolóval -- kihagyás" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: csomagfrissítés mellőzése (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: helyi (%s) frissebb mint az AUR-beli (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: állítsa be az AUR_USERNAME és AUR_PASSWORD környezeti változókat a " "szavazáshoz" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD letöltve az ABS-ből: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD letöltve: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Nem sikerült letölteni a PKGBUILD-et: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO vizsgálata: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Telepítve)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Telepítve: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Elárvult)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Elavult: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR webcím" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Állítsa be a(z) %s vagy a(z) %s környezeti változót" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Ne futtassa a yay-t root-ként/sudo-val." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Függőség ellenőrzése" #: print.go:41 msgid "Check Deps" msgstr "Függőségek ellenőrzése" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Fejlesztői csomagok ellenőrzése…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Tisztítás (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Ütközik a következővel" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Törlés (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Függőség:" #: print.go:38 msgid "Depends On" msgstr "Függőség(ek)" #: print.go:33 msgid "Description" msgstr "Leírás" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Összehasonlítások megjelenítése?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "A „provides” opció alapértelmezett kikapcsolása" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "" "Biztosan el akarja távolítani az ÖSSZES AUR csomagot a gyorsítótárból?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Biztosan el akarja távolítani az ÖSSZES nem követett AUR fájlt?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" "Biztosan el akarja távolítani az összes többi AUR csomagot a gyorsítótárból?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Mivel szerkeszti a PKGBUILD-et?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Hiba történt az AUR-ban való kereséskor: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "A csomagok kizárása részleges frissítéseket és rendszertöréseket okozhat" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Kifejezetten" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Kifejezetten telepített csomagok: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Nem sikerült megtalálni a következőhöz tartozó AUR-csomagot:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Nem sikerült telepíteni a réteget, áttérés a következő rétegre." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Nem sikerült telepíteni a következő csomagokat. Kézi beavatkozás szükséges:" #: print.go:45 msgid "First Submitted" msgstr "Először beküldve" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Elavultnak jelölt AUR-csomagok:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Idegen telepített csomagok: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Git-tároló találat: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB végzett. Nem lett új csomag telepítve" #: print.go:36 msgid "Groups" msgstr "Csoportok" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importálás?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Kulcsok importálása gpg-vel…" #: print.go:46 msgid "Keywords" msgstr "Kulcsszavak" #: print.go:47 msgid "Last Modified" msgstr "Legutóbb módosítva" #: print.go:35 msgid "Licenses" msgstr "Licencek" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Helyi" #: print.go:48 msgid "Maintainer" msgstr "Karbantartó" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Fordítási függőség" #: print.go:40 msgid "Make Deps" msgstr "Fordítási függőségek" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Hiányzik" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Hiányzó AUR-hibakereső-csomagok:" #: print.go:31 msgid "Name" msgstr "Név" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Nem található AUR-csomag a következőhöz:" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "szükséges a következő(k)höz" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nem található csomag a következőhöz:" #: print.go:225 msgid "None" msgstr "Semmi" #: print.go:39 msgid "Optional Deps" msgstr "Nem kötelező függőségek" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Árva (nem karbantartott) AUR-csomagok:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Elavult" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Importálandó PGP-kulcsok:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "A PKGBUILD naprakész, a letöltés kihagyása: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILD-ek szerkesztése?" #: print.go:61 msgid "Package Base" msgstr "Csomag alapja" #: print.go:60 msgid "Package Base ID" msgstr "Csomagazonosító-alap" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "A következő csomag nincs az AUR-ban:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Csomagok a tiszta összeállításhoz?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Kizárandó csomagok" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Kizárandó csomagok: (például: „1 2 3”, „1-3”, „^4”, vagy a tároló neve)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Telepítendő csomagok (például: „1 2 3”, „1-3” vagy „^4”)" #: print.go:49 msgid "Popularity" msgstr "Népszerűség" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Folytatja a telepítést?" #: print.go:37 msgid "Provides" msgstr "Szolgáltatók" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Fordítási függőségek eltávolítása a telepítés után?" #: print.go:43 msgid "Replaces" msgstr "Cserék" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Tároló" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR-tároló" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Frissítések keresése az AUR-ban…" #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Frissítések keresése az adatbázisokban…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Csak a tároló csomagjainak megjelenítése" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "A pacman gyorsítótárának mérete %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "A yay gyorsítótárának mérete %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Pillanatkép webcíme" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Szinkronizálás" #: print.go:100 msgid "Ten biggest packages:" msgstr "A tíz legnagyobb csomag:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "" "A következő csomagok nem kompatibilisek a processzor architektúrájával:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[1]d szolgáltató érhető el a következőhöz: %[2]d:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Lehet, hogy már fut a pacman. Kis türelmet…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Csomagok által elfoglalt összes hely: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Összes telepített csomag: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Mégis megpróbálja összeállítani őket?" #: print.go:34 msgid "URL" msgstr "Webcím" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Nem tisztítható:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Nem sikerült megtalálni a következő csomagokat:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "" "Nem lehet feldolgozni a következő csomagra való szavazást: %s. hiba: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Nem lehet eltávolítani a következőt: %s: %s" #: print.go:32 msgid "Version" msgstr "Verzió" #: print.go:50 msgid "Votes" msgstr "Szavazatok" #: print.go:87 msgid "Yay version v%s" msgstr "Yay verziója: v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]semmi" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "a felhasználó kérésére megszakítva" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "„-” argumentum lett megadva az stdin bemenete nélkül" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "nem található a PKGBUILD és az .SRCINFO a könyvtárban" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nem található a csomagnév: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "nem található a csomaghoz PKGDEST: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "nem sikerült megtalálni minden szükséges csomagot" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "" "nem sikerült megtalálni egyetlen csomagarchívumot sem a(z) %s listában" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "függőség" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "a csomag fejlesztői ellenőrzése nem sikerült: a(z) „%s” hibát észlelt" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "nem lépett ki sikeresen a szerkesztő, megszakítva: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "hiba a következő forrásfájl(ok) letöltésekor: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "hiba a következő letöltésekor: %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "hiba a tárolóból származó csomagok telepítésekor" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "hiba a következő telepítésekor:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "hiba a következő fordításakor: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "hiba a következő beolvasztásakor: %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "hiba a következő olvasásakor: %s" #: sync.go:36 msgid "error refreshing databases" msgstr "hiba az adatbázisok frissítésekor" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "hiba a következő visszaállításakor: %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "hiba a csomag telepítési okának frissítésekor a következőre: %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "kifejezetten" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "nem sikerült létrehozni a következő könyvtárat: „%s”: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "nem sikerült megnyitni a következő könyvtárat: „%s”: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "nem sikerült elemezni a következőt: %s -- kihagyás: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "nem sikerült elemezni a következőt: %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "nem sikerült elemezni az .SRCINFO-t" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "nem sikerült olvasni a következő konfigurációs fájlt: „%s”: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "nem sikerült lekérni az AUR-gyorsítótárát" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "fejlesztői csomag frissítésének mellőzése (nem található AUR-információ):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "a bemenet túl hosszú" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "érvénytelen szám: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "érvénytelen opció: „%s”" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "érvénytelen opció: „--deps” és „--explicit” nem használható egyszerre" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "érvénytelen tároló" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "érvénytelen érték: %d, az érték nem %d és %d között van" #: pkg/text/input.go:48 msgid "no" msgstr "nem" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "nincs importálandó kulcs" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nem történt lekérdezés" #: local_install.go:66 msgid "no target directories specified" msgstr "nincs megadva célkönyvtár" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nincs mit telepíteni a következőhöz: %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "egyszerre csak egy művelet használható" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "csak egy cél engedélyezett" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "csomag" msgstr[1] "csomagok" #: print.go:187 msgid "package '%s' was not found" msgstr "nem található a(z) „%s” csomag" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "a csomag nem található az AUR-ban" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "a csomag nem található a tárolókban" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "hiba a kulcsok importálásakor" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "AUR-csomagok eltávolítása a gyorsítótárból…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "nem követett AUR-fájlok eltávolítása a gyorsítótárból…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "a(z) %s PKGDEST-jét a makepkg listázza, de nem létezik: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "nincs semmi tennivaló" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "sikertelen CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "nem kezelt művelet" #: cmd.go:450 msgid "unknown-version" msgstr "ismeretlen verzió" #: pkg/text/input.go:47 msgid "yes" msgstr "igen" ================================================ FILE: po/id.po ================================================ # # Translators: # Ludovico, 2022 # Alif Fathur, 2024 # Linerly , 2024 # msgid "" msgstr "" "Last-Translator: Linerly , 2024\n" "Language-Team: Indonesian (https://app.transifex.com/yay-1/teams/123732/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: id\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Direktori build:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Masukkan sebuah nomor (bawaan=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(File build Sudah Ada)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Terpasang)" #: cmd.go:453 msgid " [Installed]" msgstr "[Terpasang]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "tidak ada yang dapat dilakukan" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Semua [Ab]Batalkan [I]Terpasang [No]Belum terpasang atau (1 2 3, 1-3, " "^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s sudah dibuat -- melewatkan proses build" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s belum diatur" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s sudah ada." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s sudah yang terbaru -- melewatkan" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s untuk diupgrade/dipasang." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s juga akan dipasang untuk tindakan ini." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, dibutuhkan oleh: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Tidak ada perubahan -- melewatkan" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: tidak dapat menggunakan target dengan --aur -- melewatkan" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: tidak dapat menggunakan target dengan opsi --repo -- melewatkan" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: mengabaikan pengupgradean paket (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: lokal (%s) lebih baru dari yang AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: seilahkan atur variabel environment AUR_USERNAME dan AUR_PASSWORD untuk " "pemungutan suara" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD diunduh dari ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD terunduh: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Gagal mengunduh PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Menguraikan SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Terpasang)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Terpasang: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Ditinggalkan)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Kedaluwarsa: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Tambahkan %s atau %s ke variabel environment Anda" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Hindari menjalankan yay sebagai root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Periksa dependensi" #: print.go:41 msgid "Check Deps" msgstr "Periksa Ketergantungan" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Memeriksa paket-paket pengembangan..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Membersihkan (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Berkonflik Dengan" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Menghapus (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Ketergantungan" #: print.go:38 msgid "Depends On" msgstr "Tergantung Pada" #: print.go:33 msgid "Description" msgstr "Deskripsi" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Tampilkan perbedaan?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Nonaktifkan pengaturan 'menyediakan' secara bawaan" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Apakah Anda ingin menghapus SEMUA paket AUR dari cache?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Apakah Anda ingin menghapus SEMUA file AUR yang tidak dilacak?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Apakah Anda ingin menghapus semua paket AUR lainnya dari cache?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Edit PKGBUILD dengan?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Terjadi kesalahan melakukan pencarian AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Tidak menyertakan paket dapat menyebabkan peningkatan parsial dan merusak " "sistem" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Eksplisit" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Paket yang terpasang secara eksplisit: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Gagal mencari paket AUR untuk" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Gagal memasang lapisan, menggulirkan ke lapisan berikutnya." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Gagal memasang paket berikut. Intervensi manual dibutuhkan:" #: print.go:45 msgid "First Submitted" msgstr "Pengiriman Pertama" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Paket AUR Yang Ditandai Sebagai Kedaluwarsa:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Total paket-paket asing yang terpasang: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Ditemukan repositori git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB selesai. Tidak ada paket yang terpasang" #: print.go:36 msgid "Groups" msgstr "Grup" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Impor?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Mengimpor kunci-kunci dengan gpg..." #: print.go:46 msgid "Keywords" msgstr "Kata Kunci" #: print.go:47 msgid "Last Modified" msgstr "Terakhir Diubah" #: print.go:35 msgid "Licenses" msgstr "Lisensi" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokal" #: print.go:48 msgid "Maintainer" msgstr "Pemelihara" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Ketergantungan Make" #: print.go:40 msgid "Make Deps" msgstr "Dependensi Make" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Tidak Ditemukan" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Paket Debug AUR Yang Tidak Ditemukan:" #: print.go:31 msgid "Name" msgstr "Nama" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Tidak ada paket AUR yang ditemukan untuk" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Tidak ada paket yang ditemukan untuk" #: print.go:225 msgid "None" msgstr "Tidak Ada" #: print.go:39 msgid "Optional Deps" msgstr "Dependensi Opsional" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Paket AUR Tersendiri (tidak dipelihara):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Kedaluwarsa" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Kunci-kunci PGP membutuhkan pengimporan:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD sudah terbaru, melewati pengunduhan: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILD apa saja untuk diedit?" #: print.go:61 msgid "Package Base" msgstr "Basis Paket" #: print.go:60 msgid "Package Base ID" msgstr "ID Basis Paket" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paket yang tidak ada di AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Paket-paket di bangunBersih?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paket untuk tidak disertakan" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Paket-paket yang tidak disertakan: (mis: \"1 2 3\", \"1-3\", \"^4\", atau " "nama repositori)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paket-paket untuk dipasang (mis: 1 2 3, 1-3, atau ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularitas" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Lanjutkan dengan instalasi?" #: print.go:37 msgid "Provides" msgstr "Menyediakan" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Hapus dependensi make setelah pemasangan?" #: print.go:43 msgid "Replaces" msgstr "Menggantikan" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositori" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositori AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Mencari AUR untuk pembaruan..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Mencari database untuk pembaruan..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Menampilkan paket-paket repositori saja" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Ukuran cache pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Ukuran cache yay%s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sinkron" #: print.go:100 msgid "Ten biggest packages:" msgstr "Sepuluh paket terbesar:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Paket-paket berikut ini tidak kompatibel dengan arsitektur Anda:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Tersedia %[1]d penyedia untuk %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Mungkin ada instansi Pacman yang lain. Menunggu..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Total Ukuran ditempati oleh paket-paket: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Total paket-paket terpasang: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Coba membangun saja?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Tidak dapat membersihkan:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Tidak dapat mencari paket-paket berikut ini:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Tidak dapat menangani pemungutan suara paket untuk: %s. kesalahan: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Tidak dapat menghapus %s: %s" #: print.go:32 msgid "Version" msgstr "Versi" #: print.go:50 msgid "Votes" msgstr "Suara" #: print.go:87 msgid "Yay version v%s" msgstr "Yay versi v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[T]idak Ada" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "membatalkan karena pengguna" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argumen '-' ditentukan tanpa masukan pada stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "tidak dapat menemukan PKGBUILD dan .SRCINFO di direktori" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "tidak dapat menemukan nama paket: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "tidak dapat menemukan PKGDEST untuk: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "tidak dapat menemukan semua paket yang dibutuhkan" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "tidak dapat mencari paket arsip apa pun yang terdaftar di %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "ketergantungan" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "pemeriksaan devel untuk paket gagal: '%s' mengalami sebuah kesalahan" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editor tidak berhasil keluar, membatalkan: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "terjadi kesalahan mengunduh sumber-sumber: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "terjadi kesalahan mendapatkan %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "terjadi kesalahan memasang paket-paket repositori" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "terjadi kesalahan memasang:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "terjadi kesalahan membuat: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "terjadi kesalahan menggabungkan %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "terjadi kesalahan membaca %s" #: sync.go:36 msgid "error refreshing databases" msgstr "terjadi kesalahan memuat ulang basis data" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "terjadi kesalahan mengatur ulang %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "terjadi kesalahan memperbarui alasan pemasangan paket ke %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "eksplisit" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "gagal untuk membuat direktori '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "gagal untuk membuka file konfigurasi '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "gagal untuk menguraikan %s -- melewatkan: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "gagal untuk menguraikan %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "gagal mengurai .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "gagal untuk membaca file konfigurasi '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "gagal mendapatkan Cache aur" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "mengabaikan pembaruan paket devel (tidak ada info AUR yang ditemukan):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "input terlalu panjang" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "Nomor tidak valid: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opsi '%s' tidak valid" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "opsi tidak valid: '--deps' dan '--explicit' tidak boleh digunakan bersama" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositori tidak valid" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "nilai tidak valid: %d tidak di antara %d dan %d" #: pkg/text/input.go:48 msgid "no" msgstr "tidak" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "tidak ada kunci-kunci untuk diimpor" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "tidak ada pencarian yang dilakukan" #: local_install.go:66 msgid "no target directories specified" msgstr "tidak ada sasaran direktori yang ditetapkan" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "tidka ada yang untuk dipasang untuk %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "hanya satu operasi yang dapat digunakan" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "hanya satu target yang diperbolehkan" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paket" #: print.go:187 msgid "package '%s' was not found" msgstr "paket '%s' tidak ditemukan" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paket tidak ditemukan di AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paket tidak ditemukan di repositori" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "terjadi masalah mengimpor kunci-kunci" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "menghapus paket-paket AUR dari cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "menghapus file AUR yang tidak dilacak dari cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST untuk %s didaftarkan oleh makepkg tetapi tidak ada: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "tidak ada yang dapat dilakukan" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "tidak dapat CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operasi tidak diproses" #: cmd.go:450 msgid "unknown-version" msgstr "versi-tidak-diketahui" #: pkg/text/input.go:47 msgid "yes" msgstr "ya" ================================================ FILE: po/it_IT.po ================================================ # # Translators: # Cardellino, 2021 # Giulio Terigi, 2022 # Simone Dotto , 2022 # jheitz223, 2023 # Vincenzo Reale , 2025 # msgid "" msgstr "" "Last-Translator: Vincenzo Reale , 2025\n" "Language-Team: Italian (Italy) (https://app.transifex.com/yay-1/teams/123732/it_IT/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it_IT\n" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Cartella di compilazione:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Inserisci un numero (predefinito=1) " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (I file di compilazione sono già presenti)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Installato)" #: cmd.go:453 msgid " [Installed]" msgstr " [Installato]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " non c'è nulla da fare" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Tutti [Ab]Annulla [I]nstallati [No]nInstallati oppure (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s già fatto -- compilazione saltata" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s non è impostato" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s è presente." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s è aggiornato -- ignorato" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s da aggiornare/installare." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s sarà inoltre installato per questa operazione." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, richiesto da: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Nessuna modifica -- ignorato " #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: non si può usare il target con l'opzione --aur -- ignorato" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: non si può usare il target con l'opzione --repo -- ignorato" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: aggiornamento dei pacchetti ignorato (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: il pacchetto locale (%s) è più nuovo del pacchetto AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: imposta le variabili d'ambiente AUR_USERNAME e AUR_PASSWORD prima di " "votare" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD scaricato da ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD scaricato: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Scaricamento PKGBUILD non riuscito: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analisi SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Installato)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Installato: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orfano)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Obsoleto: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL di AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Aggiungi %s o %s alle tue variabili d'ambiente" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Evita di eseguire yay come root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Controllo dipendenza" #: print.go:41 msgid "Check Deps" msgstr "Controllo dipendenze" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Verifica dei pacchetti di sviluppo in corso..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Pulizia (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Conflitti con" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Eliminazione (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dipendenza" #: print.go:38 msgid "Depends On" msgstr "Dipende da" #: print.go:33 msgid "Description" msgstr "Descrizione" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Differenze da mostrare?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Disabilita l'impostazione 'provides' in modo predefinito" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Vuoi rimuovere TUTTI i pacchetti AUR dalla cache?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Vuoi rimuovere TUTTI i file AUR non monitorati?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Vuoi rimuovere tutti gli altri pacchetti AUR dalla cache?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Con cosa modificare il PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Errore durante la ricerca in AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "L'esclusione di pacchetti potrebbe causare aggiornamenti parziali e " "danneggiare i sistemi" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Esplicito" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Pacchetti installati esplicitamente: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Impossibile trovare il pacchetto AUR per" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" "Installazione del livello non riuscita, passaggio al livello successivo." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Installazione dei seguenti pacchetti non riuscita. È richiesto l'intervento " "manuale:" #: print.go:45 msgid "First Submitted" msgstr "Primo invio" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Pacchetti AUR con flag obsoleto:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Totale dei pacchetti estranei installati: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Trovato repository git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB terminato. Nessun pacchetto è stato installato" #: print.go:36 msgid "Groups" msgstr "Gruppi" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importare?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importazione delle chiavi con gpg in corso..." #: print.go:46 msgid "Keywords" msgstr "Parole chiave" #: print.go:47 msgid "Last Modified" msgstr "Ultima modifica" #: print.go:35 msgid "Licenses" msgstr "Licenze" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Locale" #: print.go:48 msgid "Maintainer" msgstr "Responsabile" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Dipendenza Make" #: print.go:40 msgid "Make Deps" msgstr "Make dipendenze" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Mancante" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Pacchetti AUR di debug mancanti:" #: print.go:31 msgid "Name" msgstr "Nome" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Nessun pacchetto AUR trovato per" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "richiesto da" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nessun pacchetto trovato per" #: print.go:225 msgid "None" msgstr "Nessuno" #: print.go:39 msgid "Optional Deps" msgstr "Dipendenze opzionali" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Pacchetti AUR orfani (non mantenuti):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Obsoleto" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Le chiavi PGP devono essere importate:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD aggiornato, scaricamento ignorato: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Quali PKGBUILD modificare?" #: print.go:61 msgid "Package Base" msgstr "Pacchetto base" #: print.go:60 msgid "Package Base ID" msgstr "ID pacchetto base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pacchetti non in AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Pacchetti da compilare in modo pulito?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pacchetti da escludere" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Pacchetti da escludere: (es: \"1 2 3\", \"1-3\", \"^4\" o il nome del repo)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Pacchetti da installare (es: 1 2 3, 1-3 o ^4)" #: print.go:49 msgid "Popularity" msgstr "Popolarità" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Procedere con l'installazione?" #: print.go:37 msgid "Provides" msgstr "Fornisce" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Rimuovere le dipendenze di make dopo l'installazione?" #: print.go:43 msgid "Replaces" msgstr "Sostituisce" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repository" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repository AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Ricerca di aggiornamenti su AUR in corso..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Ricerca di aggiornamenti nei database in corso..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Visualizzazione dei soli pacchetti del repo" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Dimensione della cache di pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Dimensione della cache di yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronizza" #: print.go:100 msgid "Ten biggest packages:" msgstr "I dieci pacchetti più grandi:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "I seguenti pacchetti non sono compatibili con la tua architettura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Ci sono %[1]d fornitori disponibili per %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "" "Potrebbe esserci un'altra istanza di Pacman in esecuzione. In attesa... " #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Dimensione totale occupata dai pacchetti: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Totale dei pacchetti installati: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Provare a compilarli comunque?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Impossibile pulire:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Impossibile trovare i seguenti pacchetti:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Impossibile gestire il voto per: %s. errore: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Impossibile rimuovere %s: %s" #: print.go:32 msgid "Version" msgstr "Versione" #: print.go:50 msgid "Votes" msgstr "Voti" #: print.go:87 msgid "Yay version v%s" msgstr "Versione di yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]essuno" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "interruzione su richiesta dell'utente in corso" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argomento '-' specificato senza input su stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "impossibile trovare PKGBUILD e .SRCINFO nella cartella" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "impossibile trovare un pacchetto di nome: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "impossibile trovare il PKGDEST per: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "impossibile trovare tutti i pacchetti richiesti" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "impossibile trovare alcun archivio di pacchetti elencato in %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dipendenza" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" "devel check non riuscito per il pacchetto: '%s' ha riscontrato un errore" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "l'editor non è uscito correttamente, interruzione in corso: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "errore durante lo scaricamento dei sorgenti: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "errore durante il recupero di %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "errore durante l'installazione dei pacchetti del repo" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "installazione non riuscita:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "errore durante la creazione: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "errore durante l'unione di %s: %s " #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "errore durante la lettura di %s" #: sync.go:36 msgid "error refreshing databases" msgstr "errore durante l'aggiornamento dei database" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "errore durante il ripristino di %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" "aggiornamento della ragione d'installazione del pacchetto a %s non è " "riuscito" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "esplicito" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "creazione della cartella '%s' non riuscita: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "apertura del file di configurazione '%s' non riuscita: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "analisi di %s non riuscita -- ignorato: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "analisi di %s non riuscita: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "analisi .SRCINFO non riuscita" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "lettura del file di configurazione '%s' non riuscita: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "recupero della cache di AUR non riuscito" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "aggiornamento devel del pacchetto ignorato (nessuna informazione AUR " "trovata):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "input troppo lungo" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "numero non valido: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opzione non valida '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "opzione non valida: '--deps' e '--explicit' non possono essere usate insieme" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repository non valido" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valore non valido: %d non è compreso tra %d e %d" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "nessuna chiave da importare" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nessuna richiesta è stata eseguita" #: local_install.go:66 msgid "no target directories specified" msgstr "nessuna cartella di destinazione specificata" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nulla da installare per %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "è possibile eseguire una sola operazione alla volta" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "è permessa solo una destinazione" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "pacchetto" msgstr[1] "pacchetti" msgstr[2] "pacchetti" #: print.go:187 msgid "package '%s' was not found" msgstr "il pacchetto '%s' non è stato trovato" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "pacchetto non trovato su AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "pacchetto non trovato nei repository" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema durante l'importazione delle chiavi" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "eliminazione dei pacchetti AUR dalla cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "eliminazione dei file AUR non tracciati dalla cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "il PKGDEST per %s è elencato da makepkg, ma non esiste: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr " non c'è nulla da fare" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "impossibile creare un handle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operazione non gestita" #: cmd.go:450 msgid "unknown-version" msgstr "versione-sconosciuta" #: pkg/text/input.go:47 msgid "yes" msgstr "sì" ================================================ FILE: po/ja.po ================================================ # # Translators: # J G , 2021 # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "PO-Revision-Date: 2021-08-13 22:55+0000\n" "Last-Translator: J G , 2021\n" "Language-Team: Japanese (https://www.transifex.com/yay-1/teams/123732/ja/)\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (ビルドファイルが存在)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (インストール済み)" #: cmd.go:453 msgid " [Installed]" msgstr " [インストール済み]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " 何もすることがありません" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]全て [Ab]中止 [I]インストール済み [No]未インストール または (1 2 3, " "1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s は既に作成済みです -- ビルドをスキップします" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s が設定されていません" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s が存在します。" #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s は最新です -- スキップ" #: pkg/upgrade/service.go:292 #, fuzzy msgid "%s to upgrade/install." msgstr "アップグレードするパッケージ。" #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s、必要とするパッケージ: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: 変更なし -- スキップ" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: 対象に --aur オプションを使うことができません -- スキップ" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: 対象に --repo オプションを使うことができません -- スキップ" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: パッケージのアップグレードを無視 (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: ローカルのパッケージ (%s) は AUR (%s) よりも新しいバージョンです" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for voting" msgstr "" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) ABS から PKGBUILD をダウンロード: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 #, fuzzy msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) ABS から PKGBUILD をダウンロード: %s" #: pkg/download/aur.go:82 #, fuzzy msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) ABS から PKGBUILD をダウンロード: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO を解析中: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(インストール済み)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(インストール済み: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(メンテナ不在)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(古いバージョン: %s)" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/dep/dep_graph.go:75 #, fuzzy msgid "AUR" msgstr "URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "%s または %s を環境変数に追加してください" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "yay を root や sudo で実行しないでください。" #: pkg/dep/dep_graph.go:63 #, fuzzy msgid "Check Dependency" msgstr "チェック時の依存パッケージ" #: print.go:41 msgid "Check Deps" msgstr "チェック時の依存パッケージ" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "開発パッケージを確認..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "消去 (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "衝突するパッケージ" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "削除 (%d/%d): %s" #: pkg/dep/dep_graph.go:61 #, fuzzy msgid "Dependency" msgstr "依存するパッケージ" #: print.go:38 msgid "Depends On" msgstr "依存するパッケージ" #: print.go:33 msgid "Description" msgstr "説明" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "差異を表示しますか?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "キャッシュから全ての AUR パッケージを削除しますか?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "未追跡の AUR ファイルを全て削除しますか?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "キャッシュから他の全ての AUR パッケージを削除しますか?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "PKGBUILD をどのエディタで編集しますか?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "AUR 検索時のエラー: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "明示的にインストールしたパッケージ: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 #, fuzzy msgid "Failed to find AUR package for" msgstr "古いバージョンのフラグが立てられた AUR パッケージ:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" #: print.go:45 msgid "First Submitted" msgstr "最初の投稿" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "古いバージョンのフラグが立てられた AUR パッケージ:" #: print.go:90 #, fuzzy msgid "Foreign installed packages: %s" msgstr "全ての外部からインストールされたパッケージ: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "git リポジトリを発見しました: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB が完了しました。パッケージのインストールは行われません" #: print.go:36 msgid "Groups" msgstr "グループ" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "インポートしますか?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "鍵を gpg でインポートします..." #: print.go:46 msgid "Keywords" msgstr "キーワード" #: print.go:47 msgid "Last Modified" msgstr "最終更新" #: print.go:35 msgid "Licenses" msgstr "ライセンス" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "" #: print.go:48 msgid "Maintainer" msgstr "メンテナ" #: pkg/dep/dep_graph.go:62 #, fuzzy msgid "Make Dependency" msgstr "ビルド時の依存パッケージ" #: print.go:40 msgid "Make Deps" msgstr "ビルド時の依存パッケージ" #: pkg/query/aur_warnings.go:71 #, fuzzy msgid "Missing AUR Debug Packages:" msgstr "存在しない AUR パッケージ:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "" #: print.go:31 msgid "Name" msgstr "名前" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "" #: print.go:225 msgid "None" msgstr "なし" #: print.go:39 msgid "Optional Deps" msgstr "任意の依存パッケージ" #: pkg/query/aur_warnings.go:75 #, fuzzy msgid "Orphan (unmaintained) AUR Packages:" msgstr "メンテナが存在しない AUR パッケージ:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "古いバージョン" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP 鍵をインポートする必要があります:" #: pkg/sync/workdir/preparer.go:252 #, fuzzy msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD は最新です、スキップ (%d/%d): %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILD を編集しますか?" #: print.go:60 msgid "Package Base ID" msgstr "パッケージベース ID" #: print.go:61 msgid "Package Base" msgstr "パッケージベース" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "パッケージをクリーンビルドしますか?" #: pkg/dep/dep_graph.go:202 #, fuzzy msgid "Packages to exclude" msgstr "アップグレードするパッケージ。" #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "除外するパッケージ: (例: \"1 2 3\", \"1-3\", \"^4\" またはリポジトリ名)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "インストールするパッケージ (例: 1 2 3, 1-3 または ^4)" #: print.go:49 msgid "Popularity" msgstr "人気度" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "インストールを実行しますか?" #: print.go:37 msgid "Provides" msgstr "提供" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "ビルド時の依存パッケージをインストール後に削除しますか?" #: print.go:43 msgid "Replaces" msgstr "" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "リポジトリ AUR" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "リポジトリ" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "AUR からアップデートを検索..." #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "データベースからアップデートを検索..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "リポジトリのパッケージだけを表示" #: print.go:95 #, fuzzy msgid "Size of pacman cache %s: %s" msgstr "%s のパースに失敗: %s" #: print.go:98 #, fuzzy msgid "Size of yay cache %s: %s" msgstr "%s のパースに失敗: %s" #: print.go:62 msgid "Snapshot URL" msgstr "スナップショット URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "" #: print.go:100 msgid "Ten biggest packages:" msgstr "最も巨大な10のパッケージ:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "" "以下のパッケージはあなたの使っているアーキテクチャと互換性がありません:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 #, fuzzy msgid "There are %[1]d providers available for %[2]s:" msgstr "%[1]d 個のパッケージが %[2]s を提供しています:\n" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "他の Pacman インスタンスが実行中です。待機します..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "パッケージによって使用される合計容量: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "全てのインストールされたパッケージ: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "それでもパッケージをビルドしますか?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 #, fuzzy msgid "Unable to clean:" msgstr "ハンドルを作成できません: %s" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "" #: clean.go:170 #, fuzzy msgid "Unable to remove %s: %s" msgstr "%s のパースに失敗: %s" #: print.go:32 msgid "Version" msgstr "バージョン" #: print.go:50 msgid "Votes" msgstr "投票" #: print.go:87 msgid "Yay version v%s" msgstr "Yay バージョン v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]なし" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "ビルドディレクトリ:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "数字を入力してください (デフォルト=1): " #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "ユーザーによって中止" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "パッケージの名前を見つけることができません: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "PKGDEST を見つけることができません: %s" #: errors.go:9 #, fuzzy msgid "could not find all required packages" msgstr "必要なパッケージを全て確認することができません:" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 #, fuzzy msgid "dependency" msgstr "依存するパッケージ" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "エディタが正しく終了しませんでした、中止: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "ソースのダウンロード時にエラー: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "%s の取得時にエラー: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "リポジトリのパッケージのインストール時にエラー" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 #, fuzzy msgid "error installing:" msgstr "リポジトリのパッケージのインストール時にエラー" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "ビルド時にエラー: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "%s のマージ時にエラー: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "%s の読み取り時にエラー" #: sync.go:36 msgid "error refreshing databases" msgstr "データベースの更新時にエラー" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "%s の再設定時にエラー: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "設定ディレクトリ '%s' の作成に失敗: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "設定ファイル '%s' のオープンに失敗: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "%s のパースに失敗 -- スキップ: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "%s のパースに失敗: %s" #: local_install.go:77 #, fuzzy msgid "failed to parse .SRCINFO" msgstr "%s のパースに失敗: %s" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "設定ファイル '%s' の読み込みに失敗: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "" #: pkg/upgrade/sources.go:27 #, fuzzy msgid "ignoring package devel upgrade (no AUR info found):" msgstr "%s: パッケージのアップグレードを無視 (%s => %s)" #: pkg/text/errors.go:8 msgid "input too long" msgstr "入力が長すぎます" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "不正な数字: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "不正なオプション '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" #: pkg/download/abs.go:22 #, fuzzy msgid "invalid repository" msgstr "リポジトリ" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "不正な値: %d は %d と %d の間にありません" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "インポートする鍵がありません" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "" #: local_install.go:66 msgid "no target directories specified" msgstr "" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "一度に使用できる操作はひとつだけです" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "" #: print.go:187 msgid "package '%s' was not found" msgstr "パッケージ '%s' が見つかりませんでした" #: pkg/download/errors.go:15 #, fuzzy msgid "package not found in AUR" msgstr "パッケージ '%s' が見つかりませんでした" #: pkg/download/abs.go:23 #, fuzzy msgid "package not found in repos" msgstr "パッケージ '%s' が見つかりませんでした" #: pkg/upgrade/service.go:292 #, fuzzy msgid "package" msgid_plural "packages" msgstr[0] "パッケージベース" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "鍵のインポート時にエラー" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "キャッシュから AUR パッケージを削除..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "キャッシュから未追跡の AUR ファイルを削除..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "%s の PKGDEST が makepkg によって指定されていますが存在しません: %s" #: pkg/sync/sync.go:45 #, fuzzy msgid "there is nothing to do" msgstr " 何もすることがありません" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "ハンドルを作成できません: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "ハンドルが存在しない操作" #: cmd.go:450 msgid "unknown-version" msgstr "不明なバージョン" #: pkg/text/input.go:47 msgid "yes" msgstr "yes" #~ msgid " (Target" #~ msgstr " (対象" #~ msgid " (Wanted by: " #~ msgstr " (必要としているパッケージ: " #~ msgid " Input too long" #~ msgstr " 入力が長すぎます" #~ msgid "Installing %s will remove:" #~ msgstr "%s をインストールすることで削除されるパッケージ:" #~ msgid "%s already downloaded -- use -f to overwrite" #~ msgstr "%s は既にダウンロードされています -- -f で上書きできます" #~ msgid "%s and %s unset" #~ msgstr "%s と %s が設定されていません" #~ msgid "%s not satisfied, flushing install queue" #~ msgstr "%s が満たされていません、インストールキューを消去" #~ msgid "Checking for conflicts..." #~ msgstr "衝突を確認..." #~ msgid "Checking for inner conflicts..." #~ msgstr "内部衝突を確認..." #~ msgid "Conflicting packages will have to be confirmed manually" #~ msgstr "衝突するパッケージを手動で確認する必要があります" #~ msgid "Downloaded PKGBUILD (%d/%d): %s" #~ msgstr "PKGBUILD のダウンロード (%d/%d): %s" #~ msgid "Missing ABS packages:" #~ msgstr "存在しない ABS パッケージ:" #~ msgid "Querying AUR..." #~ msgstr "AUR を検索..." #~ msgid "" #~ "\n" #~ "Inner conflicts found:" #~ msgstr "" #~ "\n" #~ "内部衝突が存在します:" #~ msgid "" #~ "\n" #~ "Package conflicts found:" #~ msgstr "" #~ "\n" #~ "パッケージの衝突が存在します:" #~ msgid "error cloning %s: %s" #~ msgstr "%s の複製時にエラー: %s" #~ msgid "error during AUR search: %s" #~ msgstr "AUR の検索時にエラー: %s" #~ msgid "failed to create BuildDir directory '%s': %s" #~ msgstr "BuildDir ディレクトリ '%s' の作成に失敗: %s" #~ msgid "failed to get pkgbuild: %s: %s" #~ msgstr "pkgbuild の取得に失敗: %s: %s" #~ msgid "failed to link %s: %s" #~ msgstr "%s のリンクに失敗: %s" #~ msgid "failed to open vcs file '%s': %s" #~ msgstr "vcs ファイル '%s' のオープンに失敗: %s" #~ msgid "failed to read vcs file '%s': %s" #~ msgstr "vcs ファイル '%s' の読み込みに失敗: %s" #~ msgid "invalid sort mode. Fix with yay -Y --bottomup --save" #~ msgstr "不正なソートモードです。yay -Y --bottomup --save で修正してください" #~ msgid "no packages match search" #~ msgstr "検索にマッチするパッケージがありません" #~ msgid "package conflicts can not be resolved with noconfirm, aborting" #~ msgstr "パッケージの衝突は noconfirm で解決できません、中止" #~ msgid "refusing to install AUR packages as root, aborting" #~ msgstr "AUR パッケージの root によるインストールを拒否します、中止" #~ msgid "failed to create cache directory '%s': %s" #~ msgstr "キャッシュディレクトリ '%s' の作成に失敗: %s" ================================================ FILE: po/ko.po ================================================ # # Translators: # J G, 2021 # JungHee Lee , 2025 # msgid "" msgstr "" "Last-Translator: JungHee Lee , 2025\n" "Language-Team: Korean (https://app.transifex.com/yay-1/teams/123732/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "빌드 디렉터리:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "숫자를 입력하세요 (기본값=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (빌드 파일 존재)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (설치됨)" #: cmd.go:453 msgid " [Installed]" msgstr " [설치됨]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " 할 작업 없음" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]모두 [Ab]중단 [I]설치됨 [No]설치안함 / (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s 이미 빌드됨 -- 빌드 건너뛰는 중" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s 지정되지 않음" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s가 존재합니다." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s 최신 버전임 -- 건너뛰는 중" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "업그레이드/설치하려면 %s 선택합니다." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "이 작업을 위해 %s 또한 설치됩니다." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, 필요한 패키지: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: 변경사항 없음 -- 건너뛰는 중" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: 대상에 --aur 옵션 사용할 수 없음 -- 건너뛰는 중" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: 대상에 --repo 옵션 사용할 수 없음 -- 건너뛰는 중" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: 패키지 업그레이드 무시하는 중 (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: 로컬 (%s) 버전이 AUR (%s)보다 높음" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: 투표를 위해 AUR_USERNAME 및 AUR_PASSWORD 환경 변수를 설정하십시오" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) ABS에서 PKGBUILD 다운로드됨: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD 다운로드됨: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD 다운로드에 실패함: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO 분석하는 중: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(설치됨)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(설치됨: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(고립됨)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(오래됨: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "%s 또는 %s를 환경 변수에 추가합니다" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "root/sudo로 yay를 실행하지 마세요." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "종속성 확인" #: print.go:41 msgid "Check Deps" msgstr "종속성 확인" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "개발 패키지 확인하는 중..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "비우는 중 (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "충돌하는 패키지" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "삭제하는 중 (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "종속성" #: print.go:38 msgid "Depends On" msgstr "종속하는 패키지" #: print.go:33 msgid "Description" msgstr "설명" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "표시할 차이점(Diff)이 있나요?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "기본적으로 '제공 패키지' 설정 비활성화" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "캐시에 있는 AUR 패키지를 모두 제거하시겠습니까?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "추척되지 않은 AUR 파일을 모두 제거하시겠습니까?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "캐시의 다른 AUR 패키지를 모두 제거하시겠습니까?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "다음으로 PKGBUILD를 편집하시겠습니까?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "AUR 검색 중 오류: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "패키지를 제외하면 부분 업그레이드 및 시스템 중단이 발생할 수 있습니다." #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "명시적" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "명시적으로 설치된 패키지: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "다음에 대한 AUR 패키지를 찾지 못함" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "레이어를 설치하지 못하여, 다음 레이어로 롤업합니다." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "다음 패키지를 설치하지 못했습니다. 수동 개입이 필요합니다:" #: print.go:45 msgid "First Submitted" msgstr "처음 제출됨" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "오래된 AUR 패키지로 플래그 지정됨:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "외부 설치된 패키지: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "git 저장소 찾음: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB 완료됨. 패키지가 설치되지 않았습니다" #: print.go:36 msgid "Groups" msgstr "그룹" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "가져올까요?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "gpg로 키 가져오는 중..." #: print.go:46 msgid "Keywords" msgstr "키워드" #: print.go:47 msgid "Last Modified" msgstr "마지막 수정됨" #: print.go:35 msgid "Licenses" msgstr "라이선스" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "로컬" #: print.go:48 msgid "Maintainer" msgstr "유지관리자" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "종속성 만들기" #: print.go:40 msgid "Make Deps" msgstr "종속성 만들기" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "누락됨" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "누락된 AUR 디버그 패키지:" #: print.go:31 msgid "Name" msgstr "이름" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "다음 AUR 패키지를 찾을 수 없음" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "연관된 패키지" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "다음 패키지를 찾을 수 없음" #: print.go:225 msgid "None" msgstr "없음" #: print.go:39 msgid "Optional Deps" msgstr "선택적 종속성" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "고립 (유지 관리되지 않는) AUR 패키지:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "오래됨" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "가져와야 할 PGP 키:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD 최신 버전임, 다운로드 건너뛰는 중: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILD를 편집하시겠습니까?" #: print.go:61 msgid "Package Base" msgstr "패키지 베이스" #: print.go:60 msgid "Package Base ID" msgstr "패키지 베이스 ID" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "AUR에 없는 패키지:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "cleanBuild할 패키지는 무엇인가요?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "제외할 패키지" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "제외할 패키지: (예: \"1 2 3\", \"1-3\", \"^4\" 혹은 저장소 이름)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "설치할 패키지: (예: 1 2 3, 1-3 혹은 ^4)" #: print.go:49 msgid "Popularity" msgstr "인기순" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "설치를 계속 진행하시겠습니까?" #: print.go:37 msgid "Provides" msgstr "제공 패키지" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "설치 후 make 종속성을 제거하시겠습니까?" #: print.go:43 msgid "Replaces" msgstr "교체하기" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "저장소" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "저장소 AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "AUR에서 업데이트 검색하는 중..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "데이터베이스에서 업데이트 검색하는 중..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "저장소 패키지만 표시하는 중" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "pacman 캐시 %s의 크기: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "yay 캐시 %s의 크기: %s" #: print.go:62 msgid "Snapshot URL" msgstr "스냅숏 URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "동기화" #: print.go:100 msgid "Ten biggest packages:" msgstr "가장 큰 10개 패키지:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "다음 패키지는 이 컴퓨터의 아키텍처와 호환되지 않음:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[2]s에 대해 %[1]d개의 의존성 충족 패키지가 있습니다:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "다른 pacman 인스턴스가 실행 중입니다. 대기 중..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "패키지가 차지하는 전체 크기: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "설치된 전체 패키지: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "그래도 빌드할까요?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "비울 수 없음:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "다음 패키지를 찾을 수 없음:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "다음의 패키지 투표를 처리할 수 없습니다: %s. 오류: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "%s 제거할 수 없음: %s" #: print.go:32 msgid "Version" msgstr "버전" #: print.go:50 msgid "Votes" msgstr "투표" #: print.go:87 msgid "Yay version v%s" msgstr "Yay 버전 v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]안함" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "사용자에 의해 중단됨" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "stdin에 대한 입력없이 독립변수 '-' 지정됨" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "디렉터리에서 PKGBUILD 및 .SRCINFO를 찾을 수 없음" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "패키지 이름을 찾을 수 없음: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "PKGDEST를 찾을 수 없음: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "필요한 패키지를 모두 찾을 수 없음" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "%s에 나열된 패키지 아카이브를 찾을 수 없음" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "종속성" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "패키지에 대한 devel 확인 실패함: '%s'에 하나의 오류가 발생했습니다" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "편집기가 제대로 종료되지 않음, 중단하는 중: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "소스 다운로드 중 오류: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "%s 가져오는 중 오류: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "저장소 패키지 설치 중 오류" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "설치 중 오류:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "빌드 중 오류: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "%s 병합하는 중 오류: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "%s 읽는 중 오류" #: sync.go:36 msgid "error refreshing databases" msgstr "데이터베이스 갱신하는 중 오류" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "%s 재설정하는 중 오류: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "패키지 설치 근거를 %s로 업데이트하는 중 오류" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "명시적" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "'%s' 디렉터리 만들기 실패함: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr " '%s' 구성 파일 열기 실패함: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "%s 분석 실패함 -- 건너뜁니다: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "%s 분석 실패함: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr ".SRCINFO를 분석하지 못했습니다" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "'%s' 구성 파일 읽기 실패함: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "AUR 캐시를 검색하지 못했습니다" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "패키지 개발 업그레이드 무시하는 중 (AUR 정보를 찾을 수 없음):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "너무 긴 입력" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "잘못된 숫자: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "잘못된 옵션 '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "잘못된 옵션: '--deps'와 '--explicit'는 함께 사용할 수 없습니다" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "잘못된 저장소" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "잘못된 값: %d가 %d와 %d 사이에 있지 않음" #: pkg/text/input.go:48 msgid "no" msgstr "아니요" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "가져올 키 없음" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "실행된 쿼리가 없습니다" #: local_install.go:66 msgid "no target directories specified" msgstr "지정된 대상 디렉터리가 없습니다" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "%s 용으로 설치할 항목 없음" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "한번에 한 작업만 쓸 수 있음" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "하나의 대상만 허용됩니다" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "패키지" #: print.go:187 msgid "package '%s' was not found" msgstr "'%s' 패키지를 찾지 못했습니다" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "AUR에서 패키지를 찾을 수 없습니다" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "저장소에서 패키지를 찾을 수 없습니다" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "키 가져오는 중 오류" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "캐시에서 AUR 패키지 제거하는 중..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "캐시에서 추적되지 않은 AUR 패키지 제거하는 중..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "makepkg에 등록된 %s의 PKGDEST가 존재하지 않음: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "할 작업 없음" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "CreateHandle 할 수 없음: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "핸들되지 않은 작업" #: cmd.go:450 msgid "unknown-version" msgstr "알 수 없는 버전" #: pkg/text/input.go:47 msgid "yes" msgstr "예" ================================================ FILE: po/nl.po ================================================ # # Translators: # Jonathan van der Steege , 2023 # Loek Le Blansch , 2023 # Luke Rieff, 2023 # Ariejan de Vroom , 2024 # Heimen Stoffels , 2025 # msgid "" msgstr "" "Last-Translator: Heimen Stoffels , 2025\n" "Language-Team: Dutch (https://app.transifex.com/yay-1/teams/123732/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Bouwmap:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Voer een getal in (standaard=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Bouwbestanden zijn aanwezig)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Geïnstalleerd)" #: cmd.go:453 msgid " [Installed]" msgstr "[Geïnstalleerd]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "er valt niets te doen" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "[A]lle [An]nuleren [G]eïnstalleerd [N]iet geïnstalleerd of (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s is al gemaakt -- de bouw wordt overgeslagen" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s is niet ingesteld" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s is aanwezig." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s is actueel -- wordt overgeslagen" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s op te waarderen/installeren." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s wordt ook geïnstalleerd bij het uitvoeren van deze actie." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, benodigd door: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Geen wijzigingen -- wordt overgeslagen" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: kan doel niet gebruiken i.c.m. optie --aur -- wordt overgeslagen" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: kan doel niet gebruiken i.c.m. optie --repo -- wordt overgeslagen" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: pakketupgrade wordt genegeerd (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: de lokale versie (%s) is nieuwer dan de AUR-versie (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: gebruik de omgevingsvariabelen AUR_USERNAME en AUR_PASSWORD om te " "stemmen" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD gedownload van ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD gedownload: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) De PKGBUILD kan niet worden gedownload: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Bezig met verwerken van SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Geïnstalleerd)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Geïnstalleerd: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Onteigend)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Verouderd: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR-url" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Voeg %s of %s toe aan uw omgevingsvariabelen" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Voer yay niet uit als root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Afhankelijkheid controleren" #: print.go:41 msgid "Check Deps" msgstr "Afhankelijkheden controleren" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Bezig met controleren van ontwikkelpakketten…" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Bezig met opruimen… (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Botst met" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Bezig met verwijderen… (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Afhankelijkheid" #: print.go:38 msgid "Depends On" msgstr "Afhankelijk van" #: print.go:33 msgid "Description" msgstr "Beschrijving" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Verschillen tonen?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Schakel de ‘biedt’-instelling standaard uit" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Wilt u ALLE AUR-pakketten verwijderen uit de cache?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Wilt u ALLE niet-gevolgde AUR-bestanden verwijderen?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Wilt u alle andere AUR-pakketten verwijderen uit de cache?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "PKGBUILD bewerken met?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Fout tijdens AUR-zoekactie: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Pakketten uitsluiten kan mogelijk leiden tot onafgeronde upgrades en/of " "beschadigde systemen" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Uitdrukkelijk" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Uitdrukkelijk geïnstalleerde pakketten: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Er is geen AUR-pakket gevonden voor " #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "" "Het installeren van de laag is mislukt - de volgende laag wordt gebruikt" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "De volgende pakketten konden niet worden geïnstalleerd. Handmatige " "interventie is vereist:" #: print.go:45 msgid "First Submitted" msgstr "Eerst ingezonden op" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Verouderde AUR-pakketten:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Van buitenaf geïnstalleerde pakketten: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Git-repo aangetroffen: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB is afgerond. Er zijn geen pakketten zijn geïnstalleerd." #: print.go:36 msgid "Groups" msgstr "Groepen" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importeren?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Bezig met importeren van sleutels…" #: print.go:46 msgid "Keywords" msgstr "Trefwoorden" #: print.go:47 msgid "Last Modified" msgstr "Laatst bewerkt op" #: print.go:35 msgid "Licenses" msgstr "Licenties" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokaal" #: print.go:48 msgid "Maintainer" msgstr "Onderhouder" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Make-afhankelijkheid" #: print.go:40 msgid "Make Deps" msgstr "Make-afhankelijkheden" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Ontbrekend" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Ontbrekende AUR-foutopsporingspakketten:" #: print.go:31 msgid "Name" msgstr "Naam" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Geen AUR-pakket gevonden voor " #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "benodigd door" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Geen AUR-pakket gevonden voor " #: print.go:225 msgid "None" msgstr "Geen" #: print.go:39 msgid "Optional Deps" msgstr "Optionele afhankelijkheden" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Onteigende (niet-onderhouden) AUR-pakketten:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Verouderd" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Te importeren pgp-sleutels:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "" "De PKGBUILD is actueel, dus het downloaden van ‘%s’ wordt overgeslagen." #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Te bewerken PKGBUILDs?" #: print.go:61 msgid "Package Base" msgstr "Pakketbasis" #: print.go:60 msgid "Package Base ID" msgstr "Basis-id van pakket" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pakketten niet in AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Te herbouwen pakketten?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Uitgesloten pakketten" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Uit te sluiten pakketten: (bijv. ‘1 2 3’, ‘1-3’, ‘^4’ of pakketbronnaam)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Te installeren pakketten (bijv. 1 2 3, 1-3 of ^4)" #: print.go:49 msgid "Popularity" msgstr "Populariteit" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Doorgaan met installeren?" #: print.go:37 msgid "Provides" msgstr "Biedt" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Make-afhankelijkheden verwijderen na installatie?" #: print.go:43 msgid "Replaces" msgstr "Vervangt" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Pakketbron" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR-repo" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Bezig met zoeken naar AUR-updates…" #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Bezig met zoeken naar updates in databanken…" #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Toont alleen repo-pakketten" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Omvang van pacman-cache %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Omvang van yay-cache %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Momentopname-url" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchroniseren" #: print.go:100 msgid "Ten biggest packages:" msgstr "Tien grootste pakketten:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "De volgende pakketten zijn niet compatibel met uw architectuur:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Er zijn %[1]d bronnen die %[2]s aanbieden:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Wellicht is er een ander proces van Pacman bezig. Aan het wachten…" #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Totale omvang van pakketten: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Totaalaantal geïnstalleerde pakketten: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Toch doorgaan met bouwen?" #: print.go:34 msgid "URL" msgstr "Url" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Het opruimen is mislukt:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "De volgende pakketten zijn niet aangetroffen:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "De stem op ‘%s’ kan niet worden verwerkt. Foutmelding: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "‘%s’ kan niet worden verwijderd: %s" #: print.go:32 msgid "Version" msgstr "Versie" #: print.go:50 msgid "Votes" msgstr "Stemmen" #: print.go:87 msgid "Yay version v%s" msgstr "Yay-versie v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[G]een" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "afbreken vanwege gebruiker" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "‘-’ als optie ingevoerd zonder invoer op stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "Er zijn geen PKGBUILD en .SRCINFO aangetroffen in de map" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "Er is geen pakket met de naam “%v” aangetroffen" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "Er is geen PKGDEST aangetroffen voor ‘%s’" #: errors.go:9 msgid "could not find all required packages" msgstr "Niet alle vereiste pakketten zijn aangetroffen" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "Er zijn geen pakketarchieven aangetroffen in %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "afhankelijkheid" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "Ontwikkelcontrole van pakket mislukt: fout opgetreden in ‘%s’" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "De bewerker is niet goed afgesloten - het proces wordt afgebroken: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "De bronnen kunnen niet worden opgehaald: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "‘%s’ kan niet worden opgehaald: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "De pakketten kunnen niet worden geïnstalleerd" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "Het installeren van de volgende pakketten is mislukt:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "Het maken van de volgende pakketten is mislukt: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "‘%s’ kan niet worden samengevoegd: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "‘%s’ kan niet worden uitgelezen" #: sync.go:36 msgid "error refreshing databases" msgstr "De databanken kunnen niet worden vernieuwd" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "‘%s’ kan niet worden hersteld: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "Het bijwerken van de installatiereden naar %s is mislukt" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "Uitdrukkelijk" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "‘%s’ kan niet worden aangemaakt: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "‘%s’ kan niet worden geopend: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "‘%s’ kan niet worden verwerkt -- wordt overgeslagen: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "‘%s’ kan niet worden verwerkt: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr ".SRCINFO kan niet worden verwerkt" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "‘%s’ kan niet worden uitgelezen: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "De AUR-cache kan niet worden opgehaald" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "De ontwikkelupgrade is genegeerd (geen AUR-informatie aangetroffen):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "De invoer is te lang" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "Ongeldig getal: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "Ongeldige optie: ‘%s’" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "Ongeldige optie: ‘--deps’ en ‘--explicit’ mogen niet gelijktijdig worden " "gebruikt" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "Ongeldige pakketbron" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "Ongeldige waarde: %d ligt niet tussen %d en %d" #: pkg/text/input.go:48 msgid "no" msgstr "nee" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "Er zijn geen te importeren sleutels" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "Er is geen zoekactie uitgevoerd" #: local_install.go:66 msgid "no target directories specified" msgstr "Er zijn geen doelmappen opgegeven" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "Er is niets te installeren voor %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "Er mag slechts één actie per keer worden uitgevoerd" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "Er is slechts één doel toegestaan" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "Pakket" msgstr[1] "Pakketten" #: print.go:187 msgid "package '%s' was not found" msgstr "‘%s’ is niet aangetroffen" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "Het pakket is niet aangetroffen in de AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "Het pakket is niet aangetroffen in de pakketbronnen" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "De sleutels kunnen niet worden geïmporteerd" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "Bezig met verwijderen van AUR-pakketten uit cache…" #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "Bezig met verwijderen van niet-gevolgde AUR-bestanden uit cache…" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "De PKGDEST van ‘%s’ wordt vermeld door makepkg, maar bestaat niet: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "Er is niets meer te doen" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "CreateHandle kan niet worden uitgevoerd: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "Onafgehandelde actie" #: cmd.go:450 msgid "unknown-version" msgstr "Onbekende versie" #: pkg/text/input.go:47 msgid "yes" msgstr "ja" ================================================ FILE: po/pl.po ================================================ # # Translators: # J G, 2021 # Oskar , 2022 # A_Salata, 2024 # SMT Broadcast, 2024 # Chair n, 2024 # Paweł Bernaciak, 2024 # Mariusz Bubak, 2024 # Bartłomiej Konecki, 2024 # Tadeusz Magura-Witkowski, 2024 # Tymon Marek, 2025 # iwo wolani, 2025 # Artur Wdowik, 2026 # msgid "" msgstr "" "Last-Translator: Artur Wdowik, 2026\n" "Language-Team: Polish (https://app.transifex.com/yay-1/teams/123732/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Folder kompilacji:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Wybierz wartość (domyślnie=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Pliki Budowy Istnieją)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (zainstalowano)" #: cmd.go:453 msgid " [Installed]" msgstr " [zainstalowano]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " nie ma nic do zrobienia" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Wszystkie [Ab]Anuluj [I]Zainstalowane [No]Nie zainstalowane lub (1 2 " "3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s już jest zbudowany - pomijam budowanie" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s jest nieokreślony" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s jest już obecny." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s jest aktualny - pomijam" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s do aktualizacji/instalacji." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s również zostanie zainstalowanie w ramach operacji." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, wymagane przez: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Bez zmian - pomijam" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: nie jest dostępny z --aur - pomijam" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: nie jest dostępny z --repo - pomijam" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ignoruję aktualizację pakietu (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: wersja lokalna (%s) jest nowsza niż ta z AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: wprowadź proszę zmienne środowiskowe AUR_USERNAME oraz AUR_PASSWORD by " "móc głosować" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Pobrano PKGBUILD z ABS-u: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Pobrany PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Nie udało się pobrać PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Przetwarzam SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Zainstalowana)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Zainstalowano: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Osierocona)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Nieaktualny od: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Dodaj %s lub %s do zmiennych środowiskowych" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Unikaj uruchamiania yay jako root lub z sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Sprawdzanie Zależności" #: print.go:41 msgid "Check Deps" msgstr "Sprawdź Zależności" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Sprawdzanie pakietów w wersjach rozwojowych..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Czyszczenie (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Koliduje z" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Usuwanie (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Zależność" #: print.go:38 msgid "Depends On" msgstr "Zależy od" #: print.go:33 msgid "Description" msgstr "Opis" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Pokazać różnice?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Domyślnie wyłącza ustawienie 'provides' " #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Czy chcesz usunąć WSZYSTKIE pakiety AUR z pamięci podręcznej?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Czy chcesz usunąć WSZYSTKIE nieśledzone pliki AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Czy chcesz usunąć wszystkie inne pakiety AUR z pamięci podręczniej?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Edytor PKGBUILD-ów?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Błąd podczas wyszukiwania w AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Wykluczanie pakietów może spowodować częściową aktualizacje i uszkodzenie " "systemu" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Jawne" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Jawnie zainstalowane pakiety: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Nie znaleziono pakietu AUR dla" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Niepowodzenie zainstalowania warstwy, przewijanie do następnej warstwy" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Nie udało sie zainstalować następujących pakietów. Wymagana interwencja " "użytkownika:" #: print.go:45 msgid "First Submitted" msgstr "Zamieszczono po raz pierwszy" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Pakiety AUR oznaczone jako nieaktualne:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Obce zainstalowane pakiety: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Znaleziono repozytorium git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "Zakończono GenDB. Nie zainstalowano żadnych pakietów" #: print.go:36 msgid "Groups" msgstr "Grupy" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Zaimportować?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importowanie kluczy przy użyciu gpg..." #: print.go:46 msgid "Keywords" msgstr "Słowa kluczowe" #: print.go:47 msgid "Last Modified" msgstr "Ostatnio zmodyfikowano" #: print.go:35 msgid "Licenses" msgstr "Licencje" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokalne" #: print.go:48 msgid "Maintainer" msgstr "Opiekun" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Zależność Budowania" #: print.go:40 msgid "Make Deps" msgstr "Zbuduj zależności" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Zaginiony" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Brakujące Pakiety Debugowania AUR:" #: print.go:31 msgid "Name" msgstr "Nazwa" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Nie znaleziono pakietu AUR dla" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nie znaleziono pakietu dla" #: print.go:225 msgid "None" msgstr "Brak" #: print.go:39 msgid "Optional Deps" msgstr "Opcjonalne zależności" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Osierocone (nieutrzymywane) pakiety AUR:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Nieaktualne" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Klucze PGP wymagające zaimportowania:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD jest aktualny, pomijam pobieranie: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Edytować PKGBUILD-y?" #: print.go:61 msgid "Package Base" msgstr "Podstawa Pakietu" #: print.go:60 msgid "Package Base ID" msgstr "ID Podstawy Pakietu" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pakiety nie występujace w AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Pakiety do zbudowania od zera?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pakiety do wykluczenia" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Wykluczone pakiety: (np.: \"1 2 3\", \"1-3\", \"^4\" lub nazwa repozytorium)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Pakiety do zainstalowania (np.: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularność" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Kontynuować instalację?" #: print.go:37 msgid "Provides" msgstr "Dostarcza" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Usunąć zależności potrzebne do zbudowania po instalacji?" #: print.go:43 msgid "Replaces" msgstr "Zastępuje" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repozytorium" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repozytorium AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Poszukuję aktualizacji w AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Poszukuję aktualizacji w bazie danych..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Pokazuję tylko pakiety z repozytoriów" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Rozmiar pamięci podręcznej Pacmana %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Rozmiar pamięci podręcznej yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshota" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchronizacja" #: print.go:100 msgid "Ten biggest packages:" msgstr "Dziesięć największych pakietów:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Te pakiety nie są kompatybilne z architekturą Twojego systemu:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Jest %d możliwych źródeł dla %s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Prawdopodobnie działa teraz inna instancja Pacmana. Czekam..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Łączny rozmiar zajmowany przez pakiety: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Łączna liczba zainstalowanych pakietów: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Spróbować je zbudować mimo wszystko?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Nie można sprzątnąć:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Nie udało się znaleźć następujących pakietów:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Nie udało się obsłużyć oceny pakietu dla: %s. err: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Nie udało się usunąć %s: %s" #: print.go:32 msgid "Version" msgstr "Wersja" #: print.go:50 msgid "Votes" msgstr "Głosów" #: print.go:87 msgid "Yay version v%s" msgstr "Wersja yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]ic" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "przerwane przez użytkownika" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' określony bez danych wejściowych" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "nie znaleziono PKGBUILD i .SRCINFO w katalogu" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nie odnalazłem pakietu: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "nie odnalazłem PKGDEST dla: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "nie znaleziono wszystkich wymaganych pakietów" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "nie udało się znaleźć żadnych archiwów pakietów z listy %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "zależność" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "błąd sprawdzania wersji rozwojowej: '%s' napotkało na błąd" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "edytor nie zamknął się poprawnie, anuluję: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "błąd ściągania źródeł: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "błąd podczas ściągania %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "błąd podczas instalowania pakietów z repozytorium" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "Błąd podczas instalacji:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "błąd podczas budowania: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "błąd podczas scalania %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "błąd podczas odczytywania %s" #: sync.go:36 msgid "error refreshing databases" msgstr "błąd odświeżania baz danych" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "błąd podczas resetowania %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "błąd aktualizacji pakietu z przyczyn %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "jawne" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "nie udało się stworzyć katalogu '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "nie udało się otworzyć pliku konfiguracyjnego '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "nie udało się przetworzyć %s - pomijam: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "nie udało się przetworzyć %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "nie udało się przetworzyć .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "nie udało się odczytać pliku konfiguracyjnego '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "Nie udało się pozyskać pamięci podręcznej AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignorowanie aktualizacji wersji rozwojowej (brak informacji w AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "zbyt długie dane wejściowe" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "niepoprawna liczba: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "niepoprawna opcja '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "niepoprawna opcja: '--deps' i '--explicit' nie mogą być razem używane" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "Nieprawidłowe repozytorium" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "niepoprawna wartość: %d nie jest pomiędzy %d a %d" #: pkg/text/input.go:48 msgid "no" msgstr "nie" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "brak kluczy do zaimportowania" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nie uruchomiono żadnego polecenia" #: local_install.go:66 msgid "no target directories specified" msgstr "Nie określono folderu docelowego" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "Nic do instalowania dla %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "tylko jedna operacja może być użyta na raz" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "dozwolony jest tylko jeden cel" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "pakiet" msgstr[1] "pakietów" msgstr[2] "pakietów" msgstr[3] "pakiety" #: print.go:187 msgid "package '%s' was not found" msgstr "pakiet '%s' nie został odnaleziony" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "Nie znaleziono pakietu w AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "Nie znaleziono pakietu w repozytoriach" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "błąd importowania kluczy" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "usuwanie z pamięci podręcznej pakietów z AUR..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "usuwanie z pamięci podręcznej nieśledzonych plików z AUR..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST dla %s jest wyszczególniony w makepkg, ale nie istnieje: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "Nie ma nic do zrobienia" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "błąd w CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "nieobsłużona operacja" #: cmd.go:450 msgid "unknown-version" msgstr "nieznana wersja" #: pkg/text/input.go:47 msgid "yes" msgstr "tak" ================================================ FILE: po/pl_PL.po ================================================ # # Translators: # J G, 2021 # Oskar , 2022 # A_Salata, 2024 # SMT Broadcast, 2024 # Chair n, 2024 # Paweł Bernaciak, 2024 # Mariusz Bubak, 2024 # Bartłomiej Konecki, 2024 # Tadeusz Magura-Witkowski, 2024 # Tymon Marek, 2025 # iwo wolani, 2025 # Artur Wdowik, 2026 # msgid "" msgstr "" "Last-Translator: Artur Wdowik, 2026\n" "Language-Team: Polish (https://app.transifex.com/yay-1/teams/123732/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Folder kompilacji:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Wybierz wartość (domyślnie=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Pliki Budowy Istnieją)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (zainstalowano)" #: cmd.go:453 msgid " [Installed]" msgstr " [zainstalowano]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " nie ma nic do zrobienia" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Wszystkie [Ab]Anuluj [I]Zainstalowane [No]Nie zainstalowane lub (1 2 " "3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s już jest zbudowany - pomijam budowanie" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s jest nieokreślony" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s jest już obecny." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s jest aktualny - pomijam" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s do aktualizacji/instalacji." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s również zostanie zainstalowanie w ramach operacji." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, wymagane przez: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Bez zmian - pomijam" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: nie jest dostępny z --aur - pomijam" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: nie jest dostępny z --repo - pomijam" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ignoruję aktualizację pakietu (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: wersja lokalna (%s) jest nowsza niż ta z AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: wprowadź proszę zmienne środowiskowe AUR_USERNAME oraz AUR_PASSWORD by " "móc głosować" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Pobrano PKGBUILD z ABS-u: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Pobrany PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Nie udało się pobrać PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Przetwarzam SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Zainstalowana)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Zainstalowano: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Osierocona)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Nieaktualny od: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Dodaj %s lub %s do zmiennych środowiskowych" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Unikaj uruchamiania yay jako root lub z sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Sprawdzanie Zależności" #: print.go:41 msgid "Check Deps" msgstr "Sprawdź Zależności" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Sprawdzanie pakietów w wersjach rozwojowych..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Czyszczenie (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Koliduje z" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Usuwanie (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Zależność" #: print.go:38 msgid "Depends On" msgstr "Zależy od" #: print.go:33 msgid "Description" msgstr "Opis" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Pokazać różnice?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Domyślnie wyłącza ustawienie 'provides' " #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Czy chcesz usunąć WSZYSTKIE pakiety AUR z pamięci podręcznej?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Czy chcesz usunąć WSZYSTKIE nieśledzone pliki AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Czy chcesz usunąć wszystkie inne pakiety AUR z pamięci podręczniej?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Edytor PKGBUILD-ów?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Błąd podczas wyszukiwania w AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Wykluczanie pakietów może spowodować częściową aktualizacje i uszkodzenie " "systemu" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Jawne" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Jawnie zainstalowane pakiety: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Nie znaleziono pakietu AUR dla" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Niepowodzenie zainstalowania warstwy, przewijanie do następnej warstwy" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Nie udało sie zainstalować następujących pakietów. Wymagana interwencja " "użytkownika:" #: print.go:45 msgid "First Submitted" msgstr "Zamieszczono po raz pierwszy" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Pakiety AUR oznaczone jako nieaktualne:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Obce zainstalowane pakiety: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Znaleziono repozytorium git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "Zakończono GenDB. Nie zainstalowano żadnych pakietów" #: print.go:36 msgid "Groups" msgstr "Grupy" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Zaimportować?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importowanie kluczy przy użyciu gpg..." #: print.go:46 msgid "Keywords" msgstr "Słowa kluczowe" #: print.go:47 msgid "Last Modified" msgstr "Ostatnio zmodyfikowano" #: print.go:35 msgid "Licenses" msgstr "Licencje" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokalne" #: print.go:48 msgid "Maintainer" msgstr "Opiekun" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Zależność Budowania" #: print.go:40 msgid "Make Deps" msgstr "Zbuduj zależności" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Zaginiony" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Brakujące Pakiety Debugowania AUR:" #: print.go:31 msgid "Name" msgstr "Nazwa" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Nie znaleziono pakietu AUR dla" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nie znaleziono pakietu dla" #: print.go:225 msgid "None" msgstr "Brak" #: print.go:39 msgid "Optional Deps" msgstr "Opcjonalne zależności" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Osierocone (nieutrzymywane) pakiety AUR:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Nieaktualne" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Klucze PGP wymagające zaimportowania:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD jest aktualny, pomijam pobieranie: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Edytować PKGBUILD-y?" #: print.go:61 msgid "Package Base" msgstr "Podstawa Pakietu" #: print.go:60 msgid "Package Base ID" msgstr "ID Podstawy Pakietu" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pakiety nie występujace w AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Pakiety do zbudowania od zera?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pakiety do wykluczenia" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Wykluczone pakiety: (np.: \"1 2 3\", \"1-3\", \"^4\" lub nazwa repozytorium)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Pakiety do zainstalowania (np.: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularność" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Kontynuować instalację?" #: print.go:37 msgid "Provides" msgstr "Dostarcza" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Usunąć zależności potrzebne do zbudowania po instalacji?" #: print.go:43 msgid "Replaces" msgstr "Zastępuje" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repozytorium" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repozytorium AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Poszukuję aktualizacji w AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Poszukuję aktualizacji w bazie danych..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Pokazuję tylko pakiety z repozytoriów" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Rozmiar pamięci podręcznej Pacmana %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Rozmiar pamięci podręcznej yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshota" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchronizacja" #: print.go:100 msgid "Ten biggest packages:" msgstr "Dziesięć największych pakietów:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Te pakiety nie są kompatybilne z architekturą Twojego systemu:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Jest %d możliwych źródeł dla %s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Prawdopodobnie działa teraz inna instancja Pacmana. Czekam..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Łączny rozmiar zajmowany przez pakiety: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Łączna liczba zainstalowanych pakietów: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Spróbować je zbudować mimo wszystko?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Nie można sprzątnąć:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Nie udało się znaleźć następujących pakietów:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Nie udało się obsłużyć oceny pakietu dla: %s. err: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Nie udało się usunąć %s: %s" #: print.go:32 msgid "Version" msgstr "Wersja" #: print.go:50 msgid "Votes" msgstr "Głosów" #: print.go:87 msgid "Yay version v%s" msgstr "Wersja yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]ic" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "przerwane przez użytkownika" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' określony bez danych wejściowych" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "nie znaleziono PKGBUILD i .SRCINFO w katalogu" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nie odnalazłem pakietu: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "nie odnalazłem PKGDEST dla: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "nie znaleziono wszystkich wymaganych pakietów" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "nie udało się znaleźć żadnych archiwów pakietów z listy %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "zależność" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "błąd sprawdzania wersji rozwojowej: '%s' napotkało na błąd" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "edytor nie zamknął się poprawnie, anuluję: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "błąd ściągania źródeł: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "błąd podczas ściągania %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "błąd podczas instalowania pakietów z repozytorium" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "Błąd podczas instalacji:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "błąd podczas budowania: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "błąd podczas scalania %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "błąd podczas odczytywania %s" #: sync.go:36 msgid "error refreshing databases" msgstr "błąd odświeżania baz danych" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "błąd podczas resetowania %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "błąd aktualizacji pakietu z przyczyn %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "jawne" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "nie udało się stworzyć katalogu '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "nie udało się otworzyć pliku konfiguracyjnego '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "nie udało się przetworzyć %s - pomijam: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "nie udało się przetworzyć %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "nie udało się przetworzyć .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "nie udało się odczytać pliku konfiguracyjnego '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "Nie udało się pozyskać pamięci podręcznej AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignorowanie aktualizacji wersji rozwojowej (brak informacji w AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "zbyt długie dane wejściowe" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "niepoprawna liczba: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "niepoprawna opcja '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "niepoprawna opcja: '--deps' i '--explicit' nie mogą być razem używane" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "Nieprawidłowe repozytorium" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "niepoprawna wartość: %d nie jest pomiędzy %d a %d" #: pkg/text/input.go:48 msgid "no" msgstr "nie" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "brak kluczy do zaimportowania" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nie uruchomiono żadnego polecenia" #: local_install.go:66 msgid "no target directories specified" msgstr "Nie określono folderu docelowego" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "Nic do instalowania dla %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "tylko jedna operacja może być użyta na raz" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "dozwolony jest tylko jeden cel" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "pakiet" msgstr[1] "pakietów" msgstr[2] "pakietów" msgstr[3] "pakiety" #: print.go:187 msgid "package '%s' was not found" msgstr "pakiet '%s' nie został odnaleziony" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "Nie znaleziono pakietu w AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "Nie znaleziono pakietu w repozytoriach" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "błąd importowania kluczy" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "usuwanie z pamięci podręcznej pakietów z AUR..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "usuwanie z pamięci podręcznej nieśledzonych plików z AUR..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST dla %s jest wyszczególniony w makepkg, ale nie istnieje: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "Nie ma nic do zrobienia" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "błąd w CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "nieobsłużona operacja" #: cmd.go:450 msgid "unknown-version" msgstr "nieznana wersja" #: pkg/text/input.go:47 msgid "yes" msgstr "tak" ================================================ FILE: po/pt.po ================================================ # # Translators: # J G, 2021 # Eduardo Ervideira, 2023 # Hugo Carvalho , 2023 # Matheus Calegaro , 2024 # msgid "" msgstr "" "Last-Translator: Matheus Calegaro , 2024\n" "Language-Team: Portuguese (https://app.transifex.com/yay-1/teams/123732/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Diretório de compilação:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Introduza um número (por padrão=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Existem ficheiros de compilação)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Instalado)" #: cmd.go:453 msgid " [Installed]" msgstr " [Instalado]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " não há nada a fazer" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Todos [Ab]ortar [I]nstalado [No]Não instalado or (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s já efetuado -- a ignorar compilação" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s não está definido" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s está presente." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s está atualizado -- a ignorar" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s a atualizar/instalar." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s também será instalado para esta operação." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, necessário para: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Sem modificações -- a ignorar" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "" "%s: não é possível utilizar a opção --aur com este pacote -- a ignorar" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "" "%s: não é possível utilizar a opção --repo com este pacote -- a ignorar" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: a ignorar atualização de pacote (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: local (%s) é mais recente que AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: definir variáveis de ambiente AUR_USERNAME e AUR_PASSWORD para votação" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD ABS transferido: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD transferido: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Falha ao transferir PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analisando SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Instalado)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Instalado: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Orfão)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Desatualizado: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Adicione %s ou %s às suas variáveis de ambiente" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Evite executar o yay como root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Verificar dependências" #: print.go:41 msgid "Check Deps" msgstr "Dependências de verificação" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "A verificar pacotes de desenvolvimento..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "A limpar (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Em conflito com" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "A eliminar (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dependências" #: print.go:38 msgid "Depends On" msgstr "Depende de" #: print.go:33 msgid "Description" msgstr "Descrição" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Diffs a mostrar?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Desativar a definição 'fornece' por defeito" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Quer remover todos os pacotes AUR da cache?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Quer remover todos os ficheiros AUR não rastreados?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Quer remover todos os outros pacotes AUR da cache?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Editar PKGBUILD com?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Erro durante a pesquisa AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "A exclusão de pacotes pode causar atualizações parciais e quebra de sistemas" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explícito" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Pacotes explicitamente instalados: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Falha ao localizar pacote AUR para" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Falha ao instalar camada, indo até à camada seguinte." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Falha ao instalar os seguintes pacotes. É necessária a intervenção manual:" #: print.go:45 msgid "First Submitted" msgstr "Primeira submissão" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Pacotes AUR marcados como desatualizados:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Pacotes externos instalados: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Repositório git encontrado: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB terminado. Nenhum pacote foi instalado" #: print.go:36 msgid "Groups" msgstr "Grupos" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importar?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "A importar chaves com gpg..." #: print.go:46 msgid "Keywords" msgstr "Palavras-chave" #: print.go:47 msgid "Last Modified" msgstr "Última Modificação" #: print.go:35 msgid "Licenses" msgstr "Licenças" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Responsável pela manutenção" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Criar dependências" #: print.go:40 msgid "Make Deps" msgstr "Dependências Make" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Em falta" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Pacotes de depuração AUR em falta:" #: print.go:31 msgid "Name" msgstr "Nome" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Nenhum pacote AUR encontrado para" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nenhum pacote encontrado para" #: print.go:225 msgid "None" msgstr "Nenhum" #: print.go:39 msgid "Optional Deps" msgstr "Dependências opcionais" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Pacotes AUR órfãos (não mantidos):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Desatualizado" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Chaves PGP a importar:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD atualizado, a ignorar a transferência: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs a editar?" #: print.go:61 msgid "Package Base" msgstr "Pacote Base" #: print.go:60 msgid "Package Base ID" msgstr "ID do Pacote Base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pacotes que não estão no AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Pacotes a compilar a limpo?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pacotes a excluir" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Pacotes a excluir: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Pacotes a instalar (eg: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularidade" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Proceder com a instalação?" #: print.go:37 msgid "Provides" msgstr "Fornece" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Remover as dependências de make pós-instalação?" #: print.go:43 msgid "Replaces" msgstr "Substitui" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositório" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositório AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "A procurar atualizações na AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "A procurar atualizações nos repositórios..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Mostrando apenas pacotes do repositório" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Tamanho da cache do pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Tamanho da cache do yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronizar" #: print.go:100 msgid "Ten biggest packages:" msgstr "Dez maiores pacotes:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Os seguintes pacotes não são compatíveis com a sua arquitetura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Existem %[1]d provedores disponíveis para %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Poderá haver outro Pacman em execução. Aguardar..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Tamanho total ocupado por pacotes: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Total instalado de pacotes: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Tentar compilar mesmo assim?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Não foi possível limpar:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Não foi possível encontrar os seguintes pacotes:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Não foi possível gerir votação de pacote para: %s err: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Não foi possível remover %s: %s" #: print.go:32 msgid "Version" msgstr "Versão" #: print.go:50 msgid "Votes" msgstr "Votos" #: print.go:87 msgid "Yay version v%s" msgstr "Versão Yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]enhum" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "a abortar por opção do utilizador" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argumento '-' especificado sem entrada em stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "não foi possível localizar PKGBUILD e .SRCINFO no diretório" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nome de pacote não encontrado: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "não encontrado PKGDEST para: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "não foi possível localizar todos os pacotes necessários" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "não foi possível localizar nenhum arquivo de pacotes listados em %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dependência" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "" "falha na pesquisa por pacotes de desenvolvimento: '%s' encontrou um erro" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editor não terminou com sucesso, abortando: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "erro ao descarregar fontes: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "erro ao buscar %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "erro ao instalar pacotes de repositório" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "erro ao instalar:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "erro ao compilar: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "erro ao fundir %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "erro ao ler %s" #: sync.go:36 msgid "error refreshing databases" msgstr "erro ao recarregar base de dados" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "erro ao repor %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "erro ao atualizar instalação do pacote motivo para %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explícito" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "falha ao criar pasta '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "falha ao abrir arquivo de configuração '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "falha ao analisar %s -- ignorando: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "falha ao analisar %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "falha ao analisar .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "falha ao ler ficheiro de configuração '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "falha ao recuperar cache aur" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "a ignorar a atualização de desenvolvimento do pacote (não foi encontrada " "informação AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "input demasiado longo" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "número inválido: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "opção inválida '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "opção inválida: '--deps' e '--explicit' não podem ser usados em conjunto" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositório inválido" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valor inválido: %d não está entre %d e %d" #: pkg/text/input.go:48 msgid "no" msgstr "não" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "nenhuma chave a importar" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nenhuma análise foi executada" #: local_install.go:66 msgid "no target directories specified" msgstr "nenhum diretório de destino especificado" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nada a instalar para %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "apenas uma operação pode ser utilizada ao mesmo tempo" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "apenas um destino é permitido" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "pacote" msgstr[1] "pacotes" msgstr[2] "pacotes" #: print.go:187 msgid "package '%s' was not found" msgstr "pacote '%s' não foi encontrado" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "pacote não encontrado no AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "pacote não encontrado nos repositórios" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema ao importar chaves" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "a remover pacotes AUR da cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "a remover ficheiros AUR não rastreados da cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "o PKGDEST para %s é listado pelo makepkg mas não existe: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "não há nada a fazer" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "não é possível executar CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operação não implementada" #: cmd.go:450 msgid "unknown-version" msgstr "versão-desconhecida" #: pkg/text/input.go:47 msgid "yes" msgstr "sim" ================================================ FILE: po/pt_BR.po ================================================ # # Translators: # J G, 2021 # Otto Micheletti , 2021 # Harrison Ferreira, 2022 # Zisa, 2022 # Fernando Macedo, 2023 # Felipe Avelar, 2023 # Lucas Miranda , 2023 # Matheus Calegaro, 2024 # Mateus Eduardo, 2025 # msgid "" msgstr "" "Last-Translator: Mateus Eduardo, 2025\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/yay-1/teams/123732/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Diretório de compilação:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Digite um número (padrão=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Arquivos de Build Existem)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Instalado)" #: cmd.go:453 msgid " [Installed]" msgstr " [Instalado]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " não há nada a ser feito" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Todos [Ab]Abortar [I]Instalados [No]Não Instalados ou (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s já construído -- pulando build" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s não está definido" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s está presente." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s está atualizado -- pulando" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s para atualizar/instalar." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s também será instalado para essa operação." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, requeridos por: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Sem mudanças -- pulando" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "" "%s: não é possível utilizar o argumento '--aur' com este pacote -- pulando" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "" "%s: não é possível utilizar o argumento '--repo' com este pacote -- pulando" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ignorando atualização do pacote (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: o pacote local (%s) é mais recente que o do AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: por favor, configure as variáveis de ambiente AUR_USERNAME e " "AUR_PASSWORD para votar" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD baixado do ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD transferido: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Falha ao baixar PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analisando SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Instalado)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Instalado: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Órfão)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Desatualizado: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL do AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Adicione %s ou %s às suas variáveis de ambiente" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Evite executar o yay como root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Verificar dependências" #: print.go:41 msgid "Check Deps" msgstr "Dependências de verificação" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Verificando pacotes em desenvolvimento..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Limpando (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Em conflito com" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Removendo (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Dependências" #: print.go:38 msgid "Depends On" msgstr "Depende de" #: print.go:33 msgid "Description" msgstr "Descrição" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Exibir diffs?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Desativar a definição 'fornece' por padrão" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Você deseja remover TODOS os pacotes AUR do cache?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Você deseja remover TODOS os pacotes AUR não monitorados?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Você deseja remover todos os outros pacotes AUR do cache?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Editar PKGBUILD com?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Erro durante a busca no AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "A exclusão de pacotes pode causar atualizações parciais e quebrar sistemas" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explícito" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Pacotes explicitamente instalados: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Falha ao localizar pacote AUR para" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Falha ao instalar camada, indo até à camada seguinte." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Falha ao instalar os seguintes pacotes. É necessária a intervenção manual:" #: print.go:45 msgid "First Submitted" msgstr "Primeira submissão" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Pacotes AUR marcados como desatualizados:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Pacotes externos instalados: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Repositório git encontrado: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB finalizado. Nenhum pacote foi instalado" #: print.go:36 msgid "Groups" msgstr "Grupos" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importar?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importando chaves com gpg..." #: print.go:46 msgid "Keywords" msgstr "Palavras-chave" #: print.go:47 msgid "Last Modified" msgstr "Última modificação" #: print.go:35 msgid "Licenses" msgstr "Licenças" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Local" #: print.go:48 msgid "Maintainer" msgstr "Mantenedor" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Criar dependências" #: print.go:40 msgid "Make Deps" msgstr "Dependências Make" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Em falta" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Pacotes de debug do AUR em falta:" #: print.go:31 msgid "Name" msgstr "Nome" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Nenhum pacote AUR localizado para" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "exigido por" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nenhum pacote encontrado para" #: print.go:225 msgid "None" msgstr "Nenhum" #: print.go:39 msgid "Optional Deps" msgstr "Depêndencias Opcionais" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Pacotes AUR órfãos (não mantidos):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Desatualizado" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Chaves PGP que precisam ser importadas:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD atualizado, ignorando a transferência: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDs a serem editados?" #: print.go:61 msgid "Package Base" msgstr "Pacotes Base" #: print.go:60 msgid "Package Base ID" msgstr "ID do Pacote Base" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Pacotes que não estão no AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Limpar e construir quais pacotes?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Pacotes a excluir" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Pacotes a excluir: (ex: \"1 2 3\", \"1-3\", \"^4\" ou nome do repositório)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Pacotes a instalar (ex: 1 2 3, 1-3 ou ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularidade" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Prosseguir com a instalação?" #: print.go:37 msgid "Provides" msgstr "Fornece" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Remover dependências make após a instalação?" #: print.go:43 msgid "Replaces" msgstr "Substitui" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repositório" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repositório AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Procurando atualizações no AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Procurando atualizações nos bancos de dados..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Mostrando somente pacotes do repositório" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Tamanho da cache do pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Tamanho da cache do yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL para Snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Sincronizar" #: print.go:100 msgid "Ten biggest packages:" msgstr "Dez maiores pacotes:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Os seguintes pacotes não são compatíveis com a sua arquitetura:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Existem %[1]d provedores disponíveis para %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Pode haver outra instância do Pacman em execução. Aguardando..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Espaço total ocupado por pacotes: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Total de pacotes instalados: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Tentar construí-los mesmo assim?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Não foi possível limpar:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Não foi possível encontrar os seguintes pacotes:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Não foi possível gerir votação de pacote para: %s err: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Não foi possível remover %s: %s" #: print.go:32 msgid "Version" msgstr "Versão" #: print.go:50 msgid "Votes" msgstr "Votos" #: print.go:87 msgid "Yay version v%s" msgstr "Versão do Yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Nenhum" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "abortando devido ao usuário" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argumento '-' especificado sem entrada em stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "não foi possível localizar PKGBUILD e .SRCINFO no diretório" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "não foi possível encontrar o nome do pacote: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "não foi possível encontrar PKGDEST para: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "não foi possível localizar todos os pacotes necessários" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "não foi possível localizar nenhum arquivo de pacotes listados em %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "dependência" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "a busca por pacotes devel falhou: '%s' encontrou um erro" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editor não finalizou com sucesso, abortando: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "erro ao descarregar as fontes: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "erro ao buscar %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "erro ao instalar pacotes do repositório" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "erro ao instalar:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "erro ao construir: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "erro ao mesclar %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "erro ao ler %s" #: sync.go:36 msgid "error refreshing databases" msgstr "erro ao recarregar base de dados" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "erro ao resetar %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "erro ao atualizar instalação do pacote motivo para %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explícito" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "falha ao criar diretório '%s':%s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "falha ao abrir o arquivo de configuração '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "falha ao analisar %s -- pulando: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "falha ao analisar %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "falha ao analisar .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "falha ao ler o arquivo de configuração '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "falha ao recuperar cache aur" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "ignorando a atualização do desenvolvimento do pacote (não foi localizada " "informação AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "input muito longo" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "número inválido: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "argumento inválido '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "opção inválida: os argumentos '--deps' e '--explicit' não podem ser usados " "juntos " #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "repositório inválido" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "valor inválido: %d não está entre %d e %d" #: pkg/text/input.go:48 msgid "no" msgstr "não" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "nenhuma chave para ser importada" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nenhuma busca foi executada" #: local_install.go:66 msgid "no target directories specified" msgstr "nenhum diretório de destino especificado" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nada a instalar para %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "somente uma operação pode ser utilizada de cada vez" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "apenas um destino é permitido" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "pacote" msgstr[1] "pacotes" msgstr[2] "pacotes" #: print.go:187 msgid "package '%s' was not found" msgstr "pacote '%s' não foi encontrado" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "pacote não encontrado no AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "pacote não encontrado nos repositórios" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problema ao importar as chaves" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "removendo pacotes AUR do cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "removendo arquivos do AUR não rastreados do cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "o PKGDEST para %s está listado pelo makepkg porém não existe: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr " não há nada a ser feito" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "não foi possível executar CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "operação sem manuseio" #: cmd.go:450 msgid "unknown-version" msgstr "versão-desconhecida" #: pkg/text/input.go:47 msgid "yes" msgstr "sim" ================================================ FILE: po/ru.po ================================================ # # Translators: # Vladislav Zenkov, 2022 # Kira Malinova, 2023 # Victor Golovanenko , 2023 # Dancheg97 F, 2023 # falixfresh, 2023 # Ravenso BlacK, 2023 # Vladislav Grechannik, 2024 # Vladimir Yerilov, 2025 # Alexandra Harbar, 2025 # Maxim Tyuterev, 2026 # msgid "" msgstr "" "Last-Translator: Maxim Tyuterev, 2026\n" "Language-Team: Russian (https://app.transifex.com/yay-1/teams/123732/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Каталог сборки:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Введите номер (по умолчанию=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Файлы сборки существуют)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Установлено)" #: cmd.go:453 msgid " [Installed]" msgstr "[Установлено]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "делать больше нечего" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [В]се [От]менить [У]становленные [Не]установленные или (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s уже собран -- сборка пропускается" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s не задан" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s уже существует." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s обновлён -- пропуск" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "обновить/установить." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "также будет установлен для этой операции." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, требуется пакету: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Нет изменений -- пропуск" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: невозможно использовать цель с параметром --aur -- skipping" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: невозможно использовать цель с параметром --repo -- skipping" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: игнорирование обновления пакета (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: локальный пакет (%s) новее, чем в AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: пожалуйста, укажите переменные среды AUR_USERNAME и AUR_PASSWORD для " "голосования" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Загружен PKGBUILD из ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Загружен PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Не удалось загрузить PKGBUILD:" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Анализ SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Установлено)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Установлено: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Осиротевший)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Устарел: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "Ссылка на AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Добавьте %s или %s в переменные среды" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Избегайте запуска yay от имени root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Проверка зависимости" #: print.go:41 msgid "Check Deps" msgstr "Зависимости, требуемые для проверки" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Проверка пакетов разработки..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Очистка (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Конфликтует с" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Удаление (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Зависимость" #: print.go:38 msgid "Depends On" msgstr "Зависит от" #: print.go:33 msgid "Description" msgstr "Описание" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Показать изменения?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Отключите настройку 'обеспечивает' по умолчанию" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Вы хотите удалить ВСЕ пакеты AUR из кэша?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Вы хотите удалить ВСЕ неотслеживаемые файлы AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Вы хотите удалить все остальные пакеты AUR из кэша?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Отредактировать PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Ошибка поиска в AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Исключение пакетов может привести к частичному обновлению и сломать систему" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Явно" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Явно установленные пакеты: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Не удалось найти пакет AUR для" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Ошибка установки слоя, переход на следующий слой." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Не удалось установить следующие пакеты — требуется ручное вмешательство:" #: print.go:45 msgid "First Submitted" msgstr "Впервые представленный" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Пакеты AUR, помеченные как устаревшие:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Cторонних пакетов установлено: " #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Найден git репозиторий: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "Генерирование БД завершено. Никакие пакеты не были установлены" #: print.go:36 msgid "Groups" msgstr "Группы" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Импортировать?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Импортирование ключей с помощью GPG..." #: print.go:46 msgid "Keywords" msgstr "Ключевые слова" #: print.go:47 msgid "Last Modified" msgstr "Последнее изменение" #: print.go:35 msgid "Licenses" msgstr "Лицензии" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Локальный" #: print.go:48 msgid "Maintainer" msgstr "Сопровождающий" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Создание зависимости" #: print.go:40 msgid "Make Deps" msgstr "Зависимости, требуемые для сборки" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Отсутствующие" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Отсутствующие в AUR пакеты для отладки:" #: print.go:31 msgid "Name" msgstr "Название" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Не найден пакет AUR для" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "нужен для" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Не найден пакет для" #: print.go:225 msgid "None" msgstr "Нет" #: print.go:39 msgid "Optional Deps" msgstr "Дополнительные зависимости" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Осиротевшие (неподдерживаемые) пакеты AUR:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Устарел" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Ключи PGP, требующие импорта:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD находится в актуальном состоянии, пропускается загрузка: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Отредактировать PKGBUILD?" #: print.go:61 msgid "Package Base" msgstr "Базовый пакет" #: print.go:60 msgid "Package Base ID" msgstr "Идентификатор пакета" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Пакет не найден в AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Пакеты для чистой сборки?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Пакеты для исключения" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Исключить пакеты: (напр.: \"1 2 3\", \"1-3\", \"^4\" или название репозитория)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Пакеты для установки: (напр.: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Популярность" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Продолжить установку?" #: print.go:37 msgid "Provides" msgstr "Предоставляет" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Удалить зависимости сборки после установки?" #: print.go:43 msgid "Replaces" msgstr "Заменяет" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Репозиторий" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Репозиторий AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Поиск обновлений пакетов AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Поиск обновлений в базах данных..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Показываются только пакеты из репозиториев" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Размер кэша pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Размер кэша yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Ссылка снимка" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Синхронизация" #: print.go:100 msgid "Ten biggest packages:" msgstr "Десять самых больших пакетов:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Следующие пакеты несовместимы с вашей архитектурой:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[2]s доступен в %[1]d источниках:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Возможно, запущен другой процесс Pacman. Ожидание..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Суммарный размер, занятый пакетами: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Всего установлено пакетов: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Попытаться собрать, несмотря на несовместимость?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Невозможно очистить:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Не удалось найти следующие пакеты:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Не удалось обработать голосование за: %s. ошибка: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Не удается удалить %s: %s" #: print.go:32 msgid "Version" msgstr "Версия" #: print.go:50 msgid "Votes" msgstr "Голосов" #: print.go:87 msgid "Yay version v%s" msgstr "Версия Yay: v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[Н]ет" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "прервано пользователем" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "аргумент '-' задан без ввода из stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "не удалось найти PKGBUILD и .SRCINFO в каталоге" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "Неудалось найти пакет с названием: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "не удалось найти PKGDEST для: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "не удалось найти все необходимые пакеты" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "Не удалось найти архивы пакетов в %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "зависимости" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "проверка пакета '%s' не завершена из-за ошибки" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "неудачный выход из редактора, отмена: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "ошибка загрузки исходников: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "ошибка скачивания %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "ошибка установки пакетов из репозиториев" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "ошибка установки:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "ошибка сборки: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "ошибка объединения %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "ошибка чтения %s" #: sync.go:36 msgid "error refreshing databases" msgstr "ошибка обновления базы данных" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "ошибка сброса %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "ошибка изменения причины установки пакета на %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "явно" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "ошибка создания каталога '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "ошибка открытия файла конфигурации '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "ошибка при анализе %s --- пропуск: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "ошибка при анализе %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "ошибка при анализе .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "не удалось прочитать файл конфигурации '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "не удалось получить кэш AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "Игнорирование обновления пакета devel (информация об AUR не найдена):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "ввод слишком длинный" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "неверное число: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "неверная опция '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "неверная опция: невозможно использовать опции '--deps' и '--explicit' " "одновременно" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "недействительный репозиторий" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "неверное значение: %d не в промежутке между %d и %d " #: pkg/text/input.go:48 msgid "no" msgstr "нет" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "нет ключей для импорта" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "запрос не был выполнен" #: local_install.go:66 msgid "no target directories specified" msgstr "не выбраны целевые директории" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "нечего установить из %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "только одна операция может быть вызвана за один раз" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "разрешена только одна цель" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "пакет" msgstr[1] "пакеты" msgstr[2] "пакеты" msgstr[3] "пакеты" #: print.go:187 msgid "package '%s' was not found" msgstr "пакет '%s' не найден" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "пакет не найден в AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "пакет не найден в репозиториях" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "проблема импортирования ключей" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "удаление пакетов AUR из кэша..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "удаление неотслеживаемых файлов AUR из кэша..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "файл PKGDEST для %s указан в выводе makepkg, но не существует: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "делать больше нечего" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "невозможно выполнить CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "необработанная операция" #: cmd.go:450 msgid "unknown-version" msgstr "неизвестная версия" #: pkg/text/input.go:47 msgid "yes" msgstr "да" ================================================ FILE: po/ru_RU.po ================================================ # # Translators: # J G, 2021 # makvasm, 2021 # antsif.a, 2022 # Demir Yerli, 2022 # Антон Карасев , 2023 # Ravenso BlacK, 2024 # Victor Golovanenko , 2024 # Vladislav Zenkov, 2024 # falixfresh, 2024 # Kira Malinova, 2024 # Dancheg97 F, 2024 # Vladislav Grechannik, 2024 # ratijas, 2024 # Vladimir Yerilov, 2025 # Maxim Tyuterev, 2026 # msgid "" msgstr "" "Last-Translator: Maxim Tyuterev, 2026\n" "Language-Team: Russian (Russia) (https://app.transifex.com/yay-1/teams/123732/ru_RU/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru_RU\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Каталог сборки:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Введите номер (по умолчанию = 1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (файлы сборки существуют)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Установлено)" #: cmd.go:453 msgid " [Installed]" msgstr " [Установлено]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "делать нечего" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Все [Ab]Прервать [I]Установленные [No]Неустановленные или (1 2 3, 1-3," " ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s уже собран --- сборка пропускается" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s не задан" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s уже существует." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%sобновлён до последней версии --- пропуск" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s обновить/установить." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s также будет установлен для этой операции." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, требуется пакету: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Нет изменений --- пропуск" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: невозможно использовать цель с опцией --aur --- пропуск" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: невозможно использовать цель с опцией --repo --- пропуск" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: пропуск обновления пакета (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: локальная версия (%s) новее, чем в AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "пожалуйста, укажите AUR_USERNAME и AUR_PASSWORD в переменных среды для " "возможности голосовать" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Скачан PKGBUILD из ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Скачан PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Не удалось загрузить PKGBUILD:" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Анализ SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(установлено)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(установлено: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(сирота в AUR)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(устарел: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Добавьте %s или %s в переменные окружения" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Не запускайте yay от имени root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Проверка зависимости" #: print.go:41 msgid "Check Deps" msgstr "Зависимости проверки" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Проверка пакетов в разработке (-git, -svn и т.п.)..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Очистка (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Конфликтует с" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Удаление (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Зависимость" #: print.go:38 msgid "Depends On" msgstr "Зависит от" #: print.go:33 msgid "Description" msgstr "Описание" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Показать изменения?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Отключите настройку 'обеспечивает' по умолчанию" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Вы хотите удалить все пакеты AUR из кэша?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Вы хотите удалить все неотслеживаемые файлы AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Вы хотите удалить все остальные пакеты AUR из кэша?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Отредактировать PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Ошибка поиска в AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Исключение пакетов может привести к частичным обновлениям и поломкам системы" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Явно" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Пакеты, установленные по запросу пользователя: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Не удалось найти пакет AUR для" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Ошибка установки слоя, переход на следующий слой." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Не удалось установить следующие пакеты. Необходимо ручное вмешательство:" #: print.go:45 msgid "First Submitted" msgstr "Впервые загружен на AUR" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Пакеты AUR, помеченные как устаревшие:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Установлено сторонних пакетов: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Найден git-репозиторий: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB завершён без установки пакетов" #: print.go:36 msgid "Groups" msgstr "Группы" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Импортировать?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Импортирование ключей с помощью gpg..." #: print.go:46 msgid "Keywords" msgstr "Ключевые слова" #: print.go:47 msgid "Last Modified" msgstr "Последнее изменение" #: print.go:35 msgid "Licenses" msgstr "Лицензии" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Локальный" #: print.go:48 msgid "Maintainer" msgstr "Сопровождающий" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Зависимость сборки" #: print.go:40 msgid "Make Deps" msgstr "Зависимости сборки" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Отсутствующие" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Отсутствующие в AUR пакеты для отладки:" #: print.go:31 msgid "Name" msgstr "Название" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Не найден пакет AUR для" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "нужен для" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Не найден пакет для" #: print.go:225 msgid "None" msgstr "Нет" #: print.go:39 msgid "Optional Deps" msgstr "Факультативные зависимости" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Брошенные (без сопровождающего) пакеты AUR:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Устарел" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Ключи PGP, требующие импорта:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "Актуальный PKGBUILD, пропуск загрузки" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Отредактировать PKGBUILD?" #: print.go:61 msgid "Package Base" msgstr "Группа пакетов" #: print.go:60 msgid "Package Base ID" msgstr "ID группы пакетов" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Пакет не найден в AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Пакеты, для которых требуется очистить кэш сборки?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Пакеты для исключения" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Пакеты для исключения: (пример: \"1 2 3\", \"1-3\", \"^4\" или имя в репозитории)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Пакеты для установки (пример: 1 2 3, 1-3 или ^4)" #: print.go:49 msgid "Popularity" msgstr "Популярность" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Продолжить установку?" #: print.go:37 msgid "Provides" msgstr "Обеспечивает" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Удалить зависимости для сборки после установки?" #: print.go:43 msgid "Replaces" msgstr "Заменяет" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Репозиторий" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Репозиторий AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Поиск обновлений в AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Поиск обновлений в базе данных..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Показываются только пакеты из репозиториев" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Размер кэша \"pacman\"" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Размер кэша \"yay\"" #: print.go:62 msgid "Snapshot URL" msgstr "URL архива" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Синхронизация" #: print.go:100 msgid "Ten biggest packages:" msgstr "10 самых объёмных пакетов:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Следующие пакеты несовместимы с архитектурой вашего процессора:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[2]s доступен в %[1]d источниках:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Возможно, запущен другой экземпляр Pacman. Ожидание..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Суммарный размер, занятый пакетами: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Всего установлено пакетов: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Попытаться собрать их, несмотря на несовместимость?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Невозможно очистить:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Не удалось найти следующие пакеты:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Не удалось обработать голосование за: %s. ошибка: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Не удается удалить %s: %s" #: print.go:32 msgid "Version" msgstr "Версия" #: print.go:50 msgid "Votes" msgstr "Голосов" #: print.go:87 msgid "Yay version v%s" msgstr "Yay версии v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Нет" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "прерывание по запросу пользователя" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "аргумент '-' задан без ввода из stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "не удалось найти PKGBUILD и .SRCINFO в каталоге" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "невозможно найти пакет: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "невозможно найти файл PKGDEST для: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "не удалось найти все необходимые пакеты" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "Не удалось найти архивы пакетов в %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "зависимость" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "Проверка для пакета '%s' не закончилась успехом" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "выход из редактора был неуспешным, прерывание: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "ошибка загрузки исходников: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "ошибка получения %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "ошибка установки пакетов из репозиториев" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "ошибка установки:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "ошибка сборки: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "ошибка объединения %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "ошибка чтения %s" #: sync.go:36 msgid "error refreshing databases" msgstr "ошибка обновления базы данных" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "ошибка сброса %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "ошибка изменения причины установки пакета на %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "явно" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "ошибка создания каталога '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "ошибка открытия файла конфигурации '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "ошибка при анализе %s --- пропуск: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "ошибка при анализе %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "ошибка при анализе .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "ошибка чтения файла конфигурации '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "не удалось получить кэш AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "Игнорирование обновления пакета devel (информация об AUR не найдена):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "ввод слишком длинный" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "некорректный номер: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "некорректный параметр '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "некорректный параметр: '--deps' и '--explicit' не могут быть использованы " "вместе" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "недействительный репозиторий" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "некорректное значение: %d не лежит между %d и %d" #: pkg/text/input.go:48 msgid "no" msgstr "нет" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "нет ключей для импорта" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "запрос не был выполнен" #: local_install.go:66 msgid "no target directories specified" msgstr "не выбраны целевые директории" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "нечего установить из %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "только одна операция может быть вызвана за один раз" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "разрешена только одна цель" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "пакет" msgstr[1] "пакеты" msgstr[2] "пакеты" msgstr[3] "пакеты" #: print.go:187 msgid "package '%s' was not found" msgstr "пакет '%s' не найден" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "пакет не найден в AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "пакет не найден в репозиториях" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "проблема импортирования ключей" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "удаление пакетов AUR из кэша..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "удаление неотслеживаемых файлов AUR из кэша..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "файл PKGDEST для %s означен в выводе makepkg, но не существует: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "делать больше нечего" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "невозможно выполнить CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "неизвестная операция" #: cmd.go:450 msgid "unknown-version" msgstr "неизвестная версия" #: pkg/text/input.go:47 msgid "yes" msgstr "да" ================================================ FILE: po/sk.po ================================================ # # Translators: # Matej Mrenica, 2022 # Peter Cyprich, 2024 # Michal Fusatý, 2024 # msgid "" msgstr "" "Last-Translator: Michal Fusatý, 2024\n" "Language-Team: Slovak (https://app.transifex.com/yay-1/teams/123732/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Adresár zostavenia:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Zadajte číslo (predvolené=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Súbory na zostavenie existujú)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Nainštalované)" #: cmd.go:453 msgid " [Installed]" msgstr "[Nainštalované]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "nie je čo robiť" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Všetko [Ab]Zrušiť [I]Nainštalované [No]Nenainštalované alebo (1 2 3, " "1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s už vytvorené -- preskočenie zostavenia" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s nie je nastavené" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s je prítomný." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s je aktuálny -- preskakujem" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s na aktualizáciu/inštaláciu." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%sbude tiež nainštalovaný pre túto operáciu." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, vyžaduje: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s Žiadne zmeny -- preskakujem" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%snemožno použiť cieľ s voľbou --aur -- preskakujem" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s nemožno použiť cieľ s voľbou --repo -- preskakujem" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s ignorujem aktualizáciu balíčka (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s lokálne (%s) je novšie ako AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s prosím nastavte premenné prostredia AUR_USERNAME (používateľské meno AUR)" " a AUR_PASSWORD (heslo AUR) pre hlasovanie" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Stiahnuté PKGBUILD z ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Stiahnuté PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Nepodarilo sa stiahnuť PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Analýza SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Nainštalované)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Nainštalované: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Osirotené)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Zastarané: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Pridajte %s or %s do vašich premenných prostredia" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Vyhnite sa spúšťaniu yay ako root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Skontrolovať závislosti" #: print.go:41 msgid "Check Deps" msgstr "Skontrolovať závislosti" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Kontrolujem balíčky vývoja..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Čiestenie (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Konflikty s" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Mazanie (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Závislosť" #: print.go:38 msgid "Depends On" msgstr "Závisí na" #: print.go:33 msgid "Description" msgstr "Popis" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Rozdiely na zobrazenie?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Predvolene zrušiť nastavenie 'provides'" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Chcete odstrániť VŠETKY balíky AUR z vyrovnávacej pamäte?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Chcete odstrániť VŠETKY nesledované súbory AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Chcete odstrániť všetky ostatné balíky AUR z vyrovnávacej pamäte?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Upraviť PKGBUILD s?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Chyba počas vyhľadávania AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Vynechanie balíčkov môže spôsobiť čiastočné vylepšienia a zničiť systém" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Explicitne" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Explicitne nainštalované balíky: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Nepodarilo sa nájsť AUR balíček " #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Nepodarilo sa nainštalovať vrstvu, presúvam sa na ďalšiu vrstvu" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "" "Nepodarilo sa nainštalovať nasledovné balíčky. Je potrebná manuálna " "interakcia: " #: print.go:45 msgid "First Submitted" msgstr "Prvý krát predložené" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Balíčky AUR označené ako neaktuálne:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Cudzie nainšťalované balíčky: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Nájdený git repozitár: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB dokončené. Žiadne balíčky neboli nainštalované" #: print.go:36 msgid "Groups" msgstr "Skupiny" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importovať?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importovanie kľúčov pomocou gpg..." #: print.go:46 msgid "Keywords" msgstr "Kľúčové slová" #: print.go:47 msgid "Last Modified" msgstr "Naposledy upravené" #: print.go:35 msgid "Licenses" msgstr "Licencie" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokálne" #: print.go:48 msgid "Maintainer" msgstr "Správca" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Spraviť závislosť" #: print.go:40 msgid "Make Deps" msgstr "Spraviť závislosť" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Chýbajúce" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Chýbajúce AUR balíčky na ladenie: " #: print.go:31 msgid "Name" msgstr "Názov" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Nenašiel sa AUR balíček pre " #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Nenašiel sa balíček pre " #: print.go:225 msgid "None" msgstr "Žiadne" #: print.go:39 msgid "Optional Deps" msgstr "Voliteľné závislosti" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Osirotené (neudržiavané) balíčky AUR: " #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Zastarané" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP kľúče potrebujú importovať:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD aktuálne, preskakujem sťahovanie: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "PKGBUILDy na editáciu?" #: print.go:61 msgid "Package Base" msgstr "Základ balíčka" #: print.go:60 msgid "Package Base ID" msgstr "ID Základu balíčka" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Balíčky, ktoré nie sú v AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Balíčky, ktoré vybudovať nanovo?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Balíčky, ktoré vynechať" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Balíčky, ktoré vynechať: (napr.: \"1 2 3\", \"1-3\" \"^4\" alebo názov " "repozitára)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Balíčky na inštaláciu (napr: 1 2 3, 1-3 or ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularita" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Pokračovať s inštaláciou?" #: print.go:37 msgid "Provides" msgstr "Poskytuje" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Odstrániť make závislosti po inštalácií?" #: print.go:43 msgid "Replaces" msgstr "Nahrádza" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Repozitár" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Repozitár AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Vyhľadávanie aktualizácií v AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Vyhľadávanie aktualizácií v databázach..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Zobrazenie iba repo balíčkov" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Veľkosť pacman cache %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Veľkosť yay cache %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshotu" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synchronizácia" #: print.go:100 msgid "Ten biggest packages:" msgstr "Desať najväčších balíčkov:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Nasledovné balíčky nie sú kompatibilné s vašou architektúrou: " #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "" "Existuje/existujú %d poskytovateľ/poskytovatelia/poskytovateľov pre %s: " #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Môže byť spustená ďalšia inštancia Pacmana. Čakanie..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Celková veľkosť obsadená balíčkami: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Celkový počet nainštalovaných balíčkov: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Pokúsiť sa ich zostaviť aj tak?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Nepodarilo sa vyčistiť:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Nie je možné nájsť nasledujúce balíčky:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Nie je možné spracovať hlasovanie o balíku pre: %s. err: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Nepodarilo sa odstrániť %s: %s" #: print.go:32 msgid "Version" msgstr "Verzia" #: print.go:50 msgid "Votes" msgstr "Hlasy" #: print.go:87 msgid "Yay version v%s" msgstr "Verzia Yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Žiadne" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "prerušenie kvôli používateľovi" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument \"-\" špecifikovaný bez vstupu alebo štandardného vstupu" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "nepodarilo sa nájsť PKGBUILD a .SRCINFO v adresári" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "nepodarilo sa nájsť názov balíčku: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "nemožno nájsť PKGDEST pre: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "nemožno nájsť všetky potrebné balíčky" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "nemožno nájst žiadne archívy balíčkov uvedené v %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "závislosť" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "nepodarilo sa skontrolovať vývoj pre balíček: '%s' postihla chyba" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "editor nebol úspešne ukončený, ruším: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "chyba pri sťahovaní zdrojov: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "načítanie chýb %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "chyba pri inštalácií balíčkov z repozitára" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "chyba pri inštalovaní:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "chyba vytvárania: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "chyba pri zlúčení %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "chyba čítania %s" #: sync.go:36 msgid "error refreshing databases" msgstr "chyba obnovenia databáz" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "chyba resetovania %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "chyba pri aktualizácii dôvodu inštalácie balíka na %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "explicitné" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "nepodarilo sa vytvoriť adresár '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "nepodarilo sa otvoriť konfiguračný súbor '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "nepodarilo sa analyzovať %s -- preskakujem: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "nepodarilo sa analyzovať %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "nepodarilo sa analyzovať .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "nepodarilo sa prečítať konfiguračný súbor '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "nepodarilo sa získať aur cache" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignorujem aktualizáciu balíčka vývoja (nenašli sa AUR informácie):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "vstup príliš dlhý" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "neplatné číslo: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "neplatná možnosť '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "neplatná možnosť: '--deps' a '--explicit' nemôže byť použité spolu" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "neplatný repozitár" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "neplatná hodnota: %d nie je mezdi %d a %d" #: pkg/text/input.go:48 msgid "no" msgstr "nie" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "žiadne kľúče na importovanie" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "nebola vykonaná žiadna požiadavka" #: local_install.go:66 msgid "no target directories specified" msgstr "nie sú špecifikované žiadne adresáre" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "nie je čo inštalovať pre %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "naraz môže byť použitá iba jedna operácia" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "je povolený iba jeden cieľ" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "balíček" msgstr[1] "balíčkov" msgstr[2] "balíčkov" msgstr[3] "balíčky" #: print.go:187 msgid "package '%s' was not found" msgstr "balíček '%s' sa nenašiel" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "balíček sa nenašiel v AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "balíček sa nenašiel v repozitároch" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "problém s importom kľúčov" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "odstraňujem balíčky AUR z cache..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "odstraňujem nesledované AUR balíčky z cache..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST pre %s je uvedený v makepkg, ale neexistuje %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "nie je čo urobiť" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "nepodarilo sa CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "neošetrená operácia" #: cmd.go:450 msgid "unknown-version" msgstr "neznáma-verzia" #: pkg/text/input.go:47 msgid "yes" msgstr "áno" ================================================ FILE: po/sv.po ================================================ # # Translators: # J G, 2021 # August Wikerfors, 2023 # Luna Jernberg , 2026 # msgid "" msgstr "" "Last-Translator: Luna Jernberg , 2026\n" "Language-Team: Swedish (https://app.transifex.com/yay-1/teams/123732/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Byggkatalog:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Ange ett nummer: (standard = 1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (Byggfiler finns)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (Installerad)" #: cmd.go:453 msgid " [Installed]" msgstr " [Installerad]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " inget behöver göras" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]lla [Ab]Avbryt [I]nstallerade [No]EjInstallerade eller (1 2 3, 1-3, " "^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s redan byggt -- hoppar över bygge" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s är inte inställd" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s finns." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s är ajour -- hoppar över" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s att uppgradera/installera." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s kommer också att installeras för denna operation." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, krävs av: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Inga ändringar -- hoppar över" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: kan inte använda mål med växeln --aur -- hoppar över" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: kan inte använda mål med växeln --repo -- hoppar över" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ignorerar paketuppgradering (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: lokalt paket (%s) är nyare än AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: vänligen ställ in miljövariablerna AUR_USERNAME och AUR_PASSWORD för att" " rösta" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Hämtat PKGBUILD från ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Hämtat PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Misslyckades att ladda ner PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Tolkar SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Installerad)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Installerade: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Övergiven)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Föråldrade: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR-webbadress" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Lägg till %s eller %s bland dina miljövariabler" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Undvik att köra yay som root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Kontrollberoende" #: print.go:41 msgid "Check Deps" msgstr "Kontrollberoenden" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Kontrollerar utvecklingspaket..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Rensar (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Krockar med" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Tar bort (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Beroende" #: print.go:38 msgid "Depends On" msgstr "Beror av" #: print.go:33 msgid "Description" msgstr "Beskrivning" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Visa diff?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Inaktivera \"tillhandahåller\" inställning som standard" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Vill du verkligen ta bort ALLA AUR-paket från cachen?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Vill du verkligen ta bort ALLA ospårade AUR-filer?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Vill du verkligen ta bort alla andra AUR-paket från cachen?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Redigera PKGBUILD med?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Fel vid AUR-sökning: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Att exkludera paket kan orsaka partiella uppgraderingar och ha sönder system" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Uttryckligen" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Uttryckligen installerade paket: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Misslyckades att hitta AUR paket för" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Misslyckades att installera lager, rullar upp till nästa lager." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Misslyckades att bygga följande paket. Manuell intervention krävs:" #: print.go:45 msgid "First Submitted" msgstr "Först skapad" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "AUR-paket markerade som föråldrade:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Okända installerade paket: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Hittade git-arkiv: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB klar. Inga paket installerades" #: print.go:36 msgid "Groups" msgstr "Grupper" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Importera?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Importerar nycklar med gpg..." #: print.go:46 msgid "Keywords" msgstr "Nyckelord" #: print.go:47 msgid "Last Modified" msgstr "Senast ändrad" #: print.go:35 msgid "Licenses" msgstr "Licenser" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Lokal" #: print.go:48 msgid "Maintainer" msgstr "Underhållare" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Byggberoende" #: print.go:40 msgid "Make Deps" msgstr "Byggberoenden" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Saknas" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Saknar AUR-felsökningspaket:" #: print.go:31 msgid "Name" msgstr "Namn" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:564 msgid "No AUR package found for" msgstr "Inga AUR paket hittades för" #: pkg/dep/dep_graph.go:564 msgid "required by" msgstr "krävs av:" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Inget paket hittades för" #: print.go:225 msgid "None" msgstr "Inga" #: print.go:39 msgid "Optional Deps" msgstr "Valfria beroenden" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Föräldralösa (icke underhållna) AUR paket:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Föråldrat" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "PGP-nycklar kräver import:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD uppdaterad, hoppar över nedladdning: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Redigera PKGBUILD-filer?" #: print.go:61 msgid "Package Base" msgstr "Grundpaket" #: print.go:60 msgid "Package Base ID" msgstr "Grundpakets-ID" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Paket som inte finns i AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Bygg paket rent (cleanBuild)?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Paket att exkludera" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Paket att hoppa över: (t.ex: \"1 2 3\", \"1-3\", \"^4\" eller " "centralkatalogsnamn)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Paket att installera (t.ex: 1 2 3, 1-3 eller ^4)" #: print.go:49 msgid "Popularity" msgstr "Popularitet" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Fortsätt med installation?" #: print.go:37 msgid "Provides" msgstr "Tillhandahåller" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Ta bort byggberoenden efter installation?" #: print.go:43 msgid "Replaces" msgstr "Ersätter" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Centralkatalog" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Centralkatalog AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Söker efter uppdateringar på AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Söker efter uppdateringar i databaserna..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Visar endast centralkatalogspaket" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Storlek på pacman cache %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Storlek på yay cache %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Snapshot-URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Synk" #: print.go:100 msgid "Ten biggest packages:" msgstr "Tio största paketen:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Följande paket stöds inte av din arkitektur:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Det finns %[1]d tillhandahållare tillgängliga för %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Det kan finns en till instans av pacman som kör. Väntar..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Total paketstorlek: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Antal installerade paket: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Försök att bygga dem ändå?" #: print.go:34 msgid "URL" msgstr "Webbadress" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Kunde inte rensa:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Det gick inte att hitta följande paket:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Kan inte hantera paketröstning för: %s . fel: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Kunde inte ta bort %s: %s" #: print.go:32 msgid "Version" msgstr "Version" #: print.go:50 msgid "Votes" msgstr "Röster" #: print.go:87 msgid "Yay version v%s" msgstr "Yay-version v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Inga" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "avbryter på grund av användare" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "argument '-' specificerat utan indata på stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "kan inte hitta PKGBUILD eller .SRCINFO i katalog" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "kunde inte hitta paketet: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "kunde inte hitta PKGDEST för: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "kunde inte hitta alla paket som krävs" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "kunde inte hitta några paketarkiv listade i %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "beroende" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "Utvecklings koll för paket misslyckades '%s' stötte på ett fel" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "redigeraren avslutades inte korrekt, avbryter: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "fel vid nedladdning av källfiler: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "fel vid hämtning av %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "fel vid installation av centralkatalogspaket" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "fel vid installation:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "fel vid bygge: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "fel vid sammanslagning av %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "fel vid läsning av %s" #: sync.go:36 msgid "error refreshing databases" msgstr "fel vid uppdatering av databaserna" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "fel vid återställning av %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "fel vid uppdatering av paketinstallations anledning till %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "uttryckligen" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "misslyckades med att skapa katalog '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "misslyckades med att öppna konfigurationsfilen '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "misslyckades med att tolka %s -- hoppar över: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "misslyckades med att tolka %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "misslyckades med att tolka .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "misslyckades med att läsa konfigurationsfilen '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "misslyckades med att hämta aur Cache" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ignorerar paket devel uppgradering (ingen AUR info hittades):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "för lång indata" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "ogiltigt nummer: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "ogiltig växel '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "ogiltigt alternativ: '--deps' och '--explicit' kan inte användas tillsammans" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "ogiltig centralkatalog" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "ogiltigt värde: %d är inte mellan %d och %d" #: pkg/text/input.go:48 msgid "no" msgstr "nej" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "inga nycklar att importera" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "ingen fråga utfördes" #: local_install.go:66 msgid "no target directories specified" msgstr "inga målkataloger specificerade" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "inget att installera för %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "bara en operation går att utföra åt gången" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "endast ett mål är tillåtet" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "paket" msgstr[1] "paket" #: print.go:187 msgid "package '%s' was not found" msgstr "paketet '%s' kunde inte hittas" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paketet hittades inte i AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paketet hittades inte i centralkatalogerna" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "ett problem uppstod vid nyckelimporten" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "tar bort AUR-paket från cachen..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "tar bort ospårade AUR-filer från cachen..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "den PKGDEST för %s som står med i makepkg finns ej: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "det finns ingenting att göra" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "misslyckades med att skapa handtag (CreateHandle): %s" #: cmd.go:186 msgid "unhandled operation" msgstr "okänd operation" #: cmd.go:450 msgid "unknown-version" msgstr "okänd-version" #: pkg/text/input.go:47 msgid "yes" msgstr "ja" ================================================ FILE: po/tr.po ================================================ # # Translators: # Ahmet Arda Kavakcı, 2022 # Mehmet Özgür Bayhan , 2023 # can avar, 2023 # yiğit yeten, 2023 # msgid "" msgstr "" "Last-Translator: yiğit yeten, 2023\n" "Language-Team: Turkish (https://app.transifex.com/yay-1/teams/123732/tr/)\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: xgotext\n" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Kurulum Dosyaları Var)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Kuruldu)" #: cmd.go:453 msgid " [Installed]" msgstr "[Kuruldu]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "yapılacak bir şey yok" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]Hepsi [Ab]İptal [I]Kurulmuş [No]Kurulmamış veya (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s çoktan yapıldı -- yapılandırma atlanıyor" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s belirlenmiş değil" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s hâlihazırda bulunuyor." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s güncel -- atlanıyor" #: pkg/upgrade/service.go:292 #, fuzzy msgid "%s to upgrade/install." msgstr "Yükseltilecek/yüklenecek paketler." #: pkg/upgrade/service.go:286 msgid "%s will also be installed for this operation." msgstr "" #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, ihtiyaç duyan: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Değişiklik yok -- atlanıyor" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: hedef --aur seçeneği ile kullanılamaz -- atlanıyor" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: hedef --repo seçeneği ile kullanılamaz -- atlanıyor" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: paket güncellemesi göz ardı ediliyor (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: yerel (%s) AUR'dan (%s) daha güncel" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for voting" msgstr "" "Oy verebilmek için AUR_USERNAME ve AUR_PASSWORD ortam değişkenlerini " "ayarlayın" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD, ABS'den indirildi: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD indirildi: %s" #: pkg/download/aur.go:82 #, fuzzy msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) PKGBUILD indirildi: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) SRCINFO çözümleniyor: %s" #: pkg/query/types.go:72 pkg/query/types.go:103 msgid "(Installed)" msgstr "(Kuruldu)" #: pkg/query/types.go:70 pkg/query/types.go:101 msgid "(Installed: %s)" msgstr "(Kuruldu: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Öksüz bırakıldı)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Güncel değil: %s)" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Ortam değişkenlerinize %s veya %s seçeneklerini ekleyin" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Yay'ı root/sudo olarak çalıştırmaktan kaçının" #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Bağımlılık Kontrolü" #: print.go:41 msgid "Check Deps" msgstr "Bağımlıkları Kontrol Et" #: pkg/upgrade/service.go:90 msgid "Checking development packages..." msgstr "Geliştirme paketleri kontrol ediliyor..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Temizleniyor (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Çakışıyor:" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Siliniyor (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Bağımlılık" #: print.go:38 msgid "Depends On" msgstr "Bağımlı:" #: print.go:33 msgid "Description" msgstr "Açıklama" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Gösterilecek olan değişiklikler (diff)?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Varsayılan olarak 'provides' ayarını geçersiz kıl" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "AUR paketlerinin TÜMÜNÜ önbellekten silmek istiyor musun?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "İzlenmemiş AUR paketlerinin TÜM dosyalarını silmek istiyor musun?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Tüm diğer AUR paketlerini önbellekten silmek istiyor musun?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "PKGBUILD'i neyle düzenleyeceksin?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "AUR araması yaparken hata: %s\n" #: pkg/upgrade/service.go:296 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Paketlerin dışlanması, kısmi yükseltmelere ve sistemlerin bozulmasına neden " "olabilir" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Açık" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Doğrudan kurulan paketler: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Şunun için AUR paketi bulunamadı:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Katman yüklenemedi, sonraki katmana geçiliyor." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "İstenen paketler yüklenemedi. Manüel müdahale gerekli:" #: print.go:45 msgid "First Submitted" msgstr "İlk İletilen" #: pkg/query/aur_warnings.go:79 msgid "Flagged Out Of Date AUR Packages:" msgstr "Eski Olarak İşaretlenmiş AUR Paketleri:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Yabancı kurulan paketler: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Git deposu bulundu: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB tamamlandı. Hiçbir paket kurulmadı" #: print.go:36 msgid "Groups" msgstr "Gruplar" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "İçe Aktar?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "GPG anahtarları içe aktarılıyor..." #: print.go:46 msgid "Keywords" msgstr "Anahtar Kelimeler" #: print.go:47 msgid "Last Modified" msgstr "Son Değiştirme" #: print.go:35 msgid "Licenses" msgstr "Lisanslar" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Yerel" #: print.go:48 msgid "Maintainer" msgstr "Bakımcı:" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Make Bağımlılığı" #: print.go:40 msgid "Make Deps" msgstr "Make Bağımlılıkları" #: pkg/query/aur_warnings.go:71 msgid "Missing AUR Debug Packages:" msgstr "Eksik AUR Hata Ayıklama Paketleri:" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Kayıp" #: print.go:31 msgid "Name" msgstr "İsim" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Şunun için AUR paketi bulunamadı: " #: pkg/dep/dep_graph.go:182 #, fuzzy msgid "No package found for" msgstr "Şunun için AUR paketi bulunamadı: " #: print.go:225 msgid "None" msgstr "Yok" #: print.go:39 msgid "Optional Deps" msgstr "İsteğe Bağlı Bağımlılıklar" #: pkg/query/aur_warnings.go:75 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Orphan (bakımsız) AUR Paketleri:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Eski" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "İçe aktarılması gereken PGP anahtarları:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD güncel, indirme geçiliyor: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Düzenlenecek PKGBUILD'ler?" #: print.go:60 msgid "Package Base ID" msgstr "Paket Temel Kimliği (ID):" #: print.go:61 msgid "Package Base" msgstr "Paket Temeli" #: pkg/query/aur_warnings.go:67 msgid "Packages not in AUR:" msgstr "AUR'da bulunmayan paketler:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "cleanBuild yapılacak paketler?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Dışlanacak paketler" #: pkg/upgrade/service.go:295 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Dışlanacak paketler: (ör. \"1 2 3\", \"1-3\", \"^4\" veya depo ismi)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Kurulacak paketler (ör. 1 2 3, 1-3 veya ^4)" #: print.go:49 msgid "Popularity" msgstr "Popülerlik" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Kurmaya devam et?" #: print.go:37 msgid "Provides" msgstr "Sağlar:" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Make bağımlılıkları kurulum sonrası silinsin mi?" #: print.go:43 msgid "Replaces" msgstr "" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR Deposu" #: print.go:30 pkg/db/ialpm/alpm.go:191 msgid "Repository" msgstr "Depo" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:72 msgid "Searching AUR for updates..." msgstr "AUR güncellemeleri aranıyor..." #: pkg/upgrade/service.go:160 msgid "Searching databases for updates..." msgstr "Veritabanlarında güncellemeler aranıyor..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Sadece depo paketleri gösteriliyor" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "pacman önbelleğinin boyutu: %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "yay önbelleğinin boyutu: %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "Anlık görüntü URL'si" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Senkronize" #: print.go:100 msgid "Ten biggest packages:" msgstr "En büyük 10 paket:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Aşağıdaki paketler sistem mimarin ile uyumlu değil:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "%[2]s için %[1]d sağlayıcı bulunuyor:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Başka bir Pacman talebi çalışıyor olabilir. Bekleniyor..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Paketlerin tuttuğu Toplam Boyut: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "İndirilen toplam paketler: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Yine de yapılandırmaya çalış?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Temizlenemiyor:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Aşağıdaki paketler bulunamadı:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Şunlar için paket oylaması işlenemiyor:%s. hata: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Kaldırılamıyor %s: %s" #: print.go:32 msgid "Version" msgstr "Sürüm" #: print.go:50 msgid "Votes" msgstr "Oylar" #: print.go:87 msgid "Yay version v%s" msgstr "Yay sürümü v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Hiçbiri" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Yapım dizini:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Bir sayı girin (varsayılan=1):" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "kullanıcı nedeniyle iptal ediliyor" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "stdin üzerinde girdi olmadan '-' argümanı kullanıldı" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "dizin içerisinde PKGBUILD ve .SRCINFO bulunamadı" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "paket ismi bulunamadı: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "PKGDEST dizini bulunamadı: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "tüm gereken paketler bulunamadı" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "arşivde listelenmiş paketler bulunamadı %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:287 msgid "dependency" msgstr "bağımlılık" #: pkg/vcs/vcs.go:96 pkg/vcs/vcs.go:100 msgid "devel check for package failed: '%s' encountered an error" msgstr "Paket gelişim kontrolü başarısız: '%s' hatayla karşılaştı" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "düzenleyici başarılı bir şekilde çıkış yapmadı, iptal ediliyor: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "kaynaklar indirilirken hata: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "%s getirilirken hata: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "depo paketleri indirilirken hata" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "kurarken hata:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "yapılırken hata: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "%s birleştirilirken hata: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "%s okunurken hata" #: sync.go:36 msgid "error refreshing databases" msgstr "veritabanlarını yenilenirken hata" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "%s yenilenirken hata: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "paket yükleme nedeni güncellenirken hata oluştu %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "açık" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "'%s' dizini oluşturulamadı: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "'%s' yapılandırma dosyası açılamadı: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "%s çözümlenirken hata -- atlanıyor: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "%s çözümlenirken hata: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr " .SRCINFO ayrıştırılamadı" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "'%s' yapılandırma dosyası okunurken hata: %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "aur Cache alınamadı" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "devel paketi yükseltmesi yok sayılıyor (AUR bilgisi bulunamadı):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "girdi çok uzun" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "geçersiz sayı: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "geçersiz seçenek '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "geçersiz seçenek: '--deps' ve '--explicit' beraber kullanılamaz" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "geçersiz depo" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "geçersiz değer: %d, %d ile %d arasında değil." #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "aktarılacak anahtar yok" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "hiçbir sorgu çalıştırılmadı" #: local_install.go:66 msgid "no target directories specified" msgstr "hiç bir hedef dizin belirtilmedi" #: pkg/text/input.go:48 msgid "no" msgstr "hayır" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "indirelecek bir şey yok %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "bir seferde sadece bir işlem kullanılabilir" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "" #: print.go:187 msgid "package '%s' was not found" msgstr "'%s' paketi bulunamadı" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "paket AUR'da bulunamadı" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "paket depolarda bulunamadı" #: pkg/upgrade/service.go:292 #, fuzzy msgid "package" msgid_plural "packages" msgstr[0] "Paket Temeli" msgstr[1] "Paket Temeli" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "anahtarları aktarırken sorun" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "AUR paketleri önbellekten siliniyor..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "izlenmemiş AUR paketleri önbellekten siliniyor..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "%s için olan PKGDEST, makepkg'da listelenmiş ancak bulunmuyor: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "yapılacak bir şey yok" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "CreateHandle yapılamıyor: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "beklenmeyen işlem" #: cmd.go:450 msgid "unknown-version" msgstr "bilinmeyen sürüm" #: pkg/text/input.go:47 msgid "yes" msgstr "evet" #~ msgid " (Target" #~ msgstr "(Hedef" #~ msgid " (Wanted by: " #~ msgstr "(Tarafından isteniyor:" #~ msgid "%s not satisfied, flushing install queue" #~ msgstr "%s sağlanmamış, indirme kuyruğu boşaltılıyor" #~ msgid "Checking for conflicts..." #~ msgstr "Çakışmalar kontrol ediliyor..." #~ msgid "Checking for inner conflicts..." #~ msgstr "İç çakışmalar kontrol ediliyor..." #~ msgid "Conflicting packages will have to be confirmed manually" #~ msgstr "Çakışan paketlerin elle onaylanması gerekecek" #~ msgid "Inner conflicts found:" #~ msgstr "İç çakışmalar bulundu:" #~ msgid "Installing %s will remove:" #~ msgstr "%s paketini kurmak şunları kaldıracak:" #~ msgid "PKGBUILD up to date, Skipping (%d/%d): %s" #~ msgstr "PKGBUILD güncel, Atlanıyor (%d/%d): %s" #~ msgid "Package conflicts found:" #~ msgstr "Paket çakışmaları bulundu:" #~ msgid "Packages to upgrade." #~ msgstr "Yükseltilecek paketler." #~ msgid "Querying AUR..." #~ msgstr "AUR sorgulanıyor..." #~ msgid "could not find all required packages:" #~ msgstr "tüm gereken paketler bulunamadı:" #~ msgid "could not find srcinfo for: %s" #~ msgstr "%s : İçin scrinfo bulunamadı" #~ msgid "package conflicts can not be resolved with noconfirm, aborting" #~ msgstr "paket çakışmaları noconfirm ile çözümlenemedi, iptal ediliyor" ================================================ FILE: po/uk.po ================================================ # # Translators: # Volodymyr Markiv , 2021 # Igor Lukyanov, 2023 # Andrii Lytvyn, 2023 # null null, 2023 # Andrii Yermak, 2024 # msgid "" msgstr "" "Last-Translator: Andrii Yermak, 2024\n" "Language-Team: Ukrainian (https://app.transifex.com/yay-1/teams/123732/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Каталог збірки:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Введіть число (типово=1):" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Файли Збірки Існують)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Встановлено)" #: cmd.go:453 msgid " [Installed]" msgstr "[Встановлено]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "тут нічого робити" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A]Всі [Ab]Скасувати [I]Встановлені [No]Невстановлені чи (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s вже зроблено -- пропуск збірки" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s не задано" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s вже існує." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s є актуальним -- пропуск" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s для оновлення/встановлення." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s також буде встановлено для цієї операції." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s потребується для: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Нема змін -- пропуск" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: не вдалося застосувати ціль з параметром --aur -- пропуск" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: не вдалося застосувати ціль з параметром --repo -- пропуск" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: ігнорування оновлення пакунка ( %s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: встановлений пакунок (%s) новіший ніж у AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "" "%s: будь ласка встановіть змінні середовища AUR_USERNAME та AUR_PASSWORD для" " цього голосування" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) PKGBUILD завантажено з ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Завантажено PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Не вдалося завантажити PKGBUILD: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Аналіз SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Встановлено)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Встановлено: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Осиротілий)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Застарілий: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Додайте %s або %s до змінних середовища" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Не запускайте yay від імені root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Перевірити залежності" #: print.go:41 msgid "Check Deps" msgstr "Залежності Перевірки" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Перевірка пакунків розробки..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Очистка (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Конфліктує з" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Видалення (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Залежність" #: print.go:38 msgid "Depends On" msgstr "Залежить Від" #: print.go:33 msgid "Description" msgstr "Опис" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Показати зміни?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Вимкнути параметр 'provides' за замовчуванням" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Ви бажаєте видалити ВСІ пакунки AUR з кешу?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Ви бажаєте видалити ВСІ невідстежувані файли AUR?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "Ви бажаєте видалити всі інші пакунки AUR з кешу?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Редагувати PKGBUILD за допомогою?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Помилка під час пошуку у AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Виключення пакетів може привести до часткового оновлення та зламати систему " #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Явно" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Явно встановлені пакунки: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Не вдалося знайти AUR пакет для" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Не вдалося встановити шар, перекочуємось до наступного шару." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Не вдалося встановити наступні пакети. Необхідне ручне втручання:" #: print.go:45 msgid "First Submitted" msgstr "Вперше Завантажений" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Пакунки з AUR, що позначені як застарілі:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Сторонні встановлені пакунки: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Знайдено git-репозиторій: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB завершено. Пакунки не встановлено" #: print.go:36 msgid "Groups" msgstr "Групи" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Імпортувати?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Імпортування ключів за допомогою gpg..." #: print.go:46 msgid "Keywords" msgstr "Ключові слова" #: print.go:47 msgid "Last Modified" msgstr "Остання зміна" #: print.go:35 msgid "Licenses" msgstr "Ліцензії" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Локальний" #: print.go:48 msgid "Maintainer" msgstr "Супроводжувач" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Зібрати залежність" #: print.go:40 msgid "Make Deps" msgstr "Залежності Збірки" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Нестачає" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Відсутні налагоджувальні пакунки з AUR:" #: print.go:31 msgid "Name" msgstr "Ім'я" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Не знайдено AUR пакету для" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Пакунок не знайдено для" #: print.go:225 msgid "None" msgstr "Ні" #: print.go:39 msgid "Optional Deps" msgstr "Необов'язкові Залежності" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Сирі (що не супроводжуються) AUR Пакети:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Застарілий" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Необхідно імпортувати ключі PGP:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD оновлений, пропускаємо завантаження: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Редагувати PKGBUILDs?" #: print.go:61 msgid "Package Base" msgstr "Група Пакунків" #: print.go:60 msgid "Package Base ID" msgstr "ID Групи Пакунків" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Пакети не в AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Пакунки для чистої збірки?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Пакети для виключення" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "" "Пакунки, які потрібно виключити: (наприклад, \"1 2 3\", \"1-3\", \"^4\" або " "назва репозиторію)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Пакунки для встановлення (наприклад: 1 2 3, 1-3 або ^4)" #: print.go:49 msgid "Popularity" msgstr "Популярність" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Продовжити встановлення?" #: print.go:37 msgid "Provides" msgstr "Забезпечує" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Видалити залежності збірки після встановлення?" #: print.go:43 msgid "Replaces" msgstr "Замінює" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Репозиторій" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Репозиторій AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Пошук оновлень у AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Пошук оновлень у базах даних..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Показано лише пакунки з репозиторіїв" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Розмір кешу pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Розмір кешу yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL знімка" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Синхронізувати" #: print.go:100 msgid "Ten biggest packages:" msgstr "Десять найбільших пакунків:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Наступні пакунки не сумісні з вашою архітектурою:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Існує %[1]d постачальників для %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Можливо, запущено інший екземпляр Pacman. Очікування..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Загальний розмір, зайнятий пакунками: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Загальна кількість встановлених пакунків: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Все одно спробувати їх побудувати?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Неможливо очистити:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Не вдалося знайти наступні пакунки:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Неможливо обробити голос за пакет: %s. помилка: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Неможливо видалити %s: %s" #: print.go:32 msgid "Version" msgstr "Версія" #: print.go:50 msgid "Votes" msgstr "Голоси" #: print.go:87 msgid "Yay version v%s" msgstr "Версія Yay v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]Ні" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "зупинка через користувача" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "аргумент '-' вказаний без вводу у stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "не вдалося знайти PKGBUILD та .SRCINFO в директорії" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "не вдається знайти пакунок: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "не вдалося знайти PKGDEST для: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "не знайдені всі необхідні пакунки" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "не знайдено жодного пакунку з перелічених у %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "залежність" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "розробницька перевірка пакунку не вдалася: '%s' сталася помилка" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "вихід з редактора був успішним, переривання: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "помилка завантаження вихідного коду:" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "помилка отримання %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "помилка встановлення пакунку з репозиторіїв" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "помилка встановлення:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "помилка збірки: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "помилка об'єднання %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "помилка читання %s" #: sync.go:36 msgid "error refreshing databases" msgstr "помилка оновлення баз даних" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "помилка скидання %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "помилка оновлення причини встановлення пакунка для %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "явно" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "не вдалося створити каталог '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "не вдалося відкрити файл конфігурації '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "не вдалося проаналізувати %s -- пропуск: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "не вдалося проаналізувати %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "не вдалося проаналізувати .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "не вдалося прочитати конфігураційний файл '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "не вдалося отримати кеш AUR" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "ігноруємо оновлення розробки пакунка (нема інформації в AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "введення занадто довге" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "некоректний номер: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "некоректний параметр '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "некоректний параметр: '--deps' і '--explicit' не можна використовувати разом" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "недійсний репозиторій" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "некоректне значення: %d не знаходиться між %d та %d" #: pkg/text/input.go:48 msgid "no" msgstr "ні" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "нема ключів для імпорту" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "запит не виконано" #: local_install.go:66 msgid "no target directories specified" msgstr "цільова директорія не вказана" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "нема чого встановити для %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "одночасно можна використовувати лише одну операцію" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "дозволена лише одна ціль" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "пакунок" msgstr[1] "пакунки" msgstr[2] "пакунків" msgstr[3] "пакунків" #: print.go:187 msgid "package '%s' was not found" msgstr "пакунок '%s' не знайдено" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "пакунок не знайдено в AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "пакунок не знайдено в репозиторіях" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "проблема імпортування ключів" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "видалення пакунків AUR з кешу..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "видалення невідстежених файлів AUR з кешу..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "PKGDEST для %s перераховано makepkg, але не існує: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "нема чого робити" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "неможливо створити Handle:" #: cmd.go:186 msgid "unhandled operation" msgstr "необроблена операція" #: cmd.go:450 msgid "unknown-version" msgstr "невідома версія" #: pkg/text/input.go:47 msgid "yes" msgstr "так" ================================================ FILE: po/vi.po ================================================ # # Translators: # Tín Nguyễn, 2022 # Katoji Rikito, 2023 # Nguyễn Tri Phương, 2024 # Phong Phạm Thanh, 2025 # Nguyễn Hoàng Minh , 2025 # msgid "" msgstr "" "Last-Translator: Nguyễn Hoàng Minh , 2025\n" "Language-Team: Vietnamese (https://app.transifex.com/yay-1/teams/123732/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: vi\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Vị trí lưu tệp xây dựng:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Hãy nhập số (mặc định = 1)" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(Tệp Xây Dựng Đã Tồn Tại)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Đã cài đặt)" #: cmd.go:453 msgid " [Installed]" msgstr "[Đã cài đặt]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "không có tác vụ cần làm" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "" "%s [A] Tất cả [Ab] Hủy [I] Đã được cài đặt [No] Chưa được cài đặt hoặc (1" " 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s đã được xây dựng -- đang bỏ qua" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s chưa được đặt" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s có tồn tại" #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s đã là phiên bản mới nhất -- đang bỏ qua" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s để nâng cấp/cài đặt" #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s cũng sẽ được cài đặt cho hoạt động này." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, cần thiết cho: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Không có thay đổi nào -- đang bỏ qua" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: không thể sử dụng mục tiêu với lựa chọn --aur -- đang bỏ qua" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: không thể sử dụng mục tiêu với lựa chọn --repo -- đang bỏ qua" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: đang bỏ qua cập nhật gói (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: phiên bản cục bộ (%s) mới hơn phiên bản từ AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: hãy đặt biến AUR_USERNAME và AUR_PASSWORD cho việc bỏ phiếu" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Đã tải PKGBUILD từ ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Đã tải PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Tải PKGBUILD thất bại: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Đang đọc SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Đã cài đặt)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Đã cài đặt: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Đơn lẻ)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Lỗi thời: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "URL của AUR" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Thêm %s hoặc %s làm vào các biến môi trường của bạn" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Tránh chạy yay khi là root hoặc dùng sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Kiểm tra gói phụ thuộc" #: print.go:41 msgid "Check Deps" msgstr "K.tra ph.thuộc" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Đang kiểm tra các gói tự xây dựng..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Đang dọn (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Mâu thuẫn với" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Đang xóa (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Gói phụ thuộc" #: print.go:38 msgid "Depends On" msgstr "Phụ thuộc vào" #: print.go:33 msgid "Description" msgstr "Mô tả" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Hiện ra các thay đổi?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Tắt cài đặt 'provides' theo mặc định" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Bạn có muốn loại bỏ TOÀN BỘ các gói từ AUR ra khỏi bộ nhớ không?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Bạn có muốn loại bỏ TOÀN BỘ các tệp từ AUR không được theo dõi không?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" "Bạn có muốn loại bỏ toàn bộ các tệp AUR còn lại trong kho nhớ tạm không?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Chỉnh sửa PKGBUILD bằng?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Lỗi khi thực hiện tìm kiếm trong AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Việc bỏ qua các gói có thể khiến cho bản cập nhật không đầy đủ và làm hỏng " "hệ thống" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Cụ thể" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Các gói được cài đặt cụ thể: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Không thể tìm gói AUR cho" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Cài đặt lớp thất bại, đang đến lớp tiếp theo." #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Đã thất bại khi cài đặt những gói sau. Yêu cầu can thiệp thủ công:" #: print.go:45 msgid "First Submitted" msgstr "Lần nộp đầu tiên" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Các gói AUR bị đánh dấu lỗi thời:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Gói ngoại lai đã được cài đặt: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Tìm thấy kho chứa git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "Hoàn thành GenDB. Không có gói được cài đặt" #: print.go:36 msgid "Groups" msgstr "Nhóm" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Nhập?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Đang nhập khóa với gpg..." #: print.go:46 msgid "Keywords" msgstr "Từ khóa" #: print.go:47 msgid "Last Modified" msgstr "Lần sửa cuối" #: print.go:35 msgid "Licenses" msgstr "Giấy phép" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Cục bộ" #: print.go:48 msgid "Maintainer" msgstr "Người bảo trì" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Gói phụ thuộc cho make" #: print.go:40 msgid "Make Deps" msgstr "Gói ph.thuộc make" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Đang thiếu" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Đang thiếu gói AUR gỡ lỗi:" #: print.go:31 msgid "Name" msgstr "Tên" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Không tìm thấy gói AUR cho" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Không tìm thấy gói cho" #: print.go:225 msgid "None" msgstr "Không có" #: print.go:39 msgid "Optional Deps" msgstr "Gói phụ thuộc không bắt buộc" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Gói AUR đơn lẻ (không được bảo trì):" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Lỗi thời" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Khóa PGP cần nhập:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "Tệp PKGBUILD đã ở bản mới nhất, đang bỏ qua: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Những tệp PKGBUILD cần chỉnh sửa? " #: print.go:61 msgid "Package Base" msgstr "Gói gốc" #: print.go:60 msgid "Package Base ID" msgstr "ID của gói gốc" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Gói không ở trong AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Những gói để chạy cleanBuild (xây dựng từ nguồn)?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Những gói bỏ lại" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Những gói bỏ lại (v.d.: \"1 2 3\", \"1-3\", \"^4\" hay tên kho chứa)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Những gói cài đặt (vd: 1 2 3, 1-3 hoặc ^4)" #: print.go:49 msgid "Popularity" msgstr "Độ phổ biến" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Tiếp tục cài đặt?" #: print.go:37 msgid "Provides" msgstr "Cung cấp" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Gỡ phụ thuộc xây dựng gói sau khi cài đặt?" #: print.go:43 msgid "Replaces" msgstr "Thay thế" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Kho chứa" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Kho chứa AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Đang tìm kiếm phiên bản cập nhật từ AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Đang tìm kiếm phiên bản cập nhật từ cơ sở dữ liệu..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Chỉ hiện gói từ kho chứa" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Kích cỡ bộ nhớ tạm pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Kích cỡ bộ nhớ tạm yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Đồng bộ hóa" #: print.go:100 msgid "Ten biggest packages:" msgstr "Mười gói lớn nhất:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Những gói sau đây không phù hợp với kiến trúc máy tính của bạn:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Có %[1]d nhà phát hành cho %[2]s:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Có thể đang có một tiến trình Pacman khác đang chạy. Đang đợi..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Tổng kích cỡ chiếm dụng bởi các gói: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "Tổng các gói đã cài đặt: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Vẫn xây dựng gói?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Không thể dọn:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Không thể tìm thấy những gói sau:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Không thể xử lý phiếu bầu cho gói: %s. Lỗi: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Không thể xóa %s:%s" #: print.go:32 msgid "Version" msgstr "Phiên bản" #: print.go:50 msgid "Votes" msgstr "Số lượng bầu chọn" #: print.go:87 msgid "Yay version v%s" msgstr "Phiên bản yay số v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N] Không có" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "hủy bởi người dùng" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "đối số '-' được dùng mà không có đầu vào từ stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "không tìm được PKGBUILD và .SRCINFO trong thư mục" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "không thể tìm thấy gói: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "không thể tìm thấy PKGDEST cho: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "không thể tìm thấy các gói được yêu cầu" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "không thể tìm thấy các gói lưu trữ được liệt kê ở %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "phần phụ thuộc" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "thất bại khi kiểm tra gói tự xây dựng: %s đã bị lỗi" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "phần mềm chỉnh sửa không thoát thành công, đang ngắt: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "có lỗi trong khi đang tải nguồn: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "có lỗi trong khi thu thập %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "có lỗi trong khi đang tải gói từ kho mã nguồn (repo)" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "có lỗi trong khi cài đặt:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "có lỗi trong khi tạo: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "có lỗi trong khi hợp nhất %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "có lỗi trong khi đọc tệp %s" #: sync.go:36 msgid "error refreshing databases" msgstr "có lỗi trong khi cập nhật cơ sở dữ liệu" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "có lỗi trong khi đưa về trạng thái mặc định %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "có lỗi trong khi đang cập nhật lý do tải gói sang %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "cụ thể" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "tạo thư mục '%s' thất bại: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "mở tệp cài đặt '%s' thất bại: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "thất bị khi đọc %s -- bỏ qua: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "thất bại khi đọc %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "thất bại khi đọc .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "thất bại khi đọc tệp cài đặt '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "tải kho nhớ AUR thất bại" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "bỏ qua cập nhật phần mềm tự xây dựng (không tìm thấy thông tin trên AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "nhập liệu quá dài" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "số không hợp lệ: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "lựa chọn '%s' không hợp lệ" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "lựa chọn không hợp lệ: '--deps' và '--explicit' không thể sử dụng cùng nhau" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "kho không hợp lệ" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "giá trị không hợp lệ: %d không nằm giữa %d và %d" #: pkg/text/input.go:48 msgid "no" msgstr "không" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "không có keys để thêm vào" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "không có truy vấn được thực hành" #: local_install.go:66 msgid "no target directories specified" msgstr "không có thư mục được xác định" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "không có gì để tải cho %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "chỉ được sử dụng một lệnh trong một lần gọi" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "chỉ cho phép 1 đối tượng duy nhất" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "các gói" #: print.go:187 msgid "package '%s' was not found" msgstr "không tìm thấy gói '%s'" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "không tìm được gói trong AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "không tìm được gói trong các kho" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "có vấn đề trong khi thêm keys" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "đang xóa các gói AUR khỏi bộ nhớ đệm..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "đang loại bỏ các tệp AUR không được theo dõi khỏi bộ nhớ" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "" "tệp PKGDEST của %s được gọi trong lệnh makepkg nhưng không tồn tại: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "không thể làm được gì cả" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "không sử dùng được hàm CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "lệnh không được xử lý" #: cmd.go:450 msgid "unknown-version" msgstr "phiên-bản-không-xác-định" #: pkg/text/input.go:47 msgid "yes" msgstr "có" ================================================ FILE: po/vi_VN.po ================================================ # # Translators: # su va, 2023 # Tín Nguyễn, 2024 # Nguyễn Tri Phương, 2024 # Phong Phạm Thanh, 2025 # Minh Nguyen , 2025 # msgid "" msgstr "" "Last-Translator: Minh Nguyen , 2025\n" "Language-Team: Vietnamese (Viet Nam) (https://app.transifex.com/yay-1/teams/123732/vi_VN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: vi_VN\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "Vị trí lưu tệp dựng:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "Hãy nhập số (Mặc định = 1)" #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr "(File để Build có sẵn)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr "(Đã cài đặt)" #: cmd.go:453 msgid " [Installed]" msgstr "[Đã cài đặt]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr "không còn gì " #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A] Tất cả [Ab] Hủy [I] Đã cài [No] Chưa cài hoặc (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s đã make -- bỏ qua bước " #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "%s chưa được đặt" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s đã có sẵn." #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s đã cập nhật -- bỏ qua" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s để nâng cấp/cài đặt." #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s cũng sẽ được cài đặt để thực hiện việc này." #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, bắt buộc cho:%s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: Không thay đổi -- bỏ qua" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: không thể dùng target với lựa chọn --aur -- bỏ qua" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: không thể dùng đối tượng với lựa chọn --repo -- bỏ qua" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: bỏ qua việc nâng cấp gói (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: bản trên máy (%s) mới hơn bản trên AUR (%s)" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: set các biến môi trường AUR_USERNAME và AUR_PASSWORD để bầu chọn" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) Đã tải xuống PKGBUILD từ ABS: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) Đã tải xuống PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) Tải xuống PKGBUILD thất bại: %s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) Đang đọc SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(Đã cài)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(Đã cài: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(Đã bị bỏ)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(Chưa được cập nhật: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR " #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "Thêm %s hoặc %s vào các biến môi trường của bạn" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "Tránh chạy yay bằng root/sudo." #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "Kiểm tra gói phụ thuộc" #: print.go:41 msgid "Check Deps" msgstr "Kiểm tra các gói phụ thuộc" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "Đang kiểm tra các gói dev" #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "Đang dọn (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "Xung đột với" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "Đang xóa (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "Gói phụ thuộc" #: print.go:38 msgid "Depends On" msgstr "Phụ thuộc vào" #: print.go:33 msgid "Description" msgstr "Mô tả" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "Hiện ra sự khác nhau?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "Tắt cài đặt 'provides' theo mặc định" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "Bạn có muốn loại bỏ TOÀN BỘ gói AUR ra khỏi kho nhớ tạm không?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "Bạn có muốn loại bỏ TOÀN BỘ các tệp AUR chưa được theo dõi không?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "" "Bạn có muốn loại bỏ toàn bộ các tệp AUR còn lại trong kho nhớ tạm không?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "Chỉnh sửa PKGBUILD bằng?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "Lỗi khi thực hiện tìm kiếm AUR: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "" "Bỏ lại các gói này có thể khiến cho bản cập nhật không đầy đủ và làm hỏng hệ" " thống" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "Cụ thể" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "Các gói được cài đặt cụ thể:" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "Không thể tìm gói AUR cho" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "Cài đặt layer thất bại, quay trở lại layer tiếp theo" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "Cài đặt những gói dưới đây thất bại. Yêu cầu can thiệp thủ công:" #: print.go:45 msgid "First Submitted" msgstr "Bản Nộp Đầu Tiên" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "Gói AUR Đã Bị Đánh Dấu Lỗi Thời" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "Gói ngoại lai đã được cài đặt: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "Tìm thấy kho chứa git: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "Hoàn thành GenDB. Không cài đặt gói nào." #: print.go:36 msgid "Groups" msgstr "Nhóm" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "Nhập?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "Đang nhập khóa với gpg..." #: print.go:46 msgid "Keywords" msgstr "Từ khóa" #: print.go:47 msgid "Last Modified" msgstr "Sửa Lần Cuối" #: print.go:35 msgid "Licenses" msgstr "Giấy Phép" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "Cục Bộ" #: print.go:48 msgid "Maintainer" msgstr "Người Bảo Trì" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "Yêu Cầu Make" #: print.go:40 msgid "Make Deps" msgstr "Y/c Make" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "Thiếu" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "Thiếu gói AUR gỡ lỗi:" #: print.go:31 msgid "Name" msgstr "Tên" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "Không tìm thấy gói AUR cho" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "Không tìm thấy gói cho" #: print.go:225 msgid "None" msgstr "Không Có" #: print.go:39 msgid "Optional Deps" msgstr "Phụ Thuộc Không Bắt Buộc" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "Gói AUR Orphan (không được bảo trì)" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "Lỗi thời" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "Khóa PGP cần nhập:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD đã ở bản mới nhất, bỏ qua tải xuống: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "Những PKGBUILD cần chỉnh sửa? " #: print.go:61 msgid "Package Base" msgstr "Base Của Gói" #: print.go:60 msgid "Package Base ID" msgstr "Base ID Của Gói" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "Gói không ở trong AUR:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "Những gói để chạy cleanBuild?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "Những gói bỏ lại" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "Những gói bỏ lại (ví dụ: \"1 2 3\", \"1-3\", \"^4\" hay tên kho chứa)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "Những gói cài đặt (vd: 1 2 3, 1-3 hoặc ^4)" #: print.go:49 msgid "Popularity" msgstr "Độ Phổ Biến" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "Tiếp tục cài đặt?" #: print.go:37 msgid "Provides" msgstr "Cung Cấp" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "Gỡ phụ thuộc xây dựng gói sau khi cài đặt?" #: print.go:43 msgid "Replaces" msgstr "Thay Thế" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "Kho Chứa" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "Kho Chứa AUR" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "Đang tìm kiếm cập nhật từ AUR..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "Đang tìm kiếm cập nhật từ cơ sở dữ liệu..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "Chỉ hiện gói từ kho chứa" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "Kích cỡ kho nhớ tạm pacman %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "Kích cỡ kho nhớ tạm yay %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "URL Snapshot" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "Đồng Bộ Hóa" #: print.go:100 msgid "Ten biggest packages:" msgstr "Mười gói lớn nhất:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "Những gói sau đây không phù hợp với kiến trúc máy tính của bạn:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "Có %[1]d nhà phát hành cho %[2]d:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "Có thể đang có một tiến trình Pacman khác đang chạy. Đang đợi..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "Tổng Kích Cỡ chiếm dụng bởi các gói:" #: print.go:89 msgid "Total installed packages: %s" msgstr "Tổng các gói đã cài đặt: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "Vẫn xây dựng gói?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "Không thể dọn:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "Không thể tìm thấy những gói sau:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "Không thể xử lý phiếu bầu cho gói: %s. Lỗi: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "Không thể xóa %s:%s" #: print.go:32 msgid "Version" msgstr "Phiên bản" #: print.go:50 msgid "Votes" msgstr "Bầu chọn" #: print.go:87 msgid "Yay version v%s" msgstr "Phiên bản yay số %s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N] Không có" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "hủy bởi người dùng" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "đối số '-' được dùng mà không có đầu vào từ stdin" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "không tìm được PKGBUILD và .SRCINFO trong thư mục" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "không thể tìm thấy gói: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "không thể tìm thấy PKGDEST cho: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "không thể tìm thấy các gói yêu cầu" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "không thể tìm thấy các gói lưu trữ được liệt kê ở %s" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "phần phụ thuộc" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "không kiểm tra được gói tự xây dựng: %s đã bị lỗi" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "phần mềm chỉnh sửa không thoát thành công, đang ngắt: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "lỗi khi đang tải nguồn: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "lỗi khi thu thập %s: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "lỗi khi đang tải gói từ kho mã nguồn (repo)" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "có lỗi trong khi cài đặt:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "có lỗi trong khi tạo tệp:" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "có lỗi trong khi hợp nhất %s: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "có lỗi trong khi đọc tệp %s" #: sync.go:36 msgid "error refreshing databases" msgstr "có lỗi trong khi làm mới dữ liệu" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "lỗi khi đưa về trạng thái mặc định %s: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "lỗi khi đang cập nhật lý do tải gói sang %s" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "cụ thể" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "tạo thư mục '%s' thất bại: %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "mở tệp cài đặt `%s` thất bại: %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "thất bị khi đọc %s -- sẽ bỏ qua: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "thất bại khi đọc %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "thất bại khi đọc .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "thất bại khi đọc tệp cài đặt '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "tải kho nhớ AUR thất bại" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "" "bỏ qua cập nhật phần mềm tự xây dựng (không tìm thấy thông tin trên AUR):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "nhập liệu quá dài" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "số không hợp lệ: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "lựa chọn '%s' không hợp lệ" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "" "lựa chọn không hợp lệ: '--deps' và '--explicit' không thể sử dụng cùng nhau" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "kho không khả dụng" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "giá trị không hợp lệ: %d không nằm giữa %d và %d" #: pkg/text/input.go:48 msgid "no" msgstr "không" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "không có keys để thêm vào" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "không có truy vấn được thực hành" #: local_install.go:66 msgid "no target directories specified" msgstr "không có thư mục được xác định" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "không có gì để tải cho %s" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "chỉ được sử dụng một lệnh trong một lần gọi" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "chỉ cho phép 1 đối tượng duy nhất" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "các gói" #: print.go:187 msgid "package '%s' was not found" msgstr "không tìm thấy gói'%s'" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "không tìm được gói trong AUR" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "không tìm được gói trong các kho" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "có vấn đề trong khi thêm keys" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "đang xóa các gói AUR khỏi bộ nhớ đệm..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "đang loại bỏ các tệp AUR không được theo dõi khỏi bộ nhớ" #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "" "tệp PKGDEST của %s được gọi trong lệnh makepkg nhưng không tồn tại: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "không thể làm được gì cả" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "không sử dùng được hàm CreateHandle: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "không thể xử lý hoạt động" #: cmd.go:450 msgid "unknown-version" msgstr "phiên-bản-không-xác-định" #: pkg/text/input.go:47 msgid "yes" msgstr "có" ================================================ FILE: po/zh_CN.po ================================================ # # Translators: # J G, 2022 # Moon Xanadu , 2023 # kanna 5, 2023 # Songer Hy, 2023 # Celestially H., 2023 # lakejason0 , 2023 # Alex Wang, 2023 # Xuekai Deng, 2024 # CloverGit, 2024 # qsdwindows <2687267056@qq.com>, 2025 # Haowen Shi, 2025 # msgid "" msgstr "" "Last-Translator: Haowen Shi, 2025\n" "Language-Team: Chinese (China) (https://app.transifex.com/yay-1/teams/123732/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "构建目录:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "输入数字 (默认=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (构建文件已存在)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (已安装)" #: cmd.go:453 msgid " [Installed]" msgstr " [已安装]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " 今日无事可做" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]全部 [Ab]中止 [I]已安装 [No]未安装 或 (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s 已生成 -- 跳过构建" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "未设置 %s" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s 存在。" #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s 是最新的 -- 跳过" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s 将被升级/安装。" #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s 也会为此操作而被安装。" #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, 被以下需要: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: 没有更改 -- 跳过" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: 不能将目标与选项 --aur 一起使用 -- 跳过" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: 不能将目标与选项 --repo 一起使用 -- 跳过" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: 忽略包升级 (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: 本地 (%s) 比 AUR (%s) 更新" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: 请设置 AUR_USERNAME 与 AUR_PASSWORD 环境变量以投票" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) 从 ABS 下载了 PKGBUILD: %s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) 下载了 PKGBUILD: %s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d) 下载PKGBUILD失败" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) 正在解析 SRCINFO: %s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(已安装)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(已安装: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(孤包)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(过时的: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR URL" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "将 %s 或 %s 添加到你的环境变量" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "避免以 root/sudo 运行 yay。" #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "作为检查依赖安装" #: print.go:41 msgid "Check Deps" msgstr "检查依赖" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "正在检查开发包..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "正在清理 (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "与它冲突" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "正在删除 (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "作为依赖安装" #: print.go:38 msgid "Depends On" msgstr "依赖以下包" #: print.go:33 msgid "Description" msgstr "描述" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "显示哪些包的差异?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "默认禁用 'provides' 设置" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "你是否要从缓存中删除所有 AUR 软件包?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "你是否要删除所有未跟踪的 AUR 文件?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "你是否要从缓存中删除所有其他 AUR 软件包?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "使用什么来编辑 PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "搜索 AUR 时出错: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "排除软件包可能会导致不完整的升级并破坏系统" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "单独指定安装" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "单独指定安装的软件包: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "查找 AUR 软件包失败:" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "层级安装失败,正在合并到下一个层级。" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "无法安装以下软件包, 需要手动介入处理:" #: print.go:45 msgid "First Submitted" msgstr "首次提交" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "标记为过时的 AUR 软件包:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "已安装的外部软件包: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "已找到 git 仓库: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB 完成。 没有安装任何软件包" #: print.go:36 msgid "Groups" msgstr "组" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "导入?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "正在导入 GPG 密钥..." #: print.go:46 msgid "Keywords" msgstr "关键字" #: print.go:47 msgid "Last Modified" msgstr "最后修改" #: print.go:35 msgid "Licenses" msgstr "许可证" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "本地" #: print.go:48 msgid "Maintainer" msgstr "维护者" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "作为构建依赖安装" #: print.go:40 msgid "Make Deps" msgstr "构建依赖" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "缺少" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "缺少 AUR 调试包:" #: print.go:31 msgid "Name" msgstr "名称" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "找不到 AUR 包" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "找不到包" #: print.go:225 msgid "None" msgstr "没有" #: print.go:39 msgid "Optional Deps" msgstr "可选依赖" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "孤包 (无人维护) 的 AUR 软件包:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "过时" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "需要导入的 PGP 密钥:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD 是最新的,跳过下载: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "要编辑哪些 PKGBUILD?" #: print.go:61 msgid "Package Base" msgstr "基本包" #: print.go:60 msgid "Package Base ID" msgstr "基本包 ID" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "不在 AUR 中的软件包:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "清理哪些包的构建文件?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "要排除的软件包" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "要排除的包: (示例: \"1 2 3\", \"1-3\", \"^4\" 或软件库名称)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "要安装的包 (示例: 1 2 3, 1-3 或 ^4)" #: print.go:49 msgid "Popularity" msgstr "受欢迎度" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "继续安装?" #: print.go:37 msgid "Provides" msgstr "提供" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "安装后删除构建依赖?" #: print.go:43 msgid "Replaces" msgstr "替换" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "软件库" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR 软件库" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "正在搜索 AUR 更新..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "正在搜索数据库更新..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "仅显示软件库软件包" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "pacman 缓存大小 %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "yay 缓存大小 %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "快照 URL" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "同步" #: print.go:100 msgid "Ten biggest packages:" msgstr "十个最大的软件包:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "下列软件包与你的系统架构不兼容:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "对于 %[2] 有 %[1]d 个结果:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "可能还有另一个 Pacman 实例正在运行。等待中..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "包占用的总大小: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "已安装的软件包总数: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "依然尝试构建吗?" #: print.go:34 msgid "URL" msgstr "URL" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "无法清理:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "找不到下列软件包:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "无法处理软件包的投票: %s。错误: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "无法删除 %s: %s" #: print.go:32 msgid "Version" msgstr "版本" #: print.go:50 msgid "Votes" msgstr "得票" #: print.go:87 msgid "Yay version v%s" msgstr "Yay 版本 v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]没有" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "用户自行终止" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "指定了参数“-”,但未在 stdin 输入任何内容" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "在目录中找不到 PKGBUILD 和 .SRCINFO 文件" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "找不到包名: %v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "找不到 PKGDEST: %s" #: errors.go:9 msgid "could not find all required packages" msgstr "无法找到所有必需的软件包" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "在 %s 中无法找到任何软件包存档" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "作为依赖安装" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "软件包的开发检查失败: '%s' 遇到了错误" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "编辑器未成功退出,正在中止: %s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "下载源文件时出错: %s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "获取 %s 时出错: %s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "安装软件库软件包时出错" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "安装时出错: " #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "生成时出错: %s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "合并 %s 时出错: %s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "读取 %s 时出错" #: sync.go:36 msgid "error refreshing databases" msgstr "刷新数据库时出错" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "重置 %s 时出错: %s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "更新软件包时因 %s 出错" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "单独指定安装" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "无法创建目录 '%s': %s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "无法打开配置文件 '%s': %s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "无法解析 %s -- 跳过: %s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "无法解析 %s: %s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "无法解析 .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "无法读取配置文件 '%s': %s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "无法获取 AUR 缓存" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "忽略软件包开发更新(未找到 AUR 信息):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "输入过长" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "无效数字: %s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "无效选项 '%s'" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "无效选项: '--deps' 和 '--explicit' 不能同时指定" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "无效软件库" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "无效值: %d 不在 %d 和 %d 之间" #: pkg/text/input.go:48 msgid "no" msgstr "no" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "没有要导入的密钥" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "没有查询被执行" #: local_install.go:66 msgid "no target directories specified" msgstr "未指定目标目录" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "%s 没有需要安装的依赖软件包" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "一次只能使用一项操作" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "仅允许一个目标" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "软件包" #: print.go:187 msgid "package '%s' was not found" msgstr "找不到软件包 '%s'" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "AUR 中找不到软件包" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "软件库中找不到软件包" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "导入密钥时出现问题" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "正在从缓存中删除 AUR 软件包..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "正在从缓存中删除未跟踪的 AUR 文件..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "%s 的 PKGDEST 由 makepkg 列出,但不存在: %s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "今日无事可做" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "无法创建句柄: %s" #: cmd.go:186 msgid "unhandled operation" msgstr "未处理的操作" #: cmd.go:450 msgid "unknown-version" msgstr "未知版本" #: pkg/text/input.go:47 msgid "yes" msgstr "yes" ================================================ FILE: po/zh_TW.po ================================================ # # Translators: # J G, 2022 # lakejason0 , 2023 # Oliver Tzeng, 2025 # zangmen hsu(neko_0xff) , 2025 # Kisaragi Hiu , 2025 # msgid "" msgstr "" "Last-Translator: Kisaragi Hiu , 2025\n" "Language-Team: Chinese (Taiwan) (https://app.transifex.com/yay-1/teams/123732/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: xgotext\n" #: clean.go:83 msgid "" "\n" "Build directory:" msgstr "" "\n" "編譯資料夾:" #: pkg/db/ialpm/alpm.go:201 pkg/dep/dep_graph.go:740 msgid "" "\n" "Enter a number (default=1): " msgstr "" "\n" "輸入數字 (預設=1): " #: pkg/menus/menu.go:32 msgid " (Build Files Exist)" msgstr " (編譯檔案已存在)" #: pkg/menus/menu.go:27 msgid " (Installed)" msgstr " (已安裝)" #: cmd.go:453 msgid " [Installed]" msgstr " [已安裝]" #: cmd.go:410 vote.go:36 msgid " there is nothing to do" msgstr " 無事可做" #: pkg/menus/menu.go:49 msgid "%s [A]ll [Ab]ort [I]nstalled [No]tInstalled or (1 2 3, 1-3, ^4)" msgstr "%s [A]全部 [Ab]中止 [I]已安裝 [No]未安裝 或 (1 2 3, 1-3, ^4)" #: pkg/sync/build/installer.go:308 msgid "%s already made -- skipping build" msgstr "%s 已編譯 -- 跳過編譯" #: pkg/menus/edit_menu.go:57 msgid "%s is not set" msgstr "未設定 %s" #: pkg/settings/exe/cmd_builder.go:257 msgid "%s is present." msgstr "%s 存在。" #: pkg/dep/dep_graph.go:460 pkg/sync/build/installer.go:305 msgid "%s is up to date -- skipping" msgstr "%s 是最新的 -- 跳過" #: pkg/upgrade/service.go:291 msgid "%s to upgrade/install." msgstr "%s 個要升級/安裝的套件。" #: pkg/upgrade/service.go:285 msgid "%s will also be installed for this operation." msgstr "%s 也會為此操作被安裝。 " #: pkg/sync/srcinfo/pgp/keys.go:124 msgid "%s, required by: %s" msgstr "%s, 需要: %s" #: pkg/menus/diff_menu.go:49 msgid "%s: No changes -- skipping" msgstr "%s: 沒有更改 -- 跳過" #: pkg/query/filter.go:22 msgid "%s: can't use target with option --aur -- skipping" msgstr "%s: 不能將目標與選項 --aur 一起使用 -- 跳過" #: pkg/query/filter.go:17 msgid "%s: can't use target with option --repo -- skipping" msgstr "%s: 不能將目標與選項 --repo 一起使用 -- 跳過" #: pkg/upgrade/sources.go:57 msgid "%s: ignoring package upgrade (%s => %s)" msgstr "%s: 忽略套件包升級 (%s => %s)" #: pkg/query/aur_warnings.go:46 msgid "%s: local (%s) is newer than AUR (%s)" msgstr "%s: 本機 (%s) 比 AUR (%s) 更新" #: vote.go:51 msgid "" "%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for " "voting" msgstr "%s: 請設定 AUR_USERNAME 與 AUR_PASSWORD 環境變數以進行投票" #: pkg/download/unified.go:192 msgid "(%d/%d) Downloaded PKGBUILD from ABS: %s" msgstr "(%d/%d) 從 ABS 下載了 PKGBUILD:%s" #: pkg/download/aur.go:92 pkg/download/unified.go:188 msgid "(%d/%d) Downloaded PKGBUILD: %s" msgstr "(%d/%d) 下載了 PKGBUILD:%s" #: pkg/download/aur.go:82 msgid "(%d/%d) Failed to download PKGBUILD: %s" msgstr "(%d/%d)下載 PKGBUILD 時失敗:%s" #: pkg/sync/srcinfo/service.go:109 msgid "(%d/%d) Parsing SRCINFO: %s" msgstr "(%d/%d) 正在解析 SRCINFO:%s" #: pkg/query/types.go:103 pkg/query/types.go:72 msgid "(Installed)" msgstr "(已安裝)" #: pkg/query/types.go:101 pkg/query/types.go:70 msgid "(Installed: %s)" msgstr "(已安裝: %s)" #: pkg/query/types.go:61 msgid "(Orphaned)" msgstr "(棄置)" #: pkg/query/types.go:65 msgid "(Out-of-date: %s)" msgstr "(過期的: %s)" #: pkg/dep/dep_graph.go:75 msgid "AUR" msgstr "AUR" #: print.go:44 msgid "AUR URL" msgstr "AUR 網址" #: pkg/menus/edit_menu.go:58 msgid "Add %s or %s to your environment variables" msgstr "將 %s 或 %s 添加到環境變數中" #: main.go:60 msgid "Avoid running yay as root/sudo." msgstr "避免以 root/sudo 執行 yay。" #: pkg/dep/dep_graph.go:63 msgid "Check Dependency" msgstr "作為檢查依賴安裝" #: print.go:41 msgid "Check Deps" msgstr "檢查依賴" #: pkg/upgrade/service.go:89 msgid "Checking development packages..." msgstr "正在檢查開發套件包..." #: pkg/sync/workdir/clean.go:45 msgid "Cleaning (%d/%d): %s" msgstr "正在清理 (%d/%d): %s" #: print.go:42 msgid "Conflicts With" msgstr "與它衝突" #: pkg/menus/clean_menu.go:62 msgid "Deleting (%d/%d): %s" msgstr "正在刪除 (%d/%d): %s" #: pkg/dep/dep_graph.go:61 msgid "Dependency" msgstr "作為依賴安裝" #: print.go:38 msgid "Depends On" msgstr "依賴於" #: print.go:33 msgid "Description" msgstr "描述" #: pkg/menus/diff_menu.go:160 msgid "Diffs to show?" msgstr "顯示差異?" #: pkg/settings/migrations.go:25 msgid "Disable 'provides' setting by default" msgstr "預設停用「provides」設定" #: clean.go:78 msgid "Do you want to remove ALL AUR packages from cache?" msgstr "您是否要從快取中刪除所有 AUR 套件包?" #: clean.go:95 msgid "Do you want to remove ALL untracked AUR files?" msgstr "您是否要刪除所有未使用的 AUR 檔案?" #: clean.go:80 msgid "Do you want to remove all other AUR packages from cache?" msgstr "您是否要從快取中刪除所有其他 AUR 套件包?" #: pkg/menus/edit_menu.go:61 msgid "Edit PKGBUILD with?" msgstr "使用什麼來編輯 PKGBUILD?" #: pkg/query/errors.go:13 msgid "Error during AUR search: %s\n" msgstr "搜尋 AUR 時出錯: %s\n" #: pkg/upgrade/service.go:295 msgid "Excluding packages may cause partial upgrades and break systems" msgstr "排除套件包則會造成部分功能更新而使系統無法正常運作" #: pkg/dep/dep_graph.go:60 msgid "Explicit" msgstr "單獨指定安裝" #: print.go:91 msgid "Explicitly installed packages: %s" msgstr "單獨指定安装的套件包: %s" #: pkg/dep/dep_graph.go:437 pkg/dep/dep_graph.go:535 msgid "Failed to find AUR package for" msgstr "尋找 AUR 套件包時失敗" #: pkg/sync/build/installer.go:120 msgid "Failed to install layer, rolling up to next layer." msgstr "層級安裝失敗,將合併到下一個層級。" #: pkg/sync/build/errors.go:16 msgid "" "Failed to install the following packages. Manual intervention is required:" msgstr "無法安裝以下的套件包。需要自行手動處理:" #: print.go:45 msgid "First Submitted" msgstr "首次提交" #: pkg/query/aur_warnings.go:83 msgid "Flagged Out Of Date AUR Packages:" msgstr "標記為過期的 AUR 套件包:" #: print.go:90 msgid "Foreign installed packages: %s" msgstr "已安裝的外部套件包: %s" #: pkg/vcs/vcs.go:144 msgid "Found git repo: %s" msgstr "已找到 git 倉庫源: %s" #: vcs.go:72 msgid "GenDB finished. No packages were installed" msgstr "GenDB 完成。沒有安裝任何套件包" #: print.go:36 msgid "Groups" msgstr "群組別" #: pkg/sync/srcinfo/pgp/keys.go:88 msgid "Import?" msgstr "匯入?" #: pkg/sync/srcinfo/pgp/keys.go:97 msgid "Importing keys with gpg..." msgstr "正在匯入 gpg 金鑰..." #: print.go:46 msgid "Keywords" msgstr "關鍵字" #: print.go:47 msgid "Last Modified" msgstr "最後修改" #: print.go:35 msgid "Licenses" msgstr "授權條款" #: pkg/dep/dep_graph.go:77 msgid "Local" msgstr "本機" #: print.go:48 msgid "Maintainer" msgstr "維護者" #: pkg/dep/dep_graph.go:62 msgid "Make Dependency" msgstr "作為編譯依賴安裝" #: print.go:40 msgid "Make Deps" msgstr "編譯依賴" #: pkg/dep/dep_graph.go:79 msgid "Missing" msgstr "缺少" #: pkg/query/aur_warnings.go:75 msgid "Missing AUR Debug Packages:" msgstr "缺少 AUR 除錯套件包:" #: print.go:31 msgid "Name" msgstr "名稱" #: pkg/dep/dep_graph.go:442 pkg/dep/dep_graph.go:548 msgid "No AUR package found for" msgstr "沒有找到 AUR 套件包" #: pkg/dep/dep_graph.go:182 msgid "No package found for" msgstr "未找到套件包" #: print.go:225 msgid "None" msgstr "不做任何動作" #: print.go:39 msgid "Optional Deps" msgstr "可選擇的依賴選項" #: pkg/query/aur_warnings.go:79 msgid "Orphan (unmaintained) AUR Packages:" msgstr "孤立 (不受維護) 的 AUR 套件:" #: print.go:53 print.go:55 msgid "Out-of-date" msgstr "過期" #: pkg/sync/srcinfo/pgp/keys.go:115 msgid "PGP keys need importing:" msgstr "需要匯入的 PGP 金鑰:" #: pkg/sync/workdir/preparer.go:252 msgid "PKGBUILD up to date, skipping download: %s" msgstr "PKGBUILD 是最新的,跳過下載: %s" #: pkg/menus/edit_menu.go:130 msgid "PKGBUILDs to edit?" msgstr "要編輯哪些 PKGBUILD 檔案?" #: print.go:61 msgid "Package Base" msgstr "套件包基礎" #: print.go:60 msgid "Package Base ID" msgstr "套件包基礎編號" #: pkg/query/aur_warnings.go:71 msgid "Packages not in AUR:" msgstr "不在 AUR 裡的套件包:" #: pkg/menus/clean_menu.go:54 msgid "Packages to cleanBuild?" msgstr "清理哪些套件包的編譯檔案?" #: pkg/dep/dep_graph.go:202 msgid "Packages to exclude" msgstr "要排除的套件包" #: pkg/upgrade/service.go:294 msgid "Packages to exclude: (eg: \"1 2 3\", \"1-3\", \"^4\" or repo name)" msgstr "要排除的套件包:(例如: \"1 2 3\", \"1-3\", \"^4\" 或倉庫源名稱)" #: cmd.go:392 msgid "Packages to install (eg: 1 2 3, 1-3 or ^4)" msgstr "要安裝的套件包 (例如: 1 2 3, 1-3 或 ^4)" #: print.go:49 msgid "Popularity" msgstr "熱門度" #: pkg/menus/diff_menu.go:172 pkg/menus/edit_menu.go:143 msgid "Proceed with install?" msgstr "繼續安裝?" #: print.go:37 msgid "Provides" msgstr "提供" #: pkg/sync/workdir/preparer.go:125 msgid "Remove make dependencies after install?" msgstr "安裝後刪除編譯依賴?" #: print.go:43 msgid "Replaces" msgstr "替換" #: pkg/db/ialpm/alpm.go:191 print.go:30 msgid "Repository" msgstr "軟體源" #: pkg/dep/dep_graph.go:730 msgid "Repository AUR" msgstr "AUR 軟體源" #: pkg/dep/dep_graph.go:78 msgid "SRCINFO" msgstr "SRCINFO" #: pkg/upgrade/service.go:71 msgid "Searching AUR for updates..." msgstr "搜尋 AUR 來獲取更新..." #: pkg/upgrade/service.go:159 msgid "Searching databases for updates..." msgstr "搜尋資料庫來獲取更新..." #: pkg/query/query_builder.go:214 msgid "Showing repo packages only" msgstr "僅顯示軟體源中的套件包" #: print.go:95 msgid "Size of pacman cache %s: %s" msgstr "pacman 快取大小 %s: %s" #: print.go:98 msgid "Size of yay cache %s: %s" msgstr "yay 快取大小 %s: %s" #: print.go:62 msgid "Snapshot URL" msgstr "快照網址" #: pkg/dep/dep_graph.go:76 msgid "Sync" msgstr "同步" #: print.go:100 msgid "Ten biggest packages:" msgstr "前十大的套件包:" #: pkg/sync/sync.go:124 msgid "The following packages are not compatible with your architecture:" msgstr "以下套件不支援您的系統架構:" #: pkg/db/ialpm/alpm.go:179 pkg/dep/dep_graph.go:726 msgid "There are %[1]d providers available for %[2]s:" msgstr "對於 %[2]s 有 %[1]d 個結果:" #: pkg/settings/exe/cmd_builder.go:258 msgid "There may be another Pacman instance running. Waiting..." msgstr "可能還有另一個 pacman 程序正在執行。等待中..." #: print.go:92 msgid "Total Size occupied by packages: %s" msgstr "套件包佔用的空間: %s" #: print.go:89 msgid "Total installed packages: %s" msgstr "已安裝的套件包總數: %s" #: pkg/sync/sync.go:132 msgid "Try to build them anyway?" msgstr "依然嘗試編譯它們?" #: print.go:34 msgid "URL" msgstr "網址" #: clean.go:194 pkg/menus/clean_menu.go:65 pkg/menus/clean_menu.go:71 msgid "Unable to clean:" msgstr "無法清理:" #: get.go:42 get.go:74 msgid "Unable to find the following packages:" msgstr "找不到下列套件包:" #: vote.go:20 msgid "Unable to handle package vote for: %s. err: %s" msgstr "無法處理套件包的投票: %s。錯誤: %s" #: clean.go:170 msgid "Unable to remove %s: %s" msgstr "無法移除 %s: %s" #: print.go:32 msgid "Version" msgstr "版本" #: print.go:50 msgid "Votes" msgstr "得到票數" #: print.go:87 msgid "Yay version v%s" msgstr "Yay 版本 v%s" #: pkg/menus/menu.go:49 msgid "[N]one" msgstr "[N]不做任何動作" #: pkg/settings/errors.go:29 msgid "aborting due to user" msgstr "使用者已自行中止 yay" #: pkg/settings/parser/parser.go:608 msgid "argument '-' specified without input on stdin" msgstr "指定了參數 '-' 但沒有 stdin 或其他輸入值" #: local_install.go:26 msgid "cannot find PKGBUILD and .SRCINFO in directory" msgstr "目錄內找不到 PKGBUILD 與 .SRCINFO 檔案" #: pkg/sync/build/pkg_archive.go:148 msgid "cannot find package name: %v" msgstr "找不到套件名稱:%v" #: pkg/sync/build/errors.go:30 msgid "could not find PKGDEST for: %s" msgstr "找不到 PKGDEST:%s" #: errors.go:9 msgid "could not find all required packages" msgstr "找不到所有需要的套件包" #: pkg/sync/build/errors.go:61 msgid "could not find any package archives listed in %s" msgstr "找不到任何 %s 中列舉的套件封存檔" #: pkg/sync/build/errors.go:50 pkg/upgrade/service.go:286 msgid "dependency" msgstr "作為依賴安裝" #: pkg/vcs/vcs.go:100 pkg/vcs/vcs.go:96 msgid "devel check for package failed: '%s' encountered an error" msgstr "開發套件包檢查失敗:「%s」 遇到了錯誤" #: pkg/menus/edit_menu.go:110 msgid "editor did not exit successfully, aborting: %s" msgstr "編輯器未成功退出,正在中止:%s" #: pkg/sync/workdir/aur_source.go:24 msgid "error downloading sources: %s" msgstr "下載套件包檔案時出錯:%s" #: pkg/download/errors.go:25 msgid "error fetching %s: %s" msgstr "取得 %s 時出錯:%s" #: pkg/sync/build/errors.go:9 msgid "error installing repo packages" msgstr "安裝套件包時出錯" #: pkg/sync/build/installer.go:266 pkg/sync/build/installer.go:270 msgid "error installing:" msgstr "安裝時出錯:" #: pkg/sync/build/installer.go:233 pkg/sync/build/installer.go:237 msgid "error making: %s" msgstr "編譯時出錯:%s" #: pkg/sync/workdir/merge.go:24 msgid "error merging %s: %s" msgstr "合併 %s 時出錯:%s" #: pkg/download/unified.go:59 msgid "error reading %s" msgstr "讀取 %s 時出錯" #: sync.go:36 msgid "error refreshing databases" msgstr "重新整理資料庫時出錯" #: pkg/sync/workdir/clean.go:51 pkg/sync/workdir/merge.go:17 msgid "error resetting %s: %s" msgstr "重置 %s 時出錯:%s" #: pkg/sync/build/errors.go:53 msgid "error updating package install reason to %s" msgstr "更新套件安裝原因至 %s 時出錯" #: pkg/sync/build/errors.go:48 msgid "explicit" msgstr "單獨指定安裝" #: pkg/settings/errors.go:23 msgid "failed to create directory '%s': %s" msgstr "無法建立資料夾 '%s':%s" #: pkg/settings/config.go:281 msgid "failed to open config file '%s': %s" msgstr "無法開啟設定檔案 '%s':%s" #: pkg/sync/srcinfo/service.go:114 msgid "failed to parse %s -- skipping: %s" msgstr "無法解析 %s -- 跳過:%s" #: pkg/sync/srcinfo/service.go:118 msgid "failed to parse %s: %s" msgstr "無法解析 %s:%s" #: local_install.go:77 msgid "failed to parse .SRCINFO" msgstr "無法解析 .SRCINFO" #: pkg/settings/config.go:291 msgid "failed to read config file '%s': %s" msgstr "無法讀取設定檔案 '%s':%s" #: pkg/cmd/graph/main.go:46 pkg/runtime/runtime.go:73 msgid "failed to retrieve aur Cache" msgstr "無法取得 AUR 快取" #: pkg/upgrade/sources.go:27 msgid "ignoring package devel upgrade (no AUR info found):" msgstr "忽略軟體包開發更新 (未找到 AUR 資訊):" #: pkg/text/errors.go:8 msgid "input too long" msgstr "輸入太長" #: pkg/db/ialpm/alpm.go:222 pkg/dep/dep_graph.go:761 msgid "invalid number: %s" msgstr "無效數字:%s" #: pkg/settings/parser/parser.go:174 msgid "invalid option '%s'" msgstr "無效選項「%s」" #: cmd.go:197 msgid "invalid option: '--deps' and '--explicit' may not be used together" msgstr "無效選項:不能同時指定「--deps」與「--explicit」" #: pkg/download/abs.go:22 msgid "invalid repository" msgstr "無效軟體源" #: pkg/db/ialpm/alpm.go:227 pkg/dep/dep_graph.go:767 msgid "invalid value: %d is not between %d and %d" msgstr "無效值: %d 不在 %d 和 %d 之間" #: pkg/text/input.go:48 msgid "no" msgstr "否" #: pkg/sync/srcinfo/pgp/keys.go:110 msgid "no keys to import" msgstr "沒有要匯入的金鑰" #: pkg/query/errors.go:20 msgid "no query was executed" msgstr "未進行任何查詢" #: local_install.go:66 msgid "no target directories specified" msgstr "沒有指定目標目錄" #: pkg/sync/build/installer.go:242 msgid "nothing to install for %s" msgstr "沒有要為 %s 安裝的東西" #: pkg/settings/parser/parser.go:164 msgid "only one operation may be used at a time" msgstr "一次只能使用一項操作" #: pkg/cmd/graph/main.go:70 msgid "only one target is allowed" msgstr "只能有一個目標" #: pkg/upgrade/service.go:291 msgid "package" msgid_plural "packages" msgstr[0] "套件包" #: print.go:187 msgid "package '%s' was not found" msgstr "找不到套件包「%s」" #: pkg/download/errors.go:15 msgid "package not found in AUR" msgstr "AUR 中找不到套件包" #: pkg/download/abs.go:23 msgid "package not found in repos" msgstr "倉庫源中找不到套件包" #: pkg/sync/srcinfo/pgp/keys.go:100 msgid "problem importing keys" msgstr "匯入金鑰時出錯" #: clean.go:105 msgid "removing AUR packages from cache..." msgstr "正在從快取中刪除 AUR 套件包..." #: clean.go:178 pkg/sync/workdir/clean.go:41 msgid "removing untracked AUR files from cache..." msgstr "正在從暫存中刪除未追蹤的 AUR 檔案..." #: pkg/sync/build/errors.go:38 msgid "the PKGDEST for %s is listed by makepkg but does not exist: %s" msgstr "%s 的 PKGDEST 由 makepkg 列出,但不存在:%s" #: pkg/sync/sync.go:45 msgid "there is nothing to do" msgstr "無事可做" #: pkg/db/ialpm/alpm.go:247 msgid "unable to CreateHandle: %s" msgstr "無法 CreateHandle:%s" #: cmd.go:186 msgid "unhandled operation" msgstr "未處理的操作" #: cmd.go:450 msgid "unknown-version" msgstr "未知版本" #: pkg/text/input.go:47 msgid "yes" msgstr "是" ================================================ FILE: print.go ================================================ package main import ( "context" "fmt" "io" "os" "strconv" "strings" "syscall" "unicode" aur "github.com/Jguer/aur" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "golang.org/x/sys/unix" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/upgrade" ) // printInfo prints package info like pacman -Si. func printInfo(logger *text.Logger, config *settings.Configuration, a *aur.Pkg, extendedInfo bool) { printInfoValue(logger, gotext.Get("Repository"), "aur") printInfoValue(logger, gotext.Get("Name"), a.Name) printInfoValue(logger, gotext.Get("Version"), a.Version) printInfoValue(logger, gotext.Get("Description"), a.Description) printInfoValue(logger, gotext.Get("URL"), a.URL) printInfoValue(logger, gotext.Get("Licenses"), a.License...) printInfoValue(logger, gotext.Get("Groups"), a.Groups...) printInfoValue(logger, gotext.Get("Provides"), a.Provides...) printInfoValue(logger, gotext.Get("Depends On"), a.Depends...) printInfoValue(logger, gotext.Get("Optional Deps"), a.OptDepends...) printInfoValue(logger, gotext.Get("Make Deps"), a.MakeDepends...) printInfoValue(logger, gotext.Get("Check Deps"), a.CheckDepends...) printInfoValue(logger, gotext.Get("Conflicts With"), a.Conflicts...) printInfoValue(logger, gotext.Get("Replaces"), a.Replaces...) printInfoValue(logger, gotext.Get("AUR URL"), config.AURURL+"/packages/"+a.Name) printInfoValue(logger, gotext.Get("First Submitted"), text.FormatTimeQuery(a.FirstSubmitted)) printInfoValue(logger, gotext.Get("Keywords"), a.Keywords...) printInfoValue(logger, gotext.Get("Last Modified"), text.FormatTimeQuery(a.LastModified)) printInfoValue(logger, gotext.Get("Maintainer"), a.Maintainer) printInfoValue(logger, gotext.Get("Popularity"), fmt.Sprintf("%f", a.Popularity)) printInfoValue(logger, gotext.Get("Votes"), fmt.Sprintf("%d", a.NumVotes)) if a.OutOfDate != 0 { printInfoValue(logger, gotext.Get("Out-of-date"), text.FormatTimeQuery(a.OutOfDate)) } else { printInfoValue(logger, gotext.Get("Out-of-date"), "No") } if extendedInfo { printInfoValue(logger, "ID", fmt.Sprintf("%d", a.ID)) printInfoValue(logger, gotext.Get("Package Base ID"), fmt.Sprintf("%d", a.PackageBaseID)) printInfoValue(logger, gotext.Get("Package Base"), a.PackageBase) printInfoValue(logger, gotext.Get("Snapshot URL"), config.AURURL+a.URLPath) } logger.Println() } // BiggestPackages prints the name of the ten biggest packages in the system. func biggestPackages(logger *text.Logger, dbExecutor db.Executor) { pkgS := dbExecutor.BiggestPackages() if len(pkgS) < 10 { return } for i := range 10 { logger.Printf("%s: %s\n", text.Bold(pkgS[i].Name()), text.Cyan(text.Human(pkgS[i].ISize()))) } } // localStatistics prints installed packages statistics. func localStatistics(ctx context.Context, run *runtime.Runtime, dbExecutor db.Executor) error { info := statistics(run, dbExecutor) remoteNames := dbExecutor.InstalledRemotePackageNames() remote := dbExecutor.InstalledRemotePackages() run.Logger.Infoln(gotext.Get("Yay version v%s", yayVersion)) run.Logger.Println(text.Bold(text.Cyan("==========================================="))) run.Logger.Infoln(gotext.Get("Total installed packages: %s", text.Cyan(strconv.Itoa(info.Totaln)))) run.Logger.Infoln(gotext.Get("Foreign installed packages: %s", text.Cyan(strconv.Itoa(len(remoteNames))))) run.Logger.Infoln(gotext.Get("Explicitly installed packages: %s", text.Cyan(strconv.Itoa(info.Expln)))) run.Logger.Infoln(gotext.Get("Total Size occupied by packages: %s", text.Cyan(text.Human(info.TotalSize)))) for path, size := range info.pacmanCaches { run.Logger.Infoln(gotext.Get("Size of pacman cache %s: %s", path, text.Cyan(text.Human(size)))) } run.Logger.Infoln(gotext.Get("Size of yay cache %s: %s", run.Cfg.BuildDir, text.Cyan(text.Human(info.yayCache)))) run.Logger.Println(text.Bold(text.Cyan("==========================================="))) run.Logger.Infoln(gotext.Get("Ten biggest packages:")) biggestPackages(run.Logger, dbExecutor) run.Logger.Println(text.Bold(text.Cyan("==========================================="))) aurData, err := run.AURClient.Get(ctx, &aur.Query{ Needles: remoteNames, By: aur.Name, }) if err != nil { return err } warnings := query.NewWarnings(run.Logger.Child("warnings")) for i := range aurData { warnings.AddToWarnings(remote, &aurData[i]) } warnings.Print() return nil } func printUpdateList(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor, enableDowngrade bool, filter upgrade.Filter, ) error { quietMode := cmdArgs.ExistsArg("q", "quiet") // TODO: handle quiet mode in a better way logger := text.NewLogger(io.Discard, os.Stderr, os.Stdin, run.Cfg.Debug, "update-list") dbExecutor.SetLogger(logger.Child("db")) oldNoConfirm := settings.NoConfirm settings.NoConfirm = true // restoring global NoConfirm to make tests work properly defer func() { settings.NoConfirm = oldNoConfirm }() targets := mapset.NewThreadUnsafeSet(cmdArgs.Targets...) grapher := dep.NewGrapher(dbExecutor, run.AURClient, false, true, false, false, cmdArgs.ExistsArg("needed"), logger.Child("grapher")) upService := upgrade.NewUpgradeService( grapher, run.AURClient, dbExecutor, run.VCSStore, run.Cfg, true, logger.Child("upgrade")) graph, errSysUp := upService.GraphUpgrades(ctx, nil, enableDowngrade, filter) if errSysUp != nil { return errSysUp } if graph.Len() == 0 { return fmt.Errorf("") } noTargets := targets.Cardinality() == 0 foreignFilter := cmdArgs.ExistsArg("m", "foreign") nativeFilter := cmdArgs.ExistsArg("n", "native") noUpdates := true _ = graph.ForEach(func(pkgName string, ii *dep.InstallInfo) error { if !ii.Upgrade { return nil } if noTargets || targets.Contains(pkgName) { if ii.Source == dep.Sync && foreignFilter { return nil } else if ii.Source == dep.AUR && nativeFilter { return nil } if quietMode { run.Logger.Printf("%s\n", pkgName) } else { run.Logger.Printf("%s %s -> %s\n", text.Bold(pkgName), text.Bold(text.Green(ii.LocalVersion)), text.Bold(text.Green(ii.Version))) } targets.Remove(pkgName) noUpdates = false } return nil }) missing := false targets.Each(func(pkgName string) bool { if dbExecutor.LocalPackage(pkgName) == nil { run.Logger.Errorln(gotext.Get("package '%s' was not found", pkgName)) missing = true } return false }) if missing || noUpdates { return fmt.Errorf("") } return nil } func printInfoValue(logger *text.Logger, key string, values ...string) { const ( keyLength = 32 delimCount = 2 ) specialWordsCount := 0 for _, runeValue := range key { // CJK handling: the character 'ー' is Katakana // but if use unicode.Katakana, it will return false if unicode.IsOneOf([]*unicode.RangeTable{ unicode.Han, unicode.Hiragana, unicode.Katakana, unicode.Hangul, }, runeValue) || runeValue == 'ー' { specialWordsCount++ } } keyTextCount := specialWordsCount - keyLength + delimCount str := fmt.Sprintf(text.Bold("%-*s: "), keyTextCount, key) if len(values) == 0 || (len(values) == 1 && values[0] == "") { logger.Printf("%s%s\n", str, gotext.Get("None")) return } maxCols := getColumnCount() cols := keyLength + len(values[0]) str += values[0] for _, value := range values[1:] { if maxCols > keyLength && cols+len(value)+delimCount >= maxCols { cols = keyLength str += "\n" + strings.Repeat(" ", keyLength) } else if cols != keyLength { str += strings.Repeat(" ", delimCount) cols += delimCount } str += value cols += len(value) } logger.Println(str) } var cachedColumnCount = -1 func getColumnCount() int { if cachedColumnCount > 0 { return cachedColumnCount } if count, err := strconv.Atoi(os.Getenv("COLUMNS")); err == nil { cachedColumnCount = count return cachedColumnCount } if ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ); err == nil { cachedColumnCount = int(ws.Col) return cachedColumnCount } return 80 } ================================================ FILE: print_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "fmt" "io" "os" "strings" "testing" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func TestPrintUpdateList(t *testing.T) { // The current method of capturing os.Stdout hinders parallelization. // Setting of global settings.NoConfirm in printUpdateList also hinders parallelization. // t.Parallel() pacmanBin := t.TempDir() + "/pacman" f, err := os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) mockDBName := mock.NewDB("core") mockDB := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", PBase: "vosk-api", PReason: alpm.PkgReasonExplicit, }, } }, InstalledRemotePackageNamesFn: func() []string { return []string{"vosk-api"} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{ "linux": { Package: &mock.Package{ PName: "linux", PVersion: "5.10.0", PDB: mockDBName, }, LocalVersion: "4.3.0", Reason: alpm.PkgReasonExplicit, }, "go": { Package: &mock.Package{ PName: "go", PVersion: "2:1.20.4-1", PDB: mockDBName, }, LocalVersion: "2:1.20.3-1", Reason: alpm.PkgReasonExplicit, }, }, nil }, LocalPackageFn: func(s string) mock.IPackage { if s == "no-update-pkg" { return &mock.Package{ PName: "no-update-pkg", PVersion: "3.3.3", PDB: mockDBName, } } return nil }, SetLoggerFn: func(logger *text.Logger) {}, } mockDBNoUpdates := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", PBase: "vosk-api", PReason: alpm.PkgReasonExplicit, }, } }, InstalledRemotePackageNamesFn: func() []string { return []string{"vosk-api"} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{}, nil }, LocalPackageFn: func(s string) mock.IPackage { return nil }, SetLoggerFn: func(logger *text.Logger) {}, } mockAUR := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "vosk-api", PackageBase: "vosk-api", Version: "0.3.45-1", }, }, nil }, } mockAURNoUpdates := &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "vosk-api", PackageBase: "vosk-api", Version: "0.3.43-1", }, }, nil }, } type mockData struct { db *mock.DBExecutor aurCache *mockaur.MockAUR } testCases := []struct { name string mockData mockData args []string targets []string wantPkgs []string wantErr bool }{ { name: "Qu", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u"}, targets: []string{}, wantPkgs: []string{ fmt.Sprintf("%s %s -> %s", text.Bold("linux"), text.Bold(text.Green("4.3.0")), text.Bold(text.Green("5.10.0")), ), fmt.Sprintf("%s %s -> %s", text.Bold("go"), text.Bold(text.Green("2:1.20.3-1")), text.Bold(text.Green("2:1.20.4-1")), ), fmt.Sprintf("%s %s -> %s", text.Bold("vosk-api"), text.Bold(text.Green("0.3.43-1")), text.Bold(text.Green("0.3.45-1")), ), }, }, { name: "Quq", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "q"}, targets: []string{}, wantPkgs: []string{"linux", "go", "vosk-api"}, }, { name: "Quq linux", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "q"}, targets: []string{"linux"}, wantPkgs: []string{"linux"}, }, { name: "Qunq", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "n", "q"}, targets: []string{}, wantPkgs: []string{"linux", "go"}, }, { name: "Qumq", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "m", "q"}, targets: []string{}, wantPkgs: []string{"vosk-api"}, }, { name: "Quq no-update-pkg", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "q"}, targets: []string{"no-update-pkg"}, wantPkgs: []string{}, wantErr: true, }, { name: "Quq non-existent-pkg", mockData: mockData{mockDB, mockAUR}, args: []string{"Q", "u", "q"}, targets: []string{"non-existent-pkg"}, wantPkgs: []string{}, wantErr: true, }, { name: "Qu no-updates-any", mockData: mockData{mockDBNoUpdates, mockAURNoUpdates}, args: []string{"Q", "u"}, targets: []string{}, wantPkgs: []string{}, wantErr: true, }, { name: "Qun no-updates-native", mockData: mockData{mockDBNoUpdates, mockAUR}, args: []string{"Q", "u", "n"}, targets: []string{}, wantPkgs: []string{}, wantErr: true, }, { name: "Qum no-updates-foreign", mockData: mockData{mockDB, mockAURNoUpdates}, args: []string{"Q", "u", "m"}, targets: []string{}, wantPkgs: []string{}, wantErr: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cmdBuilder := &exe.CmdBuilder{ SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", Runner: &exe.MockRunner{}, SudoLoopEnabled: false, } r, w, _ := os.Pipe() logger := text.NewLogger(w, io.Discard, strings.NewReader(""), true, "test") run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: logger, CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: tc.mockData.aurCache, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg(tc.args...) cmdArgs.AddTarget(tc.targets...) err = handleCmd(context.Background(), run, cmdArgs, tc.mockData.db) w.Close() out, _ := io.ReadAll(r) if tc.wantErr { require.Error(t, err) assert.EqualError(t, err, "") } else { require.NoError(t, err) } outStr := string(out) outPkgs := make([]string, 0) if outStr != "" { outPkgs = strings.Split(strings.TrimSuffix(outStr, "\n"), "\n") } assert.ElementsMatch(t, outPkgs, tc.wantPkgs, "Lists of packages should match") }) } } ================================================ FILE: query.go ================================================ package main import ( "context" "fmt" "io/fs" "path/filepath" aur "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" ) // SyncSearch presents a query to the local repos and to the AUR. func syncSearch(ctx context.Context, pkgS []string, dbExecutor db.Executor, queryBuilder query.Builder, verbose bool, ) error { queryBuilder.Execute(ctx, dbExecutor, pkgS) searchMode := query.Minimal if verbose { searchMode = query.Detailed } return queryBuilder.Results(dbExecutor, searchMode) } // SyncInfo serves as a pacman -Si for repo packages and AUR packages. func syncInfo(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, pkgS []string, dbExecutor db.Executor, ) error { var ( remoteAurPkgs []aur.Pkg err error missing = false ) pkgS = query.RemoveInvalidTargets(run.Logger, pkgS, run.Cfg.Mode) expandedPackages := []string{} for _, pkg := range pkgS { groupPackages := dbExecutor.PackagesFromGroup(pkg) if len(groupPackages) > 0 { for _, p := range groupPackages { expandedPackages = append(expandedPackages, p.Name()) } } else { expandedPackages = append(expandedPackages, pkg) } } pkgS = expandedPackages aurS, repoS := packageSlices(pkgS, run.Cfg, dbExecutor) if len(repoS) == 0 && len(aurS) == 0 { if run.Cfg.Mode != parser.ModeRepo { aurS = dbExecutor.InstalledRemotePackageNames() } if run.Cfg.Mode != parser.ModeAUR { repoS = dbExecutor.InstalledSyncPackageNames() } } if len(aurS) != 0 { noDB := make([]string, 0, len(aurS)) for _, pkg := range aurS { _, name := text.SplitDBFromName(pkg) noDB = append(noDB, name) } remoteAurPkgs, err = run.AURClient.Get(ctx, &aur.Query{ Needles: noDB, By: aur.Name, }) if err != nil { run.Logger.Errorln(err) } // Check for any missing packages and print errors for any not found found := mapset.NewThreadUnsafeSet[string]() for i := range remoteAurPkgs { found.Add(remoteAurPkgs[i].Name) } for _, name := range noDB { if !found.Contains(name) { missing = true run.Logger.Errorln(gotext.Get("No AUR package found for"), " ", name) } } } if len(repoS) != 0 || (len(aurS) == 0 && len(repoS) == 0) { arguments := cmdArgs.Copy() arguments.ClearTargets() arguments.AddTarget(repoS...) err = run.CmdBuilder.Show(run.CmdBuilder.BuildPacmanCmd(ctx, arguments, run.Cfg.Mode, settings.NoConfirm)) if err != nil { return err } } for i := range remoteAurPkgs { printInfo(run.Logger, run.Cfg, &remoteAurPkgs[i], cmdArgs.ExistsDouble("i")) } if missing { err = fmt.Errorf("") } return err } // PackageSlices separates an input slice into aur and repo slices. func packageSlices(toCheck []string, config *settings.Configuration, dbExecutor db.Executor) (aurNames, repoNames []string) { for _, _pkg := range toCheck { dbName, name := text.SplitDBFromName(_pkg) if dbName == "aur" || config.Mode == parser.ModeAUR { aurNames = append(aurNames, _pkg) continue } else if dbName != "" || config.Mode == parser.ModeRepo { repoNames = append(repoNames, _pkg) continue } if dbExecutor.SyncSatisfierExists(name) || len(dbExecutor.PackagesFromGroup(name)) != 0 { repoNames = append(repoNames, _pkg) } else { aurNames = append(aurNames, _pkg) } } return aurNames, repoNames } // MapSetMap is a Map of Sets. type mapSetMap[T comparable] map[T]mapset.Set[T] // Add adds a new value to the Map. // If n is already in the map, then v is appended to the StringSet under that key. // Otherwise a new Set is created containing v. func (mss mapSetMap[T]) Add(n, v T) { if _, ok := mss[n]; !ok { mss[n] = mapset.NewSet[T]() } mss[n].Add(v) } // HangingPackages returns a list of packages installed as deps // and unneeded by the system // removeOptional decides whether optional dependencies are counted or not. func hangingPackages(removeOptional bool, dbExecutor db.Executor) (hanging []string) { // safePackages represents every package in the system in one of 3 states // State = 0 - Remove package from the system // State = 1 - Keep package in the system; need to iterate over dependencies // 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(mapSetMap[string]) packages := dbExecutor.LocalPackages() // Mark explicit dependencies and enumerate the provides list for _, pkg := range packages { if pkg.Reason() == alpm.PkgReasonExplicit { safePackages[pkg.Name()] = 1 } else { safePackages[pkg.Name()] = 0 } for _, dep := range dbExecutor.PackageProvides(pkg) { provides.Add(dep.Name, pkg.Name()) } } iterateAgain := true for iterateAgain { iterateAgain = false for _, pkg := range packages { if state := safePackages[pkg.Name()]; state == 0 || state == 2 { continue } safePackages[pkg.Name()] = 2 deps := dbExecutor.PackageDepends(pkg) if !removeOptional { deps = append(deps, dbExecutor.PackageOptionalDepends(pkg)...) } // Update state for dependencies for _, dep := range deps { // Don't assume a dependency is installed state, ok := safePackages[dep.Name] if !ok { // Check if dep is a provides rather than actual package name if pset, ok2 := provides[dep.Name]; ok2 { for p := range pset.Iter() { if safePackages[p] == 0 { iterateAgain = true safePackages[p] = 1 } } } continue } if state == 0 { iterateAgain = true safePackages[dep.Name] = 1 } } } } // Build list of packages to be removed for _, pkg := range packages { if safePackages[pkg.Name()] == 0 { hanging = append(hanging, pkg.Name()) } } return hanging } func getFolderSize(path string) (size int64) { _ = filepath.WalkDir(path, func(p string, entry fs.DirEntry, err error) error { if err != nil || entry == nil { return nil } info, infoErr := entry.Info() if infoErr != nil || info == nil { return nil } size += info.Size() return nil }) return size } // Statistics returns statistics about packages installed in system. func statistics(run *runtime.Runtime, dbExecutor db.Executor) (res struct { Totaln int Expln int TotalSize int64 pacmanCaches map[string]int64 yayCache int64 }, ) { for _, pkg := range dbExecutor.LocalPackages() { res.TotalSize += pkg.ISize() res.Totaln++ if pkg.Reason() == alpm.PkgReasonExplicit { res.Expln++ } } res.pacmanCaches = make(map[string]int64) for _, path := range run.PacmanConf.CacheDir { res.pacmanCaches[path] = getFolderSize(path) } res.yayCache = getFolderSize(run.Cfg.BuildDir) return } ================================================ FILE: query_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "encoding/json" "fmt" "io" "os" "os/exec" "strings" "testing" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/query" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/aur" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func getFromFile(t *testing.T, filePath string) mockaur.GetFunc { f, err := os.Open(filePath) require.NoError(t, err) fBytes, err := io.ReadAll(f) require.NoError(t, err) pkgs := []aur.Pkg{} err = json.Unmarshal(fBytes, &pkgs) require.NoError(t, err) return func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return pkgs, nil } } func TestSyncInfo(t *testing.T) { t.Parallel() pacmanBin := t.TempDir() + "/pacman" testCases := []struct { name string args []string targets []string wantShow []string wantErr bool }{ { name: "Si linux", args: []string{"S", "i"}, targets: []string{"linux"}, wantShow: []string{"pacman", "-S", "-i", "--config", "/etc/pacman.conf", "--", "linux"}, }, { name: "Si jellyfin", args: []string{"S", "i"}, targets: []string{"jellyfin"}, wantShow: []string{}, }, { name: "Si linux jellyfin", args: []string{"S", "i"}, targets: []string{"linux", "jellyfin"}, wantShow: []string{"pacman", "-S", "-i", "--config", "/etc/pacman.conf", "--", "linux"}, }, { name: "Si jellyfin", args: []string{"S", "i"}, targets: []string{"jellyfin"}, wantShow: []string{}, }, { name: "Si missing", args: []string{"S", "i"}, targets: []string{"missing"}, wantShow: []string{}, wantErr: true, }, { name: "Si arduino", args: []string{"S", "i"}, targets: []string{"arduino"}, wantShow: []string{"pacman", "-S", "-i", "--config", "/etc/pacman.conf", "--", "arduino-cli"}, }, } dbExc := &mock.DBExecutor{ SyncSatisfierFn: func(s string) mock.IPackage { if s == "linux" { return &mock.Package{ PName: "linux", PBase: "linux", } } if s == "arduino-cli" { return &mock.Package{ PName: "arduino-cli", PBase: "arduino-cli", } } return nil }, PackagesFromGroupFn: func(s string) []mock.IPackage { if s == "arduino" { return []mock.IPackage{ &mock.Package{PName: "arduino-cli"}, } } return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { if query.Needles[0] == "jellyfin" { jfinFn := getFromFile(t, "pkg/dep/testdata/jellyfin.json") return jfinFn(ctx, query) } return nil, fmt.Errorf("not found") }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mockRunner := &exe.MockRunner{ CaptureFn: func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil }, ShowFn: func(cmd *exec.Cmd) error { return nil }, } cmdBuilder := &exe.CmdBuilder{ SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } run := &runtime.Runtime{ CmdBuilder: cmdBuilder, AURClient: mockAUR, Logger: newTestLogger(), Cfg: &settings.Configuration{}, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg(tc.args...) cmdArgs.AddTarget(tc.targets...) err := handleCmd(context.Background(), run, cmdArgs, dbExc, ) if tc.wantErr { require.Error(t, err) assert.EqualError(t, err, "") } else { require.NoError(t, err) } if len(tc.wantShow) == 0 { assert.Empty(t, mockRunner.ShowCalls) return } for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } }) } } // Should not error when there is a DB called aur func TestSyncSearchAURDB(t *testing.T) { t.Parallel() pacmanBin := t.TempDir() + "/pacman" testCases := []struct { name string args []string targets []string wantShow []string wantErr bool bottomUp bool singleLine bool mixed bool }{ { name: "Ss jellyfin false false", args: []string{"S", "s"}, targets: []string{"jellyfin"}, wantShow: []string{}, }, { name: "Ss jellyfin true false", args: []string{"S", "s"}, targets: []string{"jellyfin"}, wantShow: []string{}, singleLine: true, }, { name: "Ss jellyfin true true", args: []string{"S", "s"}, targets: []string{"jellyfin"}, wantShow: []string{}, singleLine: true, mixed: true, }, { name: "Ss jellyfin false true", args: []string{"S", "s"}, targets: []string{"jellyfin"}, wantShow: []string{}, singleLine: false, mixed: true, }, { name: "Ss jellyfin true true - bottomup", args: []string{"S", "s"}, targets: []string{"jellyfin"}, wantShow: []string{}, singleLine: true, mixed: true, bottomUp: true, }, } dbExc := &mock.DBExecutor{ SyncPackagesFn: func(s ...string) []mock.IPackage { return []mock.IPackage{ &mock.Package{ PName: "jellyfin", PBase: "jellyfin", PDB: mock.NewDB("aur"), }, } }, LocalPackageFn: func(s string) mock.IPackage { return &mock.Package{ PName: "jellyfin", PBase: "jellyfin", PDB: mock.NewDB("aur"), } }, PackagesFromGroupFn: func(s string) []mock.IPackage { return nil }, } mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { if query.Needles[0] == "jellyfin" { jfinFn := getFromFile(t, "pkg/dep/testdata/jellyfin.json") return jfinFn(ctx, query) } return nil, fmt.Errorf("not found") }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mockRunner := &exe.MockRunner{ CaptureFn: func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil }, ShowFn: func(cmd *exec.Cmd) error { return nil }, } cmdBuilder := &exe.CmdBuilder{ SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } run := &runtime.Runtime{ CmdBuilder: cmdBuilder, AURClient: mockAUR, QueryBuilder: query.NewSourceQueryBuilder(mockAUR, newTestLogger(), "votes", parser.ModeAny, "name", tc.bottomUp, tc.singleLine, tc.mixed), Logger: newTestLogger(), Cfg: &settings.Configuration{}, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg(tc.args...) cmdArgs.AddTarget(tc.targets...) err := handleCmd(context.Background(), run, cmdArgs, dbExc, ) if tc.wantErr { require.Error(t, err) assert.EqualError(t, err, "") } else { require.NoError(t, err) } if len(tc.wantShow) == 0 { assert.Empty(t, mockRunner.ShowCalls) return } for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(tc.wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } }) } } ================================================ FILE: sync.go ================================================ package main import ( "context" "fmt" "strings" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/multierror" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/sync" "github.com/Jguer/yay/v12/pkg/upgrade" ) func syncInstall(ctx context.Context, run *runtime.Runtime, cmdArgs *parser.Arguments, dbExecutor db.Executor, ) error { aurCache := run.AURClient refreshArg := cmdArgs.ExistsArg("y", "refresh") noDeps := cmdArgs.ExistsArg("d", "nodeps") noCheck := strings.Contains(run.Cfg.MFlags, "--nocheck") if noDeps { run.CmdBuilder.AddMakepkgFlag("-d") } if refreshArg && run.Cfg.Mode.AtLeastRepo() { if errR := earlyRefresh(ctx, run.Cfg, run.CmdBuilder, cmdArgs); errR != nil { return fmt.Errorf("%s - %w", gotext.Get("error refreshing databases"), errR) } // we may have done -Sy, our handle now has an old // database. if errRefresh := dbExecutor.RefreshHandle(); errRefresh != nil { return errRefresh } } grapher := dep.NewGrapher(dbExecutor, aurCache, false, settings.NoConfirm, noDeps, noCheck, cmdArgs.ExistsArg("needed"), run.Logger.Child("grapher")) graph, err := grapher.GraphFromTargets(ctx, nil, cmdArgs.Targets) if err != nil { return err } excluded := []string{} if cmdArgs.ExistsArg("u", "sysupgrade") { var errSysUp error upService := upgrade.NewUpgradeService( grapher, aurCache, dbExecutor, run.VCSStore, run.Cfg, settings.NoConfirm, run.Logger.Child("upgrade")) graph, errSysUp = upService.GraphUpgrades(ctx, graph, cmdArgs.ExistsDouble("u", "sysupgrade"), func(*upgrade.Upgrade) bool { return true }) if errSysUp != nil { return errSysUp } upService.AURWarnings.Print() excluded, errSysUp = upService.UserExcludeUpgrades(graph) if errSysUp != nil { return errSysUp } } opService := sync.NewOperationService(ctx, dbExecutor, run) multiErr := &multierror.MultiError{} targets := graph.TopoSortedLayers(func(s string, ii *dep.InstallInfo) error { if ii.Source == dep.Missing { multiErr.Add(fmt.Errorf("%w: %s %s", ErrPackagesNotFound, s, ii.Version)) } return nil }) if err := multiErr.Return(); err != nil { return err } return opService.Run(ctx, run, cmdArgs, targets, excluded) } func earlyRefresh(ctx context.Context, cfg *settings.Configuration, cmdBuilder exe.ICmdBuilder, cmdArgs *parser.Arguments) error { arguments := cmdArgs.Copy() if cfg.CombinedUpgrade { arguments.DelArg("u", "sysupgrade") } arguments.DelArg("s", "search") arguments.DelArg("i", "info") arguments.DelArg("l", "list") arguments.ClearTargets() return cmdBuilder.Show(cmdBuilder.BuildPacmanCmd(ctx, arguments, cfg.Mode, settings.NoConfirm)) } ================================================ FILE: sync_test.go ================================================ //go:build !integration // +build !integration package main import ( "context" "errors" "fmt" "io" "os" "os/exec" "strings" "sync" "testing" "github.com/Jguer/aur" alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/db/mock" mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/settings" "github.com/Jguer/yay/v12/pkg/settings/exe" "github.com/Jguer/yay/v12/pkg/settings/parser" "github.com/Jguer/yay/v12/pkg/text" "github.com/Jguer/yay/v12/pkg/vcs" ) func TestSyncUpgrade(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("S") cmdArgs.AddArg("y") cmdArgs.AddArg("u") dbName := mock.NewDB("core") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{ "linux": { Package: &mock.Package{ PName: "linux", PVersion: "5.10.0", PDB: dbName, }, LocalVersion: "4.3.0", Reason: alpm.PkgReasonExplicit, }, }, nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n"), true, "test"), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) wantCapture := []string{} wantShow := []string{ "pacman -S -y --config /etc/pacman.conf --", "pacman -S -y -u --config /etc/pacman.conf --", } require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestSyncUpgrade_IgnoreAll(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("S") cmdArgs.AddArg("y") cmdArgs.AddArg("u") dbName := mock.NewDB("core") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{ "linux": { Package: &mock.Package{ PName: "linux", PVersion: "5.10.0", PDB: dbName, }, LocalVersion: "4.3.0", Reason: alpm.PkgReasonExplicit, }, }, nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) wantCapture := []string{} wantShow := []string{ "pacman -S -y --config /etc/pacman.conf --", } require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func TestSyncUpgrade_IgnoreOne(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("S") cmdArgs.AddArg("y") cmdArgs.AddArg("u") dbName := mock.NewDB("core") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{ "gcc": { Package: &mock.Package{ PName: "gcc", PVersion: "6.0.0", PDB: dbName, }, LocalVersion: "5.0.0", Reason: alpm.PkgReasonExplicit, }, "linux": { Package: &mock.Package{ PName: "linux", PVersion: "5.10.0", PDB: dbName, }, LocalVersion: "4.3.0", Reason: alpm.PkgReasonExplicit, }, "linux-headers": { Package: &mock.Package{ PName: "linux-headers", PVersion: "5.10.0", PDB: dbName, }, LocalVersion: "4.3.0", Reason: alpm.PkgReasonDepend, }, }, nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", }, Logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) wantCapture := []string{} wantShow := []string{ "pacman -S -y --config /etc/pacman.conf --", "pacman -S -y -u --config /etc/pacman.conf --ignore linux-headers --", } require.Len(t, mockRunner.ShowCalls, len(wantShow)) require.Len(t, mockRunner.CaptureCalls, len(wantCapture)) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } // Pinned deps with rollup func TestSyncUpgradeAURPinnedSplitPackage(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" tmpDir := t.TempDir() gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) pkgBuildDir := tmpDir + "/vosk-api" os.Mkdir(pkgBuildDir, 0o755) fSource, err := os.OpenFile(pkgBuildDir+"/.SRCINFO", os.O_RDWR|os.O_CREATE, 0o666) require.NoError(t, err) n, errF := fSource.WriteString(`pkgbase = vosk-api pkgdesc = Offline speech recognition toolkit pkgver = 0.3.45 pkgrel = 1 url = https://alphacephei.com/vosk/ arch = x86_64 license = Apache pkgname = vosk-api pkgdesc = vosk-api pkgname = python-vosk pkgdesc = Python module for vosk-api depends = vosk-api=0.3.45`) require.NoError(t, errF) require.Greater(t, n, 0) require.NoError(t, fSource.Close()) tars := []string{ tmpDir + "/vosk-api-0.3.45-1-x86_64.pkg.tar.zst", tmpDir + "/python-vosk-0.3.45-1-x86_64.pkg.tar.zst", } captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return strings.Join(tars, "\n"), "", nil } once := sync.Once{} showOverride := func(cmd *exec.Cmd) error { once.Do(func() { for _, tar := range tars { f, err := os.OpenFile(tar, os.O_RDONLY|os.O_CREATE, 0o666) require.NoError(t, err) require.NoError(t, f.Close()) } }) if sanitizeCall(cmd.String(), tmpDir, makepkgBin, pacmanBin, gitBin) == "pacman -U --config /etc/pacman.conf -- /testdir/vosk-api-0.3.45-1-x86_64.pkg.tar.zst" { return errors.New("Unsatisfied dependency") } return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("S") cmdArgs.AddArg("y") cmdArgs.AddArg("u") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, SyncSatisfierFn: func(s string) mock.IPackage { return nil }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", PBase: "vosk-api", PReason: alpm.PkgReasonDepend, }, "python-vosk": &mock.Package{ PName: "python-vosk", PVersion: "0.3.43-1", PBase: "python-vosk", PReason: alpm.PkgReasonExplicit, // TODO: fix mock Depends }, } }, InstalledRemotePackageNamesFn: func() []string { return []string{"vosk-api", "python-vosk"} }, LocalSatisfierExistsFn: func(s string) bool { return false }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{}, nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ DoubleConfirm: true, RemoveMake: "no", BuildDir: tmpDir, }, Logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader("\n\n\n\n"), true, "test"), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{ { Name: "vosk-api", PackageBase: "vosk-api", Version: "0.3.45-1", }, { Name: "python-vosk", PackageBase: "vosk-api", Version: "0.3.45-1", Depends: []string{ "vosk-api=0.3.45", }, }, }, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) wantCapture := []string{ "/usr/bin/git -C /testdir/vosk-api reset --hard HEAD", "/usr/bin/git -C /testdir/vosk-api merge --no-edit --ff", "makepkg --packagelist", "makepkg --packagelist", } wantShow := []string{ "pacman -S -y --config /etc/pacman.conf --", "makepkg --verifysource --skippgpcheck -f -Cc", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/vosk-api-0.3.45-1-x86_64.pkg.tar.zst", "makepkg --nobuild -f -C --ignorearch", "makepkg -c --nobuild --noextract --ignorearch", "pacman -U --config /etc/pacman.conf -- /testdir/vosk-api-0.3.45-1-x86_64.pkg.tar.zst /testdir/python-vosk-0.3.45-1-x86_64.pkg.tar.zst", "pacman -D -q --asdeps --config /etc/pacman.conf -- vosk-api", "pacman -D -q --asexplicit --config /etc/pacman.conf -- python-vosk", } require.Len(t, mockRunner.ShowCalls, len(wantShow), fmt.Sprintf("%#v", sanitizeCalls(mockRunner.ShowCalls, tmpDir, makepkgBin, pacmanBin, gitBin))) require.Len(t, mockRunner.CaptureCalls, len(wantCapture), fmt.Sprintf("%#v", sanitizeCalls(mockRunner.CaptureCalls, tmpDir, makepkgBin, pacmanBin, gitBin))) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, tmpDir, "/testdir") // replace the temp dir with a static path show = strings.ReplaceAll(show, makepkgBin, "makepkg") show = strings.ReplaceAll(show, pacmanBin, "pacman") show = strings.ReplaceAll(show, gitBin, "git") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(wantShow[i], " "), fmt.Sprintf("%d - %s", i, show)) } } func sanitizeCalls(calls []exe.Call, tmpDir, makepkg, pacman, git string) []string { san := make([]string, 0, len(calls)) for _, c := range calls { s := c.Args[0].(*exec.Cmd).String() san = append(san, sanitizeCall(s, tmpDir, makepkg, pacman, git)) } return san } func sanitizeCall(s, tmpDir, makepkg, pacman, git string) string { _, after, found := strings.Cut(s, makepkg) if found { s = "makepkg" + after } _, after, found = strings.Cut(s, pacman) if found { s = "pacman" + after } _, after, found = strings.Cut(s, git) if found { s = "git" + after } s = strings.ReplaceAll(s, tmpDir, "/testdir") return s } func TestSyncUpgrade_NoCombinedUpgrade(t *testing.T) { t.Parallel() testCases := []struct { name string combinedUpgrade bool want []string }{ { name: "combined upgrade", combinedUpgrade: true, want: []string{"pacman -S -y -u --config /etc/pacman.conf --"}, }, { name: "no combined upgrade", combinedUpgrade: false, want: []string{"pacman -S -y --config /etc/pacman.conf --"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() makepkgBin := t.TempDir() + "/makepkg" pacmanBin := t.TempDir() + "/pacman" gitBin := t.TempDir() + "/git" f, err := os.OpenFile(makepkgBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(pacmanBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) f, err = os.OpenFile(gitBin, os.O_RDONLY|os.O_CREATE, 0o755) require.NoError(t, err) require.NoError(t, f.Close()) captureOverride := func(cmd *exec.Cmd) (stdout string, stderr string, err error) { return "", "", nil } showOverride := func(cmd *exec.Cmd) error { return nil } mockRunner := &exe.MockRunner{CaptureFn: captureOverride, ShowFn: showOverride} cmdBuilder := &exe.CmdBuilder{ MakepkgBin: makepkgBin, SudoBin: "su", PacmanBin: pacmanBin, PacmanConfigPath: "/etc/pacman.conf", GitBin: "git", Runner: mockRunner, SudoLoopEnabled: false, } cmdArgs := parser.MakeArguments() cmdArgs.AddArg("S") cmdArgs.AddArg("y") cmdArgs.AddArg("u") db := &mock.DBExecutor{ AlpmArchitecturesFn: func() ([]string, error) { return []string{"x86_64"}, nil }, RefreshHandleFn: func() error { return nil }, ReposFn: func() []string { return []string{"core"} }, InstalledRemotePackagesFn: func() map[string]alpm.Package { return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} }, SyncUpgradesFn: func( bool, ) (map[string]db.SyncUpgrade, error) { return map[string]db.SyncUpgrade{}, nil }, } run := &runtime.Runtime{ Cfg: &settings.Configuration{ RemoveMake: "no", CombinedUpgrade: false, }, Logger: text.NewLogger(io.Discard, os.Stderr, strings.NewReader("1\n"), true, "test"), CmdBuilder: cmdBuilder, VCSStore: &vcs.Mock{}, AURClient: &mockaur.MockAUR{ GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { return []aur.Pkg{}, nil }, }, } err = handleCmd(context.Background(), run, cmdArgs, db) require.NoError(t, err) require.Len(t, mockRunner.ShowCalls, len(tc.want)) require.Len(t, mockRunner.CaptureCalls, 0) for i, call := range mockRunner.ShowCalls { show := call.Args[0].(*exec.Cmd).String() show = strings.ReplaceAll(show, pacmanBin, "pacman") // options are in a different order on different systems and on CI root user is used assert.Subset(t, strings.Split(show, " "), strings.Split(tc.want[i], " "), fmt.Sprintf("%d - %s", i, show)) } }) } } ================================================ FILE: testdata/cephbin/.SRCINFO ================================================ pkgbase = ceph-bin pkgdesc = Distributed, fault-tolerant storage platform delivering object, block, and file system pkgver = 17.2.6 pkgrel = 2 url = https://ceph.com/ arch = x86_64 license = GPL noextract = ceph-17.2.6-2.tar.zst noextract = ceph-libs-17.2.6-2.tar.zst options = emptydirs source = ceph-17.2.6-2.tar.zst::https://github.com/bazaah/aur-ceph/releases/download/v17.2.6-2/ceph_linux_x86_64.tar.zstd source = ceph-libs-17.2.6-2.tar.zst::https://github.com/bazaah/aur-ceph/releases/download/v17.2.6-2/ceph_libs_linux_x86_64.tar.zstd sha512sums = db38d454ef9fda99fbe9a0954ccb4a8a7b66537f5cb1998dd552161049a6750a82a218d6e95f2bbc2a501637bf13f2f2d0fd6a12b367733cd294364722ba236c sha512sums = 2218b8d81ce8b94daa63ebd7674bd6d8e3da047ad46bbde1e1666eaa10d429a329ad1e6fa43d9f9d07ba5ce06b57dcc442ca3269eb482bffbb1c25d247d2ba1b sha512sums = 029f5fd11f30800a78fdc1e4c6541c413be498b284271ef93886268fd47002f6505df698c64a8118f1c2dcef22081c4fcc79e6047db7202d66f2e3e132830742 pkgname = ceph-bin depends = ceph-libs=17.2.6-2 depends = dep1 depends = dep2 provides = ceph=17.2.6-2 conflicts = ceph pkgname = ceph-libs-bin depends = dep1 depends = curl provides = ceph-libs=17.2.6-2 conflicts = ceph-libs ================================================ FILE: testdata/cephbin/PKGBUILD ================================================ ================================================ FILE: testdata/gourou/.SRCINFO ================================================ pkgbase = gourou pkgdesc = Download and decrypt adobe encrypted (acsm) pdf and epub files pkgver = 0.8.1 pkgrel = 4 url = https://indefero.soutade.fr/p/libgourou arch = x86_64 license = LGPL3 depends = glibc depends = gcc-libs depends = zlib depends = libzip depends = openssl depends = pugixml depends = curl provides = gourou=0.8.1 provides = libgourou=0.8.1 conflicts = gourou-git conflicts = gourou-bin options = strip source = git://soutade.fr/libgourou.git#tag=v0.8.1 source = git://soutade.fr/updfparser.git#commit=a421098092ba600fb1686a7df8fc58cd67429f59 source = build.patch sha512sums = SKIP sha512sums = SKIP sha512sums = 768e49fddcabe8b4c6f771ebbddf2618ab59e7b1a399d99aa9a9881f932e092210878ef576144593684b4c3a763218c5b546dbe19fdbadeff13995245bffda19 pkgname = gourou ================================================ FILE: testdata/gourou/PKGBUILD ================================================ ================================================ FILE: testdata/jfin/.SRCINFO ================================================ pkgbase = jellyfin pkgdesc = The Free Software Media System pkgver = 10.8.4 pkgrel = 1 url = https://github.com/jellyfin/jellyfin arch = i686 arch = x86_64 arch = armv6h license = GPL2 makedepends = dotnet-sdk>=6 makedepends = dotnet-sdk<7 makedepends = nodejs makedepends = npm makedepends = git source = jellyfin-10.8.4.tar.gz::https://github.com/jellyfin/jellyfin/archive/v10.8.4.tar.gz source = jellyfin-web-10.8.4.tar.gz::https://github.com/jellyfin/jellyfin-web/archive/v10.8.4.tar.gz source = jellyfin.conf source = jellyfin.service source = jellyfin.sysusers source = jellyfin.tmpfiles sha512sums = cf472f36a759a7eb3724dac79d3bd2d6c9c58fc375293ad6eb8b5ce1ea1a8f6dd296cc36113b80b1c705a99eafb2bd9ffd9381fd52fa19aa12018d50656c9bde sha512sums = 21983940689475de7f9d37a1016fb2dd740986ac27ffa2e0eac0bc9c84d68ac557fdc8afb64ca70b867af2d1e438293b98d5c155da402d3e985ab831042ba176 sha512sums = 2aa97a1a7a8a447171b59be3e93183e09cbbc32c816843cc47c6777b9aec48bd9c1d9d354f166e0b000ad8d2e94e6e4b0559aa52e5c159abbc103ed2c5afa3f0 sha512sums = 99d02080b1b92e731250f39ddd13ceca7129d69d0c05e0939620cbc3f499a9574668c63fa889704a4905560888131e980d7ab1fbcc5837b04d33ce26daa9d42b sha512sums = 6fc2638e6ec4b1ee0240e17815c91107b694e5fde72c1bc7956c83067bbeacb632de899b86837e47a0ec04288131b15c20746373b45e0669c8976069a55d627a sha512sums = 45a62b62d97b9a83289d4dfde684163b1bcf340c1921fb958e5a701812c61b392901841940c67e5fa5148783277d5b4dc65ba01d3a22e8f855ea62154ad9be33 pkgname = jellyfin depends = jellyfin-web=10.8.4 depends = jellyfin-server=10.8.4 pkgname = jellyfin-web pkgdesc = Jellyfin web client pkgname = jellyfin-server pkgdesc = Jellyfin server component depends = dotnet-runtime>=6 depends = dotnet-runtime<7 depends = aspnet-runtime>=6 depends = aspnet-runtime<7 depends = ffmpeg depends = sqlite backup = etc/conf.d/jellyfin ================================================ FILE: testdata/jfin/PKGBUILD ================================================ ================================================ FILE: testdata/libzip-git/.SRCINFO ================================================ pkgbase = libzip-git pkgdesc = C library for reading, creating, and modifying zip archives pkgver = 1.9.2.r144.gdadb14d5 pkgrel = 1 url = https://libzip.org/ arch = i686 arch = x86_64 license = BSD makedepends = git makedepends = cmake depends = glibc depends = bzip2 depends = gnutls depends = openssl depends = xz depends = zlib depends = zstd provides = libzip=1.9.2.r144.gdadb14d5 conflicts = libzip source = git+https://github.com/nih-at/libzip.git sha256sums = SKIP pkgname = libzip-git ================================================ FILE: testdata/libzip-git/PKGBUILD ================================================ ================================================ FILE: testdata/pacman.conf ================================================ # # /etc/pacman.conf # # See the pacman.conf(5) manpage for option and repository directives # # GENERAL OPTIONS # [options] # The following paths are commented out with their default values listed. # If you wish to use different paths, uncomment and update the paths. RootDir = / DBPath = /var/lib/pacman/ CacheDir = /var/cache/pacman/pkg/ LogFile = /var/log/pacman.log GPGDir = /etc/pacman.d/gnupg/ HookDir = /etc/pacman.d/hooks/ HoldPkg = pacman glibc #XferCommand = /usr/bin/curl -L -C - -f -o %o %u XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u CleanMethod = KeepInstalled Architecture = auto # Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup IgnorePkg = xorm IgnoreGroup = yorm #NoUpgrade = #NoExtract = # Misc options UseSyslog Color NoProgressBar CheckSpace VerbosePkgLists ParallelDownloads = 5 # By default, pacman accepts packages signed by keys that its local keyring # trusts (see pacman-key and its man page), as well as unsigned packages. SigLevel = Required DatabaseOptional LocalFileSigLevel = Optional RemoteFileSigLevel = Required # NOTE: You must run `pacman-key --init` before first using pacman; the local # keyring can then be populated with the keys of all official Arch Linux # packagers with `pacman-key --populate archlinux`. # # REPOSITORIES # - can be defined here or included from another file # - pacman will search repositories in the order defined here # - local/custom mirrors can be added here or in separate files # - repositories listed first will take precedence when packages # have identical names, regardless of version number # - URLs will have $repo replaced by the name of the current repo # - URLs will have $arch replaced by the name of the architecture # # Repository entries are of the format: # [repo-name] # Server = ServerName # Include = IncludePath # # The header [repo-name] is crucial - it must be present and # uncommented to enable the repo. # # The testing repositories are disabled by default. To enable, uncomment the # repo name header and Include lines. You can add preferred servers immediately # after the header, and they will be used before the default mirrors. #[core-testing] #Include = /etc/pacman.d/mirrorlist [core] Server = Core #[extra-testing] #Include = /etc/pacman.d/mirrorlist [extra] Server = Extra # If you want to run 32 bit applications on your x86_64 system, # enable the multilib repositories as required here. #[multilib-testing] #Include = /etc/pacman.d/mirrorlist [multilib] Server = repo3 Server = multilib # An example of a custom package repository. See the pacman manpage for # tips on creating your own repositories. #[custom] #SigLevel = Optional TrustAll #Server = file:///home/custompkgs ================================================ FILE: vcs.go ================================================ package main import ( "context" "sync" "github.com/Jguer/aur" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/dep" "github.com/Jguer/yay/v12/pkg/runtime" "github.com/Jguer/yay/v12/pkg/sync/srcinfo" "github.com/Jguer/yay/v12/pkg/sync/workdir" ) func infoToInstallInfo(info []aur.Pkg) []map[string]*dep.InstallInfo { installInfo := make([]map[string]*dep.InstallInfo, 1) installInfo[0] = map[string]*dep.InstallInfo{} for i := range info { pkg := &info[i] installInfo[0][pkg.Name] = &dep.InstallInfo{ AURBase: &pkg.PackageBase, Source: dep.AUR, } } return installInfo } // createDevelDB forces yay to create a DB of the existing development packages. func createDevelDB(ctx context.Context, run *runtime.Runtime, dbExecutor db.Executor) error { remoteNames := dbExecutor.InstalledRemotePackageNames() run.QueryBuilder.Execute(ctx, dbExecutor, remoteNames) info, err := run.AURClient.Get(ctx, &aur.Query{ Needles: remoteNames, By: aur.Name, Contains: false, }) if err != nil { return err } preper := workdir.NewPreparerWithoutHooks(dbExecutor, run.CmdBuilder, run.Cfg, run.Logger.Child("workdir"), false) mapInfo := infoToInstallInfo(info) pkgBuildDirsByBase, err := preper.Run(ctx, run, mapInfo) if err != nil { return err } srcinfos, err := srcinfo.ParseSrcinfoFilesByBase(run.Logger.Child("srcinfo"), pkgBuildDirsByBase, false) if err != nil { return err } var wg sync.WaitGroup for i := range srcinfos { for iP := range srcinfos[i].Packages { wg.Add(1) go func(baseIndex string, packageIndex int) { run.VCSStore.Update(ctx, srcinfos[baseIndex].Packages[packageIndex].Pkgname, srcinfos[baseIndex].Source) wg.Done() }(i, iP) } } wg.Wait() run.Logger.OperationInfoln(gotext.Get("GenDB finished. No packages were installed")) return nil } ================================================ FILE: vote.go ================================================ package main import ( "context" "errors" "github.com/Jguer/aur" "github.com/Jguer/votar/pkg/vote" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/text" ) type ErrAURVote struct { inner error pkgName string } func (e *ErrAURVote) Error() string { return gotext.Get("Unable to handle package vote for: %s. err: %s", e.pkgName, e.inner.Error()) } func handlePackageVote(ctx context.Context, targets []string, aurClient aur.QueryClient, logger *text.Logger, voteClient *vote.Client, upvote bool, ) error { infos, err := aurClient.Get(ctx, &aur.Query{ Needles: targets, By: aur.Name, }) if err != nil { return err } if len(infos) == 0 { logger.Println(gotext.Get(" there is nothing to do")) return nil } for i := range infos { var err error if upvote { err = voteClient.Vote(ctx, infos[i].PackageBase) } else { err = voteClient.Unvote(ctx, infos[i].PackageBase) } if err != nil { if errors.Is(err, vote.ErrNoCredentials) { return errors.New( gotext.Get("%s: please set AUR_USERNAME and AUR_PASSWORD environment variables for voting", err.Error())) } return &ErrAURVote{inner: err, pkgName: infos[i].Name} } } return nil }