]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/fast-import/git-p4
If we are in verbose mode, output what we are about to run (or return)
[thirdparty/git.git] / contrib / fast-import / git-p4
CommitLineData
86949eef
SH
1#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
c8cbbee9
SH
5# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
83dce55a 7# 2007 Trolltech ASA
86949eef
SH
8# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
10
08483580 11import optparse, sys, os, marshal, popen2, subprocess, shelve
25df95cc 12import tempfile, getopt, sha, os.path, time, platform
ce6f33c8 13import re
8b41a97f 14
b984733c 15from sets import Set;
4f5cf76a 16
4addad22 17verbose = False
86949eef 18
21a50753
AK
19
20def p4_build_cmd(cmd):
21 """Build a suitable p4 command line.
22
23 This consolidates building and returning a p4 command line into one
24 location. It means that hooking into the environment, or other configuration
25 can be done more easily.
26 """
27 real_cmd = "%s %s" % ("p4", cmd)
ee06427a
AK
28 if verbose:
29 print real_cmd
21a50753
AK
30 return real_cmd
31
86dff6b6
HWN
32def die(msg):
33 if verbose:
34 raise Exception(msg)
35 else:
36 sys.stderr.write(msg + "\n")
37 sys.exit(1)
38
bce4c5fc 39def write_pipe(c, str):
4addad22 40 if verbose:
86dff6b6 41 sys.stderr.write('Writing pipe: %s\n' % c)
b016d397 42
bce4c5fc 43 pipe = os.popen(c, 'w')
b016d397 44 val = pipe.write(str)
bce4c5fc 45 if pipe.close():
86dff6b6 46 die('Command failed: %s' % c)
b016d397
HWN
47
48 return val
49
4addad22
HWN
50def read_pipe(c, ignore_error=False):
51 if verbose:
86dff6b6 52 sys.stderr.write('Reading pipe: %s\n' % c)
8b41a97f 53
bce4c5fc 54 pipe = os.popen(c, 'rb')
b016d397 55 val = pipe.read()
4addad22 56 if pipe.close() and not ignore_error:
86dff6b6 57 die('Command failed: %s' % c)
b016d397
HWN
58
59 return val
60
61
bce4c5fc 62def read_pipe_lines(c):
4addad22 63 if verbose:
86dff6b6 64 sys.stderr.write('Reading pipe: %s\n' % c)
b016d397 65 ## todo: check return status
bce4c5fc 66 pipe = os.popen(c, 'rb')
b016d397 67 val = pipe.readlines()
bce4c5fc 68 if pipe.close():
86dff6b6 69 die('Command failed: %s' % c)
b016d397
HWN
70
71 return val
caace111 72
2318121b
AK
73def p4_read_pipe_lines(c):
74 """Specifically invoke p4 on the command supplied. """
75 real_cmd = "%s %s" % ("p4", c)
76 if verbose:
77 print real_cmd
78 return read_pipe_lines(real_cmd)
79
6754a299 80def system(cmd):
4addad22 81 if verbose:
bb6e09b2 82 sys.stderr.write("executing %s\n" % cmd)
6754a299
HWN
83 if os.system(cmd) != 0:
84 die("command failed: %s" % cmd)
85
bf9320f1
AK
86def p4_system(cmd):
87 """Specifically invoke p4 as the system command. """
88 real_cmd = "%s %s" % ("p4", cmd)
89 if verbose:
90 print real_cmd
91 return system(real_cmd)
92
b9fc6ea9
DB
93def isP4Exec(kind):
94 """Determine if a Perforce 'kind' should have execute permission
95
96 'p4 help filetypes' gives a list of the types. If it starts with 'x',
97 or x follows one of a few letters. Otherwise, if there is an 'x' after
98 a plus sign, it is also executable"""
99 return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
100
c65b670e
CP
101def setP4ExecBit(file, mode):
102 # Reopens an already open file and changes the execute bit to match
103 # the execute bit setting in the passed in mode.
104
105 p4Type = "+x"
106
107 if not isModeExec(mode):
108 p4Type = getP4OpenedType(file)
109 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
110 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
111 if p4Type[-1] == "+":
112 p4Type = p4Type[0:-1]
113
87b611d5 114 p4_system("reopen -t %s %s" % (p4Type, file))
c65b670e
CP
115
116def getP4OpenedType(file):
117 # Returns the perforce file type for the given file.
118
119 result = read_pipe("p4 opened %s" % file)
f3e5ae4f 120 match = re.match(".*\((.+)\)\r?$", result)
c65b670e
CP
121 if match:
122 return match.group(1)
123 else:
f3e5ae4f 124 die("Could not determine file type for %s (result: '%s')" % (file, result))
c65b670e 125
b43b0a3c
CP
126def diffTreePattern():
127 # This is a simple generator for the diff tree regex pattern. This could be
128 # a class variable if this and parseDiffTreeEntry were a part of a class.
129 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
130 while True:
131 yield pattern
132
133def parseDiffTreeEntry(entry):
134 """Parses a single diff tree entry into its component elements.
135
136 See git-diff-tree(1) manpage for details about the format of the diff
137 output. This method returns a dictionary with the following elements:
138
139 src_mode - The mode of the source file
140 dst_mode - The mode of the destination file
141 src_sha1 - The sha1 for the source file
142 dst_sha1 - The sha1 fr the destination file
143 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
144 status_score - The score for the status (applicable for 'C' and 'R'
145 statuses). This is None if there is no score.
146 src - The path for the source file.
147 dst - The path for the destination file. This is only present for
148 copy or renames. If it is not present, this is None.
149
150 If the pattern is not matched, None is returned."""
151
152 match = diffTreePattern().next().match(entry)
153 if match:
154 return {
155 'src_mode': match.group(1),
156 'dst_mode': match.group(2),
157 'src_sha1': match.group(3),
158 'dst_sha1': match.group(4),
159 'status': match.group(5),
160 'status_score': match.group(6),
161 'src': match.group(7),
162 'dst': match.group(10)
163 }
164 return None
165
c65b670e
CP
166def isModeExec(mode):
167 # Returns True if the given git mode represents an executable file,
168 # otherwise False.
169 return mode[-3:] == "755"
170
171def isModeExecChanged(src_mode, dst_mode):
172 return isModeExec(src_mode) != isModeExec(dst_mode)
173
9f90c733 174def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
86949eef 175 cmd = "p4 -G %s" % cmd
6a49f8e2
HWN
176 if verbose:
177 sys.stderr.write("Opening pipe: %s\n" % cmd)
9f90c733
SL
178
179 # Use a temporary file to avoid deadlocks without
180 # subprocess.communicate(), which would put another copy
181 # of stdout into memory.
182 stdin_file = None
183 if stdin is not None:
184 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
185 stdin_file.write(stdin)
186 stdin_file.flush()
187 stdin_file.seek(0)
188
189 p4 = subprocess.Popen(cmd, shell=True,
190 stdin=stdin_file,
191 stdout=subprocess.PIPE)
86949eef
SH
192
193 result = []
194 try:
195 while True:
9f90c733 196 entry = marshal.load(p4.stdout)
86949eef
SH
197 result.append(entry)
198 except EOFError:
199 pass
9f90c733
SL
200 exitCode = p4.wait()
201 if exitCode != 0:
ac3e0d79
SH
202 entry = {}
203 entry["p4ExitCode"] = exitCode
204 result.append(entry)
86949eef
SH
205
206 return result
207
208def p4Cmd(cmd):
209 list = p4CmdList(cmd)
210 result = {}
211 for entry in list:
212 result.update(entry)
213 return result;
214
cb2c9db5
SH
215def p4Where(depotPath):
216 if not depotPath.endswith("/"):
217 depotPath += "/"
218 output = p4Cmd("where %s..." % depotPath)
dc524036
SH
219 if output["code"] == "error":
220 return ""
cb2c9db5
SH
221 clientPath = ""
222 if "path" in output:
223 clientPath = output.get("path")
224 elif "data" in output:
225 data = output.get("data")
226 lastSpace = data.rfind(" ")
227 clientPath = data[lastSpace + 1:]
228
229 if clientPath.endswith("..."):
230 clientPath = clientPath[:-3]
231 return clientPath
232
86949eef 233def currentGitBranch():
b25b2065 234 return read_pipe("git name-rev HEAD").split(" ")[1].strip()
86949eef 235
4f5cf76a 236def isValidGitDir(path):
bb6e09b2
HWN
237 if (os.path.exists(path + "/HEAD")
238 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
4f5cf76a
SH
239 return True;
240 return False
241
463e8af6 242def parseRevision(ref):
b25b2065 243 return read_pipe("git rev-parse %s" % ref).strip()
463e8af6 244
6ae8de88
SH
245def extractLogMessageFromGitCommit(commit):
246 logMessage = ""
b016d397
HWN
247
248 ## fixme: title is first line of commit, not 1st paragraph.
6ae8de88 249 foundTitle = False
b016d397 250 for log in read_pipe_lines("git cat-file commit %s" % commit):
6ae8de88
SH
251 if not foundTitle:
252 if len(log) == 1:
1c094184 253 foundTitle = True
6ae8de88
SH
254 continue
255
256 logMessage += log
257 return logMessage
258
bb6e09b2 259def extractSettingsGitLog(log):
6ae8de88
SH
260 values = {}
261 for line in log.split("\n"):
262 line = line.strip()
6326aa58
HWN
263 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
264 if not m:
265 continue
266
267 assignments = m.group(1).split (':')
268 for a in assignments:
269 vals = a.split ('=')
270 key = vals[0].strip()
271 val = ('='.join (vals[1:])).strip()
272 if val.endswith ('\"') and val.startswith('"'):
273 val = val[1:-1]
274
275 values[key] = val
276
845b42cb
SH
277 paths = values.get("depot-paths")
278 if not paths:
279 paths = values.get("depot-path")
a3fdd579
SH
280 if paths:
281 values['depot-paths'] = paths.split(',')
bb6e09b2 282 return values
6ae8de88 283
8136a639 284def gitBranchExists(branch):
bb6e09b2
HWN
285 proc = subprocess.Popen(["git", "rev-parse", branch],
286 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
caace111 287 return proc.wait() == 0;
8136a639 288
01265103 289def gitConfig(key):
4addad22 290 return read_pipe("git config %s" % key, ignore_error=True).strip()
01265103 291
062410bb
SH
292def p4BranchesInGit(branchesAreInRemotes = True):
293 branches = {}
294
295 cmdline = "git rev-parse --symbolic "
296 if branchesAreInRemotes:
297 cmdline += " --remotes"
298 else:
299 cmdline += " --branches"
300
301 for line in read_pipe_lines(cmdline):
302 line = line.strip()
303
304 ## only import to p4/
305 if not line.startswith('p4/') or line == "p4/HEAD":
306 continue
307 branch = line
308
309 # strip off p4
310 branch = re.sub ("^p4/", "", line)
311
312 branches[branch] = parseRevision(line)
313 return branches
314
9ceab363 315def findUpstreamBranchPoint(head = "HEAD"):
86506fe5
SH
316 branches = p4BranchesInGit()
317 # map from depot-path to branch name
318 branchByDepotPath = {}
319 for branch in branches.keys():
320 tip = branches[branch]
321 log = extractLogMessageFromGitCommit(tip)
322 settings = extractSettingsGitLog(log)
323 if settings.has_key("depot-paths"):
324 paths = ",".join(settings["depot-paths"])
325 branchByDepotPath[paths] = "remotes/p4/" + branch
326
27d2d811 327 settings = None
27d2d811
SH
328 parent = 0
329 while parent < 65535:
9ceab363 330 commit = head + "~%s" % parent
27d2d811
SH
331 log = extractLogMessageFromGitCommit(commit)
332 settings = extractSettingsGitLog(log)
86506fe5
SH
333 if settings.has_key("depot-paths"):
334 paths = ",".join(settings["depot-paths"])
335 if branchByDepotPath.has_key(paths):
336 return [branchByDepotPath[paths], settings]
27d2d811 337
86506fe5 338 parent = parent + 1
27d2d811 339
86506fe5 340 return ["", settings]
27d2d811 341
5ca44617
SH
342def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
343 if not silent:
344 print ("Creating/updating branch(es) in %s based on origin branch(es)"
345 % localRefPrefix)
346
347 originPrefix = "origin/p4/"
348
349 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
350 line = line.strip()
351 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
352 continue
353
354 headName = line[len(originPrefix):]
355 remoteHead = localRefPrefix + headName
356 originHead = line
357
358 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
359 if (not original.has_key('depot-paths')
360 or not original.has_key('change')):
361 continue
362
363 update = False
364 if not gitBranchExists(remoteHead):
365 if verbose:
366 print "creating %s" % remoteHead
367 update = True
368 else:
369 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
370 if settings.has_key('change') > 0:
371 if settings['depot-paths'] == original['depot-paths']:
372 originP4Change = int(original['change'])
373 p4Change = int(settings['change'])
374 if originP4Change > p4Change:
375 print ("%s (%s) is newer than %s (%s). "
376 "Updating p4 branch from origin."
377 % (originHead, originP4Change,
378 remoteHead, p4Change))
379 update = True
380 else:
381 print ("Ignoring: %s was imported from %s while "
382 "%s was imported from %s"
383 % (originHead, ','.join(original['depot-paths']),
384 remoteHead, ','.join(settings['depot-paths'])))
385
386 if update:
387 system("git update-ref %s %s" % (remoteHead, originHead))
388
389def originP4BranchesExist():
390 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
391
4f6432d8
SH
392def p4ChangesForPaths(depotPaths, changeRange):
393 assert depotPaths
b340fa43 394 output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
4f6432d8
SH
395 for p in depotPaths]))
396
397 changes = []
398 for line in output:
399 changeNum = line.split(" ")[1]
400 changes.append(int(changeNum))
401
402 changes.sort()
403 return changes
404
b984733c
SH
405class Command:
406 def __init__(self):
407 self.usage = "usage: %prog [options]"
8910ac0e 408 self.needsGit = True
b984733c
SH
409
410class P4Debug(Command):
86949eef 411 def __init__(self):
6ae8de88 412 Command.__init__(self)
86949eef 413 self.options = [
b1ce9447
HWN
414 optparse.make_option("--verbose", dest="verbose", action="store_true",
415 default=False),
4addad22 416 ]
c8c39116 417 self.description = "A tool to debug the output of p4 -G."
8910ac0e 418 self.needsGit = False
b1ce9447 419 self.verbose = False
86949eef
SH
420
421 def run(self, args):
b1ce9447 422 j = 0
86949eef 423 for output in p4CmdList(" ".join(args)):
b1ce9447
HWN
424 print 'Element: %d' % j
425 j += 1
86949eef 426 print output
b984733c 427 return True
86949eef 428
5834684d
SH
429class P4RollBack(Command):
430 def __init__(self):
431 Command.__init__(self)
432 self.options = [
0c66a783
SH
433 optparse.make_option("--verbose", dest="verbose", action="store_true"),
434 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
5834684d
SH
435 ]
436 self.description = "A tool to debug the multi-branch import. Don't use :)"
52102d47 437 self.verbose = False
0c66a783 438 self.rollbackLocalBranches = False
5834684d
SH
439
440 def run(self, args):
441 if len(args) != 1:
442 return False
443 maxChange = int(args[0])
0c66a783 444
ad192f28 445 if "p4ExitCode" in p4Cmd("changes -m 1"):
66a2f523
SH
446 die("Problems executing p4");
447
0c66a783
SH
448 if self.rollbackLocalBranches:
449 refPrefix = "refs/heads/"
b016d397 450 lines = read_pipe_lines("git rev-parse --symbolic --branches")
0c66a783
SH
451 else:
452 refPrefix = "refs/remotes/"
b016d397 453 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
0c66a783
SH
454
455 for line in lines:
456 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
b25b2065
HWN
457 line = line.strip()
458 ref = refPrefix + line
5834684d 459 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
460 settings = extractSettingsGitLog(log)
461
462 depotPaths = settings['depot-paths']
463 change = settings['change']
464
5834684d 465 changed = False
52102d47 466
6326aa58
HWN
467 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
468 for p in depotPaths]))) == 0:
52102d47
SH
469 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
470 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
471 continue
472
bb6e09b2 473 while change and int(change) > maxChange:
5834684d 474 changed = True
52102d47
SH
475 if self.verbose:
476 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
5834684d
SH
477 system("git update-ref %s \"%s^\"" % (ref, ref))
478 log = extractLogMessageFromGitCommit(ref)
bb6e09b2
HWN
479 settings = extractSettingsGitLog(log)
480
481
482 depotPaths = settings['depot-paths']
483 change = settings['change']
5834684d
SH
484
485 if changed:
52102d47 486 print "%s rewound to %s" % (ref, change)
5834684d
SH
487
488 return True
489
711544b0 490class P4Submit(Command):
4f5cf76a 491 def __init__(self):
b984733c 492 Command.__init__(self)
4f5cf76a 493 self.options = [
4addad22 494 optparse.make_option("--verbose", dest="verbose", action="store_true"),
4f5cf76a 495 optparse.make_option("--origin", dest="origin"),
d9a5f25b 496 optparse.make_option("-M", dest="detectRename", action="store_true"),
4f5cf76a
SH
497 ]
498 self.description = "Submit changes from git to the perforce depot."
c9b50e63 499 self.usage += " [name of git branch to submit into perforce depot]"
4f5cf76a 500 self.interactive = True
9512497b 501 self.origin = ""
d9a5f25b 502 self.detectRename = False
b0d10df7 503 self.verbose = False
f7baba8b 504 self.isWindows = (platform.system() == "Windows")
4f5cf76a 505
4f5cf76a
SH
506 def check(self):
507 if len(p4CmdList("opened ...")) > 0:
508 die("You have files opened with perforce! Close them before starting the sync.")
509
edae1e2f
SH
510 # replaces everything between 'Description:' and the next P4 submit template field with the
511 # commit message
4f5cf76a
SH
512 def prepareLogMessage(self, template, message):
513 result = ""
514
edae1e2f
SH
515 inDescriptionSection = False
516
4f5cf76a
SH
517 for line in template.split("\n"):
518 if line.startswith("#"):
519 result += line + "\n"
520 continue
521
edae1e2f
SH
522 if inDescriptionSection:
523 if line.startswith("Files:"):
524 inDescriptionSection = False
525 else:
526 continue
527 else:
528 if line.startswith("Description:"):
529 inDescriptionSection = True
530 line += "\n"
531 for messageLine in message.split("\n"):
532 line += "\t" + messageLine + "\n"
533
534 result += line + "\n"
4f5cf76a
SH
535
536 return result
537
ea99c3ae
SH
538 def prepareSubmitTemplate(self):
539 # remove lines in the Files section that show changes to files outside the depot path we're committing into
540 template = ""
541 inFilesSection = False
b340fa43 542 for line in p4_read_pipe_lines("change -o"):
f3e5ae4f
MSO
543 if line.endswith("\r\n"):
544 line = line[:-2] + "\n"
ea99c3ae
SH
545 if inFilesSection:
546 if line.startswith("\t"):
547 # path starts and ends with a tab
548 path = line[1:]
549 lastTab = path.rfind("\t")
550 if lastTab != -1:
551 path = path[:lastTab]
552 if not path.startswith(self.depotPath):
553 continue
554 else:
555 inFilesSection = False
556 else:
557 if line.startswith("Files:"):
558 inFilesSection = True
559
560 template += line
561
562 return template
563
7cb5cbef 564 def applyCommit(self, id):
0e36f2d7
SH
565 print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
566 diffOpts = ("", "-M")[self.detectRename]
567 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
4f5cf76a
SH
568 filesToAdd = set()
569 filesToDelete = set()
d336c158 570 editedFiles = set()
c65b670e 571 filesToChangeExecBit = {}
4f5cf76a 572 for line in diff:
b43b0a3c
CP
573 diff = parseDiffTreeEntry(line)
574 modifier = diff['status']
575 path = diff['src']
4f5cf76a 576 if modifier == "M":
87b611d5 577 p4_system("edit \"%s\"" % path)
c65b670e
CP
578 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
579 filesToChangeExecBit[path] = diff['dst_mode']
d336c158 580 editedFiles.add(path)
4f5cf76a
SH
581 elif modifier == "A":
582 filesToAdd.add(path)
c65b670e 583 filesToChangeExecBit[path] = diff['dst_mode']
4f5cf76a
SH
584 if path in filesToDelete:
585 filesToDelete.remove(path)
586 elif modifier == "D":
587 filesToDelete.add(path)
588 if path in filesToAdd:
589 filesToAdd.remove(path)
d9a5f25b 590 elif modifier == "R":
b43b0a3c 591 src, dest = diff['src'], diff['dst']
87b611d5
AK
592 p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
593 p4_system("edit \"%s\"" % (dest))
c65b670e
CP
594 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
595 filesToChangeExecBit[dest] = diff['dst_mode']
d9a5f25b
CP
596 os.unlink(dest)
597 editedFiles.add(dest)
598 filesToDelete.add(src)
4f5cf76a
SH
599 else:
600 die("unknown modifier %s for %s" % (modifier, path))
601
0e36f2d7 602 diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
47a130b7 603 patchcmd = diffcmd + " | git apply "
c1b296b9
SH
604 tryPatchCmd = patchcmd + "--check -"
605 applyPatchCmd = patchcmd + "--check --apply -"
51a2640a 606
47a130b7 607 if os.system(tryPatchCmd) != 0:
51a2640a
SH
608 print "Unfortunately applying the change failed!"
609 print "What do you want to do?"
610 response = "x"
611 while response != "s" and response != "a" and response != "w":
cebdf5af
HWN
612 response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
613 "and with .rej files / [w]rite the patch to a file (patch.txt) ")
51a2640a
SH
614 if response == "s":
615 print "Skipping! Good luck with the next patches..."
20947149 616 for f in editedFiles:
87b611d5 617 p4_system("revert \"%s\"" % f);
20947149
SH
618 for f in filesToAdd:
619 system("rm %s" %f)
51a2640a
SH
620 return
621 elif response == "a":
47a130b7 622 os.system(applyPatchCmd)
51a2640a
SH
623 if len(filesToAdd) > 0:
624 print "You may also want to call p4 add on the following files:"
625 print " ".join(filesToAdd)
626 if len(filesToDelete):
627 print "The following files should be scheduled for deletion with p4 delete:"
628 print " ".join(filesToDelete)
cebdf5af
HWN
629 die("Please resolve and submit the conflict manually and "
630 + "continue afterwards with git-p4 submit --continue")
51a2640a
SH
631 elif response == "w":
632 system(diffcmd + " > patch.txt")
633 print "Patch saved to patch.txt in %s !" % self.clientPath
cebdf5af
HWN
634 die("Please resolve and submit the conflict manually and "
635 "continue afterwards with git-p4 submit --continue")
51a2640a 636
47a130b7 637 system(applyPatchCmd)
4f5cf76a
SH
638
639 for f in filesToAdd:
87b611d5 640 p4_system("add \"%s\"" % f)
4f5cf76a 641 for f in filesToDelete:
87b611d5
AK
642 p4_system("revert \"%s\"" % f)
643 p4_system("delete \"%s\"" % f)
4f5cf76a 644
c65b670e
CP
645 # Set/clear executable bits
646 for f in filesToChangeExecBit.keys():
647 mode = filesToChangeExecBit[f]
648 setP4ExecBit(f, mode)
649
0e36f2d7 650 logMessage = extractLogMessageFromGitCommit(id)
0e36f2d7 651 logMessage = logMessage.strip()
4f5cf76a 652
ea99c3ae 653 template = self.prepareSubmitTemplate()
4f5cf76a
SH
654
655 if self.interactive:
656 submitTemplate = self.prepareLogMessage(template, logMessage)
67abd417
SB
657 if os.environ.has_key("P4DIFF"):
658 del(os.environ["P4DIFF"])
b016d397 659 diff = read_pipe("p4 diff -du ...")
4f5cf76a 660
f3e5ae4f 661 newdiff = ""
4f5cf76a 662 for newFile in filesToAdd:
f3e5ae4f
MSO
663 newdiff += "==== new file ====\n"
664 newdiff += "--- /dev/null\n"
665 newdiff += "+++ %s\n" % newFile
4f5cf76a
SH
666 f = open(newFile, "r")
667 for line in f.readlines():
f3e5ae4f 668 newdiff += "+" + line
4f5cf76a
SH
669 f.close()
670
f3e5ae4f 671 separatorLine = "######## everything below this line is just the diff #######\n"
4f5cf76a 672
e96e400f
SH
673 [handle, fileName] = tempfile.mkstemp()
674 tmpFile = os.fdopen(handle, "w+")
f3e5ae4f
MSO
675 if self.isWindows:
676 submitTemplate = submitTemplate.replace("\n", "\r\n")
677 separatorLine = separatorLine.replace("\n", "\r\n")
678 newdiff = newdiff.replace("\n", "\r\n")
679 tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
e96e400f
SH
680 tmpFile.close()
681 defaultEditor = "vi"
682 if platform.system() == "Windows":
683 defaultEditor = "notepad"
82cea9ff
SB
684 if os.environ.has_key("P4EDITOR"):
685 editor = os.environ.get("P4EDITOR")
686 else:
687 editor = os.environ.get("EDITOR", defaultEditor);
e96e400f
SH
688 system(editor + " " + fileName)
689 tmpFile = open(fileName, "rb")
690 message = tmpFile.read()
691 tmpFile.close()
692 os.remove(fileName)
693 submitTemplate = message[:message.index(separatorLine)]
694 if self.isWindows:
695 submitTemplate = submitTemplate.replace("\r\n", "\n")
696
e96e400f 697 write_pipe("p4 submit -i", submitTemplate)
4f5cf76a
SH
698 else:
699 fileName = "submit.txt"
700 file = open(fileName, "w+")
701 file.write(self.prepareLogMessage(template, logMessage))
702 file.close()
cebdf5af
HWN
703 print ("Perforce submit template written as %s. "
704 + "Please review/edit and then use p4 submit -i < %s to submit directly!"
705 % (fileName, fileName))
4f5cf76a
SH
706
707 def run(self, args):
c9b50e63
SH
708 if len(args) == 0:
709 self.master = currentGitBranch()
4280e533 710 if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
c9b50e63
SH
711 die("Detecting current git branch failed!")
712 elif len(args) == 1:
713 self.master = args[0]
714 else:
715 return False
716
4c2d5d72
JX
717 allowSubmit = gitConfig("git-p4.allowSubmit")
718 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
719 die("%s is not in git-p4.allowSubmit" % self.master)
720
27d2d811 721 [upstream, settings] = findUpstreamBranchPoint()
ea99c3ae 722 self.depotPath = settings['depot-paths'][0]
27d2d811
SH
723 if len(self.origin) == 0:
724 self.origin = upstream
a3fdd579
SH
725
726 if self.verbose:
727 print "Origin branch is " + self.origin
9512497b 728
ea99c3ae 729 if len(self.depotPath) == 0:
9512497b
SH
730 print "Internal error: cannot locate perforce depot path from existing branches"
731 sys.exit(128)
732
ea99c3ae 733 self.clientPath = p4Where(self.depotPath)
9512497b 734
51a2640a 735 if len(self.clientPath) == 0:
ea99c3ae 736 print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
9512497b
SH
737 sys.exit(128)
738
ea99c3ae 739 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
7944f142 740 self.oldWorkingDirectory = os.getcwd()
c1b296b9 741
51a2640a 742 os.chdir(self.clientPath)
31f9ec12 743 print "Syncronizing p4 checkout..."
87b611d5 744 p4_system("sync ...")
9512497b 745
4f5cf76a 746 self.check()
4f5cf76a 747
4c750c0d
SH
748 commits = []
749 for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
750 commits.append(line.strip())
751 commits.reverse()
4f5cf76a
SH
752
753 while len(commits) > 0:
4f5cf76a
SH
754 commit = commits[0]
755 commits = commits[1:]
7cb5cbef 756 self.applyCommit(commit)
4f5cf76a
SH
757 if not self.interactive:
758 break
759
4f5cf76a 760 if len(commits) == 0:
4c750c0d
SH
761 print "All changes applied!"
762 os.chdir(self.oldWorkingDirectory)
14594f4b 763
4c750c0d
SH
764 sync = P4Sync()
765 sync.run([])
14594f4b 766
4c750c0d
SH
767 rebase = P4Rebase()
768 rebase.rebase()
4f5cf76a 769
b984733c
SH
770 return True
771
711544b0 772class P4Sync(Command):
b984733c
SH
773 def __init__(self):
774 Command.__init__(self)
775 self.options = [
776 optparse.make_option("--branch", dest="branch"),
777 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
778 optparse.make_option("--changesfile", dest="changesFile"),
779 optparse.make_option("--silent", dest="silent", action="store_true"),
ef48f909 780 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
a028a98e 781 optparse.make_option("--verbose", dest="verbose", action="store_true"),
d2c6dd30
HWN
782 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
783 help="Import into refs/heads/ , not refs/remotes"),
8b41a97f 784 optparse.make_option("--max-changes", dest="maxChanges"),
86dff6b6 785 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
3a70cdfa
TAL
786 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
787 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
788 help="Only sync files that are included in the Perforce Client Spec")
b984733c
SH
789 ]
790 self.description = """Imports from Perforce into a git repository.\n
791 example:
792 //depot/my/project/ -- to import the current head
793 //depot/my/project/@all -- to import everything
794 //depot/my/project/@1,6 -- to import only from revision 1 to 6
795
796 (a ... is not needed in the path p4 specification, it's added implicitly)"""
797
798 self.usage += " //depot/path[@revRange]"
b984733c 799 self.silent = False
b984733c
SH
800 self.createdBranches = Set()
801 self.committedChanges = Set()
569d1bd4 802 self.branch = ""
b984733c 803 self.detectBranches = False
cb53e1f8 804 self.detectLabels = False
b984733c 805 self.changesFile = ""
01265103 806 self.syncWithOrigin = True
4b97ffb1 807 self.verbose = False
a028a98e 808 self.importIntoRemotes = True
01a9c9c5 809 self.maxChanges = ""
c1f9197f 810 self.isWindows = (platform.system() == "Windows")
8b41a97f 811 self.keepRepoPath = False
6326aa58 812 self.depotPaths = None
3c699645 813 self.p4BranchesInGit = []
354081d5 814 self.cloneExclude = []
3a70cdfa
TAL
815 self.useClientSpec = False
816 self.clientSpecDirs = []
b984733c 817
01265103
SH
818 if gitConfig("git-p4.syncFromOrigin") == "false":
819 self.syncWithOrigin = False
820
b984733c 821 def extractFilesFromCommit(self, commit):
354081d5
TT
822 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
823 for path in self.cloneExclude]
b984733c
SH
824 files = []
825 fnum = 0
826 while commit.has_key("depotFile%s" % fnum):
827 path = commit["depotFile%s" % fnum]
6326aa58 828
354081d5
TT
829 if [p for p in self.cloneExclude
830 if path.startswith (p)]:
831 found = False
832 else:
833 found = [p for p in self.depotPaths
834 if path.startswith (p)]
6326aa58 835 if not found:
b984733c
SH
836 fnum = fnum + 1
837 continue
838
839 file = {}
840 file["path"] = path
841 file["rev"] = commit["rev%s" % fnum]
842 file["action"] = commit["action%s" % fnum]
843 file["type"] = commit["type%s" % fnum]
844 files.append(file)
845 fnum = fnum + 1
846 return files
847
6326aa58 848 def stripRepoPath(self, path, prefixes):
8b41a97f 849 if self.keepRepoPath:
6326aa58
HWN
850 prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
851
852 for p in prefixes:
853 if path.startswith(p):
854 path = path[len(p):]
8b41a97f 855
6326aa58 856 return path
6754a299 857
71b112d4 858 def splitFilesIntoBranches(self, commit):
d5904674 859 branches = {}
71b112d4
SH
860 fnum = 0
861 while commit.has_key("depotFile%s" % fnum):
862 path = commit["depotFile%s" % fnum]
6326aa58
HWN
863 found = [p for p in self.depotPaths
864 if path.startswith (p)]
865 if not found:
71b112d4
SH
866 fnum = fnum + 1
867 continue
868
869 file = {}
870 file["path"] = path
871 file["rev"] = commit["rev%s" % fnum]
872 file["action"] = commit["action%s" % fnum]
873 file["type"] = commit["type%s" % fnum]
874 fnum = fnum + 1
875
6326aa58 876 relPath = self.stripRepoPath(path, self.depotPaths)
b984733c 877
4b97ffb1 878 for branch in self.knownBranches.keys():
6754a299
HWN
879
880 # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
881 if relPath.startswith(branch + "/"):
d5904674
SH
882 if branch not in branches:
883 branches[branch] = []
71b112d4 884 branches[branch].append(file)
6555b2cc 885 break
b984733c
SH
886
887 return branches
888
6a49f8e2
HWN
889 ## Should move this out, doesn't use SELF.
890 def readP4Files(self, files):
30b5940b
SH
891 filesForCommit = []
892 filesToRead = []
893
3a70cdfa 894 for f in files:
30b5940b 895 includeFile = True
3a70cdfa
TAL
896 for val in self.clientSpecDirs:
897 if f['path'].startswith(val[0]):
30b5940b
SH
898 if val[1] <= 0:
899 includeFile = False
3a70cdfa
TAL
900 break
901
30b5940b
SH
902 if includeFile:
903 filesForCommit.append(f)
904 if f['action'] != 'delete':
905 filesToRead.append(f)
6a49f8e2 906
30b5940b
SH
907 filedata = []
908 if len(filesToRead) > 0:
909 filedata = p4CmdList('-x - print',
910 stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
911 for f in filesToRead]),
912 stdin_mode='w+')
f2eda79f 913
30b5940b
SH
914 if "p4ExitCode" in filedata[0]:
915 die("Problems executing p4. Error: [%d]."
916 % (filedata[0]['p4ExitCode']));
6a49f8e2 917
d2c6dd30
HWN
918 j = 0;
919 contents = {}
b1ce9447 920 while j < len(filedata):
d2c6dd30 921 stat = filedata[j]
b1ce9447 922 j += 1
8ff45f2a 923 text = [];
f3e9512b 924 while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'):
8ff45f2a 925 text.append(filedata[j]['data'])
b1ce9447 926 j += 1
8ff45f2a 927 text = ''.join(text)
1b9a4684
HWN
928
929 if not stat.has_key('depotFile'):
930 sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
931 continue
932
8ff45f2a
MSO
933 if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
934 text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text)
935 elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
2d71bde2 936 text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$',r'$\1$', text)
8ff45f2a 937
b1ce9447 938 contents[stat['depotFile']] = text
6a49f8e2 939
30b5940b
SH
940 for f in filesForCommit:
941 path = f['path']
942 if contents.has_key(path):
943 f['data'] = contents[path]
944
945 return filesForCommit
6a49f8e2 946
6326aa58 947 def commit(self, details, files, branch, branchPrefixes, parent = ""):
b984733c
SH
948 epoch = details["time"]
949 author = details["user"]
950
4b97ffb1
SH
951 if self.verbose:
952 print "commit into %s" % branch
953
96e07dd2
HWN
954 # start with reading files; if that fails, we should not
955 # create a commit.
956 new_files = []
957 for f in files:
958 if [p for p in branchPrefixes if f['path'].startswith(p)]:
959 new_files.append (f)
960 else:
961 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
3a70cdfa 962 files = self.readP4Files(new_files)
96e07dd2 963
b984733c 964 self.gitStream.write("commit %s\n" % branch)
6a49f8e2 965# gitStream.write("mark :%s\n" % details["change"])
b984733c
SH
966 self.committedChanges.add(int(details["change"]))
967 committer = ""
b607e71e
SH
968 if author not in self.users:
969 self.getUserMapFromPerforceServer()
b984733c 970 if author in self.users:
0828ab14 971 committer = "%s %s %s" % (self.users[author], epoch, self.tz)
b984733c 972 else:
0828ab14 973 committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
b984733c
SH
974
975 self.gitStream.write("committer %s\n" % committer)
976
977 self.gitStream.write("data <<EOT\n")
978 self.gitStream.write(details["desc"])
6581de09
SH
979 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
980 % (','.join (branchPrefixes), details["change"]))
981 if len(details['options']) > 0:
982 self.gitStream.write(": options = %s" % details['options'])
983 self.gitStream.write("]\nEOT\n\n")
b984733c
SH
984
985 if len(parent) > 0:
4b97ffb1
SH
986 if self.verbose:
987 print "parent %s" % parent
b984733c
SH
988 self.gitStream.write("from %s\n" % parent)
989
6a49f8e2 990 for file in files:
b984733c 991 if file["type"] == "apple":
6a49f8e2 992 print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
b984733c
SH
993 continue
994
6a49f8e2
HWN
995 relPath = self.stripRepoPath(file['path'], branchPrefixes)
996 if file["action"] == "delete":
b984733c
SH
997 self.gitStream.write("D %s\n" % relPath)
998 else:
6a49f8e2 999 data = file['data']
b984733c 1000
74276ec6 1001 mode = "644"
b9fc6ea9 1002 if isP4Exec(file["type"]):
74276ec6
SH
1003 mode = "755"
1004 elif file["type"] == "symlink":
1005 mode = "120000"
1006 # p4 print on a symlink contains "target\n", so strip it off
1007 data = data[:-1]
1008
c1f9197f
MSO
1009 if self.isWindows and file["type"].endswith("text"):
1010 data = data.replace("\r\n", "\n")
1011
74276ec6 1012 self.gitStream.write("M %s inline %s\n" % (mode, relPath))
b984733c
SH
1013 self.gitStream.write("data %s\n" % len(data))
1014 self.gitStream.write(data)
1015 self.gitStream.write("\n")
1016
1017 self.gitStream.write("\n")
1018
1f4ba1cb
SH
1019 change = int(details["change"])
1020
9bda3a85 1021 if self.labels.has_key(change):
1f4ba1cb
SH
1022 label = self.labels[change]
1023 labelDetails = label[0]
1024 labelRevisions = label[1]
71b112d4
SH
1025 if self.verbose:
1026 print "Change %s is labelled %s" % (change, labelDetails)
1f4ba1cb 1027
6326aa58
HWN
1028 files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
1029 for p in branchPrefixes]))
1f4ba1cb
SH
1030
1031 if len(files) == len(labelRevisions):
1032
1033 cleanedFiles = {}
1034 for info in files:
1035 if info["action"] == "delete":
1036 continue
1037 cleanedFiles[info["depotFile"]] = info["rev"]
1038
1039 if cleanedFiles == labelRevisions:
1040 self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
1041 self.gitStream.write("from %s\n" % branch)
1042
1043 owner = labelDetails["Owner"]
1044 tagger = ""
1045 if author in self.users:
1046 tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
1047 else:
1048 tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
1049 self.gitStream.write("tagger %s\n" % tagger)
1050 self.gitStream.write("data <<EOT\n")
1051 self.gitStream.write(labelDetails["Description"])
1052 self.gitStream.write("EOT\n\n")
1053
1054 else:
a46668fa 1055 if not self.silent:
cebdf5af
HWN
1056 print ("Tag %s does not match with change %s: files do not match."
1057 % (labelDetails["label"], change))
1f4ba1cb
SH
1058
1059 else:
a46668fa 1060 if not self.silent:
cebdf5af
HWN
1061 print ("Tag %s does not match with change %s: file count is different."
1062 % (labelDetails["label"], change))
b984733c 1063
183b8ef8 1064 def getUserCacheFilename(self):
b2d2d16a
SH
1065 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
1066 return home + "/.gitp4-usercache.txt"
183b8ef8 1067
b607e71e 1068 def getUserMapFromPerforceServer(self):
ebd81168
SH
1069 if self.userMapFromPerforceServer:
1070 return
b984733c
SH
1071 self.users = {}
1072
1073 for output in p4CmdList("users"):
1074 if not output.has_key("User"):
1075 continue
1076 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
1077
183b8ef8
HWN
1078
1079 s = ''
1080 for (key, val) in self.users.items():
1081 s += "%s\t%s\n" % (key, val)
1082
1083 open(self.getUserCacheFilename(), "wb").write(s)
ebd81168 1084 self.userMapFromPerforceServer = True
b607e71e
SH
1085
1086 def loadUserMapFromCache(self):
1087 self.users = {}
ebd81168 1088 self.userMapFromPerforceServer = False
b607e71e 1089 try:
183b8ef8 1090 cache = open(self.getUserCacheFilename(), "rb")
b607e71e
SH
1091 lines = cache.readlines()
1092 cache.close()
1093 for line in lines:
b25b2065 1094 entry = line.strip().split("\t")
b607e71e
SH
1095 self.users[entry[0]] = entry[1]
1096 except IOError:
1097 self.getUserMapFromPerforceServer()
1098
1f4ba1cb
SH
1099 def getLabels(self):
1100 self.labels = {}
1101
6326aa58 1102 l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
10c3211b 1103 if len(l) > 0 and not self.silent:
183f8436 1104 print "Finding files belonging to labels in %s" % `self.depotPaths`
01ce1fe9
SH
1105
1106 for output in l:
1f4ba1cb
SH
1107 label = output["label"]
1108 revisions = {}
1109 newestChange = 0
71b112d4
SH
1110 if self.verbose:
1111 print "Querying files for label %s" % label
6326aa58
HWN
1112 for file in p4CmdList("files "
1113 + ' '.join (["%s...@%s" % (p, label)
1114 for p in self.depotPaths])):
1f4ba1cb
SH
1115 revisions[file["depotFile"]] = file["rev"]
1116 change = int(file["change"])
1117 if change > newestChange:
1118 newestChange = change
1119
9bda3a85
SH
1120 self.labels[newestChange] = [output, revisions]
1121
1122 if self.verbose:
1123 print "Label changes: %s" % self.labels.keys()
1f4ba1cb 1124
86dff6b6
HWN
1125 def guessProjectName(self):
1126 for p in self.depotPaths:
6e5295c4
SH
1127 if p.endswith("/"):
1128 p = p[:-1]
1129 p = p[p.strip().rfind("/") + 1:]
1130 if not p.endswith("/"):
1131 p += "/"
1132 return p
86dff6b6 1133
4b97ffb1 1134 def getBranchMapping(self):
6555b2cc
SH
1135 lostAndFoundBranches = set()
1136
4b97ffb1
SH
1137 for info in p4CmdList("branches"):
1138 details = p4Cmd("branch -o %s" % info["branch"])
1139 viewIdx = 0
1140 while details.has_key("View%s" % viewIdx):
1141 paths = details["View%s" % viewIdx].split(" ")
1142 viewIdx = viewIdx + 1
1143 # require standard //depot/foo/... //depot/bar/... mapping
1144 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
1145 continue
1146 source = paths[0]
1147 destination = paths[1]
6509e19c
SH
1148 ## HACK
1149 if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
1150 source = source[len(self.depotPaths[0]):-4]
1151 destination = destination[len(self.depotPaths[0]):-4]
6555b2cc 1152
1a2edf4e
SH
1153 if destination in self.knownBranches:
1154 if not self.silent:
1155 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
1156 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
1157 continue
1158
6555b2cc
SH
1159 self.knownBranches[destination] = source
1160
1161 lostAndFoundBranches.discard(destination)
1162
29bdbac1 1163 if source not in self.knownBranches:
6555b2cc
SH
1164 lostAndFoundBranches.add(source)
1165
1166
1167 for branch in lostAndFoundBranches:
1168 self.knownBranches[branch] = branch
29bdbac1 1169
38f9f5ec
SH
1170 def getBranchMappingFromGitBranches(self):
1171 branches = p4BranchesInGit(self.importIntoRemotes)
1172 for branch in branches.keys():
1173 if branch == "master":
1174 branch = "main"
1175 else:
1176 branch = branch[len(self.projectName):]
1177 self.knownBranches[branch] = branch
1178
29bdbac1 1179 def listExistingP4GitBranches(self):
144ff46b
SH
1180 # branches holds mapping from name to commit
1181 branches = p4BranchesInGit(self.importIntoRemotes)
1182 self.p4BranchesInGit = branches.keys()
1183 for branch in branches.keys():
1184 self.initialParents[self.refPrefix + branch] = branches[branch]
4b97ffb1 1185
bb6e09b2
HWN
1186 def updateOptionDict(self, d):
1187 option_keys = {}
1188 if self.keepRepoPath:
1189 option_keys['keepRepoPath'] = 1
1190
1191 d["options"] = ' '.join(sorted(option_keys.keys()))
1192
1193 def readOptions(self, d):
1194 self.keepRepoPath = (d.has_key('options')
1195 and ('keepRepoPath' in d['options']))
6326aa58 1196
8134f69c
SH
1197 def gitRefForBranch(self, branch):
1198 if branch == "main":
1199 return self.refPrefix + "master"
1200
1201 if len(branch) <= 0:
1202 return branch
1203
1204 return self.refPrefix + self.projectName + branch
1205
1ca3d710
SH
1206 def gitCommitByP4Change(self, ref, change):
1207 if self.verbose:
1208 print "looking in ref " + ref + " for change %s using bisect..." % change
1209
1210 earliestCommit = ""
1211 latestCommit = parseRevision(ref)
1212
1213 while True:
1214 if self.verbose:
1215 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
1216 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
1217 if len(next) == 0:
1218 if self.verbose:
1219 print "argh"
1220 return ""
1221 log = extractLogMessageFromGitCommit(next)
1222 settings = extractSettingsGitLog(log)
1223 currentChange = int(settings['change'])
1224 if self.verbose:
1225 print "current change %s" % currentChange
1226
1227 if currentChange == change:
1228 if self.verbose:
1229 print "found %s" % next
1230 return next
1231
1232 if currentChange < change:
1233 earliestCommit = "^%s" % next
1234 else:
1235 latestCommit = "%s" % next
1236
1237 return ""
1238
1239 def importNewBranch(self, branch, maxChange):
1240 # make fast-import flush all changes to disk and update the refs using the checkpoint
1241 # command so that we can try to find the branch parent in the git history
1242 self.gitStream.write("checkpoint\n\n");
1243 self.gitStream.flush();
1244 branchPrefix = self.depotPaths[0] + branch + "/"
1245 range = "@1,%s" % maxChange
1246 #print "prefix" + branchPrefix
1247 changes = p4ChangesForPaths([branchPrefix], range)
1248 if len(changes) <= 0:
1249 return False
1250 firstChange = changes[0]
1251 #print "first change in branch: %s" % firstChange
1252 sourceBranch = self.knownBranches[branch]
1253 sourceDepotPath = self.depotPaths[0] + sourceBranch
1254 sourceRef = self.gitRefForBranch(sourceBranch)
1255 #print "source " + sourceBranch
1256
1257 branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
1258 #print "branch parent: %s" % branchParentChange
1259 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
1260 if len(gitParent) > 0:
1261 self.initialParents[self.gitRefForBranch(branch)] = gitParent
1262 #print "parent git commit: %s" % gitParent
1263
1264 self.importChanges(changes)
1265 return True
1266
e87f37ae
SH
1267 def importChanges(self, changes):
1268 cnt = 1
1269 for change in changes:
1270 description = p4Cmd("describe %s" % change)
1271 self.updateOptionDict(description)
1272
1273 if not self.silent:
1274 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
1275 sys.stdout.flush()
1276 cnt = cnt + 1
1277
1278 try:
1279 if self.detectBranches:
1280 branches = self.splitFilesIntoBranches(description)
1281 for branch in branches.keys():
1282 ## HACK --hwn
1283 branchPrefix = self.depotPaths[0] + branch + "/"
1284
1285 parent = ""
1286
1287 filesForCommit = branches[branch]
1288
1289 if self.verbose:
1290 print "branch is %s" % branch
1291
1292 self.updatedBranches.add(branch)
1293
1294 if branch not in self.createdBranches:
1295 self.createdBranches.add(branch)
1296 parent = self.knownBranches[branch]
1297 if parent == branch:
1298 parent = ""
1ca3d710
SH
1299 else:
1300 fullBranch = self.projectName + branch
1301 if fullBranch not in self.p4BranchesInGit:
1302 if not self.silent:
1303 print("\n Importing new branch %s" % fullBranch);
1304 if self.importNewBranch(branch, change - 1):
1305 parent = ""
1306 self.p4BranchesInGit.append(fullBranch)
1307 if not self.silent:
1308 print("\n Resuming with change %s" % change);
1309
1310 if self.verbose:
1311 print "parent determined through known branches: %s" % parent
e87f37ae 1312
8134f69c
SH
1313 branch = self.gitRefForBranch(branch)
1314 parent = self.gitRefForBranch(parent)
e87f37ae
SH
1315
1316 if self.verbose:
1317 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
1318
1319 if len(parent) == 0 and branch in self.initialParents:
1320 parent = self.initialParents[branch]
1321 del self.initialParents[branch]
1322
1323 self.commit(description, filesForCommit, branch, [branchPrefix], parent)
1324 else:
1325 files = self.extractFilesFromCommit(description)
1326 self.commit(description, files, self.branch, self.depotPaths,
1327 self.initialParent)
1328 self.initialParent = ""
1329 except IOError:
1330 print self.gitError.read()
1331 sys.exit(1)
1332
c208a243
SH
1333 def importHeadRevision(self, revision):
1334 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
1335
1336 details = { "user" : "git perforce import user", "time" : int(time.time()) }
1337 details["desc"] = ("Initial import of %s from the state at revision %s"
1338 % (' '.join(self.depotPaths), revision))
1339 details["change"] = revision
1340 newestRevision = 0
1341
1342 fileCnt = 0
1343 for info in p4CmdList("files "
1344 + ' '.join(["%s...%s"
1345 % (p, revision)
1346 for p in self.depotPaths])):
1347
1348 if info['code'] == 'error':
1349 sys.stderr.write("p4 returned an error: %s\n"
1350 % info['data'])
1351 sys.exit(1)
1352
1353
1354 change = int(info["change"])
1355 if change > newestRevision:
1356 newestRevision = change
1357
1358 if info["action"] == "delete":
1359 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
1360 #fileCnt = fileCnt + 1
1361 continue
1362
1363 for prop in ["depotFile", "rev", "action", "type" ]:
1364 details["%s%s" % (prop, fileCnt)] = info[prop]
1365
1366 fileCnt = fileCnt + 1
1367
1368 details["change"] = newestRevision
1369 self.updateOptionDict(details)
1370 try:
1371 self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
1372 except IOError:
1373 print "IO error with git fast-import. Is your git version recent enough?"
1374 print self.gitError.read()
1375
1376
3a70cdfa
TAL
1377 def getClientSpec(self):
1378 specList = p4CmdList( "client -o" )
1379 temp = {}
1380 for entry in specList:
1381 for k,v in entry.iteritems():
1382 if k.startswith("View"):
1383 if v.startswith('"'):
1384 start = 1
1385 else:
1386 start = 0
1387 index = v.find("...")
1388 v = v[start:index]
1389 if v.startswith("-"):
1390 v = v[1:]
1391 temp[v] = -len(v)
1392 else:
1393 temp[v] = len(v)
1394 self.clientSpecDirs = temp.items()
1395 self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
1396
b984733c 1397 def run(self, args):
6326aa58 1398 self.depotPaths = []
179caebf
SH
1399 self.changeRange = ""
1400 self.initialParent = ""
6326aa58 1401 self.previousDepotPaths = []
ce6f33c8 1402
29bdbac1
SH
1403 # map from branch depot path to parent branch
1404 self.knownBranches = {}
1405 self.initialParents = {}
5ca44617 1406 self.hasOrigin = originP4BranchesExist()
a43ff00c
SH
1407 if not self.syncWithOrigin:
1408 self.hasOrigin = False
29bdbac1 1409
a028a98e
SH
1410 if self.importIntoRemotes:
1411 self.refPrefix = "refs/remotes/p4/"
1412 else:
db775559 1413 self.refPrefix = "refs/heads/p4/"
a028a98e 1414
cebdf5af
HWN
1415 if self.syncWithOrigin and self.hasOrigin:
1416 if not self.silent:
1417 print "Syncing with origin first by calling git fetch origin"
1418 system("git fetch origin")
10f880f8 1419
569d1bd4 1420 if len(self.branch) == 0:
db775559 1421 self.branch = self.refPrefix + "master"
a028a98e 1422 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
48df6fd8 1423 system("git update-ref %s refs/heads/p4" % self.branch)
48df6fd8 1424 system("git branch -D p4");
faf1bd20 1425 # create it /after/ importing, when master exists
0058a33a 1426 if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
a3c55c09 1427 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
967f72e2 1428
3a70cdfa
TAL
1429 if self.useClientSpec or gitConfig("p4.useclientspec") == "true":
1430 self.getClientSpec()
1431
6a49f8e2
HWN
1432 # TODO: should always look at previous commits,
1433 # merge with previous imports, if possible.
1434 if args == []:
d414c74a 1435 if self.hasOrigin:
5ca44617 1436 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
abcd790f
SH
1437 self.listExistingP4GitBranches()
1438
1439 if len(self.p4BranchesInGit) > 1:
1440 if not self.silent:
1441 print "Importing from/into multiple branches"
1442 self.detectBranches = True
967f72e2 1443
29bdbac1
SH
1444 if self.verbose:
1445 print "branches: %s" % self.p4BranchesInGit
1446
1447 p4Change = 0
1448 for branch in self.p4BranchesInGit:
cebdf5af 1449 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
bb6e09b2
HWN
1450
1451 settings = extractSettingsGitLog(logMsg)
29bdbac1 1452
bb6e09b2
HWN
1453 self.readOptions(settings)
1454 if (settings.has_key('depot-paths')
1455 and settings.has_key ('change')):
1456 change = int(settings['change']) + 1
29bdbac1
SH
1457 p4Change = max(p4Change, change)
1458
bb6e09b2
HWN
1459 depotPaths = sorted(settings['depot-paths'])
1460 if self.previousDepotPaths == []:
6326aa58 1461 self.previousDepotPaths = depotPaths
29bdbac1 1462 else:
6326aa58
HWN
1463 paths = []
1464 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
583e1707 1465 for i in range(0, min(len(cur), len(prev))):
6326aa58 1466 if cur[i] <> prev[i]:
583e1707 1467 i = i - 1
6326aa58
HWN
1468 break
1469
583e1707 1470 paths.append (cur[:i + 1])
6326aa58
HWN
1471
1472 self.previousDepotPaths = paths
29bdbac1
SH
1473
1474 if p4Change > 0:
bb6e09b2 1475 self.depotPaths = sorted(self.previousDepotPaths)
d5904674 1476 self.changeRange = "@%s,#head" % p4Change
330f53b8
SH
1477 if not self.detectBranches:
1478 self.initialParent = parseRevision(self.branch)
341dc1c1 1479 if not self.silent and not self.detectBranches:
967f72e2 1480 print "Performing incremental import into %s git branch" % self.branch
569d1bd4 1481
f9162f6a
SH
1482 if not self.branch.startswith("refs/"):
1483 self.branch = "refs/heads/" + self.branch
179caebf 1484
6326aa58 1485 if len(args) == 0 and self.depotPaths:
b984733c 1486 if not self.silent:
6326aa58 1487 print "Depot paths: %s" % ' '.join(self.depotPaths)
b984733c 1488 else:
6326aa58 1489 if self.depotPaths and self.depotPaths != args:
cebdf5af 1490 print ("previous import used depot path %s and now %s was specified. "
6326aa58
HWN
1491 "This doesn't work!" % (' '.join (self.depotPaths),
1492 ' '.join (args)))
b984733c 1493 sys.exit(1)
6326aa58 1494
bb6e09b2 1495 self.depotPaths = sorted(args)
b984733c 1496
1c49fc19 1497 revision = ""
b984733c 1498 self.users = {}
b984733c 1499
6326aa58
HWN
1500 newPaths = []
1501 for p in self.depotPaths:
1502 if p.find("@") != -1:
1503 atIdx = p.index("@")
1504 self.changeRange = p[atIdx:]
1505 if self.changeRange == "@all":
1506 self.changeRange = ""
6a49f8e2 1507 elif ',' not in self.changeRange:
1c49fc19 1508 revision = self.changeRange
6326aa58 1509 self.changeRange = ""
7fcff9de 1510 p = p[:atIdx]
6326aa58
HWN
1511 elif p.find("#") != -1:
1512 hashIdx = p.index("#")
1c49fc19 1513 revision = p[hashIdx:]
7fcff9de 1514 p = p[:hashIdx]
6326aa58 1515 elif self.previousDepotPaths == []:
1c49fc19 1516 revision = "#head"
6326aa58
HWN
1517
1518 p = re.sub ("\.\.\.$", "", p)
1519 if not p.endswith("/"):
1520 p += "/"
1521
1522 newPaths.append(p)
1523
1524 self.depotPaths = newPaths
1525
b984733c 1526
b607e71e 1527 self.loadUserMapFromCache()
cb53e1f8
SH
1528 self.labels = {}
1529 if self.detectLabels:
1530 self.getLabels();
b984733c 1531
4b97ffb1 1532 if self.detectBranches:
df450923
SH
1533 ## FIXME - what's a P4 projectName ?
1534 self.projectName = self.guessProjectName()
1535
38f9f5ec
SH
1536 if self.hasOrigin:
1537 self.getBranchMappingFromGitBranches()
1538 else:
1539 self.getBranchMapping()
29bdbac1
SH
1540 if self.verbose:
1541 print "p4-git branches: %s" % self.p4BranchesInGit
1542 print "initial parents: %s" % self.initialParents
1543 for b in self.p4BranchesInGit:
1544 if b != "master":
6326aa58
HWN
1545
1546 ## FIXME
29bdbac1
SH
1547 b = b[len(self.projectName):]
1548 self.createdBranches.add(b)
4b97ffb1 1549
f291b4e3 1550 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
b984733c 1551
cebdf5af 1552 importProcess = subprocess.Popen(["git", "fast-import"],
6326aa58
HWN
1553 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1554 stderr=subprocess.PIPE);
08483580
SH
1555 self.gitOutput = importProcess.stdout
1556 self.gitStream = importProcess.stdin
1557 self.gitError = importProcess.stderr
b984733c 1558
1c49fc19 1559 if revision:
c208a243 1560 self.importHeadRevision(revision)
b984733c
SH
1561 else:
1562 changes = []
1563
0828ab14 1564 if len(self.changesFile) > 0:
b984733c
SH
1565 output = open(self.changesFile).readlines()
1566 changeSet = Set()
1567 for line in output:
1568 changeSet.add(int(line))
1569
1570 for change in changeSet:
1571 changes.append(change)
1572
1573 changes.sort()
1574 else:
29bdbac1 1575 if self.verbose:
86dff6b6 1576 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
6326aa58 1577 self.changeRange)
4f6432d8 1578 changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
b984733c 1579
01a9c9c5 1580 if len(self.maxChanges) > 0:
7fcff9de 1581 changes = changes[:min(int(self.maxChanges), len(changes))]
01a9c9c5 1582
b984733c 1583 if len(changes) == 0:
0828ab14 1584 if not self.silent:
341dc1c1 1585 print "No changes to import!"
1f52af6c 1586 return True
b984733c 1587
a9d1a27a
SH
1588 if not self.silent and not self.detectBranches:
1589 print "Import destination: %s" % self.branch
1590
341dc1c1
SH
1591 self.updatedBranches = set()
1592
e87f37ae 1593 self.importChanges(changes)
b984733c 1594
341dc1c1
SH
1595 if not self.silent:
1596 print ""
1597 if len(self.updatedBranches) > 0:
1598 sys.stdout.write("Updated branches: ")
1599 for b in self.updatedBranches:
1600 sys.stdout.write("%s " % b)
1601 sys.stdout.write("\n")
b984733c 1602
b984733c 1603 self.gitStream.close()
29bdbac1
SH
1604 if importProcess.wait() != 0:
1605 die("fast-import failed: %s" % self.gitError.read())
b984733c
SH
1606 self.gitOutput.close()
1607 self.gitError.close()
1608
b984733c
SH
1609 return True
1610
01ce1fe9
SH
1611class P4Rebase(Command):
1612 def __init__(self):
1613 Command.__init__(self)
01265103 1614 self.options = [ ]
cebdf5af
HWN
1615 self.description = ("Fetches the latest revision from perforce and "
1616 + "rebases the current work (branch) against it")
68c42153 1617 self.verbose = False
01ce1fe9
SH
1618
1619 def run(self, args):
1620 sync = P4Sync()
1621 sync.run([])
d7e3868c 1622
14594f4b
SH
1623 return self.rebase()
1624
1625 def rebase(self):
36ee4ee4
SH
1626 if os.system("git update-index --refresh") != 0:
1627 die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up-to-date or stash away all your changes with git stash.");
1628 if len(read_pipe("git diff-index HEAD --")) > 0:
1629 die("You have uncommited changes. Please commit them before rebasing or stash them away with git stash.");
1630
d7e3868c
SH
1631 [upstream, settings] = findUpstreamBranchPoint()
1632 if len(upstream) == 0:
1633 die("Cannot find upstream branchpoint for rebase")
1634
1635 # the branchpoint may be p4/foo~3, so strip off the parent
1636 upstream = re.sub("~[0-9]+$", "", upstream)
1637
1638 print "Rebasing the current branch onto %s" % upstream
b25b2065 1639 oldHead = read_pipe("git rev-parse HEAD").strip()
d7e3868c 1640 system("git rebase %s" % upstream)
1f52af6c 1641 system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
01ce1fe9
SH
1642 return True
1643
f9a3a4f7
SH
1644class P4Clone(P4Sync):
1645 def __init__(self):
1646 P4Sync.__init__(self)
1647 self.description = "Creates a new git repository and imports from Perforce into it"
bb6e09b2 1648 self.usage = "usage: %prog [options] //depot/path[@revRange]"
354081d5 1649 self.options += [
bb6e09b2
HWN
1650 optparse.make_option("--destination", dest="cloneDestination",
1651 action='store', default=None,
354081d5
TT
1652 help="where to leave result of the clone"),
1653 optparse.make_option("-/", dest="cloneExclude",
1654 action="append", type="string",
1655 help="exclude depot path")
1656 ]
bb6e09b2 1657 self.cloneDestination = None
f9a3a4f7 1658 self.needsGit = False
f9a3a4f7 1659
354081d5
TT
1660 # This is required for the "append" cloneExclude action
1661 def ensure_value(self, attr, value):
1662 if not hasattr(self, attr) or getattr(self, attr) is None:
1663 setattr(self, attr, value)
1664 return getattr(self, attr)
1665
6a49f8e2
HWN
1666 def defaultDestination(self, args):
1667 ## TODO: use common prefix of args?
1668 depotPath = args[0]
1669 depotDir = re.sub("(@[^@]*)$", "", depotPath)
1670 depotDir = re.sub("(#[^#]*)$", "", depotDir)
053d9e43 1671 depotDir = re.sub(r"\.\.\.$", "", depotDir)
6a49f8e2
HWN
1672 depotDir = re.sub(r"/$", "", depotDir)
1673 return os.path.split(depotDir)[1]
1674
f9a3a4f7
SH
1675 def run(self, args):
1676 if len(args) < 1:
1677 return False
bb6e09b2
HWN
1678
1679 if self.keepRepoPath and not self.cloneDestination:
1680 sys.stderr.write("Must specify destination for --keep-path\n")
1681 sys.exit(1)
f9a3a4f7 1682
6326aa58 1683 depotPaths = args
5e100b5c
SH
1684
1685 if not self.cloneDestination and len(depotPaths) > 1:
1686 self.cloneDestination = depotPaths[-1]
1687 depotPaths = depotPaths[:-1]
1688
354081d5 1689 self.cloneExclude = ["/"+p for p in self.cloneExclude]
6326aa58
HWN
1690 for p in depotPaths:
1691 if not p.startswith("//"):
1692 return False
f9a3a4f7 1693
bb6e09b2 1694 if not self.cloneDestination:
98ad4faf 1695 self.cloneDestination = self.defaultDestination(args)
f9a3a4f7 1696
86dff6b6 1697 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
c3bf3f13
KG
1698 if not os.path.exists(self.cloneDestination):
1699 os.makedirs(self.cloneDestination)
bb6e09b2 1700 os.chdir(self.cloneDestination)
f9a3a4f7 1701 system("git init")
b86f7378 1702 self.gitdir = os.getcwd() + "/.git"
6326aa58 1703 if not P4Sync.run(self, depotPaths):
f9a3a4f7 1704 return False
f9a3a4f7 1705 if self.branch != "master":
8f9b2e08
SH
1706 if gitBranchExists("refs/remotes/p4/master"):
1707 system("git branch master refs/remotes/p4/master")
1708 system("git checkout -f")
1709 else:
1710 print "Could not detect main branch. No checkout/master branch created."
86dff6b6 1711
f9a3a4f7
SH
1712 return True
1713
09d89de2
SH
1714class P4Branches(Command):
1715 def __init__(self):
1716 Command.__init__(self)
1717 self.options = [ ]
1718 self.description = ("Shows the git branches that hold imports and their "
1719 + "corresponding perforce depot paths")
1720 self.verbose = False
1721
1722 def run(self, args):
5ca44617
SH
1723 if originP4BranchesExist():
1724 createOrUpdateBranchesFromOrigin()
1725
09d89de2
SH
1726 cmdline = "git rev-parse --symbolic "
1727 cmdline += " --remotes"
1728
1729 for line in read_pipe_lines(cmdline):
1730 line = line.strip()
1731
1732 if not line.startswith('p4/') or line == "p4/HEAD":
1733 continue
1734 branch = line
1735
1736 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
1737 settings = extractSettingsGitLog(log)
1738
1739 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
1740 return True
1741
b984733c
SH
1742class HelpFormatter(optparse.IndentedHelpFormatter):
1743 def __init__(self):
1744 optparse.IndentedHelpFormatter.__init__(self)
1745
1746 def format_description(self, description):
1747 if description:
1748 return description + "\n"
1749 else:
1750 return ""
4f5cf76a 1751
86949eef
SH
1752def printUsage(commands):
1753 print "usage: %s <command> [options]" % sys.argv[0]
1754 print ""
1755 print "valid commands: %s" % ", ".join(commands)
1756 print ""
1757 print "Try %s <command> --help for command specific help." % sys.argv[0]
1758 print ""
1759
1760commands = {
b86f7378
HWN
1761 "debug" : P4Debug,
1762 "submit" : P4Submit,
a9834f58 1763 "commit" : P4Submit,
b86f7378
HWN
1764 "sync" : P4Sync,
1765 "rebase" : P4Rebase,
1766 "clone" : P4Clone,
09d89de2
SH
1767 "rollback" : P4RollBack,
1768 "branches" : P4Branches
86949eef
SH
1769}
1770
86949eef 1771
bb6e09b2
HWN
1772def main():
1773 if len(sys.argv[1:]) == 0:
1774 printUsage(commands.keys())
1775 sys.exit(2)
4f5cf76a 1776
bb6e09b2
HWN
1777 cmd = ""
1778 cmdName = sys.argv[1]
1779 try:
b86f7378
HWN
1780 klass = commands[cmdName]
1781 cmd = klass()
bb6e09b2
HWN
1782 except KeyError:
1783 print "unknown command %s" % cmdName
1784 print ""
1785 printUsage(commands.keys())
1786 sys.exit(2)
1787
1788 options = cmd.options
b86f7378 1789 cmd.gitdir = os.environ.get("GIT_DIR", None)
bb6e09b2
HWN
1790
1791 args = sys.argv[2:]
1792
1793 if len(options) > 0:
1794 options.append(optparse.make_option("--git-dir", dest="gitdir"))
1795
1796 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
1797 options,
1798 description = cmd.description,
1799 formatter = HelpFormatter())
1800
1801 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
1802 global verbose
1803 verbose = cmd.verbose
1804 if cmd.needsGit:
b86f7378
HWN
1805 if cmd.gitdir == None:
1806 cmd.gitdir = os.path.abspath(".git")
1807 if not isValidGitDir(cmd.gitdir):
1808 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
1809 if os.path.exists(cmd.gitdir):
bb6e09b2
HWN
1810 cdup = read_pipe("git rev-parse --show-cdup").strip()
1811 if len(cdup) > 0:
1812 os.chdir(cdup);
e20a9e53 1813
b86f7378
HWN
1814 if not isValidGitDir(cmd.gitdir):
1815 if isValidGitDir(cmd.gitdir + "/.git"):
1816 cmd.gitdir += "/.git"
bb6e09b2 1817 else:
b86f7378 1818 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
e20a9e53 1819
b86f7378 1820 os.environ["GIT_DIR"] = cmd.gitdir
86949eef 1821
bb6e09b2
HWN
1822 if not cmd.run(args):
1823 parser.print_help()
4f5cf76a 1824
4f5cf76a 1825
bb6e09b2
HWN
1826if __name__ == '__main__':
1827 main()