Automating to copy a set of commits from one branch to another
Tags: git, bash, automation, commits
Published: 2020-10-26 17:42:10

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)](https://cdn-images-1.medium.com/max/10000/0*Cqwz58a0G9gTMYbg)*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)*

## Final Script

[Code Link](https://gist.github.com/Muhammadwasi/05fa262f3afd92163896fbe8295b9b16)

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>

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.

1. 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

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
    }

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

    commits=($(git rev-list "$FROM_COMMIT^..$TO_COMMIT"))

* `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

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

* `#` 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 Log](https://cdn-images-1.medium.com/max/2000/1*LniWy3M5iXMjBVUiKMYYHQ.jpeg)*Git Project Commit Log*

![Screenshot of the script when executed](https://cdn-images-1.medium.com/max/2000/1*6LxT64HczwoiZlhI0DJQpA.jpeg)*Screenshot of the script when executed*