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