Skip to content

Commit 732baaa

Browse files
committed
feat: implement branch-fetch
1 parent ac5930c commit 732baaa

File tree

6 files changed

+101
-42
lines changed

6 files changed

+101
-42
lines changed

Cargo.toml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ homepage = "https://github.com/nik-rev/patchy"
1616
pedantic = { priority = -1, level = "warn" }
1717
nursery = { priority = -1, level = "warn" }
1818
cargo = { priority = -1, level = "warn" }
19-
perf = { priority = -1, level = "deny" }
2019

2120
# sometimes it can be more readable not to inline format!() args
2221
uninlined_format_args = "allow"
@@ -34,16 +33,10 @@ too_many_lines = "allow"
3433
maybe_infinite_iter = "allow"
3534

3635
# === Restrictions ===
37-
38-
absolute_paths = "warn"
39-
deref_by_slicing = "warn"
40-
wildcard_enum_match_arm = "warn"
41-
verbose_file_reads = "warn"
42-
4336
# == safety ==
37+
4438
# Each 'unsafe' block should always contain exactly 1 unsafe operation
4539
# with clear documentation why the invariants are upheld.
46-
4740
undocumented_unsafe_blocks = "deny"
4841
unnecessary_safety_comment = "deny"
4942
unnecessary_safety_doc = "deny"
@@ -69,6 +62,7 @@ fn_to_numeric_cast_any = "deny"
6962
# integer division discards the remainder and is likely a mistake
7063
integer_division = "deny"
7164
lossy_float_literal = "deny"
65+
wildcard_enum_match_arm = "deny"
7266

7367
# == performance ==
7468

@@ -136,6 +130,9 @@ rest_pat_in_fully_bound_structs = "warn"
136130
same_name_method = "warn"
137131
# more explicit and avoids polluting the scope
138132
unused_trait_names = "warn"
133+
absolute_paths = "warn"
134+
deref_by_slicing = "warn"
135+
verbose_file_reads = "warn"
139136

140137
# == enable these before pushing a new release ==
141138
# dbg_macro = "deny"

src/commands/branch_fetch.rs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use std::process;
22

3+
use colored::Colorize as _;
4+
35
use crate::{
46
commands::help,
57
fail,
68
flags::{is_valid_flag, Flag},
7-
git_commands::is_valid_branch_name,
9+
git_commands::{fetch_branch, is_valid_branch_name, GIT},
10+
success,
811
types::CommandArgs,
912
};
1013

@@ -29,23 +32,23 @@ pub struct Item {
2932
/// # Examples
3033
///
3134
/// helix-editor/helix
32-
repo: String,
35+
pub repo: String,
3336
/// # Examples
3437
///
3538
/// master
36-
branch: String,
39+
pub branch: String,
3740
/// If specified, use a custom branch name instead of a generated one
3841
///
3942
/// # Examples
4043
///
4144
/// my-custom-branch123
42-
local_branch_name: Option<String>,
45+
pub local_branch_name: Option<String>,
4346
/// If specified, do a **hard reset** to this commit when fetching the branch
4447
///
4548
/// # Examples
4649
///
4750
/// 6049f2035
48-
commit_hash: Option<String>,
51+
pub commit_hash: Option<String>,
4952
}
5053

5154
impl Item {
@@ -76,14 +79,25 @@ Valid format is: username/repo/branch. Example: helix-editor/helix/master",
7679

7780
Ok(Self::new(repo.to_owned(), branch.to_owned(), None, hash))
7881
}
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+
}
7988
}
8089

81-
pub fn branch_fetch(args: &CommandArgs) {
90+
pub 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+
8296
let has_checkout_flag = BRANCH_FETCH_CHECKOUT_FLAG.is_in(args);
8397

8498
let mut args = args.iter().peekable();
8599

86-
let mut branches_with_maybe_custom_names = vec![];
100+
let mut items = vec![];
87101

88102
let mut no_more_flags = false;
89103

@@ -105,11 +119,7 @@ pub fn branch_fetch(args: &CommandArgs) {
105119
continue;
106120
}
107121

108-
let (remote, hash) = parse_if_maybe_hash(arg, "@");
109-
110-
let Some((repo, branch)) = remote.rsplit_once('/') else {
111-
fail!("Invalid format: {}, skipping. Valid format is: username/repo/branch. Example: helix-editor/helix/master", remote);
112-
122+
let Ok(item) = Item::create(arg).map_err(|err| fail!("{err}")) else {
113123
continue;
114124
};
115125

@@ -124,13 +134,51 @@ pub fn branch_fetch(args: &CommandArgs) {
124134
args.next();
125135
};
126136

127-
branches_with_maybe_custom_names.push(Item::new(
128-
repo.to_owned(),
129-
branch.to_owned(),
130-
maybe_custom_branch_name,
131-
hash,
132-
));
137+
let item = item.with_branch_name(maybe_custom_branch_name);
138+
139+
items.push(item);
133140
}
134141

135142
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+
// Attempt to cleanup after ourselves
159+
let _ = GIT(&["remote", "remove", &info.remote.local_remote_alias]);
160+
161+
// If user uses --checkout flag, we're going to checkout the first fetched branch
162+
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(())
136184
}

src/commands/help.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ pub fn help(command: Option<&str>) -> anyhow::Result<()> {
5252
"pr-fetch",
5353
"Fetch pull request for a GitHub repository as a local branch",
5454
);
55+
let branch_fetch = format_subcommand(
56+
"branch-fetch",
57+
"Fetch branches for a GitHub repository as a local branch",
58+
);
5559
let gen_patch = format_subcommand("gen-patch", "Generate a .patch file from commit hashes");
5660
let run = format_subcommand("run", &format!("Start {APP_NAME}"));
5761
let header = format!(
@@ -163,13 +167,13 @@ pub fn help(command: Option<&str>) -> anyhow::Result<()> {
163167

164168
let example_1 = format!(
165169
"{}
166-
{}",
170+
{}",
167171
"helix-editor/helix/master".bright_green(),
168172
format_description("Fetch a single branch")
169173
);
170174
let example_2 = format!(
171175
"{}
172-
{}",
176+
{}",
173177
"'helix-editor/helix/master@6049f20'".bright_green(),
174178
format_description("Fetch a single branch at a certain commit")
175179
);
@@ -292,6 +296,8 @@ pub fn help(command: Option<&str>) -> anyhow::Result<()> {
292296
293297
{pr_fetch}
294298
299+
{branch_fetch}
300+
295301
Flags:
296302
297303
{HELP_FLAG}

src/commands/pr_fetch.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ pub static PR_FETCH_FLAGS: &[&Flag<'static>; 5] = &[
5454
];
5555

5656
pub async fn pr_fetch(args: &CommandArgs) -> anyhow::Result<()> {
57+
if args.is_empty() {
58+
let _ = help(Some("pr-fetch"));
59+
process::exit(1);
60+
}
61+
5762
let has_checkout_flag = PR_FETCH_CHECKOUT_FLAG.is_in(args);
5863

5964
let mut args = args.iter().peekable();

src/git_commands.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{fail, types::Repo, utils::display_link};
1+
use crate::{commands::branch_fetch, fail, types::Repo, utils::display_link};
22
use colored::Colorize as _;
33
use std::{
44
env, io,
@@ -289,28 +289,24 @@ fn first_available_branch(branch: &str) -> AvailableBranch {
289289
}
290290

291291
pub async fn fetch_branch(
292-
repo: &str,
292+
item: branch_fetch::Item,
293293
client: &Client,
294-
branch_name: &str,
295-
custom_branch_name: Option<&str>,
296-
commit_hash: Option<&str>,
297294
) -> anyhow::Result<(Repo, BranchAndRemote)> {
298-
let url = format!("https://api.github.com/repos/{repo}");
295+
let url = format!("https://api.github.com/repos/{}", item.repo);
299296

300297
let response = make_request(client, &url)
301298
.await
302-
.map_err(|err| anyhow!("Could not fetch branch: {repo}\n{err}\n"))?;
299+
.map_err(|err| anyhow!("Could not fetch branch: {}\n{err}\n", item.repo))?;
303300

304301
let response: Repo = serde_json::from_str(&response).map_err(|err| {
305302
anyhow!("Could not parse response.\n{response}. Could not parse because: \n{err}")
306303
})?;
307304

308305
let info = BranchAndRemote {
309306
branch: Branch {
310-
upstream_branch_name: branch_name.to_owned(),
311-
local_branch_name: custom_branch_name.map_or_else(
307+
local_branch_name: item.local_branch_name.map_or_else(
312308
|| {
313-
let branch_name = &format!("{repo}/{branch_name}");
309+
let branch_name = &format!("{}/{}", item.repo, item.branch);
314310

315311
match first_available_branch(branch_name) {
316312
AvailableBranch::First => branch_name.to_string(),
@@ -319,15 +315,20 @@ pub async fn fetch_branch(
319315
},
320316
Into::into,
321317
),
318+
upstream_branch_name: item.branch,
322319
},
323320
remote: Remote {
324321
repository_url: response.clone_url.clone(),
325-
local_remote_alias: with_uuid(repo),
322+
local_remote_alias: with_uuid(&item.repo),
326323
},
327324
};
328325

329-
add_remote_branch(&info, commit_hash)
330-
.map_err(|err| anyhow!("Could not add remote branch {repo}, skipping.\n{err}"))?;
326+
add_remote_branch(&info, item.commit_hash.as_deref()).map_err(|err| {
327+
anyhow!(
328+
"Could not add remote branch {}, skipping.\n{err}",
329+
item.repo
330+
)
331+
})?;
331332

332333
Ok((response, info))
333334
}

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use colored::Colorize;
1+
use colored::Colorize as _;
2+
use patchy::commands::branch_fetch::branch_fetch;
23
use patchy::commands::help::{HELP_FLAG, VERSION_FLAG};
34
use patchy::commands::{gen_patch, help, init, pr_fetch, run};
45
use patchy::fail;
@@ -16,6 +17,7 @@ async fn process_subcommand(subcommand: &str, args: CommandArgs) -> Result<()> {
1617
"gen-patch" => gen_patch(&args)?,
1718
// lower level commands
1819
"pr-fetch" => pr_fetch(&args).await?,
20+
"branch-fetch" => branch_fetch(&args).await?,
1921
unrecognized => {
2022
if !unrecognized.is_empty() {
2123
fail!(

0 commit comments

Comments
 (0)