Skip to content

add RebaseCommitOnBranch param to UserFFBranchRequest

Goal

Squash commit without merge commit is a long desired feature. This MR tries to support it with minimal efforts on both Gitaly and Rails sides.

This MR is part of JiHu Gitlab milestone 16.2 and mofification on Rails is also planned.

Method

I propose to add a new, optional boolean field rebase_commit_on_branch to the GRPC request of UserFFBranch, when setting to true, accomplishing the wish of the caller, that the commit referred to by field "commit_id" is going to be rebased on the commit referred to by "branch" before the fast-forward happens.

// UserFFBranchRequest contains parameters for the UserFFBranch RPC.
message UserFFBranchRequest {
  // repository is the repository for which to perform the fast-forward merge.
  Repository repository = 1 [(target_repository)=true];
  // user is the user which to perform the fast-forward merge as. This is used
  // for authorization checks.
  User user = 2;
  // commit_id is the commit ID to update the branch to.
  string commit_id = 3;
  // branch is the name of the branch that shall be update. This must be the
  // branch name only and not a fully qualified reference, e.g. "master"
  // instead of "refs/heads/master".
  bytes branch = 4;
  // expected_old_oid is the object ID which branch is expected to point to.
  // This is used as a safety guard to avoid races when branch has been
  // updated meanwhile to point to a different object ID.
  //
  // If unset, the target branch will be overwritten regardless of its current
  // state. If set, it must either contain a valid, full object ID  or the
  // zero object ID in case the branch should be created. Otherwise, this RPC
  // will return an error.
  string expected_old_oid = 5;
  // If true, the commit referred to by "commit_id" is rebased on "branch" before
  // the fast-forward happens. The fast-forward fails if there is a rebase failure.
  bool rebase_commit_on_branch = 6;
  // timestamp is the optional timestamp to use for the rebased
  // commit's committer date when rebase_commit_on_branch is true.
  // If it's not set, the current time will be used.
  google.protobuf.Timestamp timestamp = 7;
}

In this way, Rails can implement "squash merge without merge commit" with only 2 RPC calls:

  1. UserSquash: to squash all commits in the feature branch into one, denoted as squashed commit;
  2. UserFFBranch: rebase squashed commit on the target branch(usually, main or master) via our newly added field rebase_commit_on_branch and fast-forward the pointer of the target branch to the rebased commit.

Benefits

Compare with: Rails calling UserSquash, UserRebaseConfirmable and UserFFBranch

UserRebaseConfirmable works with branches instead of commit so that Rails has to create a temporary branch on squashed commit and cleans it up after UserFFBranch, which results in 5 RPC calls at least and hard-to-clean-up senario when one of the RPC encounters error.

Future consideration: implementing "rebase merge without merge commit"

rebase merge without merge commit is also a desired feature potentially worth implementing in the future and the updated UserFFBranch in this MR already support it at Gitaly side.

Demo

Demo repo: git-merge.zip

The branches and commits of the demo repo look like:

gitGraph
    commit id: "root"
    commit id: "m-1"
    branch feature
    commit id: "f-1"
    commit id: "f-2"
    commit id: "f-3"
    checkout main
    commit id: "m-2"
    commit id: "m-3"

I wrote a Go example, calling Gitaly mocking the way of Rails: https://jihulab.com/nanmu42/call-gitaly/-/blob/43e24e4e963768eeea1263de56890701a5ecf0c0/SquashMergeWithoutMergeCommit/main.go

After squash merge without merge commit, the commits on main branch looks like:

image

Edited by LI Zhennan

Merge request reports

Loading