1use std::path::Path;
16
17pub const LOG_COMMAND: &str = "journalctl --user --unit 'cli-git@*.service'";
19
20pub const LINGER_COMMAND: &str = "loginctl enable-linger";
22
23pub const RESTART_COMMAND: &str = "systemctl --user restart 'cli-git@*.service'";
25
26pub const CHECK_COMMANDS: [&str; 2] = ["git log -1", "git status"];
28
29pub const STARTING_NOTICE: &str = "Starting the commit service...";
31
32pub const SILENT_NOTICE: &str = "The commit service stopped answering; asking it again with the same commit, which cannot land twice.";
34
35pub const UPDATING_NOTICE: &str = "Updating the commit service...";
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum Platform {
49 Windows,
51 Bsd,
53 LinuxWithoutUserServices,
55 Other,
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
69pub enum ClientFailure {
70 DidNotStart,
72 NoActivation(Platform),
74 OtherAccount,
76 MayNotHaveLanded,
78 NoCommonVersion,
80 OtherRelease,
82 RepositoryFull(String),
84 RepositoryReadOnly(String),
86 BranchKeepsChanging { branch: String, times: usize },
88 BranchMoved { branch: String },
90 BranchMovedMessageSaved { branch: String, file: String, amend: bool },
92 BranchSwitched { branch: String, now: String, file: Option<String> },
95 ServiceSilent,
97 StillWithService { minutes: u64, blocker: Option<String> },
99 WithdrawalUnconfirmed { blocker: Option<String> },
102 StepTooSlow,
104 Refused,
106 IndexInUse(String),
108 Unavailable,
110 ProgramMissing(String),
112 CouldNotStart(String),
114 UnitInTheWay(String),
116 WorkNotHandedOver(String),
118 ContentPruned,
120}
121
122pub const NOTHING_CHANGED: &str = "Nothing in the repository changed.";
124
125pub fn render(failure: &ClientFailure) -> String {
135 return match failure {
136 ClientFailure::DidNotStart => format!(
137 "The commit service did not start, so this commit was not made. Its log shows why: `{LOG_COMMAND}`. \
138 Run the commit again after fixing that. {NOTHING_CHANGED}"
139 ),
140 ClientFailure::NoActivation(platform) => format!(
141 "This computer cannot start the commit service on demand, so this commit was not made. {} {NOTHING_CHANGED}",
142 platform_remedy(*platform)
143 ),
144 ClientFailure::OtherAccount => format!(
145 "Another account runs the commit service here, so this commit was not made. Use your own account, or ask \
146 the other account to stop its service, then run the commit again. {NOTHING_CHANGED}"
147 ),
148 ClientFailure::MayNotHaveLanded => format!(
149 "The commit service stopped before it confirmed this commit. Run `{}` to see whether it was made, and `{}` \
150 to see what is still staged. If it was not made, run the commit again.",
151 CHECK_COMMANDS[0], CHECK_COMMANDS[1]
152 ),
153 ClientFailure::NoCommonVersion => format!(
154 "This git is older than the installed commit service and cannot work with it, so this commit was not made. \
155 Update the git wrapper with the tool that installed it, and run the commit again. {NOTHING_CHANGED}"
156 ),
157 ClientFailure::OtherRelease => format!(
158 "The commit service that is running was started from another release of this git wrapper and did not stop when \
159 asked, so this commit was not made. Restart it with `{RESTART_COMMAND}`, and run the commit again. {NOTHING_CHANGED}"
160 ),
161 ClientFailure::RepositoryFull(path) => format!(
162 "There is no space left for {path}, so this commit was not made. Free some space and run the commit again. \
163 {NOTHING_CHANGED}"
164 ),
165 ClientFailure::RepositoryReadOnly(path) => format!(
166 "{path} cannot be written to, so this commit was not made. Make the repository writable (check its \
167 permissions and whether the disk is mounted read-only) and run the commit again. {NOTHING_CHANGED}"
168 ),
169 ClientFailure::BranchKeepsChanging { branch, times } => format!(
170 "The branch {branch} changed {times} times while this commit was being prepared, so it was not made. Run \
171 the commit again; if other people commit to the same branch at the same moment, it will succeed once they \
172 pause. {NOTHING_CHANGED}"
173 ),
174 ClientFailure::BranchMoved { branch } => format!(
175 "The branch {branch} moved while this commit was being prepared, and a commit that replaces the last one, or one \
176 that was written or signed by hand, cannot be moved onto a different last commit, so it was not made. Run the \
177 commit again to make it on the branch as it is now. {NOTHING_CHANGED}"
178 ),
179 ClientFailure::BranchMovedMessageSaved { branch, file, amend } => format!(
180 "The branch {branch} moved while this commit was being prepared, so it was not made. The message you wrote is saved \
181 in {file}. Run `git commit {}-F {file}` to make the commit on the branch as it is now. {NOTHING_CHANGED}",
182 if *amend { "--amend " } else { "" }
183 ),
184 ClientFailure::BranchSwitched { branch, now, file } => format!(
185 "The worktree switched from {branch} to {now} while this commit was being prepared, so it was not made. {} Switch back \
186 to {branch} and run the commit again, or run it here to commit on {now}. {NOTHING_CHANGED}",
187 match file {
188 Some(saved) => format!("The message you wrote is saved in {saved}; `git commit -F {saved}` uses it."),
189 None => String::new(),
190 }
191 ),
192 ClientFailure::ServiceSilent => format!(
193 "The commit service stopped answering while this commit waited, and it did not answer when asked again, so it is \
194 not known whether the commit was made. Run `{}` to see whether it was made, and `{}` to see what is still \
195 staged. If it was not made, look at why the service is stuck with `{LOG_COMMAND}`, restart it with \
196 `{RESTART_COMMAND}`, and run the commit again.",
197 CHECK_COMMANDS[0], CHECK_COMMANDS[1]
198 ),
199 ClientFailure::StillWithService { minutes, blocker } => format!(
200 "This commit waited {minutes} minutes for the commit service and was withdrawn, so it will not land later by itself.{} \
201 Your staged changes and your worktree are as they were. Run the commit again; if the service is stuck, look at why with \
202 `{LOG_COMMAND}` and restart it with `{RESTART_COMMAND}`. {NOTHING_CHANGED}",
203 blocker.as_ref().map_or_else(String::new, |text| { return format!(" It was waiting for: {text}."); })
204 ),
205 ClientFailure::WithdrawalUnconfirmed { blocker } => format!(
206 "This commit waited for the commit service until the longest wait the wrapper allows, and the service did not confirm \
207 that it withdrew it, so it is not known whether the commit was made.{} Run `{}` to see whether it was made, and `{}` to \
208 see what is still staged. If it was not made, run the commit again; if the service is stuck, look at why with \
209 `{LOG_COMMAND}` and restart it with `{RESTART_COMMAND}`.",
210 blocker.as_ref().map_or_else(String::new, |text| { return format!(" The service had said: {}", text.trim()); }),
211 CHECK_COMMANDS[0], CHECK_COMMANDS[1]
212 ),
213 ClientFailure::StepTooSlow => format!(
214 "A Git step of this commit ran longer than the commit service allows and was ended, so this commit was not made and will not \
215 land later by itself. The step is named above, and the commit built for you is kept on the side ref named there when one \
216 is. Run the commit again; if it happens again, look at why with `{LOG_COMMAND}`. {NOTHING_CHANGED}"
217 ),
218 ClientFailure::Refused => format!(
219 "The commit service refused this commit because it names a path or a value that cannot be committed, so it \
220 was not made. Check the paths you passed to the commit. {NOTHING_CHANGED}"
221 ),
222 ClientFailure::IndexInUse(holder) => format!(
223 "{holder} is using this repository's index, so this commit was not made. Wait for it to finish, or close \
224 it, and run the commit again. {NOTHING_CHANGED}"
225 ),
226 ClientFailure::ProgramMissing(path) => format!(
227 "The commit service program was not found at {path}, so this commit was not made. Reinstall the git wrapper \
228 with the tool that installed it, so that the program sits next to it, then run the commit again. \
229 {NOTHING_CHANGED}"
230 ),
231 ClientFailure::UnitInTheWay(path) => format!(
232 "The file `{path}` has the name of a settings file of the commit service but was not written by the git wrapper, \
233 so the commit service could not be set up and this commit was not made. Move or delete that file, then run the \
234 commit again. {NOTHING_CHANGED}"
235 ),
236 ClientFailure::WorkNotHandedOver(reason) => format!(
237 "Your commit is saved. Its backup push and upkeep could not be handed to the commit service: {reason}. \
238 Run `git push` when ready."
239 ),
240 ClientFailure::ContentPruned => format!(
241 "The files of this commit were removed from the repository while the commit was being made, so it was not made. \
242 Git removes content that no branch or index refers to yet when `git gc --prune=now` or `git prune` runs in the \
243 same repository, and Git's documentation says that `--prune=now` risks failures and corruption when another \
244 process writes to the repository at the same moment. Run the commit again once the cleanup has finished, and avoid `--prune=now` while commits are \
245 being made (the default `git gc` keeps new content for two weeks). If nothing like that was running, run \
246 `git fsck` to check the repository. {NOTHING_CHANGED}"
247 ),
248 ClientFailure::CouldNotStart(reason) => format!(
249 "The commit service could not be set up, so this commit was not made: {reason}. Fix that and run the commit \
250 again. {NOTHING_CHANGED}"
251 ),
252 ClientFailure::Unavailable => format!(
253 "The commit service could not finish this commit, so it was not made. Run the commit again; if it fails \
254 again, look at the service's log with `{LOG_COMMAND}`. {NOTHING_CHANGED}"
255 ),
256 };
257}
258
259pub fn platform_remedy(platform: Platform) -> String {
269 return match platform {
270 Platform::Windows => String::from(
271 "Committing through this wrapper is not supported on Windows yet. Use the Linux or macOS machine for commits \
272 to this repository.",
273 ),
274 Platform::Bsd => String::from(
275 "Committing through this wrapper is not supported on this system yet. Use a Linux or macOS machine for \
276 commits to this repository.",
277 ),
278 Platform::LinuxWithoutUserServices => format!(
279 "This account has no user service manager. Turn it on with `{LINGER_COMMAND}` (the system must run \
280 systemd), or commit from a system that does."
281 ),
282 Platform::Other => String::from(
283 "Committing through this wrapper is not supported on this system. Use a Linux or macOS machine for \
284 commits to this repository.",
285 ),
286 };
287}
288
289pub fn position_notice(number: usize, of: usize) -> String {
299 let ahead: usize = number.saturating_sub(1);
300 let _ = of;
301 return match ahead {
302 0 => String::from("This commit is next."),
303 1 => String::from("Waiting for 1 earlier commit."),
304 many => format!("Waiting for {many} earlier commits."),
305 };
306}
307
308pub const DENYLIST: [&str; 22] = [
311 "protocol", "journal", "request id", "daemon", "socket", "errno", "epipe", "econnrefused", "eagain", "flock",
312 "lock file", "peer", "frame", "handshake", "batch", "oid", "fast-import", "unix domain", "abstract", "pid",
313 "uid", "os error",
314];
315
316pub fn path_text(path: &Path) -> String {
318 return path.display().to_string();
319}
320
321#[cfg(test)]
323#[path = "service_messages_tests.rs"]
324mod tests;