[snowpatch] [PATCH] main, git: Rework creation of patch branch

Andrew Donnellan ajd at linux.ibm.com
Wed Jul 10 11:05:29 AEST 2019


When we update our remote tracking branch after the remote branch has been
rebased, using git pull will result in a merge commit being created, which
is not the behaviour we want.

Instead, fetch the remote, and create our new patch application branch
directly, without checking out a remote tracking branch and pulling. This
is a lot cleaner. Add a few helper functions to facilitate this.

This requires a new project config option, base_remote_name, to specify the
name of the remote from which we are fetching base branches. If not
specified, it will default to the same remote to which we are pushing. This
is a breaking change and will require existing users to update their
configuration.

Signed-off-by: Andrew Donnellan <ajd at linux.ibm.com>
---
 docs/configuration.md   |  7 ++++--
 examples/openpower.toml |  2 ++
 src/git.rs              | 50 ++++++++++++++++++++++++++---------------
 src/main.rs             | 39 ++++++++++++++++++--------------
 src/settings.rs         |  1 +
 5 files changed, 62 insertions(+), 37 deletions(-)

diff --git a/docs/configuration.md b/docs/configuration.md
index 4402693cab99..d4c383fefe9e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -111,6 +111,7 @@ Example:
     # test_all_branches defaults to true
     remote_name = "github"
     remote_uri = "git at github.com:ruscur/linux.git"
+    base_remote_name = "origin"
     push_results = false
 
         [[projects.linuxppc-dev.jobs]]
@@ -132,8 +133,7 @@ Example:
 
 - `repository`: path to local clone of git repository
 
-- `branches`: a list of base branches (as defined by the local git repository)
-  that patches should be tested against
+- `branches`: a list of base branches that patches should be tested against
 
 - `test_all_branches`: if true, each patch will be tested against all base
   branches. If false, a patch will only be tested against the first base branch
@@ -144,6 +144,9 @@ Example:
 
 - `remote_uri`: the URI of the remote
 
+- `base_remote_name`: the name of the remote where base branches are retrieved
+  from (Optional, defaults to value of remote_name)
+
 - `push_results`: whether test results should be pushed to Patchwork for this project
 
 Individual jobs contain the following:
diff --git a/examples/openpower.toml b/examples/openpower.toml
index 59331455fc68..d72d47ad7c69 100644
--- a/examples/openpower.toml
+++ b/examples/openpower.toml
@@ -41,6 +41,7 @@ token = "33333333333333333333333333333333"
     test_all_branches = false
     remote_name = "github"
     remote_uri = "git at github.com:ruscur/skiboot.git"
+    base_remote_name = "origin"
     push_results = false
 
         [[projects.skiboot.jobs]]
@@ -61,6 +62,7 @@ token = "33333333333333333333333333333333"
     # test_all_branches defaults to true
     remote_name = "github"
     remote_uri = "git at github.com:ruscur/linux.git"
+    base_remote_name = "origin"
     push_results = false
 
         [[projects.linuxppc-dev.jobs]]
diff --git a/src/git.rs b/src/git.rs
index ad4bb781d23b..3ac75f2991e5 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -15,7 +15,9 @@
 //
 
 use git2::build::CheckoutBuilder;
-use git2::{Branch, Commit, Cred, Error, PushOptions, Remote, Repository};
+use git2::{
+    Branch, BranchType, Commit, Cred, Error, FetchOptions, PushOptions, Remote, Repository,
+};
 
 use std::path::Path;
 use std::process::{Command, Output};
@@ -47,25 +49,37 @@ pub fn push_to_remote(
     remote.push(refspecs, Some(&mut opts))
 }
 
-// TODO: rewrite this to use git2-rs, I think it's impossible currently
-pub fn pull(repo: &Repository) -> Result<Output, &'static str> {
-    let workdir = repo.workdir().unwrap(); // TODO: support bare repositories
+pub fn fetch_branch(
+    remote: &mut Remote,
+    branch: &str,
+    opts: Option<&mut FetchOptions>,
+) -> Result<(), Error> {
+    remote.fetch(&[branch], opts, None)
+}
 
-    let output = Command::new("git")
-        .arg("pull") // pull the cool kid's way
-        .current_dir(&workdir) // in the repo's working directory
-        .output() // run synchronously
-        .unwrap(); // TODO
+pub fn create_branch<'a>(
+    repo: &'a Repository,
+    branch_name: &str,
+    base_remote: &Remote,
+    target_ref: &str,
+) -> Result<Branch<'a>, Error> {
+    let base_branch = repo.find_branch(
+        &format!("{}/{}", base_remote.name().unwrap(), target_ref),
+        BranchType::Remote,
+    )?;
+    let commit = base_branch.get().peel_to_commit()?;
+
+    let _ = delete_branch(repo, branch_name);
+    repo.branch(branch_name, &commit, true)
+}
 
-    if output.status.success() {
-        debug!(
-            "Pull: {}",
-            String::from_utf8(output.clone().stdout).unwrap()
-        );
-        Ok(output)
-    } else {
-        Err("Error: couldn't pull changes")
+pub fn delete_branch(repo: &Repository, branch_name: &str) -> Result<(), Error> {
+    let mut branch = repo.find_branch(branch_name, BranchType::Local)?;
+    if branch.is_head() {
+        debug!("Detaching HEAD");
+        repo.set_head_detached(repo.head()?.target().unwrap())?;
     }
+    branch.delete()
 }
 
 pub fn checkout_branch(repo: &Repository, branch: &str) {
@@ -87,7 +101,7 @@ pub fn checkout_branch(repo: &Repository, branch: &str) {
         .output()
         .unwrap();
 
-    repo.set_head(&format!("{}/{}", GIT_REF_BASE, &branch))
+    repo.set_head(&branch)
         .unwrap_or_else(|err| panic!("Couldn't set HEAD: {}", err));
     repo.checkout_head(Some(&mut CheckoutBuilder::new().force()))
         .unwrap_or_else(|err| panic!("Couldn't checkout HEAD: {}", err));
diff --git a/src/main.rs b/src/main.rs
index c3c128faeb6f..ea09b07969ba 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,7 +34,7 @@ extern crate base64;
 extern crate serde_json;
 extern crate toml;
 
-use git2::{BranchType, PushOptions, RemoteCallbacks};
+use git2::{FetchOptions, PushOptions, RemoteCallbacks};
 
 use reqwest::{Client, Proxy};
 
@@ -200,31 +200,38 @@ fn test_patch(
     }
     let tag = utils::sanitise_path(path.file_name().unwrap().to_str().unwrap());
     let mut remote = repo.find_remote(&project.remote_name).unwrap();
+    let mut base_remote = repo
+        .find_remote(match &project.base_remote_name {
+            Some(name) => &name,
+            _ => &project.remote_name,
+        })
+        .unwrap();
 
     let mut push_callbacks = RemoteCallbacks::new();
     push_callbacks.credentials(|_, _, _| git::cred_from_settings(&settings.git));
-
     let mut push_opts = PushOptions::new();
     push_opts.remote_callbacks(push_callbacks);
+    let mut fetch_callbacks = RemoteCallbacks::new();
+    fetch_callbacks.credentials(|_, _, _| git::cred_from_settings(&settings.git));
+    let mut fetch_opts = FetchOptions::new();
+    fetch_opts.remote_callbacks(fetch_callbacks);
 
     let mut successfully_applied = false;
     for branch_name in project.branches.clone() {
         let tag = format!("{}_{}", tag, branch_name);
         info!("Configuring local branch for {}.", tag);
-        debug!("Switching to base branch {}...", branch_name);
-        git::checkout_branch(&repo, &branch_name);
 
-        // Make sure we're up to date
-        git::pull(&repo).unwrap();
+        debug!("Updating base branch {}...", branch_name);
+        git::fetch_branch(&mut base_remote, &branch_name, Some(&mut fetch_opts))
+            .unwrap_or_else(|err| panic!("Couldn't fetch branch: {}", err));
 
         debug!("Creating a new branch...");
-        let commit = git::get_latest_commit(&repo);
-        let mut branch = repo.branch(&tag, &commit, true).unwrap();
+        let branch = git::create_branch(&repo, &tag, &base_remote, &branch_name)
+            .unwrap_or_else(|err| panic!("Couldn't create branch: {}", err));
+
         debug!("Switching to branch...");
-        repo.set_head(branch.get().name().unwrap())
-            .unwrap_or_else(|err| panic!("Couldn't set HEAD: {}", err));
-        repo.checkout_head(None)
-            .unwrap_or_else(|err| panic!("Couldn't checkout HEAD: {}", err));
+        git::checkout_branch(&repo, branch.get().name().unwrap());
+        let commit = git::get_latest_commit(&repo);
         debug!(
             "Repo is now at head {}",
             repo.head().unwrap().name().unwrap()
@@ -236,11 +243,9 @@ fn test_patch(
             git::push_to_remote(&mut remote, &branch, false, &mut push_opts).unwrap();
         }
 
-        git::checkout_branch(&repo, &branch_name);
-        // we need to find the branch again since its head has moved
-        branch = repo.find_branch(&tag, BranchType::Local).unwrap();
-        branch.delete().unwrap();
-        debug!("Repo is back to {}", repo.head().unwrap().name().unwrap());
+        git::delete_branch(&repo, &tag).unwrap_or_else(|err| {
+            panic!("Couldn't delete branch: {}", err);
+        });
 
         match output {
             Ok(_) => {
diff --git a/src/settings.rs b/src/settings.rs
index 128f2030e467..40ac67cccf58 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -65,6 +65,7 @@ pub struct Project {
     pub test_all_branches: Option<bool>,
     pub remote_name: String,
     pub remote_uri: String,
+    pub base_remote_name: Option<String>,
     pub jobs: Vec<Job>,
     pub push_results: bool,
     pub category: Option<String>,
-- 
2.20.1



More information about the snowpatch mailing list