summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 8391548)
raw | patch | inline | side by side (parent: 8391548)
author | Junio C Hamano <junkio@cox.net> | |
Mon, 25 Sep 2006 02:49:47 +0000 (19:49 -0700) | ||
committer | Junio C Hamano <junkio@cox.net> | |
Mon, 25 Sep 2006 03:33:35 +0000 (20:33 -0700) |
This renames merge-recursive written in Python to merge-recursive-old,
and makes merge-recur as a synonym to merge-recursive. We do not remove
merge-recur yet, but we will remove merge-recur and merge-recursive-old
in a few releases down the road.
Signed-off-by: Junio C Hamano <junkio@cox.net>
and makes merge-recur as a synonym to merge-recursive. We do not remove
merge-recur yet, but we will remove merge-recur and merge-recursive-old
in a few releases down the road.
Signed-off-by: Junio C Hamano <junkio@cox.net>
.gitignore | patch | blob | history | |
Makefile | patch | blob | history | |
configure.ac | patch | blob | history | |
git-merge-recursive-old.py | [new file with mode: 0755] | patch | blob |
git-merge-recursive.py | [deleted file] | patch | blob | history |
git-merge.sh | patch | blob | history | |
git-rebase.sh | patch | blob | history | |
t/test-lib.sh | patch | blob | history |
diff --git a/.gitignore b/.gitignore
index 284db5dffc6605a44b08b21a677434758c63cbe5..25eb4637a6f971a4a290ab823a937997c700bd7f 100644 (file)
--- a/.gitignore
+++ b/.gitignore
git-merge-ours
git-merge-recur
git-merge-recursive
+git-merge-recursive-old
git-merge-resolve
git-merge-stupid
git-mktag
diff --git a/Makefile b/Makefile
index 28091d6be00666b0cbfd2faaa558770c836695cd..c888c810bf8220c25b9949f47730742347738c32 100644 (file)
--- a/Makefile
+++ b/Makefile
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
# a missing newline at the end of the file.
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
-#
# Define COLLISION_CHECK below if you believe that SHA1's
# 1461501637330902918203684832716283019655932542976 hashes do not give you
# sufficient guarantee that no collisions between objects will ever happen.
git-send-email.perl git-svn.perl
SCRIPT_PYTHON = \
- git-merge-recursive.py
+ git-merge-recursive-old.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
git-upload-pack$X git-verify-pack$X \
git-pack-redundant$X git-var$X \
git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \
- git-merge-recur$X \
+ git-merge-recursive$X \
$(EXTRA_PROGRAMS)
# Empty...
export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
### Build rules
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
+ git-merge-recur$X
all:
$(MAKE) -C templates
help.o: common-cmds.h
+git-merge-recur$X: git-merge-recursive$X
+ rm -f $@ && ln git-merge-recursive$X $@
+
$(BUILT_INS): git$X
rm -f $@ && ln git$X $@
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
-merge-recursive.o path-list.o: path-list.h
-git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS)
- $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIBS)
-
$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
$(DIFF_OBJS): diffcore.h
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
git-merge-resolve | git-merge-stupid | git-merge-recur | \
+ git-merge-recursive-old | \
git-ssh-pull | git-ssh-push ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
diff --git a/configure.ac b/configure.ac
index 511cac93d6cec11f3717e810d0c80b27646a5713..b1a5833b40b905ae07b27c414833394ef63b0c99 100644 (file)
--- a/configure.ac
+++ b/configure.ac
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
# Define PYTHON_PATH to provide path to Python.
AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
AS_HELP_STRING([--without-python], [don't use python scripts])],
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
#
-# Define NO_PYTHON if you want to lose all benefits of the recursive merge.
# Define PYTHON_PATH to provide path to Python.
if test -z "$NO_PYTHON"; then
if test -z "$PYTHON_PATH"; then
diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py
--- /dev/null
@@ -0,0 +1,944 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2005 Fredrik Kuivinen
+#
+
+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
+
+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') + \
+ '/merge-recursive-tmp-index'
+def setupIndex(temporary):
+ try:
+ os.unlink(temporaryIndexFile)
+ except OSError:
+ pass
+ if temporary:
+ newIndex = temporaryIndexFile
+ else:
+ newIndex = originalIndexFile
+ os.environ['GIT_INDEX_FILE'] = newIndex
+
+# This is a global variable which is used in a number of places but
+# only written to in the 'merge' function.
+
+# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
+# don't update the working directory.
+# False => Leave unmerged entries in the cache and update
+# the working directory.
+
+cacheOnly = False
+
+# The entry point to the merge code
+# ---------------------------------
+
+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 cleanness of the merge.'''
+ assert(isinstance(h1, Commit) and isinstance(h2, Commit))
+
+ global outputIndent
+
+ output('Merging:')
+ output(h1)
+ output(h2)
+ sys.stdout.flush()
+
+ 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:
+ output(x)
+ sys.stdout.flush()
+
+ mergedCA = ca[0]
+ for h in ca[1:]:
+ outputIndent = callDepth+1
+ [mergedCA, dummy] = merge(mergedCA, h,
+ 'Temporary merge branch 1',
+ 'Temporary merge branch 2',
+ graph, callDepth+1)
+ outputIndent = callDepth
+ assert(isinstance(mergedCA, Commit))
+
+ global cacheOnly
+ if callDepth == 0:
+ setupIndex(False)
+ cacheOnly = False
+ else:
+ setupIndex(True)
+ runProgram(['git-read-tree', h1.tree()])
+ cacheOnly = True
+
+ [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
+ branch1Name, branch2Name)
+
+ if graph and (clean or cacheOnly):
+ res = Commit(None, [h1, h2], tree=shaRes)
+ graph.addNode(res)
+ else:
+ res = None
+
+ return [res, clean]
+
+getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
+def getFilesAndDirs(tree):
+ files = Set()
+ dirs = Set()
+ out = runProgram(['git-ls-tree', '-r', '-z', '-t', 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]
+
+# Those two global variables are used in a number of places but only
+# written to in 'mergeTrees' and 'uniquePath'. They keep track of
+# every file and directory in the two branches that are about to be
+# merged.
+currentFileSet = None
+currentDirectorySet = None
+
+def mergeTrees(head, merge, common, branch1Name, branch2Name):
+ '''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:
+ output('Already uptodate!')
+ return [head, True]
+
+ if cacheOnly:
+ updateArg = '-i'
+ else:
+ updateArg = '-u'
+
+ [out, code] = runProgram(['git-read-tree', updateArg, '-m',
+ common, head, merge], returnCode = True)
+ if code != 0:
+ die('git-read-tree:', out)
+
+ [tree, code] = runProgram('git-write-tree', returnCode=True)
+ tree = tree.rstrip()
+ if code != 0:
+ global currentFileSet, currentDirectorySet
+ [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
+ [filesM, dirsM] = getFilesAndDirs(merge)
+ currentFileSet.union_update(filesM)
+ currentDirectorySet.union_update(dirsM)
+
+ entries = unmergedCacheEntries()
+ renamesHead = getRenames(head, common, head, merge, entries)
+ renamesMerge = getRenames(merge, common, head, merge, entries)
+
+ cleanMerge = processRenames(renamesHead, renamesMerge,
+ branch1Name, branch2Name)
+ for entry in entries:
+ if entry.processed:
+ continue
+ if not processEntry(entry, branch1Name, branch2Name):
+ cleanMerge = False
+
+ if cleanMerge or cacheOnly:
+ tree = runProgram('git-write-tree').rstrip()
+ else:
+ tree = None
+ else:
+ cleanMerge = True
+
+ return [tree, cleanMerge]
+
+# Low level file merging, update and removal
+# ------------------------------------------
+
+def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
+ branch1Name, branch2Name):
+
+ merge = False
+ clean = True
+
+ if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
+ clean = False
+ if stat.S_ISREG(aMode):
+ mode = aMode
+ sha = aSha
+ else:
+ mode = bMode
+ sha = bSha
+ else:
+ if aSha != oSha and bSha != oSha:
+ merge = True
+
+ if aMode == oMode:
+ mode = bMode
+ else:
+ mode = aMode
+
+ if aSha == oSha:
+ sha = bSha
+ elif bSha == oSha:
+ sha = aSha
+ elif stat.S_ISREG(aMode):
+ assert(stat.S_ISREG(bMode))
+
+ orig = runProgram(['git-unpack-file', oSha]).rstrip()
+ src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+ src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+ 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(orig)
+ os.unlink(src1)
+ os.unlink(src2)
+
+ clean = (code == 0)
+ else:
+ assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
+ sha = aSha
+
+ if aSha != bSha:
+ clean = False
+
+ return [sha, mode, clean, merge]
+
+def updateFile(clean, sha, mode, path):
+ updateCache = cacheOnly or clean
+ updateWd = not cacheOnly
+
+ return updateFileExt(sha, mode, path, updateCache, updateWd)
+
+def updateFileExt(sha, mode, path, updateCache, updateWd):
+ if cacheOnly:
+ updateWd = False
+
+ if updateWd:
+ pathComponents = path.split('/')
+ for x in xrange(1, len(pathComponents)):
+ p = '/'.join(pathComponents[0:x])
+
+ try:
+ createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
+ except OSError:
+ createDir = True
+
+ if createDir:
+ try:
+ os.mkdir(p)
+ except OSError, e:
+ die("Couldn't create directory", p, e.strerror)
+
+ 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)
+
+ if updateWd and updateCache:
+ runProgram(['git-update-index', '--add', '--', path])
+ elif updateCache:
+ 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
+
+ if updateCache:
+ runProgram(['git-update-index', '--force-remove', '--', path])
+
+ if updateWd:
+ try:
+ os.unlink(path)
+ 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):
+ try:
+ os.lstat(path)
+ return True
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ return False
+ else:
+ raise
+
+ 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)
+ currentFileSet.add(newPath)
+ return newPath
+
+# Cache entry management
+# ----------------------
+
+class CacheEntry:
+ def __init__(self, path):
+ class Stage:
+ def __init__(self):
+ self.sha1 = None
+ self.mode = None
+
+ # Used for debugging only
+ def __str__(self):
+ if self.mode != None:
+ m = '0%o' % self.mode
+ else:
+ m = 'None'
+
+ if self.sha1:
+ sha1 = self.sha1
+ else:
+ sha1 = 'None'
+ return 'sha1: ' + sha1 + ' mode: ' + m
+
+ self.stages = [Stage(), Stage(), Stage(), Stage()]
+ self.path = path
+ self.processed = False
+
+ def __str__(self):
+ return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
+
+class CacheEntryContainer:
+ def __init__(self):
+ self.entries = {}
+
+ def add(self, entry):
+ self.entries[entry.path] = entry
+
+ def get(self, path):
+ return self.entries.get(path)
+
+ def __iter__(self):
+ return self.entries.itervalues()
+
+unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
+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 = CacheEntryContainer()
+ 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))
+ path = m.group(4)
+
+ e = res.get(path)
+ if not e:
+ e = CacheEntry(path)
+ res.add(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
+
+lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
+def getCacheEntry(path, origTree, aTree, bTree):
+ '''Returns a CacheEntry object which doesn't have to correspond to
+ a real cache entry in Git's index.'''
+
+ def parse(out):
+ if out == '':
+ return [None, None]
+ else:
+ m = lsTreeRE.match(out)
+ if not m:
+ die('Unexpected output from git-ls-tree:', out)
+ elif m.group(2) == 'blob':
+ return [m.group(3), int(m.group(1), 8)]
+ else:
+ return [None, None]
+
+ res = CacheEntry(path)
+
+ [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
+ [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
+ [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
+
+ res.stages[1].sha1 = oSha
+ res.stages[1].mode = oMode
+ res.stages[2].sha1 = aSha
+ res.stages[2].mode = aMode
+ res.stages[3].sha1 = bSha
+ res.stages[3].mode = bMode
+
+ return res
+
+# Rename detection and handling
+# -----------------------------
+
+class RenameEntry:
+ def __init__(self,
+ src, srcSha, srcMode, srcCacheEntry,
+ dst, dstSha, dstMode, dstCacheEntry,
+ score):
+ self.srcName = src
+ self.srcSha = srcSha
+ self.srcMode = srcMode
+ self.srcCacheEntry = srcCacheEntry
+ self.dstName = dst
+ self.dstSha = dstSha
+ self.dstMode = dstMode
+ self.dstCacheEntry = dstCacheEntry
+ self.score = score
+
+ self.processed = False
+
+class RenameEntryContainer:
+ def __init__(self):
+ self.entriesSrc = {}
+ self.entriesDst = {}
+
+ def add(self, entry):
+ self.entriesSrc[entry.srcName] = entry
+ self.entriesDst[entry.dstName] = entry
+
+ def getSrc(self, path):
+ return self.entriesSrc.get(path)
+
+ def getDst(self, path):
+ return self.entriesDst.get(path)
+
+ def __iter__(self):
+ return self.entriesSrc.itervalues()
+
+parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
+def getRenames(tree, oTree, aTree, bTree, cacheEntries):
+ '''Get information of all renames which occured between 'oTree' and
+ 'tree'. We need the three trees in the merge ('oTree', 'aTree' and
+ 'bTree') to be able to associate the correct cache entries with
+ the rename information. 'tree' is always equal to either aTree or bTree.'''
+
+ assert(tree == aTree or tree == bTree)
+ inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
+ '-z', oTree, tree])
+
+ ret = RenameEntryContainer()
+ try:
+ recs = inp.split("\0")
+ recs.pop() # remove last entry (which is '')
+ it = recs.__iter__()
+ while True:
+ rec = it.next()
+ m = parseDiffRenamesRE.match(rec)
+
+ if not m:
+ die('Unexpected output from git-diff-tree:', rec)
+
+ srcMode = int(m.group(1), 8)
+ dstMode = int(m.group(2), 8)
+ srcSha = m.group(3)
+ dstSha = m.group(4)
+ score = m.group(5)
+ src = it.next()
+ dst = it.next()
+
+ srcCacheEntry = cacheEntries.get(src)
+ if not srcCacheEntry:
+ srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
+ cacheEntries.add(srcCacheEntry)
+
+ dstCacheEntry = cacheEntries.get(dst)
+ if not dstCacheEntry:
+ dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
+ cacheEntries.add(dstCacheEntry)
+
+ ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
+ dst, dstSha, dstMode, dstCacheEntry,
+ score))
+ except StopIteration:
+ pass
+ return ret
+
+def fmtRename(src, dst):
+ srcPath = src.split('/')
+ dstPath = dst.split('/')
+ path = []
+ endIndex = min(len(srcPath), len(dstPath)) - 1
+ for x in range(0, endIndex):
+ if srcPath[x] == dstPath[x]:
+ path.append(srcPath[x])
+ else:
+ endIndex = x
+ break
+
+ if len(path) > 0:
+ return '/'.join(path) + \
+ '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
+ '/'.join(dstPath[endIndex:]) + '}'
+ else:
+ return src + ' => ' + dst
+
+def processRenames(renamesA, renamesB, branchNameA, branchNameB):
+ srcNames = Set()
+ for x in renamesA:
+ srcNames.add(x.srcName)
+ for x in renamesB:
+ srcNames.add(x.srcName)
+
+ cleanMerge = True
+ for path in srcNames:
+ if renamesA.getSrc(path):
+ renames1 = renamesA
+ renames2 = renamesB
+ branchName1 = branchNameA
+ branchName2 = branchNameB
+ else:
+ renames1 = renamesB
+ renames2 = renamesA
+ branchName1 = branchNameB
+ branchName2 = branchNameA
+
+ ren1 = renames1.getSrc(path)
+ ren2 = renames2.getSrc(path)
+
+ ren1.dstCacheEntry.processed = True
+ ren1.srcCacheEntry.processed = True
+
+ if ren1.processed:
+ continue
+
+ ren1.processed = True
+
+ if ren2:
+ # Renamed in 1 and renamed in 2
+ assert(ren1.srcName == ren2.srcName)
+ ren2.dstCacheEntry.processed = True
+ ren2.processed = True
+
+ if ren1.dstName != ren2.dstName:
+ 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)
+ 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)
+ output(ren2.dstName, 'is a directory in', branchName1,
+ 'adding as', dstName2, 'instead.')
+ removeFile(False, ren2.dstName)
+ else:
+ dstName2 = ren2.dstName
+ setIndexStages(dstName1,
+ None, None,
+ ren1.dstSha, ren1.dstMode,
+ None, None)
+ setIndexStages(dstName2,
+ None, None,
+ None, None,
+ ren2.dstSha, ren2.dstMode)
+
+ else:
+ removeFile(True, ren1.srcName)
+
+ [resSha, resMode, clean, merge] = \
+ mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+ ren1.dstName, ren1.dstSha, ren1.dstMode,
+ ren2.dstName, ren2.dstSha, ren2.dstMode,
+ branchName1, branchName2)
+
+ if merge or not clean:
+ output('Renaming', fmtRename(path, ren1.dstName))
+
+ if merge:
+ output('Auto-merging', ren1.dstName)
+
+ if not clean:
+ output('CONFLICT (content): merge conflict in',
+ ren1.dstName)
+ cleanMerge = False
+
+ if not cacheOnly:
+ 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
+ else:
+ stage = 2
+
+ srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1
+ srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
+
+ dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1
+ dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
+
+ tryMerge = False
+
+ if ren1.dstName in currentDirectorySet:
+ newPath = uniquePath(ren1.dstName, branchName1)
+ 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:
+ 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)
+ 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
+ elif renames2.getDst(ren1.dstName):
+ dst2 = renames2.getDst(ren1.dstName)
+ newPath1 = uniquePath(ren1.dstName, branchName1)
+ newPath2 = uniquePath(dst2.dstName, branchName2)
+ 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)
+ dst2.processed = True
+ cleanMerge = False
+ else:
+ 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(oName, oSHA1, oMode,
+ aName, aSHA1, aMode,
+ bName, bSHA1, bMode,
+ aBranch, bBranch);
+
+ if merge or not clean:
+ output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
+
+ if merge:
+ output('Auto-merging', ren1.dstName)
+
+ if not clean:
+ output('CONFLICT (rename/modify): Merge conflict in',
+ ren1.dstName)
+ cleanMerge = False
+
+ if not cacheOnly:
+ setIndexStages(ren1.dstName,
+ oSHA1, oMode,
+ aSHA1, aMode,
+ bSHA1, bMode)
+
+ updateFile(clean, resSha, resMode, ren1.dstName)
+
+ return cleanMerge
+
+# Per entry merge function
+# ------------------------
+
+def processEntry(entry, branch1Name, branch2Name):
+ '''Merge one cache entry.'''
+
+ debug('processing', entry.path, 'clean cache:', cacheOnly)
+
+ cleanMerge = True
+
+ path = entry.path
+ oSha = entry.stages[1].sha1
+ oMode = entry.stages[1].mode
+ aSha = entry.stages[2].sha1
+ aMode = entry.stages[2].mode
+ bSha = entry.stages[3].sha1
+ bMode = entry.stages[3].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:
+ output('Removing', path)
+ removeFile(True, path)
+ else:
+ # Deleted in one and changed in the other
+ cleanMerge = False
+ if not aSha:
+ output('CONFLICT (delete/modify):', path, 'deleted in',
+ branch1Name, 'and modified in', branch2Name + '.',
+ 'Version', branch2Name, 'of', path, 'left in tree.')
+ mode = bMode
+ sha = bSha
+ else:
+ output('CONFLICT (modify/delete):', 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/directory'
+ else:
+ addBranch = branch2Name
+ otherBranch = branch1Name
+ mode = bMode
+ sha = bSha
+ conf = 'directory/file'
+
+ if path in currentDirectorySet:
+ cleanMerge = False
+ newPath = uniquePath(path, addBranch)
+ 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:
+ output('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
+ 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:
+ # This case is handled by git-read-tree
+ assert(False)
+ else:
+ cleanMerge = False
+ newPath1 = uniquePath(path, branch1Name)
+ newPath2 = uniquePath(path, branch2Name)
+ 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)
+
+ elif oSha and aSha and bSha:
+ #
+ # case D: Modified in both, but differently.
+ #
+ output('Auto-merging', path)
+ [sha, mode, clean, dummy] = \
+ mergeFile(path, oSha, oMode,
+ path, aSha, aMode,
+ path, bSha, bMode,
+ branch1Name, branch2Name)
+ if clean:
+ updateFile(True, sha, mode, path)
+ else:
+ cleanMerge = False
+ output('CONFLICT (content): Merge conflict in', path)
+
+ if cacheOnly:
+ updateFile(False, sha, mode, path)
+ else:
+ updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
+ else:
+ die("ERROR: Fatal merge failure, shouldn't happen.")
+
+ return cleanMerge
+
+def usage():
+ die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
+
+# main entry point as merge strategy module
+# The first parameters up to -- are merge bases, and the rest are 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:
+ 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
+ else:
+ bases.append(sys.argv[nextArg])
+
+print 'Merging', h1, 'with', h2
+
+try:
+ h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
+ h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
+
+ 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:
+ if isinstance(sys.exc_info()[1], SystemExit):
+ raise
+ else:
+ traceback.print_exc(None, sys.stderr)
+ sys.exit(2)
+
+if clean:
+ sys.exit(0)
+else:
+ sys.exit(1)
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
--- a/git-merge-recursive.py
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-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
-
-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') + \
- '/merge-recursive-tmp-index'
-def setupIndex(temporary):
- try:
- os.unlink(temporaryIndexFile)
- except OSError:
- pass
- if temporary:
- newIndex = temporaryIndexFile
- else:
- newIndex = originalIndexFile
- os.environ['GIT_INDEX_FILE'] = newIndex
-
-# This is a global variable which is used in a number of places but
-# only written to in the 'merge' function.
-
-# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
-# don't update the working directory.
-# False => Leave unmerged entries in the cache and update
-# the working directory.
-
-cacheOnly = False
-
-# The entry point to the merge code
-# ---------------------------------
-
-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 cleanness of the merge.'''
- assert(isinstance(h1, Commit) and isinstance(h2, Commit))
-
- global outputIndent
-
- output('Merging:')
- output(h1)
- output(h2)
- sys.stdout.flush()
-
- 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:
- output(x)
- sys.stdout.flush()
-
- mergedCA = ca[0]
- for h in ca[1:]:
- outputIndent = callDepth+1
- [mergedCA, dummy] = merge(mergedCA, h,
- 'Temporary merge branch 1',
- 'Temporary merge branch 2',
- graph, callDepth+1)
- outputIndent = callDepth
- assert(isinstance(mergedCA, Commit))
-
- global cacheOnly
- if callDepth == 0:
- setupIndex(False)
- cacheOnly = False
- else:
- setupIndex(True)
- runProgram(['git-read-tree', h1.tree()])
- cacheOnly = True
-
- [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
- branch1Name, branch2Name)
-
- if graph and (clean or cacheOnly):
- res = Commit(None, [h1, h2], tree=shaRes)
- graph.addNode(res)
- else:
- res = None
-
- return [res, clean]
-
-getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
-def getFilesAndDirs(tree):
- files = Set()
- dirs = Set()
- out = runProgram(['git-ls-tree', '-r', '-z', '-t', 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]
-
-# Those two global variables are used in a number of places but only
-# written to in 'mergeTrees' and 'uniquePath'. They keep track of
-# every file and directory in the two branches that are about to be
-# merged.
-currentFileSet = None
-currentDirectorySet = None
-
-def mergeTrees(head, merge, common, branch1Name, branch2Name):
- '''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:
- output('Already uptodate!')
- return [head, True]
-
- if cacheOnly:
- updateArg = '-i'
- else:
- updateArg = '-u'
-
- [out, code] = runProgram(['git-read-tree', updateArg, '-m',
- common, head, merge], returnCode = True)
- if code != 0:
- die('git-read-tree:', out)
-
- [tree, code] = runProgram('git-write-tree', returnCode=True)
- tree = tree.rstrip()
- if code != 0:
- global currentFileSet, currentDirectorySet
- [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
- [filesM, dirsM] = getFilesAndDirs(merge)
- currentFileSet.union_update(filesM)
- currentDirectorySet.union_update(dirsM)
-
- entries = unmergedCacheEntries()
- renamesHead = getRenames(head, common, head, merge, entries)
- renamesMerge = getRenames(merge, common, head, merge, entries)
-
- cleanMerge = processRenames(renamesHead, renamesMerge,
- branch1Name, branch2Name)
- for entry in entries:
- if entry.processed:
- continue
- if not processEntry(entry, branch1Name, branch2Name):
- cleanMerge = False
-
- if cleanMerge or cacheOnly:
- tree = runProgram('git-write-tree').rstrip()
- else:
- tree = None
- else:
- cleanMerge = True
-
- return [tree, cleanMerge]
-
-# Low level file merging, update and removal
-# ------------------------------------------
-
-def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
- branch1Name, branch2Name):
-
- merge = False
- clean = True
-
- if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
- clean = False
- if stat.S_ISREG(aMode):
- mode = aMode
- sha = aSha
- else:
- mode = bMode
- sha = bSha
- else:
- if aSha != oSha and bSha != oSha:
- merge = True
-
- if aMode == oMode:
- mode = bMode
- else:
- mode = aMode
-
- if aSha == oSha:
- sha = bSha
- elif bSha == oSha:
- sha = aSha
- elif stat.S_ISREG(aMode):
- assert(stat.S_ISREG(bMode))
-
- orig = runProgram(['git-unpack-file', oSha]).rstrip()
- src1 = runProgram(['git-unpack-file', aSha]).rstrip()
- src2 = runProgram(['git-unpack-file', bSha]).rstrip()
- 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(orig)
- os.unlink(src1)
- os.unlink(src2)
-
- clean = (code == 0)
- else:
- assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
- sha = aSha
-
- if aSha != bSha:
- clean = False
-
- return [sha, mode, clean, merge]
-
-def updateFile(clean, sha, mode, path):
- updateCache = cacheOnly or clean
- updateWd = not cacheOnly
-
- return updateFileExt(sha, mode, path, updateCache, updateWd)
-
-def updateFileExt(sha, mode, path, updateCache, updateWd):
- if cacheOnly:
- updateWd = False
-
- if updateWd:
- pathComponents = path.split('/')
- for x in xrange(1, len(pathComponents)):
- p = '/'.join(pathComponents[0:x])
-
- try:
- createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
- except OSError:
- createDir = True
-
- if createDir:
- try:
- os.mkdir(p)
- except OSError, e:
- die("Couldn't create directory", p, e.strerror)
-
- 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)
-
- if updateWd and updateCache:
- runProgram(['git-update-index', '--add', '--', path])
- elif updateCache:
- 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
-
- if updateCache:
- runProgram(['git-update-index', '--force-remove', '--', path])
-
- if updateWd:
- try:
- os.unlink(path)
- 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):
- try:
- os.lstat(path)
- return True
- except OSError, e:
- if e.errno == errno.ENOENT:
- return False
- else:
- raise
-
- 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)
- currentFileSet.add(newPath)
- return newPath
-
-# Cache entry management
-# ----------------------
-
-class CacheEntry:
- def __init__(self, path):
- class Stage:
- def __init__(self):
- self.sha1 = None
- self.mode = None
-
- # Used for debugging only
- def __str__(self):
- if self.mode != None:
- m = '0%o' % self.mode
- else:
- m = 'None'
-
- if self.sha1:
- sha1 = self.sha1
- else:
- sha1 = 'None'
- return 'sha1: ' + sha1 + ' mode: ' + m
-
- self.stages = [Stage(), Stage(), Stage(), Stage()]
- self.path = path
- self.processed = False
-
- def __str__(self):
- return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
-
-class CacheEntryContainer:
- def __init__(self):
- self.entries = {}
-
- def add(self, entry):
- self.entries[entry.path] = entry
-
- def get(self, path):
- return self.entries.get(path)
-
- def __iter__(self):
- return self.entries.itervalues()
-
-unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-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 = CacheEntryContainer()
- 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))
- path = m.group(4)
-
- e = res.get(path)
- if not e:
- e = CacheEntry(path)
- res.add(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
-
-lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
-def getCacheEntry(path, origTree, aTree, bTree):
- '''Returns a CacheEntry object which doesn't have to correspond to
- a real cache entry in Git's index.'''
-
- def parse(out):
- if out == '':
- return [None, None]
- else:
- m = lsTreeRE.match(out)
- if not m:
- die('Unexpected output from git-ls-tree:', out)
- elif m.group(2) == 'blob':
- return [m.group(3), int(m.group(1), 8)]
- else:
- return [None, None]
-
- res = CacheEntry(path)
-
- [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
- [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
- [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
-
- res.stages[1].sha1 = oSha
- res.stages[1].mode = oMode
- res.stages[2].sha1 = aSha
- res.stages[2].mode = aMode
- res.stages[3].sha1 = bSha
- res.stages[3].mode = bMode
-
- return res
-
-# Rename detection and handling
-# -----------------------------
-
-class RenameEntry:
- def __init__(self,
- src, srcSha, srcMode, srcCacheEntry,
- dst, dstSha, dstMode, dstCacheEntry,
- score):
- self.srcName = src
- self.srcSha = srcSha
- self.srcMode = srcMode
- self.srcCacheEntry = srcCacheEntry
- self.dstName = dst
- self.dstSha = dstSha
- self.dstMode = dstMode
- self.dstCacheEntry = dstCacheEntry
- self.score = score
-
- self.processed = False
-
-class RenameEntryContainer:
- def __init__(self):
- self.entriesSrc = {}
- self.entriesDst = {}
-
- def add(self, entry):
- self.entriesSrc[entry.srcName] = entry
- self.entriesDst[entry.dstName] = entry
-
- def getSrc(self, path):
- return self.entriesSrc.get(path)
-
- def getDst(self, path):
- return self.entriesDst.get(path)
-
- def __iter__(self):
- return self.entriesSrc.itervalues()
-
-parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
-def getRenames(tree, oTree, aTree, bTree, cacheEntries):
- '''Get information of all renames which occured between 'oTree' and
- 'tree'. We need the three trees in the merge ('oTree', 'aTree' and
- 'bTree') to be able to associate the correct cache entries with
- the rename information. 'tree' is always equal to either aTree or bTree.'''
-
- assert(tree == aTree or tree == bTree)
- inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
- '-z', oTree, tree])
-
- ret = RenameEntryContainer()
- try:
- recs = inp.split("\0")
- recs.pop() # remove last entry (which is '')
- it = recs.__iter__()
- while True:
- rec = it.next()
- m = parseDiffRenamesRE.match(rec)
-
- if not m:
- die('Unexpected output from git-diff-tree:', rec)
-
- srcMode = int(m.group(1), 8)
- dstMode = int(m.group(2), 8)
- srcSha = m.group(3)
- dstSha = m.group(4)
- score = m.group(5)
- src = it.next()
- dst = it.next()
-
- srcCacheEntry = cacheEntries.get(src)
- if not srcCacheEntry:
- srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
- cacheEntries.add(srcCacheEntry)
-
- dstCacheEntry = cacheEntries.get(dst)
- if not dstCacheEntry:
- dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
- cacheEntries.add(dstCacheEntry)
-
- ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
- dst, dstSha, dstMode, dstCacheEntry,
- score))
- except StopIteration:
- pass
- return ret
-
-def fmtRename(src, dst):
- srcPath = src.split('/')
- dstPath = dst.split('/')
- path = []
- endIndex = min(len(srcPath), len(dstPath)) - 1
- for x in range(0, endIndex):
- if srcPath[x] == dstPath[x]:
- path.append(srcPath[x])
- else:
- endIndex = x
- break
-
- if len(path) > 0:
- return '/'.join(path) + \
- '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
- '/'.join(dstPath[endIndex:]) + '}'
- else:
- return src + ' => ' + dst
-
-def processRenames(renamesA, renamesB, branchNameA, branchNameB):
- srcNames = Set()
- for x in renamesA:
- srcNames.add(x.srcName)
- for x in renamesB:
- srcNames.add(x.srcName)
-
- cleanMerge = True
- for path in srcNames:
- if renamesA.getSrc(path):
- renames1 = renamesA
- renames2 = renamesB
- branchName1 = branchNameA
- branchName2 = branchNameB
- else:
- renames1 = renamesB
- renames2 = renamesA
- branchName1 = branchNameB
- branchName2 = branchNameA
-
- ren1 = renames1.getSrc(path)
- ren2 = renames2.getSrc(path)
-
- ren1.dstCacheEntry.processed = True
- ren1.srcCacheEntry.processed = True
-
- if ren1.processed:
- continue
-
- ren1.processed = True
-
- if ren2:
- # Renamed in 1 and renamed in 2
- assert(ren1.srcName == ren2.srcName)
- ren2.dstCacheEntry.processed = True
- ren2.processed = True
-
- if ren1.dstName != ren2.dstName:
- 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)
- 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)
- output(ren2.dstName, 'is a directory in', branchName1,
- 'adding as', dstName2, 'instead.')
- removeFile(False, ren2.dstName)
- else:
- dstName2 = ren2.dstName
- setIndexStages(dstName1,
- None, None,
- ren1.dstSha, ren1.dstMode,
- None, None)
- setIndexStages(dstName2,
- None, None,
- None, None,
- ren2.dstSha, ren2.dstMode)
-
- else:
- removeFile(True, ren1.srcName)
-
- [resSha, resMode, clean, merge] = \
- mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
- ren1.dstName, ren1.dstSha, ren1.dstMode,
- ren2.dstName, ren2.dstSha, ren2.dstMode,
- branchName1, branchName2)
-
- if merge or not clean:
- output('Renaming', fmtRename(path, ren1.dstName))
-
- if merge:
- output('Auto-merging', ren1.dstName)
-
- if not clean:
- output('CONFLICT (content): merge conflict in',
- ren1.dstName)
- cleanMerge = False
-
- if not cacheOnly:
- 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
- else:
- stage = 2
-
- srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1
- srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
-
- dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1
- dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
-
- tryMerge = False
-
- if ren1.dstName in currentDirectorySet:
- newPath = uniquePath(ren1.dstName, branchName1)
- 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:
- 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)
- 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
- elif renames2.getDst(ren1.dstName):
- dst2 = renames2.getDst(ren1.dstName)
- newPath1 = uniquePath(ren1.dstName, branchName1)
- newPath2 = uniquePath(dst2.dstName, branchName2)
- 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)
- dst2.processed = True
- cleanMerge = False
- else:
- 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(oName, oSHA1, oMode,
- aName, aSHA1, aMode,
- bName, bSHA1, bMode,
- aBranch, bBranch);
-
- if merge or not clean:
- output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
-
- if merge:
- output('Auto-merging', ren1.dstName)
-
- if not clean:
- output('CONFLICT (rename/modify): Merge conflict in',
- ren1.dstName)
- cleanMerge = False
-
- if not cacheOnly:
- setIndexStages(ren1.dstName,
- oSHA1, oMode,
- aSHA1, aMode,
- bSHA1, bMode)
-
- updateFile(clean, resSha, resMode, ren1.dstName)
-
- return cleanMerge
-
-# Per entry merge function
-# ------------------------
-
-def processEntry(entry, branch1Name, branch2Name):
- '''Merge one cache entry.'''
-
- debug('processing', entry.path, 'clean cache:', cacheOnly)
-
- cleanMerge = True
-
- path = entry.path
- oSha = entry.stages[1].sha1
- oMode = entry.stages[1].mode
- aSha = entry.stages[2].sha1
- aMode = entry.stages[2].mode
- bSha = entry.stages[3].sha1
- bMode = entry.stages[3].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:
- output('Removing', path)
- removeFile(True, path)
- else:
- # Deleted in one and changed in the other
- cleanMerge = False
- if not aSha:
- output('CONFLICT (delete/modify):', path, 'deleted in',
- branch1Name, 'and modified in', branch2Name + '.',
- 'Version', branch2Name, 'of', path, 'left in tree.')
- mode = bMode
- sha = bSha
- else:
- output('CONFLICT (modify/delete):', 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/directory'
- else:
- addBranch = branch2Name
- otherBranch = branch1Name
- mode = bMode
- sha = bSha
- conf = 'directory/file'
-
- if path in currentDirectorySet:
- cleanMerge = False
- newPath = uniquePath(path, addBranch)
- 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:
- output('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
- 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:
- # This case is handled by git-read-tree
- assert(False)
- else:
- cleanMerge = False
- newPath1 = uniquePath(path, branch1Name)
- newPath2 = uniquePath(path, branch2Name)
- 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)
-
- elif oSha and aSha and bSha:
- #
- # case D: Modified in both, but differently.
- #
- output('Auto-merging', path)
- [sha, mode, clean, dummy] = \
- mergeFile(path, oSha, oMode,
- path, aSha, aMode,
- path, bSha, bMode,
- branch1Name, branch2Name)
- if clean:
- updateFile(True, sha, mode, path)
- else:
- cleanMerge = False
- output('CONFLICT (content): Merge conflict in', path)
-
- if cacheOnly:
- updateFile(False, sha, mode, path)
- else:
- updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
- else:
- die("ERROR: Fatal merge failure, shouldn't happen.")
-
- return cleanMerge
-
-def usage():
- die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
-
-# main entry point as merge strategy module
-# The first parameters up to -- are merge bases, and the rest are 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:
- 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
- else:
- bases.append(sys.argv[nextArg])
-
-print 'Merging', h1, 'with', h2
-
-try:
- h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
- h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
-
- 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:
- if isinstance(sys.exc_info()[1], SystemExit):
- raise
- else:
- traceback.print_exc(None, sys.stderr)
- sys.exit(2)
-
-if clean:
- sys.exit(0)
-else:
- sys.exit(1)
diff --git a/git-merge.sh b/git-merge.sh
index d049e164318a416941334b15699d52f87b00387f..5b34b4de99c33a99dfb841795baced8889f74a88 100755 (executable)
--- a/git-merge.sh
+++ b/git-merge.sh
LF='
'
-all_strategies='recursive recur octopus resolve stupid ours'
-case "${GIT_USE_RECUR_FOR_RECURSIVE}" in
-'')
- default_twohead_strategies=recursive ;;
-?*)
- default_twohead_strategies=recur ;;
-esac
+all_strategies='recur recursive recursive-old octopus resolve stupid ours'
+default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_trivial_merge_strategies='ours'
use_strategies=
index_merge=t
if test "@@NO_PYTHON@@"; then
- all_strategies='recur resolve octopus stupid ours'
- default_twohead_strategies='resolve'
+ all_strategies='recur recursive resolve octopus stupid ours'
fi
dropsave() {
strategy="$2"
shift ;;
esac
- case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in
- recursive,?*)
- strategy=recur ;;
- esac
case " $all_strategies " in
*" $strategy "*)
use_strategies="$use_strategies$strategy " ;;
diff --git a/git-rebase.sh b/git-rebase.sh
index 20f74d416732e681d3b44ea610e428529af49235..a7373c0532fad447263e7199d0b8ec2908c683c9 100755 (executable)
--- a/git-rebase.sh
+++ b/git-rebase.sh
To restore the original branch and stop rebasing run \"git rebase --abort\".
"
unset newbase
-case "${GIT_USE_RECUR_FOR_RECURSIVE}" in
-'')
- strategy=recursive ;;
-?*)
- strategy=recur ;;
-esac
-
+strategy=recursive
do_merge=
dotest=$GIT_DIR/.dotest-merge
prec=4
shift
done
-case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in
-recursive,?*)
- strategy=recur ;;
-esac
-
# Make sure we do not have .dotest
if test -z "$do_merge"
then
exit $?
fi
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive"
+if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
then
- die 'The recursive merge strategy currently relies on Python,
+ die 'The recursive-old merge strategy is written in Python,
which this installation of git was not configured with. Please consider
-a different merge strategy (e.g. octopus, resolve, stupid, ours)
+a different merge strategy (e.g. recursive, resolve, or stupid)
or install Python and git with Python support.'
fi
diff --git a/t/test-lib.sh b/t/test-lib.sh
index e75ad5faace6e40e7f6f3f47e9be90b0e9c4691e..0fe2718845fa8ad66dfa850ac2921e33b10b51c1 100755 (executable)
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
PYTHON=`sed -e '1{
s/^#!//
q
-}' ../git-merge-recursive` || {
+}' ../git-merge-recursive-old` || {
error "You haven't built things yet, have you?"
}
"$PYTHON" -c 'import subprocess' 2>/dev/null || {