Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41c6d1cd9a | ||
|
|
cf3893dc49 | ||
|
|
a2fbe92a25 | ||
|
|
e1754707d8 | ||
|
|
cd380a53b3 | ||
|
|
a8c29fd1a2 | ||
|
|
6b871e7949 | ||
|
|
1b5fdb6645 | ||
|
|
fe9d877cdf | ||
|
|
60e7aa8f03 | ||
|
|
18e2d3e59c | ||
|
|
d68fcb08b2 | ||
|
|
1f6baefdc3 | ||
|
|
71efce32c1 | ||
|
|
3626c9cdc8 | ||
|
|
a23b761304 | ||
|
|
3fd27e4913 | ||
|
|
b3f152b716 | ||
|
|
df381f3a79 | ||
|
|
2dec9db310 | ||
|
|
d50dc4c9f6 | ||
|
|
ed8b563f20 | ||
|
|
2a73aa731d | ||
|
|
4dd1c13bd8 | ||
|
|
c1c9fe22df | ||
|
|
06a6b7a2eb | ||
|
|
b814dd824f | ||
|
|
ce234bdb59 | ||
|
|
13a46a44a8 | ||
|
|
dc78b00c3c | ||
|
|
48ae4bf813 | ||
|
|
a50040e2d5 | ||
|
|
2c9a56a8df | ||
|
|
021320b292 | ||
|
|
9d3662c3ea | ||
|
|
8e580457a5 | ||
|
|
5350658dab | ||
|
|
1ec0ac50a5 | ||
|
|
635bfce198 | ||
|
|
1307d2d7e8 | ||
|
|
d21141fefe | ||
|
|
0ec0e5a9dd | ||
|
|
9415d7c61f | ||
|
|
42188af02b | ||
|
|
e9581bcf15 | ||
|
|
6afe4f51c6 | ||
|
|
f623746d6c | ||
|
|
1ce4d66e74 | ||
|
|
3735d5c537 | ||
|
|
f3b1d2dfb3 | ||
|
|
7f7d2633cd | ||
|
|
afd95e3d5c | ||
|
|
8f72545894 | ||
|
|
d0d447deac | ||
|
|
53a8683788 | ||
|
|
81491a8d03 | ||
|
|
83504754ac | ||
|
|
2068c2c169 | ||
|
|
dbac121a90 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -46,6 +46,18 @@ If you know the possible cause of the issue, please tell us.
|
||||
Execute the erroneous command directly to see if the problem persists
|
||||
-->
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Did you run topgrade through `Remote Execution`
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
If yes, does the issue still occur when you run topgrade directlly in your
|
||||
remote host
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
## Configuration file (Optional)
|
||||
<!--
|
||||
|
||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -7,7 +7,12 @@
|
||||
- [ ] The code passes clippy (`cargo clippy`)
|
||||
- [ ] The code passes tests (`cargo test`)
|
||||
- [ ] *Optional:* I have tested the code myself
|
||||
- [ ] I also tested that Topgrade skips the step where needed
|
||||
|
||||
## 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
|
||||
the underlying command
|
||||
|
||||
If you developed a feature or a bug fix for someone else and you do not have the
|
||||
means to test it, please tag this person here.
|
||||
|
||||
2
.github/workflows/check-and-lint.yaml
vendored
2
.github/workflows/check-and-lint.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUST_VER: '1.68.0'
|
||||
RUST_VER: 'stable'
|
||||
CROSS_VER: '0.2.5'
|
||||
CARGO_NET_RETRY: 3
|
||||
|
||||
|
||||
@@ -57,10 +57,3 @@ jobs:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./lcov.info
|
||||
fail_ci_if_error: true
|
||||
- name: Test creation of config file
|
||||
run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
2
.github/workflows/crates-publish.yml
vendored
2
.github/workflows/crates-publish.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [published, edited]
|
||||
types: [published]
|
||||
|
||||
name: Publish to crates.io on release
|
||||
|
||||
|
||||
21
.github/workflows/test-config-creation.yml
vendored
Normal file
21
.github/workflows/test-config-creation.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Test Configuration File Creation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
||||
jobs:
|
||||
TestConfig:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
99
.github/workflows/update_pypi.yml
vendored
Normal file
99
.github/workflows/update_pypi.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Update PyPi
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --skip-existing *
|
||||
@@ -1,16 +1,19 @@
|
||||
## Contributing to `topgrade`
|
||||
|
||||
Thank you for your interest in contributing to `topgrade`! We welcome and encourage
|
||||
contributions of all kinds, such as:
|
||||
Thank you for your interest in contributing to `topgrade`!
|
||||
We welcome and encourage contributions of all kinds, such as:
|
||||
|
||||
1. Issue reports or feature requests
|
||||
2. Documentation improvements
|
||||
3. Code (PR or PR Review)
|
||||
|
||||
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
||||
for commit messages.
|
||||
|
||||
## Adding a new `step`
|
||||
|
||||
In `topgrade`'s term, package manager is called `step`. To add a new `step` to
|
||||
`topgrade`:
|
||||
In `topgrade`'s term, package manager is called `step`.
|
||||
To add a new `step` to `topgrade`:
|
||||
|
||||
1. Add a new variant to
|
||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
|
||||
|
||||
132
Cargo.lock
generated
132
Cargo.lock
generated
@@ -17,17 +17,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -406,6 +395,28 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"proc-macro-hack",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
@@ -439,6 +450,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -513,9 +530,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
@@ -624,7 +644,7 @@ checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -814,9 +834,12 @@ name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -980,7 +1003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1291,12 +1314,12 @@ checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.4.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown",
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1420,6 +1443,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
@@ -1495,6 +1524,15 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
@@ -1502,7 +1540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1541,15 +1579,6 @@ version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.18"
|
||||
@@ -1612,9 +1641,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||
checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
@@ -1628,9 +1657,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.20"
|
||||
version = "0.37.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
|
||||
checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@@ -1663,9 +1692,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.1"
|
||||
version = "0.100.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
|
||||
checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
@@ -1947,16 +1976,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
version = "3.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2042,6 +2071,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@@ -2126,7 +2164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "topgrade"
|
||||
version = "12.0.0"
|
||||
version = "13.0.0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
@@ -2439,9 +2477,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
|
||||
@@ -5,7 +5,7 @@ categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
version = "12.0.0"
|
||||
version = "13.0.0"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif"]
|
||||
edition = "2021"
|
||||
@@ -37,7 +37,7 @@ chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.24", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.2"
|
||||
tempfile = "~3.6"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
@@ -63,7 +63,7 @@ depends = "$auto,git"
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "~0.2"
|
||||
nix = "~0.24"
|
||||
rust-ini = "~0.18"
|
||||
rust-ini = "~0.19"
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
10
README.md
10
README.md
@@ -8,13 +8,9 @@
|
||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||
|
||||
<img alt="Demo" src="doc/screenshot.gif" width="550px">
|
||||
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||
</div>
|
||||
|
||||
## Maintainers Wanted
|
||||
|
||||
I currently have not enough time to maintain this project on the level required and which the project deserves. For this reason I'm asking the community to help supporting the project, to help and work on resolving issues and create new features. Thanks for all your help.
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -34,11 +30,13 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
- 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)
|
||||
- PyPi: [pip](https://pypi.org/project/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.
|
||||
|
||||
Topgrade requires Rust 1.60 or above.
|
||||
> Currently, Topgrade requires Rust 1.65 or above. In general, Topgrade tracks
|
||||
> the latest stable toolchain.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,144 +1,240 @@
|
||||
# Include any additional configuration file(s)
|
||||
# [include] sections are processed in the order you write them
|
||||
# Files in $CONFIG_DIR/topgrade/topgrade.d/ are automatically included at the beginning of this file
|
||||
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
||||
[include]
|
||||
#paths = ["/etc/topgrade.toml"]
|
||||
# paths = ["/etc/topgrade.toml"]
|
||||
|
||||
|
||||
[misc]
|
||||
# Don't ask for confirmations
|
||||
#assume_yes = true
|
||||
|
||||
# Disable specific steps - same options as the command line flag
|
||||
#disable = ["system", "emacs"]
|
||||
|
||||
# Ignore failures for these steps
|
||||
#ignore_failures = ["powershell"]
|
||||
|
||||
# Run specific steps - same options as the command line flag
|
||||
#only = ["system", "emacs"]
|
||||
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
#no_retry = true
|
||||
# Run `sudo -v` to cache credentials at the start of the run
|
||||
# This avoids a blocking password prompt in the middle of an unattended run
|
||||
# (default: false)
|
||||
# pre_sudo = false
|
||||
|
||||
# Sudo command to be used
|
||||
#sudo_command = "sudo"
|
||||
# sudo_command = "sudo"
|
||||
|
||||
# Run `sudo -v` to cache credentials at the start of the run; this avoids a
|
||||
# blocking password prompt in the middle of a possibly-unattended run.
|
||||
#pre_sudo = false
|
||||
# Disable specific steps - same options as the command line flag
|
||||
# disable = ["system", "emacs"]
|
||||
|
||||
# Run inside tmux
|
||||
#run_in_tmux = true
|
||||
# Ignore failures for these steps
|
||||
# ignore_failures = ["powershell"]
|
||||
|
||||
# List of remote machines with Topgrade installed on them
|
||||
#remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Arguments to pass SSH when upgrading remote systems
|
||||
#ssh_arguments = "-o ConnectTimeout=2"
|
||||
# remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Path to Topgrade executable on remote machines
|
||||
#remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
# remote_topgrade_path = ".cargo/bin/topgrade"
|
||||
|
||||
# Arguments to pass to SSH when upgrading remote systems
|
||||
# ssh_arguments = "-o ConnectTimeout=2"
|
||||
|
||||
# Arguments to pass tmux when pulling Repositories
|
||||
#tmux_arguments = "-S /var/tmux.sock"
|
||||
# tmux_arguments = "-S /var/tmux.sock"
|
||||
|
||||
# Do not set the terminal title
|
||||
#set_title = false
|
||||
# Do not set the terminal title (dfault: true)
|
||||
# set_title = true
|
||||
|
||||
# Display the time in step titles
|
||||
# Display the time in step titles (default: true)
|
||||
# display_time = true
|
||||
|
||||
# Cleanup temporary or old files
|
||||
#cleanup = true
|
||||
# Don't ask for confirmations (no default value)
|
||||
# assume_yes = true
|
||||
|
||||
# Skip sending a notification at the end of a run
|
||||
#skip_notify = true
|
||||
# Do not ask to retry failed steps (default: false)
|
||||
# no_retry = true
|
||||
|
||||
# Whether to self update (this is ignored if the binary has been built without self update support, available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||
#no_self_update = true
|
||||
# Run inside tmux (default: false)
|
||||
# run_in_tmux = true
|
||||
|
||||
[git]
|
||||
#max_concurrency = 5
|
||||
# Additional git repositories to pull
|
||||
#repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
#]
|
||||
# Cleanup temporary or old files (default: false)
|
||||
# cleanup = true
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
#pull_predefined = false
|
||||
# Send a notification for every step (default: false)
|
||||
# notify_each_step = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
#arguments = "--rebase --autostash"
|
||||
# Skip sending a notification at the end of a run (default: false)
|
||||
# skip_notify = true
|
||||
|
||||
# The Bash-it branch to update (default: "stable")
|
||||
# bashit_branch = "stable"
|
||||
|
||||
# Run specific steps - same options as the command line flag
|
||||
# only = ["system", "emacs"]
|
||||
|
||||
# Whether to self update
|
||||
#
|
||||
# this will be ignored if the binary is built without self update support
|
||||
#
|
||||
# available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||
# no_self_update = true
|
||||
|
||||
# Extra tracing filter directives
|
||||
# These are prepended to the `--log-filter` argument
|
||||
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
# log_filters = ["topgrade::command=debug", "warn"]
|
||||
|
||||
[composer]
|
||||
#self_update = true
|
||||
|
||||
# Commands to run before anything
|
||||
[pre_commands]
|
||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
|
||||
# Commands to run after anything
|
||||
[post_commands]
|
||||
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
|
||||
# Custom commands
|
||||
[commands]
|
||||
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
#"Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||
# "Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
# "Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||
|
||||
[brew]
|
||||
#greedy_cask = true
|
||||
#autoremove = true
|
||||
|
||||
[linux]
|
||||
# Arch Package Manager to use. Allowed values: autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay.
|
||||
#arch_package_manager = "pacman"
|
||||
# Arguments to pass yay (or paru) when updating packages
|
||||
#yay_arguments = "--nodevel"
|
||||
# Arguments to pass dnf when updating packages
|
||||
#dnf_arguments = "--refresh"
|
||||
#aura_aur_arguments = "-kx"
|
||||
#aura_pacman_arguments = ""
|
||||
#garuda_update_arguments = ""
|
||||
#show_arch_news = true
|
||||
#trizen_arguments = "--devel"
|
||||
#pikaur_arguments = ""
|
||||
#pamac_arguments = "--no-devel"
|
||||
#enable_tlmgr = true
|
||||
#emerge_sync_flags = "-q"
|
||||
#emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
#redhat_distro_sync = false
|
||||
#suse_dup = false
|
||||
#rpm_ostree = false
|
||||
#nix_arguments = "--flake"
|
||||
|
||||
[python]
|
||||
#enable_pip_review = true ###disabled by default
|
||||
#enable_pip_review_local = true ###disabled by default
|
||||
#enable_pipupgrade = true ###disabled by default
|
||||
#pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
# enable_pip_review = true ###disabled by default
|
||||
# enable_pip_review_local = true ###disabled by default
|
||||
# enable_pipupgrade = true ###disabled by default
|
||||
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
|
||||
|
||||
[composer]
|
||||
# self_update = true
|
||||
|
||||
|
||||
[brew]
|
||||
# greedy_cask = true
|
||||
# autoremove = true
|
||||
|
||||
|
||||
[linux]
|
||||
# Arch Package Manager to use.
|
||||
# Allowed values:
|
||||
# autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay
|
||||
# arch_package_manager = "pacman"
|
||||
|
||||
# Arguments to pass yay (or paru) when updating packages
|
||||
# yay_arguments = "--nodevel"
|
||||
|
||||
# Arguments to pass dnf when updating packages
|
||||
# dnf_arguments = "--refresh"
|
||||
|
||||
# aura_aur_arguments = "-kx"
|
||||
|
||||
# aura_pacman_arguments = ""
|
||||
# garuda_update_arguments = ""
|
||||
|
||||
# show_arch_news = true
|
||||
|
||||
# trizen_arguments = "--devel"
|
||||
|
||||
# pikaur_arguments = ""
|
||||
|
||||
# pamac_arguments = "--no-devel"
|
||||
|
||||
# enable_tlmgr = true
|
||||
|
||||
# emerge_sync_flags = "-q"
|
||||
|
||||
# emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||
|
||||
# redhat_distro_sync = false
|
||||
|
||||
# suse_dup = false
|
||||
|
||||
# rpm_ostree = false
|
||||
|
||||
# nix_arguments = "--flake"
|
||||
|
||||
# nix_env_arguments = "--prebuilt-only"
|
||||
|
||||
# Extra Home Manager arguments
|
||||
# home_manager_arguments = ["--flake", "file"]
|
||||
|
||||
|
||||
[git]
|
||||
# max_concurrency = 5
|
||||
|
||||
# Git repositories that you want to pull and push
|
||||
# repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
# ]
|
||||
|
||||
# Repositories that you only want to pull
|
||||
# pull_only_repos = [
|
||||
# "~/.config/something_else"
|
||||
# ]
|
||||
|
||||
# Repositories that you only want to push
|
||||
# push_only_repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something_third"
|
||||
# ]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
# pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling repositories
|
||||
# pull_arguments = "--rebase --autostash"
|
||||
|
||||
# Arguments to pass Git when pushing repositories
|
||||
# push_arguments = "--all"
|
||||
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
#accept_all_updates = false
|
||||
#open_remotes_in_new_terminal = true
|
||||
#wsl_update_pre_release = true
|
||||
#wsl_update_use_web_download = true
|
||||
# accept_all_updates = false
|
||||
|
||||
# open_remotes_in_new_terminal = true
|
||||
|
||||
# wsl_update_pre_release = true
|
||||
|
||||
# wsl_update_use_web_download = true
|
||||
|
||||
# Causes Topgrade to rename itself during the run to allow package managers
|
||||
# to upgrade it. Use this only if you installed Topgrade by using a package
|
||||
# manager such as Scoop or Cargo
|
||||
#self_rename = true
|
||||
# self_rename = true
|
||||
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
#use_sudo = true
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[yarn]
|
||||
# Run `yarn global upgrade` with `sudo`
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[vim]
|
||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||
# force_plug_update = true
|
||||
|
||||
|
||||
[firmware]
|
||||
# Offer to update firmware; if false just check for and display available updates
|
||||
#upgrade = true
|
||||
# upgrade = true
|
||||
|
||||
|
||||
[vagrant]
|
||||
# Vagrant directories
|
||||
# directories = []
|
||||
|
||||
# power on vagrant boxes if needed
|
||||
# power_on = true
|
||||
|
||||
# Always suspend vagrant boxes instead of powering off
|
||||
# always_suspend = true
|
||||
|
||||
|
||||
[flatpak]
|
||||
# Use sudo for updating the system-wide installation
|
||||
#use_sudo = true
|
||||
# use_sudo = true
|
||||
|
||||
|
||||
[distrobox]
|
||||
#use_root = false
|
||||
#containers = ["archlinux-latest"]
|
||||
# use_root = false
|
||||
|
||||
# containers = ["archlinux-latest"]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 718 KiB |
BIN
doc/topgrade_demo.gif
Normal file
BIN
doc/topgrade_demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
16
pyproject.toml
Normal file
16
pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "topgrade"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
@@ -10,6 +10,8 @@ use color_eyre::eyre::Context;
|
||||
|
||||
use crate::error::TopgradeError;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
/// Like [`Output`], but UTF-8 decoded.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Utf8Output {
|
||||
@@ -183,7 +185,7 @@ impl CommandExt for Command {
|
||||
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
|
||||
|
||||
let ret = Err(err).with_context(|| message);
|
||||
tracing::debug!("Command failed: {ret:?}");
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -203,7 +205,7 @@ impl CommandExt for Command {
|
||||
let (program, _) = get_program_and_args(self);
|
||||
let err = TopgradeError::ProcessFailed(program, status);
|
||||
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
|
||||
tracing::debug!("Command failed: {ret:?}");
|
||||
debug!("Command failed: {ret:?}");
|
||||
ret
|
||||
}
|
||||
}
|
||||
@@ -239,6 +241,6 @@ fn format_program_and_args(cmd: &Command) -> String {
|
||||
|
||||
fn log(cmd: &Command) -> String {
|
||||
let command = format_program_and_args(cmd);
|
||||
tracing::debug!("Executing command `{command}`");
|
||||
debug!("Executing command `{command}`");
|
||||
command
|
||||
}
|
||||
|
||||
275
src/config.rs
275
src/config.rs
@@ -17,17 +17,19 @@ use regex::Regex;
|
||||
use regex_split::RegexSplit;
|
||||
use serde::Deserialize;
|
||||
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
||||
use tracing::debug;
|
||||
use which_crate::which;
|
||||
|
||||
use super::utils::{editor, hostname};
|
||||
use crate::command::CommandExt;
|
||||
use crate::sudo::SudoKind;
|
||||
use crate::utils::string_prepend_str;
|
||||
|
||||
use super::utils::{editor, hostname};
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
||||
|
||||
/// Topgrade's default log level.
|
||||
pub const DEFAULT_LOG_LEVEL: &str = "warn";
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! str_value {
|
||||
($section:ident, $value:ident) => {
|
||||
@@ -40,57 +42,6 @@ macro_rules! str_value {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_deprecated {
|
||||
($config:expr, $old:ident, $section:ident, $new:ident) => {
|
||||
if $config.$old.is_some() {
|
||||
println!(concat!(
|
||||
"'",
|
||||
stringify!($old),
|
||||
"' configuration option is deprecated. Rename it to '",
|
||||
stringify!($new),
|
||||
"' and put it under the section [",
|
||||
stringify!($section),
|
||||
"]",
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Get a deprecated option moved from a section to another
|
||||
macro_rules! get_deprecated_moved_opt {
|
||||
($old_section:expr, $old:ident, $new_section:expr, $new:ident) => {{
|
||||
if let Some(old_section) = &$old_section {
|
||||
if old_section.$old.is_some() {
|
||||
return &old_section.$old;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_section) = &$new_section {
|
||||
return &new_section.$new;
|
||||
}
|
||||
|
||||
return &None;
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! get_deprecated_moved_or_default_to {
|
||||
($old_section:expr, $old:ident, $new_section:expr, $new:ident, $default_ret:ident) => {{
|
||||
if let Some(old_section) = &$old_section {
|
||||
if let Some(old) = old_section.$old {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(new_section) = &$new_section {
|
||||
if let Some(new) = new_section.$new {
|
||||
return new;
|
||||
}
|
||||
}
|
||||
|
||||
return $default_ret;
|
||||
}};
|
||||
}
|
||||
|
||||
pub type Commands = BTreeMap<String, String>;
|
||||
|
||||
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||
@@ -143,8 +94,10 @@ pub enum Step {
|
||||
Kakoune,
|
||||
Helix,
|
||||
Krew,
|
||||
Lure,
|
||||
Macports,
|
||||
Mamba,
|
||||
Miktex,
|
||||
Mas,
|
||||
Maza,
|
||||
Micro,
|
||||
@@ -174,6 +127,7 @@ pub enum Step {
|
||||
Rustup,
|
||||
Scoop,
|
||||
Sdkman,
|
||||
SelfUpdate,
|
||||
Sheldon,
|
||||
Shell,
|
||||
Snap,
|
||||
@@ -189,6 +143,7 @@ pub enum Step {
|
||||
Vagrant,
|
||||
Vcpkg,
|
||||
Vim,
|
||||
Vscode,
|
||||
Winget,
|
||||
Wsl,
|
||||
WslUpdate,
|
||||
@@ -209,10 +164,17 @@ pub struct Git {
|
||||
max_concurrency: Option<usize>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
arguments: Option<String>,
|
||||
pull_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
push_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
repos: Option<Vec<String>>,
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
pull_only_repos: Option<Vec<String>>,
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
push_only_repos: Option<Vec<String>>,
|
||||
|
||||
pull_predefined: Option<bool>,
|
||||
}
|
||||
@@ -338,6 +300,9 @@ pub struct Linux {
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
nix_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
nix_env_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
apt_arguments: Option<String>,
|
||||
|
||||
@@ -351,6 +316,9 @@ pub struct Linux {
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
emerge_update_flags: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
home_manager_arguments: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -370,10 +338,7 @@ pub struct Vim {
|
||||
pub struct Misc {
|
||||
pre_sudo: Option<bool>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
git_repos: Option<Vec<String>>,
|
||||
|
||||
predefined_git_repos: Option<bool>,
|
||||
sudo_command: Option<SudoKind>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
disable: Option<Vec<Step>>,
|
||||
@@ -389,9 +354,6 @@ pub struct Misc {
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
ssh_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
git_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
tmux_arguments: Option<String>,
|
||||
|
||||
@@ -401,15 +363,6 @@ pub struct Misc {
|
||||
|
||||
assume_yes: Option<bool>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
yay_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
aura_aur_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
aura_pacman_arguments: Option<String>,
|
||||
|
||||
no_retry: Option<bool>,
|
||||
|
||||
run_in_tmux: Option<bool>,
|
||||
@@ -418,8 +371,6 @@ pub struct Misc {
|
||||
|
||||
notify_each_step: Option<bool>,
|
||||
|
||||
accept_all_windows_updates: Option<bool>,
|
||||
|
||||
skip_notify: Option<bool>,
|
||||
|
||||
bashit_branch: Option<String>,
|
||||
@@ -428,6 +379,8 @@ pub struct Misc {
|
||||
only: Option<Vec<Step>>,
|
||||
|
||||
no_self_update: Option<bool>,
|
||||
|
||||
log_filters: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -440,8 +393,6 @@ pub struct ConfigFile {
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
misc: Option<Misc>,
|
||||
|
||||
sudo_command: Option<SudoKind>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||
pre_commands: Option<Commands>,
|
||||
|
||||
@@ -589,11 +540,11 @@ impl ConfigFile {
|
||||
*/
|
||||
for include in dir_include {
|
||||
let include_contents = fs::read_to_string(&include).map_err(|e| {
|
||||
tracing::error!("Unable to read {}", include.display());
|
||||
error!("Unable to read {}", include.display());
|
||||
e
|
||||
})?;
|
||||
let include_contents_parsed = toml::from_str(include_contents.as_str()).map_err(|e| {
|
||||
tracing::error!("Failed to deserialize {}", include.display());
|
||||
error!("Failed to deserialize {}", include.display());
|
||||
e
|
||||
})?;
|
||||
|
||||
@@ -610,7 +561,7 @@ impl ConfigFile {
|
||||
}
|
||||
|
||||
let mut contents_non_split = fs::read_to_string(&config_path).map_err(|e| {
|
||||
tracing::error!("Unable to read {}", config_path.display());
|
||||
error!("Unable to read {}", config_path.display());
|
||||
e
|
||||
})?;
|
||||
|
||||
@@ -623,7 +574,7 @@ impl ConfigFile {
|
||||
|
||||
for contents in contents_split {
|
||||
let config_file_include_only: ConfigFileIncludeOnly = toml::from_str(contents).map_err(|e| {
|
||||
tracing::error!("Failed to deserialize an include section of {}", config_path.display());
|
||||
error!("Failed to deserialize an include section of {}", config_path.display());
|
||||
e
|
||||
})?;
|
||||
|
||||
@@ -636,38 +587,24 @@ impl ConfigFile {
|
||||
let include_contents = match fs::read_to_string(&include_path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("Unable to read {}: {}", include_path.display(), e);
|
||||
error!("Unable to read {}: {}", include_path.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match toml::from_str::<Self>(&include_contents) {
|
||||
Ok(include_parsed) => result.merge(include_parsed),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to deserialize {}: {}", include_path.display(), e);
|
||||
error!("Failed to deserialize {}: {}", include_path.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Configuration include found: {}", include_path.display());
|
||||
}
|
||||
} else {
|
||||
debug!("No include paths found in {}", config_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
match toml::from_str::<Self>(contents) {
|
||||
Ok(contents) => result.merge(contents),
|
||||
Err(e) => tracing::error!("Failed to deserialize {}: {}", config_path.display(), e),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(misc) = &mut result.misc {
|
||||
if let Some(ref mut paths) = &mut misc.git_repos {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!("Path {} expanded to {}", path, expanded);
|
||||
*path = expanded;
|
||||
}
|
||||
Err(e) => error!("Failed to deserialize {}: {}", config_path.display(), e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -679,7 +616,21 @@ impl ConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Loaded configuration: {:?}", result);
|
||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.pull_only_repos.as_mut()) {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!("Path {} expanded to {}", path, expanded);
|
||||
*path = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.push_only_repos.as_mut()) {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!("Path {} expanded to {}", path, expanded);
|
||||
*path = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -798,7 +749,7 @@ pub struct CommandLineArgs {
|
||||
/// Tracing filter directives.
|
||||
///
|
||||
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
|
||||
#[clap(long, default_value = "warn")]
|
||||
#[clap(long, default_value = DEFAULT_LOG_LEVEL)]
|
||||
pub log_filter: String,
|
||||
|
||||
/// Print completion script for the given shell and exit
|
||||
@@ -827,12 +778,25 @@ impl CommandLineArgs {
|
||||
&self.env
|
||||
}
|
||||
|
||||
/// In Topgrade, filter directives come from 3 places:
|
||||
/// 1. CLI option `--log-filter`
|
||||
/// 2. Config file
|
||||
/// 3. `debug` if the `--verbose` option is present
|
||||
///
|
||||
/// Before loading the configuration file, we need our logger to work, so this
|
||||
/// function will return directives coming from part 1 and 2.
|
||||
///
|
||||
///
|
||||
/// When the configuration file is loaded, `Config::tracing_filter_directives()`
|
||||
/// will return all the 3 parts.
|
||||
pub fn tracing_filter_directives(&self) -> String {
|
||||
let mut ret = self.log_filter.clone();
|
||||
if self.verbose {
|
||||
"debug".into()
|
||||
} else {
|
||||
self.log_filter.clone()
|
||||
ret.push(',');
|
||||
ret.push_str("debug");
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,6 +805,7 @@ impl CommandLineArgs {
|
||||
/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line.
|
||||
/// Its provided methods decide the appropriate options based on combining the configuration file and the
|
||||
/// command line arguments.
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
opt: CommandLineArgs,
|
||||
config_file: ConfigFile,
|
||||
@@ -857,7 +822,7 @@ impl Config {
|
||||
ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| {
|
||||
// Inform the user about errors when loading the configuration,
|
||||
// but fallback to the default config to at least attempt to do something
|
||||
tracing::error!("failed to load configuration: {}", e);
|
||||
error!("failed to load configuration: {}", e);
|
||||
ConfigFile::default()
|
||||
})
|
||||
} else {
|
||||
@@ -865,14 +830,6 @@ impl Config {
|
||||
ConfigFile::default()
|
||||
};
|
||||
|
||||
if let Some(misc) = &config_file.misc {
|
||||
check_deprecated!(misc, git_arguments, git, arguments);
|
||||
check_deprecated!(misc, git_repos, git, repos);
|
||||
check_deprecated!(misc, predefined_git_repos, git, pull_predefined);
|
||||
check_deprecated!(misc, yay_arguments, linux, yay_arguments);
|
||||
check_deprecated!(misc, accept_all_windows_updates, windows, accept_all_updates);
|
||||
}
|
||||
|
||||
let allowed_steps = Self::allowed_steps(&opt, &config_file);
|
||||
|
||||
Ok(Self {
|
||||
@@ -902,9 +859,23 @@ impl Config {
|
||||
&self.config_file.commands
|
||||
}
|
||||
|
||||
/// The list of git repositories to push and pull.
|
||||
pub fn git_repos(&self) -> Option<&Vec<String>> {
|
||||
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
|
||||
}
|
||||
/// The list of additional git repositories to pull.
|
||||
pub fn git_repos(&self) -> &Option<Vec<String>> {
|
||||
get_deprecated_moved_opt!(&self.config_file.misc, git_repos, &self.config_file.git, repos)
|
||||
pub fn git_pull_only_repos(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.pull_only_repos.as_ref())
|
||||
}
|
||||
/// The list of git repositories to push.
|
||||
pub fn git_push_only_repos(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.push_only_repos.as_ref())
|
||||
}
|
||||
|
||||
/// Tell whether the specified step should run.
|
||||
@@ -1015,9 +986,19 @@ impl Config {
|
||||
.and_then(|misc| misc.ssh_arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Extra Git arguments
|
||||
pub fn git_arguments(&self) -> &Option<String> {
|
||||
get_deprecated_moved_opt!(&self.config_file.misc, git_arguments, &self.config_file.git, arguments)
|
||||
/// Extra Git arguments for when pushing
|
||||
pub fn push_git_arguments(&self) -> Option<&String> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.push_arguments.as_ref())
|
||||
}
|
||||
/// Extra Git arguments for when pulling
|
||||
pub fn pull_git_arguments(&self) -> Option<&String> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.pull_arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Extra Tmux arguments
|
||||
@@ -1090,13 +1071,11 @@ impl Config {
|
||||
|
||||
/// Whether to accept all Windows updates
|
||||
pub fn accept_all_windows_updates(&self) -> bool {
|
||||
get_deprecated_moved_or_default_to!(
|
||||
&self.config_file.misc,
|
||||
accept_all_windows_updates,
|
||||
&self.config_file.windows,
|
||||
accept_all_updates,
|
||||
true
|
||||
)
|
||||
self.config_file
|
||||
.windows
|
||||
.as_ref()
|
||||
.and_then(|windows| windows.accept_all_updates)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Whether to self rename the Topgrade executable during the run
|
||||
@@ -1275,6 +1254,22 @@ impl Config {
|
||||
.and_then(|linux| linux.nix_arguments.as_deref())
|
||||
}
|
||||
|
||||
/// Extra nix-env arguments
|
||||
pub fn nix_env_arguments(&self) -> Option<&str> {
|
||||
self.config_file
|
||||
.linux
|
||||
.as_ref()
|
||||
.and_then(|linux| linux.nix_env_arguments.as_deref())
|
||||
}
|
||||
|
||||
/// Extra Home Manager arguments
|
||||
pub fn home_manager(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.linux
|
||||
.as_ref()
|
||||
.and_then(|misc| misc.home_manager_arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Distrobox use root
|
||||
pub fn distrobox_root(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1363,19 +1358,39 @@ impl Config {
|
||||
|
||||
pub fn use_predefined_git_repos(&self) -> bool {
|
||||
!self.opt.disable_predefined_git_repos
|
||||
&& get_deprecated_moved_or_default_to!(
|
||||
&self.config_file.misc,
|
||||
predefined_git_repos,
|
||||
&self.config_file.git,
|
||||
pull_predefined,
|
||||
true
|
||||
)
|
||||
&& self
|
||||
.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.pull_predefined)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn verbose(&self) -> bool {
|
||||
self.opt.verbose
|
||||
}
|
||||
|
||||
/// After loading the config file, filter directives consist of 3 parts:
|
||||
///
|
||||
/// 1. directives from the configuration file
|
||||
/// 2. directives from the CLI options `--log-filter`
|
||||
/// 3. `debug`, which would be enabled if the `--verbose` option is present
|
||||
///
|
||||
/// Previous directive will be overwritten if a directive with the same target
|
||||
/// appear later.
|
||||
pub fn tracing_filter_directives(&self) -> String {
|
||||
let mut ret = String::new();
|
||||
if let Some(directives) = self.config_file.misc.as_ref().and_then(|m| m.log_filters.as_ref()) {
|
||||
ret.push_str(&directives.join(","));
|
||||
}
|
||||
ret.push(',');
|
||||
ret.push_str(&self.opt.log_filter);
|
||||
if self.verbose() {
|
||||
ret.push_str(",debug");
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn show_skipped(&self) -> bool {
|
||||
self.opt.show_skipped
|
||||
}
|
||||
@@ -1389,7 +1404,7 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn sudo_command(&self) -> Option<SudoKind> {
|
||||
self.config_file.sudo_command
|
||||
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
|
||||
}
|
||||
|
||||
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! A stub for Ctrl + C handling.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use tracing::error;
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
||||
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
||||
use winapi::um::wincon::CTRL_C_EVENT;
|
||||
@@ -16,6 +17,6 @@ extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
|
||||
|
||||
pub fn set_handler() {
|
||||
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
||||
tracing::error!("Cannot set a control C handler")
|
||||
error!("Cannot set a control C handler")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ pub enum TopgradeError {
|
||||
#[cfg(target_os = "linux")]
|
||||
UnknownLinuxDistribution,
|
||||
|
||||
#[error("File \"/etc/os-release\" does not exist or is empty")]
|
||||
#[cfg(target_os = "linux")]
|
||||
EmptyOSReleaseFile,
|
||||
|
||||
#[error("Failed getting the system package manager")]
|
||||
#[cfg(target_os = "linux")]
|
||||
FailedGettingPackageManager,
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::sudo::Sudo;
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use crate::{config::Config, executor::Executor};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::env::var;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -17,16 +18,20 @@ pub struct ExecutionContext<'a> {
|
||||
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
|
||||
/// tmux window for each remote.
|
||||
tmux_session: Mutex<Option<String>>,
|
||||
/// True if topgrade is running under ssh.
|
||||
under_ssh: bool,
|
||||
}
|
||||
|
||||
impl<'a> ExecutionContext<'a> {
|
||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +56,10 @@ impl<'a> ExecutionContext<'a> {
|
||||
self.config
|
||||
}
|
||||
|
||||
pub fn under_ssh(&self) -> bool {
|
||||
self.under_ssh
|
||||
}
|
||||
|
||||
pub fn set_tmux_session(&self, session_name: String) {
|
||||
self.tmux_session.lock().unwrap().replace(session_name);
|
||||
}
|
||||
|
||||
131
src/main.rs
131
src/main.rs
@@ -17,6 +17,8 @@ use etcetera::base_strategy::{BaseStrategy, Xdg};
|
||||
use once_cell::sync::Lazy;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::steps::git::GitAction;
|
||||
|
||||
use self::config::{CommandLineArgs, Config, Step};
|
||||
use self::error::StepFailed;
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
@@ -24,6 +26,8 @@ use self::error::Upgraded;
|
||||
use self::steps::{remote::*, *};
|
||||
use self::terminal::*;
|
||||
|
||||
use self::utils::{install_color_eyre, install_tracing, update_tracing};
|
||||
|
||||
mod command;
|
||||
mod config;
|
||||
mod ctrlc;
|
||||
@@ -47,10 +51,22 @@ pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directo
|
||||
pub static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
||||
|
||||
fn run() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
install_color_eyre()?;
|
||||
ctrlc::set_handler();
|
||||
|
||||
let opt = CommandLineArgs::parse();
|
||||
// Set up the logger with the filter directives from:
|
||||
// 1. CLI option `--log-filter`
|
||||
// 2. `debug` if the `--verbose` option is present
|
||||
// We do this because we need our logger to work while loading the
|
||||
// configuration file.
|
||||
//
|
||||
// When the configuration file is loaded, update the logger with the full
|
||||
// filter directives.
|
||||
//
|
||||
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
|
||||
// and `Config::tracing_filter_directives()`.
|
||||
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
|
||||
|
||||
if let Some(shell) = opt.gen_completion {
|
||||
let cmd = &mut CommandLineArgs::command();
|
||||
@@ -64,8 +80,6 @@ fn run() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
install_tracing(&opt.tracing_filter_directives())?;
|
||||
|
||||
for env in opt.env_variables() {
|
||||
let mut splitted = env.split('=');
|
||||
let var = splitted.next().unwrap();
|
||||
@@ -84,6 +98,8 @@ fn run() -> Result<()> {
|
||||
}
|
||||
|
||||
let config = Config::load(opt)?;
|
||||
// Update the logger with the full filter directives.
|
||||
update_tracing(&reload_handle, &config.tracing_filter_directives())?;
|
||||
set_title(config.set_title());
|
||||
display_time(config.display_time());
|
||||
set_desktop_notifications(config.notify_each_step());
|
||||
@@ -92,7 +108,8 @@ fn run() -> Result<()> {
|
||||
debug!("OS: {}", env!("TARGET"));
|
||||
debug!("{:?}", std::env::args());
|
||||
debug!("Binary path: {:?}", std::env::current_exe());
|
||||
debug!("Self Update: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||
debug!("Configuration: {:?}", config);
|
||||
|
||||
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
|
||||
#[cfg(unix)]
|
||||
@@ -115,22 +132,15 @@ fn run() -> Result<()> {
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
|
||||
let mut runner = runner::Runner::new(&ctx);
|
||||
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
#[cfg(feature = "self-update")]
|
||||
{
|
||||
let config_self_upgrade = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
||||
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
||||
|
||||
if !run_type.dry() && config_self_upgrade {
|
||||
let result = self_update::self_update();
|
||||
|
||||
if let Err(e) = &result {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if e.downcast_ref::<Upgraded>().is_some() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
print_warning(format!("Self update error: {e}"));
|
||||
}
|
||||
if should_self_update {
|
||||
runner.execute(Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,15 +197,10 @@ fn run() -> Result<()> {
|
||||
}
|
||||
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
||||
|
||||
runner.execute(Step::BrewFormula, "Brew", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
|
||||
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
|
||||
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
|
||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
||||
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
||||
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
|
||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
||||
@@ -205,6 +210,12 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
|
||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
||||
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
|
||||
|
||||
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
||||
runner.execute(Step::BrewFormula, "Brew", || {
|
||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||
})?;
|
||||
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -320,8 +331,12 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
||||
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)
|
||||
})?;
|
||||
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
|
||||
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
|
||||
runner.execute(Step::Miktex, "miktex", || generic::run_miktex_packages_update(&ctx))?;
|
||||
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(&ctx))?;
|
||||
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
|
||||
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
|
||||
@@ -369,35 +384,35 @@ fn run() -> Result<()> {
|
||||
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(directory, GitAction::Pull);
|
||||
}
|
||||
}
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"), GitAction::Pull);
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".vim"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"), GitAction::Pull);
|
||||
}
|
||||
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"), GitAction::Pull);
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"), GitAction::Pull);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
git_repos.insert_if_repo(zsh::zshrc());
|
||||
git_repos.insert_if_repo(zsh::zshrc(), GitAction::Pull);
|
||||
if config.should_run(Step::Tmux) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".tmux"), GitAction::Pull);
|
||||
}
|
||||
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"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/fish"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"), GitAction::Pull);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -405,24 +420,39 @@ fn run() -> Result<()> {
|
||||
WINDOWS_DIRS
|
||||
.cache_dir()
|
||||
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
|
||||
GitAction::Pull,
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
windows::insert_startup_scripts(&mut git_repos).ok();
|
||||
|
||||
if let Some(profile) = powershell.profile() {
|
||||
git_repos.insert_if_repo(profile);
|
||||
git_repos.insert_if_repo(profile, GitAction::Pull);
|
||||
}
|
||||
}
|
||||
|
||||
if config.should_run(Step::GitRepos) {
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
git_repos.glob_insert(git_repo);
|
||||
git_repos.glob_insert(git_repo, GitAction::Pull);
|
||||
git_repos.glob_insert(git_repo, GitAction::Push);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(git_pull_only_repos) = config.git_pull_only_repos() {
|
||||
for git_repo in git_pull_only_repos {
|
||||
git_repos.glob_insert(git_repo, GitAction::Pull);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(git_push_only_repos) = config.git_push_only_repos() {
|
||||
for git_repo in git_push_only_repos {
|
||||
git_repos.glob_insert(git_repo, GitAction::Push);
|
||||
}
|
||||
}
|
||||
|
||||
runner.execute(Step::GitRepos, "Git repositories", || {
|
||||
git.multi_pull_step(&git_repos, &ctx)
|
||||
git.multi_repo_step(&git_repos, &ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -544,26 +574,3 @@ fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn install_tracing(filter_directives: &str) -> Result<()> {
|
||||
use tracing_subscriber::fmt;
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
let env_filter = EnvFilter::try_new(filter_directives)
|
||||
.or_else(|_| EnvFilter::try_from_default_env())
|
||||
.or_else(|_| EnvFilter::try_new("info"))?;
|
||||
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(false)
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.without_time();
|
||||
|
||||
let registry = tracing_subscriber::registry();
|
||||
|
||||
registry.with(env_filter).with(fmt_layer).init();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::env;
|
||||
use std::os::unix::process::CommandExt as _;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::config::Step;
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use self_update_crate::backends::github::Update;
|
||||
use self_update_crate::update::UpdateStatus;
|
||||
@@ -11,52 +12,61 @@ use super::terminal::*;
|
||||
#[cfg(windows)]
|
||||
use crate::error::Upgraded;
|
||||
|
||||
pub fn self_update() -> Result<()> {
|
||||
use crate::execution_context::ExecutionContext;
|
||||
|
||||
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Self update");
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
let target = self_update_crate::get_target();
|
||||
let result = Update::configure()
|
||||
.repo_owner("topgrade-rs")
|
||||
.repo_name("topgrade")
|
||||
.target(target)
|
||||
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
||||
.show_output(false)
|
||||
.show_download_progress(true)
|
||||
.current_version(self_update_crate::cargo_crate_version!())
|
||||
.no_confirm(true)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("\nTopgrade upgraded to {}:\n", release.version);
|
||||
if let Some(body) = &release.body {
|
||||
println!("{body}");
|
||||
}
|
||||
if ctx.run_type().dry() {
|
||||
println!("Would self-update");
|
||||
Ok(())
|
||||
} else {
|
||||
println!("Topgrade is up-to-date");
|
||||
}
|
||||
let assume_yes = ctx.config().yes(Step::SelfUpdate);
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
{
|
||||
if result.updated() {
|
||||
print_warning("Respawning...");
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
let target = self_update_crate::get_target();
|
||||
let result = Update::configure()
|
||||
.repo_owner("topgrade-rs")
|
||||
.repo_name("topgrade")
|
||||
.target(target)
|
||||
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
||||
.show_output(true)
|
||||
.show_download_progress(true)
|
||||
.current_version(self_update_crate::cargo_crate_version!())
|
||||
.no_confirm(assume_yes)
|
||||
.build()?
|
||||
.update_extended()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
if let UpdateStatus::Updated(release) = &result {
|
||||
println!("\nTopgrade upgraded to {}:\n", release.version);
|
||||
if let Some(body) = &release.body {
|
||||
println!("{body}");
|
||||
}
|
||||
} else {
|
||||
println!("Topgrade is up-to-date");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = command.status()?;
|
||||
bail!(Upgraded(status));
|
||||
{
|
||||
if result.updated() {
|
||||
print_info("Respawning...");
|
||||
let mut command = Command::new(current_exe?);
|
||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let err = command.exec();
|
||||
bail!(err);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let status = command.status()?;
|
||||
bail!(Upgraded(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[cfg(any(windows))]
|
||||
#[cfg(windows)]
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
@@ -287,7 +287,13 @@ pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("OCaml Package Manager");
|
||||
|
||||
ctx.run_type().execute(&opam).arg("update").status_checked()?;
|
||||
ctx.run_type().execute(&opam).arg("upgrade").status_checked()?;
|
||||
|
||||
let mut command = ctx.run_type().execute(&opam);
|
||||
command.arg("upgrade");
|
||||
if ctx.config().yes(Step::Opam) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
ctx.run_type().execute(&opam).arg("clean").status_checked()?;
|
||||
@@ -318,6 +324,30 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_vscode_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let vscode = require("code")?;
|
||||
print_separator("Visual Studio Code extensions");
|
||||
|
||||
// Vscode does not have CLI command to upgrade all extensions (see https://github.com/microsoft/vscode/issues/56578)
|
||||
// Instead we get the list of installed extensions with `code --list-extensions` command (obtain a line-return separated list of installed extensions)
|
||||
let extensions = Command::new(&vscode)
|
||||
.arg("--list-extensions")
|
||||
.output_checked_utf8()?
|
||||
.stdout;
|
||||
|
||||
// Then we construct the upgrade command: `code --force --install-extension [ext0] --install-extension [ext1] ... --install-extension [extN]`
|
||||
if !extensions.is_empty() {
|
||||
let mut command_args = vec!["--force"];
|
||||
for extension in extensions.split_whitespace() {
|
||||
command_args.extend(["--install-extension", extension]);
|
||||
}
|
||||
|
||||
ctx.run_type().execute(&vscode).args(command_args).status_checked()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pipx = require("pipx")?;
|
||||
print_separator("pipx");
|
||||
@@ -339,7 +369,7 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Conda");
|
||||
|
||||
let mut command = ctx.run_type().execute(conda);
|
||||
command.args(["update", "--all"]);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Conda) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
@@ -360,13 +390,23 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Mamba");
|
||||
|
||||
let mut command = ctx.run_type().execute(mamba);
|
||||
command.args(["update", "--all"]);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Mamba) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
command.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_miktex_packages_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let miktex = require("miktex")?;
|
||||
print_separator("miktex");
|
||||
|
||||
ctx.run_type()
|
||||
.execute(miktex)
|
||||
.args(["packages", "update"])
|
||||
.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let py = require("python").and_then(check_is_python_2_or_shim);
|
||||
let py3 = require("python3").and_then(check_is_python_2_or_shim);
|
||||
@@ -383,7 +423,7 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
Command::new(&python3)
|
||||
.args(["-m", "pip"])
|
||||
.output_checked_utf8()
|
||||
.map_err(|_| SkipStep("pip does not exists".to_string()))?;
|
||||
.map_err(|_| SkipStep("pip does not exist".to_string()))?;
|
||||
|
||||
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
|
||||
Command::new(&python3)
|
||||
@@ -636,6 +676,10 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
.run_type()
|
||||
.execute(&dotnet)
|
||||
.args(["tool", "list", "--global"])
|
||||
// dotnet will print a greeting message on its first run, from this question:
|
||||
// https://stackoverflow.com/q/70493706/14092446
|
||||
// Setting `DOTNET_NOLOGO` to `true` should disable it
|
||||
.env("DOTNET_NOLOGO", "true")
|
||||
.output_checked_utf8()
|
||||
{
|
||||
Ok(output) => output,
|
||||
@@ -720,7 +764,8 @@ pub fn bin_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn spicetify_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let spicetify = require("spicetify")?;
|
||||
// As of 04-07-2023 NixOS packages Spicetify with the `spicetify-cli` binary name
|
||||
let spicetify = require("spicetify").or(require("spicetify-cli"))?;
|
||||
|
||||
print_separator("Spicetify");
|
||||
ctx.run_type().execute(spicetify).arg("upgrade").status_checked()
|
||||
|
||||
150
src/steps/git.rs
150
src/steps/git.rs
@@ -3,6 +3,7 @@ use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
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};
|
||||
@@ -26,21 +27,61 @@ pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum GitAction {
|
||||
Push,
|
||||
Pull,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
repositories: HashSet<String>,
|
||||
pull_repositories: HashSet<String>,
|
||||
push_repositories: HashSet<String>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
if !(output.status.success()) {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
Err(eyre!(stderr))
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stderr = stderr.trim();
|
||||
Err(eyre!("{stderr}"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn push_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
|
||||
println!("{} {}", style("Pushing").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["push", "--porcelain"]);
|
||||
if let Some(extra_arguments) = ctx.config().push_git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let output = command.output().await?;
|
||||
let result = match output.status.success() {
|
||||
true => Ok(()),
|
||||
false => Err(format!("Failed to push {repo}")),
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
println!("{} pushing {}", style("Failed").red().bold(), &repo);
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(eyre!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
@@ -55,7 +96,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
|
||||
.current_dir(&repo)
|
||||
.args(["pull", "--ff-only"]);
|
||||
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
if let Some(extra_arguments) = ctx.config().pull_git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
@@ -66,11 +107,12 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.await?;
|
||||
let result = output_checked_utf8(pull_output).and_then(|_| output_checked_utf8(submodule_output));
|
||||
let result = output_checked_utf8(pull_output)
|
||||
.and_then(|_| output_checked_utf8(submodule_output))
|
||||
.wrap_err_with(|| format!("Failed to pull {repo}"));
|
||||
|
||||
if let Err(message) = &result {
|
||||
if result.is_err() {
|
||||
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
||||
print!("{message}");
|
||||
} else {
|
||||
let after_revision = get_head_revision(git, &repo);
|
||||
|
||||
@@ -170,14 +212,14 @@ impl Git {
|
||||
}
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::NotFound => debug!("{} does not exists", path.as_ref().display()),
|
||||
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
||||
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
||||
},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn multi_repo_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
@@ -188,12 +230,15 @@ impl Git {
|
||||
.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());
|
||||
if repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull or push")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
self.multi_pull(repositories, ctx)
|
||||
self.multi_pull(repositories, ctx)?;
|
||||
self.multi_push(repositories, ctx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -201,7 +246,7 @@ impl Git {
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.repositories
|
||||
.pull_repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
@@ -209,7 +254,7 @@ impl Git {
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.repositories
|
||||
.pull_repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
@@ -236,6 +281,47 @@ impl Git {
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
|
||||
pub fn multi_push(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
let git = self.git.as_ref().unwrap();
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.push_repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would push {}", &repo));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.push_repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
println!(
|
||||
"{} {} because it has no remotes",
|
||||
style("Skipping").yellow().bold(),
|
||||
repo
|
||||
);
|
||||
false
|
||||
}
|
||||
_ => true, // repo has remotes or command to check for remotes has failed. proceed to pull anyway.
|
||||
})
|
||||
.map(|repo| push_repository(repo.clone(), git, ctx));
|
||||
|
||||
let stream_of_futures = if let Some(limit) = ctx.config().git_concurrency_limit() {
|
||||
iter(futures_iterator).buffer_unordered(limit).boxed()
|
||||
} else {
|
||||
futures_iterator.collect::<FuturesUnordered<_>>().boxed()
|
||||
};
|
||||
|
||||
let basic_rt = runtime::Runtime::new()?;
|
||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||
|
||||
let error = results.into_iter().find(|r| r.is_err());
|
||||
error.unwrap_or(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Repositories<'a> {
|
||||
@@ -248,22 +334,27 @@ impl<'a> Repositories<'a> {
|
||||
|
||||
Self {
|
||||
git,
|
||||
repositories: HashSet::new(),
|
||||
bad_patterns: Vec::new(),
|
||||
glob_match_options,
|
||||
pull_repositories: HashSet::new(),
|
||||
push_repositories: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P, action: GitAction) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
self.repositories.insert(repo);
|
||||
match action {
|
||||
GitAction::Push => self.push_repositories.insert(repo),
|
||||
GitAction::Pull => self.pull_repositories.insert(repo),
|
||||
};
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
pub fn glob_insert(&mut self, pattern: &str, action: GitAction) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
for entry in glob {
|
||||
@@ -279,7 +370,7 @@ impl<'a> Repositories<'a> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if self.insert_if_repo(&path) {
|
||||
if self.insert_if_repo(&path, action) {
|
||||
last_git_repo = Some(path);
|
||||
}
|
||||
}
|
||||
@@ -297,14 +388,27 @@ impl<'a> Repositories<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
/// Return true if `pull_repos` and `push_repos` are both empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.repositories.is_empty()
|
||||
self.pull_repositories.is_empty() && self.push_repositories.is_empty()
|
||||
}
|
||||
|
||||
// The following 2 functions are `#[cfg(unix)]` because they are only used in
|
||||
// the `oh-my-zsh` step, which is UNIX-only.
|
||||
|
||||
#[cfg(unix)]
|
||||
/// Return true if `pull_repos` is empty.
|
||||
pub fn pull_is_empty(&self) -> bool {
|
||||
self.pull_repositories.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
/// Remove `path` from `pull_repos`
|
||||
///
|
||||
/// # Panic
|
||||
/// Will panic if `path` is not in the `pull_repos` under a debug build.
|
||||
pub fn remove_from_pull(&mut self, path: &str) {
|
||||
let _removed = self.pull_repositories.remove(path);
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ impl Aura {
|
||||
|
||||
impl ArchPackageManager for Aura {
|
||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = which("sudo").unwrap_or_else(PathBuf::new);
|
||||
let sudo = which("sudo").unwrap_or_default();
|
||||
let mut aur_update = ctx.run_type().execute(&sudo);
|
||||
|
||||
if sudo.ends_with("sudo") {
|
||||
|
||||
@@ -2,22 +2,31 @@ use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::print_separator;
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use crate::Step;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
print_separator("DragonFly BSD Packages");
|
||||
ctx.execute(sudo)
|
||||
.args(["/usr/local/sbin/pkg", "upgrade"])
|
||||
.status_checked()
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
println!();
|
||||
Command::new(sudo)
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
if !Command::new(sudo)
|
||||
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||
.status_checked()?;
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
println!("The package audit was successful, but vulnerable packages still remain on the system");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Distribution {
|
||||
}
|
||||
} else {
|
||||
Ok(Distribution::Fedora)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some("void") => Distribution::Void,
|
||||
@@ -114,10 +114,14 @@ impl Distribution {
|
||||
if PathBuf::from(OS_RELEASE_PATH).exists() {
|
||||
let os_release = Ini::load_from_file(OS_RELEASE_PATH)?;
|
||||
|
||||
if os_release.general_section().is_empty() {
|
||||
return Err(TopgradeError::EmptyOSReleaseFile.into());
|
||||
}
|
||||
|
||||
return Self::parse_os_release(&os_release);
|
||||
}
|
||||
|
||||
Err(TopgradeError::UnknownLinuxDistribution.into())
|
||||
Err(TopgradeError::EmptyOSReleaseFile.into())
|
||||
}
|
||||
|
||||
pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -248,15 +252,18 @@ fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["zypper", "refresh"])
|
||||
.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg("zypper")
|
||||
.arg(if ctx.config().suse_dup() {
|
||||
"dist-upgrade"
|
||||
} else {
|
||||
"update"
|
||||
})
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("zypper");
|
||||
cmd.arg(if ctx.config().suse_dup() {
|
||||
"dist-upgrade"
|
||||
} else {
|
||||
"update"
|
||||
});
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -268,21 +275,26 @@ fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["zypper", "refresh"])
|
||||
.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg("zypper")
|
||||
.arg("dist-upgrade")
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["zypper", "dist-upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["transactional-update", "dup"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("transactional-update");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-n");
|
||||
}
|
||||
|
||||
cmd.arg("dup").status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -305,6 +317,7 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let mut command_update = ctx.run_type().execute(sudo);
|
||||
@@ -321,11 +334,13 @@ fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
command_update.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(&which("apt-get").unwrap())
|
||||
.arg("dist-upgrade")
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg(&which("apt-get").unwrap());
|
||||
cmd.arg("dist-upgrade");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -497,10 +512,12 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["eopkg", "upgrade"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("eopkg");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.arg("upgrade").status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -539,10 +556,12 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
|
||||
let new_version = string.contains("version: 1");
|
||||
|
||||
if new_version {
|
||||
ctx.run_type()
|
||||
.execute(&pacdef)
|
||||
.args(["package", "sync"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(&pacdef);
|
||||
cmd.args(["package", "sync"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
println!();
|
||||
ctx.run_type()
|
||||
@@ -550,7 +569,13 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["package", "review"])
|
||||
.status_checked()?;
|
||||
} else {
|
||||
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(&pacdef);
|
||||
cmd.arg("sync");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
println!();
|
||||
ctx.run_type().execute(&pacdef).arg("review").status_checked()?;
|
||||
@@ -596,10 +621,12 @@ pub fn run_packer_nu(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["swupd", "update"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["swupd", "update"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--assume=yes");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -682,14 +709,52 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `needrestart` should be skipped if:
|
||||
///
|
||||
/// 1. This is a redhat-based distribution
|
||||
/// 2. This is a debian-based distribution and it is using `nala` as the `apt`
|
||||
/// alternative
|
||||
fn should_skip_needrestart() -> Result<()> {
|
||||
let distribution = Distribution::detect()?;
|
||||
let msg = "needrestart will be ran by the package manager";
|
||||
|
||||
if distribution.redhat_based() {
|
||||
return Err(SkipStep(String::from(msg)).into());
|
||||
}
|
||||
|
||||
if matches!(distribution, Distribution::Debian) {
|
||||
let apt = which("apt-fast")
|
||||
.or_else(|| {
|
||||
if which("mist").is_some() {
|
||||
Some(PathBuf::from("mist"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
if Path::new("/usr/bin/nala").exists() {
|
||||
Some(Path::new("/usr/bin/nala").to_path_buf())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| PathBuf::from("apt-get"));
|
||||
|
||||
let is_nala = apt.ends_with("nala");
|
||||
|
||||
if is_nala {
|
||||
return Err(SkipStep(String::from(msg)).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
let needrestart = require("needrestart")?;
|
||||
let distribution = Distribution::detect()?;
|
||||
|
||||
if distribution.redhat_based() {
|
||||
return Err(SkipStep(String::from("needrestart will be ran by the package manager")).into());
|
||||
}
|
||||
should_skip_needrestart()?;
|
||||
|
||||
print_separator("Check for needed restarts");
|
||||
|
||||
@@ -814,7 +879,12 @@ pub fn run_protonup_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("protonup");
|
||||
|
||||
ctx.run_type().execute(protonup).status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(protonup);
|
||||
if ctx.config().yes(Step::Protonup) {
|
||||
cmd.arg("--yes");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -887,6 +957,22 @@ pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let lure = require("lure")?;
|
||||
|
||||
print_separator("LURE");
|
||||
|
||||
let mut exe = ctx.run_type().execute(lure);
|
||||
|
||||
if ctx.config().yes(Step::Lure) {
|
||||
exe.args(["-i=false", "up"]);
|
||||
} else {
|
||||
exe.arg("up");
|
||||
}
|
||||
|
||||
exe.status_checked()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1014,4 +1100,9 @@ mod tests {
|
||||
fn test_vanilla() {
|
||||
test_template(include_str!("os_release/vanilla"), Distribution::Vanilla);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solus() {
|
||||
test_template(include_str!("os_release/solus"), Distribution::Solus);
|
||||
}
|
||||
}
|
||||
|
||||
11
src/steps/os/os_release/solus
Normal file
11
src/steps/os/os_release/solus
Normal file
@@ -0,0 +1,11 @@
|
||||
NAME="Solus"
|
||||
VERSION="4.4"
|
||||
ID="solus"
|
||||
VERSION_CODENAME=harmony
|
||||
VERSION_ID="4.4"
|
||||
PRETTY_NAME="Solus 4.4 Harmony"
|
||||
ANSI_COLOR="1;34"
|
||||
HOME_URL="https://getsol.us"
|
||||
SUPPORT_URL="https://help.getsol.us/docs/user/contributing/getting-involved"
|
||||
BUG_REPORT_URL="https://dev.getsol.us/"
|
||||
LOGO="distributor-logo-solus"
|
||||
@@ -11,6 +11,8 @@ use home;
|
||||
use ini::Ini;
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::linux::Distribution;
|
||||
use crate::error::SkipStep;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
@@ -155,7 +157,7 @@ pub fn run_oh_my_bash(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("oh-my-bash");
|
||||
|
||||
let mut update_script = oh_my_bash;
|
||||
update_script.push_str("tools/upgrade.sh");
|
||||
update_script.push_str("/tools/upgrade.sh");
|
||||
|
||||
ctx.run_type().execute("bash").arg(update_script).status_checked()
|
||||
}
|
||||
@@ -363,27 +365,26 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("nix profile: {:?}", profile_path);
|
||||
let manifest_json_path = profile_path.join("manifest.json");
|
||||
|
||||
let output = Command::new(&nix_env).args(["--query", "nix"]).output_checked_utf8();
|
||||
debug!("nix-env output: {:?}", output);
|
||||
let should_self_upgrade = output.is_ok();
|
||||
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
|
||||
#[allow(unused_mut)]
|
||||
let mut should_self_upgrade = cfg!(target_os = "macos");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// We can't use `nix upgrade-nix` on NixOS.
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
should_self_upgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
print_separator("Nix");
|
||||
|
||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||
debug!("Multi user nix: {}", multi_user);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use super::linux::Distribution;
|
||||
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
return Err(SkipStep(String::from("Nix on NixOS must be upgraded via nixos-rebuild switch")).into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Ok(..) = require("darwin-rebuild") {
|
||||
if require("darwin-rebuild").is_ok() {
|
||||
return Err(SkipStep(String::from(
|
||||
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch",
|
||||
))
|
||||
@@ -393,11 +394,20 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
let nix_args = ["--extra-experimental-features", "nix-command"];
|
||||
|
||||
if should_self_upgrade {
|
||||
if multi_user {
|
||||
ctx.execute_elevated(&nix, true)?.arg("upgrade-nix").status_checked()?;
|
||||
ctx.execute_elevated(&nix, true)?
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()?;
|
||||
} else {
|
||||
run_type.execute(&nix).arg("upgrade-nix").status_checked()?;
|
||||
run_type
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,12 +416,19 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
if Path::new(&manifest_json_path).exists() {
|
||||
run_type
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.arg("profile")
|
||||
.arg("upgrade")
|
||||
.arg(".*")
|
||||
.arg("--verbose")
|
||||
.status_checked()
|
||||
} else {
|
||||
run_type.execute(&nix_env).arg("--upgrade").status_checked()
|
||||
let mut command = run_type.execute(nix_env);
|
||||
command.arg("--upgrade");
|
||||
if let Some(args) = ctx.config().nix_env_arguments() {
|
||||
command.args(args.split_whitespace());
|
||||
};
|
||||
command.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +459,15 @@ pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
let home_manager = require("home-manager")?;
|
||||
|
||||
print_separator("home-manager");
|
||||
ctx.run_type().execute(home_manager).arg("switch").status_checked()
|
||||
|
||||
let mut cmd = ctx.run_type().execute(home_manager);
|
||||
cmd.arg("switch");
|
||||
|
||||
if let Some(extra_args) = ctx.config().home_manager() {
|
||||
cmd.args(extra_args);
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
@@ -8,8 +8,9 @@ use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::steps::git::GitAction;
|
||||
use crate::terminal::{print_separator, print_warning};
|
||||
use crate::utils::require;
|
||||
use crate::utils::{require, which};
|
||||
use crate::{error::SkipStep, steps::git::Repositories};
|
||||
use crate::{powershell, Step};
|
||||
|
||||
@@ -69,6 +70,10 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep("WSL not installed".to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
|
||||
print_separator("Update WSL");
|
||||
@@ -87,6 +92,30 @@ pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect if WSL is installed or not.
|
||||
///
|
||||
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
|
||||
/// versions of Windows (since windows 10 version 2004), this commmand is
|
||||
/// installed by default.
|
||||
///
|
||||
/// If the command is installed and the user hasn't installed any Linux distros
|
||||
/// on it, command `wsl -l` would print a help message and exit with failure, we
|
||||
/// use this to check whether WSL is install or not.
|
||||
fn is_wsl_installed() -> Result<bool> {
|
||||
if let Some(wsl) = which("wsl") {
|
||||
// Don't use `output_checked` as an execution failure log is not wanted
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let output = Command::new(wsl).arg("-l").output()?;
|
||||
let status = output.status;
|
||||
|
||||
if status.success() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
|
||||
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
|
||||
Ok(output
|
||||
@@ -100,12 +129,45 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
||||
let topgrade = Command::new(wsl)
|
||||
.args(["-d", dist, "bash", "-lc", "which topgrade"])
|
||||
.output_checked_utf8()
|
||||
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?;
|
||||
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?
|
||||
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
|
||||
let mut command = ctx.run_type().execute(wsl);
|
||||
|
||||
// The `arg` method automatically quotes its arguments.
|
||||
// This means we can't append additional arguments to `topgrade` in WSL
|
||||
// by calling `arg` successively.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// ```rust
|
||||
// command
|
||||
// .args(["-d", dist, "bash", "-c"])
|
||||
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
||||
// ```
|
||||
//
|
||||
// creates a command string like:
|
||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
|
||||
//
|
||||
// Adding the following:
|
||||
//
|
||||
// ```rust
|
||||
// command.arg("-v");
|
||||
// ```
|
||||
//
|
||||
// appends the next argument like so:
|
||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
|
||||
// which means `-v` isn't passed to `topgrade`.
|
||||
let mut args = String::new();
|
||||
if ctx.config().verbose() {
|
||||
args.push_str("-v");
|
||||
}
|
||||
|
||||
command
|
||||
.args(["-d", dist, "bash", "-c"])
|
||||
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
||||
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
|
||||
|
||||
if ctx.config().yes(Step::Wsl) {
|
||||
command.arg("-y");
|
||||
@@ -115,6 +177,10 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
||||
}
|
||||
|
||||
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep("WSL not installed".to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
let wsl_distributions = get_wsl_distributions(&wsl)?;
|
||||
let mut ran = false;
|
||||
@@ -174,7 +240,7 @@ pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
|
||||
if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) {
|
||||
debug!("Startup link: {:?}", lnk);
|
||||
if let Some(path) = lnk.relative_path() {
|
||||
git_repos.insert_if_repo(&startup_dir.join(path));
|
||||
git_repos.insert_if_repo(&startup_dir.join(path), GitAction::Pull);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
|
||||
topgrade_path,
|
||||
"--only",
|
||||
"system",
|
||||
"--no-self-update",
|
||||
"--skip-notify",
|
||||
];
|
||||
if ctx.config().yes(Step::Toolbx) {
|
||||
args.push("--yes");
|
||||
|
||||
@@ -165,6 +165,35 @@ pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("zsh")?;
|
||||
|
||||
// When updating `oh-my-zsh` on a remote machine through topgrade, the
|
||||
// following processes will be created:
|
||||
//
|
||||
// SSH -> ZSH -> ZSH ($SHELL) -> topgrade -> ZSH
|
||||
//
|
||||
// The first ZSH process, won't source zshrc (as it is a login shell),
|
||||
// and thus it won't have the ZSH environment variable, as a result, the
|
||||
// children processes won't get it either, so we source the zshrc and set
|
||||
// the ZSH variable for topgrade here.
|
||||
if ctx.under_ssh() {
|
||||
let zshrc_path = zshrc().require()?;
|
||||
let output = Command::new("zsh")
|
||||
.args([
|
||||
"-c",
|
||||
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
|
||||
format!(
|
||||
"source {} > /dev/null && export -p | grep ZSH > /dev/null && echo $ZSH",
|
||||
zshrc_path.display()
|
||||
)
|
||||
.as_str(),
|
||||
])
|
||||
.output_checked_utf8()?;
|
||||
let zsh_env = output.stdout.trim();
|
||||
if !zsh_env.is_empty() {
|
||||
env::set_var("ZSH", zsh_env);
|
||||
}
|
||||
}
|
||||
|
||||
let oh_my_zsh = env::var("ZSH")
|
||||
.map(PathBuf::from)
|
||||
// default to `~/.oh-my-zsh`
|
||||
@@ -198,11 +227,11 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
for entry in WalkDir::new(custom_dir).max_depth(2) {
|
||||
let entry = entry?;
|
||||
custom_repos.insert_if_repo(entry.path());
|
||||
custom_repos.insert_if_repo(entry.path(), crate::steps::git::GitAction::Pull);
|
||||
}
|
||||
|
||||
custom_repos.remove(&oh_my_zsh.to_string_lossy());
|
||||
if !custom_repos.is_empty() {
|
||||
custom_repos.remove_from_pull(&oh_my_zsh.to_string_lossy());
|
||||
if !custom_repos.pull_is_empty() {
|
||||
println!("Pulling custom plugins and themes");
|
||||
ctx.git().multi_pull(&custom_repos, ctx)?;
|
||||
}
|
||||
|
||||
16
src/sudo.rs
16
src/sudo.rs
@@ -26,10 +26,10 @@ impl Sudo {
|
||||
pub fn detect() -> Option<Self> {
|
||||
which("doas")
|
||||
.map(|p| (p, SudoKind::Doas))
|
||||
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
||||
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
|
||||
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
||||
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
||||
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
||||
.map(|(path, kind)| Self { path, kind })
|
||||
}
|
||||
|
||||
@@ -55,12 +55,6 @@ impl Sudo {
|
||||
// See: https://man.openbsd.org/doas
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Please => {
|
||||
// From `man please`
|
||||
// -w, --warm
|
||||
// Warm the access token and exit.
|
||||
cmd.arg("-w");
|
||||
}
|
||||
SudoKind::Sudo => {
|
||||
// From `man sudo` on macOS:
|
||||
// -v, --validate
|
||||
@@ -85,6 +79,12 @@ impl Sudo {
|
||||
// See: https://linux.die.net/man/1/pkexec
|
||||
cmd.arg("echo");
|
||||
}
|
||||
SudoKind::Please => {
|
||||
// From `man please`
|
||||
// -w, --warm
|
||||
// Warm the access token and exit.
|
||||
cmd.arg("-w");
|
||||
}
|
||||
}
|
||||
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
||||
}
|
||||
@@ -112,10 +112,10 @@ impl Sudo {
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum SudoKind {
|
||||
Doas,
|
||||
Please,
|
||||
Sudo,
|
||||
Gsudo,
|
||||
Pkexec,
|
||||
Please,
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for Sudo {
|
||||
|
||||
54
src/utils.rs
54
src/utils.rs
@@ -5,9 +5,17 @@ use std::path::{Path, PathBuf};
|
||||
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;
|
||||
use tracing_subscriber::{fmt, Registry};
|
||||
use tracing_subscriber::{registry, EnvFilter};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::config::DEFAULT_LOG_LEVEL;
|
||||
use crate::error::SkipStep;
|
||||
|
||||
pub trait PathExt
|
||||
@@ -251,3 +259,49 @@ pub fn check_is_python_2_or_shim(python: PathBuf) -> Result<PathBuf> {
|
||||
|
||||
Ok(python)
|
||||
}
|
||||
|
||||
/// Set up the tracing logger
|
||||
///
|
||||
/// # Return value
|
||||
/// A reload handle will be returned so that we can change the log level at
|
||||
/// runtime.
|
||||
pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Registry>> {
|
||||
let env_filter = EnvFilter::try_new(filter_directives)
|
||||
.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 (filter, reload_handle) = Layer::new(env_filter);
|
||||
|
||||
registry().with(filter).with(fmt_layer).init();
|
||||
|
||||
Ok(reload_handle)
|
||||
}
|
||||
|
||||
/// Update the tracing logger with new `filter_directives`.
|
||||
pub fn update_tracing(reload_handle: &Handle<EnvFilter, Registry>, filter_directives: &str) -> Result<()> {
|
||||
let new = EnvFilter::try_new(filter_directives)
|
||||
.or_else(|_| EnvFilter::try_from_default_env())
|
||||
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
|
||||
reload_handle.modify(|old| *old = new)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set up the error handler crate
|
||||
pub fn install_color_eyre() -> Result<()> {
|
||||
color_eyre::config::HookBuilder::new()
|
||||
// Don't display the backtrace reminder by default:
|
||||
// Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
|
||||
// Run with RUST_BACKTRACE=full to include source snippets.
|
||||
.display_env_section(false)
|
||||
// Display location information by default:
|
||||
// Location:
|
||||
// src/steps.rs:92
|
||||
.display_location_section(true)
|
||||
.install()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user