Merge branch 'jc/merge'

* branch 'jc/merge':
  git-merge: do not leak rev-parse output used for checking internally.
  git-merge: tighten error checking.
  merge: allow merging into a yet-to-be-born branch.
  git-merge: make it usable as the first class UI
  remove merge-recursive-old
This commit is contained in:
Junio C Hamano 2006-11-26 22:19:56 -08:00
commit 88ffc1f28a
15 changed files with 96 additions and 2510 deletions

1
.gitignore vendored
View File

@ -66,7 +66,6 @@ git-merge-one-file
git-merge-ours
git-merge-recur
git-merge-recursive
git-merge-recursive-old
git-merge-resolve
git-merge-stupid
git-mktag

View File

@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver
SYNOPSIS
--------
'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
[verse]
'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
[--reflog-action=<action>]
-m=<msg> <remote> <remote>...
DESCRIPTION
-----------
This is the top-level user interface to the merge machinery
This is the top-level interface to the merge machinery
which drives multiple merge strategy scripts.
@ -27,13 +29,19 @@ include::merge-options.txt[]
to give a good default for automated `git-merge` invocations.
<head>::
our branch head commit.
Our branch head commit. This has to be `HEAD`, so new
syntax does not require it
<remote>::
other branch head merged into our branch. You need at
Other branch head merged into our branch. You need at
least one <remote>. Specifying more than one <remote>
obviously means you are trying an Octopus.
--reflog-action=<action>::
This is used internally when `git-pull` calls this command
to record that the merge was created by `pull` command
in the `ref-log` entry that results from the merge.
include::merge-strategies.txt[]

View File

@ -99,9 +99,6 @@ Issues of note:
- "perl" and POSIX-compliant shells are needed to use most of
the barebone Porcelainish scripts.
- "python" 2.3 or more recent; if you have 2.3, you may need
to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
- Some platform specific issues are dealt with Makefile rules,
but depending on your specific installation, you may not
have all the libraries/tools needed, or you may have

View File

@ -69,8 +69,6 @@ all:
#
# Define NO_MMAP if you want to avoid mmap.
#
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
#
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
#
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
@ -116,7 +114,6 @@ prefix = $(HOME)
bindir = $(prefix)/bin
gitexecdir = $(bindir)
template_dir = $(prefix)/share/git-core/templates/
GIT_PYTHON_DIR = $(prefix)/share/git-core/python
# DESTDIR=
# default configuration for gitweb
@ -135,7 +132,7 @@ GITWEB_FAVICON = git-favicon.png
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
export prefix bindir gitexecdir template_dir
CC = gcc
AR = ar
@ -179,12 +176,8 @@ SCRIPT_PERL = \
git-svnimport.perl git-cvsexportcommit.perl \
git-send-email.perl git-svn.perl
SCRIPT_PYTHON = \
git-merge-recursive-old.py
SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
git-cherry-pick git-status git-instaweb
# ... and all the rest that could be moved out of bindir to gitexecdir
@ -227,12 +220,6 @@ endif
ifndef PERL_PATH
PERL_PATH = /usr/bin/perl
endif
ifndef PYTHON_PATH
PYTHON_PATH = /usr/bin/python
endif
PYMODULES = \
gitMergeCommon.py
LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
@ -423,16 +410,6 @@ endif
-include config.mak.autogen
-include config.mak
ifdef WITH_OWN_SUBPROCESS_PY
PYMODULES += compat/subprocess.py
else
ifeq ($(NO_PYTHON),)
ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
PYMODULES += compat/subprocess.py
endif
endif
endif
ifndef NO_CURL
ifdef CURLDIR
# This is still problematic -- gcc does not always want -R.
@ -574,8 +551,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
LIBS = $(GITLIBS) $(EXTLIBS)
@ -622,7 +597,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
-e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
$@.sh >$@+
chmod +x $@+
mv $@+ $@
@ -644,15 +618,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
chmod +x $@+
mv $@+ $@
$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS
rm -f $@ $@+
sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$@.py >$@+
chmod +x $@+
mv $@+ $@
git-cherry-pick: git-revert
cp $< $@+
mv $@+ $@
@ -689,7 +654,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
-e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
-e '/@@GITWEB_CGI@@/d' \
-e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
@ -709,7 +673,6 @@ configure: configure.ac
git$X git.spec \
$(patsubst %.sh,%,$(SCRIPT_SH)) \
$(patsubst %.perl,%,$(SCRIPT_PERL)) \
$(patsubst %.py,%,$(SCRIPT_PYTHON)) \
: GIT-VERSION-FILE
%.o: %.c GIT-CFLAGS
@ -783,7 +746,7 @@ tags:
find . -name '*.[hcS]' -print | xargs ctags -a
### Detect prefix changes
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\
TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
GIT-CFLAGS: .FORCE-GIT-CFLAGS
@ -799,7 +762,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
# However, the environment gets quite big, and some programs have problems
# with that.
export NO_PYTHON
export NO_SVN_TESTS
test: all
@ -834,8 +796,6 @@ install: all
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
$(MAKE) -C perl install
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
$(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
then \
ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
@ -922,7 +882,6 @@ check-docs::
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" || \

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,6 @@ bindir = @bindir@
#gitexecdir = @libexecdir@/git-core/
datarootdir = @datarootdir@
template_dir = @datadir@/git-core/templates/
GIT_PYTHON_DIR = @datadir@/git-core/python
mandir=@mandir@
@ -23,7 +22,6 @@ VPATH = @srcdir@
export exec_prefix mandir
export srcdir VPATH
NO_PYTHON=@NO_PYTHON@
NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
NO_OPENSSL=@NO_OPENSSL@
NO_CURL=@NO_CURL@

View File

@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell)
# Define PERL_PATH to provide path to Perl.
GIT_ARG_SET_PATH(perl)
#
# 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])],
[if test "$withval" = "no"; then \
NO_PYTHON=YesPlease; \
elif test "$withval" = "yes"; then \
NO_PYTHON=; \
else \
NO_PYTHON=; \
PYTHON_PATH=$withval; \
fi; \
])
AC_SUBST(NO_PYTHON)
AC_SUBST(PYTHON_PATH)
## Checks for programs.
@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc])
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
#
# Define PYTHON_PATH to provide path to Python.
if test -z "$NO_PYTHON"; then
if test -z "$PYTHON_PATH"; then
AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2])
fi
if test -n "$PYTHON_PATH"; then
GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@])
NO_PYTHON=""
fi
fi
## Checks for libraries.
AC_MSG_NOTICE([CHECKS for libraries])
@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV)
# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
# Enable it on Windows. By default, symrefs are still used.
#
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
AC_CACHE_CHECK([for subprocess.py],
[ac_cv_python_has_subprocess_py],
[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
ac_cv_python_has_subprocess_py=yes
else
ac_cv_python_has_subprocess_py=no
fi])
if test $ac_cv_python_has_subprocess_py != yes; then
GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
fi
#
# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
# a missing newline at the end of the file.
## Site configuration (override autodetection)
## --with-PACKAGE[=ARG] and --without-PACKAGE
AC_MSG_NOTICE([CHECKS for site configuration])

View File

@ -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)

View File

@ -3,22 +3,20 @@
# Copyright (c) 2005 Junio C Hamano
#
USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+'
. git-sh-setup
LF='
'
all_strategies='recur recursive recursive-old octopus resolve stupid ours'
all_strategies='recur recursive 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 recursive resolve octopus stupid ours'
fi
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
@ -95,7 +93,7 @@ finish () {
case "$#" in 0) usage ;; esac
rloga=
rloga= have_message=
while case "$#" in 0) break ;; esac
do
case "$1" in
@ -128,17 +126,81 @@ do
--reflog-action=*)
rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
;;
-m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
have_message=t
;;
-m|--m|--me|--mes|--mess|--messa|--messag|--message)
shift
case "$#" in
1) usage ;;
esac
merge_msg="$1"
have_message=t
;;
-*) usage ;;
*) break ;;
esac
shift
done
merge_msg="$1"
shift
head_arg="$1"
head=$(git-rev-parse --verify "$1"^0) || usage
shift
# This could be traditional "merge <msg> HEAD <commit>..." and the
# way we can tell it is to see if the second token is HEAD, but some
# people might have misused the interface and used a committish that
# is the same as HEAD there instead. Traditional format never would
# have "-m" so it is an additional safety measure to check for it.
if test -z "$have_message" &&
second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
test "$second_token" = "$head_commit"
then
merge_msg="$1"
shift
head_arg="$1"
shift
elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
then
# If the merged head is a valid one there is no reason to
# forbid "git merge" into a branch yet to be born. We do
# the same for "git pull".
if test 1 -ne $#
then
echo >&2 "Can merge only exactly one commit into empty head"
exit 1
fi
rh=$(git rev-parse --verify "$1^0") ||
die "$1 - not something we can merge"
git-update-ref -m "initial pull" HEAD "$rh" "" &&
git-read-tree --reset -u HEAD
exit
else
# We are invoked directly as the first-class UI.
head_arg=HEAD
# All the rest are the commits being merged; prepare
# the standard merge summary message to be appended to
# the given message. If remote is invalid we will die
# later in the common codepath so we discard the error
# in this loop.
merge_name=$(for remote
do
rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) &&
if git show-ref -q --verify "refs/heads/$remote"
then
what=branch
else
what=commit
fi &&
echo "$rh $what '$remote'"
done | git-fmt-merge-msg
)
merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
fi
head=$(git-rev-parse --verify "$head_arg"^0) || usage
# All the rest are remote heads
test "$#" = 0 && usage ;# we need at least one remote head.
@ -147,7 +209,7 @@ test "$rloga" = '' && rloga="merge: $@"
remoteheads=
for remote
do
remotehead=$(git-rev-parse --verify "$remote"^0) ||
remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
die "$remote - not something we can merge"
remoteheads="${remoteheads}$remotehead "
done

View File

@ -302,15 +302,6 @@ then
exit $?
fi
if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
then
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. recursive, resolve, or stupid)
or install Python and git with Python support.'
fi
# start doing a rebase with git-merge
# this is rename-aware if the recursive (default) strategy is used

View File

@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages.
%package core
Summary: Core git tools
Group: Development/Tools
Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat
%description core
This is a stupid (but extremely fast) directory content manager. It
doesn't do a whole lot, but what it _does_ do is track directory

View File

@ -1,275 +0,0 @@
#
# Copyright (C) 2005 Fredrik Kuivinen
#
import sys, re, os, traceback
from sets import Set
def die(*args):
printList(args, sys.stderr)
sys.exit(2)
def printList(list, file=sys.stdout):
for x in list:
file.write(str(x))
file.write(' ')
file.write('\n')
import subprocess
# Debugging machinery
# -------------------
DEBUG = 0
functionsToDebug = Set()
def addDebug(func):
if type(func) == str:
functionsToDebug.add(func)
else:
functionsToDebug.add(func.func_name)
def debug(*args):
if DEBUG:
funcName = traceback.extract_stack()[-2][2]
if funcName in functionsToDebug:
printList(args)
# Program execution
# -----------------
class ProgramError(Exception):
def __init__(self, progStr, error):
self.progStr = progStr
self.error = error
def __str__(self):
return self.progStr + ': ' + self.error
addDebug('runProgram')
def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
debug('runProgram prog:', str(prog), 'input:', str(input))
if type(prog) is str:
progStr = prog
else:
progStr = ' '.join(prog)
try:
if pipeOutput:
stderr = subprocess.STDOUT
stdout = subprocess.PIPE
else:
stderr = None
stdout = None
pop = subprocess.Popen(prog,
shell = type(prog) is str,
stderr=stderr,
stdout=stdout,
stdin=subprocess.PIPE,
env=env)
except OSError, e:
debug('strerror:', e.strerror)
raise ProgramError(progStr, e.strerror)
if input != None:
pop.stdin.write(input)
pop.stdin.close()
if pipeOutput:
out = pop.stdout.read()
else:
out = ''
code = pop.wait()
if returnCode:
ret = [out, code]
else:
ret = out
if code != 0 and not returnCode:
debug('error output:', out)
debug('prog:', prog)
raise ProgramError(progStr, out)
# debug('output:', out.replace('\0', '\n'))
return ret
# Code for computing common ancestors
# -----------------------------------
currentId = 0
def getUniqueId():
global currentId
currentId += 1
return currentId
# The 'virtual' commit objects have SHAs which are integers
shaRE = re.compile('^[0-9a-f]{40}$')
def isSha(obj):
return (type(obj) is str and bool(shaRE.match(obj))) or \
(type(obj) is int and obj >= 1)
class Commit(object):
__slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
'virtual']
def __init__(self, sha, parents, tree=None):
self.parents = parents
self.firstLineMsg = None
self.children = []
if tree:
tree = tree.rstrip()
assert(isSha(tree))
self._tree = tree
if not sha:
self.sha = getUniqueId()
self.virtual = True
self.firstLineMsg = 'virtual commit'
assert(isSha(tree))
else:
self.virtual = False
self.sha = sha.rstrip()
assert(isSha(self.sha))
def tree(self):
self.getInfo()
assert(self._tree != None)
return self._tree
def shortInfo(self):
self.getInfo()
return str(self.sha) + ' ' + self.firstLineMsg
def __str__(self):
return self.shortInfo()
def getInfo(self):
if self.virtual or self.firstLineMsg != None:
return
else:
info = runProgram(['git-cat-file', 'commit', self.sha])
info = info.split('\n')
msg = False
for l in info:
if msg:
self.firstLineMsg = l
break
else:
if l.startswith('tree'):
self._tree = l[5:].rstrip()
elif l == '':
msg = True
class Graph:
def __init__(self):
self.commits = []
self.shaMap = {}
def addNode(self, node):
assert(isinstance(node, Commit))
self.shaMap[node.sha] = node
self.commits.append(node)
for p in node.parents:
p.children.append(node)
return node
def reachableNodes(self, n1, n2):
res = {}
def traverse(n):
res[n] = True
for p in n.parents:
traverse(p)
traverse(n1)
traverse(n2)
return res
def fixParents(self, node):
for x in range(0, len(node.parents)):
node.parents[x] = self.shaMap[node.parents[x]]
# addDebug('buildGraph')
def buildGraph(heads):
debug('buildGraph heads:', heads)
for h in heads:
assert(isSha(h))
g = Graph()
out = runProgram(['git-rev-list', '--parents'] + heads)
for l in out.split('\n'):
if l == '':
continue
shas = l.split(' ')
# This is a hack, we temporarily use the 'parents' attribute
# to contain a list of SHA1:s. They are later replaced by proper
# Commit objects.
c = Commit(shas[0], shas[1:])
g.commits.append(c)
g.shaMap[c.sha] = c
for c in g.commits:
g.fixParents(c)
for c in g.commits:
for p in c.parents:
p.children.append(c)
return g
# Write the empty tree to the object database and return its SHA1
def writeEmptyTree():
tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
def delTmpIndex():
try:
os.unlink(tmpIndex)
except OSError:
pass
delTmpIndex()
newEnv = os.environ.copy()
newEnv['GIT_INDEX_FILE'] = tmpIndex
res = runProgram(['git-write-tree'], env=newEnv).rstrip()
delTmpIndex()
return res
def addCommonRoot(graph):
roots = []
for c in graph.commits:
if len(c.parents) == 0:
roots.append(c)
superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
graph.addNode(superRoot)
for r in roots:
r.parents = [superRoot]
superRoot.children = roots
return superRoot
def getCommonAncestors(graph, commit1, commit2):
'''Find the common ancestors for commit1 and commit2'''
assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
def traverse(start, set):
stack = [start]
while len(stack) > 0:
el = stack.pop()
set.add(el)
for p in el.parents:
if p not in set:
stack.append(p)
h1Set = Set()
h2Set = Set()
traverse(commit1, h1Set)
traverse(commit2, h2Set)
shared = h1Set.intersection(h2Set)
if len(shared) == 0:
shared = [addCommonRoot(graph)]
res = Set()
for s in shared:
if len([c for c in s.children if c in shared]) == 0:
res.add(s)
return list(res)

View File

@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
TSVN = $(wildcard t91[0-9][0-9]-*.sh)
ifdef NO_PYTHON
GIT_TEST_OPTS += --no-python
endif
all: $(T) clean
$(T):

View File

@ -20,10 +20,10 @@ modification *should* take notice and update the test vectors here.
################################################################
# It appears that people are getting bitten by not installing
# 'merge' (usually part of RCS package in binary distributions)
# or have too old python without subprocess. Check them and error
# out before running any tests. Also catch the bogosity of trying
# to run tests without building while we are at it.
# 'merge' (usually part of RCS package in binary distributions).
# Check this and error out before running any tests. Also catch
# the bogosity of trying to run tests without building while we
# are at it.
../git >/dev/null
if test $? != 1
@ -42,12 +42,6 @@ fi
. ./test-lib.sh
test "$no_python" || "$PYTHON" -c 'import subprocess' || {
echo >&2 'Your python seem to lack "subprocess" module.
Please check INSTALL document.'
exit 1
}
################################################################
# init-db has been done in an empty repository.
# make sure it is empty.

View File

@ -76,7 +76,8 @@ do
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
--no-python)
no_python=t; shift ;;
# noop now...
shift ;;
*)
break ;;
esac
@ -210,18 +211,6 @@ GIT_EXEC_PATH=$(pwd)/..
HOME=$(pwd)/trash
export PATH GIT_EXEC_PATH HOME
# Similarly use ../compat/subprocess.py if our python does not
# have subprocess.py on its own.
PYTHON=`sed -e '1{
s/^#!//
q
}' ../git-merge-recursive-old` || {
error "You haven't built things yet, have you?"
}
"$PYTHON" -c 'import subprocess' 2>/dev/null || {
PYTHONPATH=$(pwd)/../compat
export PYTHONPATH
}
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
export GITPERLLIB
test -d ../templates/blt || {