1#!/usr/bin/python
   2#
   3# Copyright (C) 2005 Fredrik Kuivinen
   4#
   5import sys
   7sys.path.append('''@@GIT_PYTHON_PATH@@''')
   8import math, random, os, re, signal, tempfile, stat, errno, traceback
  10from heapq import heappush, heappop
  11from sets import Set
  12from gitMergeCommon import *
  14outputIndent = 0
  16def output(*args):
  17    sys.stdout.write('  '*outputIndent)
  18    printList(args)
  19originalIndexFile = os.environ.get('GIT_INDEX_FILE',
  21                                   os.environ.get('GIT_DIR', '.git') + '/index')
  22temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
  23                     '/merge-recursive-tmp-index'
  24def setupIndex(temporary):
  25    try:
  26        os.unlink(temporaryIndexFile)
  27    except OSError:
  28        pass
  29    if temporary:
  30        newIndex = temporaryIndexFile
  31    else:
  32        newIndex = originalIndexFile
  33    os.environ['GIT_INDEX_FILE'] = newIndex
  34# This is a global variable which is used in a number of places but
  36# only written to in the 'merge' function.
  37# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
  39#                       don't update the working directory.
  40#              False => Leave unmerged entries in the cache and update
  41#                       the working directory.
  42cacheOnly = False
  44# The entry point to the merge code
  46# ---------------------------------
  47def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
  49    '''Merge the commits h1 and h2, return the resulting virtual
  50    commit object and a flag indicating the cleanness of the merge.'''
  51    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
  52    global outputIndent
  54    output('Merging:')
  56    output(h1)
  57    output(h2)
  58    sys.stdout.flush()
  59    if ancestor:
  61        ca = [ancestor]
  62    else:
  63        assert(isinstance(graph, Graph))
  64        ca = getCommonAncestors(graph, h1, h2)
  65    output('found', len(ca), 'common ancestor(s):')
  66    for x in ca:
  67        output(x)
  68    sys.stdout.flush()
  69    mergedCA = ca[0]
  71    for h in ca[1:]:
  72        outputIndent = callDepth+1
  73        [mergedCA, dummy] = merge(mergedCA, h,
  74                                  'Temporary merge branch 1',
  75                                  'Temporary merge branch 2',
  76                                  graph, callDepth+1)
  77        outputIndent = callDepth
  78        assert(isinstance(mergedCA, Commit))
  79    global cacheOnly
  81    if callDepth == 0:
  82        setupIndex(False)
  83        cacheOnly = False
  84    else:
  85        setupIndex(True)
  86        runProgram(['git-read-tree', h1.tree()])
  87        cacheOnly = True
  88    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
  90                                 branch1Name, branch2Name)
  91    if graph and (clean or cacheOnly):
  93        res = Commit(None, [h1, h2], tree=shaRes)
  94        graph.addNode(res)
  95    else:
  96        res = None
  97    return [res, clean]
  99getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
 101def getFilesAndDirs(tree):
 102    files = Set()
 103    dirs = Set()
 104    out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
 105    for l in out.split('\0'):
 106        m = getFilesRE.match(l)
 107        if m:
 108            if m.group(2) == 'tree':
 109                dirs.add(m.group(4))
 110            elif m.group(2) == 'blob':
 111                files.add(m.group(4))
 112    return [files, dirs]
 114# Those two global variables are used in a number of places but only
 116# written to in 'mergeTrees' and 'uniquePath'. They keep track of
 117# every file and directory in the two branches that are about to be
 118# merged.
 119currentFileSet = None
 120currentDirectorySet = None
 121def mergeTrees(head, merge, common, branch1Name, branch2Name):
 123    '''Merge the trees 'head' and 'merge' with the common ancestor
 124    'common'. The name of the head branch is 'branch1Name' and the name of
 125    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
 126    where tree is the resulting tree and cleanMerge is True iff the
 127    merge was clean.'''
 128    
 129    assert(isSha(head) and isSha(merge) and isSha(common))
 130    if common == merge:
 132        output('Already uptodate!')
 133        return [head, True]
 134    if cacheOnly:
 136        updateArg = '-i'
 137    else:
 138        updateArg = '-u'
 139    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
 141                                common, head, merge], returnCode = True)
 142    if code != 0:
 143        die('git-read-tree:', out)
 144    [tree, code] = runProgram('git-write-tree', returnCode=True)
 146    tree = tree.rstrip()
 147    if code != 0:
 148        global currentFileSet, currentDirectorySet
 149        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
 150        [filesM, dirsM] = getFilesAndDirs(merge)
 151        currentFileSet.union_update(filesM)
 152        currentDirectorySet.union_update(dirsM)
 153        entries = unmergedCacheEntries()
 155        renamesHead =  getRenames(head, common, head, merge, entries)
 156        renamesMerge = getRenames(merge, common, head, merge, entries)
 157        cleanMerge = processRenames(renamesHead, renamesMerge,
 159                                    branch1Name, branch2Name)
 160        for entry in entries:
 161            if entry.processed:
 162                continue
 163            if not processEntry(entry, branch1Name, branch2Name):
 164                cleanMerge = False
 165                
 166        if cleanMerge or cacheOnly:
 167            tree = runProgram('git-write-tree').rstrip()
 168        else:
 169            tree = None
 170    else:
 171        cleanMerge = True
 172    return [tree, cleanMerge]
 174# Low level file merging, update and removal
 176# ------------------------------------------
 177def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
 179              branch1Name, branch2Name):
 180    merge = False
 182    clean = True
 183    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
 185        clean = False
 186        if stat.S_ISREG(aMode):
 187            mode = aMode
 188            sha = aSha
 189        else:
 190            mode = bMode
 191            sha = bSha
 192    else:
 193        if aSha != oSha and bSha != oSha:
 194            merge = True
 195        if aMode == oMode:
 197            mode = bMode
 198        else:
 199            mode = aMode
 200        if aSha == oSha:
 202            sha = bSha
 203        elif bSha == oSha:
 204            sha = aSha
 205        elif stat.S_ISREG(aMode):
 206            assert(stat.S_ISREG(bMode))
 207            orig = runProgram(['git-unpack-file', oSha]).rstrip()
 209            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
 210            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
 211            try:
 212                [out, code] = runProgram(['merge',
 213                                          '-L', branch1Name + '/' + aPath,
 214                                          '-L', 'orig/' + oPath,
 215                                          '-L', branch2Name + '/' + bPath,
 216                                          src1, orig, src2], returnCode=True)
 217            except ProgramError, e:
 218                print >>sys.stderr, e
 219                die("Failed to execute 'merge'. merge(1) is used as the "
 220                    "file-level merge tool. Is 'merge' in your path?")
 221            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
 223                              src1]).rstrip()
 224            os.unlink(orig)
 226            os.unlink(src1)
 227            os.unlink(src2)
 228            clean = (code == 0)
 230        else:
 231            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
 232            sha = aSha
 233            if aSha != bSha:
 235                clean = False
 236    return [sha, mode, clean, merge]
 238def updateFile(clean, sha, mode, path):
 240    updateCache = cacheOnly or clean
 241    updateWd = not cacheOnly
 242    return updateFileExt(sha, mode, path, updateCache, updateWd)
 244def updateFileExt(sha, mode, path, updateCache, updateWd):
 246    if cacheOnly:
 247        updateWd = False
 248    if updateWd:
 250        pathComponents = path.split('/')
 251        for x in xrange(1, len(pathComponents)):
 252            p = '/'.join(pathComponents[0:x])
 253            try:
 255                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
 256            except OSError:
 257                createDir = True
 258            
 259            if createDir:
 260                try:
 261                    os.mkdir(p)
 262                except OSError, e:
 263                    die("Couldn't create directory", p, e.strerror)
 264        prog = ['git-cat-file', 'blob', sha]
 266        if stat.S_ISREG(mode):
 267            try:
 268                os.unlink(path)
 269            except OSError:
 270                pass
 271            if mode & 0100:
 272                mode = 0777
 273            else:
 274                mode = 0666
 275            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
 276            proc = subprocess.Popen(prog, stdout=fd)
 277            proc.wait()
 278            os.close(fd)
 279        elif stat.S_ISLNK(mode):
 280            linkTarget = runProgram(prog)
 281            os.symlink(linkTarget, path)
 282        else:
 283            assert(False)
 284    if updateWd and updateCache:
 286        runProgram(['git-update-index', '--add', '--', path])
 287    elif updateCache:
 288        runProgram(['git-update-index', '--add', '--cacheinfo',
 289                    '0%o' % mode, sha, path])
 290def setIndexStages(path,
 292                   oSHA1, oMode,
 293                   aSHA1, aMode,
 294                   bSHA1, bMode,
 295                   clear=True):
 296    istring = []
 297    if clear:
 298        istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
 299    if oMode:
 300        istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
 301    if aMode:
 302        istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
 303    if bMode:
 304        istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
 305    runProgram(['git-update-index', '-z', '--index-info'],
 307               input="".join(istring))
 308def removeFile(clean, path):
 310    updateCache = cacheOnly or clean
 311    updateWd = not cacheOnly
 312    if updateCache:
 314        runProgram(['git-update-index', '--force-remove', '--', path])
 315    if updateWd:
 317        try:
 318            os.unlink(path)
 319        except OSError, e:
 320            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
 321                raise
 322        try:
 323            os.removedirs(os.path.dirname(path))
 324        except OSError:
 325            pass
 326def uniquePath(path, branch):
 328    def fileExists(path):
 329        try:
 330            os.lstat(path)
 331            return True
 332        except OSError, e:
 333            if e.errno == errno.ENOENT:
 334                return False
 335            else:
 336                raise
 337    branch = branch.replace('/', '_')
 339    newPath = path + '~' + branch
 340    suffix = 0
 341    while newPath in currentFileSet or \
 342          newPath in currentDirectorySet  or \
 343          fileExists(newPath):
 344        suffix += 1
 345        newPath = path + '~' + branch + '_' + str(suffix)
 346    currentFileSet.add(newPath)
 347    return newPath
 348# Cache entry management
 350# ----------------------
 351class CacheEntry:
 353    def __init__(self, path):
 354        class Stage:
 355            def __init__(self):
 356                self.sha1 = None
 357                self.mode = None
 358            # Used for debugging only
 360            def __str__(self):
 361                if self.mode != None:
 362                    m = '0%o' % self.mode
 363                else:
 364                    m = 'None'
 365                if self.sha1:
 367                    sha1 = self.sha1
 368                else:
 369                    sha1 = 'None'
 370                return 'sha1: ' + sha1 + ' mode: ' + m
 371        
 372        self.stages = [Stage(), Stage(), Stage(), Stage()]
 373        self.path = path
 374        self.processed = False
 375    def __str__(self):
 377        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
 378class CacheEntryContainer:
 380    def __init__(self):
 381        self.entries = {}
 382    def add(self, entry):
 384        self.entries[entry.path] = entry
 385    def get(self, path):
 387        return self.entries.get(path)
 388    def __iter__(self):
 390        return self.entries.itervalues()
 391    
 392unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
 393def unmergedCacheEntries():
 394    '''Create a dictionary mapping file names to CacheEntry
 395    objects. The dictionary contains one entry for every path with a
 396    non-zero stage entry.'''
 397    lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
 399    lines.pop()
 400    res = CacheEntryContainer()
 402    for l in lines:
 403        m = unmergedRE.match(l)
 404        if m:
 405            mode = int(m.group(1), 8)
 406            sha1 = m.group(2)
 407            stage = int(m.group(3))
 408            path = m.group(4)
 409            e = res.get(path)
 411            if not e:
 412                e = CacheEntry(path)
 413                res.add(e)
 414            e.stages[stage].mode = mode
 416            e.stages[stage].sha1 = sha1
 417        else:
 418            die('Error: Merge program failed: Unexpected output from',
 419                'git-ls-files:', l)
 420    return res
 421lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
 423def getCacheEntry(path, origTree, aTree, bTree):
 424    '''Returns a CacheEntry object which doesn't have to correspond to
 425    a real cache entry in Git's index.'''
 426    
 427    def parse(out):
 428        if out == '':
 429            return [None, None]
 430        else:
 431            m = lsTreeRE.match(out)
 432            if not m:
 433                die('Unexpected output from git-ls-tree:', out)
 434            elif m.group(2) == 'blob':
 435                return [m.group(3), int(m.group(1), 8)]
 436            else:
 437                return [None, None]
 438    res = CacheEntry(path)
 440    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
 442    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
 443    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
 444    res.stages[1].sha1 = oSha
 446    res.stages[1].mode = oMode
 447    res.stages[2].sha1 = aSha
 448    res.stages[2].mode = aMode
 449    res.stages[3].sha1 = bSha
 450    res.stages[3].mode = bMode
 451    return res
 453# Rename detection and handling
 455# -----------------------------
 456class RenameEntry:
 458    def __init__(self,
 459                 src, srcSha, srcMode, srcCacheEntry,
 460                 dst, dstSha, dstMode, dstCacheEntry,
 461                 score):
 462        self.srcName = src
 463        self.srcSha = srcSha
 464        self.srcMode = srcMode
 465        self.srcCacheEntry = srcCacheEntry
 466        self.dstName = dst
 467        self.dstSha = dstSha
 468        self.dstMode = dstMode
 469        self.dstCacheEntry = dstCacheEntry
 470        self.score = score
 471        self.processed = False
 473class RenameEntryContainer:
 475    def __init__(self):
 476        self.entriesSrc = {}
 477        self.entriesDst = {}
 478    def add(self, entry):
 480        self.entriesSrc[entry.srcName] = entry
 481        self.entriesDst[entry.dstName] = entry
 482    def getSrc(self, path):
 484        return self.entriesSrc.get(path)
 485    def getDst(self, path):
 487        return self.entriesDst.get(path)
 488    def __iter__(self):
 490        return self.entriesSrc.itervalues()
 491parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
 493def getRenames(tree, oTree, aTree, bTree, cacheEntries):
 494    '''Get information of all renames which occured between 'oTree' and
 495    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
 496    'bTree') to be able to associate the correct cache entries with
 497    the rename information. 'tree' is always equal to either aTree or bTree.'''
 498    assert(tree == aTree or tree == bTree)
 500    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
 501                      '-z', oTree, tree])
 502    ret = RenameEntryContainer()
 504    try:
 505        recs = inp.split("\0")
 506        recs.pop() # remove last entry (which is '')
 507        it = recs.__iter__()
 508        while True:
 509            rec = it.next()
 510            m = parseDiffRenamesRE.match(rec)
 511            if not m:
 513                die('Unexpected output from git-diff-tree:', rec)
 514            srcMode = int(m.group(1), 8)
 516            dstMode = int(m.group(2), 8)
 517            srcSha = m.group(3)
 518            dstSha = m.group(4)
 519            score = m.group(5)
 520            src = it.next()
 521            dst = it.next()
 522            srcCacheEntry = cacheEntries.get(src)
 524            if not srcCacheEntry:
 525                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
 526                cacheEntries.add(srcCacheEntry)
 527            dstCacheEntry = cacheEntries.get(dst)
 529            if not dstCacheEntry:
 530                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
 531                cacheEntries.add(dstCacheEntry)
 532            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
 534                                dst, dstSha, dstMode, dstCacheEntry,
 535                                score))
 536    except StopIteration:
 537        pass
 538    return ret
 539def fmtRename(src, dst):
 541    srcPath = src.split('/')
 542    dstPath = dst.split('/')
 543    path = []
 544    endIndex = min(len(srcPath), len(dstPath)) - 1
 545    for x in range(0, endIndex):
 546        if srcPath[x] == dstPath[x]:
 547            path.append(srcPath[x])
 548        else:
 549            endIndex = x
 550            break
 551    if len(path) > 0:
 553        return '/'.join(path) + \
 554               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
 555               '/'.join(dstPath[endIndex:]) + '}'
 556    else:
 557        return src + ' => ' + dst
 558def processRenames(renamesA, renamesB, branchNameA, branchNameB):
 560    srcNames = Set()
 561    for x in renamesA:
 562        srcNames.add(x.srcName)
 563    for x in renamesB:
 564        srcNames.add(x.srcName)
 565    cleanMerge = True
 567    for path in srcNames:
 568        if renamesA.getSrc(path):
 569            renames1 = renamesA
 570            renames2 = renamesB
 571            branchName1 = branchNameA
 572            branchName2 = branchNameB
 573        else:
 574            renames1 = renamesB
 575            renames2 = renamesA
 576            branchName1 = branchNameB
 577            branchName2 = branchNameA
 578        
 579        ren1 = renames1.getSrc(path)
 580        ren2 = renames2.getSrc(path)
 581        ren1.dstCacheEntry.processed = True
 583        ren1.srcCacheEntry.processed = True
 584        if ren1.processed:
 586            continue
 587        ren1.processed = True
 589        if ren2:
 591            # Renamed in 1 and renamed in 2
 592            assert(ren1.srcName == ren2.srcName)
 593            ren2.dstCacheEntry.processed = True
 594            ren2.processed = True
 595            if ren1.dstName != ren2.dstName:
 597                output('CONFLICT (rename/rename): Rename',
 598                       fmtRename(path, ren1.dstName), 'in branch', branchName1,
 599                       'rename', fmtRename(path, ren2.dstName), 'in',
 600                       branchName2)
 601                cleanMerge = False
 602                if ren1.dstName in currentDirectorySet:
 604                    dstName1 = uniquePath(ren1.dstName, branchName1)
 605                    output(ren1.dstName, 'is a directory in', branchName2,
 606                           'adding as', dstName1, 'instead.')
 607                    removeFile(False, ren1.dstName)
 608                else:
 609                    dstName1 = ren1.dstName
 610                if ren2.dstName in currentDirectorySet:
 612                    dstName2 = uniquePath(ren2.dstName, branchName2)
 613                    output(ren2.dstName, 'is a directory in', branchName1,
 614                           'adding as', dstName2, 'instead.')
 615                    removeFile(False, ren2.dstName)
 616                else:
 617                    dstName2 = ren2.dstName
 618                setIndexStages(dstName1,
 619                               None, None,
 620                               ren1.dstSha, ren1.dstMode,
 621                               None, None)
 622                setIndexStages(dstName2,
 623                               None, None,
 624                               None, None,
 625                               ren2.dstSha, ren2.dstMode)
 626            else:
 628                removeFile(True, ren1.srcName)
 629                [resSha, resMode, clean, merge] = \
 631                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
 632                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
 633                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
 634                                   branchName1, branchName2)
 635                if merge or not clean:
 637                    output('Renaming', fmtRename(path, ren1.dstName))
 638                if merge:
 640                    output('Auto-merging', ren1.dstName)
 641                if not clean:
 643                    output('CONFLICT (content): merge conflict in',
 644                           ren1.dstName)
 645                    cleanMerge = False
 646                    if not cacheOnly:
 648                        setIndexStages(ren1.dstName,
 649                                       ren1.srcSha, ren1.srcMode,
 650                                       ren1.dstSha, ren1.dstMode,
 651                                       ren2.dstSha, ren2.dstMode)
 652                updateFile(clean, resSha, resMode, ren1.dstName)
 654        else:
 655            removeFile(True, ren1.srcName)
 656            # Renamed in 1, maybe changed in 2
 658            if renamesA == renames1:
 659                stage = 3
 660            else:
 661                stage = 2
 662                
 663            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
 664            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
 665            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
 667            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
 668            tryMerge = False
 670            
 671            if ren1.dstName in currentDirectorySet:
 672                newPath = uniquePath(ren1.dstName, branchName1)
 673                output('CONFLICT (rename/directory): Rename',
 674                       fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
 675                       'directory', ren1.dstName, 'added in', branchName2)
 676                output('Renaming', ren1.srcName, 'to', newPath, 'instead')
 677                cleanMerge = False
 678                removeFile(False, ren1.dstName)
 679                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
 680            elif srcShaOtherBranch == None:
 681                output('CONFLICT (rename/delete): Rename',
 682                       fmtRename(ren1.srcName, ren1.dstName), 'in',
 683                       branchName1, 'and deleted in', branchName2)
 684                cleanMerge = False
 685                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
 686            elif dstShaOtherBranch:
 687                newPath = uniquePath(ren1.dstName, branchName2)
 688                output('CONFLICT (rename/add): Rename',
 689                       fmtRename(ren1.srcName, ren1.dstName), 'in',
 690                       branchName1 + '.', ren1.dstName, 'added in', branchName2)
 691                output('Adding as', newPath, 'instead')
 692                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
 693                cleanMerge = False
 694                tryMerge = True
 695            elif renames2.getDst(ren1.dstName):
 696                dst2 = renames2.getDst(ren1.dstName)
 697                newPath1 = uniquePath(ren1.dstName, branchName1)
 698                newPath2 = uniquePath(dst2.dstName, branchName2)
 699                output('CONFLICT (rename/rename): Rename',
 700                       fmtRename(ren1.srcName, ren1.dstName), 'in',
 701                       branchName1+'. Rename',
 702                       fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
 703                output('Renaming', ren1.srcName, 'to', newPath1, 'and',
 704                       dst2.srcName, 'to', newPath2, 'instead')
 705                removeFile(False, ren1.dstName)
 706                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
 707                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
 708                dst2.processed = True
 709                cleanMerge = False
 710            else:
 711                tryMerge = True
 712            if tryMerge:
 714                oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
 716                aName, bName = ren1.dstName, ren1.srcName
 717                aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
 718                aMode, bMode = ren1.dstMode, srcModeOtherBranch
 719                aBranch, bBranch = branchName1, branchName2
 720                if renamesA != renames1:
 722                    aName, bName = bName, aName
 723                    aSHA1, bSHA1 = bSHA1, aSHA1
 724                    aMode, bMode = bMode, aMode
 725                    aBranch, bBranch = bBranch, aBranch
 726                [resSha, resMode, clean, merge] = \
 728                         mergeFile(oName, oSHA1, oMode,
 729                                   aName, aSHA1, aMode,
 730                                   bName, bSHA1, bMode,
 731                                   aBranch, bBranch);
 732                if merge or not clean:
 734                    output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
 735                if merge:
 737                    output('Auto-merging', ren1.dstName)
 738                if not clean:
 740                    output('CONFLICT (rename/modify): Merge conflict in',
 741                           ren1.dstName)
 742                    cleanMerge = False
 743                    if not cacheOnly:
 745                        setIndexStages(ren1.dstName,
 746                                       oSHA1, oMode,
 747                                       aSHA1, aMode,
 748                                       bSHA1, bMode)
 749                updateFile(clean, resSha, resMode, ren1.dstName)
 751    return cleanMerge
 753# Per entry merge function
 755# ------------------------
 756def processEntry(entry, branch1Name, branch2Name):
 758    '''Merge one cache entry.'''
 759    debug('processing', entry.path, 'clean cache:', cacheOnly)
 761    cleanMerge = True
 763    path = entry.path
 765    oSha = entry.stages[1].sha1
 766    oMode = entry.stages[1].mode
 767    aSha = entry.stages[2].sha1
 768    aMode = entry.stages[2].mode
 769    bSha = entry.stages[3].sha1
 770    bMode = entry.stages[3].mode
 771    assert(oSha == None or isSha(oSha))
 773    assert(aSha == None or isSha(aSha))
 774    assert(bSha == None or isSha(bSha))
 775    assert(oMode == None or type(oMode) is int)
 777    assert(aMode == None or type(aMode) is int)
 778    assert(bMode == None or type(bMode) is int)
 779    if (oSha and (not aSha or not bSha)):
 781    #
 782    # Case A: Deleted in one
 783    #
 784        if (not aSha     and not bSha) or \
 785           (aSha == oSha and not bSha) or \
 786           (not aSha     and bSha == oSha):
 787    # Deleted in both or deleted in one and unchanged in the other
 788            if aSha:
 789                output('Removing', path)
 790            removeFile(True, path)
 791        else:
 792    # Deleted in one and changed in the other
 793            cleanMerge = False
 794            if not aSha:
 795                output('CONFLICT (delete/modify):', path, 'deleted in',
 796                       branch1Name, 'and modified in', branch2Name + '.',
 797                       'Version', branch2Name, 'of', path, 'left in tree.')
 798                mode = bMode
 799                sha = bSha
 800            else:
 801                output('CONFLICT (modify/delete):', path, 'deleted in',
 802                       branch2Name, 'and modified in', branch1Name + '.',
 803                       'Version', branch1Name, 'of', path, 'left in tree.')
 804                mode = aMode
 805                sha = aSha
 806            updateFile(False, sha, mode, path)
 808    elif (not oSha and aSha     and not bSha) or \
 810         (not oSha and not aSha and bSha):
 811    #
 812    # Case B: Added in one.
 813    #
 814        if aSha:
 815            addBranch = branch1Name
 816            otherBranch = branch2Name
 817            mode = aMode
 818            sha = aSha
 819            conf = 'file/directory'
 820        else:
 821            addBranch = branch2Name
 822            otherBranch = branch1Name
 823            mode = bMode
 824            sha = bSha
 825            conf = 'directory/file'
 826    
 827        if path in currentDirectorySet:
 828            cleanMerge = False
 829            newPath = uniquePath(path, addBranch)
 830            output('CONFLICT (' + conf + '):',
 831                   'There is a directory with name', path, 'in',
 832                   otherBranch + '. Adding', path, 'as', newPath)
 833            removeFile(False, path)
 835            updateFile(False, sha, mode, newPath)
 836        else:
 837            output('Adding', path)
 838            updateFile(True, sha, mode, path)
 839    
 840    elif not oSha and aSha and bSha:
 841    #
 842    # Case C: Added in both (check for same permissions).
 843    #
 844        if aSha == bSha:
 845            if aMode != bMode:
 846                cleanMerge = False
 847                output('CONFLICT: File', path,
 848                       'added identically in both branches, but permissions',
 849                       'conflict', '0%o' % aMode, '->', '0%o' % bMode)
 850                output('CONFLICT: adding with permission:', '0%o' % aMode)
 851                updateFile(False, aSha, aMode, path)
 853            else:
 854                # This case is handled by git-read-tree
 855                assert(False)
 856        else:
 857            cleanMerge = False
 858            newPath1 = uniquePath(path, branch1Name)
 859            newPath2 = uniquePath(path, branch2Name)
 860            output('CONFLICT (add/add): File', path,
 861                   'added non-identically in both branches. Adding as',
 862                   newPath1, 'and', newPath2, 'instead.')
 863            removeFile(False, path)
 864            updateFile(False, aSha, aMode, newPath1)
 865            updateFile(False, bSha, bMode, newPath2)
 866    elif oSha and aSha and bSha:
 868    #
 869    # case D: Modified in both, but differently.
 870    #
 871        output('Auto-merging', path)
 872        [sha, mode, clean, dummy] = \
 873              mergeFile(path, oSha, oMode,
 874                        path, aSha, aMode,
 875                        path, bSha, bMode,
 876                        branch1Name, branch2Name)
 877        if clean:
 878            updateFile(True, sha, mode, path)
 879        else:
 880            cleanMerge = False
 881            output('CONFLICT (content): Merge conflict in', path)
 882            if cacheOnly:
 884                updateFile(False, sha, mode, path)
 885            else:
 886                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
 887    else:
 888        die("ERROR: Fatal merge failure, shouldn't happen.")
 889    return cleanMerge
 891def usage():
 893    die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
 894# main entry point as merge strategy module
 896# The first parameters up to -- are merge bases, and the rest are heads.
 897if len(sys.argv) < 4:
 899    usage()
 900bases = []
 902for nextArg in xrange(1, len(sys.argv)):
 903    if sys.argv[nextArg] == '--':
 904        if len(sys.argv) != nextArg + 3:
 905            die('Not handling anything other than two heads merge.')
 906        try:
 907            h1 = firstBranch = sys.argv[nextArg + 1]
 908            h2 = secondBranch = sys.argv[nextArg + 2]
 909        except IndexError:
 910            usage()
 911        break
 912    else:
 913        bases.append(sys.argv[nextArg])
 914print 'Merging', h1, 'with', h2
 916try:
 918    h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
 919    h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
 920    if len(bases) == 1:
 922        base = runProgram(['git-rev-parse', '--verify',
 923                           bases[0] + '^0']).rstrip()
 924        ancestor = Commit(base, None)
 925        [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
 926                               firstBranch, secondBranch, None, 0,
 927                               ancestor)
 928    else:
 929        graph = buildGraph([h1, h2])
 930        [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
 931                               firstBranch, secondBranch, graph)
 932    print ''
 934except:
 935    if isinstance(sys.exc_info()[1], SystemExit):
 936        raise
 937    else:
 938        traceback.print_exc(None, sys.stderr)
 939        sys.exit(2)
 940if clean:
 942    sys.exit(0)
 943else:
 944    sys.exit(1)