1use anyhow::*;
2use std::io::Write;
3use std::result::Result::Ok;
4
5use crate::{config, repo};
6
7#[allow(clippy::too_many_arguments)]
8pub fn tag<W: Write>(
9 wok_config: &mut config::Config,
10 umbrella: &repo::Repo,
11 stdout: &mut W,
12 tag_name: Option<&str>,
13 sign: bool,
14 push: bool,
15 all: bool,
16 target_repos: &[std::path::PathBuf],
17) -> Result<()> {
18 let repos_to_tag: Vec<config::Repo> = if all {
20 wok_config
22 .repos
23 .iter()
24 .filter(|config_repo| {
25 !config_repo.is_skipped_for("tag")
26 || target_repos.contains(&config_repo.path)
27 })
28 .cloned()
29 .collect()
30 } else if !target_repos.is_empty() {
31 wok_config
33 .repos
34 .iter()
35 .filter(|config_repo| target_repos.contains(&config_repo.path))
36 .cloned()
37 .collect()
38 } else {
39 wok_config
41 .repos
42 .iter()
43 .filter(|config_repo| config_repo.head == umbrella.head)
44 .cloned()
45 .collect()
46 };
47
48 if repos_to_tag.is_empty() {
49 writeln!(stdout, "No repositories to tag")?;
50 return Ok(());
51 }
52
53 match tag_name {
54 Some(name) => {
55 writeln!(
57 stdout,
58 "Creating tag '{}' in {} repositories...",
59 name,
60 repos_to_tag.len()
61 )?;
62
63 for config_repo in &repos_to_tag {
64 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
65 match create_tag(subrepo, name, sign) {
66 Ok(result) => match result {
67 TagResult::Created => {
68 writeln!(
69 stdout,
70 "- '{}': created tag '{}'",
71 config_repo.path.display(),
72 name
73 )?;
74 },
75 TagResult::AlreadyExists => {
76 writeln!(
77 stdout,
78 "- '{}': tag '{}' already exists",
79 config_repo.path.display(),
80 name
81 )?;
82 },
83 },
84 Err(e) => {
85 writeln!(
86 stdout,
87 "- '{}': failed to create tag '{}' - {}",
88 config_repo.path.display(),
89 name,
90 e
91 )?;
92 },
93 }
94 }
95 }
96 },
97 None => {
98 writeln!(
100 stdout,
101 "Listing tags in {} repositories...",
102 repos_to_tag.len()
103 )?;
104
105 for config_repo in &repos_to_tag {
106 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
107 match list_tags(subrepo) {
108 Ok(tags) => {
109 if tags.is_empty() {
110 writeln!(
111 stdout,
112 "- '{}': no tags found",
113 config_repo.path.display()
114 )?;
115 } else {
116 writeln!(
117 stdout,
118 "- '{}': {}",
119 config_repo.path.display(),
120 tags.join(", ")
121 )?;
122 }
123 },
124 Err(e) => {
125 writeln!(
126 stdout,
127 "- '{}': failed to list tags - {}",
128 config_repo.path.display(),
129 e
130 )?;
131 },
132 }
133 }
134 }
135 },
136 }
137
138 if push {
140 writeln!(stdout, "Pushing tags to remotes...")?;
141 for config_repo in &repos_to_tag {
142 if let Some(subrepo) = umbrella.get_subrepo_by_path(&config_repo.path) {
143 match push_tags(subrepo) {
144 Ok(_) => {
145 writeln!(
146 stdout,
147 "- '{}': pushed tags",
148 config_repo.path.display()
149 )?;
150 },
151 Err(e) => {
152 writeln!(
153 stdout,
154 "- '{}': failed to push tags - {}",
155 config_repo.path.display(),
156 e
157 )?;
158 },
159 }
160 }
161 }
162 }
163
164 writeln!(
165 stdout,
166 "Successfully processed {} repositories",
167 repos_to_tag.len()
168 )?;
169 Ok(())
170}
171
172#[derive(Debug, Clone, PartialEq)]
173enum TagResult {
174 Created,
175 AlreadyExists,
176}
177
178fn create_tag(repo: &repo::Repo, tag_name: &str, sign: bool) -> Result<TagResult> {
179 if repo
181 .git_repo
182 .revparse_single(&format!("refs/tags/{}", tag_name))
183 .is_ok()
184 {
185 return Ok(TagResult::AlreadyExists);
186 }
187
188 let head = repo.git_repo.head()?;
190 let commit = head.peel_to_commit()?;
191 let commit_obj = commit.as_object();
192
193 if sign {
195 let signature = repo.git_repo.signature()?;
197 let _tag_ref = repo.git_repo.tag(
198 tag_name,
199 commit_obj,
200 &signature,
201 &format!("Tag {}", tag_name),
202 false,
203 )?;
204 } else {
205 let _tag_ref = repo.git_repo.tag_lightweight(tag_name, commit_obj, false)?;
207 }
208
209 Ok(TagResult::Created)
210}
211
212fn list_tags(repo: &repo::Repo) -> Result<Vec<String>> {
213 let mut tags = Vec::new();
214
215 let tag_names = repo.git_repo.tag_names(None)?;
217
218 for tag_name in tag_names.iter().flatten() {
219 tags.push(tag_name.to_string());
220 }
221
222 tags.sort();
224
225 Ok(tags)
226}
227
228fn push_tags(repo: &repo::Repo) -> Result<()> {
229 let head_ref = repo.git_repo.head()?;
231 let branch_name = head_ref.shorthand().with_context(|| {
232 format!(
233 "Cannot get branch name for repo at `{}`",
234 repo.work_dir.display()
235 )
236 })?;
237
238 let remote_name = repo.get_remote_name_for_branch(branch_name)?;
239
240 let mut remote = match repo.git_repo.find_remote(&remote_name) {
242 Ok(remote) => remote,
243 Err(_) => {
244 return Err(anyhow!("No remote '{}' configured", remote_name));
245 },
246 };
247
248 let refspecs = &["refs/tags/*:refs/tags/*"];
250 let mut push_options = git2::PushOptions::new();
251 push_options.remote_callbacks(repo.remote_callbacks()?);
252
253 remote.push(refspecs, Some(&mut push_options))?;
254
255 Ok(())
256}