Compare commits

..

110 Commits

Author SHA1 Message Date
SteveLauC
1ebcc9beee chore: prepare for v15.0.0 (#843) 2024-07-01 09:45:20 +08:00
SteveLauC
55e1bbf2b9 feat: new step Lensfun's database update (#839)
* feat: new step Lensfun's database update

* refactor: take 1 as a success exit code
2024-06-30 22:41:09 +08:00
SteveLauC
f2dfa1e475 fix: consider TMUX_PLUGIN_MANAGER_PATH when searching tpm binary (#835)
* fix: consider TMUX_PLUGIN_MANAGER_PATH when searching tpm binary

* fix: correct update_plugins path when env var is present
2024-06-30 19:17:30 +08:00
SteveLauC
fcd53e772a chore: collect --dry-run and --yes opts info in feature request template (#838)
chore: collect --dry-run and --yes opts info in feature request template
2024-06-30 14:17:45 +08:00
dependabot[bot]
8b9d7ef8f3 chore(deps): bump curve25519-dalek from 4.1.2 to 4.1.3 (#827)
Bumps [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/dalek-cryptography/curve25519-dalek/releases)
- [Commits](https://github.com/dalek-cryptography/curve25519-dalek/compare/curve25519-4.1.2...curve25519-4.1.3)

---
updated-dependencies:
- dependency-name: curve25519-dalek
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 09:49:20 +08:00
SteveLauC
d8406a8cfe style: allow unused ExecutorChild (#829)
* style: allow unused ExecutorChild

* style: remove duplicate cfg on windows
2024-06-19 09:43:26 +08:00
SteveLauC
4a9ef581e5 chore: bump deps (#823) 2024-06-13 09:21:42 +08:00
Tamás Demeter-Haludka
a52db1f261 Run MasonUpdate as part of the vim updates (#821)
feat(vim): add mason update
2024-06-13 09:00:15 +08:00
Yaroslav Markin
8e16174ce7 fix(RubyGems): support no-sudo updating for rbenv and rvm (#820) 2024-06-06 19:37:06 +08:00
huajingyun
c748bb5d7a deps: bump libc from 0.2.153 to 0.2.155 (#818) 2024-05-28 09:23:10 +08:00
lachsdachs
3cc8f0d818 Add linux mint support (#817)
Update linux.rs
2024-05-26 16:26:11 +08:00
SteveLauC
f96eeeda6b chore: build binary for both macOS aarch64 and amd64 (#816) 2024-05-25 20:26:21 +08:00
SteveLauC
d1d8904376 ci: replace deprecated gh actions with alternatives (#814) 2024-05-25 19:29:17 +08:00
SteveLauC
3b329fe687 chore: update PR template (#815) 2024-05-25 17:35:46 +08:00
SteveLauC
9eb1b4ac9f ci: remove code coverage test & uniform file names (#811) 2024-05-24 09:02:05 +08:00
lachsdachs
c4c0bd7383 add upgrade stuff for bedrock linuxmint strata (#813) 2024-05-24 09:01:46 +08:00
alice
1e9de5832d feat: add support for chimera linux (#808)
since it also uses apk the update/upgrade is identical to alpine/wolfi
2024-05-19 18:48:51 +08:00
dependabot[bot]
f2b17cdd9d chore(deps): bump mio from 0.8.10 to 0.8.11 (#729)
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-19 09:55:30 +08:00
dependabot[bot]
7bfd6c2439 chore(deps): bump h2 from 0.3.24 to 0.3.26 (#766)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-19 09:55:05 +08:00
dependabot[bot]
0e8d5f0266 chore(deps): bump rustls from 0.21.10 to 0.21.12 (#804)
Bumps [rustls](https://github.com/rustls/rustls) from 0.21.10 to 0.21.12.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.10...v/0.21.12)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-19 09:54:27 +08:00
Nils
32add8f046 Dependatbot Updates (#802)
* chore(deps): bump actions/checkout from 3 to 4

Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump github/codeql-action from 2 to 3

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump softprops/action-gh-release from 1 to 2

Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-18 19:48:09 +08:00
SteveLauC
f661f00277 feat: support auto-cpufreq (#800) 2024-05-18 16:34:03 +08:00
Alok Singh
2a1999fe20 Add rye support (#799)
Rye is a new cargo-like package manager for python by @mitsuhiko.
2024-05-13 20:52:13 +08:00
SteveLauC
4d66431aad fix: Fedora Sway Atomic should be recognized as FedoraImmutable (#795)
* fix: Fedora Sway Atomic should be recognized as FedoraImmutable

* style: fmt
2024-05-11 11:20:43 +08:00
SteveLauC
767f0d91f4 refactor: 2 clippy warnings (#789) 2024-05-06 20:37:55 +08:00
edi
a3428e3477 Always display windows update step (#781)
* always display windows update step

* remove extra comma

* i guess format wants the comma
2024-05-06 20:24:57 +08:00
David C
614131b7bf fix(os): detect Fedora IoT Edition as immutable Fedora variant (#774)
Without this change, it is detected as a regular Fedora variant and
updating fails because neither `dnf` nor `yum` is found.
2024-04-17 09:05:54 +08:00
Dan Sully
9b0681f3b8 Add config flag to toggle verbose Git repository output. (#763)
* Add config flag to toggle verbose Git repository output.

If `true`: the default, no change.

If `false`: Only show repositories that have been updated or have an error.

Minor tweak to output (removed colon) so that copy and paste for 'cd' is nicer.
2024-04-14 10:28:03 +08:00
Andre Toerien
ecf8fb7a47 fix: better dotnet tool list header parsing (#772)
fix: better dotnet tool list header parsing
2024-04-14 09:10:08 +08:00
Andrew Barchuk
04bfb45a97 Fix local host detection for remotes with user (#755) 2024-04-08 19:43:32 +08:00
SteveLauC
d90ce30452 feat: support update PlatformIO Core (#759) 2024-04-07 11:03:33 +08:00
Ricardo Torres
ab21600ca6 feat: add support for mise (#757)
Add support for mise-en-place (or mise). Mise is a tool like asdf (already supported). https://mise.jdx.dev/
2024-03-30 18:40:16 +08:00
λP.(P izzy)
728ea26204 FIXES #708: add config directive for pkg_* cleanup on OpenBSD (#753)
FIXEs #708: add config directive for pkg_* cleanup on OpenBSD
2024-03-26 11:07:39 +08:00
SteveLauC
373cd3b3ae fix: don't use Command::new(bin_name) as it won't work on Windows (#750) 2024-03-24 11:48:17 +08:00
SteveLauC
f4e0258b09 style: fix 2 clippy lint unless_vec & unused_io_amount (#751) 2024-03-24 11:24:39 +08:00
SteveLauC
d50360a69a feat: support update ClamAV databases (#747) 2024-03-19 14:10:47 +08:00
SteveLauC
351922c81f feat: put step logs in a span (#746) 2024-03-16 14:17:19 +08:00
Alok Singh
9518f43866 Add support for Lean 4's elan (#742) 2024-03-16 09:35:47 +08:00
SteveLauC
2c1ce3d4e6 refactor: make GitSteps a dedicated step (#737) 2024-03-09 17:57:33 +08:00
SteveLauC
12116c3261 fix: use env BUN_INSTALL to locate package.json (#734) 2024-03-07 14:12:16 +08:00
Gerald Chen
fbc84e8aa1 fix(pipx): adds --include-injected argument to pipx (#726) 2024-03-01 15:06:23 +08:00
Brent Monning
6dab1e4f37 feat: adds xcodes step (#643) 2024-03-01 07:58:24 +08:00
Lucas Parzianello
650a143602 Adds pyenv step (#724) 2024-02-27 09:25:18 +08:00
Nils
9b6027fe78 Update GitHub Actions workflow for Codecov integration (#718)
- Refine the testing matrix to include only stable and nightly versions of Rust
- Add 'fail_ci_if_error' option to Codecov step for stricter CI checks
- Ensure newline at end of file
2024-02-25 11:19:09 +08:00
Nils
0e30e05ce8 Add GitHub Actions Workflow for Build and Test (#717)
* "Add *.profraw files to .gitignore

*.profraw files are generated by LLVM's Clang compiler when using the -fprofile-instr-generate option for Profile Guided Optimization. These files contain raw profiling data and should not be version controlled."

* Remove redundant import of TryFrom trait

The TryFrom trait was being imported explicitly in src\steps\os\windows.rs, even though it's already part of the Rust prelude and automatically imported into every Rust program. This was causing a compiler warning. This commit comments out the redundant import to resolve the warning.

* Add GitHub Actions workflow for Rust build and test

This commit adds a new GitHub Actions workflow for building and testing the Rust project across multiple operating systems (Ubuntu, Windows, macOS) and Rust versions (stable, beta, nightly). It also includes caching for dependencies and build artifacts, and uploads code coverage reports to Codecov.

* Update Codecov action and add token for coverage report upload

This commit updates the version of the Codecov GitHub Action used to upload coverage reports from v4 to v4.0.1. It also adds a token from the repository secrets to authenticate the upload. This ensures secure and authorized communication with the Codecov service.

* "Fix misuse of --jobs flag in cargo test command"

* "Fix grcov command in GitHub Actions workflow

The grcov command was previously prefixed with './', which caused an error because grcov was not found in the current directory. This commit removes the './' prefix to call grcov from the global path, where it is installed."

* Update GitHub Actions workflow for cross-platform compatibility

This commit modifies the 'build-and-test.yml' GitHub Actions workflow to ensure it works correctly across different operating systems (Ubuntu, Windows, MacOS). The RUSTFLAGS environment variable is now set in a cross-platform compatible way. The workflow will run the build and test process on every pull request and push to the main branch, generate a coverage report, and upload it to Codecov.

* Changed workflow trigger event to 'workflow_run' completion of 'Build and test' workflow

* "Updated GitHub Actions workflow to correctly set environment variables for code coverage"

* Renamed build and test workflow

* Update GitHub Actions workflow trigger

Change the trigger of the 'Test with Code Coverage' workflow to run when the 'build-and-test' workflow is completed. This ensures that code coverage is only calculated after successful build and test runs.

* Update workflow_run trigger in code-coverage.yml

* Fix CODECOV_TOKEN in code-coverage.yml workflow

* Update code-coverage workflow to trigger on pull requests and pushes to main branch

* Update .gitignore file to exclude LLVM profiling output

* Add empty line at the end

* Remove unused import in windows.rs

* Update .github/workflows/build-and-test.yml

Co-authored-by: SteveLauC <stevelauc@outlook.com>

* Update .github/workflows/build-and-test.yml

Co-authored-by: SteveLauC <stevelauc@outlook.com>

* Remove code coverage workflow

---------

Co-authored-by: SteveLauC <stevelauc@outlook.com>
2024-02-25 10:35:56 +08:00
Nils
eea952fa78 Create devskim.yml to enable GitHub code scanning for this repository (#700) 2024-02-24 18:53:10 +08:00
SteveLauC
6071a1ee3b chore: git ignore more (#715) 2024-02-24 13:45:53 +08:00
SteveLauC
a801b7b9f4 chore: bump deps (#714) 2024-02-24 13:14:53 +08:00
SteveLauC
c6e3f0ae0a revert: revert 614 to remove the -p option (#713) 2024-02-24 11:26:41 +08:00
SteveLauC
a43b03d3db feat: also detect Helix step with bin name hx (#710) 2024-02-23 07:39:31 +08:00
Md Isfarul Haque
12b0fa57ad fix: fetch and build Helix grammar as a regular user (#698) 2024-02-23 07:26:08 +08:00
Nils
d9e304f0ef Add .vs to .gitignore (#706)
* Added .vs vode to .gitignore

* Adjust .vs to .vs/
2024-02-22 09:47:37 +08:00
dependabot[bot]
842b92cca7 chore(deps): bump actions/download-artifact from 3 to 4 (#704)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 09:39:05 +08:00
dependabot[bot]
485f0ec9c8 chore(deps): bump EnricoMi/publish-unit-test-result-action from 1 to 2 (#705)
Bumps [EnricoMi/publish-unit-test-result-action](https://github.com/enricomi/publish-unit-test-result-action) from 1 to 2.
- [Release notes](https://github.com/enricomi/publish-unit-test-result-action/releases)
- [Commits](https://github.com/enricomi/publish-unit-test-result-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: EnricoMi/publish-unit-test-result-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 09:38:40 +08:00
λP.(P izzy)
5e3b5fc9a7 Fix OpenBSD Step failing to build with E0599 (#707)
* fix openbsd support failing with error E0599

* clean up a little formatting in src/os/openbsd.os
2024-02-21 21:10:34 +08:00
SteveLauC
7c63541cad fix: zinit default install location (#625) 2024-02-17 13:15:53 +08:00
SteveLauC
238e089d74 docs: document brew config entries[skip ci] (#696) 2024-02-17 13:14:39 +08:00
luciodaou
8991bc9f62 feat(brew): adds "greedy-latest" option to Brew (#636) 2024-02-17 11:45:57 +08:00
SteveLauC
7a3f3a8905 feat: support waydroid (#687) 2024-02-16 11:57:53 +08:00
dependabot[bot]
e4085e03eb chore(deps): bump actions/checkout from 2 to 4 (#688)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 16:19:58 +08:00
dependabot[bot]
4b0c366e5f chore(deps): bump actions/upload-artifact from 3 to 4 (#689)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 16:19:18 +08:00
dependabot[bot]
ea97240d09 chore(deps): bump actions/cache from 1 to 4 (#690)
Bumps [actions/cache](https://github.com/actions/cache) from 1 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v1...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 16:18:47 +08:00
dependabot[bot]
12de531abb chore(deps): bump codecov/codecov-action from 1 to 4 (#691)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v1...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 16:18:24 +08:00
dependabot[bot]
c3876ce3bf chore(deps): bump katyo/publish-crates from 1 to 2 (#692)
Bumps [katyo/publish-crates](https://github.com/katyo/publish-crates) from 1 to 2.
- [Release notes](https://github.com/katyo/publish-crates/releases)
- [Commits](https://github.com/katyo/publish-crates/compare/v1...v2)

---
updated-dependencies:
- dependency-name: katyo/publish-crates
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 16:18:00 +08:00
SteveLauC
cbbfc3a114 docs: update install doc with Winget (#693) 2024-02-15 16:17:11 +08:00
Nils
ad2bfc9abd Keeping actions up to date with Dependabot (#685) 2024-02-15 16:04:51 +08:00
Nils
528461412e Publish new releases of topgrade to the Windows Package Manager with WinGet Releaser
Publish new releases of topgrade to the Windows Package Manager with WinGet Releaser (GitHb Action).
2024-02-15 16:04:11 +08:00
SteveLauC
64db679390 ci: add macOS aarch64 check (#680) 2024-02-06 16:28:01 +08:00
Wallunen
77a8b3b7d2 feat: add fetch_head configuration option into brew (#679) 2024-02-06 16:17:27 +08:00
Nils
7007e76ab5 Fix/winget (#670)
* cargo update

* Remove the check for 'winget_enable' set to 'true'. On my Windows 10 and 11 machines, there are no issues with Winget anymore. As far as I remember, it was disabled by default because it was buggy back then.

* remove print_warning

* Revert "cargo update"

This reverts commit 5f4e532bc1.

* Removed the `enable_winget = true` configuration as winget is now enabled by default.

* Removed the #[cfg(windows)] flag.

* Revised as Recommended

* Wrapping at 80
2024-02-03 09:09:47 +08:00
Andy Piper
3c970063a9 fix: correct typos in output (#677)
Corrects a grammatical issue and a typo in two of the step output messages.
2024-01-31 09:07:38 +08:00
SteveLauC
b70830015e docs: fix a wrong preposition[skip ci] (#676) 2024-01-30 11:06:32 +08:00
SteveLauC
b43f2c8b3a ci: run cargo test in ci (#674) 2024-01-29 10:36:30 +08:00
RJ Trujillo
c311da16f3 feat: Add support for Wolfi (#672)
* feat: Add support for Wolfi

This adds support for updating Wolfi via Topgrade

* chore(wolfi): Add os release info and unit test

* chore(wolfi): Don't check ID_LIKE as it is unique
2024-01-29 09:11:53 +08:00
Nils
37608a338c Fix/usoclient (#669)
* cargo update

* Implementing a check for Windows 11 and, if detected, skipping Windows Update via usoclient.exe. It is suggested to install PSWindowsUpdate.

* Revert "cargo update"

This reverts commit 43a4d321cf.

* Revert "Implementing a check for Windows 11 and, if detected, skipping Windows Update via usoclient.exe. It is suggested to install PSWindowsUpdate."

This reverts commit e1ef2e4bc5.

* Removed the usoclient step and added an error message.

* cargo fmt
2024-01-29 09:02:40 +08:00
Nils
b07288e674 Fix/pswindowsupdate (#671)
* cargo update

* An elevated PowerShell is required to run Install-WindowsUpdate on my system.

* Revert "cargo update"

This reverts commit fb58ce761a.
2024-01-29 09:01:38 +08:00
Nils
707698faab Update Cargo.lock (#673)
cargo update
2024-01-29 09:00:08 +08:00
SteveLauC
2e70d132d0 feat: certbot renew (#665) 2024-01-28 13:03:30 +08:00
Brent Monning
30c5b31e21 fix: softwareupdate under dry run (#668) 2024-01-27 14:57:10 +08:00
SteveLauC
77ff6cb714 feat: support wildcard in ignored_containers (#666) 2024-01-27 10:54:55 +08:00
SteveLauC
ea13c51b7d chore: release v14.0.1 (#662) 2024-01-25 15:40:52 +08:00
Cat Core
3ed763b884 Fix system updates for Nobara (#661)
* Fix system updates for Nobara

* fmt

* Add os-release test for Nobara

* Make requested changes

* cargo fmt
2024-01-24 19:29:20 +08:00
samhanic
10e1e170b7 fix vscode extensions update step (#650)
* fix vscode extensions update using the new update-extensions cli

* fix non-linux compilation
2024-01-24 10:32:00 +08:00
Sandro
ffa62afc66 Follow up to the follow up in #616 (#660) 2024-01-24 10:22:36 +08:00
SteveLauC
f794329913 feat: skip breaking changes notification with env var (#659)
* feat: skip breaking changes notification with env var

* ci: apply that env in ci
2024-01-23 14:50:35 +08:00
SteveLauC
f9a35c7661 docs: add doc on how to do a new release (#658) 2024-01-23 11:58:09 +08:00
SteveLauC
ed496f3462 chore: fix file name typo[skip ci] (#657)
chore: fix file name typo
2024-01-23 11:50:02 +08:00
Rui Chen
6accdae232 workflows(homebrew): replace Homebrew/actions/bump-formulae with Homebrew/actions/bump-packages (#656)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2024-01-23 10:29:48 +08:00
SteveLauC
96efcc6c0d chore: release v14.0.0 (#652) 2024-01-22 11:13:33 +08:00
SteveLauC
bf72d7bb5a fix: oh-my-zsh step issue #646 (#647) 2024-01-22 09:18:27 +08:00
dependabot[bot]
dadffb1081 chore(deps): bump h2 from 0.3.22 to 0.3.24 (#645)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.22 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.22...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 12:13:19 +08:00
Ned Wolpert
78dc567226 Added an Audit step for FreeBSD and DragonFly packagees. (#640)
* Added an Audit step for FreeBSD and DragonFly.

Allows for auditing the packages to be disabled since they are breaking steps.
Current behaivor is the default, where if the audit fails topgrade stops. Can
be disabled in the [misc] section independenly from other sections
2024-01-08 09:40:01 +08:00
Mike Wood
362ce4f4f9 fix(os) consider Fedora Kinoite and other immutable distros to be the FedoraImmutable (#638)
* fix(os) consider Fedora Kinoite to be the Fedora Silverblue distribution

* fix(os) support additional Fedora immutable variants

Rename FedoraSilverblue Distribution to FedoraImmutable.  Add test cases for Onyx, Sericea and Silverblue.  Rename upgrade method to match distribution.

Fixes #637
2024-01-08 08:48:48 +08:00
Carrol Cox
ab35cd7b10 feat(pipx-update): add quiet flag for pipx upgrade-all on version 1.4.0+ (#635)
This commit introduces conditional logic to the `run_pipx_update` function that checks the installed version of pipx. If the version is 1.4.0 or higher, the `--quiet` argument is added to the `pipx upgrade-all` command to suppress non-critical output during the upgrade process, adhering to the new feature introduced in pipx 1.4.0 as per the documentation (https://pipx.pypa.io/stable/docs/#pipx-upgrade-all). This change aims to make the upgrade process less verbose and more manageable in automated scripts or CI/CD pipelines where log brevity is beneficial.
2023-12-31 11:38:39 +08:00
SteveLauC
15f4ad7cd1 refactor: update pip if extern managed and global.break-system-packages is true (#634)
refactor: update pip if extern managed and global.break-system-packages is true
2023-12-30 18:23:33 +08:00
Rebecca Turner
cbfb92041f Skip nix upgrade-nix when Nix is installed in a nix profile (#622)
Make `nix upgrade-nix` a separate step

Also check that Nix can be upgraded before running `nix upgrade-nix` to
work around a bug.

See: <https://github.com/NixOS/nix/issues/5473>
2023-12-21 08:55:32 +08:00
SteveLauC
a506c67cac fix: remove deprecated brew option '--ignore-pinned' (#629) 2023-12-19 17:09:32 +08:00
SteveLauC
788e0412f6 feat: inform users of breaking changes on first run (#619) 2023-12-03 09:52:35 +08:00
Nils
18b37ce3e3 Update config.example.toml (#621)
Added WinGet setting:
enable_winget = true
2023-11-26 08:06:17 +08:00
Jakob Fels
a15e6748c7 Add option to ignore containers to pull (#613) 2023-11-24 16:44:52 +08:00
SteveLauC
c6d0539fd2 chore(deps): bump all deps (#618) 2023-11-24 07:50:41 +08:00
LeSnake
3eb3867944 Bun packages fixes (#617)
* fix running with --only

* fix error when no packages installed
2023-11-23 06:36:00 +08:00
DomGlusk
810315b0e2 Make zinit and zi use parallel updates (#614)
* Update zsh.rs to make zinit and zi use parallel

* run cargo fmt

---------

Co-authored-by: Dominic Gluskin <rhinoarmyleader@gmail.com>
2023-11-22 11:18:41 +08:00
SteveLauC
b461fc2536 refactor: cleanup for #615 (#616) 2023-11-22 09:34:21 +08:00
Sam Vente
7e63977ba0 revert git pushing functionalities (#615) 2023-11-22 09:04:19 +08:00
SteveLauC
78dec892cf docs: migration and breaking changes (#606) 2023-11-12 11:43:58 +08:00
pacjo
9ea6628b5c docs: fix typo in config.example.toml (#603)
docs(config): fix typo (dfault -> default)
2023-11-10 10:32:15 +08:00
LeSnake
465df2e9be feat: add Bun packages step (#599) 2023-11-05 10:34:21 +08:00
SteveLauC
61ef926849 chore: update issue template label (#596) 2023-11-01 08:57:57 +08:00
SteveLauC
7fa38c593e fix: omz remote execution if ZSH is not present (#592) 2023-10-29 18:05:20 +08:00
61 changed files with 3393 additions and 1886 deletions

View File

@@ -2,7 +2,7 @@
name: Bug report
about: Topgrade is misbehaving
title: ''
labels: 'bug'
labels: 'C-bug'
assignees: ''
---

View File

@@ -2,16 +2,20 @@
name: Feature request
about: Can you please support...?
title: ''
labels: ''
labels: 'C-feature request'
assignees: ''
---
## I want to suggest a new step
### Which tool is this about? Where is its repository?
### Which operating systems are supported by this tool?
### What should Topgrade do to figure out if the tool needs to be invoked?
### Which exact commands should Topgrade run?
* Which tool is this about? Where is its repository?
* Which operating systems are supported by this tool?
* What should Topgrade do to figure out if the tool needs to be invoked?
* Which exact commands should Topgrade run?
* Does it have a `--dry-run` option? i.e., print what should be done and exit
* Does it need the user to confirm the execution? And does it provide a `--yes`
option to skip this step?
## I want to suggest some general feature
Topgrade should...

View File

@@ -1,14 +1,14 @@
## Standards checklist:
## What does this PR do
## Standards checklist
- [ ] The PR title is descriptive.
- [ ] I have read `CONTRIBUTING.md`
- [ ] The code compiles (`cargo build`)
- [ ] The code passes rustfmt (`cargo fmt`)
- [ ] The code passes clippy (`cargo clippy`)
- [ ] The code passes tests (`cargo test`)
- [ ] *Optional:* I have tested the code myself
## For new steps
- [ ] *Optional:* Topgrade skips this step where needed
- [ ] *Optional:* The `--dry-run` option works with this step
- [ ] *Optional:* The `--yes` option works with this step if it is supported by

10
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"

View File

@@ -1,4 +1,4 @@
name: Test Configuration File Creation
name: Check config file creation if not exists
on:
pull_request:
@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: |
CONFIG_PATH=~/.config/topgrade.toml;
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
cargo build;
./target/debug/topgrade --dry-run --only system;
TOPGRADE_SKIP_BRKC_NOTIFY=true ./target/debug/topgrade --dry-run --only system;
stat $CONFIG_PATH;

View File

@@ -0,0 +1,32 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Check Security Vulnerability
on:
pull_request:
push:
branches:
- main
jobs:
lint:
name: DevSkim
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
- name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: devskim-results.sarif

View File

@@ -8,7 +8,7 @@ jobs:
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2022-08-03

View File

@@ -14,10 +14,10 @@ env:
jobs:
fmt:
name: Rustfmt
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
@@ -42,32 +42,36 @@ jobs:
- target: x86_64-linux-android
target_name: Android
use_cross: true
os: ubuntu-20.04
os: ubuntu-latest
- target: x86_64-unknown-freebsd
target_name: FreeBSD
use_cross: true
os: ubuntu-20.04
os: ubuntu-latest
- target: x86_64-unknown-linux-gnu
target_name: Linux
os: ubuntu-20.04
os: ubuntu-latest
- target: x86_64-apple-darwin
target_name: macOS
os: macos-11
target_name: macOS-x86_64
os: macos-13
- target: aarch64-apple-darwin
target_name: macOS-aarch64
os: macos-latest
- target: x86_64-unknown-netbsd
target_name: NetBSD
use_cross: true
os: ubuntu-20.04
os: ubuntu-latest
- target: x86_64-pc-windows-msvc
target_name: Windows
os: windows-2019
os: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
@@ -84,8 +88,13 @@ jobs:
if: matrix.use_cross == true
run: curl -fL --retry 3 https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VER }}/cross-x86_64-unknown-linux-musl.tar.gz | tar vxz -C /usr/local/bin
- name: Run cargo check
- name: Run cargo/cross check
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
- name: Run cargo clippy
- name: Run cargo/cross clippy
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
- name: Run cargo test
# ONLY run test with cargo
if: matrix.use_cross == false
run: cargo test --locked --target ${{ matrix.target }}

View File

@@ -1,59 +0,0 @@
on:
pull_request:
push:
branches:
- main
env:
CARGO_TERM_COLOR: always
name: Test with Code Coverage
jobs:
test:
name: Test
env:
PROJECT_NAME_UNDERSCORE: topgrade
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort
RUSTDOCFLAGS: -Cpanic=abort
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: Cache dependencies
uses: actions/cache@v2
env:
cache-name: cache-dependencies
with:
path: |
~/.cargo/.crates.toml
~/.cargo/.crates2.json
~/.cargo/bin
~/.cargo/registry/index
~/.cargo/registry/cache
target
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Cargo.lock') }}
- name: Generate test result and coverage report
run: |
cargo install cargo2junit grcov;
cargo test $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > results.xml;
zip -0 ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print`;
grcov ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info;
- name: Upload test results
uses: EnricoMi/publish-unit-test-result-action@v1
with:
check_name: Test Results
github_token: ${{ secrets.GITHUB_TOKEN }}
files: results.xml
- name: Upload to CodeCov
uses: codecov/codecov-action@v1
with:
# required for private repositories:
# token: ${{ secrets.CODECOV_TOKEN }}
files: ./lcov.info
fail_ci_if_error: true

View File

@@ -13,40 +13,31 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ ubuntu-latest, macos-latest, windows-latest ]
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: actions/checkout@v4
- name: setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: actions-rs/cargo@v1.0.1
name: Check format
with:
command: fmt
args: --all -- --check
- uses: actions-rs/cargo@v1.0.1
name: Run clippy
with:
command: clippy
args: --all-targets --locked -- -D warnings
- uses: actions-rs/cargo@v1.0.1
name: Run clippy (All features)
with:
command: clippy
args: --all-targets --locked --all-features -- -D warnings
- uses: actions-rs/cargo@v1.0.1
name: Run tests
with:
command: test
- uses: actions-rs/cargo@v1.0.1
name: Build
with:
command: build
args: --release --all-features
- name: Check format
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --locked -- -D warnings
- name: Run clippy (All features)
run: cargo clippy --all-targets --locked --all-features -- -D warnings
- name: Run tests
run: cargo test
- name: Build in Release profile with all features enabled
run: cargo build --release --all-features
- name: Rename Release (Unix)
run: |
cargo install default-target
@@ -59,6 +50,7 @@ jobs:
ls .
if: ${{ matrix.platform != 'windows-latest' }}
shell: bash
- name: Rename Release (Windows)
run: |
cargo install default-target
@@ -71,7 +63,8 @@ jobs:
ls .
if: ${{ matrix.platform == 'windows-latest' }}
shell: bash
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
files: assets/*

View File

@@ -0,0 +1,68 @@
name: Publish release files for non-cd-native environments
on:
# workflow_run:
# workflows: ["Check SemVer compliance"]
# types:
# - completed
release:
types: [ created ]
jobs:
build:
strategy:
fail-fast: false
matrix:
target: [
"aarch64-unknown-linux-gnu",
"armv7-unknown-linux-gnueabihf",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl",
"x86_64-unknown-freebsd",
]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: install targets
run: rustup target add ${{ matrix.target }}
- name: install cross
uses: taiki-e/install-action@v2
with:
tool: cross@0.2.5
- name: Check format
run: cross fmt --all -- --check
- name: Run clippy
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
- name: Run clippy (All features)
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
- name: Run tests
run: cross test --target ${{matrix.target}}
- name: Build in Release profile with all features enabled
run: cross build --release --all-features --target ${{matrix.target}}
- name: Rename Release
run: |
mkdir assets
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
mv target/${{matrix.target}}/release/topgrade assets
cd assets
tar --format=ustar -czf $FILENAME.tar.gz topgrade
rm topgrade
ls .
- name: Release
uses: softprops/action-gh-release@v2
with:
files: assets/*

View File

@@ -1,70 +0,0 @@
name: Publish release files for non-cd-native environments
on:
# workflow_run:
# workflows: ["Check SemVer compliance"]
# types:
# - completed
release:
types: [ created ]
jobs:
build:
strategy:
fail-fast: false
matrix:
target: [ "aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf", "x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl", "x86_64-unknown-freebsd", ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
override: true
target: ${{ matrix.target }}
components: rustfmt, clippy
- uses: actions-rs/cargo@v1.0.1
name: Check format
with:
use-cross: true
command: fmt
args: --all -- --check
- uses: actions-rs/cargo@v1.0.1
name: Run clippy
with:
command: clippy
use-cross: true
args: --all-targets --locked --target ${{matrix.target}} -- -D warnings
- uses: actions-rs/cargo@v1.0.1
name: Run clippy (All features)
with:
command: clippy
use-cross: true
args: --locked --all-features --target ${{matrix.target}} -- -D warnings
- uses: actions-rs/cargo@v1.0.1
name: Run tests
with:
command: test
use-cross: true
args: --target ${{matrix.target}}
- uses: actions-rs/cargo@v1.0.1
name: Build
with:
command: build
use-cross: true
args: --release --all-features --target ${{matrix.target}}
- name: Rename Release
run: |
mkdir assets
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
mv target/${{matrix.target}}/release/topgrade assets
cd assets
tar --format=ustar -czf $FILENAME.tar.gz topgrade
rm topgrade
ls .
- name: Release
uses: softprops/action-gh-release@v1
with:
files: assets/*

View File

@@ -12,7 +12,7 @@ jobs:
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
@@ -21,7 +21,7 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: katyo/publish-crates@v1
- uses: katyo/publish-crates@v2
with:
dry-run: true
check-repo: ${{ github.event_name == 'push' }}

View File

@@ -19,7 +19,7 @@ jobs:
uses: Homebrew/actions/setup-homebrew@master
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
@@ -29,7 +29,8 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: brew install-bundler-gems
- name: Bump formulae
uses: Homebrew/actions/bump-formulae@master
uses: Homebrew/actions/bump-packages@master
continue-on-error: true
with:
# Custom GitHub access token with only the 'public_repo' scope enabled
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}

View File

@@ -14,7 +14,7 @@ jobs:
matrix:
target: [x86_64, x86, aarch64]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
@@ -23,7 +23,7 @@ jobs:
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
@@ -34,7 +34,7 @@ jobs:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
@@ -42,7 +42,7 @@ jobs:
args: --release --out dist
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
@@ -53,7 +53,7 @@ jobs:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
@@ -61,7 +61,7 @@ jobs:
args: --release --out dist
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
@@ -69,14 +69,14 @@ jobs:
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
@@ -87,7 +87,7 @@ jobs:
if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: wheels
- name: Publish to PyPI

13
.github/workflows/release_to_winget.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Publish to WinGet
on:
release:
types: [released]
jobs:
publish:
runs-on: windows-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: topgrade-rs.topgrade
max-versions-to-keep: 5 # keep only latest 5 versions
token: ${{ secrets.WINGET_TOKEN }}

18
.gitignore vendored
View File

@@ -1,4 +1,20 @@
# JetBrains IDEs
.idea/
/target
# Visual Studio
.vs/
# Visual Studio Code
.vscode/
# Generic build outputs
/build
# Specific for some languages like Rust
/target
# LLVM profiling output
*.profraw
# Backup files for any .rs files in the project
**/*.rs.bk

38
.vscode/launch.json vendored
View File

@@ -1,38 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Topgrade",
"console": "integratedTerminal",
"cargo": {
"args": [
"build",
"--bin=topgrade-rs",
"--package=topgrade-rs"
],
"filter": {
"name": "topgrade-rs",
"kind": "bin"
}
},
"args": [
"--only",
"${input:step}",
"-v"
],
"cwd": "${workspaceFolder}"
},
],
"inputs": [
{
"type": "promptString",
"id": "step",
"description": "step name",
}
]
}

14
.vscode/tasks.json vendored
View File

@@ -1,14 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "clippy",
"problemMatcher": [
"$rustc"
],
"group": "test",
"label": "rust: cargo clippy"
}
]
}

View File

@@ -1,50 +0,0 @@
{
// Place your topgrade workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Skip Step": {
"scope": "rust",
"prefix": "skipstep",
"body": [
"return Err(SkipStep(format!(\"$1\")).into());"
]
},
"Step": {
"scope": "rust",
"prefix": "step",
"body": [
"pub fn $1(ctx: &ExecutionContext) -> Result<()> {",
" $0",
" Ok(())",
"}"
]
},
"Require Binary": {
"scope": "rust",
"prefix": "req",
"description": "Require a binary to be installed",
"body": [
"let ${1:binary} = require(\"${1:binary}\")?;"
]
},
"macos": {
"scope": "rust",
"prefix": "macos",
"body": [
"#[cfg(target_os = \"macos\")]"
]
}
}

9
BREAKINGCHANGES.md Normal file
View File

@@ -0,0 +1,9 @@
# Git: Pull Repos
1. The output of "Pulling <repository path>" has been moved behind the
--verbose flag / [misc] configuration block.
# Configuration
1. The `enable_winget` configuration entry in the `windows` section has been
removed because it will not cause any issues and will be enabled by default.

0
BREAKINGCHANGES_dev.md Normal file
View File

View File

@@ -1,6 +1,6 @@
## Contributing to `topgrade`
Thank you for your interest in contributing to `topgrade`!
Thank you for your interest in contributing to `topgrade`!
We welcome and encourage contributions of all kinds, such as:
1. Issue reports or feature requests
@@ -13,9 +13,9 @@ for commit messages.
## Adding a new `step`
In `topgrade`'s term, package manager is called `step`.
To add a new `step` to `topgrade`:
To add a new `step` to `topgrade`:
1. Add a new variant to
1. Add a new variant to
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
```rust
@@ -55,9 +55,9 @@ To add a new `step` to `topgrade`:
```
Such a update function would be conventionally named `run_xxx()`, where `xxx`
is the name of the new step, and it should take a argument of type
is the name of the new step, and it should take a argument of type
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
needed (You can find some examples where extra arguments are needed
needed (You can find some examples where extra arguments are needed
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
Update function would usually do 3 things:
@@ -90,8 +90,8 @@ To add a new `step` to `topgrade`:
## Modification to the configuration entries
If your PR has the configuration options
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/config.rs))
If your PR has the configuration options
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/config.rs))
modified:
1. Adding new options
@@ -101,6 +101,21 @@ Be sure to apply your changes to
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
and have some basic documentations guiding user how to use these options.
## Breaking changes
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
it should be written in Markdown and wrapped at 80, for example:
```md
1. The configuration location has been updated to x.
2. The step x has been removed.
3. ...
```
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
## Before you submit your PR
Make sure your patch passes the following tests on your host:
@@ -133,5 +148,5 @@ Don't worry about other platforms, we have most of them covered in our CI.
```
If `xxx` respects locale, then the above code should work on English system,
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
translated to `"帮助"`, and the above code won't work.

2242
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@ categories = ["os"]
keywords = ["upgrade", "update"]
license = "GPL-3.0"
repository = "https://github.com/topgrade-rs/topgrade"
version = "13.0.0"
version = "15.0.0"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif"]
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
edition = "2021"
readme = "README.md"
@@ -22,26 +22,26 @@ path = "src/main.rs"
[dependencies]
home = "~0.5"
etcetera = "~0.8"
once_cell = "~1.17"
once_cell = "~1.19"
serde = { version = "~1.0", features = ["derive"] }
toml = "0.5"
which_crate = { version = "~4.1", package = "which" }
shellexpand = "~2.1"
clap = { version = "~3.1", features = ["cargo", "derive"] }
clap_complete = "~3.1"
clap_mangen = "~0.1"
walkdir = "~2.3"
toml = "0.8"
which_crate = { version = "~6.0", package = "which" }
shellexpand = "~3.1"
clap = { version = "~4.5", features = ["cargo", "derive"] }
clap_complete = "~4.5"
clap_mangen = "~0.2"
walkdir = "~2.5"
console = "~0.15"
lazy_static = "~1.4"
chrono = "~0.4"
glob = "~0.3"
strum = { version = "~0.24", features = ["derive"] }
strum = { version = "~0.26", features = ["derive"] }
thiserror = "~1.0"
tempfile = "~3.6"
tempfile = "~3.10"
cfg-if = "~1.0"
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
tokio = { version = "~1.38", features = ["process", "rt-multi-thread"] }
futures = "~0.3"
regex = "~1.7"
regex = "~1.10"
semver = "~1.0"
shell-words = "~1.1"
color-eyre = "~0.6"
@@ -49,10 +49,11 @@ tracing = { version = "~0.1", features = ["attributes", "log"] }
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
merge = "~0.1"
regex-split = "~0.1"
notify-rust = "~4.8"
notify-rust = "~4.11"
wildmatch = "2.3.0"
[package.metadata.generate-rpm]
assets = [{source = "target/release/topgrade", dest="/usr/bin/topgrade"}]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
[package.metadata.generate-rpm.requires]
git = "*"
@@ -61,13 +62,12 @@ git = "*"
depends = "$auto,git"
[target.'cfg(unix)'.dependencies]
libc = "~0.2"
nix = "~0.24"
rust-ini = "~0.19"
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
rust-ini = "~0.21"
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
[target.'cfg(windows)'.dependencies]
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
winapi = "~0.3"
parselnk = "~0.1"

View File

@@ -29,9 +29,12 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
- Windows: [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/topgrade.json)
- Windows: [Scoop][scoop] or [Winget][winget]
- PyPi: [pip](https://pypi.org/project/topgrade/)
[scoop]: https://scoop.sh/#/apps?q=topgrade
[winget]: https://winstall.app/apps/topgrade-rs.topgrade
Other systems users can either use `cargo install` or the compiled binaries from the release page.
The compiled binaries contain a self-upgrading feature.
@@ -42,15 +45,18 @@ The compiled binaries contain a self-upgrading feature.
Just run `topgrade`.
Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) for more information.
> **Warning**
> Work in Progress
## Configuration
See `config.example.toml` for an example configuration file.
## Migration and Breaking Changes
Whenever there is a **breaking change**, the major version number will be bumped,
and we will document these changes in the release note, please take a look at
it when updated to a major release.
> Got a question? Feel free to open an issue or discussion!
### Configuration Path
#### `CONFIG_DIR` on each platform

65
RELEASE_PROCEDURE.md Normal file
View File

@@ -0,0 +1,65 @@
> This document lists the steps that lead to a successful release of Topgrade.
1. Open a PR that:
> Here is an [Example PR](https://github.com/topgrade-rs/topgrade/pull/652)
> that you can refer to.
1. bumps the version number.
> If there are breaking changes, the major version number should be increased.
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
```sh'
$ cd topgrade
$ cp BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
$ touch BREAKINGCHANGES_dev.md
```
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
[breaking_changes]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES.md
2. Check and merge that PR.
3. Go to the [release](https://github.com/topgrade-rs/topgrade/releases) page
and click the [Draft a new release button](https://github.com/topgrade-rs/topgrade/releases/new)
4. Write the release notes
We usually use GitHub's [Automatically generated release notes][auto_gen_release_notes]
functionality to generate release notes, but you write your own one instead.
[auto_gen_release_notes]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
5. Attaching binaries
You don't need to do this as our CI will automatically do it for you,
binaries for Linux, macOS and Windows will be created and attached.
And the CI will publish the new binary to:
1. AUR
2. PyPi
3. Homebrew (seems that this is not working correctly)
4. Winget
6. Manually release it to Crates.io
> Yeah, this is unfortunate, our CI won't do this for us. We should probably add one.
1. `cd` to the Topgrade directory, make sure that it is the latest version
(i.e., including the PR that bumps the version number).
2. Set up your token with `cargo login`.
3. Dry-run the publish `cargo publish --dry-run`.
4. If step 3 works, then do the final release `cargo publish`.
> You can also take a look at the official tutorial [Publishing on crates.io][doc]
>
> [doc]: https://doc.rust-lang.org/cargo/reference/publishing.html

View File

@@ -32,7 +32,7 @@
# Arguments to pass tmux when pulling Repositories
# tmux_arguments = "-S /var/tmux.sock"
# Do not set the terminal title (dfault: true)
# Do not set the terminal title (default: true)
# set_title = true
# Display the time in step titles (default: true)
@@ -103,9 +103,25 @@
[brew]
# For the BrewCask step
# If `Repo Cask Upgrade` exists, then use the `-a` option.
# Otherwise, use the `--greedy` option.
# greedy_cask = true
# For the BrewCask step
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_latest` option.
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
# both of them, they won't clash with each other.
# greedy_latest = true
# For the BrewFormula step
# Execute `brew autoremove` after the step.
# autoremove = true
# For the BrewFormula step
# Upgrade formulae built from the HEAD branch; `brew upgrade --fetch-HEAD`
# fetch_head = true
[linux]
# Arch Package Manager to use.
@@ -153,33 +169,20 @@
[git]
# How many repos to pull at max in parallel
# max_concurrency = 5
# Git repositories that you want to pull and push
# Additional git repositories to pull
# repos = [
# "~/src/*/",
# "~/.config/something"
# ]
# Repositories that you only want to pull
# pull_only_repos = [
# "~/.config/something_else"
# ]
# Repositories that you only want to push
# push_only_repos = [
# "~/src/*/",
# "~/.config/something_third"
# ]
# Don't pull the predefined git repos
# pull_predefined = false
# Arguments to pass Git when pulling repositories
# pull_arguments = "--rebase --autostash"
# Arguments to pass Git when pushing repositories
# push_arguments = "--all"
# Arguments to pass Git when pulling Repositories
# arguments = "--rebase --autostash"
[windows]
@@ -237,4 +240,14 @@
[distrobox]
# use_root = false
# containers = ["archlinux-latest"]
# containers = ["archlinux-latest"]
[containers]
# Specify the containers to ignore while updating (Wildcard supported)
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"]
[lensfun]
# If disabled, Topgrade invokes `lensfunupdatedata` without root priviledge,
# then the update will be only available to you. Otherwise, `sudo` is required,
# and the update will be installed system-wide, i.e., available to all users.
# (default: false)
# use_sudo = false

167
src/breaking_changes.rs Normal file
View File

@@ -0,0 +1,167 @@
//! Inform the users of the breaking changes introduced in this major release.
//!
//! Print the breaking changes and possibly a migration guide when:
//! 1. The Topgrade being executed is a new major release
//! 2. This is the first launch of that major release
use crate::terminal::print_separator;
#[cfg(windows)]
use crate::WINDOWS_DIRS;
#[cfg(unix)]
use crate::XDG_DIRS;
use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use std::{
env::var,
fs::{read_to_string, OpenOptions},
io::Write,
path::PathBuf,
str::FromStr,
};
/// Version string x.y.z
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
/// Version info
#[derive(Debug)]
pub(crate) struct Version {
_major: u64,
minor: u64,
patch: u64,
}
impl FromStr for Version {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
const NOT_SEMVER: &str = "Topgrade version is not semantic";
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
let mut iter = s.split('.').take(3);
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
// They cannot be all 0s
assert!(
!(major == 0 && minor == 0 && patch == 0),
"Version numbers can not be all 0s"
);
Ok(Self {
_major: major,
minor,
patch,
})
}
}
impl Version {
/// True if this version is a new major release.
pub(crate) fn is_new_major_release(&self) -> bool {
// We have already checked that they cannot all be zeros, so `self.major`
// is guaranteed to be non-zero.
self.minor == 0 && self.patch == 0
}
}
/// Topgrade's breaking changes
///
/// We store them in the compiled binary.
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
/// Return platform's data directory.
fn data_dir() -> PathBuf {
#[cfg(unix)]
return XDG_DIRS.data_dir();
#[cfg(windows)]
return WINDOWS_DIRS.data_dir();
}
/// Return Topgrade's keep file path.
///
/// keep file is a file under the data directory containing a major version
/// number, it will be created on first run and is used to check if an execution
/// of Topgrade is the first run of a major release, for more details, see
/// `first_run_of_major_release()`.
fn keep_file_path() -> PathBuf {
let keep_file = "topgrade_keep";
data_dir().join(keep_file)
}
/// If environment variable `TOPGRADE_SKIP_BRKC_NOTIFY` is set to `true`, then
/// we won't notify the user of the breaking changes.
pub(crate) fn should_skip() -> bool {
if let Ok(var) = var("TOPGRADE_SKIP_BRKC_NOTIFY") {
return var.as_str() == "true";
}
false
}
/// True if this is the first execution of a major release.
pub(crate) fn first_run_of_major_release() -> Result<bool> {
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
let keep_file = keep_file_path();
// disable this lint here as the current code has better readability
#[allow(clippy::collapsible_if)]
if version.is_new_major_release() {
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
return Ok(true);
}
}
Ok(false)
}
/// Print breaking changes to the user.
pub(crate) fn print_breaking_changes() {
let header = format!("Topgrade {VERSION_STR} Breaking Changes");
print_separator(header);
let contents = if BREAKINGCHANGES.is_empty() {
"No Breaking changes"
} else {
BREAKINGCHANGES
};
println!("{contents}\n");
}
/// This function will be ONLY executed when the user has confirmed the breaking
/// changes, once confirmed, we write the keep file, which means the first run
/// of this major release is finished.
pub(crate) fn write_keep_file() -> Result<()> {
std::fs::create_dir_all(data_dir())?;
let keep_file = keep_file_path();
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(keep_file)?;
let _ = file.write(VERSION_STR.as_bytes())?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_new_major_release_works() {
let first_major_release: Version = "1.0.0".parse().unwrap();
let under_dev: Version = "0.1.0".parse().unwrap();
assert!(first_major_release.is_new_major_release());
assert!(!under_dev.is_new_major_release());
}
#[test]
#[should_panic(expected = "Version numbers can not be all 0s")]
fn invalid_version() {
let all_0 = "0.0.0";
all_0.parse::<Version>().unwrap();
}
}

View File

@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, fs};
use clap::{ArgEnum, Parser};
use clap::{Parser, ValueEnum};
use clap_complete::Shell;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
@@ -16,10 +16,10 @@ use merge::Merge;
use regex::Regex;
use regex_split::RegexSplit;
use serde::Deserialize;
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames};
use which_crate::which;
use super::utils::{editor, hostname};
use super::utils::editor;
use crate::command::CommandExt;
use crate::sudo::SudoKind;
use crate::utils::string_prepend_str;
@@ -44,7 +44,7 @@ macro_rules! str_value {
pub type Commands = BTreeMap<String, String>;
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
#[derive(ValueEnum, EnumString, VariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
#[clap(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
@@ -53,15 +53,20 @@ pub enum Step {
AppMan,
Asdf,
Atom,
Audit,
AutoCpufreq,
Bin,
Bob,
BrewCask,
BrewFormula,
Bun,
BunPackages,
Cargo,
Certbot,
Chezmoi,
Chocolatey,
Choosenim,
ClamAvDb,
Composer,
Conda,
ConfigUpdate,
@@ -72,6 +77,7 @@ pub enum Step {
Distrobox,
DkpPacman,
Dotnet,
Elan,
Emacs,
Firmware,
Flatpak,
@@ -95,12 +101,14 @@ pub enum Step {
Helix,
Krew,
Lure,
Lensfun,
Macports,
Mamba,
Miktex,
Mas,
Maza,
Micro,
Mise,
Myrepos,
Nix,
Node,
@@ -115,9 +123,11 @@ pub enum Step {
Pipx,
Pkg,
Pkgin,
PlatformioCore,
Pnpm,
Powershell,
Protonup,
Pyenv,
Raco,
Rcm,
Remotes,
@@ -125,6 +135,7 @@ pub enum Step {
Rtcl,
RubyGems,
Rustup,
Rye,
Scoop,
Sdkman,
SelfUpdate,
@@ -144,9 +155,11 @@ pub enum Step {
Vcpkg,
Vim,
Vscode,
Waydroid,
Winget,
Wsl,
WslUpdate,
Xcodes,
Yadm,
Yarn,
}
@@ -158,23 +171,23 @@ pub struct Include {
paths: Option<Vec<String>>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Containers {
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
ignored_containers: Option<Vec<String>>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Git {
max_concurrency: Option<usize>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
pull_arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
push_arguments: Option<String>,
arguments: Option<String>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
repos: Option<Vec<String>>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
pull_only_repos: Option<Vec<String>>,
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
push_only_repos: Option<Vec<String>>,
pull_predefined: Option<bool>,
}
@@ -195,7 +208,6 @@ pub struct Windows {
accept_all_updates: Option<bool>,
self_rename: Option<bool>,
open_remotes_in_new_terminal: Option<bool>,
enable_winget: Option<bool>,
wsl_update_pre_release: Option<bool>,
wsl_update_use_web_download: Option<bool>,
}
@@ -251,7 +263,9 @@ pub struct Flatpak {
#[serde(deny_unknown_fields)]
pub struct Brew {
greedy_cask: Option<bool>,
greedy_latest: Option<bool>,
autoremove: Option<bool>,
fetch_head: Option<bool>,
}
#[derive(Debug, Deserialize, Clone, Copy)]
@@ -383,6 +397,12 @@ pub struct Misc {
log_filters: Option<Vec<String>>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
pub struct Lensfun {
use_sudo: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
/// Configuration file
@@ -417,6 +437,9 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
git: Option<Git>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
containers: Option<Containers>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
windows: Option<Windows>,
@@ -440,6 +463,9 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
distrobox: Option<Distrobox>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
lensfun: Option<Lensfun>,
}
fn config_directory() -> PathBuf {
@@ -465,7 +491,7 @@ impl ConfigFile {
let config_directory = config_directory();
let possible_config_paths = vec![
let possible_config_paths = [
config_directory.join("topgrade.toml"),
config_directory.join("topgrade/topgrade.toml"),
];
@@ -474,7 +500,7 @@ impl ConfigFile {
for path in possible_config_paths.iter() {
if path.exists() {
debug!("Configuration at {}", path.display());
res.0 = path.clone();
res.0.clone_from(path);
break;
}
}
@@ -483,7 +509,7 @@ impl ConfigFile {
// If no config file exists, create a default one in the config directory
if !res.0.exists() && res.1.is_empty() {
res.0 = possible_config_paths[0].clone();
res.0.clone_from(&possible_config_paths[0]);
debug!("No configuration exists");
write(&res.0, EXAMPLE_CONFIG).map_err(|e| {
debug!(
@@ -616,22 +642,7 @@ impl ConfigFile {
}
}
if let Some(paths) = result.git.as_mut().and_then(|git| git.pull_only_repos.as_mut()) {
for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded);
*path = expanded;
}
}
if let Some(paths) = result.git.as_mut().and_then(|git| git.push_only_repos.as_mut()) {
for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded);
*path = expanded;
}
}
debug!("Loaded configuration: {:?}", result);
Ok(result)
}
@@ -692,19 +703,19 @@ pub struct CommandLineArgs {
no_retry: bool,
/// Do not perform upgrades for the given steps
#[clap(long = "disable", value_name = "STEP", arg_enum, multiple_values = true)]
#[clap(long = "disable", value_name = "STEP", value_enum, num_args = 1..)]
disable: Vec<Step>,
/// Perform only the specified steps (experimental)
#[clap(long = "only", value_name = "STEP", arg_enum, multiple_values = true)]
#[clap(long = "only", value_name = "STEP", value_enum, num_args = 1..)]
only: Vec<Step>,
/// Run only specific custom commands
#[clap(long = "custom-commands", value_name = "NAME", multiple_values = true)]
#[clap(long = "custom-commands", value_name = "NAME", num_args = 1..)]
custom_commands: Vec<String>,
/// Set environment variables
#[clap(long = "env", value_name = "NAME=VALUE", multiple_values = true)]
#[clap(long = "env", value_name = "NAME=VALUE", num_args = 1..)]
env: Vec<String>,
/// Output debug logs. Alias for `--log-filter debug`.
@@ -724,9 +735,8 @@ pub struct CommandLineArgs {
short = 'y',
long = "yes",
value_name = "STEP",
arg_enum,
multiple_values = true,
min_values = 0
value_enum,
num_args = 0..,
)]
yes: Option<Vec<Step>>,
@@ -753,7 +763,7 @@ pub struct CommandLineArgs {
pub log_filter: String,
/// Print completion script for the given shell and exit
#[clap(long, arg_enum, hide = true)]
#[clap(long, value_enum, hide = true)]
pub gen_completion: Option<Shell>,
/// Print roff manpage and exit
@@ -859,23 +869,17 @@ impl Config {
&self.config_file.commands
}
/// The list of git repositories to push and pull.
/// The list of additional git repositories to pull.
pub fn git_repos(&self) -> Option<&Vec<String>> {
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
}
/// The list of additional git repositories to pull.
pub fn git_pull_only_repos(&self) -> Option<&Vec<String>> {
/// The list of docker/podman containers to ignore.
pub fn containers_ignored_tags(&self) -> Option<&Vec<String>> {
self.config_file
.git
.containers
.as_ref()
.and_then(|git| git.pull_only_repos.as_ref())
}
/// The list of git repositories to push.
pub fn git_push_only_repos(&self) -> Option<&Vec<String>> {
self.config_file
.git
.as_ref()
.and_then(|git| git.push_only_repos.as_ref())
.and_then(|containers| containers.ignored_containers.as_ref())
}
/// Tell whether the specified step should run.
@@ -986,19 +990,9 @@ impl Config {
.and_then(|misc| misc.ssh_arguments.as_ref())
}
/// Extra Git arguments for when pushing
pub fn push_git_arguments(&self) -> Option<&String> {
self.config_file
.git
.as_ref()
.and_then(|git| git.push_arguments.as_ref())
}
/// Extra Git arguments for when pulling
pub fn pull_git_arguments(&self) -> Option<&String> {
self.config_file
.git
.as_ref()
.and_then(|git| git.pull_arguments.as_ref())
/// Extra Git arguments
pub fn git_arguments(&self) -> Option<&String> {
self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref())
}
/// Extra Tmux arguments
@@ -1114,6 +1108,15 @@ impl Config {
.unwrap_or(false)
}
/// Whether Brew cask should be greedy_latest
pub fn brew_greedy_latest(&self) -> bool {
self.config_file
.brew
.as_ref()
.and_then(|c| c.greedy_latest)
.unwrap_or(false)
}
/// Whether Brew should autoremove
pub fn brew_autoremove(&self) -> bool {
self.config_file
@@ -1123,6 +1126,15 @@ impl Config {
.unwrap_or(false)
}
/// Whether Brew should upgrade formulae built from the HEAD branch
pub fn brew_fetch_head(&self) -> bool {
self.config_file
.brew
.as_ref()
.and_then(|c| c.fetch_head)
.unwrap_or(false)
}
/// Whether Composer should update itself
pub fn composer_self_update(&self) -> bool {
self.config_file
@@ -1458,30 +1470,22 @@ impl Config {
#[cfg(target_os = "linux")]
str_value!(linux, emerge_update_flags);
pub fn should_execute_remote(&self, remote: &str) -> bool {
if let Ok(hostname) = hostname() {
if remote == hostname {
pub fn should_execute_remote(&self, hostname: Result<String>, remote: &str) -> bool {
let remote_host = remote.split_once('@').map_or(remote, |(_, host)| host);
if let Ok(hostname) = hostname {
if remote_host == hostname {
return false;
}
}
if let Some(limit) = self.opt.remote_host_limit.as_ref() {
return limit.is_match(remote);
if let Some(limit) = &self.opt.remote_host_limit.as_ref() {
return limit.is_match(remote_host);
}
true
}
#[cfg(windows)]
pub fn enable_winget(&self) -> bool {
return self
.config_file
.windows
.as_ref()
.and_then(|w| w.enable_winget)
.unwrap_or(false);
}
pub fn enable_pipupgrade(&self) -> bool {
return self
.config_file
@@ -1529,11 +1533,21 @@ impl Config {
self.opt.custom_commands.iter().any(|s| s == name)
}
pub fn lensfun_use_sudo(&self) -> bool {
self.config_file
.lensfun
.as_ref()
.and_then(|lensfun| lensfun.use_sudo)
.unwrap_or(false)
}
}
#[cfg(test)]
mod test {
use crate::config::ConfigFile;
use crate::config::*;
use color_eyre::eyre::eyre;
/// Test the default configuration in `config.example.toml` is valid.
#[test]
@@ -1542,4 +1556,51 @@ mod test {
assert!(toml::from_str::<ConfigFile>(str).is_ok());
}
fn config() -> Config {
Config {
opt: CommandLineArgs::parse_from::<_, String>([]),
config_file: ConfigFile::default(),
allowed_steps: Vec::new(),
}
}
#[test]
fn test_should_execute_remote_different_hostname() {
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"))
}
#[test]
fn test_should_execute_remote_different_hostname_with_user() {
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
}
#[test]
fn test_should_execute_remote_unknown_hostname() {
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"))
}
#[test]
fn test_should_not_execute_remote_same_hostname() {
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"))
}
#[test]
fn test_should_not_execute_remote_same_hostname_with_user() {
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"))
}
#[test]
fn test_should_execute_remote_matching_limit() {
let mut config = config();
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
}
#[test]
fn test_should_not_execute_remote_not_matching_limit() {
let mut config = config();
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
}
}

View File

@@ -1,6 +1,6 @@
//! SIGINT handling in Unix systems.
use crate::ctrlc::interrupted::set_interrupted;
use nix::sys::signal;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
/// Handle SIGINT. Set the interruption flag.
extern "C" fn handle_sigint(_: i32) {
@@ -10,12 +10,8 @@ extern "C" fn handle_sigint(_: i32) {
/// Set the necessary signal handlers.
/// The function panics on failure.
pub fn set_handler() {
let sig_action = signal::SigAction::new(
signal::SigHandler::Handler(handle_sigint),
signal::SaFlags::empty(),
signal::SigSet::empty(),
);
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
unsafe {
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
sigaction(Signal::SIGINT, &sig_action).unwrap();
}
}

View File

@@ -1,6 +1,5 @@
#![allow(dead_code)]
use crate::executor::RunType;
use crate::git::Git;
use crate::sudo::Sudo;
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{config::Config, executor::Executor};
@@ -12,7 +11,6 @@ use std::sync::Mutex;
pub struct ExecutionContext<'a> {
run_type: RunType,
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
/// Name of a tmux session to execute commands in, if any.
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
@@ -23,12 +21,11 @@ pub struct ExecutionContext<'a> {
}
impl<'a> ExecutionContext<'a> {
pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
pub fn new(run_type: RunType, sudo: Option<Sudo>, config: &'a Config) -> Self {
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
Self {
run_type,
sudo,
git,
config,
tmux_session: Mutex::new(None),
under_ssh,
@@ -44,10 +41,6 @@ impl<'a> ExecutionContext<'a> {
self.run_type
}
pub fn git(&self) -> &Git {
self.git
}
pub fn sudo(&self) -> &Option<Sudo> {
&self.sudo
}

View File

@@ -227,6 +227,7 @@ impl DryCommand {
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
pub enum ExecutorChild {
#[allow(unused)] // this type has not been used
Wet(Child),
Dry,
}

View File

@@ -6,19 +6,20 @@ use std::path::PathBuf;
use std::process::exit;
use std::time::Duration;
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
use clap::CommandFactory;
use clap::{crate_version, Parser};
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use console::Key;
use etcetera::base_strategy::BaseStrategy;
#[cfg(windows)]
use etcetera::base_strategy::Windows;
use etcetera::base_strategy::{BaseStrategy, Xdg};
#[cfg(unix)]
use etcetera::base_strategy::Xdg;
use once_cell::sync::Lazy;
use tracing::debug;
use crate::steps::git::GitAction;
use self::config::{CommandLineArgs, Config, Step};
use self::error::StepFailed;
#[cfg(all(windows, feature = "self-update"))]
@@ -26,8 +27,9 @@ use self::error::Upgraded;
use self::steps::{remote::*, *};
use self::terminal::*;
use self::utils::{install_color_eyre, install_tracing, update_tracing};
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
mod breaking_changes;
mod command;
mod config;
mod ctrlc;
@@ -45,10 +47,12 @@ mod sudo;
mod terminal;
mod utils;
pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
#[cfg(unix)]
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
#[cfg(windows)]
pub static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
fn run() -> Result<()> {
install_color_eyre()?;
@@ -119,8 +123,6 @@ fn run() -> Result<()> {
}
}
let git = git::Git::new();
let mut git_repos = git::Repositories::new(&git);
let powershell = powershell::Powershell::new();
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
let emacs = emacs::Emacs::new();
@@ -129,9 +131,25 @@ fn run() -> Result<()> {
let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
let run_type = executor::RunType::new(config.dry_run());
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &config);
let mut runner = runner::Runner::new(&ctx);
// If
//
// 1. the breaking changes notification shouldnot be skipped
// 2. this is the first execution of a major release
//
// inform user of breaking changes
if !should_skip() && first_run_of_major_release()? {
print_breaking_changes();
if prompt_yesno("Confirmed?")? {
write_keep_file()?;
} else {
exit(1);
}
}
// Self-Update step, this will execute only if:
// 1. the `self-update` feature is enabled
// 2. it is not disabled from configuration (env var/CLI opt/file)
@@ -164,7 +182,7 @@ fn run() -> Result<()> {
}
if let Some(topgrades) = config.remote_topgrades() {
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(hostname(), t)) {
runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
ssh::ssh_step(&ctx, remote_topgrade)
})?;
@@ -216,6 +234,8 @@ fn run() -> Result<()> {
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
})?;
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
runner.execute(Step::Waydroid, "Waydroid", || linux::run_waydroid(&ctx))?;
runner.execute(Step::AutoCpufreq, "auto-cpufreq", || linux::run_auto_cpufreq(&ctx))?;
}
#[cfg(target_os = "macos")]
@@ -239,6 +259,7 @@ fn run() -> Result<()> {
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
})?;
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
runner.execute(Step::Xcodes, "Xcodes", || macos::update_xcodes(&ctx))?;
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
runner.execute(Step::Mas, "App Store", || macos::run_mas(&ctx))?;
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
@@ -249,14 +270,14 @@ fn run() -> Result<()> {
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
dragonfly::upgrade_packages(&ctx)
})?;
dragonfly::audit_packages(&ctx)?;
runner.execute(Step::Audit, "DragonFly Audit", || dragonfly::audit_packages(&ctx))?;
}
#[cfg(target_os = "freebsd")]
{
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
freebsd::audit_packages(&ctx)?;
runner.execute(Step::Audit, "FreeBSD Audit", || freebsd::audit_packages(&ctx))?;
}
#[cfg(target_os = "openbsd")]
@@ -274,11 +295,14 @@ fn run() -> Result<()> {
{
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?;
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
@@ -302,6 +326,7 @@ fn run() -> Result<()> {
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
unix::upgrade_gnome_extensions(&ctx)
})?;
runner.execute(Step::Pyenv, "pyenv", || unix::run_pyenv(&ctx))?;
runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(&ctx))?;
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
runner.execute(Step::Maza, "maza", || unix::run_maza(&ctx))?;
@@ -319,6 +344,8 @@ fn run() -> Result<()> {
// The following update function should be executed on all OSes.
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(&ctx))?;
runner.execute(Step::Elan, "elan", || generic::run_elan(&ctx))?;
runner.execute(Step::Rye, "rye", || generic::run_rye(&ctx))?;
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&ctx))?;
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
@@ -332,7 +359,7 @@ fn run() -> Result<()> {
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
generic::run_vscode_extensions_upgrade(&ctx)
generic::run_vscode_extensions_update(&ctx)
})?;
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
@@ -379,82 +406,15 @@ fn run() -> Result<()> {
generic::run_ghcli_extensions_upgrade(&ctx)
})?;
runner.execute(Step::Bob, "Bob", || generic::run_bob(&ctx))?;
if config.use_predefined_git_repos() {
if config.should_run(Step::Emacs) {
if !emacs.is_doom() {
if let Some(directory) = emacs.directory() {
git_repos.insert_if_repo(directory, GitAction::Pull);
}
}
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"), GitAction::Pull);
}
if config.should_run(Step::Vim) {
git_repos.insert_if_repo(HOME_DIR.join(".vim"), GitAction::Pull);
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"), GitAction::Pull);
}
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"), GitAction::Pull);
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"), GitAction::Pull);
if config.should_run(Step::Rcm) {
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"), GitAction::Pull);
}
#[cfg(unix)]
{
git_repos.insert_if_repo(zsh::zshrc(), GitAction::Pull);
if config.should_run(Step::Tmux) {
git_repos.insert_if_repo(HOME_DIR.join(".tmux"), GitAction::Pull);
}
git_repos.insert_if_repo(HOME_DIR.join(".config/fish"), GitAction::Pull);
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"), GitAction::Pull);
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"), GitAction::Pull);
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"), GitAction::Pull);
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"), GitAction::Pull);
}
#[cfg(windows)]
git_repos.insert_if_repo(
WINDOWS_DIRS
.cache_dir()
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
GitAction::Pull,
);
#[cfg(windows)]
windows::insert_startup_scripts(&mut git_repos).ok();
if let Some(profile) = powershell.profile() {
git_repos.insert_if_repo(profile, GitAction::Pull);
}
}
if config.should_run(Step::GitRepos) {
if let Some(custom_git_repos) = config.git_repos() {
for git_repo in custom_git_repos {
git_repos.glob_insert(git_repo, GitAction::Pull);
git_repos.glob_insert(git_repo, GitAction::Push);
}
}
if let Some(git_pull_only_repos) = config.git_pull_only_repos() {
for git_repo in git_pull_only_repos {
git_repos.glob_insert(git_repo, GitAction::Pull);
}
}
if let Some(git_push_only_repos) = config.git_push_only_repos() {
for git_repo in git_push_only_repos {
git_repos.glob_insert(git_repo, GitAction::Push);
}
}
runner.execute(Step::GitRepos, "Git repositories", || {
git.multi_repo_step(&git_repos, &ctx)
})?;
}
runner.execute(Step::Certbot, "Certbot", || generic::run_certbot(&ctx))?;
runner.execute(Step::GitRepos, "Git Repositories", || git::run_git_pull(&ctx))?;
runner.execute(Step::ClamAvDb, "ClamAV Databases", || generic::run_freshclam(&ctx))?;
runner.execute(Step::PlatformioCore, "PlatformIO Core", || {
generic::run_platform_io(&ctx)
})?;
runner.execute(Step::Lensfun, "Lensfun's database update", || {
generic::run_lensfun_update_data(&ctx)
})?;
if should_run_powershell {
runner.execute(Step::Powershell, "Powershell Modules Update", || {

View File

@@ -34,6 +34,14 @@ impl<'a> Runner<'a> {
let key = key.into();
debug!("Step {:?}", key);
// alter the `func` to put it in a span
let func = || {
let span =
tracing::span!(parent: tracing::Span::none(), tracing::Level::TRACE, "step", step = ?step, key = %key);
let _guard = span.enter();
func()
};
loop {
match func() {
Ok(()) => {

View File

@@ -1,5 +1,3 @@
#![cfg(windows)]
use color_eyre::eyre::Result;
use std::{env::current_exe, fs, path::PathBuf};
use tracing::{debug, error};

View File

@@ -6,6 +6,7 @@ use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use tracing::{debug, error, warn};
use wildmatch::WildMatch;
use crate::command::CommandExt;
use crate::error::{self, TopgradeError};
@@ -48,7 +49,16 @@ impl Display for Container {
/// Returns a Vector of all containers, with Strings in the format
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
fn list_containers(crt: &Path) -> Result<Vec<Container>> {
///
/// 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| {
patterns
.iter()
.map(|pattern| WildMatch::new(pattern))
.collect::<Vec<WildMatch>>()
});
debug!(
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
crt.display()
@@ -83,6 +93,13 @@ fn list_containers(crt: &Path) -> Result<Vec<Container>> {
assert_eq!(split_res.len(), 2);
let (repo_tag, image_id) = (split_res[0], split_res[1]);
if let Some(ref ignored_containers) = ignored_containers {
if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
debug!("Skipping ignored container '{}'", line);
continue;
}
}
debug!(
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
crt.display(),
@@ -109,7 +126,8 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
print_separator("Containers");
let mut success = true;
let containers = list_containers(&crt).context("Failed to list Docker containers")?;
let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
debug!("Containers to inspect: {:?}", containers);
for container in containers.iter() {

View File

@@ -8,6 +8,7 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use semver::Version;
use tempfile::tempfile_in;
use tracing::{debug, error};
@@ -23,6 +24,18 @@ use crate::{
terminal::print_warning,
};
#[cfg(target_os = "linux")]
pub fn is_wsl() -> Result<bool> {
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
debug!("Uname output: {}", output);
Ok(output.contains("microsoft"))
}
#[cfg(not(target_os = "linux"))]
pub fn is_wsl() -> Result<bool> {
Ok(false)
}
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from)
@@ -107,7 +120,10 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
print_separator("RubyGems");
let gem_path_str = gem.as_os_str();
if gem_path_str.to_str().unwrap().contains("asdf") {
if gem_path_str.to_str().unwrap().contains("asdf")
|| gem_path_str.to_str().unwrap().contains(".rbenv")
|| gem_path_str.to_str().unwrap().contains(".rvm")
{
ctx.run_type()
.execute(gem)
.args(["update", "--system"])
@@ -214,6 +230,24 @@ pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(rustup).arg("update").status_checked()
}
pub fn run_rye(ctx: &ExecutionContext) -> Result<()> {
let rye = require("rye")?;
print_separator("Rye");
ctx.run_type().execute(rye).args(["self", "update"]).status_checked()
}
pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
let elan = require("elan")?;
print_separator("elan");
ctx.run_type()
.execute(&elan)
.args(["self", "update"])
.status_checked()?;
ctx.run_type().execute(&elan).arg("update").status_checked()
}
pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
let juliaup = require("juliaup")?;
@@ -324,41 +358,63 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
command.args(["upgrade", "--no-dry-run"]).status_checked()
}
pub fn run_vscode_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
let vscode = require("code")?;
print_separator("Visual Studio Code extensions");
// Vscode does not have CLI command to upgrade all extensions (see https://github.com/microsoft/vscode/issues/56578)
// Instead we get the list of installed extensions with `code --list-extensions` command (obtain a line-return separated list of installed extensions)
let extensions = Command::new(&vscode)
.arg("--list-extensions")
.output_checked_utf8()?
.stdout;
// Then we construct the upgrade command: `code --force --install-extension [ext0] --install-extension [ext1] ... --install-extension [extN]`
if !extensions.is_empty() {
let mut command_args = vec!["--force"];
for extension in extensions.split_whitespace() {
command_args.extend(["--install-extension", extension]);
}
ctx.run_type().execute(&vscode).args(command_args).status_checked()?;
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
// Calling vscode in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
if is_wsl()? {
return Err(SkipStep(String::from("Should not run in WSL")).into());
}
Ok(())
let vscode = require("code")?;
// 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
let version: Result<Version> = match Command::new(&vscode)
.arg("--version")
.output_checked_utf8()?
.stdout
.lines()
.next()
{
Some(item) => Version::parse(item).map_err(|err| err.into()),
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
};
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
return Err(SkipStep(String::from("Too old vscode version to have update extensions command")).into());
}
print_separator("Visual Studio Code extensions");
ctx.run_type()
.execute(vscode)
.arg("--update-extensions")
.status_checked()
}
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
let pipx = require("pipx")?;
print_separator("pipx");
ctx.run_type().execute(pipx).arg("upgrade-all").status_checked()
let mut command_args = vec!["upgrade-all", "--include-injected"];
// pipx version 1.4.0 introduced a new command argument `pipx upgrade-all --quiet`
// (see https://pipx.pypa.io/stable/docs/#pipx-upgrade-all)
let version_str = Command::new(&pipx)
.args(["--version"])
.output_checked_utf8()
.map(|s| s.stdout.trim().to_owned());
let version = Version::parse(&version_str?);
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
command_args.push("--quiet")
}
ctx.run_type().execute(pipx).args(command_args).status_checked()
}
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
let conda = require("conda")?;
let output = Command::new("conda")
let output = Command::new(&conda)
.args(["config", "--show", "auto_activate_base"])
.output_checked_utf8()?;
debug!("Conda output: {}", output.stdout);
@@ -379,7 +435,7 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
let mamba = require("mamba")?;
let output = Command::new("mamba")
let output = Command::new(&mamba)
.args(["config", "--show", "auto_activate_base"])
.output_checked_utf8()?;
debug!("Mamba output: {}", output.stdout);
@@ -425,24 +481,57 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
.output_checked_utf8()
.map_err(|_| SkipStep("pip does not exist".to_string()))?;
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
Command::new(&python3)
.args(["-c", check_externally_managed])
let check_extern_managed_script = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
let output = Command::new(&python3)
.args(["-c", check_extern_managed_script])
.output_checked_utf8()?;
let stdout = output.stdout.trim();
let extern_managed = match stdout {
"N" => false,
"Y" => true,
_ => unreachable!("unexpected output from `check_extern_managed_script`"),
};
let allow_break_sys_pkg = match Command::new(&python3)
.args(["-m", "pip", "config", "get", "global.break-system-packages"])
.output_checked_utf8()
.map_err(|_| SkipStep("pip may be externally managed".to_string()))
.and_then(|output| match output.stdout.trim() {
"N" => Ok(()),
"Y" => Err(SkipStep("pip is externally managed".to_string())),
_ => {
print_warning("Unexpected output when checking EXTERNALLY-MANAGED");
print_warning(output.stdout.trim());
Err(SkipStep("pip may be externally managed".to_string()))
}
})?;
{
Ok(output) => {
let stdout = output.stdout.trim();
stdout
.parse::<bool>()
.expect("unexpected output that is not `true` or `false`")
}
// it can fail because this key may not be set
//
// ```sh
// $ pip --version
// pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)
//
// $ pip config get global.break-system-packages
// ERROR: No such key - global.break-system-packages
//
// $ echo $?
// 1
// ```
Err(_) => false,
};
debug!("pip3 externally managed: {} ", extern_managed);
debug!("pip3 global.break-system-packages: {}", allow_break_sys_pkg);
// Even though pip3 is externally managed, we should still update it if
// `global.break-system-packages` is true.
if extern_managed && !allow_break_sys_pkg {
return Err(SkipStep(
"Skip pip3 update as it is externally managed and global.break-system-packages is not true".to_string(),
)
.into());
}
print_separator("pip3");
if env::var("VIRTUAL_ENV").is_ok() {
print_warning("This step is will be skipped when running inside a virtual environment");
print_warning("This step is skipped when running inside a virtual environment");
return Err(SkipStep("Does not run inside a virtual environment".to_string()).into());
}
@@ -470,6 +559,7 @@ pub fn run_pip_review_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_pip_review_local_update(ctx: &ExecutionContext) -> Result<()> {
let pip_review = require("pip-review")?;
@@ -489,6 +579,7 @@ pub fn run_pip_review_local_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
let pipupgrade = require("pipupgrade")?;
@@ -506,6 +597,7 @@ pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_stack_update(ctx: &ExecutionContext) -> Result<()> {
if require("ghcup").is_ok() {
// `ghcup` is present and probably(?) being used to install `stack`.
@@ -622,7 +714,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!(
"Composer directory {} isn't a decandent of the user's home directory",
"Composer directory {} isn't a descendant of the user's home directory",
composer_home.display()
))
.into());
@@ -687,10 +779,11 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
return Err(SkipStep(String::from(
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.",
))
.into())
.into());
}
};
let mut in_header = true;
let mut packages = output
.stdout
.lines()
@@ -698,11 +791,17 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
//
// Package Id Version Commands
// -------------------------------------
//
// One thing to note is that .NET SDK respect locale, which means this
// header can be printed in languages other than English, do NOT use it
// to do any check.
.skip(2)
.skip_while(|line| {
// The .NET SDK respects locale, so the header can be printed
// in languages other than English. The separator should hopefully
// always be at least 10 -'s long.
if in_header && line.starts_with("----------") {
in_header = false;
true
} else {
in_header
}
})
.filter(|line| !line.is_empty())
.peekable();
@@ -725,20 +824,19 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
require("helix")?;
let helix = require("helix").or(require("hx"))?;
print_separator("Helix");
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.args(["helix", "--grammar", "fetch"])
.execute(&helix)
.args(["--grammar", "fetch"])
.status_checked()
.with_context(|| "Failed to download helix grammars!")?;
ctx.run_type()
.execute(sudo)
.args(["helix", "--grammar", "build"])
.execute(&helix)
.args(["--grammar", "build"])
.status_checked()
.with_context(|| "Failed to build helix grammars!")?;
@@ -837,3 +935,70 @@ pub fn run_bob(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(bob).args(["update", "--all"]).status_checked()
}
pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let certbot = require("certbot")?;
print_separator("Certbot");
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg(certbot);
cmd.arg("renew");
cmd.status_checked()
}
/// Run `$ freshclam` to update ClamAV signature database
///
/// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam
pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> {
let freshclam = require("freshclam")?;
print_separator("Update ClamAV Database(FreshClam)");
ctx.run_type().execute(freshclam).status_checked()
}
/// Involve `pio upgrade` to update PlatformIO core.
pub fn run_platform_io(ctx: &ExecutionContext) -> Result<()> {
// We use the full path because by default the binary is not in `PATH`:
// https://github.com/topgrade-rs/topgrade/issues/754#issuecomment-2020537559
#[cfg(unix)]
fn bin_path() -> PathBuf {
HOME_DIR.join(".platformio/penv/bin/pio")
}
#[cfg(windows)]
fn bin_path() -> PathBuf {
HOME_DIR.join(".platformio/penv/Scripts/pio.exe")
}
let bin_path = require(bin_path())?;
print_separator("PlatformIO Core");
ctx.run_type().execute(bin_path).arg("upgrade").status_checked()
}
/// Run `lensfun-update-data` to update lensfun database.
///
/// `sudo` will be used if `use_sudo` configuration entry is set to true.
pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
const SEPARATOR: &str = "Lensfun's database update";
let lensfun_update_data = require("lensfun-update-data")?;
const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1;
if ctx.config().lensfun_use_sudo() {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator(SEPARATOR);
ctx.run_type()
.execute(sudo)
.arg(lensfun_update_data)
// `lensfun-update-data` returns 1 when there is no update available
// which should be considered success
.status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE])
} else {
print_separator(SEPARATOR);
ctx.run_type()
.execute(lensfun_update_data)
.status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE])
}
}

View File

@@ -6,38 +6,120 @@ use std::process::{Command, Output, Stdio};
use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result};
use console::style;
use futures::stream::{iter, FuturesUnordered};
use futures::StreamExt;
use futures::stream::{iter, FuturesUnordered, StreamExt};
use glob::{glob_with, MatchOptions};
use tokio::process::Command as AsyncCommand;
use tokio::runtime;
use tracing::{debug, error};
use crate::command::CommandExt;
use crate::config::Step;
use crate::execution_context::ExecutionContext;
use crate::steps::emacs::Emacs;
use crate::terminal::print_separator;
use crate::utils::{which, PathExt};
use crate::{error::SkipStep, terminal::print_warning};
use crate::utils::{require, PathExt};
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
use etcetera::base_strategy::BaseStrategy;
#[cfg(unix)]
use crate::XDG_DIRS;
#[cfg(windows)]
use crate::WINDOWS_DIRS;
pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
let mut repos = RepoStep::try_new()?;
let config = ctx.config();
// handle built-in repos
if config.use_predefined_git_repos() {
// should be executed on all the platforms
{
if config.should_run(Step::Emacs) {
let emacs = Emacs::new();
if !emacs.is_doom() {
if let Some(directory) = emacs.directory() {
repos.insert_if_repo(directory);
}
}
repos.insert_if_repo(HOME_DIR.join(".doom.d"));
}
if config.should_run(Step::Vim) {
repos.insert_if_repo(HOME_DIR.join(".vim"));
repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
}
repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
if config.should_run(Step::Rcm) {
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
}
let powershell = crate::steps::powershell::Powershell::new();
if let Some(profile) = powershell.profile() {
repos.insert_if_repo(profile);
}
}
#[cfg(unix)]
{
repos.insert_if_repo(crate::steps::zsh::zshrc());
if config.should_run(Step::Tmux) {
repos.insert_if_repo(HOME_DIR.join(".tmux"));
}
repos.insert_if_repo(HOME_DIR.join(".config/fish"));
repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
}
#[cfg(windows)]
{
repos.insert_if_repo(
WINDOWS_DIRS
.cache_dir()
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
);
super::os::windows::insert_startup_scripts(&mut repos).ok();
}
}
// Handle user-defined repos
if let Some(custom_git_repos) = config.git_repos() {
for git_repo in custom_git_repos {
repos.glob_insert(git_repo);
}
}
// Warn the user about the bad patterns.
//
// NOTE: this should be executed **before** skipping the Git step or the
// user won't receive this warning in the cases where all the paths configured
// are bad patterns.
repos
.bad_patterns
.iter()
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
if repos.is_repos_empty() {
return Err(SkipStep(String::from("No repositories to pull")).into());
}
print_separator("Git repositories");
repos.pull_repos(ctx)
}
#[cfg(windows)]
static PATH_PREFIX: &str = "\\\\?\\";
#[derive(Debug)]
pub struct Git {
git: Option<PathBuf>,
}
#[derive(Clone, Copy)]
pub enum GitAction {
Push,
Pull,
}
#[derive(Debug)]
pub struct Repositories<'a> {
git: &'a Git,
pull_repositories: HashSet<String>,
push_repositories: HashSet<String>,
pub struct RepoStep {
git: PathBuf,
repos: HashSet<PathBuf>,
glob_match_options: MatchOptions,
bad_patterns: Vec<String>,
}
@@ -52,131 +134,42 @@ fn output_checked_utf8(output: Output) -> Result<()> {
Ok(())
}
}
async fn push_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
let path = repo.to_string();
println!("{} {}", style("Pushing").cyan().bold(), path);
let mut command = AsyncCommand::new(git);
command
.stdin(Stdio::null())
.current_dir(&repo)
.args(["push", "--porcelain"]);
if let Some(extra_arguments) = ctx.config().push_git_arguments() {
command.args(extra_arguments.split_whitespace());
}
let output = command.output().await?;
let result = match output.status.success() {
true => Ok(()),
false => Err(format!("Failed to push {repo}")),
};
if result.is_err() {
println!("{} pushing {}", style("Failed").red().bold(), &repo);
};
match result {
Ok(_) => Ok(()),
Err(e) => Err(eyre!(e)),
}
}
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
let path = repo.to_string();
let before_revision = get_head_revision(git, &repo);
println!("{} {}", style("Pulling").cyan().bold(), path);
let mut command = AsyncCommand::new(git);
command
.stdin(Stdio::null())
.current_dir(&repo)
.args(["pull", "--ff-only"]);
if let Some(extra_arguments) = ctx.config().pull_git_arguments() {
command.args(extra_arguments.split_whitespace());
}
let pull_output = command.output().await?;
let submodule_output = AsyncCommand::new(git)
.args(["submodule", "update", "--recursive"])
.current_dir(&repo)
.stdin(Stdio::null())
.output()
.await?;
let result = output_checked_utf8(pull_output)
.and_then(|_| output_checked_utf8(submodule_output))
.wrap_err_with(|| format!("Failed to pull {repo}"));
if result.is_err() {
println!("{} pulling {}", style("Failed").red().bold(), &repo);
} else {
let after_revision = get_head_revision(git, &repo);
match (&before_revision, &after_revision) {
(Some(before), Some(after)) if before != after => {
println!("{} {}:", style("Changed").yellow().bold(), &repo);
Command::new(git)
.stdin(Stdio::null())
.current_dir(&repo)
.args([
"--no-pager",
"log",
"--no-decorate",
"--oneline",
&format!("{before}..{after}"),
])
.status_checked()?;
println!();
}
_ => {
println!("{} {}", style("Up-to-date").green().bold(), &repo);
}
}
}
result.map(|_| ())
}
fn get_head_revision(git: &Path, repo: &str) -> Option<String> {
fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
Command::new(git)
.stdin(Stdio::null())
.current_dir(repo)
.current_dir(repo.as_ref())
.args(["rev-parse", "HEAD"])
.output_checked_utf8()
.map(|output| output.stdout.trim().to_string())
.map_err(|e| {
error!("Error getting revision for {}: {}", repo, e);
error!("Error getting revision for {}: {}", repo.as_ref().display(), e);
e
})
.ok()
}
fn has_remotes(git: &Path, repo: &str) -> Option<bool> {
Command::new(git)
.stdin(Stdio::null())
.current_dir(repo)
.args(["remote", "show"])
.output_checked_utf8()
.map(|output| output.stdout.lines().count() > 0)
.map_err(|e| {
error!("Error getting remotes for {}: {}", repo, e);
e
})
.ok()
}
impl RepoStep {
/// Try to create a `RepoStep`, fail if `git` is not found.
pub fn try_new() -> Result<Self> {
let git = require("git")?;
let mut glob_match_options = MatchOptions::new();
impl Git {
pub fn new() -> Self {
Self { git: which("git") }
if cfg!(windows) {
glob_match_options.case_sensitive = false;
}
Ok(Self {
git,
repos: HashSet::new(),
bad_patterns: Vec::new(),
glob_match_options,
})
}
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<String> {
/// Try to get the root of the repo specified in `path`.
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
match path.as_ref().canonicalize() {
Ok(mut path) => {
debug_assert!(path.exists());
@@ -200,16 +193,16 @@ impl Git {
path_string
};
if let Some(git) = &self.git {
let output = Command::new(git)
.stdin(Stdio::null())
.current_dir(path)
.args(["rev-parse", "--show-toplevel"])
.output_checked_utf8()
.ok()
.map(|output| output.stdout.trim().to_string());
return output;
}
let output = Command::new(&self.git)
.stdin(Stdio::null())
.current_dir(path)
.args(["rev-parse", "--show-toplevel"])
.output_checked_utf8()
.ok()
// trim the last newline char
.map(|output| PathBuf::from(output.stdout.trim()));
return output;
}
Err(e) => match e.kind() {
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
@@ -219,142 +212,38 @@ impl Git {
None
}
pub fn multi_repo_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
// Warn the user about the bad patterns.
//
// NOTE: this should be executed **before** skipping the Git step or the
// user won't receive this warning in the cases where all the paths configured
// are bad patterns.
repositories
.bad_patterns
.iter()
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
if repositories.is_empty() {
return Err(SkipStep(String::from("No repositories to pull or push")).into());
}
print_separator("Git repositories");
self.multi_pull(repositories, ctx)?;
self.multi_push(repositories, ctx)?;
Ok(())
}
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
let git = self.git.as_ref().unwrap();
if ctx.run_type().dry() {
repositories
.pull_repositories
.iter()
.for_each(|repo| println!("Would pull {}", &repo));
return Ok(());
}
let futures_iterator = repositories
.pull_repositories
.iter()
.filter(|repo| match has_remotes(git, repo) {
Some(false) => {
println!(
"{} {} because it has no remotes",
style("Skipping").yellow().bold(),
repo
);
false
}
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
})
.map(|repo| pull_repository(repo.clone(), git, ctx));
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
iter(futures_iterator).buffer_unordered(limit).boxed()
} else {
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
};
let basic_rt = runtime::Runtime::new()?;
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
let error = results.into_iter().find(|r| r.is_err());
error.unwrap_or(Ok(()))
}
pub fn multi_push(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
let git = self.git.as_ref().unwrap();
if ctx.run_type().dry() {
repositories
.push_repositories
.iter()
.for_each(|repo| println!("Would push {}", &repo));
return Ok(());
}
let futures_iterator = repositories
.push_repositories
.iter()
.filter(|repo| match has_remotes(git, repo) {
Some(false) => {
println!(
"{} {} because it has no remotes",
style("Skipping").yellow().bold(),
repo
);
false
}
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
})
.map(|repo| push_repository(repo.clone(), git, ctx));
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
iter(futures_iterator).buffer_unordered(limit).boxed()
} else {
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
};
let basic_rt = runtime::Runtime::new()?;
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
let error = results.into_iter().find(|r| r.is_err());
error.unwrap_or(Ok(()))
}
}
impl<'a> Repositories<'a> {
pub fn new(git: &'a Git) -> Self {
let mut glob_match_options = MatchOptions::new();
if cfg!(windows) {
glob_match_options.case_sensitive = false;
}
Self {
git,
bad_patterns: Vec::new(),
glob_match_options,
pull_repositories: HashSet::new(),
push_repositories: HashSet::new(),
}
}
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P, action: GitAction) -> bool {
if let Some(repo) = self.git.get_repo_root(path) {
match action {
GitAction::Push => self.push_repositories.insert(repo),
GitAction::Pull => self.pull_repositories.insert(repo),
};
/// Check if `path` is a git repo, if yes, add it to `self.repos`.
///
/// Return the check result.
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
if let Some(repo) = self.get_repo_root(path) {
self.repos.insert(repo);
true
} else {
false
}
}
pub fn glob_insert(&mut self, pattern: &str, action: GitAction) {
/// Check if `repo` has a remote.
fn has_remotes<P: AsRef<Path>>(&self, repo: P) -> Option<bool> {
let mut cmd = Command::new(&self.git);
cmd.stdin(Stdio::null())
.current_dir(repo.as_ref())
.args(["remote", "show"]);
let res = cmd.output_checked_utf8();
res.map(|output| output.stdout.lines().count() > 0)
.map_err(|e| {
error!("Error getting remotes for {}: {}", repo.as_ref().display(), e);
e
})
.ok()
}
/// Similar to `insert_if_repo`, with glob support.
pub fn glob_insert(&mut self, pattern: &str) {
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
let mut last_git_repo: Option<PathBuf> = None;
for entry in glob {
@@ -363,14 +252,14 @@ impl<'a> Repositories<'a> {
if let Some(last_git_repo) = &last_git_repo {
if path.is_descendant_of(last_git_repo) {
debug!(
"Skipping {} because it's a decendant of last known repo {}",
"Skipping {} because it's a descendant of last known repo {}",
path.display(),
last_git_repo.display()
);
continue;
}
}
if self.insert_if_repo(&path, action) {
if self.insert_if_repo(&path) {
last_git_repo = Some(path);
}
}
@@ -388,27 +277,130 @@ impl<'a> Repositories<'a> {
}
}
/// Return true if `pull_repos` and `push_repos` are both empty.
pub fn is_empty(&self) -> bool {
self.pull_repositories.is_empty() && self.push_repositories.is_empty()
/// True if `self.repos` is empty.
pub fn is_repos_empty(&self) -> bool {
self.repos.is_empty()
}
// The following 2 functions are `#[cfg(unix)]` because they are only used in
// the `oh-my-zsh` step, which is UNIX-only.
#[cfg(unix)]
/// Return true if `pull_repos` is empty.
pub fn pull_is_empty(&self) -> bool {
self.pull_repositories.is_empty()
}
#[cfg(unix)]
/// Remove `path` from `pull_repos`
/// Remove `path` from `self.repos`.
///
/// # Panic
/// Will panic if `path` is not in the `pull_repos` under a debug build.
pub fn remove_from_pull(&mut self, path: &str) {
let _removed = self.pull_repositories.remove(path);
// `cfg(unix)` because it is only used in the oh-my-zsh step.
#[cfg(unix)]
pub fn remove<P: AsRef<Path>>(&mut self, path: P) {
let _removed = self.repos.remove(path.as_ref());
debug_assert!(_removed);
}
/// Try to pull a repo.
async fn pull_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
let before_revision = get_head_revision(&self.git, &repo);
if ctx.config().verbose() {
println!("{} {}", style("Pulling").cyan().bold(), repo.as_ref().display());
}
let mut command = AsyncCommand::new(&self.git);
command
.stdin(Stdio::null())
.current_dir(&repo)
.args(["pull", "--ff-only"]);
if let Some(extra_arguments) = ctx.config().git_arguments() {
command.args(extra_arguments.split_whitespace());
}
let pull_output = command.output().await?;
let submodule_output = AsyncCommand::new(&self.git)
.args(["submodule", "update", "--recursive"])
.current_dir(&repo)
.stdin(Stdio::null())
.output()
.await?;
let result = output_checked_utf8(pull_output)
.and_then(|_| output_checked_utf8(submodule_output))
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
if result.is_err() {
println!("{} pulling {}", style("Failed").red().bold(), repo.as_ref().display());
} else {
let after_revision = get_head_revision(&self.git, repo.as_ref());
match (&before_revision, &after_revision) {
(Some(before), Some(after)) if before != after => {
println!("{} {}", style("Changed").yellow().bold(), repo.as_ref().display());
Command::new(&self.git)
.stdin(Stdio::null())
.current_dir(&repo)
.args([
"--no-pager",
"log",
"--no-decorate",
"--oneline",
&format!("{before}..{after}"),
])
.status_checked()?;
println!();
}
_ => {
if ctx.config().verbose() {
println!("{} {}", style("Up-to-date").green().bold(), repo.as_ref().display());
}
}
}
}
result.map(|_| ())
}
/// Pull the repositories specified in `self.repos`.
///
/// # NOTE
/// This function will create an async runtime and do the real job so the
/// function itself is not async.
fn pull_repos(&self, ctx: &ExecutionContext) -> Result<()> {
if ctx.run_type().dry() {
self.repos
.iter()
.for_each(|repo| println!("Would pull {}", repo.display()));
return Ok(());
}
if !ctx.config().verbose() {
println!(
"\n{} updated repositories will be shown...\n",
style("Only").green().bold()
);
}
let futures_iterator = self
.repos
.iter()
.filter(|repo| match self.has_remotes(repo) {
Some(false) => {
println!(
"{} {} because it has no remotes",
style("Skipping").yellow().bold(),
repo.display()
);
false
}
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
})
.map(|repo| self.pull_repo(ctx, repo));
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
iter(futures_iterator).buffer_unordered(limit).boxed()
} else {
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
};
let basic_rt = runtime::Runtime::new()?;
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
let error = results.into_iter().find(|r| r.is_err());
error.unwrap_or(Ok(()))
}
}

View File

@@ -19,7 +19,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
println!();
print_separator("DragonFly BSD Audit");
#[allow(clippy::disallowed_methods)]
if !Command::new(sudo)
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])

View File

@@ -30,7 +30,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
println!();
print_separator("FreeBSD Audit");
Command::new(sudo)
.args(["/usr/sbin/pkg", "audit", "-Fr"])
.status_checked()?;

View File

@@ -8,8 +8,9 @@ use tracing::{debug, warn};
use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError};
use crate::execution_context::ExecutionContext;
use crate::steps::generic::is_wsl;
use crate::steps::os::archlinux;
use crate::terminal::print_separator;
use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
use crate::{Step, HOME_DIR};
@@ -19,12 +20,14 @@ static OS_RELEASE_PATH: &str = "/etc/os-release";
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Distribution {
Alpine,
Wolfi,
Arch,
Bedrock,
CentOS,
Chimera,
ClearLinux,
Fedora,
FedoraSilverblue,
FedoraImmutable,
Debian,
Gentoo,
OpenMandriva,
@@ -38,6 +41,7 @@ pub enum Distribution {
Exherbo,
NixOS,
KDENeon,
Nobara,
}
impl Distribution {
@@ -45,27 +49,31 @@ impl Distribution {
let section = os_release.general_section();
let id = section.get("ID");
let name = section.get("NAME");
let variant: Option<Vec<&str>> = section.get("VARIANT").map(|s| s.split_whitespace().collect());
let variant = section.get("VARIANT");
let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect());
Ok(match id {
Some("alpine") => Distribution::Alpine,
Some("chimera") => Distribution::Chimera,
Some("wolfi") => Distribution::Wolfi,
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
Some("clear-linux-os") => Distribution::ClearLinux,
Some("fedora") | Some("nobara") => {
Some("fedora") => {
return if let Some(variant) = variant {
if variant.contains(&"Silverblue") {
Ok(Distribution::FedoraSilverblue)
} else {
Ok(Distribution::Fedora)
match variant {
"Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" => {
Ok(Distribution::FedoraImmutable)
}
_ => Ok(Distribution::Fedora),
}
} else {
Ok(Distribution::Fedora)
};
}
Some("nobara") => Distribution::Nobara,
Some("void") => Distribution::Void,
Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
Some("solus") => Distribution::Solus,
Some("gentoo") => Distribution::Gentoo,
@@ -129,9 +137,11 @@ impl Distribution {
match self {
Distribution::Alpine => upgrade_alpine_linux(ctx),
Distribution::Chimera => upgrade_chimera_linux(ctx),
Distribution::Wolfi => upgrade_wolfi_linux(ctx),
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
Distribution::FedoraSilverblue => upgrade_fedora_silverblue(ctx),
Distribution::FedoraImmutable => upgrade_fedora_immutable(ctx),
Distribution::ClearLinux => upgrade_clearlinux(ctx),
Distribution::Debian => upgrade_debian(ctx),
Distribution::Gentoo => upgrade_gentoo(ctx),
@@ -147,6 +157,7 @@ impl Distribution {
Distribution::Bedrock => update_bedrock(ctx),
Distribution::OpenMandriva => upgrade_openmandriva(ctx),
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
Distribution::Nobara => upgrade_nobara(ctx),
}
}
@@ -173,7 +184,7 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
debug!("Bedrock distribution {}", distribution);
match distribution {
"arch" => archlinux::upgrade_arch_linux(ctx)?,
"debian" | "ubuntu" => upgrade_debian(ctx)?,
"debian" | "ubuntu" | "linuxmint" => upgrade_debian(ctx)?,
"centos" | "fedora" => upgrade_redhat(ctx)?,
"bedrock" => upgrade_bedrock_strata(ctx)?,
_ => {
@@ -185,13 +196,23 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
fn is_wsl() -> Result<bool> {
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
debug!("Uname output: {}", output);
Ok(output.contains("microsoft"))
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
}
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
}
fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
@@ -230,7 +251,41 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
fn upgrade_fedora_silverblue(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let pkg_manager = require("dnf")?;
let mut update_command = ctx.run_type().execute(sudo);
update_command.arg(&pkg_manager);
if ctx.config().yes(Step::System) {
update_command.arg("-y");
}
update_command.arg("update");
// See https://nobaraproject.org/docs/upgrade-troubleshooting/how-do-i-update-the-system/
update_command.args([
"rpmfusion-nonfree-release",
"rpmfusion-free-release",
"fedora-repos",
"nobara-repos",
]);
update_command.arg("--refresh").status_checked()?;
let mut upgrade_command = ctx.run_type().execute(sudo);
upgrade_command.arg(&pkg_manager);
if ctx.config().yes(Step::System) {
upgrade_command.arg("-y");
}
upgrade_command.arg("distro-sync");
upgrade_command.status_checked()?;
Ok(())
}
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree);
command.arg("upgrade");
@@ -973,6 +1028,64 @@ pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
exe.status_checked()
}
pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let waydroid = require("waydroid")?;
let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?;
// example output of `waydroid status`:
//
// ```sh
// $ waydroid status
// Session: RUNNING
// Container: RUNNING
// Vendor type: MAINLINE
// IP address: 192.168.240.112
// Session user: w568w(1000)
// Wayland display: wayland-0
// ```
//
// ```sh
// $ waydroid status
// Session: STOPPED
// Vendor type: MAINLINE
// ```
let session = status
.stdout
.lines()
.find(|line| line.contains("Session:"))
.expect("the output of `waydroid status` should contain `Session:`");
let is_container_running = session.contains("RUNNING");
let assume_yes = ctx.config().yes(Step::Waydroid);
print_separator("Waydroid");
if is_container_running && !assume_yes {
let update_allowed =
prompt_yesno("Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?")?;
if !update_allowed {
return Err(SkipStep("Skip the Waydroid step because the user don't want to proceed".to_string()).into());
}
}
ctx.run_type()
.execute(sudo)
.arg(&waydroid)
.arg("upgrade")
.status_checked()
}
pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let auto_cpu_freq = require("auto-cpufreq")?;
print_separator("auto-cpufreq");
ctx.run_type()
.execute(sudo)
.arg(auto_cpu_freq)
.arg("--update")
.status_checked()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -985,6 +1098,11 @@ mod tests {
);
}
#[test]
fn test_wolfi() {
test_template(include_str!("os_release/wolfi"), Distribution::Wolfi);
}
#[test]
fn test_arch_linux() {
test_template(include_str!("os_release/arch"), Distribution::Arch);
@@ -1036,6 +1154,22 @@ mod tests {
test_template(include_str!("os_release/fedora"), Distribution::Fedora);
}
#[test]
fn test_fedora_immutable() {
test_template(
include_str!("os_release/fedorasilverblue"),
Distribution::FedoraImmutable,
);
test_template(include_str!("os_release/fedorakinoite"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/fedoraonyx"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/fedorasericea"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/fedoraiot"), Distribution::FedoraImmutable);
test_template(
include_str!("os_release/fedoraswayatomic"),
Distribution::FedoraImmutable,
);
}
#[test]
fn test_manjaro() {
test_template(include_str!("os_release/manjaro"), Distribution::Arch);
@@ -1105,4 +1239,9 @@ mod tests {
fn test_solus() {
test_template(include_str!("os_release/solus"), Distribution::Solus);
}
#[test]
fn test_nobara() {
test_template(include_str!("os_release/nobara"), Distribution::Nobara);
}
}

View File

@@ -4,6 +4,7 @@ use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{utils::require, Step};
use color_eyre::eyre::Result;
use std::collections::HashSet;
use std::fs;
use std::process::Command;
use tracing::debug;
@@ -41,7 +42,7 @@ pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
print_separator("macOS system update");
let should_ask = !(ctx.config().yes(Step::System)) || (ctx.config().dry_run());
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
if should_ask {
println!("Finding available software");
if system_update_available()? {
@@ -93,3 +94,148 @@ pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
}
Ok(())
}
pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
let xcodes = require("xcodes")?;
print_separator("Xcodes");
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.config().dry_run());
let releases = ctx
.run_type()
.execute(&xcodes)
.args(["update"])
.output_checked_utf8()?
.stdout;
let releases_installed: Vec<String> = releases
.lines()
.filter(|r| r.contains("(Installed)"))
.map(String::from)
.collect();
if releases_installed.is_empty() {
println!("No Xcode releases installed.");
return Ok(());
}
let (installed_gm, installed_beta, installed_regular) =
releases_installed
.iter()
.fold((false, false, false), |(gm, beta, regular), release| {
(
gm || release.contains("GM") || release.contains("Release Candidate"),
beta || release.contains("Beta"),
regular
|| !(release.contains("GM")
|| release.contains("Release Candidate")
|| release.contains("Beta")),
)
});
let releases_gm = releases
.lines()
.filter(|&r| r.matches("GM").count() > 0 || r.matches("Release Candidate").count() > 0)
.map(String::from)
.collect();
let releases_beta = releases
.lines()
.filter(|&r| r.matches("Beta").count() > 0)
.map(String::from)
.collect();
let releases_regular = releases
.lines()
.filter(|&r| {
r.matches("GM").count() == 0
&& r.matches("Release Candidate").count() == 0
&& r.matches("Beta").count() == 0
})
.map(String::from)
.collect();
if installed_gm {
process_xcodes_releases(releases_gm, should_ask, ctx)?;
}
if installed_beta {
process_xcodes_releases(releases_beta, should_ask, ctx)?;
}
if installed_regular {
process_xcodes_releases(releases_regular, should_ask, ctx)?;
}
let releases_new = ctx
.run_type()
.execute(&xcodes)
.args(["list"])
.output_checked_utf8()?
.stdout;
let releases_gm_new_installed: HashSet<_> = releases_new
.lines()
.filter(|release| {
release.contains("(Installed)") && (release.contains("GM") || release.contains("Release Candidate"))
})
.collect();
let releases_beta_new_installed: HashSet<_> = releases_new
.lines()
.filter(|release| release.contains("(Installed)") && release.contains("Beta"))
.collect();
let releases_regular_new_installed: HashSet<_> = releases_new
.lines()
.filter(|release| {
release.contains("(Installed)")
&& !(release.contains("GM") || release.contains("Release Candidate") || release.contains("Beta"))
})
.collect();
for releases_new_installed in [
releases_gm_new_installed,
releases_beta_new_installed,
releases_regular_new_installed,
] {
if should_ask && releases_new_installed.len() == 2 {
let answer_uninstall = prompt_yesno("Would you like to move the former Xcode release to the trash?")?;
if answer_uninstall {
let _ = ctx
.run_type()
.execute(&xcodes)
.args([
"uninstall",
releases_new_installed.iter().next().cloned().unwrap_or_default(),
])
.status_checked();
}
}
}
Ok(())
}
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
let xcodes = require("xcodes")?;
if releases_filtered
.last()
.map(|s| !s.contains("(Installed)"))
.unwrap_or(true)
&& !releases_filtered.is_empty()
{
println!(
"New Xcode release detected: {}",
releases_filtered.last().cloned().unwrap_or_default()
);
if should_ask {
let answer_install = prompt_yesno("Would you like to install it?")?;
if answer_install {
let _ = ctx
.run_type()
.execute(xcodes)
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
.status_checked();
}
println!();
}
}
Ok(())
}

View File

@@ -1,23 +1,33 @@
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO};
use color_eyre::eyre::Result;
use std::path::PathBuf;
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Update");
ctx.run_type()
.execute(sudo)
.args(&["/usr/sbin/sysupgrade", "-n"])
.args(["/usr/sbin/sysupgrade", "-n"])
.status_checked()
}
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Packages");
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
.args(["/usr/sbin/pkg_delete", "-ac"])
.status_checked()?;
}
ctx.run_type()
.execute(sudo)
.args(&["/usr/sbin/pkg_add", "-u"])
.status_checked()
.args(["/usr/sbin/pkg_add", "-u"])
.status_checked()?;
Ok(())
}

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39.20240415.0 (IoT Edition)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39.20240415.0 (IoT Edition)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
HOME_URL="https://fedoraproject.org/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f39/system-administrators-guide/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-11-12
VARIANT="IoT Edition"
VARIANT_ID=iot
OSTREE_VERSION='39.20240415.0'

View File

@@ -0,0 +1,23 @@
NAME="Fedora Linux"
VERSION="39.20240105.0 (Kinoite)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://kinoite.fedoraproject.org"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-11-12
VARIANT="Kinoite"
VARIANT_ID=kinoite
OSTREE_VERSION='39.20240105.0'

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Onyx)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Onyx)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/onyx/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Onyx"
VARIANT_ID=onyx

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Sericea)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Sericea)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/sericea/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Sericea"
VARIANT_ID=sericea

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Silverblue)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://silverblue.fedoraproject.org"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Silverblue"
VARIANT_ID=silverblue

View File

@@ -0,0 +1,23 @@
NAME="Fedora Linux"
VERSION="40.20240426.0 (Sway Atomic)"
ID=fedora
VERSION_ID=40
VERSION_CODENAME=""
PLATFORM_ID="platform:f40"
PRETTY_NAME="Fedora Linux 40.20240426.0 (Sway Atomic)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:40"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/atomic-desktops/sway/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=40
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=40
SUPPORT_END=2025-05-13
VARIANT="Sway Atomic"
VARIANT_ID=sway-atomic
OSTREE_VERSION='40.20240426.0'

View File

@@ -0,0 +1,23 @@
NAME="Nobara Linux"
VERSION="39 (GNOME Edition)"
ID=nobara
ID_LIKE="rhel centos fedora"
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Nobara Linux 39 (GNOME Edition)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=nobara-logo-icon
CPE_NAME="cpe:/o:nobaraproject:nobara:39"
DEFAULT_HOSTNAME="nobara"
HOME_URL="https://nobaraproject.org/"
DOCUMENTATION_URL="https://www.nobaraproject.org/"
SUPPORT_URL="https://www.nobaraproject.org/"
BUG_REPORT_URL="https://gitlab.com/gloriouseggroll/nobara-images"
REDHAT_BUGZILLA_PRODUCT="Nobara"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Nobara"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="GNOME Edition"
VARIANT_ID=gnome

View File

@@ -0,0 +1,5 @@
ID=wolfi
NAME="Wolfi"
PRETTY_NAME="Wolfi"
VERSION_ID="20230201"
HOME_URL="https://wolfi.dev"

View File

@@ -1,11 +1,15 @@
use std::ffi::OsStr;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::Component;
use std::path::PathBuf;
use std::process::Command;
use std::{env::var, path::Path};
use crate::command::CommandExt;
use crate::{Step, HOME_DIR};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use home;
use ini::Ini;
@@ -281,10 +285,15 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
let run_type = ctx.run_type();
variant.execute(run_type).arg("update").status_checked()?;
variant
.execute(run_type)
.args(["upgrade", "--ignore-pinned", "--formula"])
.status_checked()?;
let mut command = variant.execute(run_type);
command.args(["upgrade", "--formula"]);
if ctx.config().brew_fetch_head() {
command.arg("--fetch-HEAD");
}
command.status_checked()?;
if ctx.config().cleanup() {
variant.execute(run_type).arg("cleanup").status_checked()?;
@@ -324,6 +333,9 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
if ctx.config().brew_cask_greedy() {
brew_args.push("--greedy");
}
if ctx.config().brew_greedy_latest() {
brew_args.push("--greedy-latest");
}
}
variant.execute(run_type).args(&brew_args).status_checked()?;
@@ -365,23 +377,8 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
debug!("nix profile: {:?}", profile_path);
let manifest_json_path = profile_path.join("manifest.json");
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
#[allow(unused_mut)]
let mut should_self_upgrade = cfg!(target_os = "macos");
#[cfg(target_os = "linux")]
{
// We can't use `nix upgrade-nix` on NixOS.
if let Ok(Distribution::NixOS) = Distribution::detect() {
should_self_upgrade = false;
}
}
print_separator("Nix");
let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user);
#[cfg(target_os = "macos")]
{
if require("darwin-rebuild").is_ok() {
@@ -393,30 +390,12 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
}
let run_type = ctx.run_type();
let nix_args = ["--extra-experimental-features", "nix-command"];
if should_self_upgrade {
if multi_user {
ctx.execute_elevated(&nix, true)?
.args(nix_args)
.arg("upgrade-nix")
.status_checked()?;
} else {
run_type
.execute(&nix)
.args(nix_args)
.arg("upgrade-nix")
.status_checked()?;
}
}
run_type.execute(nix_channel).arg("--update").status_checked()?;
if Path::new(&manifest_json_path).exists() {
run_type
.execute(&nix)
.args(nix_args)
.execute(nix)
.args(nix_args())
.arg("profile")
.arg("upgrade")
.arg(".*")
@@ -432,6 +411,123 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
}
}
pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?;
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
#[allow(unused_mut)]
let mut should_self_upgrade = cfg!(target_os = "macos");
#[cfg(target_os = "linux")]
{
// We can't use `nix upgrade-nix` on NixOS.
if let Ok(Distribution::NixOS) = Distribution::detect() {
should_self_upgrade = false;
}
}
if !should_self_upgrade {
return Err(SkipStep(String::from(
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
))
.into());
}
if nix_profile_dir(&nix)?.is_none() {
return Err(SkipStep(String::from(
"`nix upgrade-nix` cannot be run when Nix is installed in a profile",
))
.into());
}
print_separator("Nix (self-upgrade)");
let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user);
let nix_args = nix_args();
if multi_user {
ctx.execute_elevated(&nix, true)?
.args(nix_args)
.arg("upgrade-nix")
.status_checked()
} else {
ctx.run_type()
.execute(&nix)
.args(nix_args)
.arg("upgrade-nix")
.status_checked()
}
}
/// If we try to `nix upgrade-nix` but Nix is installed with `nix profile`, we'll get a `does not
/// appear to be part of a Nix profile` error.
///
/// We duplicate some of the `nix` logic here to avoid this.
/// See: <https://github.com/NixOS/nix/blob/f0180487a0e4c0091b46cb1469c44144f5400240/src/nix/upgrade-nix.cc#L102-L139>
///
/// See: <https://github.com/NixOS/nix/issues/5473>
fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
// NOTE: `nix` uses the location of the `nix-env` binary for this but we're using the `nix`
// binary; should be the same.
let nix_bin_dir = nix.parent();
if nix_bin_dir.and_then(|p| p.file_name()) != Some(OsStr::new("bin")) {
debug!("Nix is not installed in a `bin` directory: {nix_bin_dir:?}");
return Ok(None);
}
let nix_dir = nix_bin_dir
.and_then(|bin_dir| bin_dir.parent())
.ok_or_else(|| eyre!("Unable to find Nix install directory from Nix binary {nix:?}"))?;
debug!("Found Nix in {nix_dir:?}");
let mut profile_dir = nix_dir.to_path_buf();
while profile_dir.is_symlink() {
profile_dir = profile_dir
.parent()
.ok_or_else(|| eyre!("Path has no parent: {profile_dir:?}"))?
.join(
profile_dir
.read_link()
.wrap_err_with(|| format!("Failed to read symlink {profile_dir:?}"))?,
);
// NOTE: `nix` uses a hand-rolled canonicalize function, Rust just uses `realpath`.
if profile_dir
.canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?
.components()
.any(|component| component == Component::Normal(OsStr::new("profiles")))
{
break;
}
}
debug!("Found Nix profile {profile_dir:?}");
let user_env = profile_dir
.canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
Ok(
if user_env
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.ends_with("user-environment"))
.unwrap_or(false)
{
Some(profile_dir)
} else {
None
},
)
}
fn nix_args() -> [&'static str; 2] {
["--extra-experimental-features", "nix-command"]
}
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
let yadm = require("yadm")?;
@@ -455,6 +551,19 @@ pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
let mise = require("mise")?;
print_separator("mise");
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
ctx.run_type()
.execute(&mise)
.args(["plugins", "update"])
.status_checked()
}
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
let home_manager = require("home-manager")?;
@@ -484,6 +593,25 @@ pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(pearl).arg("update").status_checked()
}
pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
let pyenv = require("pyenv")?;
print_separator("pyenv");
let pyenv_dir = var("PYENV_ROOT")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
if !pyenv_dir.exists() {
return Err(SkipStep("Pyenv is installed, but $PYENV_ROOT is not set correctly".to_string()).into());
}
if !pyenv_dir.join(".git").exists() {
return Err(SkipStep("pyenv is not a git repository".to_string()).into());
}
ctx.run_type().execute(pyenv).arg("update").status_checked()
}
pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
let bash = require("bash")?;
@@ -555,6 +683,24 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(bun).arg("upgrade").status_checked()
}
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?;
print_separator("Bun Packages");
let mut package_json: PathBuf = var("BUN_INSTALL")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
package_json.push("install/global/package.json");
if !package_json.exists() {
println!("No global packages installed");
return Ok(());
}
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
}
/// Update dotfiles with `rcm(7)`.
///
/// See: <https://github.com/thoughtbot/rcm>

View File

@@ -1,4 +1,3 @@
use std::convert::TryFrom;
use std::path::Path;
use std::{ffi::OsStr, process::Command};
@@ -8,10 +7,9 @@ use tracing::debug;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::steps::git::GitAction;
use crate::terminal::{print_separator, print_warning};
use crate::utils::{require, which};
use crate::{error::SkipStep, steps::git::Repositories};
use crate::{error::SkipStep, steps::git::RepoStep};
use crate::{powershell, Step};
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
@@ -43,11 +41,6 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
print_separator("winget");
if !ctx.config().enable_winget() {
print_warning("Winget is disabled by default. Enable it by setting enable_winget=true in the [windows] section in the configuration.");
return Err(SkipStep(String::from("Winget is disabled by default")).into());
}
ctx.run_type()
.execute(winget)
.args(["upgrade", "--all"])
@@ -208,20 +201,17 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
let powershell = powershell::Powershell::windows_powershell();
if powershell.supports_windows_update() {
print_separator("Windows Update");
return powershell.windows_update(ctx);
}
let usoclient = require("UsoClient")?;
print_separator("Windows Update");
println!("Running Windows Update. Check the control panel for progress.");
ctx.run_type()
.execute(&usoclient)
.arg("ScanInstallWait")
.status_checked()?;
ctx.run_type().execute(&usoclient).arg("StartInstall").status_checked()
if powershell.supports_windows_update() {
powershell.windows_update(ctx)
} else {
print_warning(
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.",
);
Err(SkipStep("USOClient not supported.".to_string()).into())
}
}
pub fn reboot() -> Result<()> {
@@ -230,7 +220,7 @@ pub fn reboot() -> Result<()> {
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
}
pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
let startup_dir = crate::WINDOWS_DIRS
.data_dir()
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
@@ -240,7 +230,7 @@ pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) {
debug!("Startup link: {:?}", lnk);
if let Some(path) = lnk.relative_path() {
git_repos.insert_if_repo(&startup_dir.join(path), GitAction::Pull);
git_repos.insert_if_repo(&startup_dir.join(path));
}
}
}

View File

@@ -111,7 +111,7 @@ impl Powershell {
"-NoProfile",
"-Command",
&format!(
"Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose",
"Start-Process powershell -Verb runAs -ArgumentList 'Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose'",
if ctx.config().accept_all_windows_updates() {
"-AcceptAll"
} else {

View File

@@ -18,7 +18,14 @@ use crate::{
use std::os::unix::process::CommandExt as _;
pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
let tpm = HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins").require()?;
let tpm = match env::var("TMUX_PLUGIN_MANAGER_PATH") {
// If `TMUX_PLUGIN_MANAGER_PATH` is set, search for
// `$TMUX_PLUGIN_MANAGER_PATH/bin/install_plugins/tpm/bin/update_plugins`
Ok(var) => PathBuf::from(var).join("bin/install_plugins/tpm/bin/update_plugins"),
// Otherwise, use the default location `~/.tmux/plugins/tpm/bin/update_plugins`
Err(_) => HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins"),
}
.require()?;
print_separator("tmux plugins");

View File

@@ -9,6 +9,11 @@ if exists(":AstroUpdate")
quitall
endif
if exists(":MasonUpdate")
echo "MasonUpdate"
MasonUpdate
endif
if exists(":NeoBundleUpdate")
echo "NeoBundle"
NeoBundleUpdate

View File

@@ -57,8 +57,8 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
let status = output.status;
if !status.success() || ctx.config().verbose() {
io::stdout().write(&output.stdout).ok();
io::stderr().write(&output.stderr).ok();
io::stdout().write_all(&output.stdout).ok();
io::stderr().write_all(&output.stderr).ok();
}
if !status.success() {

View File

@@ -8,10 +8,12 @@ use walkdir::WalkDir;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::git::Repositories;
use crate::git::RepoStep;
use crate::terminal::print_separator;
use crate::utils::{require, PathExt};
use crate::HOME_DIR;
use crate::XDG_DIRS;
use etcetera::base_strategy::BaseStrategy;
pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
@@ -117,12 +119,12 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
env::var("ZINIT_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| HOME_DIR.join(".zinit"))
.unwrap_or_else(|_| XDG_DIRS.data_dir().join("zinit"))
.require()?;
print_separator("zinit");
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),);
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display());
ctx.run_type()
.execute(zsh)
.args(["-i", "-c", cmd.as_str()])
@@ -137,7 +139,7 @@ pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
print_separator("zi");
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),);
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display());
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
}
@@ -176,21 +178,15 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
// children processes won't get it either, so we source the zshrc and set
// the ZSH variable for topgrade here.
if ctx.under_ssh() {
let zshrc_path = zshrc().require()?;
let output = Command::new("zsh")
.args([
"-c",
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
format!(
"source {} > /dev/null && export -p | grep ZSH > /dev/null && echo $ZSH",
zshrc_path.display()
)
.as_str(),
])
.output_checked_utf8()?;
let zsh_env = output.stdout.trim();
if !zsh_env.is_empty() {
env::set_var("ZSH", zsh_env);
let res_env_zsh = Command::new("zsh")
.args(["-ic", "print -rn -- ${ZSH:?}"])
.output_checked_utf8();
// this command will fail if `ZSH` is not set
if let Ok(output) = res_env_zsh {
let env_zsh = output.stdout;
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
env::set_var("ZSH", env_zsh);
}
}
@@ -223,19 +219,14 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
debug!("oh-my-zsh custom dir: {}", custom_dir.display());
let mut custom_repos = Repositories::new(ctx.git());
let mut custom_repos = RepoStep::try_new()?;
for entry in WalkDir::new(custom_dir).max_depth(2) {
let entry = entry?;
custom_repos.insert_if_repo(entry.path(), crate::steps::git::GitAction::Pull);
}
custom_repos.remove_from_pull(&oh_my_zsh.to_string_lossy());
if !custom_repos.pull_is_empty() {
println!("Pulling custom plugins and themes");
ctx.git().multi_pull(&custom_repos, ctx)?;
custom_repos.insert_if_repo(entry.path());
}
custom_repos.remove(&oh_my_zsh);
ctx.run_type()
.execute("zsh")
.arg(&oh_my_zsh.join("tools/upgrade.sh"))

View File

@@ -7,7 +7,6 @@ use std::process::Command;
use color_eyre::eyre::Result;
use tracing::{debug, error};
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::reload::{Handle, Layer};
use tracing_subscriber::util::SubscriberInitExt;
@@ -119,44 +118,13 @@ pub fn string_prepend_str(string: &mut String, s: &str) {
*string = new_string;
}
/* sys-info-rs
*
* Copyright (c) 2015 Siyu Wang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#[cfg(target_family = "unix")]
pub fn hostname() -> Result<String> {
use std::ffi;
extern crate libc;
unsafe {
let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
let mut buf = Vec::<u8>::with_capacity(buf_size + 1);
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf_size) < 0 {
return Err(SkipStep(format!("Failed to get hostname: {}", std::io::Error::last_os_error())).into());
}
let hostname_len = libc::strnlen(buf.as_ptr() as *const libc::c_char, buf_size);
buf.set_len(hostname_len);
Ok(ffi::CString::new(buf).unwrap().into_string().unwrap())
match nix::unistd::gethostname() {
Ok(os_str) => Ok(os_str
.into_string()
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?),
Err(e) => Err(e.into()),
}
}
@@ -270,10 +238,7 @@ pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Regi
.or_else(|_| EnvFilter::try_from_default_env())
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
let fmt_layer = fmt::layer()
.with_target(false)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.without_time();
let fmt_layer = fmt::layer().with_target(false).without_time();
let (filter, reload_handle) = Layer::new(env_filter);