Skip to content
This repository was archived by the owner on Dec 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ pull-requests = [
"11164",
]

# List of branches from other repositories to merge
# Format: "owner/repo/branch"
branches = [
# Include master branch from another repo
"helix-editor/helix/master",
# Include a specific commit from a branch
"helix-editor/helix/master @ 6049f20",
]

# An optional list of patches to apply, more on them later
patches = ["remove-tab"]
```
Expand Down Expand Up @@ -133,6 +142,11 @@ pull-requests = [
"145 @ fccc58957eece10d0818dfa000bf5123e26ee32f",
"88 @ a556aeef3736a3b6b79bb9507d26224f5c0c3449"
]

branches = [
"helix-editor/helix/master @ 6049f20b7e3ca83f832790a0ad84d85a56205d47",
"other-user/fork/feature-branch @ 3a56b1c"
]
```

Where the hashes represent each `sha1` hash of every commit.
Expand Down
24 changes: 24 additions & 0 deletions example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ local-branch = "patchy"

pull-requests = []

# List of branches from other repositories to merge into the repository
# Format: "owner/repo/branch"
#
# Examples
#
# branches = [
# "helix-editor/helix/master",
# "other-user/fork/feature-branch"
# ]
#
# The above always fetch the latest commit for each branch.
#
# To use a specific commit, use the following syntax:
# "<owner/repo/branch> @ <hash-of-commit>"
#
# so for example:
#
# branches = [
# "helix-editor/helix/master",
# "other-user/fork/feature-branch @ a556aeef3736a3b6b79bb9507d26224f5c0c3449"
# ]

branches = []

# Optional: A list of patches to apply
#
# A patch allows you to do specify custom commits and not have to rely on there being a pull request for that change
Expand Down
153 changes: 117 additions & 36 deletions src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,54 +130,135 @@ pub async fn run(yes: bool) -> anyhow::Result<()> {
&info.remote.local_remote_alias,
)?;

if config.pull_requests.is_empty() {
let has_pull_requests = !config.pull_requests.is_empty();
let has_branches = !config.branches.is_empty();

if !has_pull_requests && !has_branches {
log::info!(
"You haven't specified any pull requests to fetch in your config, {}",
"You haven't specified any pull requests or branches to fetch in your config, {}",
display_link(
"see the instructions on how to configure patchy.",
"https://github.com/nik-rev/patchy?tab=readme-ov-file#config"
)
);
} else {
// TODO: make this concurrent, see https://users.rust-lang.org/t/processing-subprocesses-concurrently/79638/3
// Git cannot handle multiple threads executing commands in the same repository,
// so we can't use threads, but we can run processes in the background
for pull_request in &config.pull_requests {
let pull_request = ignore_octothorpe(pull_request);
let (pull_request, commit_hash) = parse_if_maybe_hash(&pull_request, " @ ");
// TODO: refactor this to not use such deep nesting
match git::fetch_pull_request(&config.repo, &pull_request, None, commit_hash.as_ref())
// Process pull requests
if has_pull_requests {
// TODO: make this concurrent, see https://users.rust-lang.org/t/processing-subprocesses-concurrently/79638/3
// Git cannot handle multiple threads executing commands in the same repository,
// so we can't use threads, but we can run processes in the background
for pull_request in &config.pull_requests {
let pull_request = ignore_octothorpe(pull_request);
let (pull_request, commit_hash) = parse_if_maybe_hash(&pull_request, " @ ");
// TODO: refactor this to not use such deep nesting
match git::fetch_pull_request(
&config.repo,
&pull_request,
None,
commit_hash.as_ref(),
)
.await
{
Ok((response, info)) => {
match git::merge_pull_request(
&info,
&pull_request,
&response.title,
&response.html_url,
) {
Ok(()) => {
success!(
"Merged pull request {}",
display_link(
&format!(
"{}{}{}{}",
"#".bright_blue(),
pull_request.bright_blue(),
" ".bright_blue(),
&response.title.bright_blue().italic()
{
Ok((response, info)) => {
match git::merge_pull_request(
&info,
&pull_request,
&response.title,
&response.html_url,
) {
Ok(()) => {
success!(
"Merged pull request {}",
display_link(
&format!(
"{}{}{}{}",
"#".bright_blue(),
pull_request.bright_blue(),
" ".bright_blue(),
&response.title.bright_blue().italic()
),
&response.html_url
),
&response.html_url
),
);
}
Err(err) => {
fail!("{err}");
);
}
Err(err) => {
fail!("{err}");
}
}
}
Err(err) => {
fail!("Could not fetch branch from remote\n{err}");
}
}
Err(err) => {
fail!("Could not fetch branch from remote\n{err}");
}
}

// Process branches
if has_branches {
for branch_entry in &config.branches {
let (branch_path, commit_hash) = parse_if_maybe_hash(branch_entry, " @ ");

// Parse the branch path into owner/repo/branch format
let parts: Vec<&str> = branch_path.split('/').collect();
if parts.len() < 3 {
fail!(
"Invalid branch format: {}. Expected format: owner/repo/branch",
branch_path
);
continue;
}

let owner = parts[0];
let repo = parts[1];
let branch_name = parts[2..].join("/");

let remote = crate::cli::Remote {
owner: owner.to_string(),
repo: repo.to_string(),
branch: branch_name.clone(),
};

match git::fetch_branch(&remote, commit_hash.as_ref()).await {
Ok((_, info)) => {
match git::merge_into_main(
&info.branch.local_branch_name,
&info.branch.upstream_branch_name,
) {
Ok(_) => {
success!(
"Merged branch {}/{}/{} {}",
owner.bright_blue(),
repo.bright_blue(),
branch_name.bright_blue(),
commit_hash
.map(|hash| format!(
"at commit {}",
hash.as_ref().bright_yellow()
))
.unwrap_or_default()
);

// Clean up the remote branch
if let Err(err) = git::delete_remote_and_branch(
&info.remote.local_remote_alias,
&info.branch.local_branch_name,
) {
fail!("Failed to clean up branch: {err}");
}
}
Err(err) => {
fail!("{err}");
}
}
}
Err(err) => {
fail!(
"Could not fetch branch {}/{}/{}: {err}",
owner,
repo,
branch_name
);
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "kebab-case")]
pub struct Configuration {
pub local_branch: String,
#[serde(default)]
pub patches: IndexSet<String>,
#[serde(default)]
pub pull_requests: Vec<String>,
#[serde(default)]
pub branches: Vec<String>,
pub remote_branch: String,
pub repo: String,
}
Expand Down
Loading