Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package topgrade for openSUSE:Factory checked in at 2025-12-18 18:31:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/topgrade (Old) and /work/SRC/openSUSE:Factory/.topgrade.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "topgrade" Thu Dec 18 18:31:06 2025 rev:17 rq:1323326 version:16.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/topgrade/topgrade.changes 2025-12-08 11:56:31.375944310 +0100 +++ /work/SRC/openSUSE:Factory/.topgrade.new.1928/topgrade.changes 2025-12-18 18:31:59.230962086 +0100 @@ -1,0 +2,22 @@ +Wed Dec 17 10:44:06 UTC 2025 - [email protected] + +- Update to version 16.7.0: + * chore: release v16.7.0 (#1610) + * fix(brew): fix brew casks and incomplete formula update on linux when using isolated user (#1611) + * feat(containers): add `use_sudo` option (#1618) + * chore(deps): lock file maintenance (#1615) + * ci(lint_pr): zizmor fixes (#1614) + * chore(renovate): move Renovate config (#1613) + * chore(deps): update github/codeql-action action to v4.31.8 (#1607) + * chore(deps): update github artifact actions (major) (#1609) + * fix(vscode): fix parsing of different version format (#1608) + * chore(deps): update rust crate shell-words to v1.1.1 (#1604) + * chore(deps): update itertools to 0.14.0 (#1601) + * feat(sudo): propagate --env to sudo commands (#1588) (#1589) + * chore(pre-commit): autoupdate (#1580) + * chore(deps): lock file maintenance (#1597) + * feat(aqua): run `aqua update --config $AQUA_GLOBAL_CONFIG` instead of `aqua update` (#1596) + * chore(deps): update github/codeql-action action to v4.31.7 (#1591) + * chore(deps): update release-plz/action digest to 487eb7b (#1590) + +------------------------------------------------------------------- Old: ---- topgrade-16.6.1.tar.zst New: ---- topgrade-16.7.0.tar.zst ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ topgrade.spec ++++++ --- /var/tmp/diff_new_pack.8xVJxP/_old 2025-12-18 18:32:14.387598650 +0100 +++ /var/tmp/diff_new_pack.8xVJxP/_new 2025-12-18 18:32:14.391598818 +0100 @@ -17,7 +17,7 @@ Name: topgrade -Version: 16.6.1 +Version: 16.7.0 Release: 0 Summary: Upgrade all the things License: GPL-3.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.8xVJxP/_old 2025-12-18 18:32:14.439600834 +0100 +++ /var/tmp/diff_new_pack.8xVJxP/_new 2025-12-18 18:32:14.443601001 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/topgrade-rs/topgrade.git</param> <param name="versionformat">@PARENT_TAG@</param> <param name="scm">git</param> - <param name="revision">v16.6.1</param> + <param name="revision">v16.7.0</param> <param name="match-tag">*</param> <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param> <param name="versionrewrite-replacement">\1</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.8xVJxP/_old 2025-12-18 18:32:14.471602178 +0100 +++ /var/tmp/diff_new_pack.8xVJxP/_new 2025-12-18 18:32:14.483602682 +0100 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/topgrade-rs/topgrade.git</param> - <param name="changesrevision">d9d279a35f0de3bef60081e2f3883f64e3e64b3b</param></service><service name="tar_scm"> + <param name="changesrevision">db36ce79df4a693d4e7bf666c38220fb8d141d4f</param></service><service name="tar_scm"> <param name="url">https://ghproxy.net/https://github.com/topgrade-rs/topgrade.git</param> <param name="changesrevision">ef0a0d69bbe0cb08d6c4930ee18b734e03c215fb</param></service></servicedata> (No newline at EOF) ++++++ topgrade-16.6.1.tar.zst -> topgrade-16.7.0.tar.zst ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/renovate.json5 new/topgrade-16.7.0/.github/renovate.json5 --- old/topgrade-16.6.1/.github/renovate.json5 1970-01-01 01:00:00.000000000 +0100 +++ new/topgrade-16.7.0/.github/renovate.json5 2025-12-17 11:07:46.000000000 +0100 @@ -0,0 +1,10 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + ":semanticCommits", + ], + lockFileMaintenance: { + enabled: true, + }, +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/workflows/check_security_vulnerability.yml new/topgrade-16.7.0/.github/workflows/check_security_vulnerability.yml --- old/topgrade-16.6.1/.github/workflows/check_security_vulnerability.yml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.github/workflows/check_security_vulnerability.yml 2025-12-17 11:07:46.000000000 +0100 @@ -32,6 +32,6 @@ uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16 - name: Upload DevSkim scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: sarif_file: devskim-results.sarif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/workflows/lint_pr.yml new/topgrade-16.7.0/.github/workflows/lint_pr.yml --- old/topgrade-16.6.1/.github/workflows/lint_pr.yml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.github/workflows/lint_pr.yml 2025-12-17 11:07:46.000000000 +0100 @@ -8,12 +8,18 @@ - reopened - synchronize +permissions: { } + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: main: name: Validate PR title runs-on: ubuntu-latest permissions: - pull-requests: read + pull-requests: read # amannn/action-semantic-pull-request steps: - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/workflows/release-plz.yml new/topgrade-16.7.0/.github/workflows/release-plz.yml --- old/topgrade-16.6.1/.github/workflows/release-plz.yml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.github/workflows/release-plz.yml 2025-12-17 11:07:46.000000000 +0100 @@ -25,7 +25,7 @@ uses: dtolnay/rust-toolchain@stable - name: Run release-plz id: release-plz - uses: release-plz/action@1efcf74dfcd6e500990dad806e286899ae384064 # v0.5 + uses: release-plz/action@487eb7b5c085a664d5c5ca05f4159bd9b591182a # v0.5 with: command: release env: @@ -60,7 +60,7 @@ - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Run release-plz - uses: release-plz/action@1efcf74dfcd6e500990dad806e286899ae384064 # v0.5 + uses: release-plz/action@487eb7b5c085a664d5c5ca05f4159bd9b591182a # v0.5 with: command: release-pr env: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/workflows/release_to_pypi.yml new/topgrade-16.7.0/.github/workflows/release_to_pypi.yml --- old/topgrade-16.6.1/.github/workflows/release_to_pypi.yml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.github/workflows/release_to_pypi.yml 2025-12-17 11:07:46.000000000 +0100 @@ -26,7 +26,7 @@ args: --release --out dist manylinux: auto - name: Upload wheels - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: wheels-linux-${{ matrix.target }} path: dist @@ -47,7 +47,7 @@ target: ${{ matrix.target }} args: --release --out dist - name: Upload wheels - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: wheels-windows-${{ matrix.target }} path: dist @@ -68,7 +68,7 @@ target: ${{ matrix.target }} args: --release --out dist - name: Upload wheels - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: wheels-macos-${{ matrix.target }} path: dist @@ -86,7 +86,7 @@ command: sdist args: --out dist - name: Upload sdist - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: wheels-sdist path: dist @@ -105,7 +105,7 @@ # Used to generate artifact attestation attestations: write steps: - - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - name: Generate artifact attestation uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.github/workflows/scorecards.yml new/topgrade-16.7.0/.github/workflows/scorecards.yml --- old/topgrade-16.6.1/.github/workflows/scorecards.yml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.github/workflows/scorecards.yml 2025-12-17 11:07:46.000000000 +0100 @@ -63,7 +63,7 @@ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: SARIF file path: results.sarif @@ -71,6 +71,6 @@ # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v4.31.6 + uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 with: sarif_file: results.sarif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/.pre-commit-config.yaml new/topgrade-16.7.0/.pre-commit-config.yaml --- old/topgrade-16.6.1/.pre-commit-config.yaml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/.pre-commit-config.yaml 2025-12-17 11:07:46.000000000 +0100 @@ -1,6 +1,6 @@ repos: - repo: https://github.com/gitleaks/gitleaks - rev: v8.29.1 + rev: v8.30.0 hooks: - id: gitleaks @@ -16,7 +16,7 @@ - id: trailing-whitespace - repo: https://github.com/crate-ci/typos - rev: v1.39.2 + rev: v1.40.0 hooks: - id: typos diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/CHANGELOG.md new/topgrade-16.7.0/CHANGELOG.md --- old/topgrade-16.6.1/CHANGELOG.md 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/CHANGELOG.md 2025-12-17 11:07:46.000000000 +0100 @@ -7,6 +7,33 @@ ## [Unreleased] +## [16.7.0](https://github.com/topgrade-rs/topgrade/compare/v16.6.1...v16.7.0) - 2025-12-17 + +### Added + +- *(containers)* add `use_sudo` option ([#1618](https://github.com/topgrade-rs/topgrade/pull/1618)) +- *(sudo)* propagate --env to sudo commands ([#1588](https://github.com/topgrade-rs/topgrade/pull/1588)) ([#1589](https://github.com/topgrade-rs/topgrade/pull/1589)) +- *(aqua)* run `aqua update --config $AQUA_GLOBAL_CONFIG` instead of `aqua update` ([#1596](https://github.com/topgrade-rs/topgrade/pull/1596)) + +### Fixed + +- *(brew)* fix brew casks and incomplete formula update on linux when using isolated user ([#1611](https://github.com/topgrade-rs/topgrade/pull/1611)) +- *(vscode)* fix parsing of different version format ([#1608](https://github.com/topgrade-rs/topgrade/pull/1608)) + +### Other + +- *(deps)* lock file maintenance ([#1615](https://github.com/topgrade-rs/topgrade/pull/1615)) +- *(lint_pr)* zizmor fixes ([#1614](https://github.com/topgrade-rs/topgrade/pull/1614)) +- *(renovate)* move Renovate config ([#1613](https://github.com/topgrade-rs/topgrade/pull/1613)) +- *(deps)* update github/codeql-action action to v4.31.8 ([#1607](https://github.com/topgrade-rs/topgrade/pull/1607)) +- *(deps)* update github artifact actions (major) ([#1609](https://github.com/topgrade-rs/topgrade/pull/1609)) +- *(deps)* update rust crate shell-words to v1.1.1 ([#1604](https://github.com/topgrade-rs/topgrade/pull/1604)) +- *(deps)* update itertools to 0.14.0 ([#1601](https://github.com/topgrade-rs/topgrade/pull/1601)) +- *(pre-commit)* autoupdate ([#1580](https://github.com/topgrade-rs/topgrade/pull/1580)) +- *(deps)* lock file maintenance ([#1597](https://github.com/topgrade-rs/topgrade/pull/1597)) +- *(deps)* update github/codeql-action action to v4.31.7 ([#1591](https://github.com/topgrade-rs/topgrade/pull/1591)) +- *(deps)* update release-plz/action digest to 487eb7b ([#1590](https://github.com/topgrade-rs/topgrade/pull/1590)) + ## [16.6.1](https://github.com/topgrade-rs/topgrade/compare/v16.6.0...v16.6.1) - 2025-12-06 ### Fixed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/Cargo.lock new/topgrade-16.7.0/Cargo.lock --- old/topgrade-16.6.1/Cargo.lock 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/Cargo.lock 2025-12-17 11:07:46.000000000 +0100 @@ -350,9 +350,9 @@ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "shlex", @@ -1239,9 +1239,9 @@ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", @@ -1333,9 +1333,9 @@ [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1347,9 +1347,9 @@ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1475,6 +1475,15 @@ ] [[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1511,9 +1520,9 @@ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" @@ -1540,9 +1549,9 @@ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" @@ -1620,9 +1629,9 @@ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -1914,7 +1923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit 0.23.9", ] [[package]] @@ -2127,9 +2136,9 @@ [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" dependencies = [ "base64", "bytes", @@ -2226,7 +2235,7 @@ "arc-swap", "base62", "globwalk", - "itertools", + "itertools 0.11.0", "lazy_static", "normpath", "once_cell", @@ -2500,9 +2509,9 @@ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shellexpand" @@ -2540,9 +2549,9 @@ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -2941,9 +2950,9 @@ [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" dependencies = [ "indexmap", "toml_datetime 0.7.3", @@ -2974,7 +2983,7 @@ [[package]] name = "topgrade" -version = "16.6.1" +version = "16.7.0" dependencies = [ "base64ct", "chrono", @@ -2992,6 +3001,7 @@ "ignore", "indexmap", "is_elevated", + "itertools 0.14.0", "jetbrains-toolbox-updater", "merge", "nix", @@ -3039,9 +3049,9 @@ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -3236,12 +3246,12 @@ [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/Cargo.toml new/topgrade-16.7.0/Cargo.toml --- old/topgrade-16.6.1/Cargo.toml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/Cargo.toml 2025-12-17 11:07:46.000000000 +0100 @@ -6,7 +6,7 @@ license = "GPL-3.0-or-later" repository = "https://github.com/topgrade-rs/topgrade" rust-version = "1.84.1" -version = "16.6.1" +version = "16.7.0" authors = ["Roey Darwish Dror <[email protected]>", "Thomas Schönauer <[email protected]>"] exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"] edition = "2021" @@ -47,6 +47,7 @@ jetbrains-toolbox-updater = "5.0.0" indexmap = { version = "2.9.0", features = ["serde"] } serde_json = "1.0.145" +itertools = "0.14.0" # Temporary transitive dependency pins ignore = "=0.4.23" globset = "=0.4.16" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/RELEASE_PROCEDURE.md new/topgrade-16.7.0/RELEASE_PROCEDURE.md --- old/topgrade-16.6.1/RELEASE_PROCEDURE.md 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/RELEASE_PROCEDURE.md 2025-12-17 11:07:46.000000000 +0100 @@ -4,7 +4,7 @@ > If there are breaking changes, the major version number should be increased. - 2. If the major versioin number gets bumped, update [SECURITY.md][SECURITY_file_link]. + 2. If the major version number gets bumped, update [SECURITY.md][SECURITY_file_link]. [SECURITY_file_link]: https://github.com/topgrade-rs/topgrade/blob/main/SECURITY.md diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/config.example.toml new/topgrade-16.7.0/config.example.toml --- old/topgrade-16.6.1/config.example.toml 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/config.example.toml 2025-12-17 11:07:46.000000000 +0100 @@ -362,6 +362,10 @@ # (default: false) # system_prune = false +# Use sudo for updating containers. Necessary for non-rootless installs. +# (default: false) +# use_sudo = false + [lensfun] # If disabled, Topgrade invokes `lensfun‑update‑data` without root privilege, # then the update will be only available to you. Otherwise, `sudo` is required, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/renovate.json new/topgrade-16.7.0/renovate.json --- old/topgrade-16.6.1/renovate.json 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/renovate.json 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:best-practices", - ":semanticCommits" - ], - "lockFileMaintenance": { - "enabled": true - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/config.rs new/topgrade-16.7.0/src/config.rs --- old/topgrade-16.6.1/src/config.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/config.rs 2025-12-17 11:07:46.000000000 +0100 @@ -8,8 +8,8 @@ use clap::{Parser, ValueEnum}; use clap_complete::Shell; -use color_eyre::eyre::Context; use color_eyre::eyre::Result; +use color_eyre::eyre::{Context, OptionExt}; use etcetera::base_strategy::BaseStrategy; use indexmap::IndexMap; use merge::Merge; @@ -61,6 +61,7 @@ ignored_containers: Option<Vec<String>>, runtime: Option<ContainerRuntime>, system_prune: Option<bool>, + use_sudo: Option<bool>, } #[derive(Deserialize, Default, Debug, Merge)] @@ -780,8 +781,8 @@ custom_commands: Vec<String>, /// Set environment variables - #[arg(long = "env", value_name = "NAME=VALUE", num_args = 1..)] - env: Vec<String>, + #[arg(long = "env", value_name = "NAME=VALUE", value_parser = env_args_parser, num_args = 1..)] + env: Vec<(String, String)>, /// Output debug logs. Alias for `--log-filter debug`. #[arg(short = 'v', long = "verbose")] @@ -844,6 +845,13 @@ pub no_self_update: bool, } +fn env_args_parser(arg: &str) -> Result<(String, String)> { + let (key, value) = arg + .split_once("=") + .ok_or_eyre("Environment variable must be in the format NAME=VALUE")?; + Ok((key.to_string(), value.to_string())) +} + impl CommandLineArgs { pub fn edit_config(&self) -> bool { self.edit_config @@ -853,7 +861,7 @@ self.show_config_reference } - pub fn env_variables(&self) -> &Vec<String> { + pub fn env_variables(&self) -> &Vec<(String, String)> { &self.env } @@ -985,6 +993,15 @@ .unwrap_or(false) } + /// Whether to use sudo for container operations. + pub fn containers_use_sudo(&self) -> bool { + self.config_file + .containers + .as_ref() + .and_then(|containers| containers.use_sudo) + .unwrap_or(false) + } + /// Tell whether the specified step should run. /// /// If the step appears either in the `--disable` command line argument @@ -1088,6 +1105,11 @@ .unwrap_or(false) } + /// List of user-defined environment variables + pub fn env_variables(&self) -> &Vec<(String, String)> { + self.opt.env_variables() + } + /// List of remote hosts to run Topgrade in pub fn remote_topgrades(&self) -> Option<&Vec<String>> { self.config_file @@ -1955,4 +1977,14 @@ assert_eq!(order, vec!["z", "y", "x"]); } + + #[test] + fn test_env_variable_parser() { + let mut config = config(); + config.opt = CommandLineArgs::parse_from(["topgrade", "--env", "VAR1=foo", "--env", "VAR2=bar"]); + let env_vars = config.env_variables(); + assert_eq!(env_vars.len(), 2); + assert_eq!(env_vars[0], ("VAR1".to_string(), "foo".to_string())); + assert_eq!(env_vars[1], ("VAR2".to_string(), "bar".to_string())); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/main.rs new/topgrade-16.7.0/src/main.rs --- old/topgrade-16.6.1/src/main.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/main.rs 2025-12-17 11:07:46.000000000 +0100 @@ -94,11 +94,8 @@ return Ok(()); } - for env in opt.env_variables() { - let mut parts = env.split('='); - let var = parts.next().unwrap(); - let value = parts.next().unwrap(); - env::set_var(var, value); + for (key, value) in opt.env_variables() { + env::set_var(key, value); } if opt.edit_config() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/steps/containers.rs new/topgrade-16.7.0/src/steps/containers.rs --- old/topgrade-16.6.1/src/steps/containers.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/steps/containers.rs 2025-12-17 11:07:46.000000000 +0100 @@ -2,7 +2,6 @@ use std::io; use std::io::Write; use std::path::Path; -use std::process::Command; use color_eyre::eyre::Context; use color_eyre::eyre::Result; @@ -65,8 +64,8 @@ /// "REGISTRY/[PATH/]CONTAINER_NAME:TAG" /// /// Containers specified in `ignored_containers` will be filtered out. -fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> { - let ignored_containers = ignored_containers.map(|patterns| { +fn list_containers(ctx: &ExecutionContext, crt: &Path) -> Result<Vec<Container>> { + let ignored_containers = ctx.config().containers_ignored_tags().map(|patterns| { patterns .iter() .map(|pattern| WildMatch::new(pattern)) @@ -77,9 +76,23 @@ "Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers", crt.display() ); - let output = Command::new(crt) - .args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"]) - .output_checked_utf8()?; + + let sudo = if ctx.config().containers_use_sudo() { + Some(ctx.require_sudo()?) + } else { + None + }; + + // TODO: This should run even when dry-running. Blocked by #1227. + let output = if let Some(sudo) = sudo { + sudo.execute(ctx, crt)? + .args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"]) + .output_checked_utf8()? + } else { + ctx.execute(crt) + .args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"]) + .output_checked_utf8()? + }; let mut retval = vec![]; for line in output.stdout.lines() { @@ -124,9 +137,16 @@ crt.display(), image_id ); - let inspect_output = Command::new(crt) - .args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"]) - .output_checked_utf8()?; + // TODO: This should run even when dry-running. Blocked by #1227. + let inspect_output = if let Some(sudo) = sudo { + sudo.execute(ctx, crt)? + .args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"]) + .output_checked_utf8()? + } else { + ctx.execute(crt) + .args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"]) + .output_checked_utf8()? + }; let mut platform = inspect_output.stdout; // truncate the tailing new line character platform.truncate(platform.len() - 1); @@ -146,12 +166,21 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> { // Check what runtime is specified in the config let container_runtime = ctx.config().containers_runtime().to_string(); + let use_sudo = ctx.config().containers_use_sudo(); let crt = require(container_runtime)?; debug!("Using container runtime '{}'", crt.display()); print_separator(t!("Containers")); - let output = Command::new(&crt).arg("--help").output_checked_with(|_| Ok(()))?; + let sudo = if use_sudo { Some(ctx.require_sudo()?) } else { None }; + + // TODO: This should run even when dry-running. Blocked by #1227. + let output = if let Some(sudo) = sudo { + sudo.execute(ctx, &crt)?.arg("--help").output_checked_with(|_| Ok(()))? + } else { + ctx.execute(&crt).arg("--help").output_checked_with(|_| Ok(()))? + }; + let status_code = output .status .code() @@ -185,8 +214,7 @@ )); } - let containers = - list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?; + let containers = list_containers(ctx, &crt).context("Failed to list Docker containers")?; debug!("Containers to inspect: {:?}", containers); for container in &containers { @@ -197,7 +225,11 @@ args.push(container.platform.as_str()); } - let mut exec = ctx.execute(&crt); + let mut exec = if let Some(sudo) = sudo { + sudo.execute(ctx, &crt)? + } else { + ctx.execute(&crt) + }; if let Err(e) = exec.args(&args).status_checked() { error!("Pulling container '{}' failed: {}", container, e); @@ -225,14 +257,26 @@ if ctx.config().containers_system_prune() { // Run system prune to clean up unused containers, networks, and build cache - ctx.execute(&crt) - .args(["system", "prune", "--force"]) - .status_checked()? + if let Some(sudo) = sudo { + sudo.execute(ctx, &crt)? + .args(["system", "prune", "--force"]) + .status_checked()? + } else { + ctx.execute(&crt) + .args(["system", "prune", "--force"]) + .status_checked()? + } // Only run `image prune` if we don't run `system prune` } else if ctx.config().cleanup() { // Remove dangling images debug!("Removing dangling images"); - ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked()? + if let Some(sudo) = sudo { + sudo.execute(ctx, &crt)? + .args(["image", "prune", "-f"]) + .status_checked()? + } else { + ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked()? + } } Ok(()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/steps/generic.rs new/topgrade-16.7.0/src/steps/generic.rs --- old/topgrade-16.6.1/src/steps/generic.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/steps/generic.rs 2025-12-17 11:07:46.000000000 +0100 @@ -249,14 +249,17 @@ let aqua = Aqua::get(ctx)?.aqua_cli()?; print_separator("Aqua"); - if ctx.run_type().dry() { - println!("{}", t!("Updating aqua ...")); - println!("{}", t!("Updating aqua installed cli tools ...")); - Ok(()) - } else { - ctx.execute(&aqua).arg("update-aqua").status_checked()?; - ctx.execute(&aqua).arg("update").status_checked() + + ctx.execute(&aqua).arg("update-aqua").status_checked()?; + + // Don't run plain `aqua update` because it uses the current directory by default. + if let Ok(path) = env::var("AQUA_GLOBAL_CONFIG") { + ctx.execute(&aqua) + .args(["update", "--config", &path]) + .status_checked()?; } + + Ok(()) } pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> { @@ -516,41 +519,54 @@ let bin = require(bin_name)?; // VSCode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions - // Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one // + // The output of `code --version` has two possible formats: + // 1. 3 lines: version, git commit, instruction set. We parse only the first one + // This is confirmed on an install from the apt repository + // 2. 1 line: 'bin-name 1.2.3 (commit 123abc)', example: `code-insiders 1.106.0 (commit 48cdf17f0e856e1daca2ad2747814085a2453df0)` + // See https://github.com/topgrade-rs/topgrade/issues/1605, confirmed from Microsoft website // This should apply to VSCodium as well. - let version: Result<Version> = match Command::new(&bin) - .arg("--version") - .output_checked_utf8()? - .stdout - .lines() - .next() - { - Some(item) => { - // Insiders versions have "-insider" suffix which we can simply ignore. - let item = item.trim_end_matches("-insider"); - // Strip leading zeroes because `semver` does not allow them, but VSCodium uses them sometimes. - // This is not the case for VSCode, but just in case, and it can't really cause any issues. - let item = item - .split('.') - .map(|s| if s == "0" { "0" } else { s.trim_start_matches('0') }) - .collect::<Vec<_>>() - .join("."); - Version::parse(&item).map_err(std::convert::Into::into) - } - None => { - return Err(eyre!(output_changed_message!( + + let version_output = Command::new(&bin).arg("--version").output_checked_utf8()?; + + debug!(version_output.stdout); + + let line = version_output.stdout.lines().next().ok_or_else(|| { + eyre!(output_changed_message!( + &format!("{bin_name} --version"), + "No first line" + )) + })?; + + let version_string = if line.starts_with(bin_name) { + // Case 2 + line.split_whitespace().nth(1).ok_or_else(|| { + eyre!(output_changed_message!( &format!("{bin_name} --version"), - "No first line" - ))) - } + format!("No version after '{bin_name}'") + )) + })? + } else { + // Case 1 + // The whole line should be the version + // Insiders versions have "-insider" suffix which we can simply ignore. + line.trim_end_matches("-insider") }; - // Raise any errors in parsing the version - // The benefit of handling VSCodium versions so old that the version format is something - // unexpected is outweighed by the benefit of failing fast on new breaking versions - let version = - version.wrap_err_with(|| output_changed_message!(&format!("{bin_name} --version"), "Invalid version"))?; + // Strip leading zeroes because `semver` does not allow them, but VSCodium uses them sometimes. + // This is not the case for VSCode, but just in case, and it can't really cause any issues. + let version_string = version_string + .split('.') + .map(|s| if s == "0" { "0" } else { s.trim_start_matches('0') }) + .collect::<Vec<_>>() + .join("."); + + let version = Version::parse(&version_string) + // Raise any errors in parsing the version + // The benefit of handling VSCodium versions so old that the version format is something + // unexpected is outweighed by the benefit of failing fast on new breaking versions + .wrap_err_with(|| output_changed_message!(&format!("{bin_name} --version"), "Invalid version"))?; + debug!("Detected {name} version as: {version}"); if version < Version::new(1, 86, 0) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/steps/os/unix.rs new/topgrade-16.7.0/src/steps/os/unix.rs --- old/topgrade-16.6.1/src/steps/os/unix.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/steps/os/unix.rs 2025-12-17 11:07:46.000000000 +0100 @@ -4,8 +4,6 @@ use etcetera::BaseStrategy; use home; use ini::Ini; -#[cfg(target_os = "linux")] -use nix::unistd::Uid; use regex::Regex; use rust_i18n::t; use semver::Version; @@ -35,12 +33,124 @@ use crate::terminal::print_separator; use crate::utils::{require, PathExt}; +#[cfg(target_os = "linux")] +fn brew_linux_sudo_uid() -> Option<u32> { + let linuxbrew_directory = "/home/linuxbrew/.linuxbrew"; + if let Ok(metadata) = fs::metadata(linuxbrew_directory) { + let owner_id = metadata.uid(); + let current_id = nix::unistd::Uid::effective(); + // print debug these two values + debug!("linuxbrew_directory owner_id: {owner_id}, current_id: {current_id}"); + return if owner_id == current_id.as_raw() { + None // no need for sudo if linuxbrew is owned by the current user + } else { + Some(owner_id) // otherwise use sudo to run brew as the owner + }; + } + None +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn brew_get_sudo() -> Option<String> { + #[cfg(target_os = "linux")] + { + let sudo_uid = brew_linux_sudo_uid(); + // if brew is owned by another user, execute "sudo -Hu <uid> brew update" + if let Some(user_id) = sudo_uid { + let uid = nix::unistd::Uid::from_raw(user_id); + let user = nix::unistd::User::from_uid(uid) + .expect("failed to call getpwuid()") + .expect("this user should exist"); + + return Some(user.name); + } + } + None +} + #[cfg(any(target_os = "linux", target_os = "macos"))] const INTEL_BREW: &str = "/usr/local/bin/brew"; #[cfg(any(target_os = "linux", target_os = "macos"))] const ARM_BREW: &str = "/opt/homebrew/bin/brew"; +#[derive(Clone, Debug)] +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub struct Brew { + variant: BrewVariant, + path: PathBuf, + sudo: Option<String>, +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +impl Brew { + fn new(variant: BrewVariant) -> Result<Self> { + Ok(Self { + variant, + path: require(variant.binary_name())?, + sudo: brew_get_sudo(), + }) + } + + // TODO: this is suboptimal, hopefully simplify with v17 refactor + // + `impl Clone for Executor` + /// Execute a brew command. Uses `arch` to run using the correct + /// architecture on macOS if needed, and `sudo -Hu` to run as the + /// correct user on Linux if needed. + fn execute(&self, ctx: &ExecutionContext) -> Result<Executor> { + let mut args = vec![]; + match self.variant { + BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => { + args.extend(["arch", "-x86_64"]); + } + BrewVariant::MacArm if cfg!(target_arch = "x86_64") => { + args.extend(["arch", "-arm64e"]); + } + _ => {} + } + args.push( + self.path + .to_str() + .ok_or_eyre("brew path contains non-unicode characters")?, + ); + let mut it = args.into_iter(); + + // SAFETY: guaranteed to contain at least one element, the path + let program = it.next().unwrap(); + let mut cmd = match &self.sudo { + None => ctx.execute(program), + Some(user) => { + let sudo = ctx.require_sudo()?; + sudo.execute_opts(ctx, program, SudoExecuteOpts::new().set_home().user(user))? + } + }; + cmd.args(it); + Ok(cmd) + } + + fn step_title(&self) -> String { + let both_exists = BrewVariant::both_both_exist(); + let step_title = match self.variant { + BrewVariant::MacArm if both_exists => "Brew (ARM)", + BrewVariant::MacIntel if both_exists => "Brew (Intel)", + _ => "Brew", + }; + + match &self.sudo { + Some(user) => { + format!("{} ({})", step_title, t!("sudo as user '{user}'", user = user)) + } + None => step_title.to_string(), + } + } + + #[cfg(target_os = "macos")] + fn is_macos_custom(&self) -> bool { + let path = self.path.as_os_str(); + !(path == INTEL_BREW || path == ARM_BREW) + } +} + #[derive(Copy, Clone, Debug)] #[allow(dead_code)] #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -68,57 +178,6 @@ fn both_both_exist() -> bool { Path::new(INTEL_BREW).exists() && Path::new(ARM_BREW).exists() } - - pub fn step_title(self) -> &'static str { - let both_exists = Self::both_both_exist(); - match self { - BrewVariant::MacArm if both_exists => "Brew (ARM)", - BrewVariant::MacIntel if both_exists => "Brew (Intel)", - _ => "Brew", - } - } - - /// Execute an "internal" brew command, i.e. one that should always be run - /// even when dry-running. Basically just a wrapper around [`Command::new`] - /// that uses `arch` to run using the correct architecture if needed. - fn execute_internal(self) -> Command { - match self { - BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => { - let mut command = Command::new("arch"); - command.arg("-x86_64").arg(self.binary_name()); - command - } - BrewVariant::MacArm if cfg!(target_arch = "x86_64") => { - let mut command = Command::new("arch"); - command.arg("-arm64e").arg(self.binary_name()); - command - } - _ => Command::new(self.binary_name()), - } - } - - /// Execute a brew command. Uses `arch` to run using the correct - /// architecture on macOS if needed. - fn execute(self, ctx: &ExecutionContext) -> Executor { - match self { - BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => { - let mut command = ctx.execute("arch"); - command.arg("-x86_64").arg(self.binary_name()); - command - } - BrewVariant::MacArm if cfg!(target_arch = "x86_64") => { - let mut command = ctx.execute("arch"); - command.arg("-arm64e").arg(self.binary_name()); - command - } - _ => ctx.execute(self.binary_name()), - } - } - - #[cfg(target_os = "macos")] - fn is_macos_custom(binary_name: PathBuf) -> bool { - !(binary_name.as_os_str() == INTEL_BREW || binary_name.as_os_str() == ARM_BREW) - } } pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> { @@ -290,61 +349,25 @@ .status_checked() } -#[cfg(target_os = "linux")] -pub fn brew_linux_sudo_uid() -> Option<u32> { - let linuxbrew_directory = "/home/linuxbrew/.linuxbrew"; - if let Ok(metadata) = std::fs::metadata(linuxbrew_directory) { - let owner_id = metadata.uid(); - let current_id = Uid::effective(); - // print debug these two values - debug!("linuxbrew_directory owner_id: {}, current_id: {}", owner_id, current_id); - return if owner_id == current_id.as_raw() { - None // no need for sudo if linuxbrew is owned by the current user - } else { - Some(owner_id) // otherwise use sudo to run brew as the owner - }; - } - None -} - #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { - #[allow(unused_variables)] - let binary_name = require(variant.binary_name())?; + let brew = Brew::new(variant)?; #[cfg(target_os = "macos")] { - if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { + if variant.is_path() && !brew.is_macos_custom() { return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into()); } } - #[cfg(target_os = "linux")] - { - let sudo_uid = brew_linux_sudo_uid(); - // if brew is owned by another user, execute "sudo -Hu <uid> brew update" - if let Some(user_id) = sudo_uid { - let uid = nix::unistd::Uid::from_raw(user_id); - let user = nix::unistd::User::from_uid(uid) - .expect("failed to call getpwuid()") - .expect("this user should exist"); - - let sudo_as_user = t!("sudo as user '{user}'", user = user.name); - print_separator(format!("{} ({})", variant.step_title(), sudo_as_user)); - - let sudo = ctx.require_sudo()?; - sudo.execute_opts(ctx, &binary_name, SudoExecuteOpts::new().set_home().user(&user.name))? - .current_dir("/tmp") // brew needs a writable current directory - .arg("update") - .status_checked()?; - return Ok(()); - } - } - print_separator(variant.step_title()); + print_separator(brew.step_title()); - variant.execute(ctx).arg("update").status_checked()?; + brew.execute(ctx)?.arg("update").status_checked()?; + // TODO: this had: + // `.current_dir("/tmp") // brew needs a writable current directory` + // but that only applied when sudo -Hu was used. Is it really needed? - let mut command = variant.execute(ctx); + let mut command = brew.execute(ctx)?; command.args(["upgrade", "--formula"]); if ctx.config().brew_fetch_head() { @@ -354,11 +377,11 @@ command.status_checked()?; if ctx.config().cleanup() { - variant.execute(ctx).arg("cleanup").status_checked()?; + brew.execute(ctx)?.arg("cleanup").status_checked()?; } if ctx.config().brew_autoremove() { - variant.execute(ctx).arg("autoremove").status_checked()?; + brew.execute(ctx)?.arg("autoremove").status_checked()?; } Ok(()) @@ -366,17 +389,18 @@ #[cfg(any(target_os = "linux", target_os = "macos"))] pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { - let binary_name = require(variant.binary_name())?; + let brew = Brew::new(variant)?; #[cfg(target_os = "macos")] - if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { + if variant.is_path() && !brew.is_macos_custom() { return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into()); } #[cfg(target_os = "linux")] { // Homebrew cask support was added in version 4.5.0 - let version_output = Command::new(&binary_name).arg("--version").output_checked_utf8()?; + // TODO: This should run even when dry-running. Blocked by #1227. + let version_output = brew.execute(ctx)?.arg("--version").output_checked_utf8()?; let version_line = version_output .stdout @@ -406,10 +430,13 @@ } } - print_separator(format!("{} - Cask", variant.step_title())); + print_separator(format!("{} - Cask", brew.step_title())); - let cask_upgrade_exists = variant - .execute_internal() + // TODO: this should run even when dry-running, but that + // functionality was removed to reduce complexity when + // implementing the sudo -Hu stuff. Blocked by #1227. + let cask_upgrade_exists = brew + .execute(ctx)? .args(["--repository", "buo/cask-upgrade"]) .output_checked_utf8() .map(|p| Path::new(p.stdout.trim()).exists())?; @@ -434,10 +461,10 @@ } } - variant.execute(ctx).args(&brew_args).status_checked()?; + brew.execute(ctx)?.args(&brew_args).status_checked()?; if ctx.config().cleanup() { - variant.execute(ctx).arg("cleanup").status_checked()?; + brew.execute(ctx)?.arg("cleanup").status_checked()?; } Ok(()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/topgrade-16.6.1/src/sudo.rs new/topgrade-16.7.0/src/sudo.rs --- old/topgrade-16.6.1/src/sudo.rs 2025-12-06 20:45:42.000000000 +0100 +++ new/topgrade-16.7.0/src/sudo.rs 2025-12-17 11:07:46.000000000 +0100 @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::ffi::OsStr; use std::path::Path; use std::path::PathBuf; @@ -8,6 +9,7 @@ use color_eyre::eyre::eyre; use color_eyre::eyre::Context; use color_eyre::eyre::Result; +use itertools::Itertools; use rust_i18n::t; use serde::Deserialize; use strum::Display; @@ -68,7 +70,7 @@ /// Preserve all environment variables. All, /// Preserve only the specified environment variables. - Some(&'a [&'a str]), + Some(HashSet<&'a str>), /// Preserve no environment variables. #[default] None, @@ -111,7 +113,7 @@ /// Preserve only the specified environment variables across the sudo call. #[allow(unused)] pub fn preserve_env_list(mut self, vars: &'a [&'a str]) -> Self { - self.preserve_env = SudoPreserveEnv::Some(vars); + self.preserve_env = SudoPreserveEnv::Some(vars.iter().copied().collect()); self } @@ -407,7 +409,21 @@ cmd.arg("-d"); } - match opts.preserve_env { + let mut preserve_env = opts.preserve_env; + // The `--env` arguments are set globally in `main.rs`, but sudo by default + // does not pass these environment variables through unless explicitly told to. + // So we add them here to the preserve_env list. + let cfg_env_vars = ctx.config().env_variables(); + if !cfg_env_vars.is_empty() { + let cfg_env_var_keys = cfg_env_vars.iter().map(|(key, _value)| key.as_str()); + // merge the user-specified env vars with the opts.preserve_env + match preserve_env { + SudoPreserveEnv::All => {} + SudoPreserveEnv::Some(ref mut env_set) => env_set.extend(cfg_env_var_keys), + SudoPreserveEnv::None => preserve_env = SudoPreserveEnv::Some(cfg_env_var_keys.collect()), + } + } + match preserve_env { SudoPreserveEnv::All => match self.kind { SudoKind::Sudo => { cmd.arg("-E"); @@ -426,7 +442,7 @@ }, SudoPreserveEnv::Some(vars) => match self.kind { SudoKind::Sudo => { - cmd.arg(format!("--preserve-env={}", vars.join(","))); + cmd.arg(format!("--preserve-env={}", vars.iter().join(","))); } SudoKind::Run0 => { for env in vars { @@ -435,7 +451,7 @@ } SudoKind::Please => { cmd.arg("-a"); - cmd.arg(vars.join(",")); + cmd.arg(vars.iter().join(",")); } SudoKind::Doas | SudoKind::WinSudo | SudoKind::Gsudo | SudoKind::Pkexec => { return Err(UnsupportedSudo { ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/topgrade/vendor.tar.zst /work/SRC/openSUSE:Factory/.topgrade.new.1928/vendor.tar.zst differ: char 7, line 1
