| #!/bin/bash |
| |
| # ============================================================================== |
| # *** BackFLiP (a.k.a BACKporting Fun with LInaro People) *** |
| # |
| # Usage: backflip rev_number |
| # ============================================================================== |
| |
| # ============================================================================== |
| # Configuration |
| # ============================================================================== |
| VERSION=7 |
| REF_BRANCH=linaro-local/gcc-${VERSION}-integration-branch |
| |
| DATE=`date +%Y-%m-%d` |
| NAME=`git config user.name` |
| FNAME=`echo ${NAME} | cut -d ' ' -f 1` |
| EMAIL=`git config user.email` |
| EDITOR="${EDITOR:-vim}" |
| STACKED=false |
| CMPTOOL="meld" |
| INTERACTIVE=true |
| COLOR=true |
| TMPDIR=/tmp/$(basename "$0")-$$ |
| PREFIX=${TMPDIR}/backport |
| |
| # Style |
| if $COLOR; then |
| NC='\e[0m' |
| red='\e[0;31m' |
| blue='\e[1;34m' |
| green='\e[0;32m' |
| bold='\e[1m' |
| fi |
| lines=`perl -E 'say "-" x 80'` |
| |
| # ============================================================================== |
| # Functions |
| # ============================================================================== |
| usage() { |
| tool="$(basename "$0")" |
| echo -e "$tool [-v GCC_VERSION] [-r REF_BRANCH] [-b DEV_BRANCH] [-x] REVISION" |
| } |
| |
| print_step() { |
| echo -e "${blue}${lines}\n${red}** ${1}\n${blue}${lines}${NC}\n" |
| } |
| |
| print_info() { |
| echo -e "${blue}** ${NC}${1}" |
| } |
| |
| die() { |
| echo -e "${red}${bold}ERROR: ${NC}${bold}${1}${NC}" |
| exit 1 |
| } |
| |
| open_shell() { |
| PROMPT_COMMAND="echo -n -e '${bold}[${red}BackFLiP${NC}${bold}]${NC} '" bash |
| } |
| |
| # arg1 : a sentence |
| # arg2 : a variable |
| # print arg1 and gather user input in arg2 |
| ask() { |
| echo -n -e "${blue}** ${NC}${1} ${NC}" |
| eval "read ${2}" |
| } |
| |
| # arg1 : svn rev number |
| # arg2 : ChangeLog file |
| # Create ChangeLog entry for arg1 in arg2.arg1.frag |
| forge_entry() { |
| FRAGFILE=${TMPDIR}/$(echo $2 | sed "s:/:-:g")-$1.frag |
| echo -e "\t"`dirname $2`/ >> ${PREFIX}.msg |
| echo -e "\tBackport from trunk r$1." >> ${PREFIX}.msg |
| git show --format=oneline ${SHA1} -- $2 \ |
| | grep ^+ \ |
| | sed -e '1d' -e 's:^+::' \ |
| | awk '$1 ~ /^2.*$/ {print "\t"$0} $1 !~ /^2.*$/ {print $0}' > $FRAGFILE |
| |
| # Try to fix ChangeLog format frequent issue when the same contributor |
| # commits 2 patches in a row. Git diff messes up with the context and the |
| # regular first 2 lines of the entry (DATE NAME MAIL\n\n) ended at the |
| # bottom of the patch. |
| if ! head -n 1 $FRAGFILE | grep "<.*@.*>" \ |
| && tail -n 2 $FRAGFILE | grep "<.*@.*>" > /dev/null; then |
| tail -n 2 $FRAGFILE > ${FRAGFILE}.tmp |
| head -n -2 $FRAGFILE >> ${FRAGFILE}.tmp |
| mv ${FRAGFILE}.tmp $FRAGFILE |
| fi |
| print_info "${bold}Forged ChangeLog entry:${NC}" |
| cat $FRAGFILE |
| if $INTERACTIVE; then |
| ask "${bold}Edit ChangeLog entry [N/y] ?" user_edit |
| if [ "$user_edit" == "y" ]; then |
| "$EDITOR" $FRAGFILE |
| fi |
| fi |
| cat $FRAGFILE >> ${PREFIX}.msg |
| } |
| |
| # arg1 : backported revision sha1 |
| compare() { |
| git show $1 > ${TMPDIR}/$REV.orig |
| git diff --cached > ${TMPDIR}/$REV.linaro |
| print_info "Generated diff files: ${bold}$REV.orig $REV.linaro${NC}" |
| "$CMPTOOL" ${TMPDIR}/$REV.orig ${TMPDIR}/$REV.linaro |
| ask "${bold}Do you need a Shell [N/y] ?" user_shell |
| if [ "$user_shell" == "y" ]; then |
| open_shell |
| fi |
| } |
| |
| clean() { |
| git reset --hard |
| git checkout ${REF_BRANCH} |
| if [ $1 -ne 0 ]; then |
| echo -e "\n${lines}" |
| echo -e "INTERACTIVE MODE COMMAND LINE TO REPRODUCE AND/OR FIX THE ISSUE:" |
| echo -e "$0 -v $VERSION -b $DEV_BRANCH -r $REF_BRANCH $RARGS" |
| echo -e "${lines}" |
| |
| if ! $INTERACTIVE; then |
| exec 1>&3 2>&4 |
| echo -e "Failed to backport rev $REV" |
| fi |
| fi |
| exit $1 |
| } |
| |
| # Arguments |
| while getopts ":b:r:v:x" options |
| do |
| case $options in |
| r ) REF_BRANCH=$OPTARG |
| ;; |
| b ) DEV_BRANCH=$OPTARG |
| STACKED=true |
| ;; |
| v ) VERSION=$OPTARG |
| REF_BRANCH=linaro-local/gcc-${VERSION}-integration-branch |
| ;; |
| x ) INTERACTIVE=false |
| COLOR=false |
| ;; |
| \?) print_info "Option -${bold}$OPTARG${NC} Unsupported." |
| exit 1 |
| ;; |
| : ) print_info "Option -${bold}$OPTARG${NC} requires an argument." |
| usage |
| exit 1 |
| ;; |
| esac |
| done |
| |
| if [ $# -lt 1 ]; then |
| usage ; exit 1 |
| fi |
| |
| shift $(($OPTIND - 1)) |
| |
| if [ -z ${DEV_BRANCH+x} ]; then |
| DEV_BRANCH=${REF_BRANCH} |
| fi |
| |
| # ============================================================================== |
| # Checking Configuration |
| # ============================================================================== |
| if ! git remote -v 2>&1 | grep "ssh.*review\.linaro\.org" > /dev/null ; then |
| die "You're not in the right directory !" |
| fi |
| if [ ! -e .gitreview ]; then |
| print_info "Gerrit configuration (.gitreview) is missing, creating it." |
| cat <<- EOF > .gitreview |
| [gerrit] |
| host=review.linaro.org |
| port=29418 |
| project=toolchain/gcc |
| EOF |
| fi |
| if $INTERACTIVE; then |
| if ! which $CMPTOOL > /dev/null ; then |
| print_info "Default comparison tool is missing" |
| ask "Enter tool name: " CMPTOOL |
| fi |
| fi |
| |
| if ! $INTERACTIVE; then |
| exec 3>&1 4>&2 |
| fi |
| |
| # ============================================================================== |
| # Check that all the requested backports are available |
| # ============================================================================== |
| for REV in "$@" |
| do |
| SHA1=`git log --format=format:"%H" --grep=trunk@${REV} origin/master` |
| if [ -z "$SHA1" ]; then |
| die "${red}Revision ${REV} is missing in origin/master history${NC}" |
| fi |
| done |
| |
| mkdir $TMPDIR |
| |
| # ------------------------------------------------------------------------------ |
| for REV in "$@"; do # Beginning of Stacked Backport Loop |
| # ------------------------------------------------------------------------------ |
| # Keep remaining args for error messages |
| RARGS=$@ |
| shift |
| |
| PREFIX=${PREFIX}-${REV} |
| |
| if [ -n "${PREV}" ]; then |
| STACKED=true |
| DEV_BRANCH=$bname |
| fi |
| |
| if ! $INTERACTIVE; then |
| exec >${PREFIX}.log 2>&1 |
| fi |
| |
| echo -e "${blue}${lines}" |
| print_info "Backport will de done on top of branch: ${bold}$DEV_BRANCH${NC}" |
| print_info "Reference branch used for review is: ${bold}$REF_BRANCH${NC}" |
| print_info "To change GCC version in a Linaro branch use ${bold}-v${NC}" |
| print_info "example: ${bold}backflip -v 4.8${NC}" |
| print_info "To change reference branch use ${bold}-r${NC}" |
| print_info "example: ${bold}backflip -r master${NC}" |
| |
| # ============================================================================== |
| print_step "Finding SHA1 for SVN revision $REV" |
| # ============================================================================== |
| SHA1=`git log --format=format:"%H" --grep=trunk@${REV} origin/master` |
| print_info "${NC}SVN rev ${bold}${REV}${NC} SHA1: ${bold}${SHA1}${NC}" |
| if $INTERACTIVE; then |
| ask "${bold}View the commit [N/y] ?" user_view |
| if [ "$user_view" == "y" ]; then |
| git show $SHA1 |
| fi |
| else |
| git show $SHA1 |
| fi |
| |
| # ============================================================================== |
| print_step "Cherry-picking Revision" |
| # ============================================================================== |
| git checkout $DEV_BRANCH |
| if [ $? -ne 0 ]; then |
| print_info "Cannot checkout $DEV_BRANCH branch" |
| clean 1 |
| fi |
| git cherry-pick -n $SHA1 2>/dev/null |
| |
| # ============================================================================== |
| print_step "Handling ChangeLogs Conflicts" |
| # ============================================================================== |
| CHLOGS=`git diff-tree --no-commit-id --name-only -r ${SHA1} | grep ChangeLog` |
| # For stacked backports reuse the previous commit message. |
| PMSG=`find ${TMPDIR} -name \*.msg` |
| if [ -n "$PMSG" ]; then |
| mv $PMSG ${PREFIX}.msg |
| fi |
| if [ -z "$CHLOGS" ]; then |
| print_info "${red}WARNING: No ChangeLog in this commit.${NC}" |
| forge_entry $REV "missing/ChangeLog" |
| else |
| for i in $CHLOGS; do |
| print_info "${green}$i" |
| # Revert ChangeLog corruption |
| git reset -q HEAD $i |
| git checkout $i |
| # Create ChangeLog entry |
| forge_entry $REV $i |
| done |
| fi |
| |
| # ============================================================================== |
| print_step "Status Checking" |
| # ============================================================================== |
| git status |
| if $INTERACTIVE; then |
| ask "${bold}Open a Shell [N/y] ?" user_shell |
| if [ "$user_shell" == "y" ]; then |
| open_shell |
| fi |
| else |
| git status --porcelain | grep "^.U" && \ |
| { mv ${PREFIX}.log ${PREFIX}-ko.log; clean 1; } |
| # Avoid creating branches and review request for empty modification |
| # (like ChangeLog fixes) but it can be needed for stacked backports. |
| if ! $STACKED && git diff --cached --quiet; then |
| print_info "Nothing to commit" |
| mv ${PREFIX}.log ${PREFIX}-empty.log; |
| clean 1 |
| fi |
| fi |
| |
| # ============================================================================== |
| print_step "Verification" |
| # ============================================================================== |
| if $INTERACTIVE; then |
| compare $SHA1 |
| else |
| print_info "None in batch mode." |
| fi |
| |
| # ============================================================================== |
| print_step "Branch/Commit" |
| # ============================================================================== |
| if $STACKED; then |
| bname=${DEV_BRANCH}-${REV} |
| else |
| bname=${FNAME}-${VERSION}-backport-${REV} |
| fi |
| |
| if $INTERACTIVE; then |
| ask "${bold}Create backport branch [Y/n] ?" user_bc |
| if [ "$user_bc" != "n" ]; then |
| ask "${bold}Change name (${blue}${bname}${NC}${bold}) [N/y] ?" user_bname |
| if [ "$user_bname" == "y" ]; then |
| ask "${bold}Enter name :" bname |
| fi |
| git checkout -b ${bname} ${DEV_BRANCH} |
| |
| ask "${bold}Commit backport [Y/n] ?" user_commit |
| if [ "$user_commit" != "n" ]; then |
| if $STACKED; then |
| git commit --amend -F ${PREFIX}.msg |
| else |
| git commit -F ${PREFIX}.msg |
| fi |
| |
| echo |
| print_info "If you want to stack another backport on top of this one" |
| print_info "you can stop here, and invoke backflip with command:" |
| print_info "${bold}backflip -b ${bname} REV_NUMBER${NC}" |
| print_info "The next steps are only needed for review and validation.\n" |
| |
| ask "${bold}Create patch [Y/n] ?" user_patch |
| if [ "$user_patch" != "n" ]; then |
| git format-patch -o ../ --suffix=.${bname}.patch -1 |
| fi |
| fi |
| fi |
| else |
| print_info "Create backport branch." |
| git checkout -b ${bname} ${DEV_BRANCH} |
| if [ $? -ne 0 ]; then |
| print_info "Cannot checkout $DEV_BRANCH branch" |
| clean 1 |
| fi |
| print_info "Commit backport." |
| if $STACKED; then |
| git commit --amend -F ${PREFIX}.msg |
| else |
| git commit -F ${PREFIX}.msg |
| fi |
| fi |
| |
| PREV=$REV |
| # ------------------------------------------------------------------------------ |
| done # End of stacked backport loop |
| # ------------------------------------------------------------------------------ |
| |
| # ============================================================================== |
| print_step "Review" |
| # ============================================================================== |
| if $INTERACTIVE; then |
| if [ "$user_commit" != "n" ]; then |
| ask "${bold}Request review [Y/n] ?" user_review |
| if [ "$user_review" != "n" ]; then |
| git review $REF_BRANCH |
| fi |
| fi |
| else |
| print_info "Request review." |
| git review $REF_BRANCH |
| if [ $? -ne 0 ]; then |
| print_info "Cannot request review" |
| clean 1 |
| fi |
| fi |
| |
| # ============================================================================== |
| print_step "Cleanup" |
| # ============================================================================== |
| clean 0 |