Skip to content

Commit

Permalink
feat(submit:github): implement Github forge
Browse files Browse the repository at this point in the history
- It's not very good. It's probably buggy since there's a huge amount of state to keep in sync between commits, branches, and pull requests.
- It's not very useful. When using Github's "Rebase and merge" button, it always rewrites the commit to update the committer signature. Then, for some reason, descendant commits can't be rebased automatically, and you have to manually rebase them.
  • Loading branch information
arxanas committed Feb 27, 2024
1 parent 74f77c1 commit e591fa3
Show file tree
Hide file tree
Showing 10 changed files with 1,903 additions and 26 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 32 additions & 1 deletion git-branchless-lib/src/git/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::ffi::OsStr;
use std::string::FromUtf8Error;

use thiserror::Error;
use tracing::instrument;
use tracing::{instrument, warn};

use crate::git::config::ConfigRead;
use crate::git::oid::make_non_zero_oid;
Expand Down Expand Up @@ -306,6 +306,37 @@ impl<'repo> Branch<'repo> {
Ok(target_oid)
}

/// If this branch tracks a remote ("upstream") branch, return the name of
/// that branch without the leading remote name. For example, if the
/// upstream branch is `origin/main`, this will return `main`. (Usually,
/// this is the same as the name of the local branch, but not always.)
pub fn get_upstream_branch_name_without_push_remote_name(
&self,
) -> eyre::Result<Option<String>> {
let push_remote_name = match self.get_push_remote_name()? {
Some(stack_remote_name) => stack_remote_name,
None => return Ok(None),
};
let upstream_branch = match self.get_upstream_branch()? {
Some(upstream_branch) => upstream_branch,
None => return Ok(None),
};
let upstream_branch_name = upstream_branch.get_name()?;
let upstream_branch_name_without_remote =
match upstream_branch_name.strip_prefix(&format!("{push_remote_name}/")) {
Some(upstream_branch_name_without_remote) => upstream_branch_name_without_remote,
None => {
warn!(
?push_remote_name,
?upstream_branch,
"Upstream branch name did not start with push remote name"
);
upstream_branch_name
}
};
Ok(Some(upstream_branch_name_without_remote.to_owned()))
}

/// Get the associated remote to push to for this branch. If there is no
/// associated remote, returns `None`. Note that this never reads the value
/// of `push.remoteDefault`.
Expand Down
25 changes: 25 additions & 0 deletions git-branchless-lib/src/git/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,31 @@ impl Repo {
Ok((snapshot, statuses))
}

/// Create a new branch or update an existing one. The provided name should
/// be a branch name and not a reference name, i.e. it should not start with
/// `refs/heads/`.
#[instrument]
pub fn create_branch(&self, branch_name: &str, commit: &Commit, force: bool) -> Result<Branch> {
if branch_name.starts_with("refs/heads/") {
warn!(
?branch_name,
"Branch name starts with refs/heads/; this is probably not what you intended."
);
}

let branch = self
.inner
.branch(branch_name, &commit.inner, force)
.map_err(|err| Error::CreateBranch {
source: err,
name: branch_name.to_owned(),
})?;
Ok(Branch {
repo: self,
inner: branch,
})
}

/// Create a new reference or update an existing one.
#[instrument]
pub fn create_reference(
Expand Down
2 changes: 1 addition & 1 deletion git-branchless-lib/src/git/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn make_test_command_slug(command: String) -> String {
/// A version of `NonZeroOid` that can be serialized and deserialized. This
/// exists in case we want to move this type (back) into a separate module which
/// has a `serde` dependency in the interest of improving build times.
#[derive(Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SerializedNonZeroOid(pub NonZeroOid);

impl Serialize for SerializedNonZeroOid {
Expand Down
4 changes: 4 additions & 0 deletions git-branchless-lib/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ then you can only run tests in the main `git-branchless` and \
/// Commit a file with given contents and message. The `time` argument is
/// used to set the commit timestamp, which is factored into the commit
/// hash. The filename is always appended to the message prefix.
#[track_caller]
#[instrument]
pub fn commit_file_with_contents_and_message(
&self,
Expand Down Expand Up @@ -563,6 +564,7 @@ then you can only run tests in the main `git-branchless` and \
/// Commit a file with given contents and a default message. The `time`
/// argument is used to set the commit timestamp, which is factored into the
/// commit hash.
#[track_caller]
#[instrument]
pub fn commit_file_with_contents(
&self,
Expand All @@ -575,6 +577,8 @@ then you can only run tests in the main `git-branchless` and \

/// Commit a file with default contents. The `time` argument is used to set
/// the commit timestamp, which is factored into the commit hash.
#[track_caller]
#[instrument]
pub fn commit_file(&self, name: &str, time: isize) -> eyre::Result<NonZeroOid> {
self.commit_file_with_contents(name, time, &format!("{name} contents\n"))
}
Expand Down
2 changes: 2 additions & 0 deletions git-branchless-submit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ git-branchless-invoke = { workspace = true }
git-branchless-opts = { workspace = true }
git-branchless-revset = { workspace = true }
git-branchless-test = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
lazy_static = { workspace = true }
lib = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }

Expand Down
11 changes: 9 additions & 2 deletions git-branchless-submit/src/branch_forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use lib::git::{
};
use lib::try_exit_code;
use lib::util::{ExitCode, EyreExitOr};
use tracing::warn;
use tracing::{instrument, warn};

use crate::{CommitStatus, CreateStatus, Forge, SubmitOptions, SubmitStatus};

Expand All @@ -28,6 +28,7 @@ pub struct BranchForge<'a> {
}

impl Forge for BranchForge<'_> {
#[instrument]
fn query_status(
&mut self,
commit_set: CommitSet,
Expand Down Expand Up @@ -185,6 +186,7 @@ impl Forge for BranchForge<'_> {
Ok(Ok(commit_statuses))
}

#[instrument]
fn create(
&mut self,
commits: HashMap<NonZeroOid, CommitStatus>,
Expand All @@ -204,6 +206,10 @@ impl Forge for BranchForge<'_> {
.sorted()
.collect_vec();

// FIXME: in principle, it's possible for a branch to have been assigned
// a remote without having been pushed and having created a
// corresponding remote branch. In those cases, we should use the
// branch's associated remote.
let push_remote: String = match self.repo.get_default_push_remote()? {
Some(push_remote) => push_remote,
None => {
Expand Down Expand Up @@ -258,6 +264,7 @@ These remotes are available: {}",
}
}

#[instrument]
fn update(
&mut self,
commits: HashMap<NonZeroOid, CommitStatus>,
Expand All @@ -275,7 +282,7 @@ These remotes are available: {}",
commit_status => {
warn!(
?commit_status,
"Commit was requested to be updated, but it did not have the requisite information."
"Commit was requested to be updated, but it did not have the requisite information (remote name, local branch name)."
);
None
}
Expand Down
Loading

0 comments on commit e591fa3

Please sign in to comment.