Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebcc9beee | ||
|
|
55e1bbf2b9 | ||
|
|
f2dfa1e475 | ||
|
|
fcd53e772a | ||
|
|
8b9d7ef8f3 | ||
|
|
d8406a8cfe | ||
|
|
4a9ef581e5 | ||
|
|
a52db1f261 | ||
|
|
8e16174ce7 | ||
|
|
c748bb5d7a | ||
|
|
3cc8f0d818 | ||
|
|
f96eeeda6b | ||
|
|
d1d8904376 | ||
|
|
3b329fe687 | ||
|
|
9eb1b4ac9f | ||
|
|
c4c0bd7383 | ||
|
|
1e9de5832d | ||
|
|
f2b17cdd9d | ||
|
|
7bfd6c2439 | ||
|
|
0e8d5f0266 | ||
|
|
32add8f046 | ||
|
|
f661f00277 | ||
|
|
2a1999fe20 | ||
|
|
4d66431aad | ||
|
|
767f0d91f4 | ||
|
|
a3428e3477 | ||
|
|
614131b7bf | ||
|
|
9b0681f3b8 | ||
|
|
ecf8fb7a47 | ||
|
|
04bfb45a97 | ||
|
|
d90ce30452 | ||
|
|
ab21600ca6 | ||
|
|
728ea26204 | ||
|
|
373cd3b3ae | ||
|
|
f4e0258b09 | ||
|
|
d50360a69a | ||
|
|
351922c81f | ||
|
|
9518f43866 | ||
|
|
2c1ce3d4e6 | ||
|
|
12116c3261 | ||
|
|
fbc84e8aa1 | ||
|
|
6dab1e4f37 | ||
|
|
650a143602 | ||
|
|
9b6027fe78 | ||
|
|
0e30e05ce8 | ||
|
|
eea952fa78 | ||
|
|
6071a1ee3b | ||
|
|
a801b7b9f4 | ||
|
|
c6e3f0ae0a | ||
|
|
a43b03d3db | ||
|
|
12b0fa57ad | ||
|
|
d9e304f0ef | ||
|
|
842b92cca7 | ||
|
|
485f0ec9c8 | ||
|
|
5e3b5fc9a7 | ||
|
|
7c63541cad | ||
|
|
238e089d74 | ||
|
|
8991bc9f62 | ||
|
|
7a3f3a8905 | ||
|
|
e4085e03eb | ||
|
|
4b0c366e5f | ||
|
|
ea97240d09 | ||
|
|
12de531abb | ||
|
|
c3876ce3bf | ||
|
|
cbbfc3a114 | ||
|
|
ad2bfc9abd | ||
|
|
528461412e | ||
|
|
64db679390 | ||
|
|
77a8b3b7d2 | ||
|
|
7007e76ab5 | ||
|
|
3c970063a9 | ||
|
|
b70830015e | ||
|
|
b43f2c8b3a | ||
|
|
c311da16f3 | ||
|
|
37608a338c | ||
|
|
b07288e674 | ||
|
|
707698faab | ||
|
|
2e70d132d0 | ||
|
|
30c5b31e21 | ||
|
|
77ff6cb714 | ||
|
|
ea13c51b7d | ||
|
|
3ed763b884 | ||
|
|
10e1e170b7 | ||
|
|
ffa62afc66 | ||
|
|
f794329913 | ||
|
|
f9a35c7661 | ||
|
|
ed496f3462 | ||
|
|
6accdae232 |
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,10 +8,14 @@ assignees: ''
|
||||
---
|
||||
|
||||
## I want to suggest a new step
|
||||
### Which tool is this about? Where is its repository?
|
||||
### Which operating systems are supported by this tool?
|
||||
### What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
### Which exact commands should Topgrade run?
|
||||
|
||||
* Which tool is this about? Where is its repository?
|
||||
* Which operating systems are supported by this tool?
|
||||
* What should Topgrade do to figure out if the tool needs to be invoked?
|
||||
* Which exact commands should Topgrade run?
|
||||
* Does it have a `--dry-run` option? i.e., print what should be done and exit
|
||||
* Does it need the user to confirm the execution? And does it provide a `--yes`
|
||||
option to skip this step?
|
||||
|
||||
## I want to suggest some general feature
|
||||
Topgrade should...
|
||||
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,14 +1,14 @@
|
||||
## Standards checklist:
|
||||
## What does this PR do
|
||||
|
||||
|
||||
## Standards checklist
|
||||
|
||||
- [ ] The PR title is descriptive.
|
||||
- [ ] I have read `CONTRIBUTING.md`
|
||||
- [ ] The code compiles (`cargo build`)
|
||||
- [ ] The code passes rustfmt (`cargo fmt`)
|
||||
- [ ] The code passes clippy (`cargo clippy`)
|
||||
- [ ] The code passes tests (`cargo test`)
|
||||
- [ ] *Optional:* I have tested the code myself
|
||||
|
||||
## For new steps
|
||||
|
||||
- [ ] *Optional:* Topgrade skips this step where needed
|
||||
- [ ] *Optional:* The `--dry-run` option works with this step
|
||||
- [ ] *Optional:* The `--yes` option works with this step if it is supported by
|
||||
|
||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Set update schedule for GitHub Actions
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: "weekly"
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Test Configuration File Creation
|
||||
name: Check config file creation if not exists
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,10 +12,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
./target/debug/topgrade --dry-run --only system;
|
||||
TOPGRADE_SKIP_BRKC_NOTIFY=true ./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
32
.github/workflows/check_security_vulnerability.yml
vendored
Normal file
32
.github/workflows/check_security_vulnerability.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Check Security Vulnerability
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: DevSkim
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
|
||||
- name: Upload DevSkim scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly-2022-08-03
|
||||
@@ -14,10 +14,10 @@ env:
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -42,32 +42,36 @@ jobs:
|
||||
- target: x86_64-linux-android
|
||||
target_name: Android
|
||||
use_cross: true
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-freebsd
|
||||
target_name: FreeBSD
|
||||
use_cross: true
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-unknown-linux-gnu
|
||||
target_name: Linux
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-apple-darwin
|
||||
target_name: macOS
|
||||
os: macos-11
|
||||
target_name: macOS-x86_64
|
||||
os: macos-13
|
||||
|
||||
- target: aarch64-apple-darwin
|
||||
target_name: macOS-aarch64
|
||||
os: macos-latest
|
||||
|
||||
- target: x86_64-unknown-netbsd
|
||||
target_name: NetBSD
|
||||
use_cross: true
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
|
||||
- target: x86_64-pc-windows-msvc
|
||||
target_name: Windows
|
||||
os: windows-2019
|
||||
os: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -84,8 +88,13 @@ jobs:
|
||||
if: matrix.use_cross == true
|
||||
run: curl -fL --retry 3 https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VER }}/cross-x86_64-unknown-linux-musl.tar.gz | tar vxz -C /usr/local/bin
|
||||
|
||||
- name: Run cargo check
|
||||
- name: Run cargo/cross check
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Run cargo clippy
|
||||
- name: Run cargo/cross clippy
|
||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
||||
|
||||
- name: Run cargo test
|
||||
# ONLY run test with cargo
|
||||
if: matrix.use_cross == false
|
||||
run: cargo test --locked --target ${{ matrix.target }}
|
||||
59
.github/workflows/code-coverage.yml
vendored
59
.github/workflows/code-coverage.yml
vendored
@@ -1,59 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
name: Test with Code Coverage
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
env:
|
||||
PROJECT_NAME_UNDERSCORE: topgrade
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort
|
||||
RUSTDOCFLAGS: -Cpanic=abort
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-dependencies
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
~/.cargo/bin
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('Cargo.lock') }}
|
||||
- name: Generate test result and coverage report
|
||||
run: |
|
||||
cargo install cargo2junit grcov;
|
||||
cargo test $CARGO_OPTIONS -- -Z unstable-options --format json | cargo2junit > results.xml;
|
||||
zip -0 ccov.zip `find . \( -name "$PROJECT_NAME_UNDERSCORE*.gc*" \) -print`;
|
||||
grcov ccov.zip -s . -t lcov --llvm --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info;
|
||||
- name: Upload test results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||
with:
|
||||
check_name: Test Results
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: results.xml
|
||||
- name: Upload to CodeCov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
# required for private repositories:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./lcov.info
|
||||
fail_ci_if_error: true
|
||||
@@ -13,40 +13,31 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Check format
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy (All features)
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-targets --locked --all-features -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run tests
|
||||
with:
|
||||
command: test
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Build
|
||||
with:
|
||||
command: build
|
||||
args: --release --all-features
|
||||
|
||||
- name: Check format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all-targets --locked -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cargo clippy --all-targets --locked --all-features -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cargo build --release --all-features
|
||||
|
||||
- name: Rename Release (Unix)
|
||||
run: |
|
||||
cargo install default-target
|
||||
@@ -59,6 +50,7 @@ jobs:
|
||||
ls .
|
||||
if: ${{ matrix.platform != 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Rename Release (Windows)
|
||||
run: |
|
||||
cargo install default-target
|
||||
@@ -71,7 +63,8 @@ jobs:
|
||||
ls .
|
||||
if: ${{ matrix.platform == 'windows-latest' }}
|
||||
shell: bash
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
68
.github/workflows/create_release_assets_cross.yml
vendored
Normal file
68
.github/workflows/create_release_assets_cross.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Publish release files for non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"armv7-unknown-linux-gnueabihf",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
"x86_64-unknown-freebsd",
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: install targets
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: install cross
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Check format
|
||||
run: cross fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run clippy (All features)
|
||||
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
|
||||
- name: Run tests
|
||||
run: cross test --target ${{matrix.target}}
|
||||
|
||||
- name: Build in Release profile with all features enabled
|
||||
run: cross build --release --all-features --target ${{matrix.target}}
|
||||
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: assets/*
|
||||
70
.github/workflows/release-cross.yml
vendored
70
.github/workflows/release-cross.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Publish release files for non-cd-native environments
|
||||
|
||||
on:
|
||||
# workflow_run:
|
||||
# workflows: ["Check SemVer compliance"]
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [ "aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf", "x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl", "x86_64-unknown-freebsd", ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Check format
|
||||
with:
|
||||
use-cross: true
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy
|
||||
with:
|
||||
command: clippy
|
||||
use-cross: true
|
||||
args: --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run clippy (All features)
|
||||
with:
|
||||
command: clippy
|
||||
use-cross: true
|
||||
args: --locked --all-features --target ${{matrix.target}} -- -D warnings
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Run tests
|
||||
with:
|
||||
command: test
|
||||
use-cross: true
|
||||
args: --target ${{matrix.target}}
|
||||
- uses: actions-rs/cargo@v1.0.1
|
||||
name: Build
|
||||
with:
|
||||
command: build
|
||||
use-cross: true
|
||||
args: --release --all-features --target ${{matrix.target}}
|
||||
- name: Rename Release
|
||||
run: |
|
||||
mkdir assets
|
||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
||||
mv target/${{matrix.target}}/release/topgrade assets
|
||||
cd assets
|
||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||
rm topgrade
|
||||
ls .
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: assets/*
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: katyo/publish-crates@v1
|
||||
- uses: katyo/publish-crates@v2
|
||||
with:
|
||||
dry-run: true
|
||||
check-repo: ${{ github.event_name == 'push' }}
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Cache Homebrew Bundler RubyGems
|
||||
id: cache
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
||||
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
||||
@@ -29,7 +29,8 @@ jobs:
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: brew install-bundler-gems
|
||||
- name: Bump formulae
|
||||
uses: Homebrew/actions/bump-formulae@master
|
||||
uses: Homebrew/actions/bump-packages@master
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Custom GitHub access token with only the 'public_repo' scope enabled
|
||||
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -69,14 +69,14 @@ jobs:
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
13
.github/workflows/release_to_winget.yml
vendored
Normal file
13
.github/workflows/release_to_winget.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Publish to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: topgrade-rs.topgrade
|
||||
max-versions-to-keep: 5 # keep only latest 5 versions
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,4 +1,20 @@
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
|
||||
/target
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Generic build outputs
|
||||
/build
|
||||
|
||||
# Specific for some languages like Rust
|
||||
/target
|
||||
|
||||
# LLVM profiling output
|
||||
*.profraw
|
||||
|
||||
# Backup files for any .rs files in the project
|
||||
**/*.rs.bk
|
||||
|
||||
38
.vscode/launch.json
vendored
38
.vscode/launch.json
vendored
@@ -1,38 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Topgrade",
|
||||
"console": "integratedTerminal",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=topgrade-rs",
|
||||
"--package=topgrade-rs"
|
||||
],
|
||||
"filter": {
|
||||
"name": "topgrade-rs",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"--only",
|
||||
"${input:step}",
|
||||
"-v"
|
||||
],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "promptString",
|
||||
"id": "step",
|
||||
"description": "step name",
|
||||
}
|
||||
]
|
||||
}
|
||||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "clippy",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test",
|
||||
"label": "rust: cargo clippy"
|
||||
}
|
||||
]
|
||||
}
|
||||
50
.vscode/topgrade.code-snippets
vendored
50
.vscode/topgrade.code-snippets
vendored
@@ -1,50 +0,0 @@
|
||||
{
|
||||
// Place your topgrade workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"Skip Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "skipstep",
|
||||
"body": [
|
||||
"return Err(SkipStep(format!(\"$1\")).into());"
|
||||
]
|
||||
},
|
||||
"Step": {
|
||||
"scope": "rust",
|
||||
"prefix": "step",
|
||||
"body": [
|
||||
"pub fn $1(ctx: &ExecutionContext) -> Result<()> {",
|
||||
" $0",
|
||||
" Ok(())",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"Require Binary": {
|
||||
"scope": "rust",
|
||||
"prefix": "req",
|
||||
"description": "Require a binary to be installed",
|
||||
"body": [
|
||||
"let ${1:binary} = require(\"${1:binary}\")?;"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"scope": "rust",
|
||||
"prefix": "macos",
|
||||
"body": [
|
||||
"#[cfg(target_os = \"macos\")]"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
1. In 13.0.0, we introduced a new feature, pushing git repos, now this feature
|
||||
has been removed as some users are not satisfied with it.
|
||||
# Git: Pull Repos
|
||||
|
||||
For configuration entries, the following ones are gone:
|
||||
1. The output of "Pulling <repository path>" has been moved behind the
|
||||
--verbose flag / [misc] configuration block.
|
||||
|
||||
```toml
|
||||
[git]
|
||||
pull_only_repos = []
|
||||
push_only_repos = []
|
||||
pull_arguments = ""
|
||||
push_arguments = ""
|
||||
```
|
||||
# Configuration
|
||||
|
||||
1. The `enable_winget` configuration entry in the `windows` section has been
|
||||
removed because it will not cause any issues and will be enabled by default.
|
||||
|
||||
@@ -103,8 +103,8 @@ and have some basic documentations guiding user how to use these options.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
If your PR introduces a breaking change, document it in `BREAKINGCHANGE_dev.md`,
|
||||
it should be written in Markdown and wrapped in 80, for example:
|
||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
|
||||
it should be written in Markdown and wrapped at 80, for example:
|
||||
|
||||
```md
|
||||
1. The configuration location has been updated to x.
|
||||
@@ -114,6 +114,8 @@ it should be written in Markdown and wrapped in 80, for example:
|
||||
3. ...
|
||||
```
|
||||
|
||||
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
|
||||
## Before you submit your PR
|
||||
|
||||
Make sure your patch passes the following tests on your host:
|
||||
|
||||
1870
Cargo.lock
generated
1870
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -5,9 +5,9 @@ categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
version = "14.0.0"
|
||||
version = "15.0.0"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHNAGES_dev.md"]
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||
edition = "2021"
|
||||
|
||||
readme = "README.md"
|
||||
@@ -22,24 +22,24 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
home = "~0.5"
|
||||
etcetera = "~0.8"
|
||||
once_cell = "~1.18"
|
||||
once_cell = "~1.19"
|
||||
serde = { version = "~1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
which_crate = { version = "~4.1", package = "which" }
|
||||
which_crate = { version = "~6.0", package = "which" }
|
||||
shellexpand = "~3.1"
|
||||
clap = { version = "~4.4", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.4"
|
||||
clap = { version = "~4.5", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.5"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.4"
|
||||
walkdir = "~2.5"
|
||||
console = "~0.15"
|
||||
lazy_static = "~1.4"
|
||||
chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.24", features = ["derive"] }
|
||||
strum = { version = "~0.26", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.8"
|
||||
tempfile = "~3.10"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.34", features = ["process", "rt-multi-thread"] }
|
||||
tokio = { version = "~1.38", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
regex = "~1.10"
|
||||
semver = "~1.0"
|
||||
@@ -49,7 +49,8 @@ tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
||||
merge = "~0.1"
|
||||
regex-split = "~0.1"
|
||||
notify-rust = "~4.10"
|
||||
notify-rust = "~4.11"
|
||||
wildmatch = "2.3.0"
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
@@ -61,12 +62,12 @@ git = "*"
|
||||
depends = "$auto,git"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "~0.27", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.19"
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.21"
|
||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||
winapi = "~0.3"
|
||||
parselnk = "~0.1"
|
||||
|
||||
|
||||
@@ -29,9 +29,12 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
|
||||
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
|
||||
- Windows: [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/topgrade.json)
|
||||
- Windows: [Scoop][scoop] or [Winget][winget]
|
||||
- PyPi: [pip](https://pypi.org/project/topgrade/)
|
||||
|
||||
[scoop]: https://scoop.sh/#/apps?q=topgrade
|
||||
[winget]: https://winstall.app/apps/topgrade-rs.topgrade
|
||||
|
||||
Other systems users can either use `cargo install` or the compiled binaries from the release page.
|
||||
The compiled binaries contain a self-upgrading feature.
|
||||
|
||||
|
||||
65
RELEASE_PROCEDURE.md
Normal file
65
RELEASE_PROCEDURE.md
Normal file
@@ -0,0 +1,65 @@
|
||||
> This document lists the steps that lead to a successful release of Topgrade.
|
||||
|
||||
1. Open a PR that:
|
||||
|
||||
> Here is an [Example PR](https://github.com/topgrade-rs/topgrade/pull/652)
|
||||
> that you can refer to.
|
||||
|
||||
1. bumps the version number.
|
||||
|
||||
> If there are breaking changes, the major version number should be increased.
|
||||
|
||||
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||
|
||||
```sh'
|
||||
$ cd topgrade
|
||||
$ cp BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
|
||||
$ touch BREAKINGCHANGES_dev.md
|
||||
```
|
||||
|
||||
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||
[breaking_changes]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES.md
|
||||
|
||||
2. Check and merge that PR.
|
||||
|
||||
3. Go to the [release](https://github.com/topgrade-rs/topgrade/releases) page
|
||||
and click the [Draft a new release button](https://github.com/topgrade-rs/topgrade/releases/new)
|
||||
|
||||
4. Write the release notes
|
||||
|
||||
We usually use GitHub's [Automatically generated release notes][auto_gen_release_notes]
|
||||
functionality to generate release notes, but you write your own one instead.
|
||||
|
||||
[auto_gen_release_notes]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
|
||||
|
||||
5. Attaching binaries
|
||||
|
||||
You don't need to do this as our CI will automatically do it for you,
|
||||
binaries for Linux, macOS and Windows will be created and attached.
|
||||
|
||||
And the CI will publish the new binary to:
|
||||
|
||||
1. AUR
|
||||
2. PyPi
|
||||
3. Homebrew (seems that this is not working correctly)
|
||||
4. Winget
|
||||
|
||||
6. Manually release it to Crates.io
|
||||
|
||||
> Yeah, this is unfortunate, our CI won't do this for us. We should probably add one.
|
||||
|
||||
1. `cd` to the Topgrade directory, make sure that it is the latest version
|
||||
(i.e., including the PR that bumps the version number).
|
||||
2. Set up your token with `cargo login`.
|
||||
3. Dry-run the publish `cargo publish --dry-run`.
|
||||
4. If step 3 works, then do the final release `cargo publish`.
|
||||
|
||||
> You can also take a look at the official tutorial [Publishing on crates.io][doc]
|
||||
>
|
||||
> [doc]: https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -103,9 +103,25 @@
|
||||
|
||||
|
||||
[brew]
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` exists, then use the `-a` option.
|
||||
# Otherwise, use the `--greedy` option.
|
||||
# greedy_cask = true
|
||||
|
||||
# For the BrewCask step
|
||||
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_latest` option.
|
||||
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
|
||||
# both of them, they won't clash with each other.
|
||||
# greedy_latest = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Execute `brew autoremove` after the step.
|
||||
# autoremove = true
|
||||
|
||||
# For the BrewFormula step
|
||||
# Upgrade formulae built from the HEAD branch; `brew upgrade --fetch-HEAD`
|
||||
# fetch_head = true
|
||||
|
||||
|
||||
[linux]
|
||||
# Arch Package Manager to use.
|
||||
@@ -153,6 +169,7 @@
|
||||
|
||||
|
||||
[git]
|
||||
# How many repos to pull at max in parallel
|
||||
# max_concurrency = 5
|
||||
|
||||
# Additional git repositories to pull
|
||||
@@ -167,6 +184,7 @@
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
# arguments = "--rebase --autostash"
|
||||
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
# accept_all_updates = false
|
||||
@@ -182,9 +200,6 @@
|
||||
# manager such as Scoop or Cargo
|
||||
# self_rename = true
|
||||
|
||||
# Enable WinGet upgrade
|
||||
# enable_winget = true
|
||||
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
@@ -227,4 +242,12 @@
|
||||
|
||||
# containers = ["archlinux-latest"]
|
||||
[containers]
|
||||
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest"]
|
||||
# Specify the containers to ignore while updating (Wildcard supported)
|
||||
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"]
|
||||
|
||||
[lensfun]
|
||||
# If disabled, Topgrade invokes `lensfun‑update‑data` without root priviledge,
|
||||
# then the update will be only available to you. Otherwise, `sudo` is required,
|
||||
# and the update will be installed system-wide, i.e., available to all users.
|
||||
# (default: false)
|
||||
# use_sudo = false
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::XDG_DIRS;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use std::{
|
||||
env::var,
|
||||
fs::{read_to_string, OpenOptions},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
@@ -89,6 +90,16 @@ fn keep_file_path() -> PathBuf {
|
||||
data_dir().join(keep_file)
|
||||
}
|
||||
|
||||
/// If environment variable `TOPGRADE_SKIP_BRKC_NOTIFY` is set to `true`, then
|
||||
/// we won't notify the user of the breaking changes.
|
||||
pub(crate) fn should_skip() -> bool {
|
||||
if let Ok(var) = var("TOPGRADE_SKIP_BRKC_NOTIFY") {
|
||||
return var.as_str() == "true";
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// True if this is the first execution of a major release.
|
||||
pub(crate) fn first_run_of_major_release() -> Result<bool> {
|
||||
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
|
||||
|
||||
134
src/config.rs
134
src/config.rs
@@ -16,13 +16,13 @@ use merge::Merge;
|
||||
use regex::Regex;
|
||||
use regex_split::RegexSplit;
|
||||
use serde::Deserialize;
|
||||
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames};
|
||||
use which_crate::which;
|
||||
|
||||
use super::utils::editor;
|
||||
use crate::command::CommandExt;
|
||||
use crate::sudo::SudoKind;
|
||||
use crate::utils::{hostname, string_prepend_str};
|
||||
use crate::utils::string_prepend_str;
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
||||
@@ -44,7 +44,7 @@ macro_rules! str_value {
|
||||
|
||||
pub type Commands = BTreeMap<String, String>;
|
||||
|
||||
#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||
#[derive(ValueEnum, EnumString, VariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -54,6 +54,7 @@ pub enum Step {
|
||||
Asdf,
|
||||
Atom,
|
||||
Audit,
|
||||
AutoCpufreq,
|
||||
Bin,
|
||||
Bob,
|
||||
BrewCask,
|
||||
@@ -61,9 +62,11 @@ pub enum Step {
|
||||
Bun,
|
||||
BunPackages,
|
||||
Cargo,
|
||||
Certbot,
|
||||
Chezmoi,
|
||||
Chocolatey,
|
||||
Choosenim,
|
||||
ClamAvDb,
|
||||
Composer,
|
||||
Conda,
|
||||
ConfigUpdate,
|
||||
@@ -74,6 +77,7 @@ pub enum Step {
|
||||
Distrobox,
|
||||
DkpPacman,
|
||||
Dotnet,
|
||||
Elan,
|
||||
Emacs,
|
||||
Firmware,
|
||||
Flatpak,
|
||||
@@ -97,12 +101,14 @@ pub enum Step {
|
||||
Helix,
|
||||
Krew,
|
||||
Lure,
|
||||
Lensfun,
|
||||
Macports,
|
||||
Mamba,
|
||||
Miktex,
|
||||
Mas,
|
||||
Maza,
|
||||
Micro,
|
||||
Mise,
|
||||
Myrepos,
|
||||
Nix,
|
||||
Node,
|
||||
@@ -117,9 +123,11 @@ pub enum Step {
|
||||
Pipx,
|
||||
Pkg,
|
||||
Pkgin,
|
||||
PlatformioCore,
|
||||
Pnpm,
|
||||
Powershell,
|
||||
Protonup,
|
||||
Pyenv,
|
||||
Raco,
|
||||
Rcm,
|
||||
Remotes,
|
||||
@@ -127,6 +135,7 @@ pub enum Step {
|
||||
Rtcl,
|
||||
RubyGems,
|
||||
Rustup,
|
||||
Rye,
|
||||
Scoop,
|
||||
Sdkman,
|
||||
SelfUpdate,
|
||||
@@ -146,9 +155,11 @@ pub enum Step {
|
||||
Vcpkg,
|
||||
Vim,
|
||||
Vscode,
|
||||
Waydroid,
|
||||
Winget,
|
||||
Wsl,
|
||||
WslUpdate,
|
||||
Xcodes,
|
||||
Yadm,
|
||||
Yarn,
|
||||
}
|
||||
@@ -197,7 +208,6 @@ pub struct Windows {
|
||||
accept_all_updates: Option<bool>,
|
||||
self_rename: Option<bool>,
|
||||
open_remotes_in_new_terminal: Option<bool>,
|
||||
enable_winget: Option<bool>,
|
||||
wsl_update_pre_release: Option<bool>,
|
||||
wsl_update_use_web_download: Option<bool>,
|
||||
}
|
||||
@@ -253,7 +263,9 @@ pub struct Flatpak {
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Brew {
|
||||
greedy_cask: Option<bool>,
|
||||
greedy_latest: Option<bool>,
|
||||
autoremove: Option<bool>,
|
||||
fetch_head: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
@@ -385,6 +397,12 @@ pub struct Misc {
|
||||
log_filters: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Lensfun {
|
||||
use_sudo: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
/// Configuration file
|
||||
@@ -445,6 +463,9 @@ pub struct ConfigFile {
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
distrobox: Option<Distrobox>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
lensfun: Option<Lensfun>,
|
||||
}
|
||||
|
||||
fn config_directory() -> PathBuf {
|
||||
@@ -470,7 +491,7 @@ impl ConfigFile {
|
||||
|
||||
let config_directory = config_directory();
|
||||
|
||||
let possible_config_paths = vec![
|
||||
let possible_config_paths = [
|
||||
config_directory.join("topgrade.toml"),
|
||||
config_directory.join("topgrade/topgrade.toml"),
|
||||
];
|
||||
@@ -479,7 +500,7 @@ impl ConfigFile {
|
||||
for path in possible_config_paths.iter() {
|
||||
if path.exists() {
|
||||
debug!("Configuration at {}", path.display());
|
||||
res.0 = path.clone();
|
||||
res.0.clone_from(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -488,7 +509,7 @@ impl ConfigFile {
|
||||
|
||||
// If no config file exists, create a default one in the config directory
|
||||
if !res.0.exists() && res.1.is_empty() {
|
||||
res.0 = possible_config_paths[0].clone();
|
||||
res.0.clone_from(&possible_config_paths[0]);
|
||||
debug!("No configuration exists");
|
||||
write(&res.0, EXAMPLE_CONFIG).map_err(|e| {
|
||||
debug!(
|
||||
@@ -1087,6 +1108,15 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether Brew cask should be greedy_latest
|
||||
pub fn brew_greedy_latest(&self) -> bool {
|
||||
self.config_file
|
||||
.brew
|
||||
.as_ref()
|
||||
.and_then(|c| c.greedy_latest)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether Brew should autoremove
|
||||
pub fn brew_autoremove(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1096,6 +1126,15 @@ impl Config {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether Brew should upgrade formulae built from the HEAD branch
|
||||
pub fn brew_fetch_head(&self) -> bool {
|
||||
self.config_file
|
||||
.brew
|
||||
.as_ref()
|
||||
.and_then(|c| c.fetch_head)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether Composer should update itself
|
||||
pub fn composer_self_update(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1431,30 +1470,22 @@ impl Config {
|
||||
#[cfg(target_os = "linux")]
|
||||
str_value!(linux, emerge_update_flags);
|
||||
|
||||
pub fn should_execute_remote(&self, remote: &str) -> bool {
|
||||
if let Ok(hostname) = hostname() {
|
||||
if remote == hostname {
|
||||
pub fn should_execute_remote(&self, hostname: Result<String>, remote: &str) -> bool {
|
||||
let remote_host = remote.split_once('@').map_or(remote, |(_, host)| host);
|
||||
|
||||
if let Ok(hostname) = hostname {
|
||||
if remote_host == hostname {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(limit) = self.opt.remote_host_limit.as_ref() {
|
||||
return limit.is_match(remote);
|
||||
if let Some(limit) = &self.opt.remote_host_limit.as_ref() {
|
||||
return limit.is_match(remote_host);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn enable_winget(&self) -> bool {
|
||||
return self
|
||||
.config_file
|
||||
.windows
|
||||
.as_ref()
|
||||
.and_then(|w| w.enable_winget)
|
||||
.unwrap_or(false);
|
||||
}
|
||||
|
||||
pub fn enable_pipupgrade(&self) -> bool {
|
||||
return self
|
||||
.config_file
|
||||
@@ -1502,11 +1533,21 @@ impl Config {
|
||||
|
||||
self.opt.custom_commands.iter().any(|s| s == name)
|
||||
}
|
||||
|
||||
pub fn lensfun_use_sudo(&self) -> bool {
|
||||
self.config_file
|
||||
.lensfun
|
||||
.as_ref()
|
||||
.and_then(|lensfun| lensfun.use_sudo)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::config::ConfigFile;
|
||||
|
||||
use crate::config::*;
|
||||
use color_eyre::eyre::eyre;
|
||||
|
||||
/// Test the default configuration in `config.example.toml` is valid.
|
||||
#[test]
|
||||
@@ -1515,4 +1556,51 @@ mod test {
|
||||
|
||||
assert!(toml::from_str::<ConfigFile>(str).is_ok());
|
||||
}
|
||||
|
||||
fn config() -> Config {
|
||||
Config {
|
||||
opt: CommandLineArgs::parse_from::<_, String>([]),
|
||||
config_file: ConfigFile::default(),
|
||||
allowed_steps: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_different_hostname() {
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_different_hostname_with_user() {
|
||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_unknown_hostname() {
|
||||
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_same_hostname() {
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_same_hostname_with_user() {
|
||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_execute_remote_matching_limit() {
|
||||
let mut config = config();
|
||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
|
||||
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_not_execute_remote_not_matching_limit() {
|
||||
let mut config = config();
|
||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
|
||||
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::executor::RunType;
|
||||
use crate::git::Git;
|
||||
use crate::sudo::Sudo;
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use crate::{config::Config, executor::Executor};
|
||||
@@ -12,7 +11,6 @@ use std::sync::Mutex;
|
||||
pub struct ExecutionContext<'a> {
|
||||
run_type: RunType,
|
||||
sudo: Option<Sudo>,
|
||||
git: &'a Git,
|
||||
config: &'a Config,
|
||||
/// Name of a tmux session to execute commands in, if any.
|
||||
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
|
||||
@@ -23,12 +21,11 @@ pub struct ExecutionContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ExecutionContext<'a> {
|
||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
|
||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, config: &'a Config) -> Self {
|
||||
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
||||
Self {
|
||||
run_type,
|
||||
sudo,
|
||||
git,
|
||||
config,
|
||||
tmux_session: Mutex::new(None),
|
||||
under_ssh,
|
||||
@@ -44,10 +41,6 @@ impl<'a> ExecutionContext<'a> {
|
||||
self.run_type
|
||||
}
|
||||
|
||||
pub fn git(&self) -> &Git {
|
||||
self.git
|
||||
}
|
||||
|
||||
pub fn sudo(&self) -> &Option<Sudo> {
|
||||
&self.sudo
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@ impl DryCommand {
|
||||
|
||||
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
||||
pub enum ExecutorChild {
|
||||
#[allow(unused)] // this type has not been used
|
||||
Wet(Child),
|
||||
Dry,
|
||||
}
|
||||
|
||||
98
src/main.rs
98
src/main.rs
@@ -6,7 +6,7 @@ use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, 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::{crate_version, Parser};
|
||||
use color_eyre::eyre::Context;
|
||||
@@ -27,7 +27,7 @@ use self::error::Upgraded;
|
||||
use self::steps::{remote::*, *};
|
||||
use self::terminal::*;
|
||||
|
||||
use self::utils::{install_color_eyre, install_tracing, update_tracing};
|
||||
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
|
||||
|
||||
mod breaking_changes;
|
||||
mod command;
|
||||
@@ -50,6 +50,7 @@ mod utils;
|
||||
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
||||
#[cfg(unix)]
|
||||
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
||||
|
||||
#[cfg(windows)]
|
||||
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
||||
|
||||
@@ -122,8 +123,6 @@ fn run() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let git = git::Git::new();
|
||||
let mut git_repos = git::Repositories::new(&git);
|
||||
let powershell = powershell::Powershell::new();
|
||||
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
|
||||
let emacs = emacs::Emacs::new();
|
||||
@@ -132,12 +131,16 @@ fn run() -> Result<()> {
|
||||
|
||||
let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
|
||||
let run_type = executor::RunType::new(config.dry_run());
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &config);
|
||||
let mut runner = runner::Runner::new(&ctx);
|
||||
|
||||
// If this is the first execution of a major release, inform user of breaking
|
||||
// changes
|
||||
if first_run_of_major_release()? {
|
||||
// If
|
||||
//
|
||||
// 1. the breaking changes notification shouldnot be skipped
|
||||
// 2. this is the first execution of a major release
|
||||
//
|
||||
// inform user of breaking changes
|
||||
if !should_skip() && first_run_of_major_release()? {
|
||||
print_breaking_changes();
|
||||
|
||||
if prompt_yesno("Confirmed?")? {
|
||||
@@ -179,7 +182,7 @@ fn run() -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(topgrades) = config.remote_topgrades() {
|
||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
|
||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(hostname(), t)) {
|
||||
runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
|
||||
ssh::ssh_step(&ctx, remote_topgrade)
|
||||
})?;
|
||||
@@ -231,6 +234,8 @@ fn run() -> Result<()> {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
||||
runner.execute(Step::Waydroid, "Waydroid", || linux::run_waydroid(&ctx))?;
|
||||
runner.execute(Step::AutoCpufreq, "auto-cpufreq", || linux::run_auto_cpufreq(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -254,6 +259,7 @@ fn run() -> Result<()> {
|
||||
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
|
||||
runner.execute(Step::Xcodes, "Xcodes", || macos::update_xcodes(&ctx))?;
|
||||
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
|
||||
runner.execute(Step::Mas, "App Store", || macos::run_mas(&ctx))?;
|
||||
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
|
||||
@@ -293,6 +299,7 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
|
||||
runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?;
|
||||
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
|
||||
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
|
||||
@@ -319,6 +326,7 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
|
||||
unix::upgrade_gnome_extensions(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Pyenv, "pyenv", || unix::run_pyenv(&ctx))?;
|
||||
runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(&ctx))?;
|
||||
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
|
||||
runner.execute(Step::Maza, "maza", || unix::run_maza(&ctx))?;
|
||||
@@ -336,6 +344,8 @@ fn run() -> Result<()> {
|
||||
|
||||
// The following update function should be executed on all OSes.
|
||||
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(&ctx))?;
|
||||
runner.execute(Step::Elan, "elan", || generic::run_elan(&ctx))?;
|
||||
runner.execute(Step::Rye, "rye", || generic::run_rye(&ctx))?;
|
||||
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
|
||||
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&ctx))?;
|
||||
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
|
||||
@@ -349,7 +359,7 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
||||
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
|
||||
generic::run_vscode_extensions_upgrade(&ctx)
|
||||
generic::run_vscode_extensions_update(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
|
||||
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
|
||||
@@ -396,67 +406,15 @@ fn run() -> Result<()> {
|
||||
generic::run_ghcli_extensions_upgrade(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Bob, "Bob", || generic::run_bob(&ctx))?;
|
||||
|
||||
if config.use_predefined_git_repos() {
|
||||
if config.should_run(Step::Emacs) {
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
git_repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
}
|
||||
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
git_repos.insert_if_repo(zsh::zshrc());
|
||||
if config.should_run(Step::Tmux) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
}
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/fish"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
git_repos.insert_if_repo(
|
||||
WINDOWS_DIRS
|
||||
.cache_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
windows::insert_startup_scripts(&mut git_repos).ok();
|
||||
|
||||
if let Some(profile) = powershell.profile() {
|
||||
git_repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if config.should_run(Step::GitRepos) {
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
git_repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
runner.execute(Step::GitRepos, "Git repositories", || {
|
||||
git.multi_pull_step(&git_repos, &ctx)
|
||||
runner.execute(Step::Certbot, "Certbot", || generic::run_certbot(&ctx))?;
|
||||
runner.execute(Step::GitRepos, "Git Repositories", || git::run_git_pull(&ctx))?;
|
||||
runner.execute(Step::ClamAvDb, "ClamAV Databases", || generic::run_freshclam(&ctx))?;
|
||||
runner.execute(Step::PlatformioCore, "PlatformIO Core", || {
|
||||
generic::run_platform_io(&ctx)
|
||||
})?;
|
||||
runner.execute(Step::Lensfun, "Lensfun's database update", || {
|
||||
generic::run_lensfun_update_data(&ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
if should_run_powershell {
|
||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
||||
|
||||
@@ -34,6 +34,14 @@ impl<'a> Runner<'a> {
|
||||
let key = key.into();
|
||||
debug!("Step {:?}", key);
|
||||
|
||||
// alter the `func` to put it in a span
|
||||
let func = || {
|
||||
let span =
|
||||
tracing::span!(parent: tracing::Span::none(), tracing::Level::TRACE, "step", step = ?step, key = %key);
|
||||
let _guard = span.enter();
|
||||
func()
|
||||
};
|
||||
|
||||
loop {
|
||||
match func() {
|
||||
Ok(()) => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg(windows)]
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use std::{env::current_exe, fs, path::PathBuf};
|
||||
use tracing::{debug, error};
|
||||
|
||||
@@ -6,6 +6,7 @@ use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use tracing::{debug, error, warn};
|
||||
use wildmatch::WildMatch;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{self, TopgradeError};
|
||||
@@ -51,6 +52,13 @@ impl Display for Container {
|
||||
///
|
||||
/// Containers specified in `ignored_containers` will be filtered out.
|
||||
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
|
||||
let ignored_containers = ignored_containers.map(|patterns| {
|
||||
patterns
|
||||
.iter()
|
||||
.map(|pattern| WildMatch::new(pattern))
|
||||
.collect::<Vec<WildMatch>>()
|
||||
});
|
||||
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
||||
crt.display()
|
||||
@@ -85,11 +93,8 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
||||
assert_eq!(split_res.len(), 2);
|
||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||
|
||||
if let Some(ignored_containers) = ignored_containers {
|
||||
if ignored_containers
|
||||
.iter()
|
||||
.any(|ignored_container| repo_tag.eq(ignored_container))
|
||||
{
|
||||
if let Some(ref ignored_containers) = ignored_containers {
|
||||
if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
|
||||
debug!("Skipping ignored container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,18 @@ use crate::{
|
||||
terminal::print_warning,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_wsl() -> Result<bool> {
|
||||
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
|
||||
debug!("Uname output: {}", output);
|
||||
Ok(output.contains("microsoft"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn is_wsl() -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let cargo_dir = env::var_os("CARGO_HOME")
|
||||
.map(PathBuf::from)
|
||||
@@ -108,7 +120,10 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("RubyGems");
|
||||
let gem_path_str = gem.as_os_str();
|
||||
if gem_path_str.to_str().unwrap().contains("asdf") {
|
||||
if gem_path_str.to_str().unwrap().contains("asdf")
|
||||
|| gem_path_str.to_str().unwrap().contains(".rbenv")
|
||||
|| gem_path_str.to_str().unwrap().contains(".rvm")
|
||||
{
|
||||
ctx.run_type()
|
||||
.execute(gem)
|
||||
.args(["update", "--system"])
|
||||
@@ -215,6 +230,24 @@ pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.run_type().execute(rustup).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_rye(ctx: &ExecutionContext) -> Result<()> {
|
||||
let rye = require("rye")?;
|
||||
|
||||
print_separator("Rye");
|
||||
ctx.run_type().execute(rye).args(["self", "update"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_elan(ctx: &ExecutionContext) -> Result<()> {
|
||||
let elan = require("elan")?;
|
||||
|
||||
print_separator("elan");
|
||||
ctx.run_type()
|
||||
.execute(&elan)
|
||||
.args(["self", "update"])
|
||||
.status_checked()?;
|
||||
ctx.run_type().execute(&elan).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
|
||||
let juliaup = require("juliaup")?;
|
||||
|
||||
@@ -325,39 +358,48 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_vscode_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
// Calling vscode in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
||||
if is_wsl()? {
|
||||
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
||||
}
|
||||
|
||||
let vscode = require("code")?;
|
||||
|
||||
// Vscode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
||||
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
||||
let version: Result<Version> = match Command::new(&vscode)
|
||||
.arg("--version")
|
||||
.output_checked_utf8()?
|
||||
.stdout
|
||||
.lines()
|
||||
.next()
|
||||
{
|
||||
Some(item) => Version::parse(item).map_err(|err| err.into()),
|
||||
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
|
||||
};
|
||||
|
||||
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
|
||||
return Err(SkipStep(String::from("Too old vscode version to have update extensions command")).into());
|
||||
}
|
||||
|
||||
print_separator("Visual Studio Code extensions");
|
||||
|
||||
// Vscode does not have CLI command to upgrade all extensions (see https://github.com/microsoft/vscode/issues/56578)
|
||||
// Instead we get the list of installed extensions with `code --list-extensions` command (obtain a line-return separated list of installed extensions)
|
||||
let extensions = Command::new(&vscode)
|
||||
.arg("--list-extensions")
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
// Then we construct the upgrade command: `code --force --install-extension [ext0] --install-extension [ext1] ... --install-extension [extN]`
|
||||
if !extensions.is_empty() {
|
||||
let mut command_args = vec!["--force"];
|
||||
for extension in extensions.split_whitespace() {
|
||||
command_args.extend(["--install-extension", extension]);
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&vscode).args(command_args).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
ctx.run_type()
|
||||
.execute(vscode)
|
||||
.arg("--update-extensions")
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pipx = require("pipx")?;
|
||||
print_separator("pipx");
|
||||
|
||||
let mut command_args = vec!["upgrade-all"];
|
||||
let mut command_args = vec!["upgrade-all", "--include-injected"];
|
||||
|
||||
// pipx version 1.4.0 introduced a new command argument `pipx upgrade-all --quiet`
|
||||
// (see https://pipx.pypa.io/stable/docs/#pipx-upgrade-all)
|
||||
let version_str = Command::new("pipx")
|
||||
let version_str = Command::new(&pipx)
|
||||
.args(["--version"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
@@ -372,7 +414,7 @@ pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let conda = require("conda")?;
|
||||
|
||||
let output = Command::new("conda")
|
||||
let output = Command::new(&conda)
|
||||
.args(["config", "--show", "auto_activate_base"])
|
||||
.output_checked_utf8()?;
|
||||
debug!("Conda output: {}", output.stdout);
|
||||
@@ -393,7 +435,7 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mamba = require("mamba")?;
|
||||
|
||||
let output = Command::new("mamba")
|
||||
let output = Command::new(&mamba)
|
||||
.args(["config", "--show", "auto_activate_base"])
|
||||
.output_checked_utf8()?;
|
||||
debug!("Mamba output: {}", output.stdout);
|
||||
@@ -489,7 +531,7 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("pip3");
|
||||
if env::var("VIRTUAL_ENV").is_ok() {
|
||||
print_warning("This step is will be skipped when running inside a virtual environment");
|
||||
print_warning("This step is skipped when running inside a virtual environment");
|
||||
return Err(SkipStep("Does not run inside a virtual environment".to_string()).into());
|
||||
}
|
||||
|
||||
@@ -517,6 +559,7 @@ pub fn run_pip_review_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_pip_review_local_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pip_review = require("pip-review")?;
|
||||
|
||||
@@ -536,6 +579,7 @@ pub fn run_pip_review_local_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pipupgrade = require("pipupgrade")?;
|
||||
|
||||
@@ -553,6 +597,7 @@ pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_stack_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
if require("ghcup").is_ok() {
|
||||
// `ghcup` is present and probably(?) being used to install `stack`.
|
||||
@@ -669,7 +714,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
if !composer_home.is_descendant_of(&HOME_DIR) {
|
||||
return Err(SkipStep(format!(
|
||||
"Composer directory {} isn't a decandent of the user's home directory",
|
||||
"Composer directory {} isn't a descendant of the user's home directory",
|
||||
composer_home.display()
|
||||
))
|
||||
.into());
|
||||
@@ -734,10 +779,11 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
return Err(SkipStep(String::from(
|
||||
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.",
|
||||
))
|
||||
.into())
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut in_header = true;
|
||||
let mut packages = output
|
||||
.stdout
|
||||
.lines()
|
||||
@@ -745,11 +791,17 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
//
|
||||
// Package Id Version Commands
|
||||
// -------------------------------------
|
||||
//
|
||||
// One thing to note is that .NET SDK respect locale, which means this
|
||||
// header can be printed in languages other than English, do NOT use it
|
||||
// to do any check.
|
||||
.skip(2)
|
||||
.skip_while(|line| {
|
||||
// The .NET SDK respects locale, so the header can be printed
|
||||
// in languages other than English. The separator should hopefully
|
||||
// always be at least 10 -'s long.
|
||||
if in_header && line.starts_with("----------") {
|
||||
in_header = false;
|
||||
true
|
||||
} else {
|
||||
in_header
|
||||
}
|
||||
})
|
||||
.filter(|line| !line.is_empty())
|
||||
.peekable();
|
||||
|
||||
@@ -772,20 +824,19 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("helix")?;
|
||||
let helix = require("helix").or(require("hx"))?;
|
||||
|
||||
print_separator("Helix");
|
||||
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["helix", "--grammar", "fetch"])
|
||||
.execute(&helix)
|
||||
.args(["--grammar", "fetch"])
|
||||
.status_checked()
|
||||
.with_context(|| "Failed to download helix grammars!")?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["helix", "--grammar", "build"])
|
||||
.execute(&helix)
|
||||
.args(["--grammar", "build"])
|
||||
.status_checked()
|
||||
.with_context(|| "Failed to build helix grammars!")?;
|
||||
|
||||
@@ -884,3 +935,70 @@ pub fn run_bob(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
ctx.run_type().execute(bob).args(["update", "--all"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let certbot = require("certbot")?;
|
||||
|
||||
print_separator("Certbot");
|
||||
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg(certbot);
|
||||
cmd.arg("renew");
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
/// Run `$ freshclam` to update ClamAV signature database
|
||||
///
|
||||
/// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam
|
||||
pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> {
|
||||
let freshclam = require("freshclam")?;
|
||||
print_separator("Update ClamAV Database(FreshClam)");
|
||||
ctx.run_type().execute(freshclam).status_checked()
|
||||
}
|
||||
|
||||
/// Involve `pio upgrade` to update PlatformIO core.
|
||||
pub fn run_platform_io(ctx: &ExecutionContext) -> Result<()> {
|
||||
// We use the full path because by default the binary is not in `PATH`:
|
||||
// https://github.com/topgrade-rs/topgrade/issues/754#issuecomment-2020537559
|
||||
#[cfg(unix)]
|
||||
fn bin_path() -> PathBuf {
|
||||
HOME_DIR.join(".platformio/penv/bin/pio")
|
||||
}
|
||||
#[cfg(windows)]
|
||||
fn bin_path() -> PathBuf {
|
||||
HOME_DIR.join(".platformio/penv/Scripts/pio.exe")
|
||||
}
|
||||
|
||||
let bin_path = require(bin_path())?;
|
||||
|
||||
print_separator("PlatformIO Core");
|
||||
|
||||
ctx.run_type().execute(bin_path).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
/// Run `lensfun-update-data` to update lensfun database.
|
||||
///
|
||||
/// `sudo` will be used if `use_sudo` configuration entry is set to true.
|
||||
pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
|
||||
const SEPARATOR: &str = "Lensfun's database update";
|
||||
let lensfun_update_data = require("lensfun-update-data")?;
|
||||
const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1;
|
||||
|
||||
if ctx.config().lensfun_use_sudo() {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
print_separator(SEPARATOR);
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(lensfun_update_data)
|
||||
// `lensfun-update-data` returns 1 when there is no update available
|
||||
// which should be considered success
|
||||
.status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE])
|
||||
} else {
|
||||
print_separator(SEPARATOR);
|
||||
ctx.run_type()
|
||||
.execute(lensfun_update_data)
|
||||
.status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE])
|
||||
}
|
||||
}
|
||||
|
||||
452
src/steps/git.rs
452
src/steps/git.rs
@@ -6,30 +6,120 @@ use std::process::{Command, Output, Stdio};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use console::style;
|
||||
use futures::stream::{iter, FuturesUnordered};
|
||||
use futures::StreamExt;
|
||||
use futures::stream::{iter, FuturesUnordered, StreamExt};
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use tokio::runtime;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::Step;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::steps::emacs::Emacs;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{which, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning};
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
|
||||
pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mut repos = RepoStep::try_new()?;
|
||||
let config = ctx.config();
|
||||
|
||||
// handle built-in repos
|
||||
if config.use_predefined_git_repos() {
|
||||
// should be executed on all the platforms
|
||||
{
|
||||
if config.should_run(Step::Emacs) {
|
||||
let emacs = Emacs::new();
|
||||
if !emacs.is_doom() {
|
||||
if let Some(directory) = emacs.directory() {
|
||||
repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
}
|
||||
|
||||
repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
}
|
||||
|
||||
let powershell = crate::steps::powershell::Powershell::new();
|
||||
if let Some(profile) = powershell.profile() {
|
||||
repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
repos.insert_if_repo(crate::steps::zsh::zshrc());
|
||||
if config.should_run(Step::Tmux) {
|
||||
repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
}
|
||||
repos.insert_if_repo(HOME_DIR.join(".config/fish"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
|
||||
repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
repos.insert_if_repo(
|
||||
WINDOWS_DIRS
|
||||
.cache_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
);
|
||||
|
||||
super::os::windows::insert_startup_scripts(&mut repos).ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle user-defined repos
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
// user won't receive this warning in the cases where all the paths configured
|
||||
// are bad patterns.
|
||||
repos
|
||||
.bad_patterns
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
|
||||
|
||||
if repos.is_repos_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
|
||||
repos.pull_repos(ctx)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
static PATH_PREFIX: &str = "\\\\?\\";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
repositories: HashSet<String>,
|
||||
pub struct RepoStep {
|
||||
git: PathBuf,
|
||||
repos: HashSet<PathBuf>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
@@ -45,100 +135,41 @@ fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
let before_revision = get_head_revision(git, &repo);
|
||||
|
||||
println!("{} {}", style("Pulling").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|_| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {repo}"));
|
||||
|
||||
if result.is_err() {
|
||||
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
||||
} else {
|
||||
let after_revision = get_head_revision(git, &repo);
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}:", style("Changed").yellow().bold(), &repo);
|
||||
|
||||
fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{before}..{after}"),
|
||||
])
|
||||
.status_checked()?;
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
println!("{} {}", style("Up-to-date").green().bold(), &repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
fn get_head_revision(git: &Path, repo: &str) -> Option<String> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo)
|
||||
.current_dir(repo.as_ref())
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output_checked_utf8()
|
||||
.map(|output| output.stdout.trim().to_string())
|
||||
.map_err(|e| {
|
||||
error!("Error getting revision for {}: {}", repo, e);
|
||||
error!("Error getting revision for {}: {}", repo.as_ref().display(), e);
|
||||
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn has_remotes(git: &Path, repo: &str) -> Option<bool> {
|
||||
Command::new(git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(repo)
|
||||
.args(["remote", "show"])
|
||||
.output_checked_utf8()
|
||||
.map(|output| output.stdout.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {}", repo, e);
|
||||
e
|
||||
impl RepoStep {
|
||||
/// Try to create a `RepoStep`, fail if `git` is not found.
|
||||
pub fn try_new() -> Result<Self> {
|
||||
let git = require("git")?;
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
git,
|
||||
repos: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
impl Git {
|
||||
pub fn new() -> Self {
|
||||
Self { git: which("git") }
|
||||
}
|
||||
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<String> {
|
||||
/// Try to get the root of the repo specified in `path`.
|
||||
pub fn get_repo_root<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
|
||||
match path.as_ref().canonicalize() {
|
||||
Ok(mut path) => {
|
||||
debug_assert!(path.exists());
|
||||
@@ -162,17 +193,17 @@ impl Git {
|
||||
path_string
|
||||
};
|
||||
|
||||
if let Some(git) = &self.git {
|
||||
let output = Command::new(git)
|
||||
let output = Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(path)
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.output_checked_utf8()
|
||||
.ok()
|
||||
.map(|output| output.stdout.trim().to_string());
|
||||
// trim the last newline char
|
||||
.map(|output| PathBuf::from(output.stdout.trim()));
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
||||
@@ -181,92 +212,37 @@ impl Git {
|
||||
|
||||
None
|
||||
}
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
// user won't receive this warning in the cases where all the paths configured
|
||||
// are bad patterns.
|
||||
repositories
|
||||
.bad_patterns
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
|
||||
|
||||
if repositories.repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
self.multi_pull(repositories, ctx)
|
||||
}
|
||||
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
let git = self.git.as_ref().unwrap();
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| pull_repository(repo.clone(), git, ctx));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Repositories<'a> {
|
||||
pub fn new(git: &'a Git) -> Self {
|
||||
let mut glob_match_options = MatchOptions::new();
|
||||
|
||||
if cfg!(windows) {
|
||||
glob_match_options.case_sensitive = false;
|
||||
}
|
||||
|
||||
Self {
|
||||
git,
|
||||
repositories: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `path` is a git repo, if yes, add it to `self.repos`.
|
||||
///
|
||||
/// Return the check result.
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
self.repositories.insert(repo);
|
||||
if let Some(repo) = self.get_repo_root(path) {
|
||||
self.repos.insert(repo);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `repo` has a remote.
|
||||
fn has_remotes<P: AsRef<Path>>(&self, repo: P) -> Option<bool> {
|
||||
let mut cmd = Command::new(&self.git);
|
||||
cmd.stdin(Stdio::null())
|
||||
.current_dir(repo.as_ref())
|
||||
.args(["remote", "show"]);
|
||||
|
||||
let res = cmd.output_checked_utf8();
|
||||
|
||||
res.map(|output| output.stdout.lines().count() > 0)
|
||||
.map_err(|e| {
|
||||
error!("Error getting remotes for {}: {}", repo.as_ref().display(), e);
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Similar to `insert_if_repo`, with glob support.
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
@@ -276,7 +252,7 @@ impl<'a> Repositories<'a> {
|
||||
if let Some(last_git_repo) = &last_git_repo {
|
||||
if path.is_descendant_of(last_git_repo) {
|
||||
debug!(
|
||||
"Skipping {} because it's a decendant of last known repo {}",
|
||||
"Skipping {} because it's a descendant of last known repo {}",
|
||||
path.display(),
|
||||
last_git_repo.display()
|
||||
);
|
||||
@@ -301,16 +277,130 @@ impl<'a> Repositories<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.repositories.is_empty()
|
||||
/// True if `self.repos` is empty.
|
||||
pub fn is_repos_empty(&self) -> bool {
|
||||
self.repos.is_empty()
|
||||
}
|
||||
|
||||
// The following 2 functions are `#[cfg(unix)]` because they are only used in
|
||||
// the `oh-my-zsh` step, which is UNIX-only.
|
||||
/// Remove `path` from `self.repos`.
|
||||
///
|
||||
// `cfg(unix)` because it is only used in the oh-my-zsh step.
|
||||
#[cfg(unix)]
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
pub fn remove<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let _removed = self.repos.remove(path.as_ref());
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
|
||||
/// Try to pull a repo.
|
||||
async fn pull_repo<P: AsRef<Path>>(&self, ctx: &ExecutionContext<'_>, repo: P) -> Result<()> {
|
||||
let before_revision = get_head_revision(&self.git, &repo);
|
||||
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style("Pulling").cyan().bold(), repo.as_ref().display());
|
||||
}
|
||||
|
||||
let mut command = AsyncCommand::new(&self.git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let pull_output = command.output().await?;
|
||||
let submodule_output = AsyncCommand::new(&self.git)
|
||||
.args(["submodule", "update", "--recursive"])
|
||||
.current_dir(&repo)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|_| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
||||
|
||||
if result.is_err() {
|
||||
println!("{} pulling {}", style("Failed").red().bold(), repo.as_ref().display());
|
||||
} else {
|
||||
let after_revision = get_head_revision(&self.git, repo.as_ref());
|
||||
|
||||
match (&before_revision, &after_revision) {
|
||||
(Some(before), Some(after)) if before != after => {
|
||||
println!("{} {}", style("Changed").yellow().bold(), repo.as_ref().display());
|
||||
|
||||
Command::new(&self.git)
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args([
|
||||
"--no-pager",
|
||||
"log",
|
||||
"--no-decorate",
|
||||
"--oneline",
|
||||
&format!("{before}..{after}"),
|
||||
])
|
||||
.status_checked()?;
|
||||
println!();
|
||||
}
|
||||
_ => {
|
||||
if ctx.config().verbose() {
|
||||
println!("{} {}", style("Up-to-date").green().bold(), repo.as_ref().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.map(|_| ())
|
||||
}
|
||||
|
||||
/// Pull the repositories specified in `self.repos`.
|
||||
///
|
||||
/// # NOTE
|
||||
/// This function will create an async runtime and do the real job so the
|
||||
/// function itself is not async.
|
||||
fn pull_repos(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
if ctx.run_type().dry() {
|
||||
self.repos
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", repo.display()));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !ctx.config().verbose() {
|
||||
println!(
|
||||
"\n{} updated repositories will be shown...\n",
|
||||
style("Only").green().bold()
|
||||
);
|
||||
}
|
||||
|
||||
let futures_iterator = self
|
||||
.repos
|
||||
.iter()
|
||||
.filter(|repo| match self.has_remotes(repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo.display()
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| self.pull_repo(ctx, repo));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ use tracing::{debug, warn};
|
||||
use crate::command::CommandExt;
|
||||
use crate::error::{SkipStep, TopgradeError};
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::steps::generic::is_wsl;
|
||||
use crate::steps::os::archlinux;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::terminal::{print_separator, prompt_yesno};
|
||||
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
|
||||
use crate::{Step, HOME_DIR};
|
||||
|
||||
@@ -19,9 +20,11 @@ static OS_RELEASE_PATH: &str = "/etc/os-release";
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Distribution {
|
||||
Alpine,
|
||||
Wolfi,
|
||||
Arch,
|
||||
Bedrock,
|
||||
CentOS,
|
||||
Chimera,
|
||||
ClearLinux,
|
||||
Fedora,
|
||||
FedoraImmutable,
|
||||
@@ -38,6 +41,7 @@ pub enum Distribution {
|
||||
Exherbo,
|
||||
NixOS,
|
||||
KDENeon,
|
||||
Nobara,
|
||||
}
|
||||
|
||||
impl Distribution {
|
||||
@@ -45,31 +49,31 @@ impl Distribution {
|
||||
let section = os_release.general_section();
|
||||
let id = section.get("ID");
|
||||
let name = section.get("NAME");
|
||||
let variant: Option<Vec<&str>> = section.get("VARIANT").map(|s| s.split_whitespace().collect());
|
||||
let variant = section.get("VARIANT");
|
||||
let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect());
|
||||
|
||||
Ok(match id {
|
||||
Some("alpine") => Distribution::Alpine,
|
||||
Some("chimera") => Distribution::Chimera,
|
||||
Some("wolfi") => Distribution::Wolfi,
|
||||
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
|
||||
Some("clear-linux-os") => Distribution::ClearLinux,
|
||||
Some("fedora") | Some("nobara") => {
|
||||
Some("fedora") => {
|
||||
return if let Some(variant) = variant {
|
||||
if variant.contains(&"Silverblue")
|
||||
|| variant.contains(&"Kinoite")
|
||||
|| variant.contains(&"Sericea")
|
||||
|| variant.contains(&"Onyx")
|
||||
{
|
||||
match variant {
|
||||
"Silverblue" | "Kinoite" | "Sericea" | "Onyx" | "IoT Edition" | "Sway Atomic" => {
|
||||
Ok(Distribution::FedoraImmutable)
|
||||
} else {
|
||||
Ok(Distribution::Fedora)
|
||||
}
|
||||
_ => Ok(Distribution::Fedora),
|
||||
}
|
||||
} else {
|
||||
Ok(Distribution::Fedora)
|
||||
};
|
||||
}
|
||||
|
||||
Some("nobara") => Distribution::Nobara,
|
||||
Some("void") => Distribution::Void,
|
||||
Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
|
||||
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
|
||||
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
|
||||
Some("solus") => Distribution::Solus,
|
||||
Some("gentoo") => Distribution::Gentoo,
|
||||
@@ -133,6 +137,8 @@ impl Distribution {
|
||||
|
||||
match self {
|
||||
Distribution::Alpine => upgrade_alpine_linux(ctx),
|
||||
Distribution::Chimera => upgrade_chimera_linux(ctx),
|
||||
Distribution::Wolfi => upgrade_wolfi_linux(ctx),
|
||||
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
|
||||
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
|
||||
Distribution::FedoraImmutable => upgrade_fedora_immutable(ctx),
|
||||
@@ -151,6 +157,7 @@ impl Distribution {
|
||||
Distribution::Bedrock => update_bedrock(ctx),
|
||||
Distribution::OpenMandriva => upgrade_openmandriva(ctx),
|
||||
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
|
||||
Distribution::Nobara => upgrade_nobara(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +184,7 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("Bedrock distribution {}", distribution);
|
||||
match distribution {
|
||||
"arch" => archlinux::upgrade_arch_linux(ctx)?,
|
||||
"debian" | "ubuntu" => upgrade_debian(ctx)?,
|
||||
"debian" | "ubuntu" | "linuxmint" => upgrade_debian(ctx)?,
|
||||
"centos" | "fedora" => upgrade_redhat(ctx)?,
|
||||
"bedrock" => upgrade_bedrock_strata(ctx)?,
|
||||
_ => {
|
||||
@@ -189,13 +196,23 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_wsl() -> Result<bool> {
|
||||
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
|
||||
debug!("Uname output: {}", output);
|
||||
Ok(output.contains("microsoft"))
|
||||
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let apk = require("apk")?;
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
|
||||
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
|
||||
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let apk = require("apk")?;
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
|
||||
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
|
||||
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let apk = require("apk")?;
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
|
||||
@@ -234,6 +251,40 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let pkg_manager = require("dnf")?;
|
||||
|
||||
let mut update_command = ctx.run_type().execute(sudo);
|
||||
update_command.arg(&pkg_manager);
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
update_command.arg("-y");
|
||||
}
|
||||
|
||||
update_command.arg("update");
|
||||
// See https://nobaraproject.org/docs/upgrade-troubleshooting/how-do-i-update-the-system/
|
||||
update_command.args([
|
||||
"rpmfusion-nonfree-release",
|
||||
"rpmfusion-free-release",
|
||||
"fedora-repos",
|
||||
"nobara-repos",
|
||||
]);
|
||||
update_command.arg("--refresh").status_checked()?;
|
||||
|
||||
let mut upgrade_command = ctx.run_type().execute(sudo);
|
||||
upgrade_command.arg(&pkg_manager);
|
||||
|
||||
if ctx.config().yes(Step::System) {
|
||||
upgrade_command.arg("-y");
|
||||
}
|
||||
|
||||
upgrade_command.arg("distro-sync");
|
||||
|
||||
upgrade_command.status_checked()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
|
||||
let ostree = require("rpm-ostree")?;
|
||||
let mut command = ctx.run_type().execute(ostree);
|
||||
@@ -977,6 +1028,64 @@ pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
exe.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let waydroid = require("waydroid")?;
|
||||
let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?;
|
||||
// example output of `waydroid status`:
|
||||
//
|
||||
// ```sh
|
||||
// $ waydroid status
|
||||
// Session: RUNNING
|
||||
// Container: RUNNING
|
||||
// Vendor type: MAINLINE
|
||||
// IP address: 192.168.240.112
|
||||
// Session user: w568w(1000)
|
||||
// Wayland display: wayland-0
|
||||
// ```
|
||||
//
|
||||
// ```sh
|
||||
// $ waydroid status
|
||||
// Session: STOPPED
|
||||
// Vendor type: MAINLINE
|
||||
// ```
|
||||
let session = status
|
||||
.stdout
|
||||
.lines()
|
||||
.find(|line| line.contains("Session:"))
|
||||
.expect("the output of `waydroid status` should contain `Session:`");
|
||||
let is_container_running = session.contains("RUNNING");
|
||||
let assume_yes = ctx.config().yes(Step::Waydroid);
|
||||
|
||||
print_separator("Waydroid");
|
||||
|
||||
if is_container_running && !assume_yes {
|
||||
let update_allowed =
|
||||
prompt_yesno("Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?")?;
|
||||
if !update_allowed {
|
||||
return Err(SkipStep("Skip the Waydroid step because the user don't want to proceed".to_string()).into());
|
||||
}
|
||||
}
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(&waydroid)
|
||||
.arg("upgrade")
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let auto_cpu_freq = require("auto-cpufreq")?;
|
||||
|
||||
print_separator("auto-cpufreq");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(auto_cpu_freq)
|
||||
.arg("--update")
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -989,6 +1098,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wolfi() {
|
||||
test_template(include_str!("os_release/wolfi"), Distribution::Wolfi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arch_linux() {
|
||||
test_template(include_str!("os_release/arch"), Distribution::Arch);
|
||||
@@ -1049,6 +1163,11 @@ mod tests {
|
||||
test_template(include_str!("os_release/fedorakinoite"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/fedoraonyx"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/fedorasericea"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/fedoraiot"), Distribution::FedoraImmutable);
|
||||
test_template(
|
||||
include_str!("os_release/fedoraswayatomic"),
|
||||
Distribution::FedoraImmutable,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1120,4 +1239,9 @@ mod tests {
|
||||
fn test_solus() {
|
||||
test_template(include_str!("os_release/solus"), Distribution::Solus);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nobara() {
|
||||
test_template(include_str!("os_release/nobara"), Distribution::Nobara);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::terminal::{print_separator, prompt_yesno};
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use crate::{utils::require, Step};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
@@ -41,7 +42,7 @@ pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("macOS system update");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::System)) || (ctx.config().dry_run());
|
||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
|
||||
if should_ask {
|
||||
println!("Finding available software");
|
||||
if system_update_available()? {
|
||||
@@ -93,3 +94,148 @@ pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
print_separator("Xcodes");
|
||||
|
||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.config().dry_run());
|
||||
|
||||
let releases = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args(["update"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let releases_installed: Vec<String> = releases
|
||||
.lines()
|
||||
.filter(|r| r.contains("(Installed)"))
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if releases_installed.is_empty() {
|
||||
println!("No Xcode releases installed.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (installed_gm, installed_beta, installed_regular) =
|
||||
releases_installed
|
||||
.iter()
|
||||
.fold((false, false, false), |(gm, beta, regular), release| {
|
||||
(
|
||||
gm || release.contains("GM") || release.contains("Release Candidate"),
|
||||
beta || release.contains("Beta"),
|
||||
regular
|
||||
|| !(release.contains("GM")
|
||||
|| release.contains("Release Candidate")
|
||||
|| release.contains("Beta")),
|
||||
)
|
||||
});
|
||||
|
||||
let releases_gm = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("GM").count() > 0 || r.matches("Release Candidate").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_beta = releases
|
||||
.lines()
|
||||
.filter(|&r| r.matches("Beta").count() > 0)
|
||||
.map(String::from)
|
||||
.collect();
|
||||
let releases_regular = releases
|
||||
.lines()
|
||||
.filter(|&r| {
|
||||
r.matches("GM").count() == 0
|
||||
&& r.matches("Release Candidate").count() == 0
|
||||
&& r.matches("Beta").count() == 0
|
||||
})
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
if installed_gm {
|
||||
process_xcodes_releases(releases_gm, should_ask, ctx)?;
|
||||
}
|
||||
if installed_beta {
|
||||
process_xcodes_releases(releases_beta, should_ask, ctx)?;
|
||||
}
|
||||
if installed_regular {
|
||||
process_xcodes_releases(releases_regular, should_ask, ctx)?;
|
||||
}
|
||||
|
||||
let releases_new = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args(["list"])
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
let releases_gm_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)") && (release.contains("GM") || release.contains("Release Candidate"))
|
||||
})
|
||||
.collect();
|
||||
let releases_beta_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| release.contains("(Installed)") && release.contains("Beta"))
|
||||
.collect();
|
||||
let releases_regular_new_installed: HashSet<_> = releases_new
|
||||
.lines()
|
||||
.filter(|release| {
|
||||
release.contains("(Installed)")
|
||||
&& !(release.contains("GM") || release.contains("Release Candidate") || release.contains("Beta"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
for releases_new_installed in [
|
||||
releases_gm_new_installed,
|
||||
releases_beta_new_installed,
|
||||
releases_regular_new_installed,
|
||||
] {
|
||||
if should_ask && releases_new_installed.len() == 2 {
|
||||
let answer_uninstall = prompt_yesno("Would you like to move the former Xcode release to the trash?")?;
|
||||
if answer_uninstall {
|
||||
let _ = ctx
|
||||
.run_type()
|
||||
.execute(&xcodes)
|
||||
.args([
|
||||
"uninstall",
|
||||
releases_new_installed.iter().next().cloned().unwrap_or_default(),
|
||||
])
|
||||
.status_checked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
||||
let xcodes = require("xcodes")?;
|
||||
|
||||
if releases_filtered
|
||||
.last()
|
||||
.map(|s| !s.contains("(Installed)"))
|
||||
.unwrap_or(true)
|
||||
&& !releases_filtered.is_empty()
|
||||
{
|
||||
println!(
|
||||
"New Xcode release detected: {}",
|
||||
releases_filtered.last().cloned().unwrap_or_default()
|
||||
);
|
||||
if should_ask {
|
||||
let answer_install = prompt_yesno("Would you like to install it?")?;
|
||||
if answer_install {
|
||||
let _ = ctx
|
||||
.run_type()
|
||||
.execute(xcodes)
|
||||
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
|
||||
.status_checked();
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
print_separator("OpenBSD Update");
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(&["/usr/sbin/sysupgrade", "-n"])
|
||||
.args(["/usr/sbin/sysupgrade", "-n"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
print_separator("OpenBSD Packages");
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(&["/usr/sbin/pkg_add", "-u"])
|
||||
.status_checked()
|
||||
.args(["/usr/sbin/pkg_delete", "-ac"])
|
||||
.status_checked()?;
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["/usr/sbin/pkg_add", "-u"])
|
||||
.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
22
src/steps/os/os_release/fedoraiot
Normal file
22
src/steps/os/os_release/fedoraiot
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240415.0 (IoT Edition)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240415.0 (IoT Edition)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
HOME_URL="https://fedoraproject.org/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f39/system-administrators-guide/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="IoT Edition"
|
||||
VARIANT_ID=iot
|
||||
OSTREE_VERSION='39.20240415.0'
|
||||
23
src/steps/os/os_release/fedoraswayatomic
Normal file
23
src/steps/os/os_release/fedoraswayatomic
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="40.20240426.0 (Sway Atomic)"
|
||||
ID=fedora
|
||||
VERSION_ID=40
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f40"
|
||||
PRETTY_NAME="Fedora Linux 40.20240426.0 (Sway Atomic)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:40"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/atomic-desktops/sway/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=40
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=40
|
||||
SUPPORT_END=2025-05-13
|
||||
VARIANT="Sway Atomic"
|
||||
VARIANT_ID=sway-atomic
|
||||
OSTREE_VERSION='40.20240426.0'
|
||||
23
src/steps/os/os_release/nobara
Normal file
23
src/steps/os/os_release/nobara
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Nobara Linux"
|
||||
VERSION="39 (GNOME Edition)"
|
||||
ID=nobara
|
||||
ID_LIKE="rhel centos fedora"
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Nobara Linux 39 (GNOME Edition)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=nobara-logo-icon
|
||||
CPE_NAME="cpe:/o:nobaraproject:nobara:39"
|
||||
DEFAULT_HOSTNAME="nobara"
|
||||
HOME_URL="https://nobaraproject.org/"
|
||||
DOCUMENTATION_URL="https://www.nobaraproject.org/"
|
||||
SUPPORT_URL="https://www.nobaraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/gloriouseggroll/nobara-images"
|
||||
REDHAT_BUGZILLA_PRODUCT="Nobara"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Nobara"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="GNOME Edition"
|
||||
VARIANT_ID=gnome
|
||||
5
src/steps/os/os_release/wolfi
Normal file
5
src/steps/os/os_release/wolfi
Normal file
@@ -0,0 +1,5 @@
|
||||
ID=wolfi
|
||||
NAME="Wolfi"
|
||||
PRETTY_NAME="Wolfi"
|
||||
VERSION_ID="20230201"
|
||||
HOME_URL="https://wolfi.dev"
|
||||
@@ -285,10 +285,15 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
variant.execute(run_type).arg("update").status_checked()?;
|
||||
variant
|
||||
.execute(run_type)
|
||||
.args(["upgrade", "--formula"])
|
||||
.status_checked()?;
|
||||
|
||||
let mut command = variant.execute(run_type);
|
||||
command.args(["upgrade", "--formula"]);
|
||||
|
||||
if ctx.config().brew_fetch_head() {
|
||||
command.arg("--fetch-HEAD");
|
||||
}
|
||||
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
variant.execute(run_type).arg("cleanup").status_checked()?;
|
||||
@@ -328,6 +333,9 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
|
||||
if ctx.config().brew_cask_greedy() {
|
||||
brew_args.push("--greedy");
|
||||
}
|
||||
if ctx.config().brew_greedy_latest() {
|
||||
brew_args.push("--greedy-latest");
|
||||
}
|
||||
}
|
||||
|
||||
variant.execute(run_type).args(&brew_args).status_checked()?;
|
||||
@@ -543,6 +551,19 @@ pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
||||
let mise = require("mise")?;
|
||||
|
||||
print_separator("mise");
|
||||
|
||||
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(&mise)
|
||||
.args(["plugins", "update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
let home_manager = require("home-manager")?;
|
||||
|
||||
@@ -572,6 +593,25 @@ pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.run_type().execute(pearl).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pyenv = require("pyenv")?;
|
||||
print_separator("pyenv");
|
||||
|
||||
let pyenv_dir = var("PYENV_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
|
||||
|
||||
if !pyenv_dir.exists() {
|
||||
return Err(SkipStep("Pyenv is installed, but $PYENV_ROOT is not set correctly".to_string()).into());
|
||||
}
|
||||
|
||||
if !pyenv_dir.join(".git").exists() {
|
||||
return Err(SkipStep("pyenv is not a git repository".to_string()).into());
|
||||
}
|
||||
|
||||
ctx.run_type().execute(pyenv).arg("update").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||
let bash = require("bash")?;
|
||||
|
||||
@@ -648,7 +688,12 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Bun Packages");
|
||||
|
||||
if !HOME_DIR.join(".bun/install/global/package.json").exists() {
|
||||
let mut package_json: PathBuf = var("BUN_INSTALL")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
|
||||
package_json.push("install/global/package.json");
|
||||
|
||||
if !package_json.exists() {
|
||||
println!("No global packages installed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
use std::{ffi::OsStr, process::Command};
|
||||
|
||||
@@ -10,7 +9,7 @@ use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::{print_separator, print_warning};
|
||||
use crate::utils::{require, which};
|
||||
use crate::{error::SkipStep, steps::git::Repositories};
|
||||
use crate::{error::SkipStep, steps::git::RepoStep};
|
||||
use crate::{powershell, Step};
|
||||
|
||||
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -42,11 +41,6 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("winget");
|
||||
|
||||
if !ctx.config().enable_winget() {
|
||||
print_warning("Winget is disabled by default. Enable it by setting enable_winget=true in the [windows] section in the configuration.");
|
||||
return Err(SkipStep(String::from("Winget is disabled by default")).into());
|
||||
}
|
||||
|
||||
ctx.run_type()
|
||||
.execute(winget)
|
||||
.args(["upgrade", "--all"])
|
||||
@@ -207,20 +201,17 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let powershell = powershell::Powershell::windows_powershell();
|
||||
|
||||
print_separator("Windows Update");
|
||||
|
||||
if powershell.supports_windows_update() {
|
||||
print_separator("Windows Update");
|
||||
return powershell.windows_update(ctx);
|
||||
powershell.windows_update(ctx)
|
||||
} else {
|
||||
print_warning(
|
||||
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.",
|
||||
);
|
||||
|
||||
Err(SkipStep("USOClient not supported.".to_string()).into())
|
||||
}
|
||||
|
||||
let usoclient = require("UsoClient")?;
|
||||
|
||||
print_separator("Windows Update");
|
||||
println!("Running Windows Update. Check the control panel for progress.");
|
||||
ctx.run_type()
|
||||
.execute(&usoclient)
|
||||
.arg("ScanInstallWait")
|
||||
.status_checked()?;
|
||||
ctx.run_type().execute(&usoclient).arg("StartInstall").status_checked()
|
||||
}
|
||||
|
||||
pub fn reboot() -> Result<()> {
|
||||
@@ -229,7 +220,7 @@ pub fn reboot() -> Result<()> {
|
||||
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
|
||||
pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
|
||||
let startup_dir = crate::WINDOWS_DIRS
|
||||
.data_dir()
|
||||
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
|
||||
|
||||
@@ -111,7 +111,7 @@ impl Powershell {
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
&format!(
|
||||
"Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose",
|
||||
"Start-Process powershell -Verb runAs -ArgumentList 'Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose'",
|
||||
if ctx.config().accept_all_windows_updates() {
|
||||
"-AcceptAll"
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,14 @@ use crate::{
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
|
||||
pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
|
||||
let tpm = HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins").require()?;
|
||||
let tpm = match env::var("TMUX_PLUGIN_MANAGER_PATH") {
|
||||
// If `TMUX_PLUGIN_MANAGER_PATH` is set, search for
|
||||
// `$TMUX_PLUGIN_MANAGER_PATH/bin/install_plugins/tpm/bin/update_plugins`
|
||||
Ok(var) => PathBuf::from(var).join("bin/install_plugins/tpm/bin/update_plugins"),
|
||||
// Otherwise, use the default location `~/.tmux/plugins/tpm/bin/update_plugins`
|
||||
Err(_) => HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins"),
|
||||
}
|
||||
.require()?;
|
||||
|
||||
print_separator("tmux plugins");
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ if exists(":AstroUpdate")
|
||||
quitall
|
||||
endif
|
||||
|
||||
if exists(":MasonUpdate")
|
||||
echo "MasonUpdate"
|
||||
MasonUpdate
|
||||
endif
|
||||
|
||||
if exists(":NeoBundleUpdate")
|
||||
echo "NeoBundle"
|
||||
NeoBundleUpdate
|
||||
|
||||
@@ -57,8 +57,8 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
|
||||
let status = output.status;
|
||||
|
||||
if !status.success() || ctx.config().verbose() {
|
||||
io::stdout().write(&output.stdout).ok();
|
||||
io::stderr().write(&output.stderr).ok();
|
||||
io::stdout().write_all(&output.stdout).ok();
|
||||
io::stderr().write_all(&output.stderr).ok();
|
||||
}
|
||||
|
||||
if !status.success() {
|
||||
|
||||
@@ -8,10 +8,12 @@ use walkdir::WalkDir;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::git::Repositories;
|
||||
use crate::git::RepoStep;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require, PathExt};
|
||||
use crate::HOME_DIR;
|
||||
use crate::XDG_DIRS;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
|
||||
pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
|
||||
let zsh = require("zsh")?;
|
||||
@@ -117,15 +119,12 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
env::var("ZINIT_HOME")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| HOME_DIR.join(".zinit"))
|
||||
.unwrap_or_else(|_| XDG_DIRS.data_dir().join("zinit"))
|
||||
.require()?;
|
||||
|
||||
print_separator("zinit");
|
||||
|
||||
let cmd = format!(
|
||||
"source {} && zinit self-update && zinit update --all -p",
|
||||
zshrc.display(),
|
||||
);
|
||||
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display());
|
||||
ctx.run_type()
|
||||
.execute(zsh)
|
||||
.args(["-i", "-c", cmd.as_str()])
|
||||
@@ -140,7 +139,7 @@ pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("zi");
|
||||
|
||||
let cmd = format!("source {} && zi self-update && zi update --all -p", zshrc.display(),);
|
||||
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display());
|
||||
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
||||
}
|
||||
|
||||
@@ -220,19 +219,14 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
debug!("oh-my-zsh custom dir: {}", custom_dir.display());
|
||||
|
||||
let mut custom_repos = Repositories::new(ctx.git());
|
||||
let mut custom_repos = RepoStep::try_new()?;
|
||||
|
||||
for entry in WalkDir::new(custom_dir).max_depth(2) {
|
||||
let entry = entry?;
|
||||
custom_repos.insert_if_repo(entry.path());
|
||||
}
|
||||
|
||||
custom_repos.remove(&oh_my_zsh.to_string_lossy());
|
||||
if !custom_repos.is_empty() {
|
||||
println!("Pulling custom plugins and themes");
|
||||
ctx.git().multi_pull(&custom_repos, ctx)?;
|
||||
}
|
||||
|
||||
custom_repos.remove(&oh_my_zsh);
|
||||
ctx.run_type()
|
||||
.execute("zsh")
|
||||
.arg(&oh_my_zsh.join("tools/upgrade.sh"))
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::process::Command;
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use tracing::{debug, error};
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::reload::{Handle, Layer};
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
@@ -239,10 +238,7 @@ pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Regi
|
||||
.or_else(|_| EnvFilter::try_from_default_env())
|
||||
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
|
||||
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(false)
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.without_time();
|
||||
let fmt_layer = fmt::layer().with_target(false).without_time();
|
||||
|
||||
let (filter, reload_handle) = Layer::new(env_filter);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user