Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96efcc6c0d | ||
|
|
bf72d7bb5a | ||
|
|
dadffb1081 | ||
|
|
78dc567226 | ||
|
|
362ce4f4f9 | ||
|
|
ab35cd7b10 | ||
|
|
15f4ad7cd1 | ||
|
|
cbfb92041f | ||
|
|
a506c67cac | ||
|
|
788e0412f6 | ||
|
|
18b37ce3e3 | ||
|
|
a15e6748c7 | ||
|
|
c6d0539fd2 | ||
|
|
3eb3867944 | ||
|
|
810315b0e2 | ||
|
|
b461fc2536 | ||
|
|
7e63977ba0 | ||
|
|
78dec892cf | ||
|
|
9ea6628b5c | ||
|
|
465df2e9be | ||
|
|
61ef926849 | ||
|
|
7fa38c593e |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Topgrade is misbehaving
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
labels: 'C-bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Can you please support...?
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: 'C-feature request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
12
BREAKINGCHANGES.md
Normal file
12
BREAKINGCHANGES.md
Normal file
@@ -0,0 +1,12 @@
|
||||
1. In 13.0.0, we introduced a new feature, pushing git repos, now this feature
|
||||
has been removed as some users are not satisfied with it.
|
||||
|
||||
For configuration entries, the following ones are gone:
|
||||
|
||||
```toml
|
||||
[git]
|
||||
pull_only_repos = []
|
||||
push_only_repos = []
|
||||
pull_arguments = ""
|
||||
push_arguments = ""
|
||||
```
|
||||
0
BREAKINGCHNAGES_dev.md
Normal file
0
BREAKINGCHNAGES_dev.md
Normal file
@@ -101,6 +101,19 @@ Be sure to apply your changes to
|
||||
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
|
||||
and have some basic documentations guiding user how to use these options.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
If your PR introduces a breaking change, document it in `BREAKINGCHANGE_dev.md`,
|
||||
it should be written in Markdown and wrapped in 80, for example:
|
||||
|
||||
```md
|
||||
1. The configuration location has been updated to x.
|
||||
|
||||
2. The step x has been removed.
|
||||
|
||||
3. ...
|
||||
```
|
||||
|
||||
## Before you submit your PR
|
||||
|
||||
Make sure your patch passes the following tests on your host:
|
||||
|
||||
1400
Cargo.lock
generated
1400
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -5,9 +5,9 @@ categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
version = "13.0.0"
|
||||
version = "14.0.0"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif"]
|
||||
exclude = ["doc/screenshot.gif", "BREAKINGCHNAGES_dev.md"]
|
||||
edition = "2021"
|
||||
|
||||
readme = "README.md"
|
||||
@@ -22,26 +22,26 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
home = "~0.5"
|
||||
etcetera = "~0.8"
|
||||
once_cell = "~1.17"
|
||||
once_cell = "~1.18"
|
||||
serde = { version = "~1.0", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
toml = "0.8"
|
||||
which_crate = { version = "~4.1", package = "which" }
|
||||
shellexpand = "~2.1"
|
||||
clap = { version = "~3.1", features = ["cargo", "derive"] }
|
||||
clap_complete = "~3.1"
|
||||
clap_mangen = "~0.1"
|
||||
walkdir = "~2.3"
|
||||
shellexpand = "~3.1"
|
||||
clap = { version = "~4.4", features = ["cargo", "derive"] }
|
||||
clap_complete = "~4.4"
|
||||
clap_mangen = "~0.2"
|
||||
walkdir = "~2.4"
|
||||
console = "~0.15"
|
||||
lazy_static = "~1.4"
|
||||
chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.24", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.6"
|
||||
tempfile = "~3.8"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
||||
tokio = { version = "~1.34", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
regex = "~1.7"
|
||||
regex = "~1.10"
|
||||
semver = "~1.0"
|
||||
shell-words = "~1.1"
|
||||
color-eyre = "~0.6"
|
||||
@@ -49,10 +49,10 @@ tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
||||
merge = "~0.1"
|
||||
regex-split = "~0.1"
|
||||
notify-rust = "~4.8"
|
||||
notify-rust = "~4.10"
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [{source = "target/release/topgrade", dest="/usr/bin/topgrade"}]
|
||||
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
|
||||
|
||||
[package.metadata.generate-rpm.requires]
|
||||
git = "*"
|
||||
@@ -61,8 +61,7 @@ git = "*"
|
||||
depends = "$auto,git"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "~0.2"
|
||||
nix = "~0.24"
|
||||
nix = { version = "~0.27", features = ["hostname", "signal", "user"] }
|
||||
rust-ini = "~0.19"
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -42,15 +42,18 @@ The compiled binaries contain a self-upgrading feature.
|
||||
|
||||
Just run `topgrade`.
|
||||
|
||||
Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) for more information.
|
||||
|
||||
> **Warning**
|
||||
> Work in Progress
|
||||
|
||||
## Configuration
|
||||
|
||||
See `config.example.toml` for an example configuration file.
|
||||
|
||||
## Migration and Breaking Changes
|
||||
|
||||
Whenever there is a **breaking change**, the major version number will be bumped,
|
||||
and we will document these changes in the release note, please take a look at
|
||||
it when updated to a major release.
|
||||
|
||||
> Got a question? Feel free to open an issue or discussion!
|
||||
|
||||
### Configuration Path
|
||||
|
||||
#### `CONFIG_DIR` on each platform
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# Arguments to pass tmux when pulling Repositories
|
||||
# tmux_arguments = "-S /var/tmux.sock"
|
||||
|
||||
# Do not set the terminal title (dfault: true)
|
||||
# Do not set the terminal title (default: true)
|
||||
# set_title = true
|
||||
|
||||
# Display the time in step titles (default: true)
|
||||
@@ -155,32 +155,17 @@
|
||||
[git]
|
||||
# max_concurrency = 5
|
||||
|
||||
# Git repositories that you want to pull and push
|
||||
# Additional git repositories to pull
|
||||
# repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
# ]
|
||||
|
||||
# Repositories that you only want to pull
|
||||
# pull_only_repos = [
|
||||
# "~/.config/something_else"
|
||||
# ]
|
||||
|
||||
# Repositories that you only want to push
|
||||
# push_only_repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something_third"
|
||||
# ]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
# pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling repositories
|
||||
# pull_arguments = "--rebase --autostash"
|
||||
|
||||
# Arguments to pass Git when pushing repositories
|
||||
# push_arguments = "--all"
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
# arguments = "--rebase --autostash"
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
@@ -197,6 +182,9 @@
|
||||
# manager such as Scoop or Cargo
|
||||
# self_rename = true
|
||||
|
||||
# Enable WinGet upgrade
|
||||
# enable_winget = true
|
||||
|
||||
|
||||
[npm]
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
@@ -237,4 +225,6 @@
|
||||
[distrobox]
|
||||
# use_root = false
|
||||
|
||||
# containers = ["archlinux-latest"]
|
||||
# containers = ["archlinux-latest"]
|
||||
[containers]
|
||||
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest"]
|
||||
|
||||
156
src/breaking_changes.rs
Normal file
156
src/breaking_changes.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! Inform the users of the breaking changes introduced in this major release.
|
||||
//!
|
||||
//! Print the breaking changes and possibly a migration guide when:
|
||||
//! 1. The Topgrade being executed is a new major release
|
||||
//! 2. This is the first launch of that major release
|
||||
|
||||
use crate::terminal::print_separator;
|
||||
#[cfg(windows)]
|
||||
use crate::WINDOWS_DIRS;
|
||||
#[cfg(unix)]
|
||||
use crate::XDG_DIRS;
|
||||
use color_eyre::eyre::Result;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
use std::{
|
||||
fs::{read_to_string, OpenOptions},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Version string x.y.z
|
||||
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Version info
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Version {
|
||||
_major: u64,
|
||||
minor: u64,
|
||||
patch: u64,
|
||||
}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
const NOT_SEMVER: &str = "Topgrade version is not semantic";
|
||||
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
|
||||
|
||||
let mut iter = s.split('.').take(3);
|
||||
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||
|
||||
// They cannot be all 0s
|
||||
assert!(
|
||||
!(major == 0 && minor == 0 && patch == 0),
|
||||
"Version numbers can not be all 0s"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
_major: major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// True if this version is a new major release.
|
||||
pub(crate) fn is_new_major_release(&self) -> bool {
|
||||
// We have already checked that they cannot all be zeros, so `self.major`
|
||||
// is guaranteed to be non-zero.
|
||||
self.minor == 0 && self.patch == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Topgrade's breaking changes
|
||||
///
|
||||
/// We store them in the compiled binary.
|
||||
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
|
||||
|
||||
/// Return platform's data directory.
|
||||
fn data_dir() -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
return XDG_DIRS.data_dir();
|
||||
|
||||
#[cfg(windows)]
|
||||
return WINDOWS_DIRS.data_dir();
|
||||
}
|
||||
|
||||
/// Return Topgrade's keep file path.
|
||||
///
|
||||
/// keep file is a file under the data directory containing a major version
|
||||
/// number, it will be created on first run and is used to check if an execution
|
||||
/// of Topgrade is the first run of a major release, for more details, see
|
||||
/// `first_run_of_major_release()`.
|
||||
fn keep_file_path() -> PathBuf {
|
||||
let keep_file = "topgrade_keep";
|
||||
data_dir().join(keep_file)
|
||||
}
|
||||
|
||||
/// True if this is the first execution of a major release.
|
||||
pub(crate) fn first_run_of_major_release() -> Result<bool> {
|
||||
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
// disable this lint here as the current code has better readability
|
||||
#[allow(clippy::collapsible_if)]
|
||||
if version.is_new_major_release() {
|
||||
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Print breaking changes to the user.
|
||||
pub(crate) fn print_breaking_changes() {
|
||||
let header = format!("Topgrade {VERSION_STR} Breaking Changes");
|
||||
print_separator(header);
|
||||
let contents = if BREAKINGCHANGES.is_empty() {
|
||||
"No Breaking changes"
|
||||
} else {
|
||||
BREAKINGCHANGES
|
||||
};
|
||||
println!("{contents}\n");
|
||||
}
|
||||
|
||||
/// This function will be ONLY executed when the user has confirmed the breaking
|
||||
/// changes, once confirmed, we write the keep file, which means the first run
|
||||
/// of this major release is finished.
|
||||
pub(crate) fn write_keep_file() -> Result<()> {
|
||||
std::fs::create_dir_all(data_dir())?;
|
||||
let keep_file = keep_file_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(keep_file)?;
|
||||
let _ = file.write(VERSION_STR.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_new_major_release_works() {
|
||||
let first_major_release: Version = "1.0.0".parse().unwrap();
|
||||
let under_dev: Version = "0.1.0".parse().unwrap();
|
||||
|
||||
assert!(first_major_release.is_new_major_release());
|
||||
assert!(!under_dev.is_new_major_release());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Version numbers can not be all 0s")]
|
||||
fn invalid_version() {
|
||||
let all_0 = "0.0.0";
|
||||
all_0.parse::<Version>().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
||||
use clap::{ArgEnum, Parser};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use clap_complete::Shell;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
@@ -19,10 +19,10 @@ use serde::Deserialize;
|
||||
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
||||
use which_crate::which;
|
||||
|
||||
use super::utils::{editor, hostname};
|
||||
use super::utils::editor;
|
||||
use crate::command::CommandExt;
|
||||
use crate::sudo::SudoKind;
|
||||
use crate::utils::string_prepend_str;
|
||||
use crate::utils::{hostname, string_prepend_str};
|
||||
use tracing::{debug, error};
|
||||
|
||||
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
||||
@@ -44,7 +44,7 @@ macro_rules! str_value {
|
||||
|
||||
pub type Commands = BTreeMap<String, String>;
|
||||
|
||||
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||
#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -53,11 +53,13 @@ pub enum Step {
|
||||
AppMan,
|
||||
Asdf,
|
||||
Atom,
|
||||
Audit,
|
||||
Bin,
|
||||
Bob,
|
||||
BrewCask,
|
||||
BrewFormula,
|
||||
Bun,
|
||||
BunPackages,
|
||||
Cargo,
|
||||
Chezmoi,
|
||||
Chocolatey,
|
||||
@@ -158,23 +160,23 @@ pub struct Include {
|
||||
paths: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Containers {
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
ignored_containers: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Git {
|
||||
max_concurrency: Option<usize>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
pull_arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
push_arguments: Option<String>,
|
||||
arguments: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
repos: Option<Vec<String>>,
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
pull_only_repos: Option<Vec<String>>,
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
push_only_repos: Option<Vec<String>>,
|
||||
|
||||
pull_predefined: Option<bool>,
|
||||
}
|
||||
@@ -417,6 +419,9 @@ pub struct ConfigFile {
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
git: Option<Git>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
containers: Option<Containers>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
windows: Option<Windows>,
|
||||
|
||||
@@ -616,22 +621,7 @@ impl ConfigFile {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.pull_only_repos.as_mut()) {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!("Path {} expanded to {}", path, expanded);
|
||||
*path = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.push_only_repos.as_mut()) {
|
||||
for path in paths.iter_mut() {
|
||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||
debug!("Path {} expanded to {}", path, expanded);
|
||||
*path = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Loaded configuration: {:?}", result);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -692,19 +682,19 @@ pub struct CommandLineArgs {
|
||||
no_retry: bool,
|
||||
|
||||
/// Do not perform upgrades for the given steps
|
||||
#[clap(long = "disable", value_name = "STEP", arg_enum, multiple_values = true)]
|
||||
#[clap(long = "disable", value_name = "STEP", value_enum, num_args = 1..)]
|
||||
disable: Vec<Step>,
|
||||
|
||||
/// Perform only the specified steps (experimental)
|
||||
#[clap(long = "only", value_name = "STEP", arg_enum, multiple_values = true)]
|
||||
#[clap(long = "only", value_name = "STEP", value_enum, num_args = 1..)]
|
||||
only: Vec<Step>,
|
||||
|
||||
/// Run only specific custom commands
|
||||
#[clap(long = "custom-commands", value_name = "NAME", multiple_values = true)]
|
||||
#[clap(long = "custom-commands", value_name = "NAME", num_args = 1..)]
|
||||
custom_commands: Vec<String>,
|
||||
|
||||
/// Set environment variables
|
||||
#[clap(long = "env", value_name = "NAME=VALUE", multiple_values = true)]
|
||||
#[clap(long = "env", value_name = "NAME=VALUE", num_args = 1..)]
|
||||
env: Vec<String>,
|
||||
|
||||
/// Output debug logs. Alias for `--log-filter debug`.
|
||||
@@ -724,9 +714,8 @@ pub struct CommandLineArgs {
|
||||
short = 'y',
|
||||
long = "yes",
|
||||
value_name = "STEP",
|
||||
arg_enum,
|
||||
multiple_values = true,
|
||||
min_values = 0
|
||||
value_enum,
|
||||
num_args = 0..,
|
||||
)]
|
||||
yes: Option<Vec<Step>>,
|
||||
|
||||
@@ -753,7 +742,7 @@ pub struct CommandLineArgs {
|
||||
pub log_filter: String,
|
||||
|
||||
/// Print completion script for the given shell and exit
|
||||
#[clap(long, arg_enum, hide = true)]
|
||||
#[clap(long, value_enum, hide = true)]
|
||||
pub gen_completion: Option<Shell>,
|
||||
|
||||
/// Print roff manpage and exit
|
||||
@@ -859,23 +848,17 @@ impl Config {
|
||||
&self.config_file.commands
|
||||
}
|
||||
|
||||
/// The list of git repositories to push and pull.
|
||||
/// The list of additional git repositories to pull.
|
||||
pub fn git_repos(&self) -> Option<&Vec<String>> {
|
||||
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
|
||||
}
|
||||
/// The list of additional git repositories to pull.
|
||||
pub fn git_pull_only_repos(&self) -> Option<&Vec<String>> {
|
||||
|
||||
/// The list of docker/podman containers to ignore.
|
||||
pub fn containers_ignored_tags(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.git
|
||||
.containers
|
||||
.as_ref()
|
||||
.and_then(|git| git.pull_only_repos.as_ref())
|
||||
}
|
||||
/// The list of git repositories to push.
|
||||
pub fn git_push_only_repos(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.push_only_repos.as_ref())
|
||||
.and_then(|containers| containers.ignored_containers.as_ref())
|
||||
}
|
||||
|
||||
/// Tell whether the specified step should run.
|
||||
@@ -986,19 +969,9 @@ impl Config {
|
||||
.and_then(|misc| misc.ssh_arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Extra Git arguments for when pushing
|
||||
pub fn push_git_arguments(&self) -> Option<&String> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.push_arguments.as_ref())
|
||||
}
|
||||
/// Extra Git arguments for when pulling
|
||||
pub fn pull_git_arguments(&self) -> Option<&String> {
|
||||
self.config_file
|
||||
.git
|
||||
.as_ref()
|
||||
.and_then(|git| git.pull_arguments.as_ref())
|
||||
/// Extra Git arguments
|
||||
pub fn git_arguments(&self) -> Option<&String> {
|
||||
self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Extra Tmux arguments
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! SIGINT handling in Unix systems.
|
||||
use crate::ctrlc::interrupted::set_interrupted;
|
||||
use nix::sys::signal;
|
||||
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
|
||||
|
||||
/// Handle SIGINT. Set the interruption flag.
|
||||
extern "C" fn handle_sigint(_: i32) {
|
||||
@@ -10,12 +10,8 @@ extern "C" fn handle_sigint(_: i32) {
|
||||
/// Set the necessary signal handlers.
|
||||
/// The function panics on failure.
|
||||
pub fn set_handler() {
|
||||
let sig_action = signal::SigAction::new(
|
||||
signal::SigHandler::Handler(handle_sigint),
|
||||
signal::SaFlags::empty(),
|
||||
signal::SigSet::empty(),
|
||||
);
|
||||
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
|
||||
unsafe {
|
||||
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
|
||||
sigaction(Signal::SIGINT, &sig_action).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
82
src/main.rs
82
src/main.rs
@@ -6,19 +6,20 @@ use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, write_keep_file};
|
||||
use clap::CommandFactory;
|
||||
use clap::{crate_version, Parser};
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use console::Key;
|
||||
use etcetera::base_strategy::BaseStrategy;
|
||||
#[cfg(windows)]
|
||||
use etcetera::base_strategy::Windows;
|
||||
use etcetera::base_strategy::{BaseStrategy, Xdg};
|
||||
#[cfg(unix)]
|
||||
use etcetera::base_strategy::Xdg;
|
||||
use once_cell::sync::Lazy;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::steps::git::GitAction;
|
||||
|
||||
use self::config::{CommandLineArgs, Config, Step};
|
||||
use self::error::StepFailed;
|
||||
#[cfg(all(windows, feature = "self-update"))]
|
||||
@@ -28,6 +29,7 @@ use self::terminal::*;
|
||||
|
||||
use self::utils::{install_color_eyre, install_tracing, update_tracing};
|
||||
|
||||
mod breaking_changes;
|
||||
mod command;
|
||||
mod config;
|
||||
mod ctrlc;
|
||||
@@ -45,10 +47,11 @@ mod sudo;
|
||||
mod terminal;
|
||||
mod utils;
|
||||
|
||||
pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
||||
pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
||||
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
||||
#[cfg(unix)]
|
||||
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
||||
#[cfg(windows)]
|
||||
pub static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
||||
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
||||
|
||||
fn run() -> Result<()> {
|
||||
install_color_eyre()?;
|
||||
@@ -132,6 +135,18 @@ fn run() -> Result<()> {
|
||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
|
||||
let mut runner = runner::Runner::new(&ctx);
|
||||
|
||||
// If this is the first execution of a major release, inform user of breaking
|
||||
// changes
|
||||
if first_run_of_major_release()? {
|
||||
print_breaking_changes();
|
||||
|
||||
if prompt_yesno("Confirmed?")? {
|
||||
write_keep_file()?;
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Self-Update step, this will execute only if:
|
||||
// 1. the `self-update` feature is enabled
|
||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||
@@ -249,14 +264,14 @@ fn run() -> Result<()> {
|
||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
||||
dragonfly::upgrade_packages(&ctx)
|
||||
})?;
|
||||
dragonfly::audit_packages(&ctx)?;
|
||||
runner.execute(Step::Audit, "DragonFly Audit", || dragonfly::audit_packages(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
{
|
||||
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
|
||||
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
|
||||
freebsd::audit_packages(&ctx)?;
|
||||
runner.execute(Step::Audit, "FreeBSD Audit", || freebsd::audit_packages(&ctx))?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
@@ -274,11 +289,13 @@ fn run() -> Result<()> {
|
||||
{
|
||||
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
||||
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
||||
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
|
||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
|
||||
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
|
||||
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
|
||||
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
|
||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
|
||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
|
||||
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
|
||||
@@ -384,35 +401,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, GitAction::Pull);
|
||||
git_repos.insert_if_repo(directory);
|
||||
}
|
||||
}
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".doom.d"));
|
||||
}
|
||||
|
||||
if config.should_run(Step::Vim) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".vim"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".vim"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
|
||||
}
|
||||
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
|
||||
|
||||
if config.should_run(Step::Rcm) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
git_repos.insert_if_repo(zsh::zshrc(), GitAction::Pull);
|
||||
git_repos.insert_if_repo(zsh::zshrc());
|
||||
if config.should_run(Step::Tmux) {
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".tmux"), GitAction::Pull);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".tmux"));
|
||||
}
|
||||
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);
|
||||
git_repos.insert_if_repo(HOME_DIR.join(".config/fish"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
|
||||
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -420,39 +437,24 @@ 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, GitAction::Pull);
|
||||
git_repos.insert_if_repo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
if config.should_run(Step::GitRepos) {
|
||||
if let Some(custom_git_repos) = config.git_repos() {
|
||||
for git_repo in custom_git_repos {
|
||||
git_repos.glob_insert(git_repo, GitAction::Pull);
|
||||
git_repos.glob_insert(git_repo, GitAction::Push);
|
||||
git_repos.glob_insert(git_repo);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(git_pull_only_repos) = config.git_pull_only_repos() {
|
||||
for git_repo in git_pull_only_repos {
|
||||
git_repos.glob_insert(git_repo, GitAction::Pull);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(git_push_only_repos) = config.git_push_only_repos() {
|
||||
for git_repo in git_push_only_repos {
|
||||
git_repos.glob_insert(git_repo, GitAction::Push);
|
||||
}
|
||||
}
|
||||
|
||||
runner.execute(Step::GitRepos, "Git repositories", || {
|
||||
git.multi_repo_step(&git_repos, &ctx)
|
||||
git.multi_pull_step(&git_repos, &ctx)
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,9 @@ impl Display for Container {
|
||||
|
||||
/// Returns a Vector of all containers, with Strings in the format
|
||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||
fn list_containers(crt: &Path) -> Result<Vec<Container>> {
|
||||
///
|
||||
/// Containers specified in `ignored_containers` will be filtered out.
|
||||
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
|
||||
debug!(
|
||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
||||
crt.display()
|
||||
@@ -83,6 +85,16 @@ fn list_containers(crt: &Path) -> Result<Vec<Container>> {
|
||||
assert_eq!(split_res.len(), 2);
|
||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||
|
||||
if let Some(ignored_containers) = ignored_containers {
|
||||
if ignored_containers
|
||||
.iter()
|
||||
.any(|ignored_container| repo_tag.eq(ignored_container))
|
||||
{
|
||||
debug!("Skipping ignored container '{}'", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
|
||||
crt.display(),
|
||||
@@ -109,7 +121,8 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("Containers");
|
||||
let mut success = true;
|
||||
let containers = list_containers(&crt).context("Failed to list Docker containers")?;
|
||||
let containers =
|
||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||
debug!("Containers to inspect: {:?}", containers);
|
||||
|
||||
for container in containers.iter() {
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::{fs, io::Write};
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use semver::Version;
|
||||
use tempfile::tempfile_in;
|
||||
use tracing::{debug, error};
|
||||
|
||||
@@ -352,7 +353,20 @@ pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
let pipx = require("pipx")?;
|
||||
print_separator("pipx");
|
||||
|
||||
ctx.run_type().execute(pipx).arg("upgrade-all").status_checked()
|
||||
let mut command_args = vec!["upgrade-all"];
|
||||
|
||||
// pipx version 1.4.0 introduced a new command argument `pipx upgrade-all --quiet`
|
||||
// (see https://pipx.pypa.io/stable/docs/#pipx-upgrade-all)
|
||||
let version_str = Command::new("pipx")
|
||||
.args(["--version"])
|
||||
.output_checked_utf8()
|
||||
.map(|s| s.stdout.trim().to_owned());
|
||||
let version = Version::parse(&version_str?);
|
||||
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
|
||||
command_args.push("--quiet")
|
||||
}
|
||||
|
||||
ctx.run_type().execute(pipx).args(command_args).status_checked()
|
||||
}
|
||||
|
||||
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -425,20 +439,53 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
.output_checked_utf8()
|
||||
.map_err(|_| SkipStep("pip does not exist".to_string()))?;
|
||||
|
||||
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
|
||||
Command::new(&python3)
|
||||
.args(["-c", check_externally_managed])
|
||||
let check_extern_managed_script = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
|
||||
let output = Command::new(&python3)
|
||||
.args(["-c", check_extern_managed_script])
|
||||
.output_checked_utf8()?;
|
||||
let stdout = output.stdout.trim();
|
||||
let extern_managed = match stdout {
|
||||
"N" => false,
|
||||
"Y" => true,
|
||||
_ => unreachable!("unexpected output from `check_extern_managed_script`"),
|
||||
};
|
||||
|
||||
let allow_break_sys_pkg = match Command::new(&python3)
|
||||
.args(["-m", "pip", "config", "get", "global.break-system-packages"])
|
||||
.output_checked_utf8()
|
||||
.map_err(|_| SkipStep("pip may be externally managed".to_string()))
|
||||
.and_then(|output| match output.stdout.trim() {
|
||||
"N" => Ok(()),
|
||||
"Y" => Err(SkipStep("pip is externally managed".to_string())),
|
||||
_ => {
|
||||
print_warning("Unexpected output when checking EXTERNALLY-MANAGED");
|
||||
print_warning(output.stdout.trim());
|
||||
Err(SkipStep("pip may be externally managed".to_string()))
|
||||
}
|
||||
})?;
|
||||
{
|
||||
Ok(output) => {
|
||||
let stdout = output.stdout.trim();
|
||||
stdout
|
||||
.parse::<bool>()
|
||||
.expect("unexpected output that is not `true` or `false`")
|
||||
}
|
||||
// it can fail because this key may not be set
|
||||
//
|
||||
// ```sh
|
||||
// $ pip --version
|
||||
// pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)
|
||||
//
|
||||
// $ pip config get global.break-system-packages
|
||||
// ERROR: No such key - global.break-system-packages
|
||||
//
|
||||
// $ echo $?
|
||||
// 1
|
||||
// ```
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
debug!("pip3 externally managed: {} ", extern_managed);
|
||||
debug!("pip3 global.break-system-packages: {}", allow_break_sys_pkg);
|
||||
|
||||
// Even though pip3 is externally managed, we should still update it if
|
||||
// `global.break-system-packages` is true.
|
||||
if extern_managed && !allow_break_sys_pkg {
|
||||
return Err(SkipStep(
|
||||
"Skip pip3 update as it is externally managed and global.break-system-packages is not true".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
print_separator("pip3");
|
||||
if env::var("VIRTUAL_ENV").is_ok() {
|
||||
|
||||
132
src/steps/git.rs
132
src/steps/git.rs
@@ -27,17 +27,9 @@ pub struct Git {
|
||||
git: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum GitAction {
|
||||
Push,
|
||||
Pull,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Repositories<'a> {
|
||||
git: &'a Git,
|
||||
pull_repositories: HashSet<String>,
|
||||
push_repositories: HashSet<String>,
|
||||
repositories: HashSet<String>,
|
||||
glob_match_options: MatchOptions,
|
||||
bad_patterns: Vec<String>,
|
||||
}
|
||||
@@ -52,36 +44,6 @@ fn output_checked_utf8(output: Output) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn push_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
|
||||
println!("{} {}", style("Pushing").cyan().bold(), path);
|
||||
|
||||
let mut command = AsyncCommand::new(git);
|
||||
|
||||
command
|
||||
.stdin(Stdio::null())
|
||||
.current_dir(&repo)
|
||||
.args(["push", "--porcelain"]);
|
||||
if let Some(extra_arguments) = ctx.config().push_git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
let output = command.output().await?;
|
||||
let result = match output.status.success() {
|
||||
true => Ok(()),
|
||||
false => Err(format!("Failed to push {repo}")),
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
println!("{} pushing {}", style("Failed").red().bold(), &repo);
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(eyre!(e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -> Result<()> {
|
||||
let path = repo.to_string();
|
||||
@@ -96,7 +58,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().pull_git_arguments() {
|
||||
if let Some(extra_arguments) = ctx.config().git_arguments() {
|
||||
command.args(extra_arguments.split_whitespace());
|
||||
}
|
||||
|
||||
@@ -219,7 +181,7 @@ impl Git {
|
||||
|
||||
None
|
||||
}
|
||||
pub fn multi_repo_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
// Warn the user about the bad patterns.
|
||||
//
|
||||
// NOTE: this should be executed **before** skipping the Git step or the
|
||||
@@ -230,15 +192,12 @@ impl Git {
|
||||
.iter()
|
||||
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
|
||||
|
||||
if repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull or push")).into());
|
||||
if repositories.repositories.is_empty() {
|
||||
return Err(SkipStep(String::from("No repositories to pull")).into());
|
||||
}
|
||||
|
||||
print_separator("Git repositories");
|
||||
self.multi_pull(repositories, ctx)?;
|
||||
self.multi_push(repositories, ctx)?;
|
||||
|
||||
Ok(())
|
||||
self.multi_pull(repositories, ctx)
|
||||
}
|
||||
|
||||
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -246,7 +205,7 @@ impl Git {
|
||||
|
||||
if ctx.run_type().dry() {
|
||||
repositories
|
||||
.pull_repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.for_each(|repo| println!("Would pull {}", &repo));
|
||||
|
||||
@@ -254,7 +213,7 @@ impl Git {
|
||||
}
|
||||
|
||||
let futures_iterator = repositories
|
||||
.pull_repositories
|
||||
.repositories
|
||||
.iter()
|
||||
.filter(|repo| match has_remotes(git, repo) {
|
||||
Some(false) => {
|
||||
@@ -281,47 +240,6 @@ 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> {
|
||||
@@ -334,27 +252,22 @@ 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, action: GitAction) -> bool {
|
||||
pub fn insert_if_repo<P: AsRef<Path>>(&mut self, path: P) -> bool {
|
||||
if let Some(repo) = self.git.get_repo_root(path) {
|
||||
match action {
|
||||
GitAction::Push => self.push_repositories.insert(repo),
|
||||
GitAction::Pull => self.pull_repositories.insert(repo),
|
||||
};
|
||||
|
||||
self.repositories.insert(repo);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn glob_insert(&mut self, pattern: &str, action: GitAction) {
|
||||
pub fn glob_insert(&mut self, pattern: &str) {
|
||||
if let Ok(glob) = glob_with(pattern, self.glob_match_options) {
|
||||
let mut last_git_repo: Option<PathBuf> = None;
|
||||
for entry in glob {
|
||||
@@ -370,7 +283,7 @@ impl<'a> Repositories<'a> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if self.insert_if_repo(&path, action) {
|
||||
if self.insert_if_repo(&path) {
|
||||
last_git_repo = Some(path);
|
||||
}
|
||||
}
|
||||
@@ -388,27 +301,16 @@ impl<'a> Repositories<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if `pull_repos` and `push_repos` are both empty.
|
||||
#[cfg(unix)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pull_repositories.is_empty() && self.push_repositories.is_empty()
|
||||
self.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)]
|
||||
/// 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);
|
||||
pub fn remove(&mut self, path: &str) {
|
||||
let _removed = self.repositories.remove(path);
|
||||
debug_assert!(_removed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
println!();
|
||||
|
||||
print_separator("DragonFly BSD Audit");
|
||||
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
if !Command::new(sudo)
|
||||
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||
|
||||
@@ -30,7 +30,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
println!();
|
||||
|
||||
print_separator("FreeBSD Audit");
|
||||
|
||||
Command::new(sudo)
|
||||
.args(["/usr/sbin/pkg", "audit", "-Fr"])
|
||||
.status_checked()?;
|
||||
|
||||
@@ -24,7 +24,7 @@ pub enum Distribution {
|
||||
CentOS,
|
||||
ClearLinux,
|
||||
Fedora,
|
||||
FedoraSilverblue,
|
||||
FedoraImmutable,
|
||||
Debian,
|
||||
Gentoo,
|
||||
OpenMandriva,
|
||||
@@ -54,8 +54,12 @@ impl Distribution {
|
||||
Some("clear-linux-os") => Distribution::ClearLinux,
|
||||
Some("fedora") | Some("nobara") => {
|
||||
return if let Some(variant) = variant {
|
||||
if variant.contains(&"Silverblue") {
|
||||
Ok(Distribution::FedoraSilverblue)
|
||||
if variant.contains(&"Silverblue")
|
||||
|| variant.contains(&"Kinoite")
|
||||
|| variant.contains(&"Sericea")
|
||||
|| variant.contains(&"Onyx")
|
||||
{
|
||||
Ok(Distribution::FedoraImmutable)
|
||||
} else {
|
||||
Ok(Distribution::Fedora)
|
||||
}
|
||||
@@ -131,7 +135,7 @@ impl Distribution {
|
||||
Distribution::Alpine => upgrade_alpine_linux(ctx),
|
||||
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
|
||||
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
|
||||
Distribution::FedoraSilverblue => upgrade_fedora_silverblue(ctx),
|
||||
Distribution::FedoraImmutable => upgrade_fedora_immutable(ctx),
|
||||
Distribution::ClearLinux => upgrade_clearlinux(ctx),
|
||||
Distribution::Debian => upgrade_debian(ctx),
|
||||
Distribution::Gentoo => upgrade_gentoo(ctx),
|
||||
@@ -230,7 +234,7 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_fedora_silverblue(ctx: &ExecutionContext) -> Result<()> {
|
||||
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
|
||||
let ostree = require("rpm-ostree")?;
|
||||
let mut command = ctx.run_type().execute(ostree);
|
||||
command.arg("upgrade");
|
||||
@@ -1036,6 +1040,17 @@ mod tests {
|
||||
test_template(include_str!("os_release/fedora"), Distribution::Fedora);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fedora_immutable() {
|
||||
test_template(
|
||||
include_str!("os_release/fedorasilverblue"),
|
||||
Distribution::FedoraImmutable,
|
||||
);
|
||||
test_template(include_str!("os_release/fedorakinoite"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/fedoraonyx"), Distribution::FedoraImmutable);
|
||||
test_template(include_str!("os_release/fedorasericea"), Distribution::FedoraImmutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manjaro() {
|
||||
test_template(include_str!("os_release/manjaro"), Distribution::Arch);
|
||||
|
||||
23
src/steps/os/os_release/fedorakinoite
Normal file
23
src/steps/os/os_release/fedorakinoite
Normal file
@@ -0,0 +1,23 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39.20240105.0 (Kinoite)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://kinoite.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-11-12
|
||||
VARIANT="Kinoite"
|
||||
VARIANT_ID=kinoite
|
||||
OSTREE_VERSION='39.20240105.0'
|
||||
22
src/steps/os/os_release/fedoraonyx
Normal file
22
src/steps/os/os_release/fedoraonyx
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Onyx)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Onyx)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/onyx/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Onyx"
|
||||
VARIANT_ID=onyx
|
||||
22
src/steps/os/os_release/fedorasericea
Normal file
22
src/steps/os/os_release/fedorasericea
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Sericea)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Sericea)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://fedoraproject.org/sericea/"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Sericea"
|
||||
VARIANT_ID=sericea
|
||||
22
src/steps/os/os_release/fedorasilverblue
Normal file
22
src/steps/os/os_release/fedorasilverblue
Normal file
@@ -0,0 +1,22 @@
|
||||
NAME="Fedora Linux"
|
||||
VERSION="39 (Silverblue)"
|
||||
ID=fedora
|
||||
VERSION_ID=39
|
||||
VERSION_CODENAME=""
|
||||
PLATFORM_ID="platform:f39"
|
||||
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
|
||||
ANSI_COLOR="0;38;2;60;110;180"
|
||||
LOGO=fedora-logo-icon
|
||||
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||
DEFAULT_HOSTNAME="fedora"
|
||||
HOME_URL="https://silverblue.fedoraproject.org"
|
||||
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
|
||||
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
|
||||
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||
SUPPORT_END=2024-05-14
|
||||
VARIANT="Silverblue"
|
||||
VARIANT_ID=silverblue
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Component;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env::var, path::Path};
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::{Step, HOME_DIR};
|
||||
use color_eyre::eyre::eyre;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::eyre::Result;
|
||||
use home;
|
||||
use ini::Ini;
|
||||
@@ -283,7 +287,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
||||
variant.execute(run_type).arg("update").status_checked()?;
|
||||
variant
|
||||
.execute(run_type)
|
||||
.args(["upgrade", "--ignore-pinned", "--formula"])
|
||||
.args(["upgrade", "--formula"])
|
||||
.status_checked()?;
|
||||
|
||||
if ctx.config().cleanup() {
|
||||
@@ -365,23 +369,8 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
debug!("nix profile: {:?}", profile_path);
|
||||
let manifest_json_path = profile_path.join("manifest.json");
|
||||
|
||||
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
|
||||
#[allow(unused_mut)]
|
||||
let mut should_self_upgrade = cfg!(target_os = "macos");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// We can't use `nix upgrade-nix` on NixOS.
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
should_self_upgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
print_separator("Nix");
|
||||
|
||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||
debug!("Multi user nix: {}", multi_user);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if require("darwin-rebuild").is_ok() {
|
||||
@@ -393,30 +382,12 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
let run_type = ctx.run_type();
|
||||
|
||||
let nix_args = ["--extra-experimental-features", "nix-command"];
|
||||
|
||||
if should_self_upgrade {
|
||||
if multi_user {
|
||||
ctx.execute_elevated(&nix, true)?
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()?;
|
||||
} else {
|
||||
run_type
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()?;
|
||||
}
|
||||
}
|
||||
|
||||
run_type.execute(nix_channel).arg("--update").status_checked()?;
|
||||
|
||||
if Path::new(&manifest_json_path).exists() {
|
||||
run_type
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.execute(nix)
|
||||
.args(nix_args())
|
||||
.arg("profile")
|
||||
.arg("upgrade")
|
||||
.arg(".*")
|
||||
@@ -432,6 +403,123 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let nix = require("nix")?;
|
||||
|
||||
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
|
||||
#[allow(unused_mut)]
|
||||
let mut should_self_upgrade = cfg!(target_os = "macos");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// We can't use `nix upgrade-nix` on NixOS.
|
||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||
should_self_upgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
if !should_self_upgrade {
|
||||
return Err(SkipStep(String::from(
|
||||
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
if nix_profile_dir(&nix)?.is_none() {
|
||||
return Err(SkipStep(String::from(
|
||||
"`nix upgrade-nix` cannot be run when Nix is installed in a profile",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
print_separator("Nix (self-upgrade)");
|
||||
|
||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||
debug!("Multi user nix: {}", multi_user);
|
||||
|
||||
let nix_args = nix_args();
|
||||
if multi_user {
|
||||
ctx.execute_elevated(&nix, true)?
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()
|
||||
} else {
|
||||
ctx.run_type()
|
||||
.execute(&nix)
|
||||
.args(nix_args)
|
||||
.arg("upgrade-nix")
|
||||
.status_checked()
|
||||
}
|
||||
}
|
||||
|
||||
/// If we try to `nix upgrade-nix` but Nix is installed with `nix profile`, we'll get a `does not
|
||||
/// appear to be part of a Nix profile` error.
|
||||
///
|
||||
/// We duplicate some of the `nix` logic here to avoid this.
|
||||
/// See: <https://github.com/NixOS/nix/blob/f0180487a0e4c0091b46cb1469c44144f5400240/src/nix/upgrade-nix.cc#L102-L139>
|
||||
///
|
||||
/// See: <https://github.com/NixOS/nix/issues/5473>
|
||||
fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
||||
// NOTE: `nix` uses the location of the `nix-env` binary for this but we're using the `nix`
|
||||
// binary; should be the same.
|
||||
let nix_bin_dir = nix.parent();
|
||||
if nix_bin_dir.and_then(|p| p.file_name()) != Some(OsStr::new("bin")) {
|
||||
debug!("Nix is not installed in a `bin` directory: {nix_bin_dir:?}");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let nix_dir = nix_bin_dir
|
||||
.and_then(|bin_dir| bin_dir.parent())
|
||||
.ok_or_else(|| eyre!("Unable to find Nix install directory from Nix binary {nix:?}"))?;
|
||||
|
||||
debug!("Found Nix in {nix_dir:?}");
|
||||
|
||||
let mut profile_dir = nix_dir.to_path_buf();
|
||||
while profile_dir.is_symlink() {
|
||||
profile_dir = profile_dir
|
||||
.parent()
|
||||
.ok_or_else(|| eyre!("Path has no parent: {profile_dir:?}"))?
|
||||
.join(
|
||||
profile_dir
|
||||
.read_link()
|
||||
.wrap_err_with(|| format!("Failed to read symlink {profile_dir:?}"))?,
|
||||
);
|
||||
|
||||
// NOTE: `nix` uses a hand-rolled canonicalize function, Rust just uses `realpath`.
|
||||
if profile_dir
|
||||
.canonicalize()
|
||||
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?
|
||||
.components()
|
||||
.any(|component| component == Component::Normal(OsStr::new("profiles")))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Found Nix profile {profile_dir:?}");
|
||||
|
||||
let user_env = profile_dir
|
||||
.canonicalize()
|
||||
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
|
||||
|
||||
Ok(
|
||||
if user_env
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.map(|name| name.ends_with("user-environment"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(profile_dir)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn nix_args() -> [&'static str; 2] {
|
||||
["--extra-experimental-features", "nix-command"]
|
||||
}
|
||||
|
||||
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
||||
let yadm = require("yadm")?;
|
||||
|
||||
@@ -555,6 +643,19 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
|
||||
ctx.run_type().execute(bun).arg("upgrade").status_checked()
|
||||
}
|
||||
|
||||
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let bun = require("bun")?;
|
||||
|
||||
print_separator("Bun Packages");
|
||||
|
||||
if !HOME_DIR.join(".bun/install/global/package.json").exists() {
|
||||
println!("No global packages installed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
|
||||
}
|
||||
|
||||
/// Update dotfiles with `rcm(7)`.
|
||||
///
|
||||
/// See: <https://github.com/thoughtbot/rcm>
|
||||
|
||||
@@ -8,7 +8,6 @@ use tracing::debug;
|
||||
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::steps::git::GitAction;
|
||||
use crate::terminal::{print_separator, print_warning};
|
||||
use crate::utils::{require, which};
|
||||
use crate::{error::SkipStep, steps::git::Repositories};
|
||||
@@ -240,7 +239,7 @@ pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
|
||||
if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) {
|
||||
debug!("Startup link: {:?}", lnk);
|
||||
if let Some(path) = lnk.relative_path() {
|
||||
git_repos.insert_if_repo(&startup_dir.join(path), GitAction::Pull);
|
||||
git_repos.insert_if_repo(&startup_dir.join(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,10 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("zinit");
|
||||
|
||||
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),);
|
||||
let cmd = format!(
|
||||
"source {} && zinit self-update && zinit update --all -p",
|
||||
zshrc.display(),
|
||||
);
|
||||
ctx.run_type()
|
||||
.execute(zsh)
|
||||
.args(["-i", "-c", cmd.as_str()])
|
||||
@@ -137,7 +140,7 @@ pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
print_separator("zi");
|
||||
|
||||
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),);
|
||||
let cmd = format!("source {} && zi self-update && zi update --all -p", zshrc.display(),);
|
||||
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
||||
}
|
||||
|
||||
@@ -176,21 +179,15 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
// children processes won't get it either, so we source the zshrc and set
|
||||
// the ZSH variable for topgrade here.
|
||||
if ctx.under_ssh() {
|
||||
let zshrc_path = zshrc().require()?;
|
||||
let output = Command::new("zsh")
|
||||
.args([
|
||||
"-c",
|
||||
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
|
||||
format!(
|
||||
"source {} > /dev/null && export -p | grep ZSH > /dev/null && echo $ZSH",
|
||||
zshrc_path.display()
|
||||
)
|
||||
.as_str(),
|
||||
])
|
||||
.output_checked_utf8()?;
|
||||
let zsh_env = output.stdout.trim();
|
||||
if !zsh_env.is_empty() {
|
||||
env::set_var("ZSH", zsh_env);
|
||||
let res_env_zsh = Command::new("zsh")
|
||||
.args(["-ic", "print -rn -- ${ZSH:?}"])
|
||||
.output_checked_utf8();
|
||||
|
||||
// this command will fail if `ZSH` is not set
|
||||
if let Ok(output) = res_env_zsh {
|
||||
let env_zsh = output.stdout;
|
||||
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
|
||||
env::set_var("ZSH", env_zsh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,11 +224,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(), crate::steps::git::GitAction::Pull);
|
||||
custom_repos.insert_if_repo(entry.path());
|
||||
}
|
||||
|
||||
custom_repos.remove_from_pull(&oh_my_zsh.to_string_lossy());
|
||||
if !custom_repos.pull_is_empty() {
|
||||
custom_repos.remove(&oh_my_zsh.to_string_lossy());
|
||||
if !custom_repos.is_empty() {
|
||||
println!("Pulling custom plugins and themes");
|
||||
ctx.git().multi_pull(&custom_repos, ctx)?;
|
||||
}
|
||||
|
||||
41
src/utils.rs
41
src/utils.rs
@@ -119,44 +119,13 @@ pub fn string_prepend_str(string: &mut String, s: &str) {
|
||||
*string = new_string;
|
||||
}
|
||||
|
||||
/* sys-info-rs
|
||||
*
|
||||
* Copyright (c) 2015 Siyu Wang
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn hostname() -> Result<String> {
|
||||
use std::ffi;
|
||||
extern crate libc;
|
||||
|
||||
unsafe {
|
||||
let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
|
||||
let mut buf = Vec::<u8>::with_capacity(buf_size + 1);
|
||||
|
||||
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf_size) < 0 {
|
||||
return Err(SkipStep(format!("Failed to get hostname: {}", std::io::Error::last_os_error())).into());
|
||||
}
|
||||
let hostname_len = libc::strnlen(buf.as_ptr() as *const libc::c_char, buf_size);
|
||||
buf.set_len(hostname_len);
|
||||
|
||||
Ok(ffi::CString::new(buf).unwrap().into_string().unwrap())
|
||||
match nix::unistd::gethostname() {
|
||||
Ok(os_str) => Ok(os_str
|
||||
.into_string()
|
||||
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user