DEV Community

loading...

Automating to copy a set of commits from one branch to another

Muhammad Wasi Naseer
Software Engineer at VentureDive | Java Backend Engineer| Python | Linux
・3 min read

This article will describe how to copy a set of contiguous commits from one branch to another branch in a repository

Photo by [Yancy Min](https://unsplash.com/@yancymin?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)Photo by Yancy Min on Unsplash

Final Script

Code Link

The final version of our bash script copy_commitswould be like this

copy_commits.sh -p <project_path> -s <source-branch> -t <target-branch> -f <commit-start> -e <commit-end>
Enter fullscreen mode Exit fullscreen mode

Let’s assume that we have two branches dev1 and dev2, and we want to move a set of contiguous commits from dev1 to dev2

dev1 branch has 5 commits and the dev2 branch has 1 commit. And, we want to copy the 4 extra commits of dev1 to the dev2 branch by specifying the start commit and end commit.

Steps

We will do the following steps to automate the task.

  1. First, we’ll parse the parameters passed at the time of the script execution. The values will be store in the following five variables:
  • $PROJECT_PATH

  • $SOURCE_BRANCH

  • $TARGET_BRANCH

  • $FROM_COMMIT

  • $TO_COMMIT

  1. We will first check out to dev1 branch (source branch) and get all the commits in between the start and end commit (inclusive) in an array.

  2. We’ll then check out to dev2 branch, and we’ll take each of the commits from the array and cherry-pick the commit on dev2 (target branch)

Step 1: Parsing parameters and storing parameters into variables

while getopts ":p:s:t:f:e:" o; do
    case "${o}" in
    p)
            PROJECT_PATH="${OPTARG}"
            ;;
        s)
            SOURCE_BRANCH="${OPTARG}"
            ;;
        t)
            TARGET_BRANCH="${OPTARG}"
            ;;
        f)
            FROM_COMMIT="${OPTARG}"
            ;;
        e)
            TO_COMMIT="${OPTARG}"
            ;;
        *)
            abnormal_exit
            ;;
    esac
done
Enter fullscreen mode Exit fullscreen mode

Following is the code for the abnormal_exit() function

# Prints usage
usage() {                                      
  echo "Usage: $0 -p <project_path> -s <source-branch> -t <target-branch> -f <commit-start> -e <commit-target>  " 1>&2 
}

# Prints usage and exit
abnormal_exit() {                              
  usage
  exit 1
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Getting commits’ hash from the source branch and storing it into an array

commits=($(git rev-list "$FROM_COMMIT^..$TO_COMMIT"))
Enter fullscreen mode Exit fullscreen mode
  • git rev-list is used to list the git commits in reverse chronological order. Therefore, the latest commit will be at the start of the array.

  • <start_commit_hash>..<end_commit_hash> is a notation to specify that start from the start_commit_hash (not included) to the end_commit_hash (included). If we want to get start_commit_hash in the result then we have to specify the parent of the start commit. The parent of the start commit can be specified using caret ^ at the end of the commit hash. That is why a caret has been used at the end of the start commit.

  • $FROM_COMMIT^ : It means that start from the parent of the $FROM_COMMIT. The caret (^) is used to specify the parent of the commit otherwise the $FROM_COMMIT will not be included in the final result.

  • $(some_command_here) : This notation is used when we want to run some command but want to store the output of the command into a variable. Hence, we can assign its output to a variable on the left.

  • ($(some_command_here)) : If we encapsulate some text into parentheses, then bash will convert them into an array based on the IFS(Internal Field Separator). It is by default whitespace. Hence the output of the $(some_command_here) will be converted into an array.

Step 3: Applying commits on the target branch by picking them from the commits array

We have got all the commits into the commits array. Now, we have to check out the target branch.

git checkout $TARGET_BRANCH

if ! [[ $? -eq 0 ]]
then
    echo $'\u274c' "ERROR: Failed to checkout to $TARGET_BRANCH"
    exit 2
fi
Enter fullscreen mode Exit fullscreen mode

We have to pick each commit one by one and run the cherry-pick command. But, remember that we have to pick the commits in reverse order because the latest commit is at the start of the array. That is why we will start picking commits from length-1 index of the commits array.

# For each commit
for ((i=$((${#commits[@]}-1));i>=0;i--))
do
    echo "Cherry Picking ${commits[i]} ..."
    git cherry-pick "${commits[i]}"
    # If the above command fails, then exit with proper message
    if ! [[ $? -eq 0 ]]
    then
        echo "ERROR: Cherry pick commit with ${commits[i]} failed, exiting..."
        exit 2
    fi
    # Show success message for the copied commit
    echo "DONE: Commit ${commits[i]} copied"
done
Enter fullscreen mode Exit fullscreen mode
  • # is used to find the length of the array.

  • The loop starts at length-1, and we will decrementing i until it is less than 0.

  • $? is used to get the exit status code of the most recent command executed.

Final Script Screenshot

Git Project Commit LogGit Project Commit Log

Screenshot of the script when executedScreenshot of the script when executed

Discussion (0)