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