JAW Speak

Jonathan Andrew Wolter

Archive for the ‘trunk based development’ tag

Some Problems with Branch Based Development, And Recommendations

with 3 comments

Reading time: 3 – 4 minutes

As I have previously written, I dislike branch based development (especially when it involves subversion and long lived feature branches). Sometimes projects have multiple teams concurrently working in the same codebase, each on different release schedules. For example, team “Delta Force” will go live in a year, with some super top secret and amazing functionality. (Do not get me started on how bad an idea this usually is. I believe in short, frequent releases and rapid customer feedback). All the while, team “Alpha Squadron” is working on periodic releases into production every few weeks or months. Nothing from Delta can go live with Alpha’s releases. How do you enable these teams to cooperate? Some may suggest a long lived feature branch for Delta Force’s code. And then “just” merge the changes from Alpha down to Delta. I believe this is a Bad Idea.

But, if you’re forced into this scenario, at least explain to people the importance of frequently merging (i.e. on a daily basis). The longer you wait between merges, the greater the danger of increasing complexity and merge difficulties.
why-branch-and-merge-is-bad-1

Rather than using a long lived branch, I suggest the following solution: Trunk Based Development. This means you do not create actual branches. Everyone is on the same trunk. As my colleague Paul Hammant phrases it, you Branch By Abstraction. My other colleague Martin Fowler calls this Feature Toggles. First, this enables refactoring, because everyone is working in one codebase. Second, as feature toggles become permanently turned on, you can remove them, and the conceptual divergence between the two “branches” drops. This is a Good Thing.

why-branch-and-merge-is-bad-2

Once again, if you have a long lived branch, and even if you frequently merge, the divergence will grow as the not-reintegrated changes accumulate in the long lived branch. This divergence is RISK. It inhibits refactoring and encourages technical debt.

Prefer Trunk Based Development. It is not perfect, as there still is additional complexity in the codebase. But you can mitigate this with polymorphism instead of if conditionals, and have multiple continuous integration pipelines for all deployable feature toggle combinations.

why-branch-and-merge-is-bad-3
By using Trunk based development, we make it easier to do the right thing. This is an example of the Boy Scout Rule. The Boy Scouts of America have a simple rule that we can apply to our profession. “Leave the campground cleaner than you found it.”

If we all checked-in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanup doesn’t have to be something big. Change one variable name for the better, break up one function that’s a little too large, eliminate one small bit of duplication, clean up one if statement.

Long lived feature branches make this difficult, because any changes you make to one branch needs to be replicated, or you may have merge difficulties. This is especially a problem for a “long lived receiving” branch, which by it’s nature does not reintegrate its changes into the mainline. (Thus that branch is limited in what it can refactor).

Written by Jonathan

November 7th, 2010 at 8:00 am

Subversion Branch Merging and Tree Conflicts

with 5 comments

Reading time: 2 – 4 minutes

I’ve written several times about how I recommend avoiding long lived feature branches. But, if you have to use these, you might run into a problem with subversion identifying unmergable tree conflicts. I think this has been fixed in current versions of subversion, but let me walk through a conceptual scenario of what was happening when you get this error message:

Error message:
svn: Attempt to add tree conflict that already exists
svn: Error reading spooled REPORT request response

(Using svn, version 1.6.4 (r38063) compiled Aug 7 2009, 03:47:20)

It starts when you have a branch and a cherry-picked merge from the branch to trunk.
svn-merge-tree-conflicts-1

Next, you want to merge all of the branch, to trunk. And you’re using subversion merge tracking.
svn-merge-tree-conflicts-2

Subversion really does two merges: one up to the cherry pick, one after the cherry pick.
svn-merge-tree-conflicts-3

But, if there were tree conflicts in both the merge before and after the cherry pick, subversion (as of an older version we used last year) would die. I hope this is out of date, and you can avoid the problem with newer versions.
svn-merge-tree-conflicts-4

I had to recompile subversion from source, hacking in some special logging and then forcing it to continue after the merge conflict. Read more on the mailing list here and search for jawspeak or paul_hammant. With my patch, it would log a special message, continue, and not die.

svn-merge-tree-conflicts-5

Resolve it by manually fixing the tree conflicts (some of these scripts might help you), and mark the conflicts as resolved.
svn-merge-tree-conflicts-6

Moral of the story? Use Trunk Based Development, and not feature branches.

Written by Jonathan

November 6th, 2010 at 3:58 am

Posted in code

Tagged with ,

Subversion Parallel Multi-Branch Development And Merging

with 4 comments

Reading time: 2 – 2 minutes

As discussed in my previous post, I dislike merging-based-development, preferring Trunk Based Development instead. But, sometimes you’re stuck with a long-lived development branch, and you need to merge changes (subversion tree-conflicts and all). At the end of the post, I have several scripts I used to make this easier. Not the prettiest, but saved a lot of pain when we had major refactorings in trunk, and needed to locate and merge the changes to those files in a long lived (read: horrible) dev branch.

Imagine this scenario: Multiple streams of development, with a long-lived “3.0 dev” branch that has never reintegrated with the trunk. (Because 3.0 has new features that won’t go into production for many months).

branch-and-merge-problems-1

There are substantial dangers in this approach. This diagram only touches on the surface of the areas of risk in which a merge could fail. Solution? Trunk based development / branch by abstraction.

branch-and-merge-problems-2

Given this required scenario, I developed a few best practices and scripts for merging. The best practices involved having multiple branches checked out into different directories. And then we would find equivalent files that have moved and merge the tree-conflicts.

Scripts to assist in Subversion 3 way merging.

Custom diff3-cmd configuration setting in svn:

Written by Jonathan

November 3rd, 2010 at 4:21 am

How to do 3-way merges with Subversion and Kdiff3

with 4 comments

Reading time: 4 – 7 minutes

I do not endorse branch based development. I prefer trunk based development. Specifically I like what my colleague Paul calls Branch By Abstraction, coined by Stacy Curl, and recently mentioned by Martin Fowler (All one time ThoughtWorkers, and 2 currently).

If you’re stuck with merging though, 3-way merges make it much easier. Doing it with subversion is easy. Instructions are for Linux.

  1. apt-get or yum install kdiff3.
  2. Edit your /etc/subversion/config and fin the line with diff3-cmd, set it to: diff3-cmd=/usr/local/bin/svndiff.sh
  3. Next, create the file /usr/local/bin/svndiff.sh. See below for the script you’ll want to enter in it.

Now when you get a merge conflict you will choose M and merge will open in kdiff3. On the left is the base revision, in the middle is your working copy, and on the right the incoming change. This is a little more to look at, but it is invaluable when dealing with merges. I wouldn’t go back to 2 way diff ever again.

#!/bin/bash
 
# tim/paul: this is a copy of the file located at http://www.yolinux.com/TUTORIALS/src/svndiffwrapper.txt
#    modified to do a non-conflicting merge automatically. see #HERE#
 
# Return an errorcode of 0 on successful merge, 1 if unresolved conflicts
# remain in the result.  Any other errorcode will be treated as fatal.
# Author: Michael Bradley
 
#NOTE: all output must be redirected to stderr with "1>&2" as all stdout output is written to the output file
 
VDIFF3="kdiff3"
DIFF3="diff3"
DIFF="kdiff3"  
 
promptUser ()
{
    read answer
    case "${answer}" in
 
        "M"         )
        echo "" 1>&2
        echo "Attempting to merge ${baseFileName} with ${DIFF}" 1>&2
        $VDIFF3 $older $mine $theirs --L1 $labelOlder --L2 $labelMine --L3 $labelTheirs -o $output 1>&2
        bLoop=1
        if [ -f $output ]; then
            if [ -s $output ]; then
                #output succesfully written
                bLoop=0
            fi
        fi
        if [ $bLoop = 0 ]; then
            cat $output
            rm -f $output
            exit 0
        else
            echo "Merge failed, try again" 1>&2
        fi
 
        ;;
 
        "m"         )
        echo "" 1>&2
        echo "Attempting to auto-merge ${baseFileName}" 1>&2
        diff3 -L $labelMine -L $labelOlder -L $labelTheirs -Em $mine $older $theirs > $output
        if [ $? = 1 ]; then
            #Can't auto merge
            rm -f $output
            $VDIFF3 $older $mine $theirs --L1 $labelOlder --L2 $labelMine --L3 $labelTheirs -o $output --auto 1>&2
            bLoop=1
            if [ -f $output ]; then
                if [ -s $output ]; then
                    #output succesfully written
                    bLoop=0
                fi
            fi
            if [ $bLoop = 0 ]; then
                cat $output
                rm -f $output
                exit 0
            else
                echo "Merge failed, try again" 1>&2
            fi
        else
            #We can automerge, and we already did it
            cat $output
            rm -f $output
            exit 0
        fi
        ;;
 
        "diff3" | "Diff3" | "DIFF3"  )
        echo "" 1>&2
        echo "Diffing..." 1>&2
        $VDIFF3 $older $mine $theirs --L1 $labelOlder --L2 $labelMine --L3 $labelTheirs 1>&2
        ;;
 
        "diff" | "Diff" | "DIFF"  )
        echo "" 1>&2
        echo "Diffing..." 1>&2
        $DIFF $mine $theirs -L $labelMine -L $labelTheirs 1>&2
        ;;
 
        "A" | "a"   )
        echo "" 1>&2
        echo "Accepting remote version of file..." 1>&2
        cat ${theirs}
        exit 0
        ;;
 
        "I" | "i"   )
        echo "" 1>&2
        echo "Keeping local modifications..." 1>&2
        cat ${mine}
        exit 0
        ;;
 
        "R" | "r"   )
        echo "" 1>&2
        echo "Reverting to base..." 1>&2
        cat ${older}
        exit 0
        ;;
 
        "D" | "d"   )
        echo "" 1>&2
        echo "Runnig diff3..." 1>&2
        diff3 -L $labelMine -L $labelOlder -L $labelTheirs -Em $mine $older $theirs
        #Exit with return vaule of the diff3 (to write out files if necessary)
        exit $?
        ;;
 
        "S" | "s"   )
        echo "" 1>&2
        echo "Saving for later..." 1>&2
        cat ${mine}
        #Exit with return vaule of 1 to force writting of files
        exit 1
        ;;
 
        "Fail" | "fail" | "FAIL"   )
        echo "" 1>&2
        echo "Failing..." 1>&2
        exit 2
        ;;
 
        "H" | "h"   )
        echo "" 1>&2
        echo "USAGE OPTIONS:" 1>&2
        echo "  [A]ccept    Accept $labelTheirs and throw out local modifications" 1>&2
        echo "  [D]efault   Use diff3 to merge files (same behavior as vanilla SVN)" 1>&2
        echo "  [Fail]      Kills the command (not suggested)" 1>&2
        echo "  [H]elp      Print this message" 1>&2
        echo "  [I]gnore    Keep your locally modified version as is" 1>&2
        echo "  [M]erge     Manually merge using ${VDIFF3}" 1>&2
        echo "  [m]erge     Same as "M" but attempts to automerge if possible" 1>&2
        echo "  [R]evert    Revert to base version (${labelOlder})" 1>&2
        echo "  [S]ave      Same as 'I' but writes out rold, rnew, and rmine files to deal with later" 1>&2
        echo "  [diff]      Type 'diff' to diff versions $labelMine and $labelTheirsthe before making a descision" 1>&2
        echo "  [diff3]     Type 'diff3' to diff all three versions before making a descision" 1>&2
        echo "" 1>&2
        ;;
 
        *   )
        echo "'${answer}' is not an option, try again." 1>&2
        ;;
    esac
}
 
if [ -z $2 ]
then
    echo ERROR: This script expects to be called by subversion
    exit 1
fi
 
if [ $2 = "-m" ]
then
    #Setup vars
    labelMine=${4}
    labelOlder=${6}
    labelTheirs=${8}
    mine=${9}
    older=${10}
    theirs=${11}
    output=${9}.svnDiff3TempOutput
    baseFileName=`echo $mine | sed -e "s/.tmp$//"`
 
#HERE#
    diff3 -L $labelMine -L $labelOlder -L $labelTheirs -Em $mine $older $theirs > $output
    if [ $? = 1 ]; then
        #Can't auto merge
        #Prompt user for direction
        while [ 1 ]
        do
            echo "" 1>&2
            echo "${baseFileName} requires merging." 1>&2
            echo "" 1>&2
            echo "What would you like to do?" 1>&2
            echo "[M]erge [A]ccept [I]gnore [R]evert [D]efault [H]elp" 1>&2
            promptUser
        done
    else
        #We can automerge, and we already did it
        cat $output
        rm -f $output
        exit 0
    fi
else
    L="-L"         #Argument option for left label
    R="-L"         #Argument option for right label
    label1=$3       #Left label
    label2=$5       #Right label
    file1=$6        #Left file
    file2=$7        #Right file
 
    $DIFF $file1 $file2 $L "$label1" $L "$label2" &
    #$DIFF $file1 $file2 &
    #wait for the command to finish
    wait
fi
exit 0

Note: I also posted this to a gist on github: svndiff.sh.

Written by Jonathan

September 17th, 2009 at 8:49 pm