1use anyhow::*;
2use std::io::Write;
3use std::result::Result::Ok;
4
5use crate::{config, repo};
6
7pub fn push<W: Write>(
8 wok_config: &mut config::Config,
9 umbrella: &repo::Repo,
10 stdout: &mut W,
11 set_upstream: bool,
12 all: bool,
13 branch_name: Option<&str>,
14 target_repos: &[std::path::PathBuf],
15) -> Result<()> {
16 let target_branch = match branch_name {
18 Some(name) => name.to_string(),
19 None => umbrella.head.clone(),
20 };
21
22 let repos_to_push: Vec<config::Repo> = if all {
24 wok_config
26 .repos
27 .iter()
28 .filter(|config_repo| {
29 !config_repo.is_skipped_for("push")
30 || target_repos.contains(&config_repo.path)
31 })
32 .cloned()
33 .collect()
34 } else if !target_repos.is_empty() {
35 wok_config
37 .repos
38 .iter()
39 .filter(|config_repo| target_repos.contains(&config_repo.path))
40 .cloned()
41 .collect()
42 } else {
43 wok_config
45 .repos
46 .iter()
47 .filter(|config_repo| config_repo.head == umbrella.head)
48 .cloned()
49 .collect()
50 };
51
52 if repos_to_push.is_empty() {
53 writeln!(stdout, "No repositories to push")?;
54 return Ok(());
55 }
56
57 writeln!(
58 stdout,
59 "Pushing {} repositories to branch '{}'...",
60 repos_to_push.len(),
61 target_branch
62 )?;
63
64 for config_repo in &repos_to_push {
66 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
67 match push_repo(subrepo, &target_branch, set_upstream) {
68 Ok(result) => match result {
69 PushResult::Pushed => {
70 writeln!(
71 stdout,
72 "- '{}': pushed to '{}'",
73 config_repo.path.display(),
74 target_branch
75 )?;
76 },
77 PushResult::UpstreamSet => {
78 writeln!(
79 stdout,
80 "- '{}': pushed to '{}' and set upstream",
81 config_repo.path.display(),
82 target_branch
83 )?;
84 },
85 PushResult::UpToDate => {
86 writeln!(
87 stdout,
88 "- '{}': already up to date",
89 config_repo.path.display()
90 )?;
91 },
92 PushResult::NoRemote => {
93 writeln!(
94 stdout,
95 "- '{}': no remote configured, skipping",
96 config_repo.path.display()
97 )?;
98 },
99 },
100 Err(e) => {
101 writeln!(
102 stdout,
103 "- '{}': failed to push to '{}' - {}",
104 config_repo.path.display(),
105 target_branch,
106 e
107 )?;
108 },
109 }
110 }
111 }
112
113 writeln!(
114 stdout,
115 "Successfully processed {} repositories",
116 repos_to_push.len()
117 )?;
118 Ok(())
119}
120
121#[derive(Debug, Clone, PartialEq)]
122enum PushResult {
123 Pushed,
124 UpstreamSet,
125 UpToDate,
126 NoRemote,
127}
128
129fn push_repo(
130 repo: &repo::Repo,
131 branch_name: &str,
132 set_upstream: bool,
133) -> Result<PushResult> {
134 let remote_name = repo.get_remote_name_for_branch(branch_name)?;
136
137 let mut remote = match repo.git_repo.find_remote(&remote_name) {
139 Ok(remote) => remote,
140 Err(_) => {
141 return Ok(PushResult::NoRemote);
142 },
143 };
144
145 let branch_ref = format!("refs/heads/{}", branch_name);
147
148 if repo.git_repo.refname_to_id(&branch_ref).is_err() {
150 return Err(anyhow!("Branch '{}' does not exist locally", branch_name));
151 }
152
153 let refspec = format!("{}:refs/heads/{}", branch_ref, branch_name);
155
156 let mut push_options = git2::PushOptions::new();
158 push_options.remote_callbacks(repo.remote_callbacks()?);
159
160 match remote.push(&[&refspec], Some(&mut push_options)) {
161 Ok(_) => {
162 if set_upstream {
163 set_upstream_branch(repo, branch_name, &remote_name)?;
165 Ok(PushResult::UpstreamSet)
166 } else {
167 Ok(PushResult::Pushed)
168 }
169 },
170 Err(e) => {
171 if e.message().contains("up to date")
173 || e.message().contains("non-fast-forward")
174 {
175 Ok(PushResult::UpToDate)
176 } else {
177 Err(e.into())
178 }
179 },
180 }
181}
182
183fn set_upstream_branch(
184 repo: &repo::Repo,
185 branch_name: &str,
186 remote_name: &str,
187) -> Result<()> {
188 let mut config = repo.git_repo.config()?;
190 config.set_str(&format!("branch.{}.remote", branch_name), remote_name)?;
191 config.set_str(
192 &format!("branch.{}.merge", branch_name),
193 &format!("refs/heads/{}", branch_name),
194 )?;
195
196 Ok(())
197}