1 import sys, re, os, traceback
5 printList(args, sys.stderr)
8 def printList(list, file=sys.stdout):
20 functionsToDebug = Set()
24 functionsToDebug.add(func)
26 functionsToDebug.add(func.func_name)
30 funcName = traceback.extract_stack()[-2][2]
31 if funcName in functionsToDebug:
37 class ProgramError(Exception):
38 def __init__(self, progStr, error):
39 self.progStr = progStr
43 return self.progStr + ': ' + self.error
45 addDebug('runProgram')
46 def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
47 debug('runProgram prog:', str(prog), 'input:', str(input))
51 progStr = ' '.join(prog)
55 stderr = subprocess.STDOUT
56 stdout = subprocess.PIPE
60 pop = subprocess.Popen(prog,
61 shell = type(prog) is str,
64 stdin=subprocess.PIPE,
67 debug('strerror:', e.strerror)
68 raise ProgramError(progStr, e.strerror)
71 pop.stdin.write(input)
75 out = pop.stdout.read()
84 if code != 0 and not returnCode:
85 debug('error output:', out)
87 raise ProgramError(progStr, out)
88 # debug('output:', out.replace('\0', '\n'))
91 # Code for computing common ancestors
92 # -----------------------------------
100 # The 'virtual' commit objects have SHAs which are integers
101 shaRE = re.compile('^[0-9a-f]{40}$')
103 return (type(obj) is str and bool(shaRE.match(obj))) or \
104 (type(obj) is int and obj >= 1)
107 def __init__(self, sha, parents, tree=None):
108 self.parents = parents
109 self.firstLineMsg = None
118 self.sha = getUniqueId()
120 self.firstLineMsg = 'virtual commit'
124 self.sha = sha.rstrip()
125 assert(isSha(self.sha))
129 assert(self._tree != None)
134 return str(self.sha) + ' ' + self.firstLineMsg
137 return self.shortInfo()
140 if self.virtual or self.firstLineMsg != None:
143 info = runProgram(['git-cat-file', 'commit', self.sha])
144 info = info.split('\n')
148 self.firstLineMsg = l
151 if l.startswith('tree'):
152 self._tree = l[5:].rstrip()
161 def addNode(self, node):
162 assert(isinstance(node, Commit))
163 self.shaMap[node.sha] = node
164 self.commits.append(node)
165 for p in node.parents:
166 p.children.append(node)
169 def reachableNodes(self, n1, n2):
180 def fixParents(self, node):
181 for x in range(0, len(node.parents)):
182 node.parents[x] = self.shaMap[node.parents[x]]
184 # addDebug('buildGraph')
185 def buildGraph(heads):
186 debug('buildGraph heads:', heads)
192 out = runProgram(['git-rev-list', '--parents'] + heads)
193 for l in out.split('\n'):
198 # This is a hack, we temporarily use the 'parents' attribute
199 # to contain a list of SHA1:s. They are later replaced by proper
201 c = Commit(shas[0], shas[1:])
214 # Write the empty tree to the object database and return its SHA1
215 def writeEmptyTree():
216 tmpIndex = os.environ['GIT_DIR'] + '/merge-tmp-index'
223 newEnv = os.environ.copy()
224 newEnv['GIT_INDEX_FILE'] = tmpIndex
225 res = runProgram(['git-write-tree'], env=newEnv).rstrip()
229 def addCommonRoot(graph):
231 for c in graph.commits:
232 if len(c.parents) == 0:
235 superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
236 graph.addNode(superRoot)
238 r.parents = [superRoot]
239 superRoot.children = roots
242 def getCommonAncestors(graph, commit1, commit2):
243 '''Find the common ancestors for commit1 and commit2'''
244 assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
246 def traverse(start, set):
248 while len(stack) > 0:
256 traverse(commit1, h1Set)
257 traverse(commit2, h2Set)
258 shared = h1Set.intersection(h2Set)
261 shared = [addCommonRoot(graph)]
266 if len([c for c in s.children if c in shared]) == 0: