#!/usr/bin/python
+#
+# Copyright (C) 2005 Fredrik Kuivinen
+#
-import sys, math, random, os, re, signal, tempfile, stat, errno, traceback
+import sys
+sys.path.append('''@@GIT_PYTHON_PATH@@''')
+
+import math, random, os, re, signal, tempfile, stat, errno, traceback
from heapq import heappush, heappop
from sets import Set
-sys.path.append('''@@GIT_PYTHON_PATH@@''')
from gitMergeCommon import *
+outputIndent = 0
+def output(*args):
+ sys.stdout.write(' '*outputIndent)
+ printList(args)
+
originalIndexFile = os.environ.get('GIT_INDEX_FILE',
os.environ.get('GIT_DIR', '.git') + '/index')
temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
# The entry point to the merge code
# ---------------------------------
-def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
+def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
'''Merge the commits h1 and h2, return the resulting virtual
commit object and a flag indicating the cleaness of the merge.'''
assert(isinstance(h1, Commit) and isinstance(h2, Commit))
- assert(isinstance(graph, Graph))
- def infoMsg(*args):
- sys.stdout.write(' '*callDepth)
- printList(args)
+ global outputIndent
- infoMsg('Merging:')
- infoMsg(h1)
- infoMsg(h2)
+ output('Merging:')
+ output(h1)
+ output(h2)
sys.stdout.flush()
- ca = getCommonAncestors(graph, h1, h2)
- infoMsg('found', len(ca), 'common ancestor(s):')
+ if ancestor:
+ ca = [ancestor]
+ else:
+ assert(isinstance(graph, Graph))
+ ca = getCommonAncestors(graph, h1, h2)
+ output('found', len(ca), 'common ancestor(s):')
for x in ca:
- infoMsg(x)
+ output(x)
sys.stdout.flush()
mergedCA = ca[0]
for h in ca[1:]:
+ outputIndent = callDepth+1
[mergedCA, dummy] = merge(mergedCA, h,
- 'Temporary shared merge branch 1',
- 'Temporary shared merge branch 2',
+ 'Temporary merge branch 1',
+ 'Temporary merge branch 2',
graph, callDepth+1)
+ outputIndent = callDepth
assert(isinstance(mergedCA, Commit))
global cacheOnly
[shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
branch1Name, branch2Name)
- if clean or cacheOnly:
+ if graph and (clean or cacheOnly):
res = Commit(None, [h1, h2], tree=shaRes)
graph.addNode(res)
else:
def getFilesAndDirs(tree):
files = Set()
dirs = Set()
- out = runProgram(['git-ls-tree', '-r', '-z', tree])
+ out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
for l in out.split('\0'):
m = getFilesRE.match(l)
if m:
assert(isSha(head) and isSha(merge) and isSha(common))
if common == merge:
- print 'Already uptodate!'
+ output('Already uptodate!')
return [head, True]
if cacheOnly:
# Low level file merging, update and removal
# ------------------------------------------
-MERGE_NONE = 0
-MERGE_TRIVIAL = 1
-MERGE_3WAY = 2
def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
branch1Name, branch2Name):
- merge = MERGE_NONE
+ merge = False
clean = True
if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
sha = bSha
else:
if aSha != oSha and bSha != oSha:
- merge = MERGE_TRIVIAL
+ merge = True
if aMode == oMode:
mode = bMode
orig = runProgram(['git-unpack-file', oSha]).rstrip()
src1 = runProgram(['git-unpack-file', aSha]).rstrip()
src2 = runProgram(['git-unpack-file', bSha]).rstrip()
- [out, code] = runProgram(['merge',
- '-L', branch1Name + '/' + aPath,
- '-L', 'orig/' + oPath,
- '-L', branch2Name + '/' + bPath,
- src1, orig, src2], returnCode=True)
+ try:
+ [out, code] = runProgram(['merge',
+ '-L', branch1Name + '/' + aPath,
+ '-L', 'orig/' + oPath,
+ '-L', branch2Name + '/' + bPath,
+ src1, orig, src2], returnCode=True)
+ except ProgramError, e:
+ print >>sys.stderr, e
+ die("Failed to execute 'merge'. merge(1) is used as the "
+ "file-level merge tool. Is 'merge' in your path?")
sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
src1]).rstrip()
os.unlink(src1)
os.unlink(src2)
- merge = MERGE_3WAY
clean = (code == 0)
else:
assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
try:
createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
- except:
+ except OSError:
createDir = True
if createDir:
runProgram(['git-update-index', '--add', '--cacheinfo',
'0%o' % mode, sha, path])
+def setIndexStages(path,
+ oSHA1, oMode,
+ aSHA1, aMode,
+ bSHA1, bMode,
+ clear=True):
+ istring = []
+ if clear:
+ istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
+ if oMode:
+ istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
+ if aMode:
+ istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
+ if bMode:
+ istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
+
+ runProgram(['git-update-index', '-z', '--index-info'],
+ input="".join(istring))
+
def removeFile(clean, path):
updateCache = cacheOnly or clean
updateWd = not cacheOnly
except OSError, e:
if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
raise
+ try:
+ os.removedirs(os.path.dirname(path))
+ except OSError:
+ pass
def uniquePath(path, branch):
def fileExists(path):
else:
raise
- newPath = path + '_' + branch
+ branch = branch.replace('/', '_')
+ newPath = path + '~' + branch
suffix = 0
while newPath in currentFileSet or \
newPath in currentDirectorySet or \
fileExists(newPath):
suffix += 1
- newPath = path + '_' + branch + '_' + str(suffix)
+ newPath = path + '~' + branch + '_' + str(suffix)
currentFileSet.add(newPath)
return newPath
continue
ren1.processed = True
- removeFile(True, ren1.srcName)
+
if ren2:
# Renamed in 1 and renamed in 2
assert(ren1.srcName == ren2.srcName)
ren2.processed = True
if ren1.dstName != ren2.dstName:
- print 'CONFLICT (rename/rename): Rename', \
- fmtRename(path, ren1.dstName), 'in branch', branchName1, \
- 'rename', fmtRename(path, ren2.dstName), 'in', branchName2
+ output('CONFLICT (rename/rename): Rename',
+ fmtRename(path, ren1.dstName), 'in branch', branchName1,
+ 'rename', fmtRename(path, ren2.dstName), 'in',
+ branchName2)
cleanMerge = False
if ren1.dstName in currentDirectorySet:
dstName1 = uniquePath(ren1.dstName, branchName1)
- print ren1.dstName, 'is a directory in', branchName2, \
- 'adding as', dstName1, 'instead.'
+ output(ren1.dstName, 'is a directory in', branchName2,
+ 'adding as', dstName1, 'instead.')
removeFile(False, ren1.dstName)
else:
dstName1 = ren1.dstName
if ren2.dstName in currentDirectorySet:
dstName2 = uniquePath(ren2.dstName, branchName2)
- print ren2.dstName, 'is a directory in', branchName1, \
- 'adding as', dstName2, 'instead.'
+ output(ren2.dstName, 'is a directory in', branchName1,
+ 'adding as', dstName2, 'instead.')
removeFile(False, ren2.dstName)
else:
- dstName2 = ren1.dstName
+ dstName2 = ren2.dstName
+ setIndexStages(dstName1,
+ None, None,
+ ren1.dstSha, ren1.dstMode,
+ None, None)
+ setIndexStages(dstName2,
+ None, None,
+ None, None,
+ ren2.dstSha, ren2.dstMode)
- updateFile(False, ren1.dstSha, ren1.dstMode, dstName1)
- updateFile(False, ren2.dstSha, ren2.dstMode, dstName2)
else:
+ removeFile(True, ren1.srcName)
+
[resSha, resMode, clean, merge] = \
mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
ren1.dstName, ren1.dstSha, ren1.dstMode,
branchName1, branchName2)
if merge or not clean:
- print 'Renaming', fmtRename(path, ren1.dstName)
+ output('Renaming', fmtRename(path, ren1.dstName))
- if merge == MERGE_3WAY:
- print 'Auto-merging', ren1.dstName
+ if merge:
+ output('Auto-merging', ren1.dstName)
if not clean:
- print 'CONFLICT (content): merge conflict in', ren1.dstName
+ output('CONFLICT (content): merge conflict in',
+ ren1.dstName)
cleanMerge = False
if not cacheOnly:
- updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
- updateCache=True, updateWd=False)
+ setIndexStages(ren1.dstName,
+ ren1.srcSha, ren1.srcMode,
+ ren1.dstSha, ren1.dstMode,
+ ren2.dstSha, ren2.dstMode)
+
updateFile(clean, resSha, resMode, ren1.dstName)
else:
+ removeFile(True, ren1.srcName)
+
# Renamed in 1, maybe changed in 2
if renamesA == renames1:
stage = 3
if ren1.dstName in currentDirectorySet:
newPath = uniquePath(ren1.dstName, branchName1)
- print 'CONFLICT (rename/directory): Rename', \
- fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,\
- 'directory', ren1.dstName, 'added in', branchName2
- print 'Renaming', ren1.srcName, 'to', newPath, 'instead'
+ output('CONFLICT (rename/directory): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
+ 'directory', ren1.dstName, 'added in', branchName2)
+ output('Renaming', ren1.srcName, 'to', newPath, 'instead')
cleanMerge = False
removeFile(False, ren1.dstName)
updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
elif srcShaOtherBranch == None:
- print 'CONFLICT (rename/delete): Rename', \
- fmtRename(ren1.srcName, ren1.dstName), 'in', \
- branchName1, 'and deleted in', branchName2
+ output('CONFLICT (rename/delete): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1, 'and deleted in', branchName2)
cleanMerge = False
updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
elif dstShaOtherBranch:
newPath = uniquePath(ren1.dstName, branchName2)
- print 'CONFLICT (rename/add): Rename', \
- fmtRename(ren1.srcName, ren1.dstName), 'in', \
- branchName1 + '.', ren1.dstName, 'added in', branchName2
- print 'Adding as', newPath, 'instead'
+ output('CONFLICT (rename/add): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1 + '.', ren1.dstName, 'added in', branchName2)
+ output('Adding as', newPath, 'instead')
updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
cleanMerge = False
tryMerge = True
dst2 = renames2.getDst(ren1.dstName)
newPath1 = uniquePath(ren1.dstName, branchName1)
newPath2 = uniquePath(dst2.dstName, branchName2)
- print 'CONFLICT (rename/rename): Rename', \
- fmtRename(ren1.srcName, ren1.dstName), 'in', \
- branchName1+'. Rename', \
- fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2
- print 'Renaming', ren1.srcName, 'to', newPath1, 'and', \
- dst2.srcName, 'to', newPath2, 'instead'
+ output('CONFLICT (rename/rename): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1+'. Rename',
+ fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
+ output('Renaming', ren1.srcName, 'to', newPath1, 'and',
+ dst2.srcName, 'to', newPath2, 'instead')
removeFile(False, ren1.dstName)
updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
tryMerge = True
if tryMerge:
+
+ oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
+ aName, bName = ren1.dstName, ren1.srcName
+ aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
+ aMode, bMode = ren1.dstMode, srcModeOtherBranch
+ aBranch, bBranch = branchName1, branchName2
+
+ if renamesA != renames1:
+ aName, bName = bName, aName
+ aSHA1, bSHA1 = bSHA1, aSHA1
+ aMode, bMode = bMode, aMode
+ aBranch, bBranch = bBranch, aBranch
+
[resSha, resMode, clean, merge] = \
- mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
- ren1.dstName, ren1.dstSha, ren1.dstMode,
- ren1.srcName, srcShaOtherBranch, srcModeOtherBranch,
- branchName1, branchName2)
+ mergeFile(oName, oSHA1, oMode,
+ aName, aSHA1, aMode,
+ bName, bSHA1, bMode,
+ aBranch, bBranch);
if merge or not clean:
- print 'Renaming', fmtRename(ren1.srcName, ren1.dstName)
+ output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
- if merge == MERGE_3WAY:
- print 'Auto-merging', ren1.dstName
+ if merge:
+ output('Auto-merging', ren1.dstName)
if not clean:
- print 'CONFLICT (rename/modify): Merge conflict in', ren1.dstName
+ output('CONFLICT (rename/modify): Merge conflict in',
+ ren1.dstName)
cleanMerge = False
if not cacheOnly:
- updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
- updateCache=True, updateWd=False)
+ setIndexStages(ren1.dstName,
+ oSHA1, oMode,
+ aSHA1, aMode,
+ bSHA1, bMode)
+
updateFile(clean, resSha, resMode, ren1.dstName)
return cleanMerge
(not aSha and bSha == oSha):
# Deleted in both or deleted in one and unchanged in the other
if aSha:
- print 'Removing', path
+ output('Removing', path)
removeFile(True, path)
else:
# Deleted in one and changed in the other
cleanMerge = False
if not aSha:
- print 'CONFLICT (delete/modify):', path, 'deleted in', \
- branch1Name, 'and modified in', branch2Name + '.', \
- 'Version', branch2Name, 'of', path, 'left in tree.'
+ output('CONFLICT (delete/modify):', path, 'deleted in',
+ branch1Name, 'and modified in', branch2Name + '.',
+ 'Version', branch2Name, 'of', path, 'left in tree.')
mode = bMode
sha = bSha
else:
- print 'CONFLICT (modify/delete):', path, 'deleted in', \
- branch2Name, 'and modified in', branch1Name + '.', \
- 'Version', branch1Name, 'of', path, 'left in tree.'
+ output('CONFLICT (modify/delete):', path, 'deleted in',
+ branch2Name, 'and modified in', branch1Name + '.',
+ 'Version', branch1Name, 'of', path, 'left in tree.')
mode = aMode
sha = aSha
if path in currentDirectorySet:
cleanMerge = False
newPath = uniquePath(path, addBranch)
- print 'CONFLICT (' + conf + '):', \
- 'There is a directory with name', path, 'in', \
- otherBranch + '. Adding', path, 'as', newPath
+ output('CONFLICT (' + conf + '):',
+ 'There is a directory with name', path, 'in',
+ otherBranch + '. Adding', path, 'as', newPath)
removeFile(False, path)
updateFile(False, sha, mode, newPath)
else:
- print 'Adding', path
+ output('Adding', path)
updateFile(True, sha, mode, path)
elif not oSha and aSha and bSha:
if aSha == bSha:
if aMode != bMode:
cleanMerge = False
- print 'CONFLICT: File', path, \
- 'added identically in both branches, but permissions', \
- 'conflict', '0%o' % aMode, '->', '0%o' % bMode
- print 'CONFLICT: adding with permission:', '0%o' % aMode
+ output('CONFLICT: File', path,
+ 'added identically in both branches, but permissions',
+ 'conflict', '0%o' % aMode, '->', '0%o' % bMode)
+ output('CONFLICT: adding with permission:', '0%o' % aMode)
updateFile(False, aSha, aMode, path)
else:
cleanMerge = False
newPath1 = uniquePath(path, branch1Name)
newPath2 = uniquePath(path, branch2Name)
- print 'CONFLICT (add/add): File', path, \
- 'added non-identically in both branches. Adding as', \
- newPath1, 'and', newPath2, 'instead.'
+ output('CONFLICT (add/add): File', path,
+ 'added non-identically in both branches. Adding as',
+ newPath1, 'and', newPath2, 'instead.')
removeFile(False, path)
updateFile(False, aSha, aMode, newPath1)
updateFile(False, bSha, bMode, newPath2)
#
# case D: Modified in both, but differently.
#
- print 'Auto-merging', path
+ output('Auto-merging', path)
[sha, mode, clean, dummy] = \
mergeFile(path, oSha, oMode,
path, aSha, aMode,
updateFile(True, sha, mode, path)
else:
cleanMerge = False
- print 'CONFLICT (content): Merge conflict in', path
+ output('CONFLICT (content): Merge conflict in', path)
if cacheOnly:
updateFile(False, sha, mode, path)
else:
- updateFileExt(aSha, aMode, path,
- updateCache=True, updateWd=False)
updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
else:
die("ERROR: Fatal merge failure, shouldn't happen.")
# main entry point as merge strategy module
# The first parameters up to -- are merge bases, and the rest are heads.
-# This strategy module figures out merge bases itself, so we only
-# get heads.
if len(sys.argv) < 4:
usage()
+bases = []
for nextArg in xrange(1, len(sys.argv)):
if sys.argv[nextArg] == '--':
if len(sys.argv) != nextArg + 3:
except IndexError:
usage()
break
+ else:
+ bases.append(sys.argv[nextArg])
print 'Merging', h1, 'with', h2
h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
- graph = buildGraph([h1, h2])
-
- [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
- firstBranch, secondBranch, graph)
+ if len(bases) == 1:
+ base = runProgram(['git-rev-parse', '--verify',
+ bases[0] + '^0']).rstrip()
+ ancestor = Commit(base, None)
+ [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
+ firstBranch, secondBranch, None, 0,
+ ancestor)
+ else:
+ graph = buildGraph([h1, h2])
+ [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
+ firstBranch, secondBranch, graph)
print ''
except: