1use crate::git;
2use crate::metadata::WipMetadata;
3use crate::ref_name;
4
5pub struct SaveResult {
6 pub name: String,
7 pub wip_ref: String,
8 pub sha: String,
9 pub files: usize,
10 pub untracked: usize,
11 pub task: Option<String>,
12 pub clean: bool,
13 pub stashed: bool,
14}
15
16fn next_increment(base: &str, user: &str, remote: &str) -> Result<String, String> {
20 let pattern = ref_name::wip_ref(&format!("{base}-*"), user);
21 let ls = git::git_stdout(&["ls-remote", remote, &pattern])
22 .map_err(|e| format!("could not reach remote '{}': {}", remote, e))?;
23
24 let prefix = format!("refs/wip/{user}/{base}-");
25 let max_n = ls
26 .lines()
27 .filter_map(|line| {
28 let refname = line.split_whitespace().nth(1)?;
29 let suffix = refname.strip_prefix(&prefix)?;
30 suffix.parse::<u32>().ok()
31 })
32 .max()
33 .unwrap_or(0);
34
35 Ok(format!("{base}-{:02}", max_n + 1))
36}
37
38pub fn run(
39 name: Option<String>,
40 message: String,
41 task: Option<String>,
42 force: bool,
43 include_ignored: bool,
44 stash: bool,
45 remote: String,
46) -> Result<SaveResult, String> {
47 let user = ref_name::user()?;
48 let base = ref_name::resolve_name(name)?;
49 let (name, wip_ref) = if force {
50 let wip_ref = ref_name::wip_ref(&base, &user);
52 (base, wip_ref)
53 } else {
54 let name = next_increment(&base, &user, &remote)?;
56 let wip_ref = ref_name::wip_ref(&name, &user);
57 (name, wip_ref)
58 };
59
60 let original_head = git::git_stdout(&["rev-parse", "HEAD"])?;
62 let branch = git::git_stdout(&["rev-parse", "--abbrev-ref", "HEAD"])?;
63
64 if include_ignored {
66 git::git(&["add", "-A", "--force"])?;
67 } else {
68 git::git(&["add", "-A"])?;
69 }
70
71 let status = git::git_stdout(&["status", "--porcelain"])?;
73 if status.is_empty() {
74 return Ok(SaveResult {
75 name,
76 wip_ref,
77 sha: String::new(),
78 files: 0,
79 untracked: 0,
80 task,
81 clean: true,
82 stashed: false,
83 });
84 }
85
86 let files = status.lines().count();
87 let untracked = status
88 .lines()
89 .filter(|l| l.starts_with("A ") || l.starts_with("??"))
90 .count();
91
92 let meta = WipMetadata {
94 message: message.clone(),
95 branch: branch.clone(),
96 task: task.clone(),
97 files,
98 untracked,
99 };
100
101 let commit_msg = meta.to_commit_message();
103 git::git(&["commit", "--allow-empty", "-m", &commit_msg])?;
104 let wip_sha = git::git_stdout(&["rev-parse", "HEAD"])?;
105
106 let refspec = format!("{wip_sha}:{wip_ref}");
108 let push_result = git::git(&["push", &remote, &refspec, "--force"]);
109
110 if stash {
112 git::git(&["reset", &original_head, "--hard"])?;
113 git::git(&["clean", "-fd"])?;
114 } else {
115 git::git(&["reset", &original_head, "--mixed"])?;
116 }
117
118 push_result?;
120
121 Ok(SaveResult {
122 name,
123 wip_ref,
124 sha: wip_sha,
125 files,
126 untracked,
127 task,
128 clean: false,
129 stashed: stash,
130 })
131}