3 # Copyright (C) 2005 Fredrik Kuivinen
7 sys
.path
.append('''@@GIT_PYTHON_PATH@@''')
9 import math
, random
, os
, re
, signal
, tempfile
, stat
, errno
, traceback
10 from heapq
import heappush
, heappop
13 from gitMergeCommon
import *
17 sys
.stdout
.write(' '*outputIndent
)
20 originalIndexFile
= os
.environ
.get('GIT_INDEX_FILE',
21 os
.environ
.get('GIT_DIR', '.git') + '/index')
22 temporaryIndexFile
= os
.environ
.get('GIT_DIR', '.git') + \
23 '/merge-recursive-tmp-index'
24 def setupIndex(temporary
):
26 os
.unlink(temporaryIndexFile
)
30 newIndex
= temporaryIndexFile
32 newIndex
= originalIndexFile
33 os
.environ
['GIT_INDEX_FILE'] = newIndex
35 # This is a global variable which is used in a number of places but
36 # only written to in the 'merge' function.
38 # cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
39 # don't update the working directory.
40 # False => Leave unmerged entries in the cache and update
41 # the working directory.
45 # The entry point to the merge code
46 # ---------------------------------
48 def merge(h1
, h2
, branch1Name
, branch2Name
, graph
, callDepth
=0):
49 '''Merge the commits h1 and h2, return the resulting virtual
50 commit object and a flag indicating the cleaness of the merge.'''
51 assert(isinstance(h1
, Commit
) and isinstance(h2
, Commit
))
52 assert(isinstance(graph
, Graph
))
61 ca
= getCommonAncestors(graph
, h1
, h2
)
62 output('found', len(ca
), 'common ancestor(s):')
69 outputIndent
= callDepth
+1
70 [mergedCA
, dummy
] = merge(mergedCA
, h
,
71 'Temporary merge branch 1',
72 'Temporary merge branch 2',
74 outputIndent
= callDepth
75 assert(isinstance(mergedCA
, Commit
))
83 runProgram(['git-read-tree', h1
.tree()])
86 [shaRes
, clean
] = mergeTrees(h1
.tree(), h2
.tree(), mergedCA
.tree(),
87 branch1Name
, branch2Name
)
89 if clean
or cacheOnly
:
90 res
= Commit(None, [h1
, h2
], tree
=shaRes
)
97 getFilesRE
= re
.compile(r
'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re
.S
)
98 def getFilesAndDirs(tree
):
101 out
= runProgram(['git-ls-tree', '-r', '-z', '-t', tree
])
102 for l
in out
.split('\0'):
103 m
= getFilesRE
.match(l
)
105 if m
.group(2) == 'tree':
107 elif m
.group(2) == 'blob':
108 files
.add(m
.group(4))
112 # Those two global variables are used in a number of places but only
113 # written to in 'mergeTrees' and 'uniquePath'. They keep track of
114 # every file and directory in the two branches that are about to be
116 currentFileSet
= None
117 currentDirectorySet
= None
119 def mergeTrees(head
, merge
, common
, branch1Name
, branch2Name
):
120 '''Merge the trees 'head' and 'merge' with the common ancestor
121 'common'. The name of the head branch is 'branch1Name' and the name of
122 the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
123 where tree is the resulting tree and cleanMerge is True iff the
126 assert(isSha(head
) and isSha(merge
) and isSha(common
))
129 output('Already uptodate!')
137 [out
, code
] = runProgram(['git-read-tree', updateArg
, '-m',
138 common
, head
, merge
], returnCode
= True)
140 die('git-read-tree:', out
)
142 [tree
, code
] = runProgram('git-write-tree', returnCode
=True)
145 global currentFileSet
, currentDirectorySet
146 [currentFileSet
, currentDirectorySet
] = getFilesAndDirs(head
)
147 [filesM
, dirsM
] = getFilesAndDirs(merge
)
148 currentFileSet
.union_update(filesM
)
149 currentDirectorySet
.union_update(dirsM
)
151 entries
= unmergedCacheEntries()
152 renamesHead
= getRenames(head
, common
, head
, merge
, entries
)
153 renamesMerge
= getRenames(merge
, common
, head
, merge
, entries
)
155 cleanMerge
= processRenames(renamesHead
, renamesMerge
,
156 branch1Name
, branch2Name
)
157 for entry
in entries
:
160 if not processEntry(entry
, branch1Name
, branch2Name
):
163 if cleanMerge
or cacheOnly
:
164 tree
= runProgram('git-write-tree').rstrip()
170 return [tree
, cleanMerge
]
172 # Low level file merging, update and removal
173 # ------------------------------------------
175 def mergeFile(oPath
, oSha
, oMode
, aPath
, aSha
, aMode
, bPath
, bSha
, bMode
,
176 branch1Name
, branch2Name
):
181 if stat
.S_IFMT(aMode
) != stat
.S_IFMT(bMode
):
183 if stat
.S_ISREG(aMode
):
190 if aSha
!= oSha
and bSha
!= oSha
:
202 elif stat
.S_ISREG(aMode
):
203 assert(stat
.S_ISREG(bMode
))
205 orig
= runProgram(['git-unpack-file', oSha
]).rstrip()
206 src1
= runProgram(['git-unpack-file', aSha
]).rstrip()
207 src2
= runProgram(['git-unpack-file', bSha
]).rstrip()
208 [out
, code
] = runProgram(['merge',
209 '-L', branch1Name
+ '/' + aPath
,
210 '-L', 'orig/' + oPath
,
211 '-L', branch2Name
+ '/' + bPath
,
212 src1
, orig
, src2
], returnCode
=True)
214 sha
= runProgram(['git-hash-object', '-t', 'blob', '-w',
223 assert(stat
.S_ISLNK(aMode
) and stat
.S_ISLNK(bMode
))
229 return [sha
, mode
, clean
, merge
]
231 def updateFile(clean
, sha
, mode
, path
):
232 updateCache
= cacheOnly
or clean
233 updateWd
= not cacheOnly
235 return updateFileExt(sha
, mode
, path
, updateCache
, updateWd
)
237 def updateFileExt(sha
, mode
, path
, updateCache
, updateWd
):
242 pathComponents
= path
.split('/')
243 for x
in xrange(1, len(pathComponents
)):
244 p
= '/'.join(pathComponents
[0:x
])
247 createDir
= not stat
.S_ISDIR(os
.lstat(p
).st_mode
)
255 die("Couldn't create directory", p
, e
.strerror
)
257 prog
= ['git-cat-file', 'blob', sha
]
258 if stat
.S_ISREG(mode
):
267 fd
= os
.open(path
, os
.O_WRONLY | os
.O_TRUNC | os
.O_CREAT
, mode
)
268 proc
= subprocess
.Popen(prog
, stdout
=fd
)
271 elif stat
.S_ISLNK(mode
):
272 linkTarget
= runProgram(prog
)
273 os
.symlink(linkTarget
, path
)
277 if updateWd
and updateCache
:
278 runProgram(['git-update-index', '--add', '--', path
])
280 runProgram(['git-update-index', '--add', '--cacheinfo',
281 '0%o' % mode
, sha
, path
])
283 def setIndexStages(path
,
290 istring
.append("0 " + ("0" * 40) + "\t" + path
+ "\0")
292 istring
.append("%o %s %d\t%s\0" % (oMode
, oSHA1
, 1, path
))
294 istring
.append("%o %s %d\t%s\0" % (aMode
, aSHA1
, 2, path
))
296 istring
.append("%o %s %d\t%s\0" % (bMode
, bSHA1
, 3, path
))
298 runProgram(['git-update-index', '-z', '--index-info'],
299 input="".join(istring
))
301 def removeFile(clean
, path
):
302 updateCache
= cacheOnly
or clean
303 updateWd
= not cacheOnly
306 runProgram(['git-update-index', '--force-remove', '--', path
])
312 if e
.errno
!= errno
.ENOENT
and e
.errno
!= errno
.EISDIR
:
315 os
.removedirs(os
.path
.dirname(path
))
319 def uniquePath(path
, branch
):
320 def fileExists(path
):
325 if e
.errno
== errno
.ENOENT
:
330 branch
= branch
.replace('/', '_')
331 newPath
= path
+ '~' + branch
333 while newPath
in currentFileSet
or \
334 newPath
in currentDirectorySet
or \
337 newPath
= path
+ '~' + branch
+ '_' + str(suffix
)
338 currentFileSet
.add(newPath
)
341 # Cache entry management
342 # ----------------------
345 def __init__(self
, path
):
351 # Used for debugging only
353 if self
.mode
!= None:
354 m
= '0%o' % self
.mode
362 return 'sha1: ' + sha1
+ ' mode: ' + m
364 self
.stages
= [Stage(), Stage(), Stage(), Stage()]
366 self
.processed
= False
369 return 'path: ' + self
.path
+ ' stages: ' + repr([str(x
) for x
in self
.stages
])
371 class CacheEntryContainer
:
375 def add(self
, entry
):
376 self
.entries
[entry
.path
] = entry
379 return self
.entries
.get(path
)
382 return self
.entries
.itervalues()
384 unmergedRE
= re
.compile(r
'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re
.S
)
385 def unmergedCacheEntries():
386 '''Create a dictionary mapping file names to CacheEntry
387 objects. The dictionary contains one entry for every path with a
388 non-zero stage entry.'''
390 lines
= runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
393 res
= CacheEntryContainer()
395 m
= unmergedRE
.match(l
)
397 mode
= int(m
.group(1), 8)
399 stage
= int(m
.group(3))
407 e
.stages
[stage
].mode
= mode
408 e
.stages
[stage
].sha1
= sha1
410 die('Error: Merge program failed: Unexpected output from',
414 lsTreeRE
= re
.compile(r
'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re
.S
)
415 def getCacheEntry(path
, origTree
, aTree
, bTree
):
416 '''Returns a CacheEntry object which doesn't have to correspond to
417 a real cache entry in Git's index.'''
423 m
= lsTreeRE
.match(out
)
425 die('Unexpected output from git-ls-tree:', out
)
426 elif m
.group(2) == 'blob':
427 return [m
.group(3), int(m
.group(1), 8)]
431 res
= CacheEntry(path
)
433 [oSha
, oMode
] = parse(runProgram(['git-ls-tree', origTree
, '--', path
]))
434 [aSha
, aMode
] = parse(runProgram(['git-ls-tree', aTree
, '--', path
]))
435 [bSha
, bMode
] = parse(runProgram(['git-ls-tree', bTree
, '--', path
]))
437 res
.stages
[1].sha1
= oSha
438 res
.stages
[1].mode
= oMode
439 res
.stages
[2].sha1
= aSha
440 res
.stages
[2].mode
= aMode
441 res
.stages
[3].sha1
= bSha
442 res
.stages
[3].mode
= bMode
446 # Rename detection and handling
447 # -----------------------------
451 src
, srcSha
, srcMode
, srcCacheEntry
,
452 dst
, dstSha
, dstMode
, dstCacheEntry
,
456 self
.srcMode
= srcMode
457 self
.srcCacheEntry
= srcCacheEntry
460 self
.dstMode
= dstMode
461 self
.dstCacheEntry
= dstCacheEntry
464 self
.processed
= False
466 class RenameEntryContainer
:
471 def add(self
, entry
):
472 self
.entriesSrc
[entry
.srcName
] = entry
473 self
.entriesDst
[entry
.dstName
] = entry
475 def getSrc(self
, path
):
476 return self
.entriesSrc
.get(path
)
478 def getDst(self
, path
):
479 return self
.entriesDst
.get(path
)
482 return self
.entriesSrc
.itervalues()
484 parseDiffRenamesRE
= re
.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
485 def getRenames(tree
, oTree
, aTree
, bTree
, cacheEntries
):
486 '''Get information of all renames which occured between 'oTree' and
487 'tree'. We need the three trees in the merge ('oTree', 'aTree' and
488 'bTree') to be able to associate the correct cache entries with
489 the rename information. 'tree' is always equal to either aTree or bTree.'''
491 assert(tree
== aTree
or tree
== bTree
)
492 inp
= runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
495 ret
= RenameEntryContainer()
497 recs
= inp
.split("\0")
498 recs
.pop() # remove last entry (which is '')
502 m
= parseDiffRenamesRE
.match(rec
)
505 die('Unexpected output from git-diff-tree:', rec
)
507 srcMode
= int(m
.group(1), 8)
508 dstMode
= int(m
.group(2), 8)
515 srcCacheEntry
= cacheEntries
.get(src
)
516 if not srcCacheEntry
:
517 srcCacheEntry
= getCacheEntry(src
, oTree
, aTree
, bTree
)
518 cacheEntries
.add(srcCacheEntry
)
520 dstCacheEntry
= cacheEntries
.get(dst
)
521 if not dstCacheEntry
:
522 dstCacheEntry
= getCacheEntry(dst
, oTree
, aTree
, bTree
)
523 cacheEntries
.add(dstCacheEntry
)
525 ret
.add(RenameEntry(src
, srcSha
, srcMode
, srcCacheEntry
,
526 dst
, dstSha
, dstMode
, dstCacheEntry
,
528 except StopIteration:
532 def fmtRename(src
, dst
):
533 srcPath
= src
.split('/')
534 dstPath
= dst
.split('/')
536 endIndex
= min(len(srcPath
), len(dstPath
)) - 1
537 for x
in range(0, endIndex
):
538 if srcPath
[x
] == dstPath
[x
]:
539 path
.append(srcPath
[x
])
545 return '/'.join(path
) + \
546 '/{' + '/'.join(srcPath
[endIndex
:]) + ' => ' + \
547 '/'.join(dstPath
[endIndex
:]) + '}'
549 return src
+ ' => ' + dst
551 def processRenames(renamesA
, renamesB
, branchNameA
, branchNameB
):
554 srcNames
.add(x
.srcName
)
556 srcNames
.add(x
.srcName
)
559 for path
in srcNames
:
560 if renamesA
.getSrc(path
):
563 branchName1
= branchNameA
564 branchName2
= branchNameB
568 branchName1
= branchNameB
569 branchName2
= branchNameA
571 ren1
= renames1
.getSrc(path
)
572 ren2
= renames2
.getSrc(path
)
574 ren1
.dstCacheEntry
.processed
= True
575 ren1
.srcCacheEntry
.processed
= True
580 ren1
.processed
= True
583 # Renamed in 1 and renamed in 2
584 assert(ren1
.srcName
== ren2
.srcName
)
585 ren2
.dstCacheEntry
.processed
= True
586 ren2
.processed
= True
588 if ren1
.dstName
!= ren2
.dstName
:
589 output('CONFLICT (rename/rename): Rename',
590 fmtRename(path
, ren1
.dstName
), 'in branch', branchName1
,
591 'rename', fmtRename(path
, ren2
.dstName
), 'in',
595 if ren1
.dstName
in currentDirectorySet
:
596 dstName1
= uniquePath(ren1
.dstName
, branchName1
)
597 output(ren1
.dstName
, 'is a directory in', branchName2
,
598 'adding as', dstName1
, 'instead.')
599 removeFile(False, ren1
.dstName
)
601 dstName1
= ren1
.dstName
603 if ren2
.dstName
in currentDirectorySet
:
604 dstName2
= uniquePath(ren2
.dstName
, branchName2
)
605 output(ren2
.dstName
, 'is a directory in', branchName1
,
606 'adding as', dstName2
, 'instead.')
607 removeFile(False, ren2
.dstName
)
609 dstName2
= ren2
.dstName
610 setIndexStages(dstName1
,
612 ren1
.dstSha
, ren1
.dstMode
,
614 setIndexStages(dstName2
,
617 ren2
.dstSha
, ren2
.dstMode
)
620 removeFile(True, ren1
.srcName
)
622 [resSha
, resMode
, clean
, merge
] = \
623 mergeFile(ren1
.srcName
, ren1
.srcSha
, ren1
.srcMode
,
624 ren1
.dstName
, ren1
.dstSha
, ren1
.dstMode
,
625 ren2
.dstName
, ren2
.dstSha
, ren2
.dstMode
,
626 branchName1
, branchName2
)
628 if merge
or not clean
:
629 output('Renaming', fmtRename(path
, ren1
.dstName
))
632 output('Auto-merging', ren1
.dstName
)
635 output('CONFLICT (content): merge conflict in',
640 setIndexStages(ren1
.dstName
,
641 ren1
.srcSha
, ren1
.srcMode
,
642 ren1
.dstSha
, ren1
.dstMode
,
643 ren2
.dstSha
, ren2
.dstMode
)
645 updateFile(clean
, resSha
, resMode
, ren1
.dstName
)
647 removeFile(True, ren1
.srcName
)
649 # Renamed in 1, maybe changed in 2
650 if renamesA
== renames1
:
655 srcShaOtherBranch
= ren1
.srcCacheEntry
.stages
[stage
].sha1
656 srcModeOtherBranch
= ren1
.srcCacheEntry
.stages
[stage
].mode
658 dstShaOtherBranch
= ren1
.dstCacheEntry
.stages
[stage
].sha1
659 dstModeOtherBranch
= ren1
.dstCacheEntry
.stages
[stage
].mode
663 if ren1
.dstName
in currentDirectorySet
:
664 newPath
= uniquePath(ren1
.dstName
, branchName1
)
665 output('CONFLICT (rename/directory): Rename',
666 fmtRename(ren1
.srcName
, ren1
.dstName
), 'in', branchName1
,
667 'directory', ren1
.dstName
, 'added in', branchName2
)
668 output('Renaming', ren1
.srcName
, 'to', newPath
, 'instead')
670 removeFile(False, ren1
.dstName
)
671 updateFile(False, ren1
.dstSha
, ren1
.dstMode
, newPath
)
672 elif srcShaOtherBranch
== None:
673 output('CONFLICT (rename/delete): Rename',
674 fmtRename(ren1
.srcName
, ren1
.dstName
), 'in',
675 branchName1
, 'and deleted in', branchName2
)
677 updateFile(False, ren1
.dstSha
, ren1
.dstMode
, ren1
.dstName
)
678 elif dstShaOtherBranch
:
679 newPath
= uniquePath(ren1
.dstName
, branchName2
)
680 output('CONFLICT (rename/add): Rename',
681 fmtRename(ren1
.srcName
, ren1
.dstName
), 'in',
682 branchName1
+ '.', ren1
.dstName
, 'added in', branchName2
)
683 output('Adding as', newPath
, 'instead')
684 updateFile(False, dstShaOtherBranch
, dstModeOtherBranch
, newPath
)
687 elif renames2
.getDst(ren1
.dstName
):
688 dst2
= renames2
.getDst(ren1
.dstName
)
689 newPath1
= uniquePath(ren1
.dstName
, branchName1
)
690 newPath2
= uniquePath(dst2
.dstName
, branchName2
)
691 output('CONFLICT (rename/rename): Rename',
692 fmtRename(ren1
.srcName
, ren1
.dstName
), 'in',
693 branchName1
+'. Rename',
694 fmtRename(dst2
.srcName
, dst2
.dstName
), 'in', branchName2
)
695 output('Renaming', ren1
.srcName
, 'to', newPath1
, 'and',
696 dst2
.srcName
, 'to', newPath2
, 'instead')
697 removeFile(False, ren1
.dstName
)
698 updateFile(False, ren1
.dstSha
, ren1
.dstMode
, newPath1
)
699 updateFile(False, dst2
.dstSha
, dst2
.dstMode
, newPath2
)
700 dst2
.processed
= True
707 oName
, oSHA1
, oMode
= ren1
.srcName
, ren1
.srcSha
, ren1
.srcMode
708 aName
, bName
= ren1
.dstName
, ren1
.srcName
709 aSHA1
, bSHA1
= ren1
.dstSha
, srcShaOtherBranch
710 aMode
, bMode
= ren1
.dstMode
, srcModeOtherBranch
711 aBranch
, bBranch
= branchName1
, branchName2
713 if renamesA
!= renames1
:
714 aName
, bName
= bName
, aName
715 aSHA1
, bSHA1
= bSHA1
, aSHA1
716 aMode
, bMode
= bMode
, aMode
717 aBranch
, bBranch
= bBranch
, aBranch
719 [resSha
, resMode
, clean
, merge
] = \
720 mergeFile(oName
, oSHA1
, oMode
,
725 if merge
or not clean
:
726 output('Renaming', fmtRename(ren1
.srcName
, ren1
.dstName
))
729 output('Auto-merging', ren1
.dstName
)
732 output('CONFLICT (rename/modify): Merge conflict in',
737 setIndexStages(ren1
.dstName
,
742 updateFile(clean
, resSha
, resMode
, ren1
.dstName
)
746 # Per entry merge function
747 # ------------------------
749 def processEntry(entry
, branch1Name
, branch2Name
):
750 '''Merge one cache entry.'''
752 debug('processing', entry
.path
, 'clean cache:', cacheOnly
)
757 oSha
= entry
.stages
[1].sha1
758 oMode
= entry
.stages
[1].mode
759 aSha
= entry
.stages
[2].sha1
760 aMode
= entry
.stages
[2].mode
761 bSha
= entry
.stages
[3].sha1
762 bMode
= entry
.stages
[3].mode
764 assert(oSha
== None or isSha(oSha
))
765 assert(aSha
== None or isSha(aSha
))
766 assert(bSha
== None or isSha(bSha
))
768 assert(oMode
== None or type(oMode
) is int)
769 assert(aMode
== None or type(aMode
) is int)
770 assert(bMode
== None or type(bMode
) is int)
772 if (oSha
and (not aSha
or not bSha
)):
774 # Case A: Deleted in one
776 if (not aSha
and not bSha
) or \
777 (aSha
== oSha
and not bSha
) or \
778 (not aSha
and bSha
== oSha
):
779 # Deleted in both or deleted in one and unchanged in the other
781 output('Removing', path
)
782 removeFile(True, path
)
784 # Deleted in one and changed in the other
787 output('CONFLICT (delete/modify):', path
, 'deleted in',
788 branch1Name
, 'and modified in', branch2Name
+ '.',
789 'Version', branch2Name
, 'of', path
, 'left in tree.')
793 output('CONFLICT (modify/delete):', path
, 'deleted in',
794 branch2Name
, 'and modified in', branch1Name
+ '.',
795 'Version', branch1Name
, 'of', path
, 'left in tree.')
799 updateFile(False, sha
, mode
, path
)
801 elif (not oSha
and aSha
and not bSha
) or \
802 (not oSha
and not aSha
and bSha
):
804 # Case B: Added in one.
807 addBranch
= branch1Name
808 otherBranch
= branch2Name
811 conf
= 'file/directory'
813 addBranch
= branch2Name
814 otherBranch
= branch1Name
817 conf
= 'directory/file'
819 if path
in currentDirectorySet
:
821 newPath
= uniquePath(path
, addBranch
)
822 output('CONFLICT (' + conf
+ '):',
823 'There is a directory with name', path
, 'in',
824 otherBranch
+ '. Adding', path
, 'as', newPath
)
826 removeFile(False, path
)
827 updateFile(False, sha
, mode
, newPath
)
829 output('Adding', path
)
830 updateFile(True, sha
, mode
, path
)
832 elif not oSha
and aSha
and bSha
:
834 # Case C: Added in both (check for same permissions).
839 output('CONFLICT: File', path
,
840 'added identically in both branches, but permissions',
841 'conflict', '0%o' % aMode
, '->', '0%o' % bMode
)
842 output('CONFLICT: adding with permission:', '0%o' % aMode
)
844 updateFile(False, aSha
, aMode
, path
)
846 # This case is handled by git-read-tree
850 newPath1
= uniquePath(path
, branch1Name
)
851 newPath2
= uniquePath(path
, branch2Name
)
852 output('CONFLICT (add/add): File', path
,
853 'added non-identically in both branches. Adding as',
854 newPath1
, 'and', newPath2
, 'instead.')
855 removeFile(False, path
)
856 updateFile(False, aSha
, aMode
, newPath1
)
857 updateFile(False, bSha
, bMode
, newPath2
)
859 elif oSha
and aSha
and bSha
:
861 # case D: Modified in both, but differently.
863 output('Auto-merging', path
)
864 [sha
, mode
, clean
, dummy
] = \
865 mergeFile(path
, oSha
, oMode
,
868 branch1Name
, branch2Name
)
870 updateFile(True, sha
, mode
, path
)
873 output('CONFLICT (content): Merge conflict in', path
)
876 updateFile(False, sha
, mode
, path
)
878 updateFileExt(sha
, mode
, path
, updateCache
=False, updateWd
=True)
880 die("ERROR: Fatal merge failure, shouldn't happen.")
885 die('Usage:', sys
.argv
[0], ' <base>... -- <head> <remote>..')
887 # main entry point as merge strategy module
888 # The first parameters up to -- are merge bases, and the rest are heads.
889 # This strategy module figures out merge bases itself, so we only
892 if len(sys
.argv
) < 4:
895 for nextArg
in xrange(1, len(sys
.argv
)):
896 if sys
.argv
[nextArg
] == '--':
897 if len(sys
.argv
) != nextArg
+ 3:
898 die('Not handling anything other than two heads merge.')
900 h1
= firstBranch
= sys
.argv
[nextArg
+ 1]
901 h2
= secondBranch
= sys
.argv
[nextArg
+ 2]
906 print 'Merging', h1
, 'with', h2
909 h1
= runProgram(['git-rev-parse', '--verify', h1
+ '^0']).rstrip()
910 h2
= runProgram(['git-rev-parse', '--verify', h2
+ '^0']).rstrip()
912 graph
= buildGraph([h1
, h2
])
914 [dummy
, clean
] = merge(graph
.shaMap
[h1
], graph
.shaMap
[h2
],
915 firstBranch
, secondBranch
, graph
)
919 if isinstance(sys
.exc_info()[1], SystemExit):
922 traceback
.print_exc(None, sys
.stderr
)