Compare commits

...

58 Commits
v16.2.1 ... v17

Author SHA1 Message Date
GideonBear
5bd5f10f0c chore(deps): update clap-cargo to v0.18.x 2025-11-23 13:33:53 +01:00
GideonBear
bfaca61deb chore(deps): update base64ct, globset, ignore 2025-11-23 13:26:47 +01:00
GideonBear
b00c4c1503 chore: upgrade to edition 2024
Co-authored-by: Ehren Bendler <a5ehren@gmail.com>
2025-11-23 13:26:47 +01:00
GideonBear
54a722be21 chore(deb): update copyright
Co-authored-by: Ehren Bendler <a5ehren@gmail.com>
2025-11-23 13:26:47 +01:00
GideonBear
bf4555ed29 chore(deps): unpin toml
Co-authored-by: Ehren Bendler <a5ehren@gmail.com>
2025-11-23 13:26:47 +01:00
GideonBear
46eb74109e style: apply new clippy fixes 2025-11-23 13:26:47 +01:00
GideonBear
0bde798b4a chore(deps): bump etcetera to 0.11.0 2025-11-23 13:26:47 +01:00
GideonBear
ebb4448950 chore(deps): remove temporary transitive dependency pins 2025-11-23 13:26:46 +01:00
GideonBear
b2b51bc8d2 refactor: replace home dependency with std::env::home_dir 2025-11-23 13:26:06 +01:00
GideonBear
8840a273b4 chore!: bump MSRV to 1.87.0 2025-11-23 13:26:06 +01:00
Filip Czaplicki
50e55dea77 feat: add colors to --help/-h (#1553) 2025-11-23 13:15:50 +01:00
Rubin Bhandari
f7c9e42066 feat(mise): add support for parallel job configuration in mise (#1548)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-21 09:08:40 +01:00
Daniil Kulchenko
ef3ee7bea7 feat(brew): add Homebrew cask support for Linux (#1539) 2025-11-20 20:10:47 +01:00
renovate[bot]
8eb300c4fb chore(deps): update rust crate indexmap to v2.12.1 (#1550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 19:33:19 +01:00
renovate[bot]
b7b99a725c chore(deps): update actions/checkout action to v6 (#1551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 19:31:17 +01:00
Gideon
df090a89c4 docs: Add metadata to Python package for PyPI (#1549) 2025-11-20 19:25:56 +01:00
LILAY
856fa0ed5b docs(installation): update copr repo info in readme (#1545) 2025-11-20 17:28:39 +01:00
Rubin Bhandari
9bb5a680ac feat(mise): add mise configuration options for bump and interactive modes (#1546) 2025-11-20 17:28:13 +01:00
github-actions[bot]
2d40f7bdb3 chore: release v16.4.2 (#1544)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-20 14:23:57 +01:00
renovate[bot]
06b210d1c9 chore(deps): update dawidd6/action-homebrew-bump-formula action to v6 (#1543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 14:22:19 +01:00
github-actions[bot]
e0e714e7b5 chore: release v16.4.1 (#1542)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-20 10:09:17 +01:00
Gideon
dfb8342d8b refactor: refactor run_containers error handling (#1541) 2025-11-20 10:08:15 +01:00
github-actions[bot]
9d662e36a1 chore: release v16.4.0 (#1528)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-20 09:23:51 +01:00
Gideon
45159c29fe chore(deps): update clap, clap_builder, clap_complete (#1540) 2025-11-20 09:09:07 +01:00
renovate[bot]
a27c68a1dd chore(deps): update github/codeql-action action to v4.31.4 (#1531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 08:58:33 +01:00
Wang Bing-hua
cdee1c14d9 test(config): add custom commands order test (#1536) 2025-11-19 18:37:55 +01:00
Wang Bing-hua
8de6a36d86 fix(deps): restore custom commands order (#1535) 2025-11-19 16:38:54 +01:00
Gideon
b03a8d53bb refactor: make Config methods more consistent by utilizing #[derive(Default)] (#1534) 2025-11-19 15:00:18 +01:00
Gideon
b0510cdade docs(issue templates): use issue types (#1533) 2025-11-19 11:11:30 +01:00
John Holt
7945311b4b feat(os): add Origami Linux support (#1530)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-18 09:24:10 +01:00
renovate[bot]
75de4dfd3b chore(deps): lock file maintenance (#1505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 20:45:02 +01:00
renovate[bot]
6a838bbcb7 chore(deps): update actions/checkout digest to 93cb6ef (#1526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 20:44:03 +01:00
renovate[bot]
ab2bab8c9b chore(deps): update actions/checkout action to v5.0.1 (#1527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 20:43:49 +01:00
Jason Stelzer
9a55278d32 feat(containers): add option to run system prune (#1523)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-17 20:39:54 +01:00
github-actions[bot]
f9735f3b31 chore: release v16.3.0 (#1465)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-17 16:39:39 +01:00
tranzystorekk
50be214b56 chore(license): switch license variant to GPL-3.0-or-later (#1518) 2025-11-16 19:08:49 +01:00
Ehren Bendler
9ec8e83f41 chore(deps): update some dependencies (#1512) 2025-11-15 17:05:48 +01:00
renovate[bot]
c70984d458 chore(deps): update github/codeql-action action to v4.31.3 (#1483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-15 13:02:36 +01:00
Gideon
a3503c0c70 refactor: remove unnecessary cfg-if dependency (#1509) 2025-11-15 12:47:54 +01:00
Gideon
ca2d16edfd ci(lint_pr): run on synchronize, and add zizmor ignore (#1508) 2025-11-15 10:56:42 +01:00
pre-commit-ci[bot]
722b1ad09e chore(pre-commit): autoupdate (#1464)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-15 10:55:29 +01:00
Gideon
743845a66b fix(elan): skip running elan update on elan >=4.0.0 (#1507) 2025-11-15 10:46:18 +01:00
Gideon
2594f4c0fb docs: improve issue templates (#1235)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-11-15 10:07:55 +01:00
Gideon
639d055f9a feat: print summary and run post commands when (q)uit is used (#1254) 2025-11-15 10:03:46 +01:00
Gideon
ea2ccdd69f chore(deps): bump mac-notification-sys, use main branch temporarily (#1506) 2025-11-15 08:58:37 +01:00
Gideon
84a50afa83 chore: replace Dependabot with Renovate (#1486) 2025-11-15 08:22:27 +01:00
renovate[bot]
b13c1bd2d7 chore(deps): lock file maintenance (#1481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: GideonBear <87426140+GideonBear@users.noreply.github.com>
2025-11-14 19:52:50 +01:00
Gideon
7749f41d56 fix(deps): Fix non-locked install on older version of Rust (#1485) 2025-11-14 19:33:56 +01:00
Gideon
593a2a33d9 fix(deps): Fix non-locked install on older version of Rust (#1482) 2025-11-14 19:20:12 +01:00
renovate[bot]
4f693aeaf3 chore(deps): pin dependencies (#1478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 18:35:35 +01:00
renovate[bot]
c3d34184d0 chore(deps): update actions/dependency-review-action action to v4.8.2 (#1479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 18:34:49 +01:00
Gideon
4aa224de87 chore: Add Renovate (#1477) 2025-11-14 18:23:17 +01:00
Andre Toerien
320b13c06b fix(bun): skip self-update if not installed via official script (#1476)
Co-authored-by: Steve Lau <stevelauc@outlook.com>
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-14 08:51:31 +01:00
Izzy Meyer
907d778c55 fix(openbsd): fix compilation on OpenBSD (#1473)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-12 18:38:41 +01:00
Stuart Reilly
f3fccb86c0 refactor: Replace main's self update with a proper step call (#1470)
Co-authored-by: Stuart Reilly <sreilly@scottlogic.com>
2025-11-11 17:31:04 +01:00
Andre Toerien
bb4afb71e9 feat: run pre_sudo before pre_commands (#1469) 2025-11-11 16:17:00 +01:00
Gideon
ec8d30f634 ci(release): Fix homebrew releases (#1468) 2025-11-11 16:12:50 +01:00
Rubin Bhandari
50d318641a feat(chezmoi): add exclude_encrypted config (#1453)
Co-authored-by: Gideon <87426140+GideonBear@users.noreply.github.com>
2025-11-10 21:12:40 +01:00
55 changed files with 1819 additions and 1408 deletions

View File

@@ -2,7 +2,7 @@
name: Bug report name: Bug report
about: Topgrade is misbehaving about: Topgrade is misbehaving
title: '' title: ''
labels: 'C-bug' type: Bug
assignees: '' assignees: ''
--- ---
@@ -14,52 +14,65 @@ Please make sure to
before filing a new one! before filing a new one!
Questions labeled with `Optional` can be skipped. Questions labeled with `Optional` can be skipped.
-->
<!--
If you're here to report about a "No asset found" error, please make sure that If you're here to report about a "No asset found" error, please make sure that
an hour has been passed since the last release was made. an hour has been passed since the last release was made.
--> -->
## Checklist
- [ ] I have searched the issue tracker for relevant or duplicate issues.
## Erroneous Behavior ## Erroneous Behavior
<!-- <!--
What actually happened? What actually happened?
--> -->
## Expected Behavior ## Expected Behavior
<!-- <!--
Describe the expected behavior Describe the expected behavior.
--> -->
## Steps to reproduce ## Steps to reproduce
<!-- <!--
A minimal example to reproduce the issue A minimal example to reproduce the issue.
--> -->
## Possible Cause (Optional) ## Possible Cause (Optional)
<!-- <!--
If you know the possible cause of the issue, please tell us. If you know the possible cause of the issue, please tell us.
--> -->
## Problem persists without calling from topgrade ## Problem persists without calling from topgrade
<!-- <!--
Execute the erroneous command directly to see if the problem persists Execute the erroneous command directly to see if the problem persists.
--> -->
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
## Did you run topgrade through `Remote Execution` ## Ran through `Remote Execution`
<!--
Did you run topgrade through `Remote Execution`?
-->
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
If yes, does the issue still occur when you run topgrade directly in your If yes, does the issue still occur when you run topgrade directly in your
remote host remote host?
- [ ] Yes - [ ] Yes
- [ ] No - [ ] No
## Configuration file (Optional) ## Configuration file (Optional)
<!-- <!--
Paste your configuration file inside the code block if you think this issue is Paste your configuration file inside the code block if you think this issue is
related to configuration. related to configuration.
@@ -70,6 +83,7 @@ related to configuration.
``` ```
## Additional Details ## Additional Details
- Operation System/Version - Operation System/Version
<!-- For example, Fedora Linux 38 --> <!-- For example, Fedora Linux 38 -->
@@ -82,6 +96,7 @@ related to configuration.
- Topgrade version (`topgrade -V`) - Topgrade version (`topgrade -V`)
## Verbose Output (`topgrade -v`) ## Verbose Output (`topgrade -v`)
<!-- <!--
Paste the verbose output into the pre-tags Paste the verbose output into the pre-tags
--> -->

View File

@@ -1,21 +1,15 @@
--- ---
name: Feature request name: General feature request
about: Can you please support...? about: Suggest a general feature, or feature within an already existing step
title: '' title: ''
labels: 'C-feature request' type: Feature
assignees: '' assignees: ''
--- ---
## I want to suggest a new step ## Checklist
* Which tool is this about? Where is its repository? - [ ] I have searched the issue tracker for relevant or duplicate issues.
* 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 ## I want to suggest some general feature
@@ -25,3 +19,5 @@ Topgrade should...
<!-- Assuming that someone else implements the feature, <!-- Assuming that someone else implements the feature,
please state if you know how to test it from a side branch of Topgrade. --> please state if you know how to test it from a side branch of Topgrade. -->
- [ ] I am able and willing to implement this feature myself

30
.github/ISSUE_TEMPLATE/step_request.md vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: New step request
about: Suggest a new step/package manager to update
title: ''
type: Feature
labels: request step
assignees: ''
---
## Checklist
- [ ] I have searched the issue tracker for relevant or duplicate issues.
## 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?
* 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?
## More information
<!-- Assuming that someone else implements the step,
please state if you know how to test it from a side branch of Topgrade. -->
- [ ] I am able and willing to implement this step myself

View File

@@ -1,24 +0,0 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: cargo
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "06:00"
timezone: "UTC"
versioning-strategy: increase
labels: ["dependencies", "cargo"]
commit-message:
prefix: "deps(cargo)"
include: "scope"
groups:
cargo-minor-patch:
update-types: ["minor", "patch"]

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -24,7 +24,7 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -32,6 +32,6 @@ jobs:
uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16 uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16
- name: Upload DevSkim scan results to GitHub Security tab - name: Upload DevSkim scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4.31.2 uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with: with:
sarif_file: devskim-results.sarif sarif_file: devskim-results.sarif

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -135,7 +135,7 @@ jobs:
matrix_target: ${{ matrix.target }} matrix_target: ${{ matrix.target }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false

View File

@@ -33,7 +33,7 @@ jobs:
env: env:
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -137,7 +137,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate artifact attestations - name: Generate artifact attestations
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: assets/* subject-path: assets/*
@@ -169,7 +169,7 @@ jobs:
matrix_target: ${{ matrix.target }} matrix_target: ${{ matrix.target }}
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -284,7 +284,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate artifact attestations - name: Generate artifact attestations
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: assets/* subject-path: assets/*

View File

@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2

View File

@@ -1,11 +1,12 @@
name: 'Lint PR' name: 'Lint PR'
on: on:
pull_request_target: pull_request_target: # zizmor: ignore[dangerous-triggers] this is the only way, and we're not running user code
types: types:
- opened - opened
- edited - edited
- reopened - reopened
- synchronize
jobs: jobs:
main: main:
@@ -14,6 +15,6 @@ jobs:
permissions: permissions:
pull-requests: read pull-requests: read
steps: steps:
- uses: amannn/action-semantic-pull-request@v6.1.1 - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -17,7 +17,7 @@ jobs:
id-token: write # For trusted publishing id-token: write # For trusted publishing
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
@@ -25,7 +25,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Run release-plz - name: Run release-plz
id: release-plz id: release-plz
uses: release-plz/action@v0.5 uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
with: with:
command: release command: release
env: env:
@@ -53,14 +53,14 @@ jobs:
cancel-in-progress: false cancel-in-progress: false
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Run release-plz - name: Run release-plz
uses: release-plz/action@v0.5 uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
with: with:
command: release-pr command: release-pr
env: env:

View File

@@ -12,10 +12,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Bump formulae - name: Bump formulae
uses: dawidd6/action-homebrew-bump-formula@3428a0601bba3173ec0bdcc945be23fa27aa4c31 # v5 uses: dawidd6/action-homebrew-bump-formula@c5ddc585e75f0f750a8b4f610688b4bec9e80915 # v6
with: with:
# Custom GitHub access token with only the 'public_repo' scope enabled # Custom GitHub access token with only the 'public_repo' scope enabled
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}} token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
formula: topgrade formula: topgrade
tag: ${{ github.event.client_payload.tag }} tag: ${{ github.event.client_payload.tag }}
org: topgrade-rs # We cannot use an org because org forks cannot give push access to maintainers, which Homebrew requires.
# org: topgrade-rs

View File

@@ -15,7 +15,7 @@ jobs:
matrix: matrix:
target: [x86_64, x86, aarch64] target: [x86_64, x86, aarch64]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -26,7 +26,7 @@ jobs:
args: --release --out dist args: --release --out dist
manylinux: auto manylinux: auto
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-linux-${{ matrix.target }} name: wheels-linux-${{ matrix.target }}
path: dist path: dist
@@ -37,7 +37,7 @@ jobs:
matrix: matrix:
target: [x64, x86] target: [x64, x86]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
target: ${{ matrix.target }} target: ${{ matrix.target }}
args: --release --out dist args: --release --out dist
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-windows-${{ matrix.target }} name: wheels-windows-${{ matrix.target }}
path: dist path: dist
@@ -58,7 +58,7 @@ jobs:
matrix: matrix:
target: [x86_64, aarch64] target: [x86_64, aarch64]
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -68,7 +68,7 @@ jobs:
target: ${{ matrix.target }} target: ${{ matrix.target }}
args: --release --out dist args: --release --out dist
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-macos-${{ matrix.target }} name: wheels-macos-${{ matrix.target }}
path: dist path: dist
@@ -76,7 +76,7 @@ jobs:
sdist: sdist:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -86,7 +86,7 @@ jobs:
command: sdist command: sdist
args: --out dist args: --out dist
- name: Upload sdist - name: Upload sdist
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: wheels-sdist name: wheels-sdist
path: dist path: dist
@@ -103,10 +103,10 @@ jobs:
# Used to generate artifact attestation # Used to generate artifact attestation
attestations: write attestations: write
steps: steps:
- uses: actions/download-artifact@v6.0.0 - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
- name: Generate artifact attestation - name: Generate artifact attestation
uses: actions/attest-build-provenance@v3.0.0 uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with: with:
subject-path: 'wheels-*/*' subject-path: 'wheels-*/*'

View File

@@ -36,7 +36,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@v5.0.0 uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with: with:
persist-credentials: false persist-credentials: false
@@ -63,7 +63,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@v5.0.0 uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@@ -71,6 +71,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v4.31.2 uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/gitleaks/gitleaks - repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0 rev: v8.29.0
hooks: hooks:
- id: gitleaks - id: gitleaks
@@ -16,7 +16,7 @@ repos:
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.38.1 rev: v1.39.2
hooks: hooks:
- id: typos - id: typos

View File

@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [16.4.2](https://github.com/topgrade-rs/topgrade/compare/v16.4.1...v16.4.2) - 2025-11-20
### Other
- *(deps)* update dawidd6/action-homebrew-bump-formula action to v6 ([#1543](https://github.com/topgrade-rs/topgrade/pull/1543))
## [16.4.1](https://github.com/topgrade-rs/topgrade/compare/v16.4.0...v16.4.1) - 2025-11-20
### Other
- refactor run_containers error handling ([#1541](https://github.com/topgrade-rs/topgrade/pull/1541))
## [16.4.0](https://github.com/topgrade-rs/topgrade/compare/v16.3.0...v16.4.0) - 2025-11-20
### Added
- *(os)* add Origami Linux support ([#1530](https://github.com/topgrade-rs/topgrade/pull/1530))
- *(containers)* add option to run `system prune` ([#1523](https://github.com/topgrade-rs/topgrade/pull/1523))
### Fixed
- *(deps)* restore custom commands order ([#1535](https://github.com/topgrade-rs/topgrade/pull/1535))
### Other
- *(deps)* update clap, clap_builder, clap_complete ([#1540](https://github.com/topgrade-rs/topgrade/pull/1540))
- *(deps)* update github/codeql-action action to v4.31.4 ([#1531](https://github.com/topgrade-rs/topgrade/pull/1531))
- *(config)* add custom commands order test ([#1536](https://github.com/topgrade-rs/topgrade/pull/1536))
- make Config methods more consistent by utilizing `#[derive(Default)]` ([#1534](https://github.com/topgrade-rs/topgrade/pull/1534))
- *(issue templates)* use issue types ([#1533](https://github.com/topgrade-rs/topgrade/pull/1533))
- *(deps)* lock file maintenance ([#1505](https://github.com/topgrade-rs/topgrade/pull/1505))
- *(deps)* update actions/checkout digest to 93cb6ef ([#1526](https://github.com/topgrade-rs/topgrade/pull/1526))
- *(deps)* update actions/checkout action to v5.0.1 ([#1527](https://github.com/topgrade-rs/topgrade/pull/1527))
## [16.3.0](https://github.com/topgrade-rs/topgrade/compare/v16.2.1...v16.3.0) - 2025-11-16
### Added
- print summary and run post commands when (q)uit is used ([#1254](https://github.com/topgrade-rs/topgrade/pull/1254))
- run pre_sudo before pre_commands ([#1469](https://github.com/topgrade-rs/topgrade/pull/1469))
- *(chezmoi)* add `exclude_encrypted` config ([#1453](https://github.com/topgrade-rs/topgrade/pull/1453))
### Fixed
- *(elan)* skip running elan update on elan >=4.0.0 ([#1507](https://github.com/topgrade-rs/topgrade/pull/1507))
- *(deps)* Fix non-locked install on older version of Rust ([#1485](https://github.com/topgrade-rs/topgrade/pull/1485))
- *(deps)* Fix non-locked install on older version of Rust ([#1482](https://github.com/topgrade-rs/topgrade/pull/1482))
- *(bun)* skip self-update if not installed via official script ([#1476](https://github.com/topgrade-rs/topgrade/pull/1476))
- *(openbsd)* fix compilation on OpenBSD ([#1473](https://github.com/topgrade-rs/topgrade/pull/1473))
### Other
- *(license)* switch license variant to GPL-3.0-or-later ([#1518](https://github.com/topgrade-rs/topgrade/pull/1518))
- *(deps)* update some dependencies ([#1512](https://github.com/topgrade-rs/topgrade/pull/1512))
- *(deps)* update github/codeql-action action to v4.31.3 ([#1483](https://github.com/topgrade-rs/topgrade/pull/1483))
- remove unnecessary cfg-if dependency ([#1509](https://github.com/topgrade-rs/topgrade/pull/1509))
- *(lint_pr)* run on synchronize, and add zizmor ignore ([#1508](https://github.com/topgrade-rs/topgrade/pull/1508))
- *(pre-commit)* autoupdate ([#1464](https://github.com/topgrade-rs/topgrade/pull/1464))
- improve issue templates ([#1235](https://github.com/topgrade-rs/topgrade/pull/1235))
- *(deps)* bump mac-notification-sys, use main branch temporarily ([#1506](https://github.com/topgrade-rs/topgrade/pull/1506))
- *(deps)* lock file maintenance ([#1481](https://github.com/topgrade-rs/topgrade/pull/1481))
- *(deps)* pin dependencies ([#1478](https://github.com/topgrade-rs/topgrade/pull/1478))
- *(deps)* update actions/dependency-review-action action to v4.8.2 ([#1479](https://github.com/topgrade-rs/topgrade/pull/1479))
- Add Renovate ([#1477](https://github.com/topgrade-rs/topgrade/pull/1477))
- Replace main's self update with a proper step call ([#1470](https://github.com/topgrade-rs/topgrade/pull/1470))
- *(release)* Fix homebrew releases ([#1468](https://github.com/topgrade-rs/topgrade/pull/1468))
## [16.2.1](https://github.com/topgrade-rs/topgrade/compare/v16.2.0...v16.2.1) - 2025-11-10 ## [16.2.1](https://github.com/topgrade-rs/topgrade/compare/v16.2.0...v16.2.1) - 2025-11-10
### Fixed ### Fixed

2251
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,37 +3,35 @@ name = "topgrade"
description = "Upgrade all the things" description = "Upgrade all the things"
categories = ["os"] categories = ["os"]
keywords = ["upgrade", "update"] keywords = ["upgrade", "update"]
license = "GPL-3.0" license = "GPL-3.0-or-later"
repository = "https://github.com/topgrade-rs/topgrade" repository = "https://github.com/topgrade-rs/topgrade"
rust-version = "1.84.1" rust-version = "1.87.0"
version = "16.2.1" version = "16.4.2"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"] authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"] exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
edition = "2021" edition = "2024"
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
home = "~0.5" etcetera = "~0.11.0"
etcetera = "~0.8"
serde = { version = "~1.0", features = ["derive"] } serde = { version = "~1.0", features = ["derive"] }
toml = "0.8" toml = { version = "~0.9.8", features = ["preserve_order"] }
which_crate = { version = "~6.0", package = "which" } which_crate = { version = "~8.0", package = "which" }
shellexpand = "~3.1" shellexpand = "~3.1"
clap = { version = "~4.5", features = ["cargo", "derive"] } clap = { version = "~4.5", features = ["cargo", "derive"] }
clap_complete = "~4.5" clap_complete = "~4.5"
clap_mangen = "~0.2" clap_mangen = "~0.2"
walkdir = "~2.5" walkdir = "~2.5"
console = "~0.15" console = "~0.16"
chrono = "~0.4" chrono = "~0.4"
glob = "~0.3" glob = "~0.3"
strum = { version = "~0.26", features = ["derive"] } strum = { version = "~0.27", features = ["derive"] }
thiserror = "~1.0" thiserror = "~2.0"
tempfile = "~3.10" tempfile = "~3.23"
cfg-if = "~1.0" tokio = { version = "~1.48", features = ["process", "rt-multi-thread"] }
tokio = { version = "~1.47", features = ["process", "rt-multi-thread"] }
futures = "~0.3" futures = "~0.3"
regex = "~1.10" regex = "~1.12"
semver = "~1.0" semver = "~1.0"
shell-words = "~1.1" shell-words = "~1.1"
color-eyre = "~0.6" color-eyre = "~0.6"
@@ -48,6 +46,10 @@ sys-locale = "0.3.1"
jetbrains-toolbox-updater = "5.0.0" jetbrains-toolbox-updater = "5.0.0"
indexmap = { version = "2.9.0", features = ["serde"] } indexmap = { version = "2.9.0", features = ["serde"] }
serde_json = "1.0.145" serde_json = "1.0.145"
clap-cargo = "0.18.0"
[patch.crates-io]
mac-notification-sys = { git = "https://github.com/h4llow3En/mac-notification-sys" }
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }] assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
@@ -58,7 +60,7 @@ git = "*"
[package.metadata.deb] [package.metadata.deb]
name = "topgrade" name = "topgrade"
maintainer = "Chris Gelatt <kreeblah@gmail.com>" maintainer = "Chris Gelatt <kreeblah@gmail.com>"
copyright = "2024, Topgrade Team" copyright = "2025, Topgrade Team"
license-file = ["LICENSE", "0"] license-file = ["LICENSE", "0"]
depends = "$auto" depends = "$auto"
extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them." extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them."
@@ -77,14 +79,14 @@ assets = [
] ]
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "~0.29", features = ["hostname", "signal", "user"] } nix = { version = "~0.30", features = ["hostname", "signal", "user"] }
rust-ini = "~0.21" rust-ini = "~0.21"
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] } self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
is_elevated = "~0.1" is_elevated = "~0.1"
parselnk = "~0.1" parselnk = "~0.1"
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] } self_update_crate = { version = "~0.42", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
windows = { version = "~0.62", features = ["Win32_System_Console"] } windows = { version = "~0.62", features = ["Win32_System_Console"] }
windows-registry = "~0.6" windows-registry = "~0.6"

View File

@@ -3,10 +3,10 @@
<img alt="Topgrade" src="doc/topgrade_transparent.png" width="850px"> <img alt="Topgrade" src="doc/topgrade_transparent.png" width="850px">
</h1> </h1>
<a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a> <a href="https://github.com/topgrade-rs/topgrade/releases"><img alt="GitHub Release" src="https://img.shields.io/github/release/topgrade-rs/topgrade.svg"></a>
<a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a> <a href="https://crates.io/crates/topgrade"><img alt="crates.io" src="https://img.shields.io/crates/v/topgrade.svg"></a>
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a> <a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a> <a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
<img alt="Demo" src="doc/topgrade_demo.gif"> <img alt="Demo" src="doc/topgrade_demo.gif">
</div> </div>
@@ -37,6 +37,8 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- Windows ([Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)): [ - Windows ([Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)): [
`winget install --id=topgrade-rs.topgrade -e`](https://winstall.app/apps/topgrade-rs.topgrade) `winget install --id=topgrade-rs.topgrade -e`](https://winstall.app/apps/topgrade-rs.topgrade)
- macOS or Linux ([Homebrew](https://brew.sh/)): [`brew install topgrade`](https://formulae.brew.sh/formula/topgrade) - macOS or Linux ([Homebrew](https://brew.sh/)): [`brew install topgrade`](https://formulae.brew.sh/formula/topgrade)
- Fedora/RHEL/AlmaLinux/CentOS-Stream ([Copr](https://copr.fedorainfracloud.org/)): [
`sudo dnf copr enable lilay/topgrade && sudo dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
### Community-maintained ### Community-maintained
@@ -46,8 +48,6 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
`scoop bucket add main && scoop install main/topgrade`](https://scoop.sh/#/apps?q=topgrade) `scoop bucket add main && scoop install main/topgrade`](https://scoop.sh/#/apps?q=topgrade)
- macOS ([MacPorts](https://www.macports.org/)): [ - macOS ([MacPorts](https://www.macports.org/)): [
`sudo port install topgrade`](https://ports.macports.org/port/topgrade/) `sudo port install topgrade`](https://ports.macports.org/port/topgrade/)
- Fedora ([Copr](https://copr.fedorainfracloud.org/)): [
`dnf copr enable lilay/topgrade && dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
- NixOS or Nix (nixpkgs): [topgrade](https://search.nixos.org/packages?show=topgrade) - NixOS or Nix (nixpkgs): [topgrade](https://search.nixos.org/packages?show=topgrade)
- Void Linux: [`sudo xbps-install -S topgrade`](https://voidlinux.org/packages/?arch=x86_64&q=topgrade) - Void Linux: [`sudo xbps-install -S topgrade`](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)

View File

@@ -286,6 +286,25 @@
# winget_use_sudo = true # winget_use_sudo = true
[chezmoi]
# Exclude encrypted files from update
# (default: false)
# exclude_encrypted = false
[mise]
# Upgrades to the latest version available, bumping the version in mise.toml
# (default: false)
# bump = false
# Number of jobs to run in parallel
# (default: 4)
# jobs = 4
# Run interactively
# (default: false)
# interactive = false
[npm] [npm]
# Use sudo if the NPM directory isn't owned by the current user # Use sudo if the NPM directory isn't owned by the current user
# use_sudo = true # use_sudo = true
@@ -337,6 +356,10 @@
# Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman") # Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman")
# runtime = "podman" # runtime = "podman"
# Run 'docker system prune' to clean up unused containers, networks, and build cache
# (default: false)
# system_prune = false
[lensfun] [lensfun]
# If disabled, Topgrade invokes `lensfunupdatedata` without root privilege, # If disabled, Topgrade invokes `lensfunupdatedata` without root privilege,
# then the update will be only available to you. Otherwise, `sudo` is required, # then the update will be only available to you. Otherwise, `sudo` is required,

View File

@@ -694,6 +694,14 @@ _version: 2
zh_CN: "不是专用的 macOS brew" zh_CN: "不是专用的 macOS brew"
zh_TW: "不是專門的 macOS brew" zh_TW: "不是專門的 macOS brew"
de: "Kein angepasstes Brew für macOS" de: "Kein angepasstes Brew für macOS"
"Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found {version})":
en: "Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found %{version})"
lt: "Homebrew cask palaikymas Linux sistemoje reikalauja Homebrew 4.5.0 arba naujesnes versijos (rasta %{version})"
es: "El soporte de cask de Homebrew en Linux requiere Homebrew 4.5.0 o posterior (encontrado %{version})"
fr: "Le support de cask Homebrew sur Linux nécessite Homebrew 4.5.0 ou supérieur (trouvé %{version})"
zh_CN: "Linux 上的 Homebrew cask 支持需要 Homebrew 4.5.0 或更高版本(找到 %{version}"
zh_TW: "Linux 上的 Homebrew cask 支援需要 Homebrew 4.5.0 或更高版本(找到 %{version}"
de: "Homebrew-Cask-Unterstützung unter Linux erfordert Homebrew 4.5.0 oder höher (gefunden %{version})"
"Guix Pull Failed, Skipping": "Guix Pull Failed, Skipping":
en: "Guix Pull Failed, Skipping" en: "Guix Pull Failed, Skipping"
lt: "Guix traukti nepavyko, praleidžiama" lt: "Guix traukti nepavyko, praleidžiama"
@@ -1306,14 +1314,6 @@ _version: 2
zh_CN: "Windows 更新" zh_CN: "Windows 更新"
zh_TW: "Windows 更新" zh_TW: "Windows 更新"
de: "Windows-Update" de: "Windows-Update"
"Checking if /etc/motd contains -current or -beta":
en: "Checking if /etc/motd contains -current or -beta"
lt: "Tikrinimas, jei /etc/motd yra -current arba -beta"
es: "Comprobación de si /etc/motd contiene -current o -beta"
fr: "Vérification si /etc/motd contient -current ou -beta"
zh_CN: "检查 /etc/motd 是否包含 -current 或 -beta"
zh_TW: "檢查 /etc/motd 是否包含 -current 或 -beta"
de: "Überprüfen, ob /etc/motd -current oder -beta enthält"
"Microsoft Store": "Microsoft Store":
en: "Microsoft Store" en: "Microsoft Store"
lt: "Microsoft parduotuvė" lt: "Microsoft parduotuvė"

View File

@@ -5,13 +5,20 @@ build-backend = "maturin"
[project] [project]
name = "topgrade" name = "topgrade"
dynamic = ["version"] dynamic = ["version"]
description = "Upgrade all the things"
readme = "README.md"
license = "GPL-3.0-or-later"
requires-python = ">=3.7" requires-python = ">=3.7"
classifiers = [ classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Rust", "Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
] ]
urls.bugs = "https://github.com/topgrade-rs/topgrade/issues"
urls.homepage = "https://github.com/topgrade-rs/topgrade"
[tool.maturin] [tool.maturin]
bindings = "bin" bindings = "bin"

10
renovate.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
":semanticCommits"
],
"lockFileMaintenance": {
"enabled": true
}
}

View File

@@ -1,2 +1,2 @@
[toolchain] [toolchain]
channel = "1.84.1" channel = "1.87.0"

View File

@@ -4,17 +4,17 @@
//! 1. The Topgrade being executed is a new major release //! 1. The Topgrade being executed is a new major release
//! 2. This is the first launch of that major release //! 2. This is the first launch of that major release
use crate::terminal::print_separator;
#[cfg(windows)] #[cfg(windows)]
use crate::WINDOWS_DIRS; use crate::WINDOWS_DIRS;
#[cfg(unix)] #[cfg(unix)]
use crate::XDG_DIRS; use crate::XDG_DIRS;
use crate::terminal::print_separator;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t; use rust_i18n::t;
use std::{ use std::{
env::var, env::var,
fs::{read_to_string, OpenOptions}, fs::{OpenOptions, read_to_string},
io::Write, io::Write,
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,

View File

@@ -5,8 +5,8 @@ use std::process::Child;
use std::process::{Command, ExitStatus, Output}; use std::process::{Command, ExitStatus, Output};
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::eyre;
use crate::error::TopgradeError; use crate::error::TopgradeError;

View File

@@ -1,6 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::fs::{write, File}; use std::fs::{File, write};
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
@@ -60,6 +60,7 @@ pub struct Containers {
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)] #[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
ignored_containers: Option<Vec<String>>, ignored_containers: Option<Vec<String>>,
runtime: Option<ContainerRuntime>, runtime: Option<ContainerRuntime>,
system_prune: Option<bool>,
} }
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
@@ -165,6 +166,22 @@ pub struct Deno {
version: Option<String>, version: Option<String>,
} }
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Chezmoi {
exclude_encrypted: Option<bool>,
}
#[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct Mise {
bump: Option<bool>,
interactive: Option<bool>,
jobs: Option<u32>,
}
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
@@ -196,9 +213,10 @@ pub struct Brew {
fetch_head: Option<bool>, fetch_head: Option<bool>,
} }
#[derive(Debug, Deserialize, Clone, Copy)] #[derive(Debug, Deserialize, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ArchPackageManager { pub enum ArchPackageManager {
#[default]
Autodetect, Autodetect,
Aura, Aura,
GarudaUpdate, GarudaUpdate,
@@ -210,9 +228,10 @@ pub enum ArchPackageManager {
Yay, Yay,
} }
#[derive(Clone, Copy, Debug, Deserialize)] #[derive(Clone, Copy, Debug, Deserialize, Default)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ContainerRuntime { pub enum ContainerRuntime {
#[default] // defaults to a popular choice
Docker, Docker,
Podman, Podman,
} }
@@ -350,10 +369,11 @@ pub struct Misc {
show_distribution_summary: Option<bool>, show_distribution_summary: Option<bool>,
} }
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)] #[derive(Clone, Copy, Debug, Deserialize, ValueEnum, Default)]
#[clap(rename_all = "snake_case")] #[clap(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum TmuxSessionMode { pub enum TmuxSessionMode {
#[default]
AttachIfNotInSession, AttachIfNotInSession,
AttachAlways, AttachAlways,
} }
@@ -457,6 +477,12 @@ pub struct ConfigFile {
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)] #[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
npm: Option<NPM>, npm: Option<NPM>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
chezmoi: Option<Chezmoi>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
mise: Option<Mise>,
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)] #[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
yarn: Option<Yarn>, yarn: Option<Yarn>,
@@ -703,7 +729,7 @@ impl ConfigFile {
// TODO: i18n of clap currently not easily possible. Waiting for https://github.com/clap-rs/clap/issues/380 // TODO: i18n of clap currently not easily possible. Waiting for https://github.com/clap-rs/clap/issues/380
// Tracking issue for i18n: https://github.com/topgrade-rs/topgrade/issues/859 // Tracking issue for i18n: https://github.com/topgrade-rs/topgrade/issues/859
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "topgrade", version)] #[command(name = "topgrade", version, styles = clap_cargo::style::CLAP_STYLING)]
pub struct CommandLineArgs { pub struct CommandLineArgs {
/// Edit the configuration file /// Edit the configuration file
#[arg(long = "edit-config")] #[arg(long = "edit-config")]
@@ -945,7 +971,16 @@ impl Config {
.containers .containers
.as_ref() .as_ref()
.and_then(|containers| containers.runtime) .and_then(|containers| containers.runtime)
.unwrap_or(ContainerRuntime::Docker) // defaults to a popular choice .unwrap_or_default()
}
/// Whether to run system prune for containers.
pub fn containers_system_prune(&self) -> bool {
self.config_file
.containers
.as_ref()
.and_then(|containers| containers.system_prune)
.unwrap_or(false)
} }
/// Tell whether the specified step should run. /// Tell whether the specified step should run.
@@ -1017,7 +1052,7 @@ impl Config {
.misc .misc
.as_ref() .as_ref()
.and_then(|misc| misc.tmux_session_mode) .and_then(|misc| misc.tmux_session_mode)
.unwrap_or(TmuxSessionMode::AttachIfNotInSession) .unwrap_or_default()
} }
/// Tell whether we should perform cleanup steps. /// Tell whether we should perform cleanup steps.
@@ -1271,7 +1306,7 @@ impl Config {
.vim .vim
.as_ref() .as_ref()
.and_then(|c| c.force_plug_update) .and_then(|c| c.force_plug_update)
.unwrap_or_default() .unwrap_or(false)
} }
/// Whether to send a desktop notification at the beginning of every step /// Whether to send a desktop notification at the beginning of every step
@@ -1344,7 +1379,7 @@ impl Config {
.linux .linux
.as_ref() .as_ref()
.and_then(|s| s.arch_package_manager) .and_then(|s| s.arch_package_manager)
.unwrap_or(ArchPackageManager::Autodetect) .unwrap_or_default()
} }
/// Extra yay arguments /// Extra yay arguments
@@ -1597,8 +1632,7 @@ impl Config {
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command) self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
} }
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the /// If `true`, `sudo -v` should be called to cache credentials at the start of the run
/// start of the session (and not in the middle).
pub fn pre_sudo(&self) -> bool { pub fn pre_sudo(&self) -> bool {
self.config_file self.config_file
.misc .misc
@@ -1774,6 +1808,34 @@ impl Config {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn chezmoi_exclude_encrypted(&self) -> bool {
self.config_file
.chezmoi
.as_ref()
.and_then(|chezmoi| chezmoi.exclude_encrypted)
.unwrap_or(false)
}
pub fn mise_bump(&self) -> bool {
self.config_file
.mise
.as_ref()
.and_then(|mise| mise.bump)
.unwrap_or(false)
}
pub fn mise_jobs(&self) -> u32 {
self.config_file.mise.as_ref().and_then(|mise| mise.jobs).unwrap_or(4)
}
pub fn mise_interactive(&self) -> bool {
self.config_file
.mise
.as_ref()
.and_then(|mise| mise.interactive)
.unwrap_or(false)
}
pub fn vscode_profile(&self) -> Option<&str> { pub fn vscode_profile(&self) -> Option<&str> {
let vscode_cfg = self.config_file.vscode.as_ref()?; let vscode_cfg = self.config_file.vscode.as_ref()?;
let profile = vscode_cfg.profile.as_ref()?; let profile = vscode_cfg.profile.as_ref()?;
@@ -1862,4 +1924,24 @@ mod test {
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]); config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname")); assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
} }
/// Ensure that custom commands are stored in insertion order.
#[test]
fn test_custom_commands_order() {
let toml_str = r#"
[commands]
z = "cmd_z"
y = "cmd_y"
x = "cmd_x"
"#;
let order: Vec<_> = toml::from_str::<ConfigFile>(toml_str)
.expect("toml parse error")
.commands
.expect("commands field missing")
.keys()
.cloned()
.collect();
assert_eq!(order, vec!["z", "y", "x"]);
}
} }

View File

@@ -1,6 +1,6 @@
//! SIGINT handling in Unix systems. //! SIGINT handling in Unix systems.
use crate::ctrlc::interrupted::set_interrupted; use crate::ctrlc::interrupted::set_interrupted;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, Signal, sigaction};
/// Handle SIGINT. Set the interruption flag. /// Handle SIGINT. Set the interruption flag.
extern "C" fn handle_sigint(_: i32) { extern "C" fn handle_sigint(_: i32) {

View File

@@ -1,8 +1,8 @@
//! A stub for Ctrl + C handling. //! A stub for Ctrl + C handling.
use crate::ctrlc::interrupted::set_interrupted; use crate::ctrlc::interrupted::set_interrupted;
use tracing::error; use tracing::error;
use windows::Win32::System::Console::{CTRL_C_EVENT, SetConsoleCtrlHandler};
use windows::core::BOOL; use windows::core::BOOL;
use windows::Win32::System::Console::{SetConsoleCtrlHandler, CTRL_C_EVENT};
extern "system" fn handler(ctrl_type: u32) -> BOOL { extern "system" fn handler(ctrl_type: u32) -> BOOL {
match ctrl_type { match ctrl_type {

View File

@@ -7,7 +7,7 @@ use std::process::{Child, Command, ExitStatus, Output};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t; use rust_i18n::t;
use tracing::{debug, enabled, Level}; use tracing::{Level, debug, enabled};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::DryRun; use crate::error::DryRun;

View File

@@ -1,6 +1,7 @@
#![allow(clippy::cognitive_complexity)] #![allow(clippy::cognitive_complexity)]
use std::env; use std::env;
use std::env::home_dir;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; use std::process::exit;
@@ -8,11 +9,10 @@ use std::time::Duration;
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file}; use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
use clap::CommandFactory; use clap::CommandFactory;
use clap::{crate_version, Parser}; use clap::{Parser, crate_version};
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use console::Key; use console::Key;
use etcetera::base_strategy::BaseStrategy;
#[cfg(windows)] #[cfg(windows)]
use etcetera::base_strategy::Windows; use etcetera::base_strategy::Windows;
#[cfg(unix)] #[cfg(unix)]
@@ -49,7 +49,7 @@ mod sudo;
mod terminal; mod terminal;
mod utils; mod utils;
pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home::home_dir().expect("No home directory")); pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home_dir().expect("No home directory"));
#[cfg(unix)] #[cfg(unix)]
pub(crate) static XDG_DIRS: LazyLock<Xdg> = LazyLock::new(|| Xdg::new().expect("No home directory")); pub(crate) static XDG_DIRS: LazyLock<Xdg> = LazyLock::new(|| Xdg::new().expect("No home directory"));
@@ -99,7 +99,7 @@ fn run() -> Result<()> {
let mut parts = env.split('='); let mut parts = env.split('=');
let var = parts.next().unwrap(); let var = parts.next().unwrap();
let value = parts.next().unwrap(); let value = parts.next().unwrap();
env::set_var(var, value); unsafe { env::set_var(var, value) };
} }
if opt.edit_config() { if opt.edit_config() {
@@ -187,17 +187,7 @@ fn run() -> Result<()> {
} }
} }
// Self-Update step, this will execute only if: step::Step::SelfUpdate.run(&mut runner, &ctx)?;
// 1. the `self-update` feature is enabled
// 2. it is not disabled from configuration (env var/CLI opt/file)
#[cfg(feature = "self-update")]
{
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
if should_self_update {
runner.execute(step::Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
}
}
#[cfg(windows)] #[cfg(windows)]
let _self_rename = if config.self_rename() { let _self_rename = if config.self_rename() {
@@ -206,20 +196,32 @@ fn run() -> Result<()> {
None None
}; };
if let Some(commands) = config.pre_commands() {
for (name, command) in commands {
generic::run_custom_command(name, command, &ctx)?;
}
}
if config.pre_sudo() { if config.pre_sudo() {
if let Some(sudo) = ctx.sudo() { if let Some(sudo) = ctx.sudo() {
sudo.elevate(&ctx)?; sudo.elevate(&ctx)?;
} }
} }
if let Some(commands) = config.pre_commands() {
for (name, command) in commands {
generic::run_custom_command(name, command, &ctx)?;
}
}
for step in step::default_steps() { for step in step::default_steps() {
step.run(&mut runner, &ctx)? match step.run(&mut runner, &ctx) {
Ok(()) => (),
Err(error)
if error
.downcast_ref::<io::Error>()
.is_some_and(|e| e.kind() == io::ErrorKind::Interrupted) =>
{
println!();
debug!("Interrupted (possibly with 'q' during retry prompt). Printing summary.");
break;
}
Err(error) => return Err(error),
}
} }
let mut failed = false; let mut failed = false;
@@ -320,11 +322,7 @@ fn run() -> Result<()> {
); );
} }
if failed { if failed { Err(StepFailed.into()) } else { Ok(()) }
Err(StepFailed.into())
} else {
Ok(())
}
} }
fn main() { fn main() {

View File

@@ -1,14 +1,15 @@
use color_eyre::eyre::Result; use color_eyre::eyre::{Result, WrapErr};
use rust_i18n::t; use rust_i18n::t;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Debug; use std::fmt::Debug;
use std::io;
use tracing::debug; use tracing::debug;
use crate::ctrlc; use crate::ctrlc;
use crate::error::{DryRun, MissingSudo, SkipStep}; use crate::error::{DryRun, MissingSudo, SkipStep};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::step::Step; use crate::step::Step;
use crate::terminal::{print_error, print_warning, should_retry}; use crate::terminal::{ShouldRetry, print_error, print_warning, should_retry};
pub enum StepResult { pub enum StepResult {
Success, Success,
@@ -98,12 +99,13 @@ impl<'a> Runner<'a> {
let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure); let should_ask = interrupted || !(self.ctx.config().no_retry() || ignore_failure);
let should_retry = if should_ask { let should_retry = if should_ask {
print_error(&key, format!("{e:?}")); print_error(&key, format!("{e:?}"));
should_retry(interrupted, key.as_ref())? should_retry(key.as_ref())?
} else { } else {
false ShouldRetry::No
}; };
if !should_retry { match should_retry {
ShouldRetry::No | ShouldRetry::Quit => {
self.push_result( self.push_result(
key, key,
if ignore_failure { if ignore_failure {
@@ -112,8 +114,14 @@ impl<'a> Runner<'a> {
StepResult::Failure StepResult::Failure
}, },
); );
if let ShouldRetry::Quit = should_retry {
return Err(io::Error::from(io::ErrorKind::Interrupted))
.context("Quit from user input");
}
break; break;
} }
ShouldRetry::Yes => (),
}
} }
} }
} }

View File

@@ -1,14 +1,14 @@
use std::env; use std::env;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
use std::process::Command;
#[cfg(windows)] #[cfg(windows)]
use std::process::exit; use std::process::exit;
use std::process::Command;
use crate::step::Step; use crate::step::Step;
use color_eyre::eyre::Result;
#[cfg(unix)] #[cfg(unix)]
use color_eyre::eyre::bail; use color_eyre::eyre::bail;
use color_eyre::eyre::Result;
use rust_i18n::t; use rust_i18n::t;
use self_update_crate::backends::github::Update; use self_update_crate::backends::github::Update;
use self_update_crate::update::UpdateStatus; use self_update_crate::update::UpdateStatus;

View File

@@ -225,7 +225,7 @@ impl Step {
Bin => runner.execute(*self, "bin", || generic::bin_update(ctx))?, Bin => runner.execute(*self, "bin", || generic::bin_update(ctx))?,
Bob => runner.execute(*self, "Bob", || generic::run_bob(ctx))?, Bob => runner.execute(*self, "Bob", || generic::run_bob(ctx))?,
BrewCask => { BrewCask => {
#[cfg(target_os = "macos")] #[cfg(any(target_os = "linux", target_os = "macos"))]
runner.execute(*self, "Brew Cask", || unix::run_brew_cask(ctx, unix::BrewVariant::Path))?; runner.execute(*self, "Brew Cask", || unix::run_brew_cask(ctx, unix::BrewVariant::Path))?;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
runner.execute(*self, "Brew Cask (Intel)", || { runner.execute(*self, "Brew Cask (Intel)", || {
@@ -544,6 +544,9 @@ impl Step {
runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))? runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))?
} }
SelfUpdate => { SelfUpdate => {
// 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)
#[cfg(feature = "self-update")] #[cfg(feature = "self-update")]
{ {
if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() { if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() {
@@ -747,6 +750,7 @@ pub(crate) fn default_steps() -> Vec<Step> {
Restarts, Restarts,
Flatpak, Flatpak,
BrewFormula, BrewFormula,
BrewCask,
Lure, Lure,
Waydroid, Waydroid,
AutoCpufreq, AutoCpufreq,

View File

@@ -6,12 +6,12 @@ use std::process::Command;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::eyre::{eyre, OptionExt}; use color_eyre::eyre::{OptionExt, eyre};
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use wildmatch::WildMatch; use wildmatch::WildMatch;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{self, SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{execution_context::ExecutionContext, utils::require}; use crate::{execution_context::ExecutionContext, utils::require};
use rust_i18n::t; use rust_i18n::t;
@@ -185,7 +185,6 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
)); ));
} }
let mut success = true;
let containers = let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?; list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
debug!("Containers to inspect: {:?}", containers); debug!("Containers to inspect: {:?}", containers);
@@ -220,22 +219,21 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
continue; continue;
} }
success = false; return Err(e);
} }
} }
if ctx.config().cleanup() { if ctx.config().containers_system_prune() {
// Run system prune to clean up unused containers, networks, and build cache
ctx.execute(&crt)
.args(["system", "prune", "--force"])
.status_checked()?
// Only run `image prune` if we don't run `system prune`
} else if ctx.config().cleanup() {
// Remove dangling images // Remove dangling images
debug!("Removing dangling images"); debug!("Removing dangling images");
if let Err(e) = ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked() { ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked()?
error!("Removing dangling images failed: {}", e);
success = false;
}
} }
if success {
Ok(()) Ok(())
} else {
Err(eyre!(error::StepFailed))
}
} }

View File

@@ -10,7 +10,7 @@ use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::step::Step; use crate::step::Step;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, require_option, PathExt}; use crate::utils::{PathExt, require, require_option};
const EMACS_UPGRADE: &str = include_str!("emacs.el"); const EMACS_UPGRADE: &str = include_str!("emacs.el");
#[cfg(windows)] #[cfg(windows)]

View File

@@ -1,7 +1,7 @@
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::eyre::{eyre, OptionExt}; use color_eyre::eyre::{OptionExt, eyre};
use jetbrains_toolbox_updater::{find_jetbrains_toolbox, update_jetbrains_toolbox, FindError}; use jetbrains_toolbox_updater::{FindError, find_jetbrains_toolbox, update_jetbrains_toolbox};
use regex::bytes::Regex; use regex::bytes::Regex;
use rust_i18n::t; use rust_i18n::t;
use semver::Version; use semver::Version;
@@ -16,6 +16,7 @@ use std::{fs, io::Write};
use tempfile::tempfile_in; use tempfile::tempfile_in;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use crate::HOME_DIR;
use crate::command::{CommandExt, Utf8Output}; use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::ExecutorOutput; use crate::executor::ExecutorOutput;
@@ -23,8 +24,7 @@ use crate::output_changed_message;
use crate::step::Step; use crate::step::Step;
use crate::sudo::SudoExecuteOpts; use crate::sudo::SudoExecuteOpts;
use crate::terminal::{print_separator, shell}; use crate::terminal::{print_separator, shell};
use crate::utils::{check_is_python_2_or_shim, require, require_one, require_option, which, PathExt}; use crate::utils::{PathExt, check_is_python_2_or_shim, require, require_one, require_option, which};
use crate::HOME_DIR;
use crate::{ use crate::{
error::{SkipStep, StepFailed, TopgradeError}, error::{SkipStep, StepFailed, TopgradeError},
terminal::print_warning, terminal::print_warning,
@@ -65,7 +65,9 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists()); .or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
let Some(cargo_update) = cargo_update else { let Some(cargo_update) = cargo_update else {
let message = String::from("cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`"); let message = String::from(
"cargo-update isn't installed so Topgrade can't upgrade cargo packages.\nInstall cargo-update by running `cargo install cargo-update`",
);
print_warning(&message); print_warning(&message);
return Err(SkipStep(message).into()); return Err(SkipStep(message).into());
}; };
@@ -81,7 +83,9 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
if let Some(e) = cargo_cache { if let Some(e) = cargo_cache {
ctx.execute(e).args(["-a"]).status_checked()?; ctx.execute(e).args(["-a"]).status_checked()?;
} else { } else {
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`"); let message = String::from(
"cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`",
);
print_warning(message); print_warning(message);
} }
} }
@@ -282,6 +286,17 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
print_separator("elan"); print_separator("elan");
let version_output = ctx.execute(&elan).arg("--version").output_checked_utf8()?;
let version_string = version_output.stdout.split_whitespace().nth(1).ok_or_else(|| {
eyre!(output_changed_message!(
"elan --version",
"Expected version after 'elan '"
))
})?;
let version = Version::parse(version_string)
.wrap_err_with(|| output_changed_message!("elan --version", "Invalid version"))?;
debug!("Detected elan version as: {}", version);
let disabled_error_msg = "self-update is disabled"; let disabled_error_msg = "self-update is disabled";
let executor_output = ctx.execute(&elan).args(["self", "update"]).output()?; let executor_output = ctx.execute(&elan).args(["self", "update"]).output()?;
match executor_output { match executor_output {
@@ -310,7 +325,12 @@ pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
ExecutorOutput::Dry => { /* nothing needed because in a dry run */ } ExecutorOutput::Dry => { /* nothing needed because in a dry run */ }
} }
ctx.execute(&elan).arg("update").status_checked() // In elan 4.0.0, `elan update` was removed, as toolchains are now updated automatically
if version < Version::new(4, 0, 0) {
ctx.execute(&elan).arg("update").status_checked()?;
}
Ok(())
} }
pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> { pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
@@ -526,7 +546,7 @@ fn run_vscode_compatible(variant: VSCodeVariant, ctx: &ExecutionContext) -> Resu
return Err(eyre!(output_changed_message!( return Err(eyre!(output_changed_message!(
&format!("{bin_name} --version"), &format!("{bin_name} --version"),
"No first line" "No first line"
))) )));
} }
}; };
@@ -873,12 +893,11 @@ pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
cfg_if::cfg_if! { if cfg!(any(target_os = "linux", target_os = "android")) && !ctx.config().enable_tlmgr_linux() {
if #[cfg(any(target_os = "linux", target_os = "android"))] { return Err(SkipStep(String::from(
if !ctx.config().enable_tlmgr_linux() { "tlmgr must be explicitly enabled in the configuration to run in Android/Linux",
return Err(SkipStep(String::from("tlmgr must be explicitly enabled in the configuration to run in Android/Linux")).into()); ))
} .into());
}
} }
let tlmgr = require("tlmgr")?; let tlmgr = require("tlmgr")?;
@@ -916,9 +935,17 @@ pub fn run_chezmoi_update(ctx: &ExecutionContext) -> Result<()> {
let chezmoi = require("chezmoi")?; let chezmoi = require("chezmoi")?;
HOME_DIR.join(".local/share/chezmoi").require()?; HOME_DIR.join(".local/share/chezmoi").require()?;
let mut cmd = ctx.execute(chezmoi);
print_separator("chezmoi"); print_separator("chezmoi");
ctx.execute(chezmoi).arg("update").status_checked() cmd.arg("update");
if ctx.config().chezmoi_exclude_encrypted() {
cmd.arg("--exclude=encrypted");
}
cmd.status_checked()
} }
pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> {
@@ -975,25 +1002,21 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
print_separator(t!("Composer")); print_separator(t!("Composer"));
if ctx.config().composer_self_update() { if ctx.config().composer_self_update() {
cfg_if::cfg_if! { if cfg!(unix) {
if #[cfg(unix)] {
// If self-update fails without sudo then there's probably an update // If self-update fails without sudo then there's probably an update
let has_update = match ctx.execute(&composer).arg("self-update").output()? { let has_update = match ctx.execute(&composer).arg("self-update").output()? {
ExecutorOutput::Wet(output) => !output.status.success(), ExecutorOutput::Wet(output) => !output.status.success(),
_ => false _ => false,
}; };
if has_update { if has_update {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
sudo.execute(ctx, &composer)? sudo.execute(ctx, &composer)?.arg("self-update").status_checked()?;
.arg("self-update")
.status_checked()?;
} }
} else { } else {
ctx.execute(&composer).arg("self-update").status_checked()?; ctx.execute(&composer).arg("self-update").status_checked()?;
} }
} }
}
let output = ctx.execute(&composer).args(["global", "update"]).output()?; let output = ctx.execute(&composer).args(["global", "update"]).output()?;
if let ExecutorOutput::Wet(output) = output { if let ExecutorOutput::Wet(output) = output {
@@ -1220,11 +1243,7 @@ pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
}; };
} }
if success { if success { Ok(()) } else { Err(eyre!(StepFailed)) }
Ok(())
} else {
Err(eyre!(StepFailed))
}
} }
pub fn run_stew(ctx: &ExecutionContext) -> Result<()> { pub fn run_stew(ctx: &ExecutionContext) -> Result<()> {
@@ -1409,8 +1428,7 @@ pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
.map_err(|e| SkipStep(format!("Could not find interpreter for {}: {}", poetry.display(), e)))?; .map_err(|e| SkipStep(format!("Could not find interpreter for {}: {}", poetry.display(), e)))?;
debug!("poetry interpreter: {:?}, args: {:?}", interp, interp_args); debug!("poetry interpreter: {:?}, args: {:?}", interp, interp_args);
let check_official_install_script = let check_official_install_script = "import sys; from os import path; print('Y') if path.isfile(path.join(sys.prefix, 'poetry_env')) else print('N')";
"import sys; from os import path; print('Y') if path.isfile(path.join(sys.prefix, 'poetry_env')) else print('N')";
let mut command = Command::new(&interp); let mut command = Command::new(&interp);
if let Some(args) = interp_args { if let Some(args) = interp_args {
command.arg(args); command.arg(args);
@@ -1561,9 +1579,25 @@ pub fn run_zvm(ctx: &ExecutionContext) -> Result<()> {
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> { pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?; let bun = require("bun")?;
// From the official install script (both install.sh and install.ps1), Bun uses
// the path set in this variable as the install root, and its defaults to
// `$HOME/.bun`
//
// UNIX: https://bun.sh/install.sh
// Windows: https://bun.sh/install.ps1
let bun_install_env = env::var("BUN_INSTALL")
.map(PathBuf::from)
.unwrap_or(HOME_DIR.join(".bun"));
// If `bun` is a descendant of `bun_install_env`, then Bun is installed
// through the official script
if bun.is_descendant_of(&bun_install_env) {
print_separator("Bun"); print_separator("Bun");
ctx.execute(bun).arg("upgrade").status_checked() ctx.execute(bun).arg("upgrade").status_checked()
} else {
Err(SkipStep("Not installed through the official script".to_string()).into())
}
} }
pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> { pub fn run_zigup(ctx: &ExecutionContext) -> Result<()> {
@@ -1673,7 +1707,10 @@ fn run_jetbrains_ide_generic<const IS_JETBRAINS: bool>(ctx: &ExecutionContext, b
.code() .code()
.ok_or_eyre("Failed to get status code; was killed with signal")?; .ok_or_eyre("Failed to get status code; was killed with signal")?;
if status_code != 1 { if status_code != 1 {
return Err(eyre!("Expected status code 1 ('Only one instance of <IDE> can be run at a time.'), but found status code {}. Output: {output:?}", status_code)); return Err(eyre!(
"Expected status code 1 ('Only one instance of <IDE> can be run at a time.'), but found status code {}. Output: {output:?}",
status_code
));
} }
// Don't crash, but don't be silent either // Don't crash, but don't be silent either
warn!("{name} is already running, can't update it now."); warn!("{name} is already running, can't update it now.");

View File

@@ -4,10 +4,10 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio}; use std::process::{Command, Output, Stdio};
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::{Result, eyre};
use console::style; use console::style;
use futures::stream::{iter, FuturesUnordered, StreamExt}; use futures::stream::{FuturesUnordered, StreamExt, iter};
use glob::{glob_with, MatchOptions}; use glob::{MatchOptions, glob_with};
use tokio::process::Command as AsyncCommand; use tokio::process::Command as AsyncCommand;
use tokio::runtime; use tokio::runtime;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -17,8 +17,8 @@ use crate::execution_context::ExecutionContext;
use crate::step::Step; use crate::step::Step;
use crate::steps::emacs::Emacs; use crate::steps::emacs::Emacs;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{PathExt, require};
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR}; use crate::{HOME_DIR, error::SkipStep, terminal::print_warning};
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t; use rust_i18n::t;

View File

@@ -14,7 +14,7 @@ use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::terminal::{print_info, print_separator}; use crate::terminal::{print_info, print_separator};
use crate::utils::{require, PathExt}; use crate::utils::{PathExt, require};
use crate::{error::SkipStep, execution_context::ExecutionContext}; use crate::{error::SkipStep, execution_context::ExecutionContext};
enum NPMVariant { enum NPMVariant {
@@ -65,11 +65,7 @@ impl NPM {
/// If the “NPM” version is larger than 8.11.0, we use /// If the “NPM” version is larger than 8.11.0, we use
/// `--location=global`; otherwise, use `-g`. /// `--location=global`; otherwise, use `-g`.
fn global_location_arg(&self) -> &str { fn global_location_arg(&self) -> &str {
if self.is_npm_8() { if self.is_npm_8() { "--location=global" } else { "-g" }
"--location=global"
} else {
"-g"
}
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@@ -6,6 +6,7 @@ use ini::Ini;
use rust_i18n::t; use rust_i18n::t;
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::HOME_DIR;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -14,8 +15,7 @@ use crate::steps::generic::is_wsl;
use crate::steps::os::archlinux; use crate::steps::os::archlinux;
use crate::sudo::SudoExecuteOpts; use crate::sudo::SudoExecuteOpts;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require, require_one, which, PathExt}; use crate::utils::{PathExt, require, require_one, which};
use crate::HOME_DIR;
static OS_RELEASE_PATH: &str = "/etc/os-release"; static OS_RELEASE_PATH: &str = "/etc/os-release";
@@ -78,6 +78,8 @@ impl Distribution {
Some("neon") => Distribution::KDENeon, Some("neon") => Distribution::KDENeon,
Some("openmandriva") => Distribution::OpenMandriva, Some("openmandriva") => Distribution::OpenMandriva,
Some("pclinuxos") => Distribution::PCLinuxOS, Some("pclinuxos") => Distribution::PCLinuxOS,
Some(id) if id.starts_with("origami") => Distribution::FedoraImmutable,
_ => { _ => {
if let Some(name) = name { if let Some(name) = name {
if name.contains("Vanilla") { if name.contains("Vanilla") {
@@ -1341,4 +1343,11 @@ mod tests {
fn test_cachyos() { fn test_cachyos() {
test_template(include_str!("os_release/cachyos"), Distribution::Arch); test_template(include_str!("os_release/cachyos"), Distribution::Arch);
} }
#[test]
fn test_origami() {
test_template(include_str!("os_release/origami"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/origami-nvidia"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/origami-test"), Distribution::FedoraImmutable);
}
} }

View File

@@ -200,7 +200,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> { pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
let xcodes = require("xcodes")?; let xcodes = require("xcodes")?;
if releases_filtered.last().map_or(true, |s| !s.contains("(Installed)")) && !releases_filtered.is_empty() { if releases_filtered.last().is_none_or(|s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
println!( println!(
"{} {}", "{} {}",
t!("New Xcode release detected:"), t!("New Xcode release detected:"),

View File

@@ -1,20 +1,17 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t; use rust_i18n::t;
use std::fs; use std::fs;
use tracing::debug;
fn is_openbsd_current(ctx: &ExecutionContext) -> Result<bool> { fn is_openbsd_current() -> Result<bool> {
let motd_content = fs::read_to_string("/etc/motd")?; let motd_content = fs::read_to_string("/etc/motd")?;
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s)); let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
match ctx.config.run_type() {
RunType::Dry | RunType::Damp => { debug!("OpenBSD is -current/-beta: {is_current}");
println!("{}", t!("Checking if /etc/motd contains -current or -beta"));
}
RunType::Wet => {}
}
Ok(is_current) Ok(is_current)
} }
@@ -23,12 +20,7 @@ pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
let is_current = is_openbsd_current(ctx)?; let is_current = is_openbsd_current()?;
if ctx.config().dry_run() {
println!("{}", t!("Would upgrade the OpenBSD system"));
return Ok(());
}
if is_current { if is_current {
sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked() sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked()
@@ -42,7 +34,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = ctx.require_sudo()?; let sudo = ctx.require_sudo()?;
let is_current = is_openbsd_current(ctx)?; let is_current = is_openbsd_current()?;
if ctx.config().cleanup() { if ctx.config().cleanup() {
sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?; sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?;

View File

@@ -0,0 +1,23 @@
NAME="Origami Linux"
VERSION="43.20251117.0 (COSMIC Atomic)"
RELEASE_TYPE="stable"
ID="origami-linux"
VERSION_ID="43"
VERSION_CODENAME=""
PRETTY_NAME="Origami 折り紙"
ANSI_COLOR="0;38;2;60;110;180"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
DEFAULT_HOSTNAME="origami"
HOME_URL="https://origami.wf/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION="43"
SUPPORT_END="2026-12-02"
VARIANT="COSMIC Atomic"
VARIANT_ID="cosmic-atomic"
OSTREE_VERSION="43.20251117.0"

View File

@@ -0,0 +1,23 @@
NAME="Origami Linux Nvidia"
VERSION="43.20251117.0 (COSMIC Atomic)"
RELEASE_TYPE="stable"
ID="origami-linux-nvidia"
VERSION_ID="43"
VERSION_CODENAME=""
PRETTY_NAME="Origami 折り紙"
ANSI_COLOR="0;38;2;60;110;180"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
DEFAULT_HOSTNAME="origami"
HOME_URL="https://origami.wf/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION="43"
SUPPORT_END="2026-12-02"
VARIANT="COSMIC Atomic"
VARIANT_ID="cosmic-atomic"
OSTREE_VERSION="43.20251117.0"

View File

@@ -0,0 +1,23 @@
NAME="Origami Linux Test"
VERSION="43.20251117.0 (COSMIC Atomic)"
RELEASE_TYPE="stable"
ID="origami-linux-test"
VERSION_ID="43"
VERSION_CODENAME=""
PRETTY_NAME="Origami 折り紙"
ANSI_COLOR="0;38;2;60;110;180"
LOGO="fedora-logo-icon"
CPE_NAME="cpe:/o:fedoraproject:fedora:43"
DEFAULT_HOSTNAME="origami"
HOME_URL="https://origami.wf/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f43/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/groups/origami-linux/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION="43"
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION="43"
SUPPORT_END="2026-12-02"
VARIANT="COSMIC Atomic"
VARIANT_ID="cosmic-atomic"
OSTREE_VERSION="43.20251117.0"

View File

@@ -1,14 +1,14 @@
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::eyre::{eyre, OptionExt}; use color_eyre::eyre::{OptionExt, eyre};
use etcetera::BaseStrategy; use etcetera::BaseStrategy;
use home;
use ini::Ini; use ini::Ini;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
use regex::Regex; use regex::Regex;
use rust_i18n::t; use rust_i18n::t;
use semver::Version; use semver::Version;
use std::env::home_dir;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::io::Write; use std::io::Write;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
@@ -20,10 +20,10 @@ use std::{env::var, path::Path};
use std::{fs, io}; use std::{fs, io};
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::XDG_DIRS;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::sudo::SudoExecuteOpts; use crate::sudo::SudoExecuteOpts;
use crate::XDG_DIRS; use crate::{HOME_DIR, output_changed_message};
use crate::{output_changed_message, HOME_DIR};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use super::linux::Distribution; use super::linux::Distribution;
@@ -33,7 +33,7 @@ use crate::execution_context::ExecutionContext;
use crate::executor::Executor; use crate::executor::Executor;
use crate::step::Step; use crate::step::Step;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{PathExt, require};
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
const INTEL_BREW: &str = "/usr/local/bin/brew"; const INTEL_BREW: &str = "/usr/local/bin/brew";
@@ -81,7 +81,6 @@ impl BrewVariant {
/// Execute an "internal" brew command, i.e. one that should always be run /// Execute an "internal" brew command, i.e. one that should always be run
/// even when dry-running. Basically just a wrapper around [`Command::new`] /// even when dry-running. Basically just a wrapper around [`Command::new`]
/// that uses `arch` to run using the correct architecture if needed. /// that uses `arch` to run using the correct architecture if needed.
#[cfg(target_os = "macos")]
fn execute_internal(self) -> Command { fn execute_internal(self) -> Command {
match self { match self {
BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => { BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => {
@@ -365,12 +364,48 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
Ok(()) Ok(())
} }
#[cfg(target_os = "macos")] #[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
let binary_name = require(variant.binary_name())?; let binary_name = require(variant.binary_name())?;
#[cfg(target_os = "macos")]
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into()); return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
} }
#[cfg(target_os = "linux")]
{
// Homebrew cask support was added in version 4.5.0
let version_output = Command::new(&binary_name).arg("--version").output_checked_utf8()?;
let version_line = version_output
.stdout
.lines()
.next()
.ok_or_else(|| eyre!(output_changed_message!("brew --version", "no output lines")))?;
let version_str = version_line.split_whitespace().nth(1).ok_or_else(|| {
eyre!(output_changed_message!(
"brew --version",
"Expected version after 'Homebrew'"
))
})?;
let version = Version::parse(version_str)
.wrap_err_with(|| output_changed_message!("brew --version", "Invalid version"))?;
if version < Version::new(4, 5, 0) {
return Err(SkipStep(
t!(
"Homebrew cask support on Linux requires Homebrew 4.5.0 or later (found {version})",
version = version
)
.to_string(),
)
.into());
}
}
print_separator(format!("{} - Cask", variant.step_title())); print_separator(format!("{} - Cask", variant.step_title()));
let cask_upgrade_exists = variant let cask_upgrade_exists = variant
@@ -491,8 +526,8 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?; let nix = require("nix")?;
let nix_channel = require("nix-channel")?; let nix_channel = require("nix-channel")?;
let nix_env = require("nix-env")?; let nix_env = require("nix-env")?;
// TODO: Is None possible here? // TODO: Is None possible here? Should we use HOME_DIR instead?
let profile_path = match home::home_dir() { let profile_path = match home_dir() {
Some(home) => XDG_DIRS Some(home) => XDG_DIRS
.state_dir() .state_dir()
.map(|d| d.join("nix/profile")) .map(|d| d.join("nix/profile"))
@@ -824,7 +859,23 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
} }
} }
ctx.execute(&mise).arg("upgrade").status_checked() let mut cmd = ctx.execute(&mise);
cmd.arg("upgrade");
if ctx.config().mise_interactive() {
cmd.arg("--interactive");
}
if ctx.config().mise_bump() {
cmd.arg("--bump");
}
if ctx.config().mise_jobs() != 4 {
cmd.args(["--jobs", &ctx.config().mise_jobs().to_string()]);
}
cmd.status_checked()
} }
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> { pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {

View File

@@ -254,11 +254,7 @@ pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> {
} }
let ret_val = output.stdout.trim(); let ret_val = output.stdout.trim();
debug!("Command return value: {}", ret_val); debug!("Command return value: {}", ret_val);
if ret_val == "0" { if ret_val == "0" { Ok(()) } else { Err(()) }
Ok(())
} else {
Err(())
}
})?; })?;
println!( println!(
"{}", "{}",

View File

@@ -1,15 +1,15 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result;
#[cfg(windows)] #[cfg(windows)]
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Result;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal; use crate::terminal;
use crate::utils::{which, PathExt}; use crate::utils::{PathExt, which};
pub struct Powershell { pub struct Powershell {
path: PathBuf, path: PathBuf,

View File

@@ -2,20 +2,20 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::eyre::eyre;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::config::TmuxConfig; use crate::config::TmuxConfig;
use crate::config::TmuxSessionMode; use crate::config::TmuxSessionMode;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{HOME_DIR, XDG_DIRS};
use crate::{ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{which, PathExt}, utils::{PathExt, which},
}; };
use crate::{HOME_DIR, XDG_DIRS};
use rust_i18n::t; use rust_i18n::t;
#[cfg(unix)] #[cfg(unix)]

View File

@@ -1,6 +1,6 @@
use crate::HOME_DIR;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
@@ -8,7 +8,7 @@ use crate::executor::{Executor, ExecutorOutput};
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{require, PathExt}, utils::{PathExt, require},
}; };
use rust_i18n::t; use rust_i18n::t;
use std::path::PathBuf; use std::path::PathBuf;

View File

@@ -6,13 +6,13 @@ use color_eyre::eyre::Result;
use tracing::debug; use tracing::debug;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::HOME_DIR;
use crate::XDG_DIRS;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::git::RepoStep; use crate::git::RepoStep;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{PathExt, require};
use crate::HOME_DIR;
use crate::XDG_DIRS;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
pub fn run_zr(ctx: &ExecutionContext) -> Result<()> { pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
@@ -162,7 +162,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
if let Ok(output) = res_env_zsh { if let Ok(output) = res_env_zsh {
let env_zsh = output.stdout; let env_zsh = output.stdout;
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh); debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
env::set_var("ZSH", env_zsh); unsafe { env::set_var("ZSH", env_zsh) };
} }
} }

View File

@@ -4,10 +4,10 @@ use std::path::PathBuf;
#[cfg(windows)] #[cfg(windows)]
use color_eyre::eyre; use color_eyre::eyre;
#[cfg(windows)]
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
#[cfg(windows)]
use color_eyre::eyre::eyre;
use rust_i18n::t; use rust_i18n::t;
use serde::Deserialize; use serde::Deserialize;
use strum::Display; use strum::Display;

View File

@@ -8,7 +8,7 @@ use std::time::Duration;
use chrono::{Local, Timelike}; use chrono::{Local, Timelike};
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use console::{style, Key, Term}; use console::{Key, Term, style};
use notify_rust::{Notification, Timeout}; use notify_rust::{Notification, Timeout};
use rust_i18n::t; use rust_i18n::t;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -201,10 +201,11 @@ impl Terminal {
} }
} }
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn should_retry(&mut self, interrupted: bool, step_name: &str) -> eyre::Result<bool> { fn should_retry(&mut self, step_name: &str) -> eyre::Result<ShouldRetry> {
if self.width.is_none() { if self.width.is_none() {
return Ok(false); return Ok(ShouldRetry::No);
} }
if self.set_title { if self.set_title {
@@ -223,7 +224,7 @@ impl Terminal {
let answer = loop { let answer = loop {
match self.term.read_key() { match self.term.read_key() {
Ok(Key::Char('y' | 'Y')) => break Ok(true), Ok(Key::Char('y' | 'Y')) => break Ok(ShouldRetry::Yes),
Ok(Key::Char('s' | 'S')) => { Ok(Key::Char('s' | 'S')) => {
println!( println!(
"\n\n{}\n", "\n\n{}\n",
@@ -232,16 +233,16 @@ impl Terminal {
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } else {
break Ok(true); break Ok(ShouldRetry::Yes);
} }
} }
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(false), Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(ShouldRetry::No),
Err(e) => { Err(e) => {
error!("Error reading from terminal: {}", e); error!("Error reading from terminal: {}", e);
break Ok(false); break Ok(ShouldRetry::No);
} }
Ok(Key::Char('q' | 'Q')) => { Ok(Key::Char('q' | 'Q')) => {
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input") break Ok(ShouldRetry::Quit);
} }
_ => (), _ => (),
} }
@@ -257,14 +258,21 @@ impl Terminal {
} }
} }
#[derive(Clone, Copy)]
pub enum ShouldRetry {
Yes,
No,
Quit,
}
impl Default for Terminal { impl Default for Terminal {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> { pub fn should_retry(step_name: &str) -> eyre::Result<ShouldRetry> {
TERMINAL.lock().unwrap().should_retry(interrupted, step_name) TERMINAL.lock().unwrap().should_retry(step_name)
} }
pub fn print_separator<P: AsRef<str>>(message: P) { pub fn print_separator<P: AsRef<str>>(message: P) {

View File

@@ -11,8 +11,8 @@ use tracing::{debug, error};
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::reload::{Handle, Layer}; use tracing_subscriber::reload::{Handle, Layer};
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{fmt, Registry}; use tracing_subscriber::{EnvFilter, registry};
use tracing_subscriber::{registry, EnvFilter}; use tracing_subscriber::{Registry, fmt};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::config::DEFAULT_LOG_LEVEL; use crate::config::DEFAULT_LOG_LEVEL;
@@ -218,7 +218,7 @@ pub mod merge_strategies {
where where
T: Merge, T: Merge,
{ {
if let Some(ref mut left_inner) = left { if let Some(left_inner) = left {
if let Some(right_inner) = right { if let Some(right_inner) = right {
left_inner.merge(right_inner); left_inner.merge(right_inner);
} }
@@ -228,7 +228,7 @@ pub mod merge_strategies {
} }
pub fn commands_merge_opt(left: &mut Option<Commands>, right: Option<Commands>) { pub fn commands_merge_opt(left: &mut Option<Commands>, right: Option<Commands>) {
if let Some(ref mut left_inner) = left { if let Some(left_inner) = left {
if let Some(right_inner) = right { if let Some(right_inner) = right {
left_inner.extend(right_inner); left_inner.extend(right_inner);
} }