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