patchy/commands/
branch_fetch.rs1use std::process;
2
3use colored::Colorize as _;
4
5use crate::{
6 commands::help,
7 fail,
8 flags::{is_valid_flag, Flag},
9 git_commands::{fetch_branch, is_valid_branch_name, GIT},
10 success,
11 types::CommandArgs,
12};
13
14use super::run::parse_if_maybe_hash;
15
16pub static BRANCH_FETCH_CHECKOUT_FLAG: Flag<'static> = Flag {
17 short: "-c",
18 long: "--checkout",
19 description: "Check out the first fetched branch",
20};
21
22pub static BRANCH_FETCH_BRANCH_NAME_FLAG: Flag<'static> = Flag {
23 short: "-b=",
24 long: "--branch-name=",
25 description: "Choose local name for the branch belonging to the preceding pull request",
26};
27
28pub static BRANCH_FETCH_FLAGS: &[&Flag<'static>; 2] =
29 &[&BRANCH_FETCH_BRANCH_NAME_FLAG, &BRANCH_FETCH_CHECKOUT_FLAG];
30
31pub struct Item {
32 pub repo: String,
36 pub branch: String,
40 pub local_branch_name: Option<String>,
46 pub commit_hash: Option<String>,
52}
53
54impl Item {
55 pub fn new(
56 repo: String,
57 branch: String,
58 local_branch_name: Option<String>,
59 commit_hash: Option<String>,
60 ) -> Self {
61 Self {
62 repo,
63 branch,
64 local_branch_name,
65 commit_hash,
66 }
67 }
68
69 pub fn create(arg: &str) -> anyhow::Result<Self> {
70 let (remote, hash) = parse_if_maybe_hash(arg, "@");
71
72 let (repo, branch) = remote.rsplit_once('/').ok_or_else(|| {
73 anyhow::anyhow!(
74 "Invalid format: {}, skipping. \
75Valid format is: username/repo/branch. Example: helix-editor/helix/master",
76 remote
77 )
78 })?;
79
80 Ok(Self::new(repo.to_owned(), branch.to_owned(), None, hash))
81 }
82
83 #[must_use]
84 pub fn with_branch_name(mut self, branch_name: Option<String>) -> Self {
85 self.local_branch_name = branch_name;
86 self
87 }
88}
89
90pub async fn branch_fetch(args: &CommandArgs) -> anyhow::Result<()> {
91 if args.is_empty() {
92 let _ = help(Some("branch-fetch"));
93 process::exit(1);
94 }
95
96 let has_checkout_flag = BRANCH_FETCH_CHECKOUT_FLAG.is_in(args);
97
98 let mut args = args.iter().peekable();
99
100 let mut items = vec![];
101
102 let mut no_more_flags = false;
103
104 while let Some(arg) = args.next() {
105 if arg == "--" {
107 no_more_flags = true;
108 continue;
109 };
110
111 if arg.starts_with('-') && !no_more_flags {
112 if !is_valid_flag(arg, BRANCH_FETCH_FLAGS) {
113 fail!("Invalid flag: {arg}");
114 let _ = help(Some("branch-fetch"));
115 process::exit(1);
116 }
117
118 continue;
120 }
121
122 let Ok(item) = Item::create(arg).map_err(|err| fail!("{err}")) else {
123 continue;
124 };
125
126 let next_arg = args.peek();
127 let maybe_custom_branch_name: Option<String> = next_arg.and_then(|next_arg| {
128 BRANCH_FETCH_BRANCH_NAME_FLAG
129 .extract_from_arg(next_arg)
130 .filter(|branch_name| is_valid_branch_name(branch_name))
131 });
132
133 if maybe_custom_branch_name.is_some() {
134 args.next();
135 };
136
137 let item = item.with_branch_name(maybe_custom_branch_name);
138
139 items.push(item);
140 }
141
142 let client = reqwest::Client::new();
143
144 for (i, item) in items.into_iter().enumerate() {
145 let hash = item.commit_hash.clone();
146 let repo = item.repo.clone();
147 match fetch_branch(item, &client).await {
148 Ok((_, info)) => {
149 success!(
150 "Fetched branch {}/{} available at branch {}{}",
151 repo,
152 info.branch.upstream_branch_name,
153 info.branch.local_branch_name.bright_cyan(),
154 hash.map(|commit_hash| format!(", at commit {}", commit_hash.bright_yellow()))
155 .unwrap_or_default()
156 );
157
158 let _ = GIT(&["remote", "remove", &info.remote.local_remote_alias]);
160
161 if i == 0 && has_checkout_flag {
163 if let Err(cant_checkout) = GIT(&["checkout", &info.branch.local_branch_name]) {
164 fail!(
165 "Could not check out branch {}:\n{cant_checkout}",
166 info.branch.local_branch_name
167 );
168 } else {
169 success!(
170 "Automatically checked out the first branch: {}",
171 info.branch.local_branch_name
172 );
173 }
174 }
175 }
176 Err(err) => {
177 fail!("{err}");
178 continue;
179 }
180 };
181 }
182
183 Ok(())
184}