From: Fredrik Kuivinen Date: Tue, 13 Sep 2005 06:22:26 +0000 (+0200) Subject: [PATCH] Rename the 'fredrik' merge strategy to 'recursive'. X-Git-Tag: v0.99.7~54 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=e4cf17ce0db2dab7c9525a732f86c5e3df3b4ed0;p=git.git [PATCH] Rename the 'fredrik' merge strategy to 'recursive'. Otherwise we would regret when Fredrik comes up with another merge algorithm with different pros-and-cons with the current one. Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- diff --git a/.gitignore b/.gitignore index cc2b29ebf..31c03f468 100644 --- a/.gitignore +++ b/.gitignore @@ -44,10 +44,10 @@ git-mailinfo git-mailsplit git-merge git-merge-base -git-merge-fredrik git-merge-index git-merge-octopus git-merge-one-file +git-merge-recursive git-merge-resolve git-merge-stupid git-mktag diff --git a/Makefile b/Makefile index 9c2bdd642..a851f5646 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ SCRIPT_PERL = \ git-rename.perl git-shortlog.perl SCRIPT_PYTHON = \ - git-merge-fredrik.py + git-merge-recursive.py # The ones that do not have to link with lcrypto nor lz. SIMPLE_PROGRAMS = \ diff --git a/git-merge-fredrik.py b/git-merge-fredrik.py deleted file mode 100755 index 27b84867b..000000000 --- a/git-merge-fredrik.py +++ /dev/null @@ -1,429 +0,0 @@ -#!/usr/bin/python - -import sys, 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 * - -alwaysWriteTree = False - -# The actual merge code -# --------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): - '''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) - infoMsg('Merging:') - infoMsg(h1) - infoMsg(h2) - sys.stdout.flush() - - ca = getCommonAncestors(graph, h1, h2) - infoMsg('found', len(ca), 'common ancestor(s):') - for x in ca: - infoMsg(x) - sys.stdout.flush() - - Ms = ca[0] - for h in ca[1:]: - [Ms, ignore] = merge(Ms, h, - 'Temporary shared merge branch 1', - 'Temporary shared merge branch 2', - graph, callDepth+1) - assert(isinstance(Ms, Commit)) - - if callDepth == 0: - if len(ca) > 1: - runProgram(['git-read-tree', h1.tree()]) - runProgram(['git-update-cache', '-q', '--refresh']) - # Use the original index if we only have one common ancestor - - updateWd = True - if alwaysWriteTree: - cleanCache = True - else: - cleanCache = False - else: - runProgram(['git-read-tree', h1.tree()]) - updateWd = False - cleanCache = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(), - branch1Name, branch2Name, - cleanCache, updateWd) - - if clean or cleanCache: - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile('([0-9]+) ([a-z0-9]+) ([0-9a-f]{40})\t(.*)') -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - self.stages = [Stage(), Stage(), Stage()] - self.path = path - -unmergedRE = re.compile('^([0-9]+) ([0-9a-f]{40}) ([1-3])\t(.*)$') -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = {} - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - 1 - path = m.group(4) - - if res.has_key(path): - e = res[path] - else: - e = CacheEntry(path) - res[path] = e - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', \ - 'git-ls-files:', l) - return res - -def mergeTrees(head, merge, common, branch1Name, branch2Name, - cleanCache, updateWd): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - print 'Already uptodate!' - return [head, True] - - if updateWd: - updateArg = '-u' - else: - updateArg = '-i' - runProgram(['git-read-tree', updateArg, '-m', common, head, merge]) - cleanMerge = True - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - [files, dirs] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - files.union_update(filesM) - dirs.union_update(dirsM) - - cleanMerge = True - entries = unmergedCacheEntries() - for name in entries: - if not processEntry(entries[name], branch1Name, branch2Name, - files, dirs, cleanCache, updateWd): - cleanMerge = False - - if cleanMerge or cleanCache: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -def processEntry(entry, branch1Name, branch2Name, files, dirs, - cleanCache, updateWd): - '''Merge one cache entry. 'files' is a Set with the files in both of - the heads that we are going to merge. 'dirs' contains the - corresponding data for directories. If 'cleanCache' is True no - non-zero stages will be left in the cache for the path - corresponding to the entry 'entry'.''' - -# cleanCache == True => Don't leave any non-stage 0 entries in the cache. -# False => Leave unmerged entries - -# updateWd == True => Update the working directory to correspond to the cache -# False => Leave the working directory unchanged - -# clean == True => non-conflict case -# False => conflict case - -# If cleanCache == False then the cache shouldn't be updated if clean == False - - def updateFile(clean, sha, mode, path): - if cleanCache or (not cleanCache and clean): - runProgram(['git-update-cache', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - - if updateWd: - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - runProgram(['git-update-cache', '--', path]) - - def removeFile(clean, path): - if cleanCache or (not cleanCache and clean): - runProgram(['git-update-cache', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - - def uniquePath(path, branch): - newPath = path + '_' + branch - suffix = 0 - while newPath in files or newPath in dirs: - suffix += 1 - newPath = path + '_' + branch + '_' + str(suffix) - files.add(newPath) - return newPath - - debug('processing', entry.path, 'clean cache:', cleanCache, - 'wd:', updateWd) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[0].sha1 - oMode = entry.stages[0].mode - aSha = entry.stages[1].sha1 - aMode = entry.stages[1].mode - bSha = entry.stages[2].sha1 - bMode = entry.stages[2].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - print 'Removing ' + path - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - print 'CONFLICT (del/mod): "' + path + '" deleted in', \ - branch1Name, 'and modified in', branch2Name, \ - '. Version', branch2Name, ' of "' + path + \ - '" left in tree' - mode = bMode - sha = bSha - else: - print 'CONFLICT (mod/del): "' + path + '" deleted in', \ - branch2Name, 'and modified in', branch1Name + \ - '. Version', branch1Name, 'of "' + path + \ - '" left in tree' - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/dir' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'dir/file' - - if path in dirs: - cleanMerge = False - newPath = uniquePath(path, addBranch) - print 'CONFLICT (' + conf + \ - '): There is a directory with name "' + path + '" in', \ - otherBranch + '. Adding "' + path + '" as "' + newPath + '"' - - removeFile(False, path) - path = newPath - else: - print 'Adding "' + path + '"' - - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - print 'CONFLICT: File "' + path + \ - '" added identically in both branches,' - print 'CONFLICT: but permissions conflict', '0%o' % aMode, \ - '->', '0%o' % bMode - print 'CONFLICT: adding with permission:', '0%o' % aMode - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - print 'CONFLICT (add/add): File "' + path + \ - '" added non-identically in both branches.', \ - 'Adding "' + newPath1 + '" and "' + newPath2 + '" instead.' - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - print 'Auto-merging', path - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - [out, ret] = runProgram(['merge', - '-L', branch1Name + '/' + path, - '-L', 'orig/' + path, - '-L', branch2Name + '/' + path, - src1, orig, src2], returnCode=True) - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - if ret != 0: - cleanMerge = False - print 'CONFLICT (content): Merge conflict in "' + path + '".' - updateFile(False, sha, mode, path) - else: - updateFile(True, sha, mode, path) - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' ... -- ..') - -# 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() - -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - graph = buildGraph([h1, h2]) - - [res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - print 'Automatic merge failed, fix up by hand' - sys.exit(1) diff --git a/git-merge-recursive.py b/git-merge-recursive.py new file mode 100755 index 000000000..27b84867b --- /dev/null +++ b/git-merge-recursive.py @@ -0,0 +1,429 @@ +#!/usr/bin/python + +import sys, 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 * + +alwaysWriteTree = False + +# The actual merge code +# --------------------- + +def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0): + '''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) + infoMsg('Merging:') + infoMsg(h1) + infoMsg(h2) + sys.stdout.flush() + + ca = getCommonAncestors(graph, h1, h2) + infoMsg('found', len(ca), 'common ancestor(s):') + for x in ca: + infoMsg(x) + sys.stdout.flush() + + Ms = ca[0] + for h in ca[1:]: + [Ms, ignore] = merge(Ms, h, + 'Temporary shared merge branch 1', + 'Temporary shared merge branch 2', + graph, callDepth+1) + assert(isinstance(Ms, Commit)) + + if callDepth == 0: + if len(ca) > 1: + runProgram(['git-read-tree', h1.tree()]) + runProgram(['git-update-cache', '-q', '--refresh']) + # Use the original index if we only have one common ancestor + + updateWd = True + if alwaysWriteTree: + cleanCache = True + else: + cleanCache = False + else: + runProgram(['git-read-tree', h1.tree()]) + updateWd = False + cleanCache = True + + [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), Ms.tree(), + branch1Name, branch2Name, + cleanCache, updateWd) + + if clean or cleanCache: + res = Commit(None, [h1, h2], tree=shaRes) + graph.addNode(res) + else: + res = None + + return [res, clean] + +getFilesRE = re.compile('([0-9]+) ([a-z0-9]+) ([0-9a-f]{40})\t(.*)') +def getFilesAndDirs(tree): + files = Set() + dirs = Set() + out = runProgram(['git-ls-tree', '-r', '-z', tree]) + for l in out.split('\0'): + m = getFilesRE.match(l) + if m: + if m.group(2) == 'tree': + dirs.add(m.group(4)) + elif m.group(2) == 'blob': + files.add(m.group(4)) + + return [files, dirs] + +class CacheEntry: + def __init__(self, path): + class Stage: + def __init__(self): + self.sha1 = None + self.mode = None + + self.stages = [Stage(), Stage(), Stage()] + self.path = path + +unmergedRE = re.compile('^([0-9]+) ([0-9a-f]{40}) ([1-3])\t(.*)$') +def unmergedCacheEntries(): + '''Create a dictionary mapping file names to CacheEntry + objects. The dictionary contains one entry for every path with a + non-zero stage entry.''' + + lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') + lines.pop() + + res = {} + for l in lines: + m = unmergedRE.match(l) + if m: + mode = int(m.group(1), 8) + sha1 = m.group(2) + stage = int(m.group(3)) - 1 + path = m.group(4) + + if res.has_key(path): + e = res[path] + else: + e = CacheEntry(path) + res[path] = e + + e.stages[stage].mode = mode + e.stages[stage].sha1 = sha1 + else: + die('Error: Merge program failed: Unexpected output from', \ + 'git-ls-files:', l) + return res + +def mergeTrees(head, merge, common, branch1Name, branch2Name, + cleanCache, updateWd): + '''Merge the trees 'head' and 'merge' with the common ancestor + 'common'. The name of the head branch is 'branch1Name' and the name of + the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) + where tree is the resulting tree and cleanMerge is True iff the + merge was clean.''' + + assert(isSha(head) and isSha(merge) and isSha(common)) + + if common == merge: + print 'Already uptodate!' + return [head, True] + + if updateWd: + updateArg = '-u' + else: + updateArg = '-i' + runProgram(['git-read-tree', updateArg, '-m', common, head, merge]) + cleanMerge = True + + [tree, code] = runProgram('git-write-tree', returnCode=True) + tree = tree.rstrip() + if code != 0: + [files, dirs] = getFilesAndDirs(head) + [filesM, dirsM] = getFilesAndDirs(merge) + files.union_update(filesM) + dirs.union_update(dirsM) + + cleanMerge = True + entries = unmergedCacheEntries() + for name in entries: + if not processEntry(entries[name], branch1Name, branch2Name, + files, dirs, cleanCache, updateWd): + cleanMerge = False + + if cleanMerge or cleanCache: + tree = runProgram('git-write-tree').rstrip() + else: + tree = None + else: + cleanMerge = True + + return [tree, cleanMerge] + +def processEntry(entry, branch1Name, branch2Name, files, dirs, + cleanCache, updateWd): + '''Merge one cache entry. 'files' is a Set with the files in both of + the heads that we are going to merge. 'dirs' contains the + corresponding data for directories. If 'cleanCache' is True no + non-zero stages will be left in the cache for the path + corresponding to the entry 'entry'.''' + +# cleanCache == True => Don't leave any non-stage 0 entries in the cache. +# False => Leave unmerged entries + +# updateWd == True => Update the working directory to correspond to the cache +# False => Leave the working directory unchanged + +# clean == True => non-conflict case +# False => conflict case + +# If cleanCache == False then the cache shouldn't be updated if clean == False + + def updateFile(clean, sha, mode, path): + if cleanCache or (not cleanCache and clean): + runProgram(['git-update-cache', '--add', '--cacheinfo', + '0%o' % mode, sha, path]) + + if updateWd: + prog = ['git-cat-file', 'blob', sha] + if stat.S_ISREG(mode): + try: + os.unlink(path) + except OSError: + pass + if mode & 0100: + mode = 0777 + else: + mode = 0666 + fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) + proc = subprocess.Popen(prog, stdout=fd) + proc.wait() + os.close(fd) + elif stat.S_ISLNK(mode): + linkTarget = runProgram(prog) + os.symlink(linkTarget, path) + else: + assert(False) + runProgram(['git-update-cache', '--', path]) + + def removeFile(clean, path): + if cleanCache or (not cleanCache and clean): + runProgram(['git-update-cache', '--force-remove', '--', path]) + + if updateWd: + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT and e.errno != errno.EISDIR: + raise + + def uniquePath(path, branch): + newPath = path + '_' + branch + suffix = 0 + while newPath in files or newPath in dirs: + suffix += 1 + newPath = path + '_' + branch + '_' + str(suffix) + files.add(newPath) + return newPath + + debug('processing', entry.path, 'clean cache:', cleanCache, + 'wd:', updateWd) + + cleanMerge = True + + path = entry.path + oSha = entry.stages[0].sha1 + oMode = entry.stages[0].mode + aSha = entry.stages[1].sha1 + aMode = entry.stages[1].mode + bSha = entry.stages[2].sha1 + bMode = entry.stages[2].mode + + assert(oSha == None or isSha(oSha)) + assert(aSha == None or isSha(aSha)) + assert(bSha == None or isSha(bSha)) + + assert(oMode == None or type(oMode) is int) + assert(aMode == None or type(aMode) is int) + assert(bMode == None or type(bMode) is int) + + if (oSha and (not aSha or not bSha)): + # + # Case A: Deleted in one + # + if (not aSha and not bSha) or \ + (aSha == oSha and not bSha) or \ + (not aSha and bSha == oSha): + # Deleted in both or deleted in one and unchanged in the other + if aSha: + print 'Removing ' + path + removeFile(True, path) + else: + # Deleted in one and changed in the other + cleanMerge = False + if not aSha: + print 'CONFLICT (del/mod): "' + path + '" deleted in', \ + branch1Name, 'and modified in', branch2Name, \ + '. Version', branch2Name, ' of "' + path + \ + '" left in tree' + mode = bMode + sha = bSha + else: + print 'CONFLICT (mod/del): "' + path + '" deleted in', \ + branch2Name, 'and modified in', branch1Name + \ + '. Version', branch1Name, 'of "' + path + \ + '" left in tree' + mode = aMode + sha = aSha + + updateFile(False, sha, mode, path) + + elif (not oSha and aSha and not bSha) or \ + (not oSha and not aSha and bSha): + # + # Case B: Added in one. + # + if aSha: + addBranch = branch1Name + otherBranch = branch2Name + mode = aMode + sha = aSha + conf = 'file/dir' + else: + addBranch = branch2Name + otherBranch = branch1Name + mode = bMode + sha = bSha + conf = 'dir/file' + + if path in dirs: + cleanMerge = False + newPath = uniquePath(path, addBranch) + print 'CONFLICT (' + conf + \ + '): There is a directory with name "' + path + '" in', \ + otherBranch + '. Adding "' + path + '" as "' + newPath + '"' + + removeFile(False, path) + path = newPath + else: + print 'Adding "' + path + '"' + + updateFile(True, sha, mode, path) + + elif not oSha and aSha and bSha: + # + # Case C: Added in both (check for same permissions). + # + if aSha == bSha: + if aMode != bMode: + cleanMerge = False + print 'CONFLICT: File "' + path + \ + '" added identically in both branches,' + print 'CONFLICT: but permissions conflict', '0%o' % aMode, \ + '->', '0%o' % bMode + print 'CONFLICT: adding with permission:', '0%o' % aMode + + updateFile(False, aSha, aMode, path) + else: + # This case is handled by git-read-tree + assert(False) + else: + cleanMerge = False + newPath1 = uniquePath(path, branch1Name) + newPath2 = uniquePath(path, branch2Name) + print 'CONFLICT (add/add): File "' + path + \ + '" added non-identically in both branches.', \ + 'Adding "' + newPath1 + '" and "' + newPath2 + '" instead.' + removeFile(False, path) + updateFile(False, aSha, aMode, newPath1) + updateFile(False, bSha, bMode, newPath2) + + elif oSha and aSha and bSha: + # + # case D: Modified in both, but differently. + # + print 'Auto-merging', path + orig = runProgram(['git-unpack-file', oSha]).rstrip() + src1 = runProgram(['git-unpack-file', aSha]).rstrip() + src2 = runProgram(['git-unpack-file', bSha]).rstrip() + [out, ret] = runProgram(['merge', + '-L', branch1Name + '/' + path, + '-L', 'orig/' + path, + '-L', branch2Name + '/' + path, + src1, orig, src2], returnCode=True) + + if aMode == oMode: + mode = bMode + else: + mode = aMode + + sha = runProgram(['git-hash-object', '-t', 'blob', '-w', + src1]).rstrip() + + if ret != 0: + cleanMerge = False + print 'CONFLICT (content): Merge conflict in "' + path + '".' + updateFile(False, sha, mode, path) + else: + updateFile(True, sha, mode, path) + + os.unlink(orig) + os.unlink(src1) + os.unlink(src2) + else: + die("ERROR: Fatal merge failure, shouldn't happen.") + + return cleanMerge + +def usage(): + die('Usage:', sys.argv[0], ' ... -- ..') + +# 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() + +for nextArg in xrange(1, len(sys.argv)): + if sys.argv[nextArg] == '--': + if len(sys.argv) != nextArg + 3: + die('Not handling anything other than two heads merge.') + try: + h1 = firstBranch = sys.argv[nextArg + 1] + h2 = secondBranch = sys.argv[nextArg + 2] + except IndexError: + usage() + break + +print 'Merging', h1, 'with', h2 + +try: + h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() + h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() + + graph = buildGraph([h1, h2]) + + [res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], + firstBranch, secondBranch, graph) + + print '' +except: + traceback.print_exc(None, sys.stderr) + sys.exit(2) + +if clean: + sys.exit(0) +else: + print 'Automatic merge failed, fix up by hand' + sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index e51e73499..818e6b772 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -12,9 +12,9 @@ usage () { die "git-merge [-n] [-s ]... +" } -# all_strategies='resolve fredrik stupid octopus' +# all_strategies='resolve recursive stupid octopus' -all_strategies='fredrik octopus resolve stupid' +all_strategies='recursive octopus resolve stupid' default_strategies='resolve octopus' use_strategies=