soft-fork-tools/rebase.sh

226 lines
6.3 KiB
Bash

#!/usr/bin/env bash
source $(dirname $0)/common.sh
source $(dirname $0)/api.sh
source $(dirname $0)/git.sh
declare -A REBASE_STATE
function rebase_latest_archive_date() {
local latest=$(git_visible_soft_fork | git_get_dates | head -1)
test -n "$latest"
echo $latest
}
function rebase_archive_branch() {
local branch=$1
echo "soft-fork/$(rebase_latest_archive_date)/$branch"
}
function rebase_base_branch_sanity_check() {
local base_branch=$1
local base_hash=$(git_branch_hash $base_branch)
test "$base_hash"
local gitea_branch=$(get_gitea_branch $base_branch)
test "$gitea_branch"
local gitea_hash=$(git_branch_hash $gitea_branch)
test "$gitea_hash"
#
# If the tip of the Gitea branch is an ancestor of the base branch
# it means it was rebased already and ready to be used. Otherwise
# it means an attempt to rebase will cherry-pick commits on top of
# a base branch that is obsolete because not yet rebased.
#
git merge-base --is-ancestor $gitea_hash $base_hash
}
function rebase_head_branch() {
local branch=$1
echo rebase-$branch
}
function rebase_base_branch() {
local branch=$1
test "$base_branch"
echo ${BASE_BRANCH[$branch]}
}
function rebase_archived_branch() {
local branch=$1
local date=$(rebase_latest_archive_date)
echo "soft-fork/$date/$branch"
}
function rebase_create_branch() {
local branch=$1
#
# Archive branch sanity checks because there is no point
# creating an rebase branch if they do not pass
#
local archive_branch=$(rebase_archive_branch $branch)
git_branch_exists $archive_branch
local base_branch=$(rebase_base_branch $branch)
rebase_base_branch_sanity_check $base_branch
local head_branch=$(rebase_head_branch $branch)
if ! git_branch_exists $head_branch ; then
comment_line "Create rebase head branch $head_branch"
git_create_branch $base_branch $head_branch
fi
}
function rebase_switch() {
local branch=$1
local head_branch=$(rebase_head_branch $branch)
local base_branch=$(rebase_base_branch $branch)
if test $(git branch --show-current) = $head_branch ; then
return
fi
if git show-ref --quiet refs/heads/$head_branch; then
git switch $head_branch
else
git switch --create $head_branch $(git_branch_to_ref $base_branch)
git config branch.$head_branch.pushRemote $REMOTE
fi
}
#
# For branch BRANCH based on BASE, get the commits between them
#
# git log soft-fork/<latest>/$BASE..soft-fork/<latest>/$BRANCH
#
function rebase_get_commits_to_cherry_pick() {
local branch=$1
local branch_ref=$(git_branch_to_ref $(rebase_archived_branch $branch))
local base_ref=$(git_branch_to_ref $(rebase_archived_branch $(rebase_base_branch $branch)))
git_get_commits $base_ref $branch_ref
}
function rebase_get_commits_in_the_pr() {
local branch=$1
local head_ref=$(git_branch_to_ref $(rebase_head_branch $branch))
local base_ref=$(git_branch_to_ref $(rebase_base_branch $branch))
git_get_commits $base_ref $head_ref
}
function rebase_get_commits_in_the_local_branch() {
local branch=$1
local head_branch=$(rebase_head_branch $branch)
local base_ref=$(git_branch_to_ref $(rebase_base_branch $branch))
git_get_commits $base_ref refs/heads/$head_branch
}
#
# Given two files that contain a list of commit hash, compare
# each of them using git patch-id. If all are identical display
# nothing. Otherwise display the commits that are different.
#
function rebase_compare_commit_series() {
local file_a="$1"
local file_b="$2"
paste $file_a $file_b | while read a b; do
if test -z "$a" -o -z "$b"; then
echo "$a\t$b"
elif test "$(git_patch_id $a)" != "$(git_patch_id $b)"; then
echo "$a\t$b"
fi
done
}
function rebase_cherry_pick() {
local branch="$1"
comment_header "cherry-pick $branch"
rebase_get_commits_to_cherry_pick $branch > $TMP_DIR/commits_to_cherry_pick
rebase_get_commits_in_the_local_branch $branch > $TMP_DIR/commits_in_the_local_branch
if test -s $TMP_DIR/commits_in_the_local_branch; then
rebase_compare_commit_series $TMP_DIR/commits_to_cherry_pick $TMP_DIR/commits_in_the_local_branch > $TMP_DIR/compare_commit_series
if test -s $TMP_DIR/compare_commit_series; then
comment_line "No cherry-pick because the commits are different"
comment_file $TMP_DIR/compare_commit_series
else
comment_line "All commits already cherry-picked, do nothing"
fi
else
comment_line "The local branch is empty, cherry-pick"
comment_commits $(cat $TMP_DIR/commits_to_cherry_pick)
$DRY_RUN git cherry-pick -x $(cat $TMP_DIR/commits_to_cherry_pick)
local head_branch=$(rebase_head_branch $branch)
$DRY_RUN git push --quiet $REMOTE $head_branch
fi
}
function rebase_create_pr() {
local branch=$1
local base_branch=$(rebase_base_branch $branch)
local head_branch=$(rebase_head_branch $branch)
if ! api_pr_exists $base_branch $head_branch ; then
api_pr_create $base_branch $head_branch "Rebase $branch onto $base_branch"
fi
}
function rebase_check_status() {
local branch=$1
local head_hash=$(git_branch_hash $(rebase_head_branch $branch))
local state=$(api_check_status $head_hash)
if test "$state" != success; then
comment_line "The status of the PR is not (yet) success"
return 1
fi
}
function rebase_force_push() {
local branch=$1
local head_branch=$(rebase_head_branch $branch)
local head_ref=$(git_branch_to_ref $head_branch)
$DRY_RUN git push --force $REMOTE $head_ref:refs/heads/$branch
}
function rebase_close_pr() {
local branch=$1
local head_branch=$(rebase_head_branch $branch)
local base_branch=$(rebase_base_branch $branch)
local id=$(api_pr_id $base_branch $head_branch)
api_pr_close $id
}
function rebase_delete_branch() {
local branch=$1
local head_branch=$(rebase_head_branch $branch)
$DRY_RUN git push --delete $REMOTE refs/heads/$head_branch
git switch --quiet main
$DRY_RUN git branch --force --delete $head_branch
}
function rebase_feature_branch() {
local branch=$1
git_fetch
rebase_create_branch $branch
rebase_switch $branch
rebase_create_pr $branch
rebase_cherry_pick $branch
rebase_check_status $branch
rebase_force_push $branch
rebase_close_pr $branch
rebase_delete_branch $branch
}