Skip to content
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
39 changes: 39 additions & 0 deletions src/forge/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,45 @@ use super::Forge;
use crate::core::stack::{PatchEntry, PatchStatus};
use crate::git::ops::Repo;

/// If `gh` reports the repo default branch, return a ref that exists locally (`origin/<name>` first,
/// then `<name>`). Called from [`crate::forge::stack_base_hint::try_from_forge_cli`] for [`crate::forge::ForgeKind::GitHub`].
pub(crate) fn try_cli_default_stack_base(repo: &Repo) -> Option<String> {
let output = Command::new("gh")
.current_dir(&repo.workdir)
.args([
"repo",
"view",
"--json",
"defaultBranchRef",
"--jq",
".defaultBranchRef.name",
])
.stderr(std::process::Stdio::null())
.output()
.ok()?;
if !output.status.success() {
return None;
}
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
if name.is_empty() || name == "null" {
return None;
}
let origin_ref = format!("origin/{}", name);
if repo
.git_pub(&["rev-parse", "--verify", "--quiet", &origin_ref])
.is_ok()
{
return Some(origin_ref);
}
if repo
.git_pub(&["rev-parse", "--verify", "--quiet", &name])
.is_ok()
{
return Some(name);
}
None
}

pub struct GitHub;

impl Forge for GitHub {
Expand Down
38 changes: 31 additions & 7 deletions src/forge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,34 @@ pub mod gitea;
pub mod github;
pub mod gitlab;
pub mod phabricator;
pub mod stack_base_hint;

use std::collections::HashMap;

/// Forge platform from `.pilegit.toml` `[forge] type = ...`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ForgeKind {
GitHub,
GitLab,
Gitea,
Phabricator,
Custom,
}

impl ForgeKind {
/// Parse from config `type` string. Unknown values match [`create_forge`]: treated as GitHub.
pub fn from_config_str(s: &str) -> Self {
match s {
"github" => Self::GitHub,
"gitlab" => Self::GitLab,
"gitea" => Self::Gitea,
"phabricator" => Self::Phabricator,
"custom" => Self::Custom,
_ => Self::GitHub,
}
}
}

use color_eyre::Result;

use crate::core::config::Config;
Expand Down Expand Up @@ -151,14 +176,13 @@ pub trait Forge {

/// Create the appropriate Forge based on config.
pub fn create_forge(config: &Config) -> Box<dyn Forge> {
match config.forge.forge_type.as_str() {
"github" => Box::new(github::GitHub),
"gitlab" => Box::new(gitlab::GitLab),
"gitea" => Box::new(gitea::Gitea),
"phabricator" => Box::new(phabricator::Phabricator),
"custom" => Box::new(custom::Custom::new(
match ForgeKind::from_config_str(config.forge.forge_type.as_str()) {
ForgeKind::GitHub => Box::new(github::GitHub),
ForgeKind::GitLab => Box::new(gitlab::GitLab),
ForgeKind::Gitea => Box::new(gitea::Gitea),
ForgeKind::Phabricator => Box::new(phabricator::Phabricator),
ForgeKind::Custom => Box::new(custom::Custom::new(
config.forge.submit_cmd.clone().unwrap_or_default(),
)),
_ => Box::new(github::GitHub),
}
}
18 changes: 18 additions & 0 deletions src/forge/stack_base_hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! When `[repo].base` is unset, optional detection of the default branch via each forge's CLI.
//!
//! Precedence is implemented in [`crate::git::ops::Repo::resolve_base`]: explicit config, then
//! [`try_from_forge_cli`], then git ref heuristics. Add new [`crate::forge::ForgeKind`] arms here
//! when a CLI can report the hosting default (e.g. GitLab `glab`, Gitea `tea`).

use crate::forge::ForgeKind;
use crate::git::ops::Repo;

/// Best-effort default stack base from the configured forge's CLI (`None` → fall back to git heuristics).
pub fn try_from_forge_cli(repo: &Repo, forge: ForgeKind) -> Option<String> {
match forge {
ForgeKind::GitHub => super::github::try_cli_default_stack_base(repo),
// Future: ForgeKind::GitLab => super::gitlab::try_cli_default_stack_base(repo),
// Future: ForgeKind::Gitea => super::gitea::try_cli_default_stack_base(repo),
ForgeKind::GitLab | ForgeKind::Gitea | ForgeKind::Phabricator | ForgeKind::Custom => None,
}
}
12 changes: 9 additions & 3 deletions src/git/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::process::Command;
use color_eyre::{eyre::eyre, Result};

use crate::core::stack::{PatchEntry, PatchStatus};
use crate::forge::ForgeKind;

/// Wrapper around a git repository.
pub struct Repo {
Expand Down Expand Up @@ -39,8 +40,9 @@ impl Repo {
}
}

/// Use explicit `repo.base` from config when valid, otherwise [`Self::detect_base`].
pub fn resolve_base(&self, configured: Option<&str>) -> Result<String> {
/// Resolve stack base: explicit `[repo].base`, then [`crate::forge::stack_base_hint`] for the
/// configured forge (CLI default branch when implemented), then [`Self::detect_base`].
pub fn resolve_base(&self, configured: Option<&str>, forge: ForgeKind) -> Result<String> {
if let Some(b) = configured {
let b = b.trim();
if !b.is_empty() {
Expand All @@ -54,6 +56,9 @@ impl Repo {
));
}
}
if let Some(b) = crate::forge::stack_base_hint::try_from_forge_cli(self, forge) {
return Ok(b);
}
self.detect_base()
}

Expand All @@ -76,7 +81,8 @@ impl Repo {
}
}
Err(eyre!(
"Could not detect base branch (tried origin/main, origin/master, main, master). \
"Could not detect base branch (tried forge CLI default for your `[forge].type`, \
then origin/main, origin/master, main, master). \
Set repo.base in .pilegit.toml (for example base = \"origin/develop\") or run `pgit init`."
))
}
Expand Down
6 changes: 5 additions & 1 deletion src/git/repo_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use color_eyre::Result;

use super::ops::Repo;
use crate::core::config::{Config, ForgeConfig, RepoConfig};
use crate::forge::ForgeKind;

/// Open the current git repo and resolve the stack base from config (if any) or heuristics.
pub fn open_resolved() -> Result<Repo> {
Expand All @@ -15,6 +16,9 @@ pub fn open_resolved() -> Result<Repo> {
},
repo: RepoConfig::default(),
});
let base = repo.resolve_base(config.repo.base.as_deref())?;
let base = repo.resolve_base(
config.repo.base.as_deref(),
ForgeKind::from_config_str(config.forge.forge_type.as_str()),
)?;
Ok(repo.with_resolved_base(base))
}
Loading