Hi,

I have cleaned up the code for the merge algorithm quite a lot. It is
in a much better shape now.

The main changes compared to the previous version are:

* Lots of clean up.
* Some of the scripts have been renamed to better match the naming
  convention used in Git.
* A new option ('-s') has been added to git-merge-cache
* Unclean merges are detected and reported
* Clean merges are committed

The user interface has been changed to:

   git-merge-script <branch> <message>

HEAD will be merged with <branch>. If the merge turns out to be clean
then it will be committed with the message <message>. The working
directory will always be updated to reflect the result of the merge.

git-merge-cache has a new flag, '-s'. When this flag is given
git-merge-cache will only run the merge-script once, instead of once
per cache entry. The needed information is written to the
merge-scripts stdin in the following form:

<oSha> SP <aSha> SP <bSha> SP <oMode> SP <aMode> SP <bMode> SP <path> NUL

where

   <oSha>  - original file SHA1 (or empty)
   <aSha>  - file in branch1 SHA1 (or empty)
   <bSha>  - file in branch2 SHA1 (or empty)
   <oMode> - orignal file mode (or empty)
   <aMode> - file in branch1 mode (or empty)
   <bMode> - file in branch2 mode (or empty)
   <path>  - pathname in repository

SP is a single space and NUL is \0. An empty SHA1 is represented as
'0000000000000000000000000000000000000000' and an empty mode is
represented with '0'.

This flag is nice to have when you have a merge-script which needs to
keep some state between different cache entries.

- Fredrik

Signed-off-by: Fredrik Kuivinen <[EMAIL PROTECTED]>

---

 Makefile               |    3 -
 git-merge-files-script |  188 ++++++++++++++++++++++++++++++++
 git-merge-script       |   94 ++++++++++++++++
 gitMergeCommon.py      |   86 +++++++++++++++
 gitMergeCore.py        |  281 ++++++++++++++++++++++++++++++++++++++++++++++++
 merge-cache.c          |   78 +++++++++++++
 read-tree.c            |   13 ++
 7 files changed, 737 insertions(+), 6 deletions(-)
 create mode 100755 git-merge-files-script
 create mode 100755 git-merge-script
 create mode 100644 gitMergeCommon.py
 create mode 100644 gitMergeCore.py

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -66,7 +66,8 @@ SCRIPTS=git git-apply-patch-script git-m
        git-format-patch-script git-sh-setup-script git-push-script \
        git-branch-script git-parse-remote-script git-verify-tag-script \
        git-ls-remote-script git-clone-dumb-http git-rename-script \
-       git-request-pull-script git-bisect-script
+       git-request-pull-script git-bisect-script git-merge-script \
+       gitMergeCommon.py gitMergeCore.py git-merge-files-script
 
 SCRIPTS += git-count-objects-script
 SCRIPTS += git-revert-script
diff --git a/git-merge-files-script b/git-merge-files-script
new file mode 100755
--- /dev/null
+++ b/git-merge-files-script
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+#
+# This script is based on 'git-merge-one-file-script' in the Git
+# distribution.
+#
+# This script is supposed to be executed by git-merge-cache.  When
+# git-merge-cache is used with the '-s' flag it will write entries to
+# the executed programs stdin. The syntax of one entry is:
+#
+# <oSha> SP <aSha> SP <bSha> SP <oMode> SP <aMode> SP <bMode> SP <path> NUL
+#
+#   <oSha>  - original file SHA1 (or empty)
+#   <aSha>  - file in branch1 SHA1 (or empty)
+#   <bSha>  - file in branch2 SHA1 (or empty)
+#   <oMode> - orignal file mode (or empty)
+#   <aMode> - file in branch1 mode (or empty)
+#   <bMode> - file in branch2 mode (or empty)
+#   <path>  - pathname in repository
+#
+# SP is a single space and NUL is \0. An empty SHA1 is represented as
+# '0000000000000000000000000000000000000000' and an empty mode is
+# represented with '0'.
+
+import sys, os, re
+from gitMergeCommon import *
+from sets import Set
+
+# TODO
+# Generate a unique file name when we get a name conflict.
+
+# x <==> sha != '', - <==> sha == '' 
+#
+# Case  o a b
+# D     x x x
+# A     x x -
+# A     x - x
+# A     x - -
+# C     - x x
+# B     - x -
+# B     - - x
+#       - - - Doesn't happen
+def processEntry(oSha, aSha, bSha, oMode, aMode, bMode, path):
+    global cleanMerge
+    debug('processing', path)
+    if oSha == '0'*40:
+        oSha = ''
+    if aSha == '0'*40:
+        aSha = ''
+    if bSha == '0'*40:
+        bSha = ''
+    if oMode == '0':
+        oMode = ''
+    if aMode == '0':
+        aMode = ''
+    if bMode == '0':
+        bMode = ''
+
+    if (oSha != '' and (aSha == '' or bSha == '')):
+    #
+    # Case A: Deleted in one
+    #
+        if (aSha == ''   and bSha == '') or \
+           (aSha == oSha and bSha == '') or \
+           (aSha == ''   and bSha == oSha):
+    # Deleted in both or deleted in one and unchanged in the other
+            print 'Removing ' + path
+            runProgram(['git-update-cache', '--force-remove', '--', path])
+        else:
+    # Deleted in one and changed in the other
+            cleanMerge = False
+            if aSha == '':
+                print 'CONFLICT (del/mod): "' + path + '" deleted in', \
+                      branch1, 'and modified in', branch2, '. Version', 
branch2, ' of "' + path + '" left in tree'
+                mode = bMode
+                sha = bSha
+            else:
+                print 'CONFLICT (mod/del): "' + path + '" deleted in', 
branch2, 'and modified in', branch1, \
+                      '. Version', branch1, 'of "' + path + '" left in tree'
+                mode = aMode
+                sha = aSha
+            runProgram(['git-update-cache', '--cacheinfo', mode, sha, path])
+    
+    elif (oSha == '' and aSha != '' and bSha == '') or \
+         (oSha == '' and aSha == '' and bSha != ''):
+    #
+    # Case B: Added in one.
+    #
+        if aSha != '':
+            addBranch = branch1
+            otherBranch = branch2
+            conf = 'file/dir'
+        else:
+            addBranch = branch2
+            otherBranch = branch1
+            conf = 'dir/file'
+    
+        if path in dirs:
+            cleanMerge = False
+            newPath = path + '_' + addBranch
+            print 'CONFLICT (' + conf + '): There is a directory with name "' 
+ path + '" in', otherBranch, \
+                  '. Adding "' + path + '" as "' + newPath + '"'
+            runProgram(['git-update-cache', '--force-remove', '--', path])
+            path = newPath
+        else:
+            print 'Adding "' + path + '"'
+        runProgram(['git-update-cache', '--add', '--cacheinfo', aMode+bMode, 
aSha+bSha, path])
+    
+    elif oSha == '' and aSha != '' and bSha != '':
+    #
+    # Case C: Added in both (check for same permissions).
+    #
+        if aSha == bSha:
+            if aMode != bMode:
+                cleanMerge = False
+                print 'CONFLICT: File "' + path + '" added identically in both 
branches,'
+                print 'CONFLICT: but permissions conflict', aMode, '->', bMode
+                print 'CONFLICT: adding with permission:', aMode
+                runProgram(['git-update-cache', '--add', '--cacheinfo', aMode, 
aSha, path])
+            else:
+                # This case is handled by git-read-tree
+                print 'Adding', path
+                runProgram(['git-update-cache', '--add', '--cacheinfo', aMode, 
aSha, path])
+        else:
+            cleanMerge = False
+            newPath1 = path + '_' + branch1
+            newPath2 = path + '_' + branch2
+            print 'CONFLICT (add/add): File "' + path + '" added 
non-identically in both branches.', \
+                  'Adding "' + newPath1 + '" and "' + newPath2 + '" instead.'
+            runProgram(['git-update-cache', '--force-remove', '--', path])
+            runProgram(['git-update-cache', '--add',
+                        '--cacheinfo', aMode, aSha, newPath1,
+                        '--cacheinfo', bMode, bSha, newPath2])
+    
+    elif oSha != '' and aSha != '' and bSha != '':
+    #
+    # case D: Modified in both, but differently.
+    #
+        print 'Auto-merging', path 
+        orig = runProgram(['git-unpack-file', oSha]).rstrip()
+        src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+        src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+        [out, ret] = runProgram(['merge',
+                                 '-L', os.environ['GIT_MERGE_BRANCH_1'] + '/' 
+ path,
+                                 '-L', 'orig/' + path,
+                                 '-L', os.environ['GIT_MERGE_BRANCH_2'] + '/' 
+ path,
+                                 src1, orig, src2], returnCode=True)
+    
+        if ret != 0:
+            cleanMerge = False
+            print 'CONFLICT (content): Merge conflict in "' + path + '".'
+    
+        if aMode == oMode:
+            mode = bMode
+        else:
+            mode = aMode
+    
+        sha = runProgram(['git-hash-object', '-t', 'blob', '-w', src1])
+        runProgram(['git-update-cache', '--cacheinfo', aMode, sha, path])
+        os.unlink(orig)
+        os.unlink(src1)
+        os.unlink(src2)
+    else:
+        print 'ERROR: Fatal merge failure.'
+        print "ERROR: Shouldn't happen"
+        sys.exit(1)
+
+[files, dirs] = eval(open(os.environ['GIT_MERGE_DIRS']).read())
+branch1 = os.environ['GIT_MERGE_BRANCH_1']
+branch2 = os.environ['GIT_MERGE_BRANCH_2']
+cleanMerge = True
+
+input = sys.stdin.read()
+entries = input.split("\0")
+entries.pop() # remove last entry (which is '')
+entryRE = re.compile('^([0-9a-f]{40}) ([0-9a-f]{40}) ([0-9a-f]{40}) ([0-9]+) 
([0-9]+) ([0-9]+) (.*)$')
+debug('starting processing...', repr(entries))
+for entry in entries:
+    m = entryRE.match(entry)
+    if m:
+        processEntry(m.group(1), m.group(2), m.group(3), m.group(4),
+                     m.group(5), m.group(6), m.group(7))
+    else:
+        print sys.argv[0], 'Invalid input from merge program:', entry
+        sys.exit(1)
+
+if not cleanMerge:
+    sys.exit(1)
+
diff --git a/git-merge-script b/git-merge-script
new file mode 100755
--- /dev/null
+++ b/git-merge-script
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+from gitMergeCommon import *
+from gitMergeCore import buildGraph, merge
+import sys
+
+def dropHeads():
+    try:
+        os.unlink(os.environ['GIT_DIR'] + '/MERGE_HEAD')
+    except OSError:
+        pass
+
+    try:
+        os.unlink(os.environ['GIT_DIR'] + '/LAST_MERGE')
+    except OSError:
+        pass
+
+def writeHead(head, sha):
+    if sha[-1] != '\n':
+        sha += '\n'
+
+    try:
+        f = open(os.environ['GIT_DIR'] + '/' + head, 'w')
+        f.write(sha)
+        f.close()
+    except IOError, e:
+        print 'Failed to write to', os.environ['GIT_DIR'] + '/' + head + ':', 
e.strerror
+        sys.exit(1)
+    return True
+
+def checkCleanTree():
+    [out, code] = runProgram(['git-update-cache', '--refresh'], returnCode = 
True)
+
+    if code != 0:
+        return False
+    out = runProgram(['git-diff-cache', '--name-only', '--cached', 'HEAD'])
+    if len(out) > 0:
+        return False
+
+    return True
+
+def doCommit(msg, p1, p2):
+    tree = runProgram(['git-write-tree'])
+    tree = tree.rstrip()
+
+    commit = runProgram(['git-commit-tree', tree, '-p', p1, '-p', p2], msg + 
'\n')
+    writeHead('HEAD', commit)
+    return commit.rstrip()
+
+if len(sys.argv) < 2:
+    print 'Usage:', sys.argv[0], '<branch> <msg>'
+    sys.exit(1)
+
+setupEnvironment()
+repoValid()
+
+if not checkCleanTree():
+    print 'Either the cache is out of sync or the working tree does not match 
HEAD.'
+    print 'Aborting merge.'
+    sys.exit(1)
+
+h1 = firstBranch = 'HEAD'
+h2 = secondBranch = sys.argv[1]
+commitMessage = sys.argv[2]
+
+print 'Merging', h1, 'with', h2
+h1 = runProgram(['git-rev-parse', '--revs-only', h1]).rstrip()
+h2 = runProgram(['git-rev-parse', '--revs-only', h2]).rstrip()
+print 'Resolved heads: h1:', h1, 'h2:', h2
+
+writeHead('ORIG_HEAD', h1)
+writeHead('LAST_MERGE', h2)
+
+print 'Building graph...'
+graph = buildGraph([h1, h2])
+print 'graph done.'
+
+[res, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], firstBranch, 
secondBranch, graph)
+print 'Merge result: (tree)', res.tree()
+
+# Checkout the merge results
+runProgram(['git-read-tree', 'HEAD'])
+runProgram(['git-update-cache', '--refresh'])
+runProgram(['git-read-tree', '-m', '-u', 'HEAD', res.tree()])
+
+if clean:
+    cmit = doCommit(commitMessage, h1, h2)
+    print 'Committed merge', cmit
+    print runProgram('git-diff-tree -p ORIG_HEAD ' + cmit + ' | git-apply 
--stat')
+    dropHeads()
+else:
+    print 'Automatic merge failed, fix up by hand'
+    writeHead('MERGE_HEAD', h2)
+    sys.exit(1)
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
new file mode 100644
--- /dev/null
+++ b/gitMergeCommon.py
@@ -0,0 +1,86 @@
+import sys
+
+if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and 
sys.version_info[1] < 4):
+    print 'Python version 2.4 required, found', \
+    
str(sys.version_info[0])+'.'+str(sys.version_info[1])+'.'+str(sys.version_info[2])
+    sys.exit(1)
+
+import subprocess, os, sets, traceback
+
+DEBUG = 0
+
+functionsToDebug = sets.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)
+
+def printList(list):
+    for x in list:
+        sys.stdout.write(str(x))
+        sys.stdout.write(' ')
+    sys.stdout.write('\n')
+
+class ProgramError(Exception):
+    def __init__(self, progStr, error):
+        self.progStr = progStr
+        self.error = error
+
+# addDebug('runProgram')
+def runProgram(prog, input=None, returnCode=False, env=None):
+    debug('runProgram prog:', str(prog), 'input:', str(input))
+    if type(prog) is str:
+        progStr = prog
+    else:
+        progStr = ' '.join(prog)
+    
+    try:
+        pop = subprocess.Popen(prog,
+                               shell = type(prog) is str,
+                               stderr=subprocess.STDOUT,
+                               stdout=subprocess.PIPE,
+                               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()
+    
+    out = pop.stdout.read()
+    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
+
+def setupEnvironment():
+    if 'GIT_DIR' not in os.environ:
+        os.environ['GIT_DIR'] = '.git'
+
+    if not os.environ.has_key('GIT_OBJECT_DIRECTORY'):
+        os.environ['GIT_OBJECT_DIRECTORY'] = os.environ['GIT_DIR'] + '/objects'
+
+def repoValid():
+    if not (os.path.exists(os.environ['GIT_DIR']) and
+            os.path.exists(os.environ['GIT_DIR'] + '/refs') and
+            os.path.exists(os.environ['GIT_OBJECT_DIRECTORY']) and
+            os.path.exists(os.environ['GIT_OBJECT_DIRECTORY'] + '/00')):
+        print "Not a Git archive"
+        sys.exit(1)
diff --git a/gitMergeCore.py b/gitMergeCore.py
new file mode 100644
--- /dev/null
+++ b/gitMergeCore.py
@@ -0,0 +1,281 @@
+import sys, math, random, os, re, signal, tempfile
+from gitMergeCommon import runProgram, debug, addDebug, printList
+from heapq import heappush, heappop
+from sets import Set
+
+currentId = 0
+def getUniqueId():
+    global currentId
+    currentId += 1
+    return currentId
+
+class Commit:
+    def __init__(self, sha, parents, tree=None):
+        self.parents = parents
+        self.firstLineMsg = None
+        self.children = []
+
+        if tree:
+            tree = tree.rstrip()
+        self._tree = tree
+
+        if not sha:
+            self.sha = getUniqueId()
+            self.virtual = True
+            self.firstLineMsg = 'virtual commit'
+            assert(tree != None)
+        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['GIT_DIR'] + '/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 findShared(h1, h2):
+    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(h1, h1Set)
+    traverse(h2, h2Set)
+
+    return h1Set.intersection(h2Set)
+
+def sharedHeads(shared):
+    h = Set()
+
+    for s in shared:
+        if len([c for c in s.children if c in shared]) == 0:
+            h.add(s)
+    return list(h)
+
+getFilesRE = re.compile('([0-9]+) ([a-z0-9]+) ([0-9a-f]{40})\t(.*)')
+def getFilesAndDirs(tree1, tree2):
+    files = Set()
+    dirs = Set()
+    def addFilesDirs(tree):
+        out = runProgram(['git-ls-tree', '-r', '-z', tree])
+        for l in out.split('\0'):
+            m = getFilesRE.match(l)
+            if m:
+                if m.group(2) == 'tree':
+                    dirs.add(m.group(4))
+                elif m.group(2) == 'blob':
+                    files.add(m.group(4))
+
+    addFilesDirs(tree1)
+    addFilesDirs(tree2)
+    return [files, dirs]
+
+def merge(h1, h2, branch1Name, branch2Name, graph, indent=0, first=True):
+    def infoMsg(*args):
+        sys.stdout.write('  '*indent)
+        printList(args)
+    infoMsg('Merging:')
+    infoMsg(h1)
+    infoMsg(h2)
+    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
+    assert(isinstance(graph, Graph))
+
+    s = findShared(h1, h2)
+    if len(s) == 0:
+        s = [addCommonRoot(graph)]
+
+    # infoMsg('found', len(s), 'shared commits.')
+    # if len(s) < 10:
+    #    for x in s:
+    #        infoMsg(x)
+    sys.stdout.flush()
+    s = sharedHeads(s)
+    infoMsg('found', len(s), 'shared head(s):')
+    for x in s :
+        infoMsg(x)
+    Ms = s[0]
+
+    for h in s[1:]:
+        [Ms, ignore] = merge(Ms, h, branch1Name, branch2Name, graph, indent+1, 
False)
+
+    filesDirs = getFilesAndDirs(h1.tree(), h2.tree())
+    
+    infoMsg('running resolve on')
+    infoMsg(h1)
+    infoMsg(h2)
+
+    if first:
+        b1 = branch1Name
+        b2 = branch2Name
+    else:
+        b1 = 'Temporary shared merge branch 1'
+        b2 = 'Temporary shared merge branch 2'
+    [shaRes, clean] = resolve(h1.tree(), h2.tree(), Ms.tree(), b1, b2, 
filesDirs)
+
+    res = Commit(None, [h1, h2], tree=shaRes)
+    graph.addNode(res)
+    # infoMsg('merge returned: (tree)',  res.tree())
+    return [res, clean]
+
+# 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)
+
+def resolve(head, merge, common, b1, b2, filesDirs):
+    assert(isSha(head) and isSha(merge) and isSha(common))
+    head = runProgram(['git-rev-parse', '--revs-only', head]).rstrip()
+    merge = runProgram(['git-rev-parse', '--revs-only', merge]).rstrip()
+
+    if common == merge:
+        return head
+
+    if common == head:
+        return merge
+
+    runProgram(['git-read-tree', head])
+    runProgram(['git-read-tree', '-i', '-m', common, head, merge])
+    os.environ['GIT_MERGE_BRANCH_1'] = b1
+    os.environ['GIT_MERGE_BRANCH_2'] = b2
+    fdirs = tempfile.NamedTemporaryFile('w')
+    fdirs.write(repr(filesDirs))
+    fdirs.flush()
+    os.environ['GIT_MERGE_DIRS'] = fdirs.name
+    try:
+        [tree, code] = runProgram('git-write-tree', returnCode=True)
+        tree = tree.rstrip()
+        if code != 0:
+            [out, code] = runProgram(['git-merge-cache', '-s',
+                                      'git-merge-files-script', '-a'],
+                                     returnCode=True)
+            print out
+            if code != 0:
+                # Non-clean merge
+                clean = False
+            else:
+                clean = True
+            tree = runProgram('git-write-tree').rstrip()
+        else:
+            clean = True
+    finally:
+        fdirs.close()
+
+    return [tree, clean]
+
diff --git a/merge-cache.c b/merge-cache.c
--- a/merge-cache.c
+++ b/merge-cache.c
@@ -5,7 +5,7 @@
 
 static const char *pgm = NULL;
 static const char *arguments[8];
-static int one_shot, quiet;
+static int one_shot, quiet, use_stdin, stdin_fd, pgm_pid;
 static int err;
 
 static void run_program(void)
@@ -37,6 +37,59 @@ static void run_program(void)
        }
 }
 
+static void start_program(void)
+{
+       int pid, fds[2];
+
+       if (pipe(fds) < 0)
+               die("pipe failed: %s", strerror(errno));
+
+       pid = fork();
+       if (pid < 0)
+               die("unable to fork");
+
+       if (!pid) {
+               close(fds[1]);
+               dup2(fds[0], 0);
+               close(fds[0]);
+               execlp(pgm, pgm, NULL);
+               die("unable to execute '%s': %s", pgm, strerror(errno));
+       }
+
+       pgm_pid = pid;
+       close(fds[0]);
+       stdin_fd = fds[1];
+}
+
+static void die_write(int fd, const char* buf, int len)
+{
+       if (write(fd, buf, len) != len)
+               die("unable to write: %s", strerror(errno));
+}
+
+static void write_to_program(void)
+{
+       int arg;
+       for(arg = 1; arg < 4; arg++) {
+               if(!strcmp("", arguments[arg]))
+                       die_write(stdin_fd, 
"0000000000000000000000000000000000000000", 40);
+               else
+                       die_write(stdin_fd, arguments[arg], 40);
+               die_write(stdin_fd, " ", 1);
+       }
+
+       for(arg = 5; arg < 8; arg++) {
+               if(!strcmp("", arguments[arg]))
+                       die_write(stdin_fd, "0", 1);
+               else
+                       die_write(stdin_fd, arguments[arg], 
strlen(arguments[arg]));
+               die_write(stdin_fd, " ", 1);
+       }
+
+       /* Make sure we write the terminating \0 too. */
+       die_write(stdin_fd, arguments[4], strlen(arguments[4])+1);
+}
+
 static int merge_entry(int pos, const char *path)
 {
        int found;
@@ -68,7 +121,11 @@ static int merge_entry(int pos, const ch
        } while (++pos < active_nr);
        if (!found)
                die("git-merge-cache: %s not in the cache", path);
-       run_program();
+
+        if(use_stdin)
+            write_to_program();
+        else
+            run_program();
        return found;
 }
 
@@ -100,7 +157,7 @@ int main(int argc, char **argv)
        int i, force_file = 0;
 
        if (argc < 3)
-               usage("git-merge-cache [-o] [-q] <merge-program> (-a | 
<filename>*)");
+               usage("git-merge-cache [-o] [-q] [-s] <merge-program> (-a | 
<filename>*)");
 
        read_cache();
 
@@ -113,7 +170,14 @@ int main(int argc, char **argv)
                quiet = 1;
                i++;
        }
+        if (!strcmp(argv[i], "-s")) {
+            use_stdin = 1;
+            i++;
+        }
        pgm = argv[i++];
+        if(use_stdin)
+            start_program();
+        
        for (; i < argc; i++) {
                char *arg = argv[i];
                if (!force_file && *arg == '-') {
@@ -129,6 +193,14 @@ int main(int argc, char **argv)
                }
                merge_file(arg);
        }
+
+        if(use_stdin) {
+            int status;
+            close(stdin_fd);
+            if (waitpid(pgm_pid, &status, 0) < 0 || !WIFEXITED(status) || 
WEXITSTATUS(status))
+                err = 1;
+       }
+
        if (err && !quiet)
                die("merge program failed");
        return err;
diff --git a/read-tree.c b/read-tree.c
--- a/read-tree.c
+++ b/read-tree.c
@@ -7,6 +7,7 @@
 
 static int stage = 0;
 static int update = 0;
+static int ignore_working_dir = 0;
 
 static int unpack_tree(unsigned char *sha1)
 {
@@ -80,7 +81,10 @@ static void verify_uptodate(struct cache
 {
        struct stat st;
 
-       if (!lstat(ce->name, &st)) {
+        if (ignore_working_dir)
+            return;
+
+        if (!lstat(ce->name, &st)) {
                unsigned changed = ce_match_stat(ce, &st);
                if (!changed)
                        return;
@@ -510,7 +514,7 @@ static int read_cache_unmerged(void)
        return deleted;
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u] <sha1> 
[<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u] [-i] 
<sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
 
@@ -535,6 +539,11 @@ int main(int argc, char **argv)
                        continue;
                }
 
+                if (!strcmp(arg, "-i")) {
+                        ignore_working_dir = 1;
+                        continue;
+                }
+
                /* This differs from "-m" in that we'll silently ignore 
unmerged entries */
                if (!strcmp(arg, "--reset")) {
                        if (stage || merge || emu23)
-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to