1use std::time::Duration;
4
5use anyhow::Result;
6
7use crate::cli::PushMode;
8use crate::git;
9
10pub const PROVIDER_KEY: &str = "stk.provider";
11pub const REMOTE_KEY: &str = "stk.remote";
12pub const UPDATE_REFS_KEY: &str = "stk.updateRefs";
13pub const PUSH_ON_RESTACK_KEY: &str = "stk.pushOnRestack";
14pub const PUSH_ON_SUBMIT_KEY: &str = "stk.pushOnSubmit";
15pub const SUBMIT_STACK_KEY: &str = "stk.submitStack";
16pub const MERGE_STRATEGY_KEY: &str = "stk.mergeStrategy";
17pub const MERGE_WAIT_KEY: &str = "stk.mergeWait";
18pub const SUBMIT_DRAFT_KEY: &str = "stk.submitDraft";
19pub const NO_UPDATE_CHECK_KEY: &str = "stk.noUpdateCheck";
20pub const ABSORB_INCLUDE_UNSTAGED_KEY: &str = "stk.absorbIncludeUnstaged";
21pub const GITLAB_HOST_KEY: &str = "stk.gitlabHost";
22pub const CHECK_TIMEOUT_KEY: &str = "stk.checkTimeout";
23pub const DEFAULT_REMOTE: &str = "origin";
24
25pub const DEFAULT_CHECK_TIMEOUT_SECS: u64 = 1800;
29
30pub const SETTINGS: &[(&str, &str)] = &[
33 (PROVIDER_KEY, "auto-detect from the remote URL"),
34 (REMOTE_KEY, DEFAULT_REMOTE),
35 (UPDATE_REFS_KEY, "false"),
36 (PUSH_ON_RESTACK_KEY, "false"),
37 (PUSH_ON_SUBMIT_KEY, "false"),
38 (SUBMIT_STACK_KEY, "false"),
39 (MERGE_STRATEGY_KEY, "squash"),
40 (MERGE_WAIT_KEY, "false"),
41 (SUBMIT_DRAFT_KEY, "false"),
42 (NO_UPDATE_CHECK_KEY, "false"),
43 (ABSORB_INCLUDE_UNSTAGED_KEY, "false"),
44 (GITLAB_HOST_KEY, "none; gitlab.com is always detected"),
45 (CHECK_TIMEOUT_KEY, "1800 (30m); 0 waits indefinitely"),
46];
47
48pub fn remote() -> Result<String> {
50 Ok(git::config_get(REMOTE_KEY)?.unwrap_or_else(|| DEFAULT_REMOTE.to_owned()))
51}
52
53pub fn gitlab_host() -> Result<Option<String>> {
57 git::config_get(GITLAB_HOST_KEY)
58}
59
60pub fn merge_strategy() -> Result<String> {
62 let strategy = git::config_get(MERGE_STRATEGY_KEY)?.unwrap_or_else(|| "squash".to_owned());
63 match strategy.as_str() {
64 "squash" | "rebase" | "merge" => Ok(strategy),
65 other => anyhow::bail!(
66 "unsupported stk.mergeStrategy value {other:?}; expected squash, rebase, or merge"
67 ),
68 }
69}
70
71pub fn check_timeout() -> Result<Option<Duration>> {
75 parse_check_timeout(git::config_get(CHECK_TIMEOUT_KEY)?.as_deref())
76}
77
78fn parse_check_timeout(value: Option<&str>) -> Result<Option<Duration>> {
79 let seconds = match value {
80 Some(raw) => raw.trim().parse::<u64>().map_err(|_| {
81 anyhow::anyhow!(
82 "invalid {CHECK_TIMEOUT_KEY} value {raw:?}; expected a whole number of seconds"
83 )
84 })?,
85 None => DEFAULT_CHECK_TIMEOUT_SECS,
86 };
87 Ok((seconds > 0).then(|| Duration::from_secs(seconds)))
89}
90
91pub fn bool_setting(key: &str) -> Result<bool> {
93 Ok(git::config_get_bool(key)?.unwrap_or(false))
94}
95
96pub fn push_enabled(mode: PushMode, key: &str) -> Result<bool> {
98 match mode {
99 PushMode::Config => Ok(git::config_get_bool(key)?.unwrap_or(false)),
100 PushMode::Enabled => Ok(true),
101 PushMode::Disabled => Ok(false),
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn check_timeout_defaults_when_unset() {
111 assert_eq!(
112 parse_check_timeout(None).unwrap(),
113 Some(Duration::from_secs(DEFAULT_CHECK_TIMEOUT_SECS))
114 );
115 }
116
117 #[test]
118 fn check_timeout_zero_waits_indefinitely() {
119 assert_eq!(parse_check_timeout(Some("0")).unwrap(), None);
120 }
121
122 #[test]
123 fn check_timeout_reads_whole_seconds() {
124 assert_eq!(
125 parse_check_timeout(Some("300")).unwrap(),
126 Some(Duration::from_secs(300))
127 );
128 assert_eq!(
130 parse_check_timeout(Some(" 60 ")).unwrap(),
131 Some(Duration::from_secs(60))
132 );
133 }
134
135 #[test]
136 fn check_timeout_rejects_non_numbers() {
137 let error = parse_check_timeout(Some("soon")).unwrap_err();
138 assert!(
139 error.to_string().contains("stk.checkTimeout"),
140 "unexpected error: {error:#}"
141 );
142 }
143}