Backporting

General

We backport important fixes and improvements from the current master release to get them to our users faster.

Process

We mostly consider bug fixes for back porting. Occasionally, important changes to the API can be backported to make it easier for developers to keep their apps working between major releases. If you think a pull request (PR) is relevant for the stable release, go through these steps:

  1. Make sure the PR is merged to master

  2. Ask the feature maintainer if the code should be backported and add the label backport-request to the PR

  3. If the maintainer say yes then create a new branch based on the respective stable branch, cherry-pick the needed commits to that branch and create a PR on GitHub.

  4. Specify the corresponding milestone for that series to this PR and reference the original PR in there. This enables the QA team to find the backported items for testing and having the original PR with detailed description linked.

Before each patch release there is a freeze to be able to test everything as a whole without pulling in new changes. This freeze is announced on the owncloud-devel mailinglist. While this freeze is active a backport isn’t allowed and has to wait for the next patch release.

The QA team will try to reproduce all the issues with the X.Y.Z-next-maintenance milestone on the relevant release and verify it is fixed by the patch release (and doesn’t cause new problems). Once the patch release is out, the post-fix -next-maintenance is removed and a new -next-maintenance milestone is created for that series.

Steps

Because pushing directly to particular ownCloud branches is forbidden (e.g., origin/stable-xx), you need to create your own remote branch, based off of the branch that you wish to backport to. However, doing so can involve a number of manual steps. To reduce the effort and time involved, use the script below instead.

The script uses curl and the jq (lightweight and flexible command-line JSON processor) package. Please install them before first usage. Please see this link for installation details of jq covering varios OS.
This script uses the github API. For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Unauthenticated requests are associated with the originating IP address, and not the user making requests. Please see this link for more information about github rate limiting.
In case of conflicts, the script exits. The merge conflicts will need to be resolved before manually continuing the backport.
#!/bin/bash

if ! [ -x "$(command -v jq)" ]; then
  echo 'Error: jq is not installed.' >&2
  echo 'Please install package "jq" before using this script'
  exit 1
fi

if ! [ -x "$(command -v curl)" ]; then
  echo 'Error: curl is not installed.' >&2
  echo 'Please install package "curl" before using this script'
  exit 1
fi

if [ "$#" -lt 2 ]; then
    echo "Illegal number of parameters"
    echo "  $0 <merge/commit-sha> <targetBranchName>"
    echo "  For example: $0 1234567 stable10"
    exit
fi

commit=$1
targetBranch=$2
baseBranch=$(git rev-parse --abbrev-ref HEAD)

is_merged=$(git branch --contains $1 | grep -oP '(?<=\*).*')
if [ -z "$is_merged" ]; then
    echo "$commit has not been merged or branch $baseBranch is not pulled/rebased. Exiting"
    echo
    exit
fi

# get the PR number from commit
pullId=$(git log $1^! --oneline 2>/dev/null | tail -n 3 | grep -oP '(?<=#)[0-9]*')

# get the repository from commit
repository=$(git config --get remote.origin.url 2>/dev/null | perl -lne 'print $1 if /(?:(?:https?:\/\/github.com\/)|:)(.*?).git/')

# get the list of commits in PR without any merge commit
# $1^ means the first parent of the merge commit (that is passed in as $1).
# Because $1 is a "magically-generated" merge commit, it happily "jumps back"
# to the point on the main branch just before where the PR was merged.
# And so the commits from that point are exactly the list of individual
# commits in the original PR.
# --no-merges leaves out the merge commit itself, and we get just what we want
commitList=$(git log --no-merges --reverse --format=format:%h $1^..$1)

# get the request reset time window from github in epoch
rateLimitReset=$(curl -i https://api.github.com/users/octocat 2>&1 | grep -m1 'X-RateLimit-Reset:' | grep -o '[[:digit:]]*')

# get the remaining requests in window from github
rateLimitRemaining=$(curl -i https://api.github.com/users/octocat 2>&1 | grep -m1 'X-RateLimit-Remaining:' | grep -o '[[:digit:]]*')

# time remaining in epoch
now=$(date +%s)
((remaining=rateLimitReset-now))

# time remaining in HMS
remaining=$(date -u -d @$remaining +%H:%M:%S)

# check if there are commits to cherry pick and list them in case
if [[ -z "$commitList" ]]; then
  echo "There are no commits to cherry pick. Exiting"
  exit
else
  lineCount=$(echo "$commitList" | grep '' | wc -l)
  echo "$lineCount commits being cherry picked:"
  echo
  echo "$commitList"
fi

if [ $rateLimitRemaining -le 0 ]; then
  # do not continue if there are no remaining github requests available
  echo "You do not have enough github requests available to backport"
  echo "The current rate limit window resets in $remaining"
  exit
else
  # get the PR title, this is the only automated valid way to get the title
  pullTitle=$(curl https://api.github.com/repos/$repository/pulls/$pullId 2>/dev/null | jq '.title' | sed 's/^.//' | sed 's/.$//')
fi

# build names used
targetCommit="$targetBranch-$commit-$pullId"
message="[$targetBranch] [PR $pullId] $pullTitle"

echo
echo "Info:"
echo "You have $rateLimitRemaining backport requests remaining in the current github rate limit window"
echo "The current rate limit window resets in $remaining"
echo
echo "Backporting commit $commit to $targetBranch with the following text:"
echo "$message"
echo

set -e

git fetch -p --quiet
git checkout "$targetBranch"
git pull --rebase --quiet
git checkout -b "$targetCommit"

echo

# cherry pick all commits from commitList
lC=1
echo "$commitList" | while IFS= read -r line; do
  echo "Cherry picking commit $lC: $line"
  # if you do not want to use a default conflict resolution to take theirs
  # (help fix missing cherry picked commits or file renames)
  #git cherry-pick $line > /dev/null
  git cherry-pick -Xtheirs $line > /dev/null
  lC=$(( $lC + 1 ))
done
echo

git commit --quiet --amend -m "$message" -m "Backport of PR #$pullId"

echo "Pushing: $message"
echo

git push --quiet -u origin "$targetCommit"
git checkout --quiet "$baseBranch"
It is highly recommended to use the merge commit sha when backporting a Pull Request. The merge commit includes all PR sub commits to be backported. With that, no individual sub commit backporting is necessary.

The following example assumes that:

  • You save the script in a file called <path>/backport.sh and mark it executable

  • You have checked out the branch containing the merged sha (like master)

  • Your merge sha = 1234567 and your target branch = stable10

The command to backport this Pull Request would be called as follows:

<path>/backport.sh 1234567 stable10
4 commits beeing cherry picked:

2e03d938
fef19729
61ac3f09
0528601f
...
Switched to a new branch ‘stable10-1234567-34654‘
...
[stable10] [PR 34654] Each generated birthday or death event gets a new UID
...
Cherry picking commit 1: 2e03d938
Cherry picking commit 1: fef19729
...
Pushing: ...
...

Please keep in mind that this is an example and you have to adapt the commit hash and the target branch accordingly.

The script also lists the quantity and commits to be backported and the current one in process. This can be helpful in case there is a conflict and you manually continue after the conflict has been resolved.

When the script completes, go to GitHub, where it will suggest that you make a PR from pushed branch. Even the script tries to automate, you may need to set the Pull Request title and description text manually via copy/paste.

Change the base branch to be committed against, from master to your target branch (in our example stable10) and continue.

In case you have installed the xdg-utils package, you can add at the end of the script above following code which opens the PR to be finalized in your browser. macOS does not need this package, use the command open instead:

echo "Creating pull request for branch $targetBranch in $repository"

xdg-open "https://github.com/$repository/pull/new/$targetBranch...$targetCommit" &>/dev/null
This command opens the Pull Request and sets the target branch (in our example stable10) for the backport automatically.