jujutsu_lib/
settings.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16use std::sync::{Arc, Mutex};
17
18use chrono::DateTime;
19use rand::prelude::*;
20use rand_chacha::ChaCha20Rng;
21
22use crate::backend::{ChangeId, ObjectId, Signature, Timestamp};
23
24#[derive(Debug, Clone)]
25pub struct UserSettings {
26    config: config::Config,
27    timestamp: Option<Timestamp>,
28    rng: Arc<JJRng>,
29}
30
31#[derive(Debug, Clone)]
32pub struct RepoSettings {
33    _config: config::Config,
34}
35
36#[derive(Debug, Clone)]
37pub struct GitSettings {
38    pub auto_local_branch: bool,
39}
40
41impl GitSettings {
42    pub fn from_config(config: &config::Config) -> Self {
43        GitSettings {
44            auto_local_branch: config.get_bool("git.auto-local-branch").unwrap_or(true),
45        }
46    }
47}
48
49impl Default for GitSettings {
50    fn default() -> Self {
51        GitSettings {
52            auto_local_branch: true,
53        }
54    }
55}
56
57fn get_timestamp_config(config: &config::Config, key: &str) -> Option<Timestamp> {
58    match config.get_string(key) {
59        Ok(timestamp_str) => match DateTime::parse_from_rfc3339(&timestamp_str) {
60            Ok(datetime) => Some(Timestamp::from_datetime(datetime)),
61            Err(_) => None,
62        },
63        Err(_) => None,
64    }
65}
66
67fn get_rng_seed_config(config: &config::Config) -> Option<u64> {
68    config
69        .get_string("debug.randomness-seed")
70        .ok()
71        .and_then(|str| str.parse().ok())
72}
73
74impl UserSettings {
75    pub fn from_config(config: config::Config) -> Self {
76        let timestamp = get_timestamp_config(&config, "debug.commit-timestamp");
77        let rng_seed = get_rng_seed_config(&config);
78        UserSettings {
79            config,
80            timestamp,
81            rng: Arc::new(JJRng::new(rng_seed)),
82        }
83    }
84
85    // TODO: Reconsider UserSettings/RepoSettings abstraction. See
86    // https://github.com/martinvonz/jj/issues/616#issuecomment-1345170699
87    pub fn with_repo(&self, _repo_path: &Path) -> Result<RepoSettings, config::ConfigError> {
88        let config = self.config.clone();
89        Ok(RepoSettings { _config: config })
90    }
91
92    pub fn get_rng(&self) -> Arc<JJRng> {
93        self.rng.clone()
94    }
95
96    pub fn user_name(&self) -> String {
97        self.config
98            .get_string("user.name")
99            .unwrap_or_else(|_| Self::user_name_placeholder().to_string())
100    }
101
102    pub fn user_name_placeholder() -> &'static str {
103        "(no name configured)"
104    }
105
106    pub fn user_email(&self) -> String {
107        self.config
108            .get_string("user.email")
109            .unwrap_or_else(|_| Self::user_email_placeholder().to_string())
110    }
111
112    pub fn user_email_placeholder() -> &'static str {
113        "(no email configured)"
114    }
115
116    pub fn operation_timestamp(&self) -> Option<Timestamp> {
117        get_timestamp_config(&self.config, "debug.operation-timestamp")
118    }
119
120    pub fn operation_hostname(&self) -> String {
121        self.config
122            .get_string("operation.hostname")
123            .unwrap_or_else(|_| whoami::hostname())
124    }
125
126    pub fn operation_username(&self) -> String {
127        self.config
128            .get_string("operation.username")
129            .unwrap_or_else(|_| whoami::username())
130    }
131
132    pub fn push_branch_prefix(&self) -> String {
133        self.config
134            .get_string("push.branch-prefix")
135            .unwrap_or_else(|_| "push-".to_string())
136    }
137
138    pub fn default_revset(&self) -> String {
139        self.config
140            .get_string("ui.default-revset")
141            .unwrap_or_else(|_| {
142                "@ | (remote_branches() | tags()).. | ((remote_branches() | tags())..)-".to_string()
143            })
144    }
145
146    pub fn signature(&self) -> Signature {
147        let timestamp = self.timestamp.clone().unwrap_or_else(Timestamp::now);
148        Signature {
149            name: self.user_name(),
150            email: self.user_email(),
151            timestamp,
152        }
153    }
154
155    pub fn allow_native_backend(&self) -> bool {
156        self.config
157            .get_bool("ui.allow-init-native")
158            .unwrap_or(false)
159    }
160
161    pub fn oplog_relative_timestamps(&self) -> bool {
162        self.config
163            .get_bool("ui.oplog-relative-timestamps")
164            .unwrap_or(true)
165    }
166
167    pub fn config(&self) -> &config::Config {
168        &self.config
169    }
170
171    pub fn git_settings(&self) -> GitSettings {
172        GitSettings::from_config(&self.config)
173    }
174
175    pub fn graph_style(&self) -> String {
176        self.config
177            .get_string("ui.graph.style")
178            .unwrap_or_else(|_| "curved".to_string())
179    }
180}
181
182/// This Rng uses interior mutability to allow generating random values using an
183/// immutable reference. It also fixes a specific seedable RNG for
184/// reproducibility.
185#[derive(Debug)]
186pub struct JJRng(Mutex<ChaCha20Rng>);
187impl JJRng {
188    pub fn new_change_id(&self, length: usize) -> ChangeId {
189        let mut rng = self.0.lock().unwrap();
190        let random_bytes = (0..length).map(|_| rng.gen::<u8>()).collect();
191        ChangeId::new(random_bytes)
192    }
193
194    /// Creates a new RNGs. Could be made public, but we'd like to encourage all
195    /// RNGs references to point to the same RNG.
196    fn new(seed: Option<u64>) -> Self {
197        Self(Mutex::new(JJRng::internal_rng_from_seed(seed)))
198    }
199
200    fn internal_rng_from_seed(seed: Option<u64>) -> ChaCha20Rng {
201        match seed {
202            Some(seed) => ChaCha20Rng::seed_from_u64(seed),
203            None => ChaCha20Rng::from_entropy(),
204        }
205    }
206}