Refactor `pijul-config` to support layered configuration
Dependencies
- [2]
SXEYMYF7Fixing the bad changes in history (unfortunately, by rebooting). - [3]
L4JXJHWXpijul/*: reorganize imports and remove extern crate - [4]
SEWGHUHQ.pijul/config: simplify remotes and hooks - [5]
BZSC7VMYaddress clippy lints - [6]
SLJ3OHD4unrecord: show list of changes if none were given as arguments - [7]
CCLLB7OIUpgrading to Sanakirja 0.15 + version bump - [8]
KWAGWB73Adding extra dependencies from the config file - [9]
VL7ZYKHBRunning hooks through shell on Windows and Unix - [10]
5BB266P6Optional colours in the global config file - [11]
I24UEJQLVarious post-fire fixes - [12]
TFPETWTVAdd config options for patch message templates - [13]
EEBKW7VTKeys and identities - [14]
ZSFJT4SFAllow remotes to have a different push and pull address - [15]
CB7UPUQFCustomizable ignore_kinds (and a fix of .write()) - [16]
FVU3Y2U3Adding a local "unrecord_changes" option in addition to the global one - [17]
FVQYZQFLCreate dialoguer themes based on global config - [18]
4OJWMSOWFully replace crate::Identity - [19]
6FRPUHWKFix identity tests - [20]
H4AU6QRPNew config for HTTP remotes - [21]
DOEG3V7UOnly re-write identity data when changed - [22]
QCPIBC6MMake the default remote configurable through the cli - [23]
EJ7TFFOWRe-adding Cargo.lock - [24]
LZOGKBJXnew command `pijul client` for authenticating to a HTTP server - [25]
RVAH6PXAGetting libpijul to compile to WASM32 - [26]
X642QQQTRun record hooks from the repository root - [27]
7UU3TV5WRefactor `pijul::config` into new crate - [28]
HRNMY2PGCorrectly read empty config file - [29]
2TWREKSRTreat missing config file as empty - [30]
2MKP7CB7Move dependencies into workspace `Cargo.toml` - [31]
HJVWPKWVMigrate crates to edition 2024 - [32]
LTI3LT2GBump all dependencies to latest compatible minor versions - [33]
67GIAQEUHandle named remotes in pijul remote and remote delete - [34]
OUEZV7EL🩹 Resolve conflicts, bump Cargo.lock - [35]
A3RM526YIntegrating identity malleability - [36]
ICEK2JVGAdd reset_overwrites_changes config option. When set to never --force is required for reset - [37]
YWL2K3P7Removing the `Direction` argument in pijul::remote::Repository::remote - [38]
IUGP6ZGBAdd support for ~/.config/pijul even on macos - [39]
5OGOE4VWStore the current channel in the pristine - [40]
VQPAUKBQchannel switch as an alias to reset
Change contents
- edit in Cargo.lock at line 115
[[package]]name = "atomic"version = "0.6.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"dependencies = ["bytemuck",] - edit in Cargo.lock at line 283
[[package]]name = "bytemuck"version = "1.23.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" - edit in Cargo.lock at line 862
name = "figment"version = "0.10.19"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"dependencies = ["atomic","serde","toml","uncased","version_check",][[package]] - edit in Cargo.lock at line 2229
"figment","libpijul", - edit in Cargo.lock at line 3374
name = "uncased"version = "0.9.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"dependencies = ["version_check",][[package]] - edit in Cargo.toml at line 57
figment = { version = "0.10", features = ["toml"] } - file addition: template.rs[27.41]
use std::path::PathBuf;use serde_derive::{Deserialize, Serialize};#[derive(Clone, Debug, Serialize, Deserialize)]pub struct Template {pub message: Option<PathBuf>,pub description: Option<PathBuf>,} - file addition: remote.rs[27.41]
use std::collections::HashMap;use serde_derive::{Deserialize, Serialize};#[derive(Clone, Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum RemoteConfig {Ssh {name: String,ssh: String,},Http {name: String,http: String,#[serde(default)]headers: HashMap<String, RemoteHttpHeader>,},}impl RemoteConfig {pub fn name(&self) -> &str {match self {RemoteConfig::Ssh { name, .. } => name,RemoteConfig::Http { name, .. } => name,}}pub fn url(&self) -> &str {match self {RemoteConfig::Ssh { ssh, .. } => ssh,RemoteConfig::Http { http, .. } => http,}}pub fn db_uses_name(&self) -> bool {match self {RemoteConfig::Ssh { .. } => false,RemoteConfig::Http { .. } => true,}}}#[derive(Clone, Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum RemoteHttpHeader {String(String),Shell(Shell),}#[derive(Clone, Debug, Serialize, Deserialize)]pub struct Shell {pub shell: String,} - file addition: local.rs[27.41]
use std::fs::File;use std::io::{Read, Write};use std::path::{Path, PathBuf};use crate::remote::RemoteConfig;use crate::{REPOSITORY_CONFIG_FILE, Shared};use serde_derive::{Deserialize, Serialize};#[derive(Clone, Debug, Serialize, Deserialize, Default)]pub struct Local {#[serde(skip)]source_file: Option<PathBuf>,pub default_remote: Option<String>,#[serde(default, skip_serializing_if = "Vec::is_empty")]pub extra_dependencies: Vec<String>,#[serde(default, skip_serializing_if = "Vec::is_empty")]pub remotes: Vec<RemoteConfig>,#[serde(flatten)]pub shared_config: Shared,}impl Local {pub fn config_file(repository_path: &Path) -> PathBuf {repository_path.join(libpijul::DOT_DIR).join(REPOSITORY_CONFIG_FILE)}pub fn read_contents(config_path: &Path) -> Result<String, anyhow::Error> {let mut config_file = File::open(config_path)?;let mut file_contents = String::new();config_file.read_to_string(&mut file_contents)?;Ok(file_contents)}pub fn parse_contents(config_path: &Path, toml_data: &str) -> Result<Self, anyhow::Error> {let mut config: Self = toml::from_str(&toml_data)?;// Store the location of the original configuration file, so it can later be written to// The `source_file` field is annotated with `#[serde(skip)]` and should be always be None unless set manuallyassert!(config.source_file.is_none());config.source_file = Some(config_path.to_path_buf());Ok(config)}pub fn write(&self) -> Result<(), anyhow::Error> {let mut config_file = File::create(self.source_file.clone().unwrap())?;let file_contents = toml::to_string_pretty(self)?;config_file.write_all(file_contents.as_bytes())?;Ok(())}} - edit in pijul-config/src/lib.rs at line 1
pub mod author;pub mod global;pub mod hook;pub mod local;pub mod remote;pub mod template;use author::Author;use global::Global;use hook::Hooks;use local::Local;use remote::RemoteConfig;use template::Template; - replacement in pijul-config/src/lib.rs at line 16
use std::fs::File;use std::io;use std::io::{Read, Write};use std::path::{Path, PathBuf};use std::path::Path; - edit in pijul-config/src/lib.rs at line 18
use anyhow::anyhow; - replacement in pijul-config/src/lib.rs at line 19
use log::debug;use figment::Figment;use figment::providers::{Format, Toml}; - replacement in pijul-config/src/lib.rs at line 23[3.1021]→[29.114:164](∅→∅),[29.164]→[2.89940:89960](∅→∅),[2.89940]→[2.89940:89960](∅→∅),[2.89960]→[28.0:22](∅→∅),[28.22]→[13.89:113](∅→∅),[2.89960]→[13.89:113](∅→∅)
#[derive(Debug, Default, Serialize, Deserialize)]pub struct Global {#[serde(default)]pub author: Author,pub const REPOSITORY_CONFIG_FILE: &str = "config";pub const GLOBAL_CONFIG_FILE: &str = ".pijulconfig";pub const CONFIG_DIR: &str = "pijul";pub const CONFIG_FILE: &str = "config.toml";#[derive(Clone, Debug, Default, Serialize, Deserialize)]pub struct Shared { - replacement in pijul-config/src/lib.rs at line 34[10.63]→[12.25:62](∅→∅),[12.62]→[15.603:663](∅→∅),[12.62]→[10.63:66](∅→∅),[15.663]→[10.63:66](∅→∅),[10.63]→[10.63:66](∅→∅),[10.66]→[21.2124:2187](∅→∅),[21.2187]→[13.162:182](∅→∅),[13.162]→[13.162:182](∅→∅),[13.182]→[19.4662:4739](∅→∅),[19.4739]→[18.14349:14429](∅→∅),[18.14349]→[18.14349:14429](∅→∅),[18.14429]→[19.4740:4766](∅→∅),[19.4766]→[18.14456:14987](∅→∅),[18.14456]→[18.14456:14987](∅→∅),[18.14987]→[19.4767:4804](∅→∅),[19.4804]→[18.15025:15184](∅→∅),[18.15025]→[18.15025:15184](∅→∅),[18.15184]→[13.302:305](∅→∅),[13.302]→[13.302:305](∅→∅),[13.305]→[10.66:251](∅→∅),[10.66]→[10.66:251](∅→∅),[10.251]→[12.63:66](∅→∅),[12.66]→[17.139:220](∅→∅),[17.220]→[12.66:202](∅→∅),[12.66]→[12.66:202](∅→∅),[12.202]→[2.90002:90005](∅→∅),[10.251]→[2.90002:90005](∅→∅),[6.822]→[2.90002:90005](∅→∅),[2.90002]→[2.90002:90005](∅→∅),[2.90005]→[13.306:358](∅→∅),[13.358]→[5.295:329](∅→∅),[2.90005]→[5.295:329](∅→∅),[5.329]→[2.90047:90048](∅→∅),[2.90047]→[2.90047:90048](∅→∅),[2.90048]→[13.359:407](∅→∅),[13.407]→[17.221:407](∅→∅),[17.407]→[13.460:543](∅→∅),[13.460]→[13.460:543](∅→∅),[13.543]→[29.165:479](∅→∅)
pub template: Option<Templates>,pub ignore_kinds: Option<HashMap<String, Vec<String>>>,}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct Author {// Older versions called this 'name', but 'username' is more descriptive#[serde(alias = "name", default, skip_serializing_if = "String::is_empty")]pub username: String,#[serde(alias = "full_name", default, skip_serializing_if = "String::is_empty")]pub display_name: String,#[serde(default, skip_serializing_if = "String::is_empty")]pub email: String,#[serde(default, skip_serializing_if = "String::is_empty")]pub origin: String,// This has been moved to identity::Config, but we should still be able to read the values#[serde(default, skip_serializing)]pub key_path: Option<PathBuf>,}impl Default for Author {fn default() -> Self {Self {username: String::new(),email: String::new(),display_name: whoami::realname(),origin: String::new(),key_path: None,}}}#[derive(Debug, Serialize, Deserialize)]pub enum Choice {#[serde(rename = "auto")]Auto,#[serde(rename = "always")]Always,#[serde(rename = "never")]Never,}impl Default for Choice {fn default() -> Self {Self::Auto}}#[derive(Debug, Serialize, Deserialize)]pub struct Templates {pub message: Option<PathBuf>,pub description: Option<PathBuf>,}pub const GLOBAL_CONFIG_DIR: &str = ".pijulconfig";const CONFIG_DIR: &str = "pijul";pub fn global_config_dir() -> Option<PathBuf> {if let Ok(path) = std::env::var("PIJUL_CONFIG_DIR") {let dir = std::path::PathBuf::from(path);Some(dir)} else if let Some(mut dir) = dirs_next::config_dir() {dir.push(CONFIG_DIR);Some(dir)} else {None}}fn try_load_file(path: impl Into<PathBuf> + AsRef<Path>) -> Option<(io::Result<File>, PathBuf)> {let pp = path.as_ref();match File::open(pp) {Ok(v) => Some((Ok(v), path.into())),Err(e) if e.kind() == io::ErrorKind::NotFound => None,Err(e) => Some((Err(e), path.into())),}pub template: Option<Template>,#[serde(default)]pub hooks: Hooks, - edit in pijul-config/src/lib.rs at line 38[29.481]→[29.481:482](∅→∅),[29.482]→[2.90048:90062](∅→∅),[13.543]→[2.90048:90062](∅→∅),[2.90048]→[2.90048:90062](∅→∅),[2.90062]→[29.483:1533](∅→∅)
impl Global {pub fn load() -> Result<(Global, Option<u64>), anyhow::Error> {let res = None.or_else(|| {let mut path = global_config_dir()?;path.push("config.toml");try_load_file(path)}).or_else(|| {// Read from `$HOME/.config/pijul` dirlet mut path = dirs_next::home_dir()?;path.push(".config");path.push(CONFIG_DIR);path.push("config.toml");try_load_file(path)}).or_else(|| {// Read from `$HOME/.pijulconfig`let mut path = dirs_next::home_dir()?;path.push(GLOBAL_CONFIG_DIR);try_load_file(path)});let Some((file, path)) = res else {return Ok((Global::default(), None));};let mut file = file.map_err(|e| {anyhow!("Could not open configuration file at {}", path.display()).context(e)})?; - replacement in pijul-config/src/lib.rs at line 39
let mut buf = String::new();file.read_to_string(&mut buf).map_err(|e| {anyhow!("Could not read configuration file at {}", path.display()).context(e)})?;debug!("buf = {:?}", buf);let global: Global = toml::from_str(&buf).map_err(|e| {anyhow!("Could not parse configuration file at {}", path.display()).context(e)})?;let metadata = file.metadata()?;let file_age = metadata.modified()?.duration_since(std::time::SystemTime::UNIX_EPOCH)?.as_secs();#[derive(Debug, Default, Deserialize)]pub struct Config {// Store a copy of the original files, so that they can be modified independently#[serde(skip)]global_config: Option<Global>,#[serde(skip)]local_config: Option<Local>, - replacement in pijul-config/src/lib.rs at line 47
Ok((global, Some(file_age)))}}// Globalpub author: Author,#[serde(default)]pub ignore_kinds: HashMap<String, Vec<String>>, - replacement in pijul-config/src/lib.rs at line 52[2.90973]→[17.408:458](∅→∅),[14.307]→[2.91023:91043](∅→∅),[17.458]→[2.91023:91043](∅→∅),[2.91023]→[2.91023:91043](∅→∅)
#[derive(Debug, Serialize, Deserialize, Default)]pub struct Config {// Local - replacement in pijul-config/src/lib.rs at line 54
#[serde(default, skip_serializing_if = "Vec::is_empty")]#[serde(default)] - replacement in pijul-config/src/lib.rs at line 56
#[serde(default, skip_serializing_if = "Vec::is_empty")]#[serde(default)] - replacement in pijul-config/src/lib.rs at line 58
#[serde(default)]pub hooks: Hooks,// Shared - edit in pijul-config/src/lib.rs at line 64
pub template: Option<Template>,#[serde(default)]pub hooks: hook::Hooks, - replacement in pijul-config/src/lib.rs at line 69
#[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum RemoteConfig {Ssh {name: String,ssh: String,},Http {name: String,http: String,#[serde(default)]headers: HashMap<String, RemoteHttpHeader>,},}impl Config {pub fn load(repository_path: &Path) -> Result<Self, anyhow::Error> {let global_config_path = Global::config_file().unwrap();let local_config_path = Local::config_file(&repository_path); - replacement in pijul-config/src/lib.rs at line 74
impl RemoteConfig {pub fn name(&self) -> &str {match self {RemoteConfig::Ssh { name, .. } => name,RemoteConfig::Http { name, .. } => name,}}let global_config_contents = Global::read_contents(&global_config_path)?;let local_config_contents = Local::read_contents(&local_config_path)?;// Validate that the configuration sources are correctlet global_config = Global::parse_contents(&global_config_path, &global_config_contents)?;let local_config = Local::parse_contents(&local_config_path, &local_config_contents)?; - replacement in pijul-config/src/lib.rs at line 81[33.17]→[33.17:173](∅→∅),[33.173]→[14.794:804](∅→∅),[20.4092]→[14.794:804](∅→∅),[14.794]→[14.794:804](∅→∅)
pub fn url(&self) -> &str {match self {RemoteConfig::Ssh { ssh, .. } => ssh,RemoteConfig::Http { http, .. } => http,}// Merge the two configuration values, using the raw TOML string instead of the deserialized structs.// Figment uses a dictionary to store which fields are set, and using an already-deserialized// struct will guarantee that each layer will override the previous one.//// For example, if the optional `unrecord_changes` field is set as 1 globally but not set locally:// - Using deserialized structs (incorrect behaviour):// - Global config is set to Some(1)// - Local config is set to None - no value was found, so serde inserted the default// - The local config technically has a value set, so the final (incorrect) value is None// - Using strings (correct behaviour):// - Global config is set to Some(1)// - Local config is unset// - The final (correct) value is Some(1)let mut config: Self = Figment::new().merge(Toml::string(&global_config_contents)).merge(Toml::string(&local_config_contents)).extract()?;// These fields are annotated with #[serde(skip)] and therefore should be Noneassert!(config.global_config.is_none());assert!(config.local_config.is_none());// Store the original configuration sources so they can be modified laterconfig.global_config = Some(global_config);config.local_config = Some(local_config);Ok(config) - replacement in pijul-config/src/lib.rs at line 110
pub fn db_uses_name(&self) -> bool {match self {RemoteConfig::Ssh { .. } => false,RemoteConfig::Http { .. } => true,}pub fn global(&self) -> Option<Global> {self.global_config.clone() - edit in pijul-config/src/lib.rs at line 113
} - replacement in pijul-config/src/lib.rs at line 114[14.1609]→[20.4093:4219](∅→∅),[4.67]→[2.91196:91198](∅→∅),[10.315]→[2.91196:91198](∅→∅),[14.2296]→[2.91196:91198](∅→∅),[20.4219]→[2.91196:91198](∅→∅),[2.91196]→[2.91196:91198](∅→∅)
#[derive(Debug, Serialize, Deserialize)]#[serde(untagged)]pub enum RemoteHttpHeader {String(String),Shell(Shell),}pub fn local(&self) -> Option<Local> {self.local_config.clone()} - replacement in pijul-config/src/lib.rs at line 118[2.91199]→[17.488:529](∅→∅),[17.529]→[20.4220:4262](∅→∅),[20.4336]→[14.2396:2398](∅→∅),[14.2396]→[14.2396:2398](∅→∅)
#[derive(Debug, Serialize, Deserialize)]pub struct Shell {pub shell: String,}/// Choose the right dialoguer theme based on user's configpub fn theme(&self) -> Box<dyn theme::Theme + Send + Sync> {let color_choice = self.colors.unwrap_or_default(); - replacement in pijul-config/src/lib.rs at line 122[14.2399]→[2.91199:91268](∅→∅),[2.91199]→[2.91199:91268](∅→∅),[2.91268]→[4.68:90](∅→∅),[4.90]→[9.0:32](∅→∅)
#[derive(Debug, Serialize, Deserialize, Default)]pub struct Hooks {#[serde(default)]pub record: Vec<HookEntry>,match color_choice {Choice::Auto | Choice::Always => Box::new(theme::ColorfulTheme::default()),Choice::Never => Box::new(theme::SimpleTheme),}} - replacement in pijul-config/src/lib.rs at line 129
#[derive(Debug, Serialize, Deserialize)]pub struct HookEntry(toml::Value);#[derive(Debug, Serialize, Deserialize)]struct RawHook {command: String,args: Vec<String>,#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]pub enum Choice {#[default]#[serde(rename = "auto")]Auto,#[serde(rename = "always")]Always,#[serde(rename = "never")]Never, - edit in pijul-config/src/lib.rs at line 154[24.4341]→[20.4848:4851](∅→∅),[20.4848]→[20.4848:4851](∅→∅),[20.4851]→[9.217:234](∅→∅),[9.217]→[9.217:234](∅→∅),[9.234]→[26.27:95](∅→∅),[26.95]→[9.287:327](∅→∅),[9.287]→[9.287:327](∅→∅),[9.327]→[31.1734:1774](∅→∅),[31.1774]→[9.371:587](∅→∅),[9.371]→[9.371:587](∅→∅),[9.587]→[26.96:143](∅→∅),[26.143]→[9.587:923](∅→∅),[9.587]→[9.587:923](∅→∅),[9.923]→[26.144:187](∅→∅),[26.187]→[9.923:1331](∅→∅),[9.923]→[9.923:1331](∅→∅),[9.1331]→[26.188:231](∅→∅),[26.231]→[9.1331:1582](∅→∅),[9.1331]→[9.1331:1582](∅→∅),[9.1614]→[9.1614:1818](∅→∅),[11.1906]→[11.1906:1921](∅→∅),[2.91619]→[2.91619:93366](∅→∅),[2.93366]→[17.530:536](∅→∅)
}impl HookEntry {pub fn run(&self, path: PathBuf) -> Result<(), anyhow::Error> {let (proc, s) = match &self.0 {toml::Value::String(s) => {if s.is_empty() {return Ok(());}(if cfg!(target_os = "windows") {std::process::Command::new("cmd").current_dir(path).args(&["/C", s]).output().expect("failed to execute process")} else {std::process::Command::new(std::env::var("SHELL").unwrap_or("sh".to_string()),).current_dir(path).arg("-c").arg(s).output().expect("failed to execute process")},s.clone(),)}v => {let hook = v.clone().try_into::<RawHook>()?;(std::process::Command::new(&hook.command).current_dir(path).args(&hook.args).output().expect("failed to execute process"),hook.command,)}};if !proc.status.success() {let mut stderr = std::io::stderr();writeln!(stderr, "Hook {:?} exited with code {:?}", s, proc.status)?;std::process::exit(proc.status.code().unwrap_or(1))}Ok(())}}#[derive(Debug, Serialize, Deserialize)]struct Remote_ {ssh: Option<SshRemote>,local: Option<String>,url: Option<String>,}#[derive(Debug)]pub enum Remote {Ssh(SshRemote),Local { local: String },Http { url: String },None,}#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SshRemote {pub addr: String,}impl<'de> serde::Deserialize<'de> for Remote {fn deserialize<D>(deserializer: D) -> Result<Remote, D::Error>whereD: serde::de::Deserializer<'de>,{let r = Remote_::deserialize(deserializer)?;if let Some(ssh) = r.ssh {Ok(Remote::Ssh(ssh))} else if let Some(local) = r.local {Ok(Remote::Local { local })} else if let Some(url) = r.url {Ok(Remote::Http { url })} else {Ok(Remote::None)}}}impl serde::Serialize for Remote {fn serialize<D>(&self, serializer: D) -> Result<D::Ok, D::Error>whereD: serde::ser::Serializer,{let r = match *self {Remote::Ssh(ref ssh) => Remote_ {ssh: Some(ssh.clone()),local: None,url: None,},Remote::Local { ref local } => Remote_ {local: Some(local.to_string()),ssh: None,url: None,},Remote::Http { ref url } => Remote_ {local: None,ssh: None,url: Some(url.to_string()),},Remote::None => Remote_ {local: None,ssh: None,url: None,},};r.serialize(serializer)} - edit in pijul-config/src/lib.rs at line 155
/// Choose the right dialoguer theme based on user's configpub fn load_theme() -> Result<Box<dyn theme::Theme>, anyhow::Error> {if let Ok((config, _)) = Global::load() {let color_choice = config.colors.unwrap_or_default();match color_choice {Choice::Auto | Choice::Always => Ok(Box::new(theme::ColorfulTheme::default())),Choice::Never => Ok(Box::new(theme::SimpleTheme)),}} else {Ok(Box::new(theme::ColorfulTheme::default()))}} - file addition: hook.rs[27.41]
use std::io::Write;use std::path::PathBuf;use serde_derive::{Deserialize, Serialize};#[derive(Clone, Debug, Serialize, Deserialize, Default)]pub struct Hooks {#[serde(default)]pub record: Vec<HookEntry>,}#[derive(Clone, Debug, Serialize, Deserialize)]pub struct HookEntry(toml::Value);#[derive(Debug, Serialize, Deserialize)]struct RawHook {command: String,args: Vec<String>,}impl HookEntry {pub fn run(&self, path: PathBuf) -> Result<(), anyhow::Error> {let (proc, s) = match &self.0 {toml::Value::String(s) => {if s.is_empty() {return Ok(());}(if cfg!(target_os = "windows") {std::process::Command::new("cmd").current_dir(path).args(&["/C", s]).output().expect("failed to execute process")} else {std::process::Command::new(std::env::var("SHELL").unwrap_or("sh".to_string()),).current_dir(path).arg("-c").arg(s).output().expect("failed to execute process")},s.clone(),)}v => {let hook = v.clone().try_into::<RawHook>()?;(std::process::Command::new(&hook.command).current_dir(path).args(&hook.args).output().expect("failed to execute process"),hook.command,)}};if !proc.status.success() {let mut stderr = std::io::stderr();writeln!(stderr, "Hook {:?} exited with code {:?}", s, proc.status)?;std::process::exit(proc.status.code().unwrap_or(1))}Ok(())}} - file addition: global.rs[27.41]
use crate::author::Author;use crate::{CONFIG_DIR, CONFIG_FILE, GLOBAL_CONFIG_FILE, Shared};use std::collections::HashMap;use std::fs::File;use std::io::{Read, Write};use std::path::{Path, PathBuf};use serde_derive::{Deserialize, Serialize};#[derive(Clone, Debug, Default, Serialize, Deserialize)]pub struct Global {#[serde(skip)]source_file: Option<PathBuf>,#[serde(default)]pub author: Author,#[serde(default, skip_serializing_if = "HashMap::is_empty")]pub ignore_kinds: HashMap<String, Vec<String>>,#[serde(flatten)]pub shared_config: Shared,}impl Global {/// Select which configuration file to usepub fn config_file() -> Option<PathBuf> {// 1. PIJUL_CONFIG_DIR environment variablestd::env::var("PIJUL_CONFIG_DIR").ok().map(PathBuf::from)// 2. ~/.config/pijul/config.toml.or_else(|| match dirs_next::config_dir() {Some(config_dir) => {let config_path = config_dir.join(CONFIG_DIR).join(CONFIG_FILE);match config_path.exists() {true => Some(config_path),false => None,}}None => None,})// 3. ~/.pijulconfig.or_else(|| match dirs_next::home_dir() {Some(home_dir) => {let config_path = home_dir.join(GLOBAL_CONFIG_FILE);match config_path.exists() {true => Some(config_path),false => None,}}None => None,})}pub fn read_contents(config_path: &Path) -> Result<String, anyhow::Error> {let mut config_file = File::open(config_path)?;let mut file_contents = String::new();config_file.read_to_string(&mut file_contents)?;Ok(file_contents)}pub fn parse_contents(config_path: &Path, toml_data: &str) -> Result<Self, anyhow::Error> {let mut config: Self = toml::from_str(&toml_data)?;// Store the location of the original configuration file, so it can later be written to// The `source_file` field is annotated with `#[serde(skip)]` and should be always be None unless set manuallyassert!(config.source_file.is_none());config.source_file = Some(config_path.to_path_buf());Ok(config)}pub fn write(&self) -> Result<(), anyhow::Error> {let mut config_file = File::create(self.source_file.clone().unwrap())?;let file_contents = toml::to_string_pretty(self)?;config_file.write_all(file_contents.as_bytes())?;Ok(())}}// pub fn global_config_dir() -> Option<PathBuf> {// if let Ok(path) = std::env::var("PIJUL_CONFIG_DIR") {// let dir = std::path::PathBuf::from(path);// Some(dir)// } else if let Some(mut dir) = dirs_next::config_dir() {// dir.push(CONFIG_DIR);// Some(dir)// } else {// None// }// }// impl Global {// pub fn load() -> Result<(Global, Option<u64>), anyhow::Error> {// let res = None// .or_else(|| {// let mut path = global_config_dir()?;// path.push("config.toml");// try_load_file(path)// })// .or_else(|| {// // Read from `$HOME/.config/pijul` dir// let mut path = dirs_next::home_dir()?;// path.push(".config");// path.push(CONFIG_DIR);// path.push("config.toml");// try_load_file(path)// })// .or_else(|| {// // Read from `$HOME/.pijulconfig`// let mut path = dirs_next::home_dir()?;// path.push(GLOBAL_CONFIG_DIR);// try_load_file(path)// });// let Some((file, path)) = res else {// return Ok((Global::default(), None));// };// let mut file = file.map_err(|e| {// anyhow!("Could not open configuration file at {}", path.display()).context(e)// })?;// let mut buf = String::new();// file.read_to_string(&mut buf).map_err(|e| {// anyhow!("Could not read configuration file at {}", path.display()).context(e)// })?;// debug!("buf = {:?}", buf);// let global: Global = toml::from_str(&buf).map_err(|e| {// anyhow!("Could not parse configuration file at {}", path.display()).context(e)// })?;// let metadata = file.metadata()?;// let file_age = metadata// .modified()?// .duration_since(std::time::SystemTime::UNIX_EPOCH)?// .as_secs();// Ok((global, Some(file_age)))// }// } - file addition: author.rs[27.41]
use std::path::PathBuf;use serde_derive::{Deserialize, Serialize};#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct Author {// Older versions called this 'name', but 'username' is more descriptive#[serde(alias = "name", default, skip_serializing_if = "String::is_empty")]pub username: String,#[serde(alias = "full_name", default, skip_serializing_if = "String::is_empty")]pub display_name: String,#[serde(default, skip_serializing_if = "String::is_empty")]pub email: String,#[serde(default, skip_serializing_if = "String::is_empty")]pub origin: String,// This has been moved to identity::Config, but we should still be able to read the values#[serde(default, skip_serializing)]pub key_path: Option<PathBuf>,}impl Default for Author {fn default() -> Self {Self {username: String::new(),email: String::new(),display_name: whoami::realname(),origin: String::new(),key_path: None,}}} - edit in pijul-config/Cargo.toml at line 11
libpijul.workspace = true - edit in pijul-config/Cargo.toml at line 16
figment.workspace = true