1use anyhow::Context as _;
2use git::repository::{Remote, RemoteCommandOutput};
3use linkify::{LinkFinder, LinkKind};
4use ui::SharedString;
5use util::ResultExt as _;
6
7#[derive(Clone)]
8pub enum RemoteAction {
9 Fetch,
10 Pull(Remote),
11 Push(SharedString, Remote),
12}
13
14impl RemoteAction {
15 pub fn name(&self) -> &'static str {
16 match self {
17 RemoteAction::Fetch => "fetch",
18 RemoteAction::Pull(_) => "pull",
19 RemoteAction::Push(_, _) => "push",
20 }
21 }
22}
23
24pub enum SuccessStyle {
25 Toast,
26 ToastWithLog { output: RemoteCommandOutput },
27 PushPrLink { link: String },
28}
29
30pub struct SuccessMessage {
31 pub message: String,
32 pub style: SuccessStyle,
33}
34
35pub fn format_output(action: &RemoteAction, output: RemoteCommandOutput) -> SuccessMessage {
36 match action {
37 RemoteAction::Fetch => {
38 if output.stderr.is_empty() {
39 SuccessMessage {
40 message: "Already up to date".into(),
41 style: SuccessStyle::Toast,
42 }
43 } else {
44 SuccessMessage {
45 message: "Synchronized with remotes".into(),
46 style: SuccessStyle::ToastWithLog { output },
47 }
48 }
49 }
50 RemoteAction::Pull(remote_ref) => {
51 let get_changes = |output: &RemoteCommandOutput| -> anyhow::Result<u32> {
52 let last_line = output
53 .stdout
54 .lines()
55 .last()
56 .context("Failed to get last line of output")?
57 .trim();
58
59 let files_changed = last_line
60 .split_whitespace()
61 .next()
62 .context("Failed to get first word of last line")?
63 .parse()?;
64
65 Ok(files_changed)
66 };
67
68 if output.stderr.starts_with("Everything up to date") {
69 SuccessMessage {
70 message: output.stderr.trim().to_owned(),
71 style: SuccessStyle::Toast,
72 }
73 } else if output.stdout.starts_with("Updating") {
74 let files_changed = get_changes(&output).log_err();
75 let message = if let Some(files_changed) = files_changed {
76 format!(
77 "Received {} file change{} from {}",
78 files_changed,
79 if files_changed == 1 { "" } else { "s" },
80 remote_ref.name
81 )
82 } else {
83 format!("Fast forwarded from {}", remote_ref.name)
84 };
85 SuccessMessage {
86 message,
87 style: SuccessStyle::ToastWithLog { output },
88 }
89 } else if output.stdout.starts_with("Merge") {
90 let files_changed = get_changes(&output).log_err();
91 let message = if let Some(files_changed) = files_changed {
92 format!(
93 "Merged {} file change{} from {}",
94 files_changed,
95 if files_changed == 1 { "" } else { "s" },
96 remote_ref.name
97 )
98 } else {
99 format!("Merged from {}", remote_ref.name)
100 };
101 SuccessMessage {
102 message,
103 style: SuccessStyle::ToastWithLog { output },
104 }
105 } else if output.stdout.contains("Successfully rebased") {
106 SuccessMessage {
107 message: format!("Successfully rebased from {}", remote_ref.name),
108 style: SuccessStyle::ToastWithLog { output },
109 }
110 } else {
111 SuccessMessage {
112 message: format!("Successfully pulled from {}", remote_ref.name),
113 style: SuccessStyle::ToastWithLog { output },
114 }
115 }
116 }
117 RemoteAction::Push(branch_name, remote_ref) => {
118 if output.stderr.contains("* [new branch]") {
119 let style = if output.stderr.contains("Create a pull request") {
120 let finder = LinkFinder::new();
121 let first_link = finder
122 .links(&output.stderr)
123 .filter(|link| *link.kind() == LinkKind::Url)
124 .map(|link| link.start()..link.end())
125 .next();
126 if let Some(link) = first_link {
127 let link = output.stderr[link].to_string();
128 SuccessStyle::PushPrLink { link }
129 } else {
130 SuccessStyle::ToastWithLog { output }
131 }
132 } else {
133 SuccessStyle::ToastWithLog { output }
134 };
135 SuccessMessage {
136 message: format!("Published {} to {}", branch_name, remote_ref.name),
137 style,
138 }
139 } else if output.stderr.starts_with("Everything up to date") {
140 SuccessMessage {
141 message: output.stderr.trim().to_owned(),
142 style: SuccessStyle::Toast,
143 }
144 } else {
145 SuccessMessage {
146 message: format!("Pushed {} to {}", branch_name, remote_ref.name),
147 style: SuccessStyle::ToastWithLog { output },
148 }
149 }
150 }
151 }
152}