blob: a7ec1187066b90675f55e44103ba54e03d132caa [file] [log] [blame]
Simon Hausmann86949ee2007-03-19 20:59:12 +01001#!/usr/bin/env python
2#
3# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
4#
Simon Hausmannc8cbbee2007-05-28 14:43:25 +02005# Author: Simon Hausmann <simon@lst.de>
6# Copyright: 2007 Simon Hausmann <simon@lst.de>
Simon Hausmann83dce552007-03-19 22:26:36 +01007# 2007 Trolltech ASA
Simon Hausmann86949ee2007-03-19 20:59:12 +01008# License: MIT <http://www.opensource.org/licenses/mit-license.php>
9#
Eric S. Raymonda33faf22012-12-28 11:40:59 -050010import sys
11if sys.hexversion < 0x02040000:
12 # The limiter is the subprocess module
13 sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
14 sys.exit(1)
Pete Wyckofff629fa52013-01-26 22:11:05 -050015import os
16import optparse
17import marshal
18import subprocess
19import tempfile
20import time
21import platform
22import re
23import shutil
Pete Wyckoffd20f0f82013-01-26 22:11:19 -050024import stat
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -030025
Brandon Caseya235e852013-01-26 11:14:33 -080026try:
27 from subprocess import CalledProcessError
28except ImportError:
29 # from python2.7:subprocess.py
30 # Exception classes used by this module.
31 class CalledProcessError(Exception):
32 """This exception is raised when a process run by check_call() returns
33 a non-zero exit status. The exit status will be stored in the
34 returncode attribute."""
35 def __init__(self, returncode, cmd):
36 self.returncode = returncode
37 self.cmd = cmd
38 def __str__(self):
39 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
40
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -030041verbose = False
Simon Hausmann86949ee2007-03-19 20:59:12 +010042
Luke Diamand06804c72012-04-11 17:21:24 +020043# Only labels/tags matching this will be imported/exported
Luke Diamandc8942a22012-04-11 17:21:24 +020044defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$'
Anand Kumria21a50752008-08-10 19:26:28 +010045
Luke Diamand1051ef02015-06-10 08:30:59 +010046# Grab changes in blocks of this many revisions, unless otherwise requested
47defaultBlockSize = 512
48
Anand Kumria21a50752008-08-10 19:26:28 +010049def p4_build_cmd(cmd):
50 """Build a suitable p4 command line.
51
52 This consolidates building and returning a p4 command line into one
53 location. It means that hooking into the environment, or other configuration
54 can be done more easily.
55 """
Luke Diamand6de040d2011-10-16 10:47:52 -040056 real_cmd = ["p4"]
Anand Kumriaabcaf072008-08-10 19:26:31 +010057
58 user = gitConfig("git-p4.user")
59 if len(user) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040060 real_cmd += ["-u",user]
Anand Kumriaabcaf072008-08-10 19:26:31 +010061
62 password = gitConfig("git-p4.password")
63 if len(password) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040064 real_cmd += ["-P", password]
Anand Kumriaabcaf072008-08-10 19:26:31 +010065
66 port = gitConfig("git-p4.port")
67 if len(port) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040068 real_cmd += ["-p", port]
Anand Kumriaabcaf072008-08-10 19:26:31 +010069
70 host = gitConfig("git-p4.host")
71 if len(host) > 0:
Russell Myers41799aa2012-02-22 11:16:05 -080072 real_cmd += ["-H", host]
Anand Kumriaabcaf072008-08-10 19:26:31 +010073
74 client = gitConfig("git-p4.client")
75 if len(client) > 0:
Luke Diamand6de040d2011-10-16 10:47:52 -040076 real_cmd += ["-c", client]
Anand Kumriaabcaf072008-08-10 19:26:31 +010077
Luke Diamand6de040d2011-10-16 10:47:52 -040078
79 if isinstance(cmd,basestring):
80 real_cmd = ' '.join(real_cmd) + ' ' + cmd
81 else:
82 real_cmd += cmd
Anand Kumria21a50752008-08-10 19:26:28 +010083 return real_cmd
84
Miklós Fazekasbbd84862013-03-11 17:45:29 -040085def chdir(path, is_client_path=False):
86 """Do chdir to the given path, and set the PWD environment
87 variable for use by P4. It does not look at getcwd() output.
88 Since we're not using the shell, it is necessary to set the
89 PWD environment variable explicitly.
90
91 Normally, expand the path to force it to be absolute. This
92 addresses the use of relative path names inside P4 settings,
93 e.g. P4CONFIG=.p4config. P4 does not simply open the filename
94 as given; it looks for .p4config using PWD.
95
96 If is_client_path, the path was handed to us directly by p4,
97 and may be a symbolic link. Do not call os.getcwd() in this
98 case, because it will cause p4 to think that PWD is not inside
99 the client path.
100 """
101
102 os.chdir(path)
103 if not is_client_path:
104 path = os.getcwd()
105 os.environ['PWD'] = path
Robert Blum053fd0c2008-08-01 12:50:03 -0700106
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -0300107def die(msg):
108 if verbose:
109 raise Exception(msg)
110 else:
111 sys.stderr.write(msg + "\n")
112 sys.exit(1)
113
Luke Diamand6de040d2011-10-16 10:47:52 -0400114def write_pipe(c, stdin):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300115 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400116 sys.stderr.write('Writing pipe: %s\n' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300117
Luke Diamand6de040d2011-10-16 10:47:52 -0400118 expand = isinstance(c,basestring)
119 p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
120 pipe = p.stdin
121 val = pipe.write(stdin)
122 pipe.close()
123 if p.wait():
124 die('Command failed: %s' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300125
126 return val
127
Luke Diamand6de040d2011-10-16 10:47:52 -0400128def p4_write_pipe(c, stdin):
Anand Kumriad9429192008-08-14 23:40:38 +0100129 real_cmd = p4_build_cmd(c)
Luke Diamand6de040d2011-10-16 10:47:52 -0400130 return write_pipe(real_cmd, stdin)
Anand Kumriad9429192008-08-14 23:40:38 +0100131
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300132def read_pipe(c, ignore_error=False):
133 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400134 sys.stderr.write('Reading pipe: %s\n' % str(c))
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -0300135
Luke Diamand6de040d2011-10-16 10:47:52 -0400136 expand = isinstance(c,basestring)
Lars Schneider1f5f3902015-09-21 12:01:41 +0200137 p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand)
138 (out, err) = p.communicate()
139 if p.returncode != 0 and not ignore_error:
140 die('Command failed: %s\nError: %s' % (str(c), err))
141 return out
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300142
Anand Kumriad9429192008-08-14 23:40:38 +0100143def p4_read_pipe(c, ignore_error=False):
144 real_cmd = p4_build_cmd(c)
145 return read_pipe(real_cmd, ignore_error)
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300146
Han-Wen Nienhuysbce4c5f2007-05-23 17:14:33 -0300147def read_pipe_lines(c):
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300148 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400149 sys.stderr.write('Reading pipe: %s\n' % str(c))
150
151 expand = isinstance(c, basestring)
152 p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
153 pipe = p.stdout
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300154 val = pipe.readlines()
Luke Diamand6de040d2011-10-16 10:47:52 -0400155 if pipe.close() or p.wait():
156 die('Command failed: %s' % str(c))
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300157
158 return val
Simon Hausmanncaace112007-05-15 14:57:57 +0200159
Anand Kumria23181212008-08-10 19:26:24 +0100160def p4_read_pipe_lines(c):
161 """Specifically invoke p4 on the command supplied. """
Anand Kumria155af832008-08-10 19:26:30 +0100162 real_cmd = p4_build_cmd(c)
Anand Kumria23181212008-08-10 19:26:24 +0100163 return read_pipe_lines(real_cmd)
164
Gary Gibbons8e9497c2012-07-12 19:29:00 -0400165def p4_has_command(cmd):
166 """Ask p4 for help on this command. If it returns an error, the
167 command does not exist in this version of p4."""
168 real_cmd = p4_build_cmd(["help", cmd])
169 p = subprocess.Popen(real_cmd, stdout=subprocess.PIPE,
170 stderr=subprocess.PIPE)
171 p.communicate()
172 return p.returncode == 0
173
Pete Wyckoff249da4c2012-11-23 17:35:35 -0500174def p4_has_move_command():
175 """See if the move command exists, that it supports -k, and that
176 it has not been administratively disabled. The arguments
177 must be correct, but the filenames do not have to exist. Use
178 ones with wildcards so even if they exist, it will fail."""
179
180 if not p4_has_command("move"):
181 return False
182 cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
183 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
184 (out, err) = p.communicate()
185 # return code will be 1 in either case
186 if err.find("Invalid option") >= 0:
187 return False
188 if err.find("disabled") >= 0:
189 return False
190 # assume it failed because @... was invalid changelist
191 return True
192
Luke Diamandcbff4b22015-11-21 09:54:40 +0000193def system(cmd, ignore_error=False):
Luke Diamand6de040d2011-10-16 10:47:52 -0400194 expand = isinstance(cmd,basestring)
Han-Wen Nienhuys4addad22007-05-23 18:49:35 -0300195 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400196 sys.stderr.write("executing %s\n" % str(cmd))
Brandon Caseya235e852013-01-26 11:14:33 -0800197 retcode = subprocess.call(cmd, shell=expand)
Luke Diamandcbff4b22015-11-21 09:54:40 +0000198 if retcode and not ignore_error:
Brandon Caseya235e852013-01-26 11:14:33 -0800199 raise CalledProcessError(retcode, cmd)
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -0300200
Luke Diamandcbff4b22015-11-21 09:54:40 +0000201 return retcode
202
Anand Kumriabf9320f2008-08-10 19:26:26 +0100203def p4_system(cmd):
204 """Specifically invoke p4 as the system command. """
Anand Kumria155af832008-08-10 19:26:30 +0100205 real_cmd = p4_build_cmd(cmd)
Luke Diamand6de040d2011-10-16 10:47:52 -0400206 expand = isinstance(real_cmd, basestring)
Brandon Caseya235e852013-01-26 11:14:33 -0800207 retcode = subprocess.call(real_cmd, shell=expand)
208 if retcode:
209 raise CalledProcessError(retcode, real_cmd)
Luke Diamand6de040d2011-10-16 10:47:52 -0400210
Pete Wyckoff7f0e5962013-01-26 22:11:13 -0500211_p4_version_string = None
212def p4_version_string():
213 """Read the version string, showing just the last line, which
214 hopefully is the interesting version bit.
215
216 $ p4 -V
217 Perforce - The Fast Software Configuration Management System.
218 Copyright 1995-2011 Perforce Software. All rights reserved.
219 Rev. P4/NTX86/2011.1/393975 (2011/12/16).
220 """
221 global _p4_version_string
222 if not _p4_version_string:
223 a = p4_read_pipe_lines(["-V"])
224 _p4_version_string = a[-1].rstrip()
225 return _p4_version_string
226
Luke Diamand6de040d2011-10-16 10:47:52 -0400227def p4_integrate(src, dest):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400228 p4_system(["integrate", "-Dt", wildcard_encode(src), wildcard_encode(dest)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400229
Pete Wyckoff8d7ec362012-04-29 20:57:14 -0400230def p4_sync(f, *options):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400231 p4_system(["sync"] + list(options) + [wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400232
233def p4_add(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400234 # forcibly add file names with wildcards
235 if wildcard_present(f):
236 p4_system(["add", "-f", f])
237 else:
238 p4_system(["add", f])
Luke Diamand6de040d2011-10-16 10:47:52 -0400239
240def p4_delete(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400241 p4_system(["delete", wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400242
243def p4_edit(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400244 p4_system(["edit", wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400245
246def p4_revert(f):
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400247 p4_system(["revert", wildcard_encode(f)])
Luke Diamand6de040d2011-10-16 10:47:52 -0400248
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400249def p4_reopen(type, f):
250 p4_system(["reopen", "-t", type, wildcard_encode(f)])
Anand Kumriabf9320f2008-08-10 19:26:26 +0100251
Gary Gibbons8e9497c2012-07-12 19:29:00 -0400252def p4_move(src, dest):
253 p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
254
Luke Diamand1051ef02015-06-10 08:30:59 +0100255def p4_last_change():
256 results = p4CmdList(["changes", "-m", "1"])
257 return int(results[0]['change'])
258
Pete Wyckoff18fa13d2012-11-23 17:35:34 -0500259def p4_describe(change):
260 """Make sure it returns a valid result by checking for
261 the presence of field "time". Return a dict of the
262 results."""
263
264 ds = p4CmdList(["describe", "-s", str(change)])
265 if len(ds) != 1:
266 die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
267
268 d = ds[0]
269
270 if "p4ExitCode" in d:
271 die("p4 describe -s %d exited with %d: %s" % (change, d["p4ExitCode"],
272 str(d)))
273 if "code" in d:
274 if d["code"] == "error":
275 die("p4 describe -s %d returned error code: %s" % (change, str(d)))
276
277 if "time" not in d:
278 die("p4 describe -s %d returned no \"time\": %s" % (change, str(d)))
279
280 return d
281
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -0400282#
283# Canonicalize the p4 type and return a tuple of the
284# base type, plus any modifiers. See "p4 help filetypes"
285# for a list and explanation.
286#
287def split_p4_type(p4type):
David Brownb9fc6ea2007-09-19 13:12:48 -0700288
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -0400289 p4_filetypes_historical = {
290 "ctempobj": "binary+Sw",
291 "ctext": "text+C",
292 "cxtext": "text+Cx",
293 "ktext": "text+k",
294 "kxtext": "text+kx",
295 "ltext": "text+F",
296 "tempobj": "binary+FSw",
297 "ubinary": "binary+F",
298 "uresource": "resource+F",
299 "uxbinary": "binary+Fx",
300 "xbinary": "binary+x",
301 "xltext": "text+Fx",
302 "xtempobj": "binary+Swx",
303 "xtext": "text+x",
304 "xunicode": "unicode+x",
305 "xutf16": "utf16+x",
306 }
307 if p4type in p4_filetypes_historical:
308 p4type = p4_filetypes_historical[p4type]
309 mods = ""
310 s = p4type.split("+")
311 base = s[0]
312 mods = ""
313 if len(s) > 1:
314 mods = s[1]
315 return (base, mods)
316
Luke Diamand60df0712012-02-23 07:51:30 +0000317#
318# return the raw p4 type of a file (text, text+ko, etc)
319#
Pete Wyckoff79467e62014-01-21 18:16:45 -0500320def p4_type(f):
321 results = p4CmdList(["fstat", "-T", "headType", wildcard_encode(f)])
Luke Diamand60df0712012-02-23 07:51:30 +0000322 return results[0]['headType']
323
324#
325# Given a type base and modifier, return a regexp matching
326# the keywords that can be expanded in the file
327#
328def p4_keywords_regexp_for_type(base, type_mods):
329 if base in ("text", "unicode", "binary"):
330 kwords = None
331 if "ko" in type_mods:
332 kwords = 'Id|Header'
333 elif "k" in type_mods:
334 kwords = 'Id|Header|Author|Date|DateTime|Change|File|Revision'
335 else:
336 return None
337 pattern = r"""
338 \$ # Starts with a dollar, followed by...
339 (%s) # one of the keywords, followed by...
Pete Wyckoff6b2bf412012-11-04 17:04:02 -0500340 (:[^$\n]+)? # possibly an old expansion, followed by...
Luke Diamand60df0712012-02-23 07:51:30 +0000341 \$ # another dollar
342 """ % kwords
343 return pattern
344 else:
345 return None
346
347#
348# Given a file, return a regexp matching the possible
349# RCS keywords that will be expanded, or None for files
350# with kw expansion turned off.
351#
352def p4_keywords_regexp_for_file(file):
353 if not os.path.exists(file):
354 return None
355 else:
356 (type_base, type_mods) = split_p4_type(p4_type(file))
357 return p4_keywords_regexp_for_type(type_base, type_mods)
David Brownb9fc6ea2007-09-19 13:12:48 -0700358
Chris Pettittc65b6702007-11-01 20:43:14 -0700359def setP4ExecBit(file, mode):
360 # Reopens an already open file and changes the execute bit to match
361 # the execute bit setting in the passed in mode.
362
363 p4Type = "+x"
364
365 if not isModeExec(mode):
366 p4Type = getP4OpenedType(file)
367 p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
368 p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
369 if p4Type[-1] == "+":
370 p4Type = p4Type[0:-1]
371
Luke Diamand6de040d2011-10-16 10:47:52 -0400372 p4_reopen(p4Type, file)
Chris Pettittc65b6702007-11-01 20:43:14 -0700373
374def getP4OpenedType(file):
375 # Returns the perforce file type for the given file.
376
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400377 result = p4_read_pipe(["opened", wildcard_encode(file)])
Blair Holloway34a0dbf2015-04-04 09:46:03 +0100378 match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
Chris Pettittc65b6702007-11-01 20:43:14 -0700379 if match:
380 return match.group(1)
381 else:
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +0100382 die("Could not determine file type for %s (result: '%s')" % (file, result))
Chris Pettittc65b6702007-11-01 20:43:14 -0700383
Luke Diamand06804c72012-04-11 17:21:24 +0200384# Return the set of all p4 labels
385def getP4Labels(depotPaths):
386 labels = set()
387 if isinstance(depotPaths,basestring):
388 depotPaths = [depotPaths]
389
390 for l in p4CmdList(["labels"] + ["%s..." % p for p in depotPaths]):
391 label = l['label']
392 labels.add(label)
393
394 return labels
395
396# Return the set of all git tags
397def getGitTags():
398 gitTags = set()
399 for line in read_pipe_lines(["git", "tag"]):
400 tag = line.strip()
401 gitTags.add(tag)
402 return gitTags
403
Chris Pettittb43b0a32007-11-01 20:43:13 -0700404def diffTreePattern():
405 # This is a simple generator for the diff tree regex pattern. This could be
406 # a class variable if this and parseDiffTreeEntry were a part of a class.
407 pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
408 while True:
409 yield pattern
410
411def parseDiffTreeEntry(entry):
412 """Parses a single diff tree entry into its component elements.
413
414 See git-diff-tree(1) manpage for details about the format of the diff
415 output. This method returns a dictionary with the following elements:
416
417 src_mode - The mode of the source file
418 dst_mode - The mode of the destination file
419 src_sha1 - The sha1 for the source file
420 dst_sha1 - The sha1 fr the destination file
421 status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
422 status_score - The score for the status (applicable for 'C' and 'R'
423 statuses). This is None if there is no score.
424 src - The path for the source file.
425 dst - The path for the destination file. This is only present for
426 copy or renames. If it is not present, this is None.
427
428 If the pattern is not matched, None is returned."""
429
430 match = diffTreePattern().next().match(entry)
431 if match:
432 return {
433 'src_mode': match.group(1),
434 'dst_mode': match.group(2),
435 'src_sha1': match.group(3),
436 'dst_sha1': match.group(4),
437 'status': match.group(5),
438 'status_score': match.group(6),
439 'src': match.group(7),
440 'dst': match.group(10)
441 }
442 return None
443
Chris Pettittc65b6702007-11-01 20:43:14 -0700444def isModeExec(mode):
445 # Returns True if the given git mode represents an executable file,
446 # otherwise False.
447 return mode[-3:] == "755"
448
449def isModeExecChanged(src_mode, dst_mode):
450 return isModeExec(src_mode) != isModeExec(dst_mode)
451
Luke Diamandb9327052009-07-30 00:13:46 +0100452def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
Luke Diamand6de040d2011-10-16 10:47:52 -0400453
454 if isinstance(cmd,basestring):
455 cmd = "-G " + cmd
456 expand = True
457 else:
458 cmd = ["-G"] + cmd
459 expand = False
460
461 cmd = p4_build_cmd(cmd)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -0300462 if verbose:
Luke Diamand6de040d2011-10-16 10:47:52 -0400463 sys.stderr.write("Opening pipe: %s\n" % str(cmd))
Scott Lamb9f90c732007-07-15 20:58:10 -0700464
465 # Use a temporary file to avoid deadlocks without
466 # subprocess.communicate(), which would put another copy
467 # of stdout into memory.
468 stdin_file = None
469 if stdin is not None:
470 stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
Luke Diamand6de040d2011-10-16 10:47:52 -0400471 if isinstance(stdin,basestring):
472 stdin_file.write(stdin)
473 else:
474 for i in stdin:
475 stdin_file.write(i + '\n')
Scott Lamb9f90c732007-07-15 20:58:10 -0700476 stdin_file.flush()
477 stdin_file.seek(0)
478
Luke Diamand6de040d2011-10-16 10:47:52 -0400479 p4 = subprocess.Popen(cmd,
480 shell=expand,
Scott Lamb9f90c732007-07-15 20:58:10 -0700481 stdin=stdin_file,
482 stdout=subprocess.PIPE)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100483
484 result = []
485 try:
486 while True:
Scott Lamb9f90c732007-07-15 20:58:10 -0700487 entry = marshal.load(p4.stdout)
Andrew Garberc3f61632011-04-07 02:01:21 -0400488 if cb is not None:
489 cb(entry)
490 else:
491 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100492 except EOFError:
493 pass
Scott Lamb9f90c732007-07-15 20:58:10 -0700494 exitCode = p4.wait()
495 if exitCode != 0:
Simon Hausmannac3e0d72007-05-23 23:32:32 +0200496 entry = {}
497 entry["p4ExitCode"] = exitCode
498 result.append(entry)
Simon Hausmann86949ee2007-03-19 20:59:12 +0100499
500 return result
501
502def p4Cmd(cmd):
503 list = p4CmdList(cmd)
504 result = {}
505 for entry in list:
506 result.update(entry)
507 return result;
508
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100509def p4Where(depotPath):
510 if not depotPath.endswith("/"):
511 depotPath += "/"
Vitor Antunescd884102015-04-21 23:49:30 +0100512 depotPathLong = depotPath + "..."
513 outputList = p4CmdList(["where", depotPathLong])
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100514 output = None
515 for entry in outputList:
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100516 if "depotFile" in entry:
Vitor Antunescd884102015-04-21 23:49:30 +0100517 # Search for the base client side depot path, as long as it starts with the branch's P4 path.
518 # The base path always ends with "/...".
519 if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":
Tor Arvid Lund75bc9572008-12-09 16:41:50 +0100520 output = entry
521 break
522 elif "data" in entry:
523 data = entry.get("data")
524 space = data.find(" ")
525 if data[:space] == depotPath:
526 output = entry
527 break
Tor Arvid Lund7f705dc2008-12-04 14:37:33 +0100528 if output == None:
529 return ""
Simon Hausmanndc524032007-05-21 09:34:56 +0200530 if output["code"] == "error":
531 return ""
Simon Hausmanncb2c9db2007-03-24 09:15:11 +0100532 clientPath = ""
533 if "path" in output:
534 clientPath = output.get("path")
535 elif "data" in output:
536 data = output.get("data")
537 lastSpace = data.rfind(" ")
538 clientPath = data[lastSpace + 1:]
539
540 if clientPath.endswith("..."):
541 clientPath = clientPath[:-3]
542 return clientPath
543
Simon Hausmann86949ee2007-03-19 20:59:12 +0100544def currentGitBranch():
Luke Diamand00ad6e32015-11-21 09:54:41 +0000545 retcode = system(["git", "symbolic-ref", "-q", "HEAD"], ignore_error=True)
546 if retcode != 0:
547 # on a detached head
548 return None
549 else:
550 return read_pipe(["git", "name-rev", "HEAD"]).split(" ")[1].strip()
Simon Hausmann86949ee2007-03-19 20:59:12 +0100551
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100552def isValidGitDir(path):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300553 if (os.path.exists(path + "/HEAD")
554 and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
Simon Hausmann4f5cf762007-03-19 22:25:17 +0100555 return True;
556 return False
557
Simon Hausmann463e8af2007-05-17 09:13:54 +0200558def parseRevision(ref):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -0300559 return read_pipe("git rev-parse %s" % ref).strip()
Simon Hausmann463e8af2007-05-17 09:13:54 +0200560
Pete Wyckoff28755db2011-12-24 21:07:40 -0500561def branchExists(ref):
562 rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref],
563 ignore_error=True)
564 return len(rev) > 0
565
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100566def extractLogMessageFromGitCommit(commit):
567 logMessage = ""
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300568
569 ## fixme: title is first line of commit, not 1st paragraph.
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100570 foundTitle = False
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -0300571 for log in read_pipe_lines("git cat-file commit %s" % commit):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100572 if not foundTitle:
573 if len(log) == 1:
Simon Hausmann1c094182007-05-01 23:15:48 +0200574 foundTitle = True
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100575 continue
576
577 logMessage += log
578 return logMessage
579
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300580def extractSettingsGitLog(log):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100581 values = {}
582 for line in log.split("\n"):
583 line = line.strip()
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300584 m = re.search (r"^ *\[git-p4: (.*)\]$", line)
585 if not m:
586 continue
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100587
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -0300588 assignments = m.group(1).split (':')
589 for a in assignments:
590 vals = a.split ('=')
591 key = vals[0].strip()
592 val = ('='.join (vals[1:])).strip()
593 if val.endswith ('\"') and val.startswith('"'):
594 val = val[1:-1]
595
596 values[key] = val
597
Simon Hausmann845b42c2007-06-07 09:19:34 +0200598 paths = values.get("depot-paths")
599 if not paths:
600 paths = values.get("depot-path")
Simon Hausmanna3fdd572007-06-07 22:54:32 +0200601 if paths:
602 values['depot-paths'] = paths.split(',')
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300603 return values
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100604
Simon Hausmann8136a632007-03-22 21:27:14 +0100605def gitBranchExists(branch):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -0300606 proc = subprocess.Popen(["git", "rev-parse", branch],
607 stderr=subprocess.PIPE, stdout=subprocess.PIPE);
Simon Hausmanncaace112007-05-15 14:57:57 +0200608 return proc.wait() == 0;
Simon Hausmann8136a632007-03-22 21:27:14 +0100609
John Chapman36bd8442008-11-08 14:22:49 +1100610_gitConfig = {}
Pete Wyckoffb345d6c2013-01-26 22:11:23 -0500611
Pete Wyckoff0d609032013-01-26 22:11:24 -0500612def gitConfig(key):
John Chapman36bd8442008-11-08 14:22:49 +1100613 if not _gitConfig.has_key(key):
Pete Wyckoff0d609032013-01-26 22:11:24 -0500614 cmd = [ "git", "config", key ]
Pete Wyckoffb345d6c2013-01-26 22:11:23 -0500615 s = read_pipe(cmd, ignore_error=True)
616 _gitConfig[key] = s.strip()
John Chapman36bd8442008-11-08 14:22:49 +1100617 return _gitConfig[key]
Simon Hausmann01265102007-05-25 10:36:10 +0200618
Pete Wyckoff0d609032013-01-26 22:11:24 -0500619def gitConfigBool(key):
620 """Return a bool, using git config --bool. It is True only if the
621 variable is set to true, and False if set to false or not present
622 in the config."""
623
624 if not _gitConfig.has_key(key):
625 cmd = [ "git", "config", "--bool", key ]
626 s = read_pipe(cmd, ignore_error=True)
627 v = s.strip()
628 _gitConfig[key] = v == "true"
Simon Hausmann062410b2007-07-18 10:56:31 +0200629 return _gitConfig[key]
630
Vitor Antunes7199cf12011-08-19 00:44:05 +0100631def gitConfigList(key):
632 if not _gitConfig.has_key(key):
Pete Wyckoff2abba302013-01-26 22:11:22 -0500633 s = read_pipe(["git", "config", "--get-all", key], ignore_error=True)
634 _gitConfig[key] = s.strip().split(os.linesep)
Vitor Antunes7199cf12011-08-19 00:44:05 +0100635 return _gitConfig[key]
636
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500637def p4BranchesInGit(branchesAreInRemotes=True):
638 """Find all the branches whose names start with "p4/", looking
639 in remotes or heads as specified by the argument. Return
640 a dictionary of { branch: revision } for each one found.
641 The branch names are the short names, without any
642 "p4/" prefix."""
643
Simon Hausmann062410b2007-07-18 10:56:31 +0200644 branches = {}
645
646 cmdline = "git rev-parse --symbolic "
647 if branchesAreInRemotes:
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500648 cmdline += "--remotes"
Simon Hausmann062410b2007-07-18 10:56:31 +0200649 else:
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500650 cmdline += "--branches"
Simon Hausmann062410b2007-07-18 10:56:31 +0200651
652 for line in read_pipe_lines(cmdline):
653 line = line.strip()
654
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500655 # only import to p4/
656 if not line.startswith('p4/'):
Simon Hausmann062410b2007-07-18 10:56:31 +0200657 continue
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500658 # special symbolic ref to p4/master
659 if line == "p4/HEAD":
660 continue
Simon Hausmann062410b2007-07-18 10:56:31 +0200661
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500662 # strip off p4/ prefix
663 branch = line[len("p4/"):]
Simon Hausmann062410b2007-07-18 10:56:31 +0200664
665 branches[branch] = parseRevision(line)
Pete Wyckoff2c8037e2013-01-14 19:46:57 -0500666
Simon Hausmann062410b2007-07-18 10:56:31 +0200667 return branches
668
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -0500669def branch_exists(branch):
670 """Make sure that the given ref name really exists."""
671
672 cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
673 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
674 out, _ = p.communicate()
675 if p.returncode:
676 return False
677 # expect exactly one line of output: the branch name
678 return out.rstrip() == branch
679
Simon Hausmann9ceab362007-06-22 00:01:57 +0200680def findUpstreamBranchPoint(head = "HEAD"):
Simon Hausmann86506fe2007-07-18 12:40:12 +0200681 branches = p4BranchesInGit()
682 # map from depot-path to branch name
683 branchByDepotPath = {}
684 for branch in branches.keys():
685 tip = branches[branch]
686 log = extractLogMessageFromGitCommit(tip)
687 settings = extractSettingsGitLog(log)
688 if settings.has_key("depot-paths"):
689 paths = ",".join(settings["depot-paths"])
690 branchByDepotPath[paths] = "remotes/p4/" + branch
691
Simon Hausmann27d2d812007-06-12 14:31:59 +0200692 settings = None
Simon Hausmann27d2d812007-06-12 14:31:59 +0200693 parent = 0
694 while parent < 65535:
Simon Hausmann9ceab362007-06-22 00:01:57 +0200695 commit = head + "~%s" % parent
Simon Hausmann27d2d812007-06-12 14:31:59 +0200696 log = extractLogMessageFromGitCommit(commit)
697 settings = extractSettingsGitLog(log)
Simon Hausmann86506fe2007-07-18 12:40:12 +0200698 if settings.has_key("depot-paths"):
699 paths = ",".join(settings["depot-paths"])
700 if branchByDepotPath.has_key(paths):
701 return [branchByDepotPath[paths], settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200702
Simon Hausmann86506fe2007-07-18 12:40:12 +0200703 parent = parent + 1
Simon Hausmann27d2d812007-06-12 14:31:59 +0200704
Simon Hausmann86506fe2007-07-18 12:40:12 +0200705 return ["", settings]
Simon Hausmann27d2d812007-06-12 14:31:59 +0200706
Simon Hausmann5ca44612007-08-24 17:44:16 +0200707def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent=True):
708 if not silent:
709 print ("Creating/updating branch(es) in %s based on origin branch(es)"
710 % localRefPrefix)
711
712 originPrefix = "origin/p4/"
713
714 for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
715 line = line.strip()
716 if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
717 continue
718
719 headName = line[len(originPrefix):]
720 remoteHead = localRefPrefix + headName
721 originHead = line
722
723 original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
724 if (not original.has_key('depot-paths')
725 or not original.has_key('change')):
726 continue
727
728 update = False
729 if not gitBranchExists(remoteHead):
730 if verbose:
731 print "creating %s" % remoteHead
732 update = True
733 else:
734 settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
735 if settings.has_key('change') > 0:
736 if settings['depot-paths'] == original['depot-paths']:
737 originP4Change = int(original['change'])
738 p4Change = int(settings['change'])
739 if originP4Change > p4Change:
740 print ("%s (%s) is newer than %s (%s). "
741 "Updating p4 branch from origin."
742 % (originHead, originP4Change,
743 remoteHead, p4Change))
744 update = True
745 else:
746 print ("Ignoring: %s was imported from %s while "
747 "%s was imported from %s"
748 % (originHead, ','.join(original['depot-paths']),
749 remoteHead, ','.join(settings['depot-paths'])))
750
751 if update:
752 system("git update-ref %s %s" % (remoteHead, originHead))
753
754def originP4BranchesExist():
755 return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
756
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200757
Luke Diamand1051ef02015-06-10 08:30:59 +0100758def p4ParseNumericChangeRange(parts):
759 changeStart = int(parts[0][1:])
760 if parts[1] == '#head':
761 changeEnd = p4_last_change()
762 else:
763 changeEnd = int(parts[1])
764
765 return (changeStart, changeEnd)
766
767def chooseBlockSize(blockSize):
768 if blockSize:
769 return blockSize
770 else:
771 return defaultBlockSize
772
773def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):
774 assert depotPaths
775
776 # Parse the change range into start and end. Try to find integer
777 # revision ranges as these can be broken up into blocks to avoid
778 # hitting server-side limits (maxrows, maxscanresults). But if
779 # that doesn't work, fall back to using the raw revision specifier
780 # strings, without using block mode.
781
Lex Spoon96b2d542015-04-20 11:00:20 -0400782 if changeRange is None or changeRange == '':
Luke Diamand1051ef02015-06-10 08:30:59 +0100783 changeStart = 1
784 changeEnd = p4_last_change()
785 block_size = chooseBlockSize(requestedBlockSize)
Lex Spoon96b2d542015-04-20 11:00:20 -0400786 else:
787 parts = changeRange.split(',')
788 assert len(parts) == 2
Luke Diamand1051ef02015-06-10 08:30:59 +0100789 try:
790 (changeStart, changeEnd) = p4ParseNumericChangeRange(parts)
791 block_size = chooseBlockSize(requestedBlockSize)
792 except:
793 changeStart = parts[0][1:]
794 changeEnd = parts[1]
795 if requestedBlockSize:
796 die("cannot use --changes-block-size with non-numeric revisions")
797 block_size = None
Lex Spoon96b2d542015-04-20 11:00:20 -0400798
799 # Accumulate change numbers in a dictionary to avoid duplicates
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500800 changes = {}
Lex Spoon96b2d542015-04-20 11:00:20 -0400801
802 for p in depotPaths:
803 # Retrieve changes a block at a time, to prevent running
Luke Diamand1051ef02015-06-10 08:30:59 +0100804 # into a MaxResults/MaxScanRows error from the server.
805
806 while True:
Lex Spoon96b2d542015-04-20 11:00:20 -0400807 cmd = ['changes']
Luke Diamand1051ef02015-06-10 08:30:59 +0100808
809 if block_size:
810 end = min(changeEnd, changeStart + block_size)
811 revisionRange = "%d,%d" % (changeStart, end)
812 else:
813 revisionRange = "%s,%s" % (changeStart, changeEnd)
814
815 cmd += ["%s...@%s" % (p, revisionRange)]
816
Lex Spoon96b2d542015-04-20 11:00:20 -0400817 for line in p4_read_pipe_lines(cmd):
818 changeNum = int(line.split(" ")[1])
Lex Spoon96b2d542015-04-20 11:00:20 -0400819 changes[changeNum] = True
Luke Diamand1051ef02015-06-10 08:30:59 +0100820
821 if not block_size:
822 break
823
824 if end >= changeEnd:
825 break
826
827 changeStart = end + 1
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200828
Pete Wyckoffb4b0ba02009-02-18 13:12:14 -0500829 changelist = changes.keys()
830 changelist.sort()
831 return changelist
Simon Hausmann4f6432d2007-08-26 15:56:36 +0200832
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +0100833def p4PathStartsWith(path, prefix):
834 # This method tries to remedy a potential mixed-case issue:
835 #
836 # If UserA adds //depot/DirA/file1
837 # and UserB adds //depot/dira/file2
838 #
839 # we may or may not have a problem. If you have core.ignorecase=true,
840 # we treat DirA and dira as the same directory
Pete Wyckoff0d609032013-01-26 22:11:24 -0500841 if gitConfigBool("core.ignorecase"):
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +0100842 return path.lower().startswith(prefix.lower())
843 return path.startswith(prefix)
844
Pete Wyckoff543987b2012-02-25 20:06:25 -0500845def getClientSpec():
846 """Look at the p4 client spec, create a View() object that contains
847 all the mappings, and return it."""
848
849 specList = p4CmdList("client -o")
850 if len(specList) != 1:
851 die('Output from "client -o" is %d lines, expecting 1' %
852 len(specList))
853
854 # dictionary of all client parameters
855 entry = specList[0]
856
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +0900857 # the //client/ name
858 client_name = entry["Client"]
859
Pete Wyckoff543987b2012-02-25 20:06:25 -0500860 # just the keys that start with "View"
861 view_keys = [ k for k in entry.keys() if k.startswith("View") ]
862
863 # hold this new View
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +0900864 view = View(client_name)
Pete Wyckoff543987b2012-02-25 20:06:25 -0500865
866 # append the lines, in order, to the view
867 for view_num in range(len(view_keys)):
868 k = "View%d" % view_num
869 if k not in view_keys:
870 die("Expected view key %s missing" % k)
871 view.append(entry[k])
872
873 return view
874
875def getClientRoot():
876 """Grab the client directory."""
877
878 output = p4CmdList("client -o")
879 if len(output) != 1:
880 die('Output from "client -o" is %d lines, expecting 1' % len(output))
881
882 entry = output[0]
883 if "Root" not in entry:
884 die('Client has no "Root"')
885
886 return entry["Root"]
887
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400888#
889# P4 wildcards are not allowed in filenames. P4 complains
890# if you simply add them, but you can force it with "-f", in
891# which case it translates them into %xx encoding internally.
892#
893def wildcard_decode(path):
894 # Search for and fix just these four characters. Do % last so
895 # that fixing it does not inadvertently create new %-escapes.
896 # Cannot have * in a filename in windows; untested as to
897 # what p4 would do in such a case.
898 if not platform.system() == "Windows":
899 path = path.replace("%2A", "*")
900 path = path.replace("%23", "#") \
901 .replace("%40", "@") \
902 .replace("%25", "%")
903 return path
904
905def wildcard_encode(path):
906 # do % first to avoid double-encoding the %s introduced here
907 path = path.replace("%", "%25") \
908 .replace("*", "%2A") \
909 .replace("#", "%23") \
910 .replace("@", "%40")
911 return path
912
913def wildcard_present(path):
Brandon Casey598354c2013-01-26 11:14:32 -0800914 m = re.search("[*#@%]", path)
915 return m is not None
Pete Wyckoff9d7d4462012-04-29 20:57:17 -0400916
Simon Hausmannb9847332007-03-20 20:54:23 +0100917class Command:
918 def __init__(self):
919 self.usage = "usage: %prog [options]"
Simon Hausmann8910ac02007-03-26 08:18:55 +0200920 self.needsGit = True
Luke Diamand6a10b6a2012-04-24 09:33:23 +0100921 self.verbose = False
Simon Hausmannb9847332007-03-20 20:54:23 +0100922
Luke Diamand3ea2cfd2011-04-21 20:50:23 +0100923class P4UserMap:
924 def __init__(self):
925 self.userMapFromPerforceServer = False
Luke Diamandaffb4742012-01-19 09:52:27 +0000926 self.myP4UserId = None
927
928 def p4UserId(self):
929 if self.myP4UserId:
930 return self.myP4UserId
931
932 results = p4CmdList("user -o")
933 for r in results:
934 if r.has_key('User'):
935 self.myP4UserId = r['User']
936 return r['User']
937 die("Could not find your p4 user id")
938
939 def p4UserIsMe(self, p4User):
940 # return True if the given p4 user is actually me
941 me = self.p4UserId()
942 if not p4User or p4User != me:
943 return False
944 else:
945 return True
Luke Diamand3ea2cfd2011-04-21 20:50:23 +0100946
947 def getUserCacheFilename(self):
948 home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
949 return home + "/.gitp4-usercache.txt"
950
951 def getUserMapFromPerforceServer(self):
952 if self.userMapFromPerforceServer:
953 return
954 self.users = {}
955 self.emails = {}
956
957 for output in p4CmdList("users"):
958 if not output.has_key("User"):
959 continue
960 self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
961 self.emails[output["Email"]] = output["User"]
962
963
964 s = ''
965 for (key, val) in self.users.items():
966 s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
967
968 open(self.getUserCacheFilename(), "wb").write(s)
969 self.userMapFromPerforceServer = True
970
971 def loadUserMapFromCache(self):
972 self.users = {}
973 self.userMapFromPerforceServer = False
974 try:
975 cache = open(self.getUserCacheFilename(), "rb")
976 lines = cache.readlines()
977 cache.close()
978 for line in lines:
979 entry = line.strip().split("\t")
980 self.users[entry[0]] = entry[1]
981 except IOError:
982 self.getUserMapFromPerforceServer()
983
Simon Hausmannb9847332007-03-20 20:54:23 +0100984class P4Debug(Command):
Simon Hausmann86949ee2007-03-19 20:59:12 +0100985 def __init__(self):
Simon Hausmann6ae8de82007-03-22 21:10:25 +0100986 Command.__init__(self)
Luke Diamand6a10b6a2012-04-24 09:33:23 +0100987 self.options = []
Simon Hausmannc8c39112007-03-19 21:02:30 +0100988 self.description = "A tool to debug the output of p4 -G."
Simon Hausmann8910ac02007-03-26 08:18:55 +0200989 self.needsGit = False
Simon Hausmann86949ee2007-03-19 20:59:12 +0100990
991 def run(self, args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300992 j = 0
Luke Diamand6de040d2011-10-16 10:47:52 -0400993 for output in p4CmdList(args):
Han-Wen Nienhuysb1ce9442007-05-23 18:49:35 -0300994 print 'Element: %d' % j
995 j += 1
Simon Hausmann86949ee2007-03-19 20:59:12 +0100996 print output
Simon Hausmannb9847332007-03-20 20:54:23 +0100997 return True
Simon Hausmann86949ee2007-03-19 20:59:12 +0100998
Simon Hausmann58346842007-05-21 22:57:06 +0200999class P4RollBack(Command):
1000 def __init__(self):
1001 Command.__init__(self)
1002 self.options = [
Simon Hausmann0c66a782007-05-23 20:07:57 +02001003 optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
Simon Hausmann58346842007-05-21 22:57:06 +02001004 ]
1005 self.description = "A tool to debug the multi-branch import. Don't use :)"
Simon Hausmann0c66a782007-05-23 20:07:57 +02001006 self.rollbackLocalBranches = False
Simon Hausmann58346842007-05-21 22:57:06 +02001007
1008 def run(self, args):
1009 if len(args) != 1:
1010 return False
1011 maxChange = int(args[0])
Simon Hausmann0c66a782007-05-23 20:07:57 +02001012
Simon Hausmannad192f22007-05-23 23:44:19 +02001013 if "p4ExitCode" in p4Cmd("changes -m 1"):
Simon Hausmann66a2f522007-05-23 23:40:48 +02001014 die("Problems executing p4");
1015
Simon Hausmann0c66a782007-05-23 20:07:57 +02001016 if self.rollbackLocalBranches:
1017 refPrefix = "refs/heads/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -03001018 lines = read_pipe_lines("git rev-parse --symbolic --branches")
Simon Hausmann0c66a782007-05-23 20:07:57 +02001019 else:
1020 refPrefix = "refs/remotes/"
Han-Wen Nienhuysb016d392007-05-23 17:10:46 -03001021 lines = read_pipe_lines("git rev-parse --symbolic --remotes")
Simon Hausmann0c66a782007-05-23 20:07:57 +02001022
1023 for line in lines:
1024 if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03001025 line = line.strip()
1026 ref = refPrefix + line
Simon Hausmann58346842007-05-21 22:57:06 +02001027 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001028 settings = extractSettingsGitLog(log)
1029
1030 depotPaths = settings['depot-paths']
1031 change = settings['change']
1032
Simon Hausmann58346842007-05-21 22:57:06 +02001033 changed = False
Simon Hausmann52102d42007-05-21 23:44:24 +02001034
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03001035 if len(p4Cmd("changes -m 1 " + ' '.join (['%s...@%s' % (p, maxChange)
1036 for p in depotPaths]))) == 0:
Simon Hausmann52102d42007-05-21 23:44:24 +02001037 print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
1038 system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
1039 continue
1040
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001041 while change and int(change) > maxChange:
Simon Hausmann58346842007-05-21 22:57:06 +02001042 changed = True
Simon Hausmann52102d42007-05-21 23:44:24 +02001043 if self.verbose:
1044 print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
Simon Hausmann58346842007-05-21 22:57:06 +02001045 system("git update-ref %s \"%s^\"" % (ref, ref))
1046 log = extractLogMessageFromGitCommit(ref)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03001047 settings = extractSettingsGitLog(log)
1048
1049
1050 depotPaths = settings['depot-paths']
1051 change = settings['change']
Simon Hausmann58346842007-05-21 22:57:06 +02001052
1053 if changed:
Simon Hausmann52102d42007-05-21 23:44:24 +02001054 print "%s rewound to %s" % (ref, change)
Simon Hausmann58346842007-05-21 22:57:06 +02001055
1056 return True
1057
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001058class P4Submit(Command, P4UserMap):
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001059
1060 conflict_behavior_choices = ("ask", "skip", "quit")
1061
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001062 def __init__(self):
Simon Hausmannb9847332007-03-20 20:54:23 +01001063 Command.__init__(self)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001064 P4UserMap.__init__(self)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001065 self.options = [
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001066 optparse.make_option("--origin", dest="origin"),
Vitor Antunesae901092011-02-20 01:18:24 +00001067 optparse.make_option("-M", dest="detectRenames", action="store_true"),
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001068 # preserve the user, requires relevant p4 permissions
1069 optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02001070 optparse.make_option("--export-labels", dest="exportLabels", action="store_true"),
Pete Wyckoffef739f02012-09-09 16:16:11 -04001071 optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001072 optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001073 optparse.make_option("--conflict", dest="conflict_behavior",
Pete Wyckoff44e8d262013-01-14 19:47:08 -05001074 choices=self.conflict_behavior_choices),
1075 optparse.make_option("--branch", dest="branch"),
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001076 ]
1077 self.description = "Submit changes from git to the perforce depot."
Simon Hausmannc9b50e62007-03-29 19:15:24 +02001078 self.usage += " [name of git branch to submit into perforce depot]"
Simon Hausmann95124972007-03-23 09:16:07 +01001079 self.origin = ""
Vitor Antunesae901092011-02-20 01:18:24 +00001080 self.detectRenames = False
Pete Wyckoff0d609032013-01-26 22:11:24 -05001081 self.preserveUser = gitConfigBool("git-p4.preserveUser")
Pete Wyckoffef739f02012-09-09 16:16:11 -04001082 self.dry_run = False
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001083 self.prepare_p4_only = False
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001084 self.conflict_behavior = None
Marius Storm-Olsenf7baba82007-06-07 14:07:01 +02001085 self.isWindows = (platform.system() == "Windows")
Luke Diamand06804c72012-04-11 17:21:24 +02001086 self.exportLabels = False
Pete Wyckoff249da4c2012-11-23 17:35:35 -05001087 self.p4HasMoveCommand = p4_has_move_command()
Pete Wyckoff44e8d262013-01-14 19:47:08 -05001088 self.branch = None
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001089
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001090 def check(self):
1091 if len(p4CmdList("opened ...")) > 0:
1092 die("You have files opened with perforce! Close them before starting the sync.")
1093
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001094 def separate_jobs_from_description(self, message):
1095 """Extract and return a possible Jobs field in the commit
1096 message. It goes into a separate section in the p4 change
1097 specification.
1098
1099 A jobs line starts with "Jobs:" and looks like a new field
1100 in a form. Values are white-space separated on the same
1101 line or on following lines that start with a tab.
1102
1103 This does not parse and extract the full git commit message
1104 like a p4 form. It just sees the Jobs: line as a marker
1105 to pass everything from then on directly into the p4 form,
1106 but outside the description section.
1107
1108 Return a tuple (stripped log message, jobs string)."""
1109
1110 m = re.search(r'^Jobs:', message, re.MULTILINE)
1111 if m is None:
1112 return (message, None)
1113
1114 jobtext = message[m.start():]
1115 stripped_message = message[:m.start()].rstrip()
1116 return (stripped_message, jobtext)
1117
1118 def prepareLogMessage(self, template, message, jobs):
1119 """Edits the template returned from "p4 change -o" to insert
1120 the message in the Description field, and the jobs text in
1121 the Jobs field."""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001122 result = ""
1123
Simon Hausmannedae1e22008-02-19 09:29:06 +01001124 inDescriptionSection = False
1125
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001126 for line in template.split("\n"):
1127 if line.startswith("#"):
1128 result += line + "\n"
1129 continue
1130
Simon Hausmannedae1e22008-02-19 09:29:06 +01001131 if inDescriptionSection:
Michael Horowitzc9dbab02011-02-25 21:31:13 -05001132 if line.startswith("Files:") or line.startswith("Jobs:"):
Simon Hausmannedae1e22008-02-19 09:29:06 +01001133 inDescriptionSection = False
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001134 # insert Jobs section
1135 if jobs:
1136 result += jobs + "\n"
Simon Hausmannedae1e22008-02-19 09:29:06 +01001137 else:
1138 continue
1139 else:
1140 if line.startswith("Description:"):
1141 inDescriptionSection = True
1142 line += "\n"
1143 for messageLine in message.split("\n"):
1144 line += "\t" + messageLine + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001145
Simon Hausmannedae1e22008-02-19 09:29:06 +01001146 result += line + "\n"
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001147
1148 return result
1149
Luke Diamand60df0712012-02-23 07:51:30 +00001150 def patchRCSKeywords(self, file, pattern):
1151 # Attempt to zap the RCS keywords in a p4 controlled file matching the given pattern
1152 (handle, outFileName) = tempfile.mkstemp(dir='.')
1153 try:
1154 outFile = os.fdopen(handle, "w+")
1155 inFile = open(file, "r")
1156 regexp = re.compile(pattern, re.VERBOSE)
1157 for line in inFile.readlines():
1158 line = regexp.sub(r'$\1$', line)
1159 outFile.write(line)
1160 inFile.close()
1161 outFile.close()
1162 # Forcibly overwrite the original file
1163 os.unlink(file)
1164 shutil.move(outFileName, file)
1165 except:
1166 # cleanup our temporary file
1167 os.unlink(outFileName)
1168 print "Failed to strip RCS keywords in %s" % file
1169 raise
1170
1171 print "Patched up RCS keywords in %s" % file
1172
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001173 def p4UserForCommit(self,id):
1174 # Return the tuple (perforce user,git email) for a given git commit id
1175 self.getUserMapFromPerforceServer()
Pete Wyckoff9bf28852013-01-26 22:11:20 -05001176 gitEmail = read_pipe(["git", "log", "--max-count=1",
1177 "--format=%ae", id])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001178 gitEmail = gitEmail.strip()
1179 if not self.emails.has_key(gitEmail):
1180 return (None,gitEmail)
1181 else:
1182 return (self.emails[gitEmail],gitEmail)
1183
1184 def checkValidP4Users(self,commits):
1185 # check if any git authors cannot be mapped to p4 users
1186 for id in commits:
1187 (user,email) = self.p4UserForCommit(id)
1188 if not user:
1189 msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
Pete Wyckoff0d609032013-01-26 22:11:24 -05001190 if gitConfigBool("git-p4.allowMissingP4Users"):
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001191 print "%s" % msg
1192 else:
1193 die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
1194
1195 def lastP4Changelist(self):
1196 # Get back the last changelist number submitted in this client spec. This
1197 # then gets used to patch up the username in the change. If the same
1198 # client spec is being used by multiple processes then this might go
1199 # wrong.
1200 results = p4CmdList("client -o") # find the current client
1201 client = None
1202 for r in results:
1203 if r.has_key('Client'):
1204 client = r['Client']
1205 break
1206 if not client:
1207 die("could not get client spec")
Luke Diamand6de040d2011-10-16 10:47:52 -04001208 results = p4CmdList(["changes", "-c", client, "-m", "1"])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001209 for r in results:
1210 if r.has_key('change'):
1211 return r['change']
1212 die("Could not get changelist number for last submit - cannot patch up user details")
1213
1214 def modifyChangelistUser(self, changelist, newUser):
1215 # fixup the user field of a changelist after it has been submitted.
1216 changes = p4CmdList("change -o %s" % changelist)
Luke Diamandecdba362011-05-07 11:19:43 +01001217 if len(changes) != 1:
1218 die("Bad output from p4 change modifying %s to user %s" %
1219 (changelist, newUser))
1220
1221 c = changes[0]
1222 if c['User'] == newUser: return # nothing to do
1223 c['User'] = newUser
1224 input = marshal.dumps(c)
1225
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001226 result = p4CmdList("change -f -i", stdin=input)
1227 for r in result:
1228 if r.has_key('code'):
1229 if r['code'] == 'error':
1230 die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
1231 if r.has_key('data'):
1232 print("Updated user field for changelist %s to %s" % (changelist, newUser))
1233 return
1234 die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
1235
1236 def canChangeChangelists(self):
1237 # check to see if we have p4 admin or super-user permissions, either of
1238 # which are required to modify changelists.
Luke Diamand52a48802012-01-19 09:52:25 +00001239 results = p4CmdList(["protects", self.depotPath])
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001240 for r in results:
1241 if r.has_key('perm'):
1242 if r['perm'] == 'admin':
1243 return 1
1244 if r['perm'] == 'super':
1245 return 1
1246 return 0
1247
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001248 def prepareSubmitTemplate(self):
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001249 """Run "p4 change -o" to grab a change specification template.
1250 This does not use "p4 -G", as it is nice to keep the submission
1251 template in original order, since a human might edit it.
1252
1253 Remove lines in the Files section that show changes to files
1254 outside the depot path we're committing into."""
1255
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001256 template = ""
1257 inFilesSection = False
Luke Diamand6de040d2011-10-16 10:47:52 -04001258 for line in p4_read_pipe_lines(['change', '-o']):
Marius Storm-Olsenf3e5ae42008-03-28 15:40:40 +01001259 if line.endswith("\r\n"):
1260 line = line[:-2] + "\n"
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001261 if inFilesSection:
1262 if line.startswith("\t"):
1263 # path starts and ends with a tab
1264 path = line[1:]
1265 lastTab = path.rfind("\t")
1266 if lastTab != -1:
1267 path = path[:lastTab]
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01001268 if not p4PathStartsWith(path, self.depotPath):
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001269 continue
1270 else:
1271 inFilesSection = False
1272 else:
1273 if line.startswith("Files:"):
1274 inFilesSection = True
1275
1276 template += line
1277
1278 return template
1279
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001280 def edit_template(self, template_file):
1281 """Invoke the editor to let the user change the submission
1282 message. Return true if okay to continue with the submit."""
1283
1284 # if configured to skip the editing part, just submit
Pete Wyckoff0d609032013-01-26 22:11:24 -05001285 if gitConfigBool("git-p4.skipSubmitEdit"):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001286 return True
1287
1288 # look at the modification time, to check later if the user saved
1289 # the file
1290 mtime = os.stat(template_file).st_mtime
1291
1292 # invoke the editor
Luke Diamandf95ceaf2012-04-24 09:33:21 +01001293 if os.environ.has_key("P4EDITOR") and (os.environ.get("P4EDITOR") != ""):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001294 editor = os.environ.get("P4EDITOR")
1295 else:
1296 editor = read_pipe("git var GIT_EDITOR").strip()
Luke Diamand2dade7a2015-05-19 23:23:17 +01001297 system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001298
1299 # If the file was not saved, prompt to see if this patch should
1300 # be skipped. But skip this verification step if configured so.
Pete Wyckoff0d609032013-01-26 22:11:24 -05001301 if gitConfigBool("git-p4.skipSubmitEditCheck"):
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001302 return True
1303
Pete Wyckoffd1652042011-12-17 12:39:03 -05001304 # modification time updated means user saved the file
1305 if os.stat(template_file).st_mtime > mtime:
1306 return True
1307
1308 while True:
1309 response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
1310 if response == 'y':
1311 return True
1312 if response == 'n':
1313 return False
Pete Wyckoff7c766e52011-12-04 19:22:45 -05001314
Maxime Costee2a892e2014-06-11 14:09:59 +01001315 def get_diff_description(self, editedFiles, filesToAdd):
Maxime Costeb4073bb2014-05-24 18:40:35 +01001316 # diff
1317 if os.environ.has_key("P4DIFF"):
1318 del(os.environ["P4DIFF"])
1319 diff = ""
1320 for editedFile in editedFiles:
1321 diff += p4_read_pipe(['diff', '-du',
1322 wildcard_encode(editedFile)])
1323
1324 # new file diff
1325 newdiff = ""
1326 for newFile in filesToAdd:
1327 newdiff += "==== new file ====\n"
1328 newdiff += "--- /dev/null\n"
1329 newdiff += "+++ %s\n" % newFile
1330 f = open(newFile, "r")
1331 for line in f.readlines():
1332 newdiff += "+" + line
1333 f.close()
1334
Maxime Costee2a892e2014-06-11 14:09:59 +01001335 return (diff + newdiff).replace('\r\n', '\n')
Maxime Costeb4073bb2014-05-24 18:40:35 +01001336
Han-Wen Nienhuys7cb5cbe2007-05-23 16:55:48 -03001337 def applyCommit(self, id):
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001338 """Apply one commit, return True if it succeeded."""
1339
1340 print "Applying", read_pipe(["git", "show", "-s",
1341 "--format=format:%h %s", id])
Vitor Antunesae901092011-02-20 01:18:24 +00001342
Luke Diamand848de9c2011-05-13 20:46:00 +01001343 (p4User, gitEmail) = self.p4UserForCommit(id)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001344
Gary Gibbons84cb0002012-07-04 09:40:19 -04001345 diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id))
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001346 filesToAdd = set()
1347 filesToDelete = set()
Simon Hausmannd336c152007-05-16 09:41:26 +02001348 editedFiles = set()
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001349 pureRenameCopy = set()
Chris Pettittc65b6702007-11-01 20:43:14 -07001350 filesToChangeExecBit = {}
Luke Diamand60df0712012-02-23 07:51:30 +00001351
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001352 for line in diff:
Chris Pettittb43b0a32007-11-01 20:43:13 -07001353 diff = parseDiffTreeEntry(line)
1354 modifier = diff['status']
1355 path = diff['src']
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001356 if modifier == "M":
Luke Diamand6de040d2011-10-16 10:47:52 -04001357 p4_edit(path)
Chris Pettittc65b6702007-11-01 20:43:14 -07001358 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
1359 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmannd336c152007-05-16 09:41:26 +02001360 editedFiles.add(path)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001361 elif modifier == "A":
1362 filesToAdd.add(path)
Chris Pettittc65b6702007-11-01 20:43:14 -07001363 filesToChangeExecBit[path] = diff['dst_mode']
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001364 if path in filesToDelete:
1365 filesToDelete.remove(path)
1366 elif modifier == "D":
1367 filesToDelete.add(path)
1368 if path in filesToAdd:
1369 filesToAdd.remove(path)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001370 elif modifier == "C":
1371 src, dest = diff['src'], diff['dst']
Luke Diamand6de040d2011-10-16 10:47:52 -04001372 p4_integrate(src, dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001373 pureRenameCopy.add(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001374 if diff['src_sha1'] != diff['dst_sha1']:
Luke Diamand6de040d2011-10-16 10:47:52 -04001375 p4_edit(dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001376 pureRenameCopy.discard(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001377 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
Luke Diamand6de040d2011-10-16 10:47:52 -04001378 p4_edit(dest)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001379 pureRenameCopy.discard(dest)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001380 filesToChangeExecBit[dest] = diff['dst_mode']
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001381 if self.isWindows:
1382 # turn off read-only attribute
1383 os.chmod(dest, stat.S_IWRITE)
Vitor Antunes4fddb412011-02-20 01:18:25 +00001384 os.unlink(dest)
1385 editedFiles.add(dest)
Chris Pettittd9a5f252007-10-15 22:15:06 -07001386 elif modifier == "R":
Chris Pettittb43b0a32007-11-01 20:43:13 -07001387 src, dest = diff['src'], diff['dst']
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001388 if self.p4HasMoveCommand:
1389 p4_edit(src) # src must be open before move
1390 p4_move(src, dest) # opens for (move/delete, move/add)
Pete Wyckoffb6ad6dc2012-04-29 20:57:16 -04001391 else:
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001392 p4_integrate(src, dest)
1393 if diff['src_sha1'] != diff['dst_sha1']:
1394 p4_edit(dest)
1395 else:
1396 pureRenameCopy.add(dest)
Chris Pettittc65b6702007-11-01 20:43:14 -07001397 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001398 if not self.p4HasMoveCommand:
1399 p4_edit(dest) # with move: already open, writable
Chris Pettittc65b6702007-11-01 20:43:14 -07001400 filesToChangeExecBit[dest] = diff['dst_mode']
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001401 if not self.p4HasMoveCommand:
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001402 if self.isWindows:
1403 os.chmod(dest, stat.S_IWRITE)
Gary Gibbons8e9497c2012-07-12 19:29:00 -04001404 os.unlink(dest)
1405 filesToDelete.add(src)
Chris Pettittd9a5f252007-10-15 22:15:06 -07001406 editedFiles.add(dest)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001407 else:
1408 die("unknown modifier %s for %s" % (modifier, path))
1409
Tolga Ceylan749b6682014-05-06 22:48:54 -07001410 diffcmd = "git diff-tree --full-index -p \"%s\"" % (id)
Simon Hausmann47a130b2007-05-20 16:33:21 +02001411 patchcmd = diffcmd + " | git apply "
Simon Hausmannc1b296b2007-05-20 16:55:05 +02001412 tryPatchCmd = patchcmd + "--check -"
1413 applyPatchCmd = patchcmd + "--check --apply -"
Luke Diamand60df0712012-02-23 07:51:30 +00001414 patch_succeeded = True
Simon Hausmann51a26402007-04-15 09:59:56 +02001415
Simon Hausmann47a130b2007-05-20 16:33:21 +02001416 if os.system(tryPatchCmd) != 0:
Luke Diamand60df0712012-02-23 07:51:30 +00001417 fixed_rcs_keywords = False
1418 patch_succeeded = False
Simon Hausmann51a26402007-04-15 09:59:56 +02001419 print "Unfortunately applying the change failed!"
Luke Diamand60df0712012-02-23 07:51:30 +00001420
1421 # Patch failed, maybe it's just RCS keyword woes. Look through
1422 # the patch to see if that's possible.
Pete Wyckoff0d609032013-01-26 22:11:24 -05001423 if gitConfigBool("git-p4.attemptRCSCleanup"):
Luke Diamand60df0712012-02-23 07:51:30 +00001424 file = None
1425 pattern = None
1426 kwfiles = {}
1427 for file in editedFiles | filesToDelete:
1428 # did this file's delta contain RCS keywords?
1429 pattern = p4_keywords_regexp_for_file(file)
1430
1431 if pattern:
1432 # this file is a possibility...look for RCS keywords.
1433 regexp = re.compile(pattern, re.VERBOSE)
1434 for line in read_pipe_lines(["git", "diff", "%s^..%s" % (id, id), file]):
1435 if regexp.search(line):
1436 if verbose:
1437 print "got keyword match on %s in %s in %s" % (pattern, line, file)
1438 kwfiles[file] = pattern
1439 break
1440
1441 for file in kwfiles:
1442 if verbose:
1443 print "zapping %s with %s" % (line,pattern)
Pete Wyckoffd20f0f82013-01-26 22:11:19 -05001444 # File is being deleted, so not open in p4. Must
1445 # disable the read-only bit on windows.
1446 if self.isWindows and file not in editedFiles:
1447 os.chmod(file, stat.S_IWRITE)
Luke Diamand60df0712012-02-23 07:51:30 +00001448 self.patchRCSKeywords(file, kwfiles[file])
1449 fixed_rcs_keywords = True
1450
1451 if fixed_rcs_keywords:
1452 print "Retrying the patch with RCS keywords cleaned up"
1453 if os.system(tryPatchCmd) == 0:
1454 patch_succeeded = True
1455
1456 if not patch_succeeded:
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001457 for f in editedFiles:
1458 p4_revert(f)
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001459 return False
Simon Hausmann51a26402007-04-15 09:59:56 +02001460
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001461 #
1462 # Apply the patch for real, and do add/delete/+x handling.
1463 #
Simon Hausmann47a130b2007-05-20 16:33:21 +02001464 system(applyPatchCmd)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001465
1466 for f in filesToAdd:
Luke Diamand6de040d2011-10-16 10:47:52 -04001467 p4_add(f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001468 for f in filesToDelete:
Luke Diamand6de040d2011-10-16 10:47:52 -04001469 p4_revert(f)
1470 p4_delete(f)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001471
Chris Pettittc65b6702007-11-01 20:43:14 -07001472 # Set/clear executable bits
1473 for f in filesToChangeExecBit.keys():
1474 mode = filesToChangeExecBit[f]
1475 setP4ExecBit(f, mode)
1476
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001477 #
1478 # Build p4 change description, starting with the contents
1479 # of the git commit message.
1480 #
Simon Hausmann0e36f2d2008-02-19 09:33:08 +01001481 logMessage = extractLogMessageFromGitCommit(id)
Simon Hausmann0e36f2d2008-02-19 09:33:08 +01001482 logMessage = logMessage.strip()
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001483 (logMessage, jobs) = self.separate_jobs_from_description(logMessage)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001484
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001485 template = self.prepareSubmitTemplate()
Pete Wyckofff19cb0a2012-07-04 09:34:20 -04001486 submitTemplate = self.prepareLogMessage(template, logMessage, jobs)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001487
1488 if self.preserveUser:
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001489 submitTemplate += "\n######## Actual user %s, modified after commit\n" % p4User
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001490
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001491 if self.checkAuthorship and not self.p4UserIsMe(p4User):
1492 submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
1493 submitTemplate += "######## Use option --preserve-user to modify authorship.\n"
1494 submitTemplate += "######## Variable git-p4.skipUserNameCheck hides this message.\n"
1495
1496 separatorLine = "######## everything below this line is just the diff #######\n"
Maxime Costeb4073bb2014-05-24 18:40:35 +01001497 if not self.prepare_p4_only:
1498 submitTemplate += separatorLine
Maxime Costee2a892e2014-06-11 14:09:59 +01001499 submitTemplate += self.get_diff_description(editedFiles, filesToAdd)
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001500
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001501 (handle, fileName) = tempfile.mkstemp()
Maxime Costee2a892e2014-06-11 14:09:59 +01001502 tmpFile = os.fdopen(handle, "w+b")
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001503 if self.isWindows:
1504 submitTemplate = submitTemplate.replace("\n", "\r\n")
Maxime Costeb4073bb2014-05-24 18:40:35 +01001505 tmpFile.write(submitTemplate)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001506 tmpFile.close()
1507
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001508 if self.prepare_p4_only:
1509 #
1510 # Leave the p4 tree prepared, and the submit template around
1511 # and let the user decide what to do next
1512 #
1513 print
1514 print "P4 workspace prepared for submission."
1515 print "To submit or revert, go to client workspace"
1516 print " " + self.clientPath
1517 print
1518 print "To submit, use \"p4 submit\" to write a new description,"
Luke Diamand10de86d2015-01-23 09:15:12 +00001519 print "or \"p4 submit -i <%s\" to use the one prepared by" \
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001520 " \"git p4\"." % fileName
1521 print "You can delete the file \"%s\" when finished." % fileName
1522
1523 if self.preserveUser and p4User and not self.p4UserIsMe(p4User):
1524 print "To preserve change ownership by user %s, you must\n" \
1525 "do \"p4 change -f <change>\" after submitting and\n" \
1526 "edit the User field."
1527 if pureRenameCopy:
1528 print "After submitting, renamed files must be re-synced."
1529 print "Invoke \"p4 sync -f\" on each of these files:"
1530 for f in pureRenameCopy:
1531 print " " + f
1532
1533 print
1534 print "To revert the changes, use \"p4 revert ...\", and delete"
1535 print "the submit template file \"%s\"" % fileName
1536 if filesToAdd:
1537 print "Since the commit adds new files, they must be deleted:"
1538 for f in filesToAdd:
1539 print " " + f
1540 print
1541 return True
1542
Pete Wyckoff55ac2ed2012-09-09 16:16:08 -04001543 #
1544 # Let the user edit the change description, then submit it.
1545 #
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001546 submitted = False
Luke Diamandecdba362011-05-07 11:19:43 +01001547
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001548 try:
1549 if self.edit_template(fileName):
1550 # read the edited message and submit
1551 tmpFile = open(fileName, "rb")
1552 message = tmpFile.read()
1553 tmpFile.close()
1554 if self.isWindows:
1555 message = message.replace("\r\n", "\n")
1556 submitTemplate = message[:message.index(separatorLine)]
1557 p4_write_pipe(['submit', '-i'], submitTemplate)
Luke Diamandecdba362011-05-07 11:19:43 +01001558
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001559 if self.preserveUser:
1560 if p4User:
1561 # Get last changelist number. Cannot easily get it from
1562 # the submit command output as the output is
1563 # unmarshalled.
1564 changelist = self.lastP4Changelist()
1565 self.modifyChangelistUser(changelist, p4User)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001566
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001567 # The rename/copy happened by applying a patch that created a
1568 # new file. This leaves it writable, which confuses p4.
1569 for f in pureRenameCopy:
1570 p4_sync(f, "-f")
1571 submitted = True
1572
1573 finally:
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001574 # skip this patch
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001575 if not submitted:
1576 print "Submission cancelled, undoing p4 changes."
1577 for f in editedFiles:
1578 p4_revert(f)
1579 for f in filesToAdd:
1580 p4_revert(f)
1581 os.remove(f)
1582 for f in filesToDelete:
1583 p4_revert(f)
Pete Wyckoffc47178d2012-07-04 09:34:18 -04001584
1585 os.remove(fileName)
GIRARD Etienneb7638fe2015-11-24 07:43:59 +00001586 return submitted
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001587
Luke Diamand06804c72012-04-11 17:21:24 +02001588 # Export git tags as p4 labels. Create a p4 label and then tag
1589 # with that.
1590 def exportGitTags(self, gitTags):
Luke Diamandc8942a22012-04-11 17:21:24 +02001591 validLabelRegexp = gitConfig("git-p4.labelExportRegexp")
1592 if len(validLabelRegexp) == 0:
1593 validLabelRegexp = defaultLabelRegexp
1594 m = re.compile(validLabelRegexp)
Luke Diamand06804c72012-04-11 17:21:24 +02001595
1596 for name in gitTags:
1597
1598 if not m.match(name):
1599 if verbose:
Luke Diamand05a3cec2012-05-11 07:25:17 +01001600 print "tag %s does not match regexp %s" % (name, validLabelRegexp)
Luke Diamand06804c72012-04-11 17:21:24 +02001601 continue
1602
1603 # Get the p4 commit this corresponds to
Luke Diamandc8942a22012-04-11 17:21:24 +02001604 logMessage = extractLogMessageFromGitCommit(name)
1605 values = extractSettingsGitLog(logMessage)
Luke Diamand06804c72012-04-11 17:21:24 +02001606
Luke Diamandc8942a22012-04-11 17:21:24 +02001607 if not values.has_key('change'):
Luke Diamand06804c72012-04-11 17:21:24 +02001608 # a tag pointing to something not sent to p4; ignore
1609 if verbose:
1610 print "git tag %s does not give a p4 commit" % name
1611 continue
Luke Diamandc8942a22012-04-11 17:21:24 +02001612 else:
1613 changelist = values['change']
Luke Diamand06804c72012-04-11 17:21:24 +02001614
1615 # Get the tag details.
1616 inHeader = True
1617 isAnnotated = False
1618 body = []
1619 for l in read_pipe_lines(["git", "cat-file", "-p", name]):
1620 l = l.strip()
1621 if inHeader:
1622 if re.match(r'tag\s+', l):
1623 isAnnotated = True
1624 elif re.match(r'\s*$', l):
1625 inHeader = False
1626 continue
1627 else:
1628 body.append(l)
1629
1630 if not isAnnotated:
1631 body = ["lightweight tag imported by git p4\n"]
1632
1633 # Create the label - use the same view as the client spec we are using
1634 clientSpec = getClientSpec()
1635
1636 labelTemplate = "Label: %s\n" % name
1637 labelTemplate += "Description:\n"
1638 for b in body:
1639 labelTemplate += "\t" + b + "\n"
1640 labelTemplate += "View:\n"
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001641 for depot_side in clientSpec.mappings:
1642 labelTemplate += "\t%s\n" % depot_side
Luke Diamand06804c72012-04-11 17:21:24 +02001643
Pete Wyckoffef739f02012-09-09 16:16:11 -04001644 if self.dry_run:
1645 print "Would create p4 label %s for tag" % name
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001646 elif self.prepare_p4_only:
1647 print "Not creating p4 label %s for tag due to option" \
1648 " --prepare-p4-only" % name
Pete Wyckoffef739f02012-09-09 16:16:11 -04001649 else:
1650 p4_write_pipe(["label", "-i"], labelTemplate)
Luke Diamand06804c72012-04-11 17:21:24 +02001651
Pete Wyckoffef739f02012-09-09 16:16:11 -04001652 # Use the label
1653 p4_system(["tag", "-l", name] +
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001654 ["%s@%s" % (depot_side, changelist) for depot_side in clientSpec.mappings])
Luke Diamand06804c72012-04-11 17:21:24 +02001655
Pete Wyckoffef739f02012-09-09 16:16:11 -04001656 if verbose:
1657 print "created p4 label for tag %s" % name
Luke Diamand06804c72012-04-11 17:21:24 +02001658
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001659 def run(self, args):
Simon Hausmannc9b50e62007-03-29 19:15:24 +02001660 if len(args) == 0:
1661 self.master = currentGitBranch()
Simon Hausmannc9b50e62007-03-29 19:15:24 +02001662 elif len(args) == 1:
1663 self.master = args[0]
Pete Wyckoff28755db2011-12-24 21:07:40 -05001664 if not branchExists(self.master):
1665 die("Branch %s does not exist" % self.master)
Simon Hausmannc9b50e62007-03-29 19:15:24 +02001666 else:
1667 return False
1668
Luke Diamand00ad6e32015-11-21 09:54:41 +00001669 if self.master:
1670 allowSubmit = gitConfig("git-p4.allowSubmit")
1671 if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","):
1672 die("%s is not in git-p4.allowSubmit" % self.master)
Jing Xue4c2d5d72008-06-22 14:12:39 -04001673
Simon Hausmann27d2d812007-06-12 14:31:59 +02001674 [upstream, settings] = findUpstreamBranchPoint()
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001675 self.depotPath = settings['depot-paths'][0]
Simon Hausmann27d2d812007-06-12 14:31:59 +02001676 if len(self.origin) == 0:
1677 self.origin = upstream
Simon Hausmanna3fdd572007-06-07 22:54:32 +02001678
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001679 if self.preserveUser:
1680 if not self.canChangeChangelists():
1681 die("Cannot preserve user names without p4 super-user or admin permissions")
1682
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001683 # if not set from the command line, try the config file
1684 if self.conflict_behavior is None:
1685 val = gitConfig("git-p4.conflict")
1686 if val:
1687 if val not in self.conflict_behavior_choices:
1688 die("Invalid value '%s' for config git-p4.conflict" % val)
1689 else:
1690 val = "ask"
1691 self.conflict_behavior = val
1692
Simon Hausmanna3fdd572007-06-07 22:54:32 +02001693 if self.verbose:
1694 print "Origin branch is " + self.origin
Simon Hausmann95124972007-03-23 09:16:07 +01001695
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001696 if len(self.depotPath) == 0:
Simon Hausmann95124972007-03-23 09:16:07 +01001697 print "Internal error: cannot locate perforce depot path from existing branches"
1698 sys.exit(128)
1699
Pete Wyckoff543987b2012-02-25 20:06:25 -05001700 self.useClientSpec = False
Pete Wyckoff0d609032013-01-26 22:11:24 -05001701 if gitConfigBool("git-p4.useclientspec"):
Pete Wyckoff543987b2012-02-25 20:06:25 -05001702 self.useClientSpec = True
1703 if self.useClientSpec:
1704 self.clientSpecDirs = getClientSpec()
Simon Hausmann95124972007-03-23 09:16:07 +01001705
Vitor Antunescd884102015-04-21 23:49:30 +01001706 # Check for the existance of P4 branches
1707 branchesDetected = (len(p4BranchesInGit().keys()) > 1)
1708
1709 if self.useClientSpec and not branchesDetected:
Pete Wyckoff543987b2012-02-25 20:06:25 -05001710 # all files are relative to the client spec
1711 self.clientPath = getClientRoot()
1712 else:
1713 self.clientPath = p4Where(self.depotPath)
1714
1715 if self.clientPath == "":
1716 die("Error: Cannot locate perforce checkout of %s in client view" % self.depotPath)
Simon Hausmann95124972007-03-23 09:16:07 +01001717
Simon Hausmannea99c3a2007-08-08 17:06:55 +02001718 print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
Simon Hausmann7944f142007-05-21 11:04:26 +02001719 self.oldWorkingDirectory = os.getcwd()
Simon Hausmannc1b296b2007-05-20 16:55:05 +02001720
Gary Gibbons0591cfa2011-12-09 18:48:14 -05001721 # ensure the clientPath exists
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04001722 new_client_dir = False
Gary Gibbons0591cfa2011-12-09 18:48:14 -05001723 if not os.path.exists(self.clientPath):
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04001724 new_client_dir = True
Gary Gibbons0591cfa2011-12-09 18:48:14 -05001725 os.makedirs(self.clientPath)
1726
Miklós Fazekasbbd84862013-03-11 17:45:29 -04001727 chdir(self.clientPath, is_client_path=True)
Pete Wyckoffef739f02012-09-09 16:16:11 -04001728 if self.dry_run:
1729 print "Would synchronize p4 checkout in %s" % self.clientPath
Pete Wyckoff8d7ec362012-04-29 20:57:14 -04001730 else:
Pete Wyckoffef739f02012-09-09 16:16:11 -04001731 print "Synchronizing p4 checkout..."
1732 if new_client_dir:
1733 # old one was destroyed, and maybe nobody told p4
1734 p4_sync("...", "-f")
1735 else:
1736 p4_sync("...")
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001737 self.check()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001738
Simon Hausmann4c750c02008-02-19 09:37:16 +01001739 commits = []
Luke Diamand00ad6e32015-11-21 09:54:41 +00001740 if self.master:
1741 commitish = self.master
1742 else:
1743 commitish = 'HEAD'
1744
1745 for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, commitish)]):
Simon Hausmann4c750c02008-02-19 09:37:16 +01001746 commits.append(line.strip())
1747 commits.reverse()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001748
Pete Wyckoff0d609032013-01-26 22:11:24 -05001749 if self.preserveUser or gitConfigBool("git-p4.skipUserNameCheck"):
Luke Diamand848de9c2011-05-13 20:46:00 +01001750 self.checkAuthorship = False
1751 else:
1752 self.checkAuthorship = True
1753
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001754 if self.preserveUser:
1755 self.checkValidP4Users(commits)
1756
Gary Gibbons84cb0002012-07-04 09:40:19 -04001757 #
1758 # Build up a set of options to be passed to diff when
1759 # submitting each commit to p4.
1760 #
1761 if self.detectRenames:
1762 # command-line -M arg
1763 self.diffOpts = "-M"
1764 else:
1765 # If not explicitly set check the config variable
1766 detectRenames = gitConfig("git-p4.detectRenames")
1767
1768 if detectRenames.lower() == "false" or detectRenames == "":
1769 self.diffOpts = ""
1770 elif detectRenames.lower() == "true":
1771 self.diffOpts = "-M"
1772 else:
1773 self.diffOpts = "-M%s" % detectRenames
1774
1775 # no command-line arg for -C or --find-copies-harder, just
1776 # config variables
1777 detectCopies = gitConfig("git-p4.detectCopies")
1778 if detectCopies.lower() == "false" or detectCopies == "":
1779 pass
1780 elif detectCopies.lower() == "true":
1781 self.diffOpts += " -C"
1782 else:
1783 self.diffOpts += " -C%s" % detectCopies
1784
Pete Wyckoff0d609032013-01-26 22:11:24 -05001785 if gitConfigBool("git-p4.detectCopiesHarder"):
Gary Gibbons84cb0002012-07-04 09:40:19 -04001786 self.diffOpts += " --find-copies-harder"
1787
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001788 #
1789 # Apply the commits, one at a time. On failure, ask if should
1790 # continue to try the rest of the patches, or quit.
1791 #
Pete Wyckoffef739f02012-09-09 16:16:11 -04001792 if self.dry_run:
1793 print "Would apply"
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001794 applied = []
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001795 last = len(commits) - 1
1796 for i, commit in enumerate(commits):
Pete Wyckoffef739f02012-09-09 16:16:11 -04001797 if self.dry_run:
1798 print " ", read_pipe(["git", "show", "-s",
1799 "--format=format:%h %s", commit])
1800 ok = True
1801 else:
1802 ok = self.applyCommit(commit)
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001803 if ok:
1804 applied.append(commit)
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001805 else:
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001806 if self.prepare_p4_only and i < last:
1807 print "Processing only the first commit due to option" \
1808 " --prepare-p4-only"
1809 break
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001810 if i < last:
1811 quit = False
1812 while True:
Pete Wyckoff6bbfd132012-09-09 16:16:13 -04001813 # prompt for what to do, or use the option/variable
1814 if self.conflict_behavior == "ask":
1815 print "What do you want to do?"
1816 response = raw_input("[s]kip this commit but apply"
1817 " the rest, or [q]uit? ")
1818 if not response:
1819 continue
1820 elif self.conflict_behavior == "skip":
1821 response = "s"
1822 elif self.conflict_behavior == "quit":
1823 response = "q"
1824 else:
1825 die("Unknown conflict_behavior '%s'" %
1826 self.conflict_behavior)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001827
Pete Wyckoff7e5dd9f2012-09-09 16:16:05 -04001828 if response[0] == "s":
1829 print "Skipping this commit, but applying the rest"
1830 break
1831 if response[0] == "q":
1832 print "Quitting"
1833 quit = True
1834 break
1835 if quit:
1836 break
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001837
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001838 chdir(self.oldWorkingDirectory)
1839
Pete Wyckoffef739f02012-09-09 16:16:11 -04001840 if self.dry_run:
1841 pass
Pete Wyckoff728b7ad2012-09-09 16:16:12 -04001842 elif self.prepare_p4_only:
1843 pass
Pete Wyckoffef739f02012-09-09 16:16:11 -04001844 elif len(commits) == len(applied):
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001845 print "All commits applied!"
Simon Hausmann14594f42007-08-22 09:07:15 +02001846
Simon Hausmann4c750c02008-02-19 09:37:16 +01001847 sync = P4Sync()
Pete Wyckoff44e8d262013-01-14 19:47:08 -05001848 if self.branch:
1849 sync.branch = self.branch
Simon Hausmann4c750c02008-02-19 09:37:16 +01001850 sync.run([])
Simon Hausmann14594f42007-08-22 09:07:15 +02001851
Simon Hausmann4c750c02008-02-19 09:37:16 +01001852 rebase = P4Rebase()
1853 rebase.rebase()
Simon Hausmann4f5cf762007-03-19 22:25:17 +01001854
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001855 else:
1856 if len(applied) == 0:
1857 print "No commits applied."
1858 else:
1859 print "Applied only the commits marked with '*':"
1860 for c in commits:
1861 if c in applied:
1862 star = "*"
1863 else:
1864 star = " "
1865 print star, read_pipe(["git", "show", "-s",
1866 "--format=format:%h %s", c])
1867 print "You will have to do 'git p4 sync' and rebase."
1868
Pete Wyckoff0d609032013-01-26 22:11:24 -05001869 if gitConfigBool("git-p4.exportLabels"):
Luke Diamand06dcd152012-05-11 07:25:18 +01001870 self.exportLabels = True
Luke Diamand06804c72012-04-11 17:21:24 +02001871
1872 if self.exportLabels:
1873 p4Labels = getP4Labels(self.depotPath)
1874 gitTags = getGitTags()
1875
1876 missingGitTags = gitTags - p4Labels
1877 self.exportGitTags(missingGitTags)
1878
Ondřej Bílka98e023d2013-07-29 10:18:21 +02001879 # exit with error unless everything applied perfectly
Pete Wyckoff67b0fe22012-09-09 16:16:03 -04001880 if len(commits) != len(applied):
1881 sys.exit(1)
1882
Simon Hausmannb9847332007-03-20 20:54:23 +01001883 return True
1884
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001885class View(object):
1886 """Represent a p4 view ("p4 help views"), and map files in a
1887 repo according to the view."""
1888
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001889 def __init__(self, client_name):
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001890 self.mappings = []
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001891 self.client_prefix = "//%s/" % client_name
1892 # cache results of "p4 where" to lookup client file locations
1893 self.client_spec_path_cache = {}
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001894
1895 def append(self, view_line):
1896 """Parse a view line, splitting it into depot and client
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001897 sides. Append to self.mappings, preserving order. This
1898 is only needed for tag creation."""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001899
1900 # Split the view line into exactly two words. P4 enforces
1901 # structure on these lines that simplifies this quite a bit.
1902 #
1903 # Either or both words may be double-quoted.
1904 # Single quotes do not matter.
1905 # Double-quote marks cannot occur inside the words.
1906 # A + or - prefix is also inside the quotes.
1907 # There are no quotes unless they contain a space.
1908 # The line is already white-space stripped.
1909 # The two words are separated by a single space.
1910 #
1911 if view_line[0] == '"':
1912 # First word is double quoted. Find its end.
1913 close_quote_index = view_line.find('"', 1)
1914 if close_quote_index <= 0:
1915 die("No first-word closing quote found: %s" % view_line)
1916 depot_side = view_line[1:close_quote_index]
1917 # skip closing quote and space
1918 rhs_index = close_quote_index + 1 + 1
1919 else:
1920 space_index = view_line.find(" ")
1921 if space_index <= 0:
1922 die("No word-splitting space found: %s" % view_line)
1923 depot_side = view_line[0:space_index]
1924 rhs_index = space_index + 1
1925
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001926 # prefix + means overlay on previous mapping
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001927 if depot_side.startswith("+"):
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001928 depot_side = depot_side[1:]
1929
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001930 # prefix - means exclude this path, leave out of mappings
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001931 exclude = False
1932 if depot_side.startswith("-"):
1933 exclude = True
1934 depot_side = depot_side[1:]
1935
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001936 if not exclude:
1937 self.mappings.append(depot_side)
1938
1939 def convert_client_path(self, clientFile):
1940 # chop off //client/ part to make it relative
1941 if not clientFile.startswith(self.client_prefix):
1942 die("No prefix '%s' on clientFile '%s'" %
1943 (self.client_prefix, clientFile))
1944 return clientFile[len(self.client_prefix):]
1945
1946 def update_client_spec_path_cache(self, files):
1947 """ Caching file paths by "p4 where" batch query """
1948
1949 # List depot file paths exclude that already cached
1950 fileArgs = [f['path'] for f in files if f['path'] not in self.client_spec_path_cache]
1951
1952 if len(fileArgs) == 0:
1953 return # All files in cache
1954
1955 where_result = p4CmdList(["-x", "-", "where"], stdin=fileArgs)
1956 for res in where_result:
1957 if "code" in res and res["code"] == "error":
1958 # assume error is "... file(s) not in client view"
1959 continue
1960 if "clientFile" not in res:
Pete Wyckoff20005442014-01-21 18:16:46 -05001961 die("No clientFile in 'p4 where' output")
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001962 if "unmap" in res:
1963 # it will list all of them, but only one not unmap-ped
1964 continue
Lars Schneidera0a50d82015-08-28 14:00:34 +02001965 if gitConfigBool("core.ignorecase"):
1966 res['depotFile'] = res['depotFile'].lower()
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001967 self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])
1968
1969 # not found files or unmap files set to ""
1970 for depotFile in fileArgs:
Lars Schneidera0a50d82015-08-28 14:00:34 +02001971 if gitConfigBool("core.ignorecase"):
1972 depotFile = depotFile.lower()
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001973 if depotFile not in self.client_spec_path_cache:
1974 self.client_spec_path_cache[depotFile] = ""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001975
1976 def map_in_client(self, depot_path):
1977 """Return the relative location in the client where this
1978 depot file should live. Returns "" if the file should
1979 not be mapped in the client."""
1980
Lars Schneidera0a50d82015-08-28 14:00:34 +02001981 if gitConfigBool("core.ignorecase"):
1982 depot_path = depot_path.lower()
1983
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001984 if depot_path in self.client_spec_path_cache:
1985 return self.client_spec_path_cache[depot_path]
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001986
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09001987 die( "Error: %s is not found in client spec path" % depot_path )
1988 return ""
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05001989
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001990class P4Sync(Command, P4UserMap):
Pete Wyckoff56c09342011-02-19 08:17:57 -05001991 delete_actions = ( "delete", "move/delete", "purge" )
1992
Simon Hausmannb9847332007-03-20 20:54:23 +01001993 def __init__(self):
1994 Command.__init__(self)
Luke Diamand3ea2cfd2011-04-21 20:50:23 +01001995 P4UserMap.__init__(self)
Simon Hausmannb9847332007-03-20 20:54:23 +01001996 self.options = [
1997 optparse.make_option("--branch", dest="branch"),
1998 optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
1999 optparse.make_option("--changesfile", dest="changesFile"),
2000 optparse.make_option("--silent", dest="silent", action="store_true"),
Simon Hausmannef48f902007-05-17 22:17:49 +02002001 optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02002002 optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
Han-Wen Nienhuysd2c6dd32007-05-23 18:49:35 -03002003 optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
2004 help="Import into refs/heads/ , not refs/remotes"),
Lex Spoon96b2d542015-04-20 11:00:20 -04002005 optparse.make_option("--max-changes", dest="maxChanges",
2006 help="Maximum number of changes to import"),
2007 optparse.make_option("--changes-block-size", dest="changes_block_size", type="int",
2008 help="Internal block size to use when iteratively calling p4 changes"),
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03002009 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002010 help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
2011 optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
Luke Diamand51334bb2015-01-17 20:56:38 +00002012 help="Only sync files that are included in the Perforce Client Spec"),
2013 optparse.make_option("-/", dest="cloneExclude",
2014 action="append", type="string",
2015 help="exclude depot path"),
Simon Hausmannb9847332007-03-20 20:54:23 +01002016 ]
2017 self.description = """Imports from Perforce into a git repository.\n
2018 example:
2019 //depot/my/project/ -- to import the current head
2020 //depot/my/project/@all -- to import everything
2021 //depot/my/project/@1,6 -- to import only from revision 1 to 6
2022
2023 (a ... is not needed in the path p4 specification, it's added implicitly)"""
2024
2025 self.usage += " //depot/path[@revRange]"
Simon Hausmannb9847332007-03-20 20:54:23 +01002026 self.silent = False
Reilly Grant1d7367d2009-09-10 00:02:38 -07002027 self.createdBranches = set()
2028 self.committedChanges = set()
Simon Hausmann569d1bd2007-03-22 21:34:16 +01002029 self.branch = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01002030 self.detectBranches = False
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02002031 self.detectLabels = False
Luke Diamand06804c72012-04-11 17:21:24 +02002032 self.importLabels = False
Simon Hausmannb9847332007-03-20 20:54:23 +01002033 self.changesFile = ""
Simon Hausmann01265102007-05-25 10:36:10 +02002034 self.syncWithOrigin = True
Simon Hausmanna028a982007-05-23 00:03:08 +02002035 self.importIntoRemotes = True
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02002036 self.maxChanges = ""
Luke Diamand1051ef02015-06-10 08:30:59 +01002037 self.changes_block_size = None
Han-Wen Nienhuys8b41a972007-05-23 18:20:53 -03002038 self.keepRepoPath = False
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002039 self.depotPaths = None
Simon Hausmann3c699642007-06-16 13:09:21 +02002040 self.p4BranchesInGit = []
Tommy Thorn354081d2008-02-03 10:38:51 -08002041 self.cloneExclude = []
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002042 self.useClientSpec = False
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05002043 self.useClientSpec_from_options = False
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002044 self.clientSpecDirs = None
Vitor Antunesfed23692012-01-25 23:48:22 +00002045 self.tempBranches = []
2046 self.tempBranchLocation = "git-p4-tmp"
Simon Hausmannb9847332007-03-20 20:54:23 +01002047
Simon Hausmann01265102007-05-25 10:36:10 +02002048 if gitConfig("git-p4.syncFromOrigin") == "false":
2049 self.syncWithOrigin = False
2050
Luke Diamand51334bb2015-01-17 20:56:38 +00002051 # This is required for the "append" cloneExclude action
2052 def ensure_value(self, attr, value):
2053 if not hasattr(self, attr) or getattr(self, attr) is None:
2054 setattr(self, attr, value)
2055 return getattr(self, attr)
2056
Vitor Antunesfed23692012-01-25 23:48:22 +00002057 # Force a checkpoint in fast-import and wait for it to finish
2058 def checkpoint(self):
2059 self.gitStream.write("checkpoint\n\n")
2060 self.gitStream.write("progress checkpoint\n\n")
2061 out = self.gitOutput.readline()
2062 if self.verbose:
2063 print "checkpoint finished: " + out
2064
Simon Hausmannb9847332007-03-20 20:54:23 +01002065 def extractFilesFromCommit(self, commit):
Tommy Thorn354081d2008-02-03 10:38:51 -08002066 self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
2067 for path in self.cloneExclude]
Simon Hausmannb9847332007-03-20 20:54:23 +01002068 files = []
2069 fnum = 0
2070 while commit.has_key("depotFile%s" % fnum):
2071 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002072
Tommy Thorn354081d2008-02-03 10:38:51 -08002073 if [p for p in self.cloneExclude
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01002074 if p4PathStartsWith(path, p)]:
Tommy Thorn354081d2008-02-03 10:38:51 -08002075 found = False
2076 else:
2077 found = [p for p in self.depotPaths
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01002078 if p4PathStartsWith(path, p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002079 if not found:
Simon Hausmannb9847332007-03-20 20:54:23 +01002080 fnum = fnum + 1
2081 continue
2082
2083 file = {}
2084 file["path"] = path
2085 file["rev"] = commit["rev%s" % fnum]
2086 file["action"] = commit["action%s" % fnum]
2087 file["type"] = commit["type%s" % fnum]
2088 files.append(file)
2089 fnum = fnum + 1
2090 return files
2091
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002092 def stripRepoPath(self, path, prefixes):
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002093 """When streaming files, this is called to map a p4 depot path
2094 to where it should go in git. The prefixes are either
2095 self.depotPaths, or self.branchPrefixes in the case of
2096 branch detection."""
2097
Ian Wienand39527102011-02-11 16:33:48 -08002098 if self.useClientSpec:
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002099 # branch detection moves files up a level (the branch name)
2100 # from what client spec interpretation gives
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002101 path = self.clientSpecDirs.map_in_client(path)
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002102 if self.detectBranches:
2103 for b in self.knownBranches:
2104 if path.startswith(b + "/"):
2105 path = path[len(b)+1:]
2106
2107 elif self.keepRepoPath:
2108 # Preserve everything in relative path name except leading
2109 # //depot/; just look at first prefix as they all should
2110 # be in the same depot.
2111 depot = re.sub("^(//[^/]+/).*", r'\1', prefixes[0])
2112 if p4PathStartsWith(path, depot):
2113 path = path[len(depot):]
Ian Wienand39527102011-02-11 16:33:48 -08002114
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002115 else:
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002116 for p in prefixes:
2117 if p4PathStartsWith(path, p):
2118 path = path[len(p):]
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002119 break
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002120
Pete Wyckoff0d1696e2012-08-11 12:55:03 -04002121 path = wildcard_decode(path)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002122 return path
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -03002123
Simon Hausmann71b112d2007-05-19 11:54:11 +02002124 def splitFilesIntoBranches(self, commit):
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002125 """Look at each depotFile in the commit to figure out to what
2126 branch it belongs."""
2127
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002128 if self.clientSpecDirs:
2129 files = self.extractFilesFromCommit(commit)
2130 self.clientSpecDirs.update_client_spec_path_cache(files)
2131
Simon Hausmannd5904672007-05-19 11:07:32 +02002132 branches = {}
Simon Hausmann71b112d2007-05-19 11:54:11 +02002133 fnum = 0
2134 while commit.has_key("depotFile%s" % fnum):
2135 path = commit["depotFile%s" % fnum]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002136 found = [p for p in self.depotPaths
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01002137 if p4PathStartsWith(path, p)]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002138 if not found:
Simon Hausmann71b112d2007-05-19 11:54:11 +02002139 fnum = fnum + 1
2140 continue
2141
2142 file = {}
2143 file["path"] = path
2144 file["rev"] = commit["rev%s" % fnum]
2145 file["action"] = commit["action%s" % fnum]
2146 file["type"] = commit["type%s" % fnum]
2147 fnum = fnum + 1
2148
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002149 # start with the full relative path where this file would
2150 # go in a p4 client
2151 if self.useClientSpec:
2152 relPath = self.clientSpecDirs.map_in_client(path)
2153 else:
2154 relPath = self.stripRepoPath(path, self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01002155
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002156 for branch in self.knownBranches.keys():
Pete Wyckoff21ef5df2012-08-11 12:55:04 -04002157 # add a trailing slash so that a commit into qt/4.2foo
2158 # doesn't end up in qt/4.2, e.g.
Han-Wen Nienhuys6754a292007-05-23 17:41:50 -03002159 if relPath.startswith(branch + "/"):
Simon Hausmannd5904672007-05-19 11:07:32 +02002160 if branch not in branches:
2161 branches[branch] = []
Simon Hausmann71b112d2007-05-19 11:54:11 +02002162 branches[branch].append(file)
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002163 break
Simon Hausmannb9847332007-03-20 20:54:23 +01002164
2165 return branches
2166
Luke Diamandb9327052009-07-30 00:13:46 +01002167 # output one file from the P4 stream
2168 # - helper for streamP4Files
2169
2170 def streamOneP4File(self, file, contents):
Luke Diamandb9327052009-07-30 00:13:46 +01002171 relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
2172 if verbose:
2173 sys.stderr.write("%s\n" % relPath)
2174
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -04002175 (type_base, type_mods) = split_p4_type(file["type"])
2176
2177 git_mode = "100644"
2178 if "x" in type_mods:
2179 git_mode = "100755"
2180 if type_base == "symlink":
2181 git_mode = "120000"
Alexandru Juncu1292df12013-08-08 16:17:38 +03002182 # p4 print on a symlink sometimes contains "target\n";
2183 # if it does, remove the newline
Evan Powersb39c3612010-02-16 00:44:08 -08002184 data = ''.join(contents)
Pete Wyckoff40f846c2014-01-21 18:16:40 -05002185 if not data:
2186 # Some version of p4 allowed creating a symlink that pointed
2187 # to nothing. This causes p4 errors when checking out such
2188 # a change, and errors here too. Work around it by ignoring
2189 # the bad symlink; hopefully a future change fixes it.
2190 print "\nIgnoring empty symlink in %s" % file['depotFile']
2191 return
2192 elif data[-1] == '\n':
Alexandru Juncu1292df12013-08-08 16:17:38 +03002193 contents = [data[:-1]]
2194 else:
2195 contents = [data]
Luke Diamandb9327052009-07-30 00:13:46 +01002196
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -04002197 if type_base == "utf16":
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002198 # p4 delivers different text in the python output to -G
2199 # than it does when using "print -o", or normal p4 client
2200 # operations. utf16 is converted to ascii or utf8, perhaps.
2201 # But ascii text saved as -t utf16 is completely mangled.
2202 # Invoke print -o to get the real contents.
Pete Wyckoff7f0e5962013-01-26 22:11:13 -05002203 #
2204 # On windows, the newlines will always be mangled by print, so put
2205 # them back too. This is not needed to the cygwin windows version,
2206 # just the native "NT" type.
2207 #
Lars Schneider1f5f3902015-09-21 12:01:41 +02002208 try:
2209 text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])])
2210 except Exception as e:
2211 if 'Translation of file content failed' in str(e):
2212 type_base = 'binary'
2213 else:
2214 raise e
2215 else:
2216 if p4_version_string().find('/NT') >= 0:
2217 text = text.replace('\r\n', '\n')
2218 contents = [ text ]
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002219
Pete Wyckoff9f7ef0e2011-11-05 13:36:07 -04002220 if type_base == "apple":
2221 # Apple filetype files will be streamed as a concatenation of
2222 # its appledouble header and the contents. This is useless
2223 # on both macs and non-macs. If using "print -q -o xx", it
2224 # will create "xx" with the data, and "%xx" with the header.
2225 # This is also not very useful.
2226 #
2227 # Ideally, someday, this script can learn how to generate
2228 # appledouble files directly and import those to git, but
2229 # non-mac machines can never find a use for apple filetype.
2230 print "\nIgnoring apple filetype file %s" % file['depotFile']
2231 return
2232
Pete Wyckoff55aa5712011-09-17 19:16:14 -04002233 # Note that we do not try to de-mangle keywords on utf16 files,
2234 # even though in theory somebody may want that.
Luke Diamand60df0712012-02-23 07:51:30 +00002235 pattern = p4_keywords_regexp_for_type(type_base, type_mods)
2236 if pattern:
2237 regexp = re.compile(pattern, re.VERBOSE)
2238 text = ''.join(contents)
2239 text = regexp.sub(r'$\1$', text)
2240 contents = [ text ]
Luke Diamandb9327052009-07-30 00:13:46 +01002241
Pete Wyckoff9cffb8c2011-10-16 10:45:01 -04002242 self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
Luke Diamandb9327052009-07-30 00:13:46 +01002243
2244 # total length...
2245 length = 0
2246 for d in contents:
2247 length = length + len(d)
2248
2249 self.gitStream.write("data %d\n" % length)
2250 for d in contents:
2251 self.gitStream.write(d)
2252 self.gitStream.write("\n")
2253
2254 def streamOneP4Deletion(self, file):
2255 relPath = self.stripRepoPath(file['path'], self.branchPrefixes)
2256 if verbose:
2257 sys.stderr.write("delete %s\n" % relPath)
2258 self.gitStream.write("D %s\n" % relPath)
2259
2260 # handle another chunk of streaming data
2261 def streamP4FilesCb(self, marshalled):
2262
Pete Wyckoff78189be2012-11-23 17:35:36 -05002263 # catch p4 errors and complain
2264 err = None
2265 if "code" in marshalled:
2266 if marshalled["code"] == "error":
2267 if "data" in marshalled:
2268 err = marshalled["data"].rstrip()
2269 if err:
2270 f = None
2271 if self.stream_have_file_info:
2272 if "depotFile" in self.stream_file:
2273 f = self.stream_file["depotFile"]
2274 # force a failure in fast-import, else an empty
2275 # commit will be made
2276 self.gitStream.write("\n")
2277 self.gitStream.write("die-now\n")
2278 self.gitStream.close()
2279 # ignore errors, but make sure it exits first
2280 self.importProcess.wait()
2281 if f:
2282 die("Error from p4 print for %s: %s" % (f, err))
2283 else:
2284 die("Error from p4 print: %s" % err)
2285
Andrew Garberc3f61632011-04-07 02:01:21 -04002286 if marshalled.has_key('depotFile') and self.stream_have_file_info:
2287 # start of a new file - output the old one first
2288 self.streamOneP4File(self.stream_file, self.stream_contents)
2289 self.stream_file = {}
2290 self.stream_contents = []
2291 self.stream_have_file_info = False
Luke Diamandb9327052009-07-30 00:13:46 +01002292
Andrew Garberc3f61632011-04-07 02:01:21 -04002293 # pick up the new file information... for the
2294 # 'data' field we need to append to our array
2295 for k in marshalled.keys():
2296 if k == 'data':
2297 self.stream_contents.append(marshalled['data'])
2298 else:
2299 self.stream_file[k] = marshalled[k]
Luke Diamandb9327052009-07-30 00:13:46 +01002300
Andrew Garberc3f61632011-04-07 02:01:21 -04002301 self.stream_have_file_info = True
Luke Diamandb9327052009-07-30 00:13:46 +01002302
2303 # Stream directly from "p4 files" into "git fast-import"
2304 def streamP4Files(self, files):
Simon Hausmann30b59402008-03-03 11:55:48 +01002305 filesForCommit = []
2306 filesToRead = []
Luke Diamandb9327052009-07-30 00:13:46 +01002307 filesToDelete = []
Simon Hausmann30b59402008-03-03 11:55:48 +01002308
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002309 for f in files:
Pete Wyckoffecb7cf92012-01-02 18:05:53 -05002310 filesForCommit.append(f)
2311 if f['action'] in self.delete_actions:
2312 filesToDelete.append(f)
2313 else:
2314 filesToRead.append(f)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03002315
Luke Diamandb9327052009-07-30 00:13:46 +01002316 # deleted files...
2317 for f in filesToDelete:
2318 self.streamOneP4Deletion(f)
2319
Simon Hausmann30b59402008-03-03 11:55:48 +01002320 if len(filesToRead) > 0:
Luke Diamandb9327052009-07-30 00:13:46 +01002321 self.stream_file = {}
2322 self.stream_contents = []
2323 self.stream_have_file_info = False
2324
Andrew Garberc3f61632011-04-07 02:01:21 -04002325 # curry self argument
2326 def streamP4FilesCbSelf(entry):
2327 self.streamP4FilesCb(entry)
Luke Diamandb9327052009-07-30 00:13:46 +01002328
Luke Diamand6de040d2011-10-16 10:47:52 -04002329 fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
2330
2331 p4CmdList(["-x", "-", "print"],
2332 stdin=fileArgs,
2333 cb=streamP4FilesCbSelf)
Han-Wen Nienhuysf2eda792007-05-23 18:49:35 -03002334
Luke Diamandb9327052009-07-30 00:13:46 +01002335 # do the last chunk
2336 if self.stream_file.has_key('depotFile'):
2337 self.streamOneP4File(self.stream_file, self.stream_contents)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03002338
Luke Diamandaffb4742012-01-19 09:52:27 +00002339 def make_email(self, userid):
2340 if userid in self.users:
2341 return self.users[userid]
2342 else:
2343 return "%s <a@b>" % userid
2344
Luke Diamand06804c72012-04-11 17:21:24 +02002345 def streamTag(self, gitStream, labelName, labelDetails, commit, epoch):
Luke Diamandb43702a2015-08-27 08:18:58 +01002346 """ Stream a p4 tag.
2347 commit is either a git commit, or a fast-import mark, ":<p4commit>"
2348 """
2349
Luke Diamand06804c72012-04-11 17:21:24 +02002350 if verbose:
2351 print "writing tag %s for commit %s" % (labelName, commit)
2352 gitStream.write("tag %s\n" % labelName)
2353 gitStream.write("from %s\n" % commit)
2354
2355 if labelDetails.has_key('Owner'):
2356 owner = labelDetails["Owner"]
2357 else:
2358 owner = None
2359
2360 # Try to use the owner of the p4 label, or failing that,
2361 # the current p4 user id.
2362 if owner:
2363 email = self.make_email(owner)
2364 else:
2365 email = self.make_email(self.p4UserId())
2366 tagger = "%s %s %s" % (email, epoch, self.tz)
2367
2368 gitStream.write("tagger %s\n" % tagger)
2369
2370 print "labelDetails=",labelDetails
2371 if labelDetails.has_key('Description'):
2372 description = labelDetails['Description']
2373 else:
2374 description = 'Label from git p4'
2375
2376 gitStream.write("data %d\n" % len(description))
2377 gitStream.write(description)
2378 gitStream.write("\n")
2379
Lars Schneider4ae048e2015-12-08 10:36:22 +01002380 def inClientSpec(self, path):
2381 if not self.clientSpecDirs:
2382 return True
2383 inClientSpec = self.clientSpecDirs.map_in_client(path)
2384 if not inClientSpec and self.verbose:
2385 print('Ignoring file outside of client spec: {0}'.format(path))
2386 return inClientSpec
2387
2388 def hasBranchPrefix(self, path):
2389 if not self.branchPrefixes:
2390 return True
2391 hasPrefix = [p for p in self.branchPrefixes
2392 if p4PathStartsWith(path, p)]
2393 if hasPrefix and self.verbose:
2394 print('Ignoring file outside of prefix: {0}'.format(path))
2395 return hasPrefix
2396
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002397 def commit(self, details, files, branch, parent = ""):
Simon Hausmannb9847332007-03-20 20:54:23 +01002398 epoch = details["time"]
2399 author = details["user"]
2400
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002401 if self.verbose:
Lars Schneider4ae048e2015-12-08 10:36:22 +01002402 print('commit into {0}'.format(branch))
Han-Wen Nienhuys96e07dd2007-05-23 18:49:35 -03002403
Kazuki Saitoh9d57c4a2013-08-30 19:02:06 +09002404 if self.clientSpecDirs:
2405 self.clientSpecDirs.update_client_spec_path_cache(files)
2406
Lars Schneider4ae048e2015-12-08 10:36:22 +01002407 files = [f for f in files
2408 if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
2409
2410 if not files and not gitConfigBool('git-p4.keepEmptyCommits'):
2411 print('Ignoring revision {0} as it would produce an empty commit.'
2412 .format(details['change']))
2413 return
2414
Simon Hausmannb9847332007-03-20 20:54:23 +01002415 self.gitStream.write("commit %s\n" % branch)
Luke Diamandb43702a2015-08-27 08:18:58 +01002416 self.gitStream.write("mark :%s\n" % details["change"])
Simon Hausmannb9847332007-03-20 20:54:23 +01002417 self.committedChanges.add(int(details["change"]))
2418 committer = ""
Simon Hausmannb607e712007-05-20 10:55:54 +02002419 if author not in self.users:
2420 self.getUserMapFromPerforceServer()
Luke Diamandaffb4742012-01-19 09:52:27 +00002421 committer = "%s %s %s" % (self.make_email(author), epoch, self.tz)
Simon Hausmannb9847332007-03-20 20:54:23 +01002422
2423 self.gitStream.write("committer %s\n" % committer)
2424
2425 self.gitStream.write("data <<EOT\n")
2426 self.gitStream.write(details["desc"])
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002427 self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" %
2428 (','.join(self.branchPrefixes), details["change"]))
Simon Hausmann6581de02007-06-11 10:01:58 +02002429 if len(details['options']) > 0:
2430 self.gitStream.write(": options = %s" % details['options'])
2431 self.gitStream.write("]\nEOT\n\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01002432
2433 if len(parent) > 0:
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002434 if self.verbose:
2435 print "parent %s" % parent
Simon Hausmannb9847332007-03-20 20:54:23 +01002436 self.gitStream.write("from %s\n" % parent)
2437
Lars Schneider4ae048e2015-12-08 10:36:22 +01002438 self.streamP4Files(files)
Simon Hausmannb9847332007-03-20 20:54:23 +01002439 self.gitStream.write("\n")
2440
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002441 change = int(details["change"])
2442
Simon Hausmann9bda3a82007-05-19 12:05:40 +02002443 if self.labels.has_key(change):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002444 label = self.labels[change]
2445 labelDetails = label[0]
2446 labelRevisions = label[1]
Simon Hausmann71b112d2007-05-19 11:54:11 +02002447 if self.verbose:
2448 print "Change %s is labelled %s" % (change, labelDetails)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002449
Luke Diamand6de040d2011-10-16 10:47:52 -04002450 files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002451 for p in self.branchPrefixes])
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002452
2453 if len(files) == len(labelRevisions):
2454
2455 cleanedFiles = {}
2456 for info in files:
Pete Wyckoff56c09342011-02-19 08:17:57 -05002457 if info["action"] in self.delete_actions:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002458 continue
2459 cleanedFiles[info["depotFile"]] = info["rev"]
2460
2461 if cleanedFiles == labelRevisions:
Luke Diamand06804c72012-04-11 17:21:24 +02002462 self.streamTag(self.gitStream, 'tag_%s' % labelDetails['label'], labelDetails, branch, epoch)
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002463
2464 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02002465 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03002466 print ("Tag %s does not match with change %s: files do not match."
2467 % (labelDetails["label"], change))
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002468
2469 else:
Simon Hausmanna46668f2007-03-28 17:05:38 +02002470 if not self.silent:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03002471 print ("Tag %s does not match with change %s: file count is different."
2472 % (labelDetails["label"], change))
Simon Hausmannb9847332007-03-20 20:54:23 +01002473
Luke Diamand06804c72012-04-11 17:21:24 +02002474 # Build a dictionary of changelists and labels, for "detect-labels" option.
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002475 def getLabels(self):
2476 self.labels = {}
2477
Luke Diamand52a48802012-01-19 09:52:25 +00002478 l = p4CmdList(["labels"] + ["%s..." % p for p in self.depotPaths])
Simon Hausmann10c32112007-04-08 10:15:47 +02002479 if len(l) > 0 and not self.silent:
Shun Kei Leung183f8432007-11-21 11:01:19 +08002480 print "Finding files belonging to labels in %s" % `self.depotPaths`
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02002481
2482 for output in l:
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002483 label = output["label"]
2484 revisions = {}
2485 newestChange = 0
Simon Hausmann71b112d2007-05-19 11:54:11 +02002486 if self.verbose:
2487 print "Querying files for label %s" % label
Luke Diamand6de040d2011-10-16 10:47:52 -04002488 for file in p4CmdList(["files"] +
2489 ["%s...@%s" % (p, label)
2490 for p in self.depotPaths]):
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002491 revisions[file["depotFile"]] = file["rev"]
2492 change = int(file["change"])
2493 if change > newestChange:
2494 newestChange = change
2495
Simon Hausmann9bda3a82007-05-19 12:05:40 +02002496 self.labels[newestChange] = [output, revisions]
2497
2498 if self.verbose:
2499 print "Label changes: %s" % self.labels.keys()
Simon Hausmann1f4ba1c2007-03-26 22:34:34 +02002500
Luke Diamand06804c72012-04-11 17:21:24 +02002501 # Import p4 labels as git tags. A direct mapping does not
2502 # exist, so assume that if all the files are at the same revision
2503 # then we can use that, or it's something more complicated we should
2504 # just ignore.
2505 def importP4Labels(self, stream, p4Labels):
2506 if verbose:
2507 print "import p4 labels: " + ' '.join(p4Labels)
2508
2509 ignoredP4Labels = gitConfigList("git-p4.ignoredP4Labels")
Luke Diamandc8942a22012-04-11 17:21:24 +02002510 validLabelRegexp = gitConfig("git-p4.labelImportRegexp")
Luke Diamand06804c72012-04-11 17:21:24 +02002511 if len(validLabelRegexp) == 0:
2512 validLabelRegexp = defaultLabelRegexp
2513 m = re.compile(validLabelRegexp)
2514
2515 for name in p4Labels:
2516 commitFound = False
2517
2518 if not m.match(name):
2519 if verbose:
2520 print "label %s does not match regexp %s" % (name,validLabelRegexp)
2521 continue
2522
2523 if name in ignoredP4Labels:
2524 continue
2525
2526 labelDetails = p4CmdList(['label', "-o", name])[0]
2527
2528 # get the most recent changelist for each file in this label
2529 change = p4Cmd(["changes", "-m", "1"] + ["%s...@%s" % (p, name)
2530 for p in self.depotPaths])
2531
2532 if change.has_key('change'):
2533 # find the corresponding git commit; take the oldest commit
2534 changelist = int(change['change'])
Luke Diamandb43702a2015-08-27 08:18:58 +01002535 if changelist in self.committedChanges:
2536 gitCommit = ":%d" % changelist # use a fast-import mark
Luke Diamand06804c72012-04-11 17:21:24 +02002537 commitFound = True
Luke Diamandb43702a2015-08-27 08:18:58 +01002538 else:
2539 gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
2540 "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
2541 if len(gitCommit) == 0:
2542 print "importing label %s: could not find git commit for changelist %d" % (name, changelist)
2543 else:
2544 commitFound = True
2545 gitCommit = gitCommit.strip()
2546
2547 if commitFound:
Luke Diamand06804c72012-04-11 17:21:24 +02002548 # Convert from p4 time format
2549 try:
2550 tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
2551 except ValueError:
Pete Wyckoffa4e90542012-11-23 17:35:38 -05002552 print "Could not convert label time %s" % labelDetails['Update']
Luke Diamand06804c72012-04-11 17:21:24 +02002553 tmwhen = 1
2554
2555 when = int(time.mktime(tmwhen))
2556 self.streamTag(stream, name, labelDetails, gitCommit, when)
2557 if verbose:
2558 print "p4 label %s mapped to git commit %s" % (name, gitCommit)
2559 else:
2560 if verbose:
2561 print "Label %s has no changelists - possibly deleted?" % name
2562
2563 if not commitFound:
2564 # We can't import this label; don't try again as it will get very
2565 # expensive repeatedly fetching all the files for labels that will
2566 # never be imported. If the label is moved in the future, the
2567 # ignore will need to be removed manually.
2568 system(["git", "config", "--add", "git-p4.ignoredP4Labels", name])
2569
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03002570 def guessProjectName(self):
2571 for p in self.depotPaths:
Simon Hausmann6e5295c2007-06-11 08:50:57 +02002572 if p.endswith("/"):
2573 p = p[:-1]
2574 p = p[p.strip().rfind("/") + 1:]
2575 if not p.endswith("/"):
2576 p += "/"
2577 return p
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03002578
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002579 def getBranchMapping(self):
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002580 lostAndFoundBranches = set()
2581
Vitor Antunes8ace74c2011-08-19 00:44:04 +01002582 user = gitConfig("git-p4.branchUser")
2583 if len(user) > 0:
2584 command = "branches -u %s" % user
2585 else:
2586 command = "branches"
2587
2588 for info in p4CmdList(command):
Luke Diamand52a48802012-01-19 09:52:25 +00002589 details = p4Cmd(["branch", "-o", info["branch"]])
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02002590 viewIdx = 0
2591 while details.has_key("View%s" % viewIdx):
2592 paths = details["View%s" % viewIdx].split(" ")
2593 viewIdx = viewIdx + 1
2594 # require standard //depot/foo/... //depot/bar/... mapping
2595 if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
2596 continue
2597 source = paths[0]
2598 destination = paths[1]
Simon Hausmann6509e192007-06-07 09:41:53 +02002599 ## HACK
Tor Arvid Lundd53de8b2011-03-15 13:08:02 +01002600 if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
Simon Hausmann6509e192007-06-07 09:41:53 +02002601 source = source[len(self.depotPaths[0]):-4]
2602 destination = destination[len(self.depotPaths[0]):-4]
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002603
Simon Hausmann1a2edf42007-06-17 15:10:24 +02002604 if destination in self.knownBranches:
2605 if not self.silent:
2606 print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
2607 print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
2608 continue
2609
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002610 self.knownBranches[destination] = source
2611
2612 lostAndFoundBranches.discard(destination)
2613
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002614 if source not in self.knownBranches:
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002615 lostAndFoundBranches.add(source)
2616
Vitor Antunes7199cf12011-08-19 00:44:05 +01002617 # Perforce does not strictly require branches to be defined, so we also
2618 # check git config for a branch list.
2619 #
2620 # Example of branch definition in git config file:
2621 # [git-p4]
2622 # branchList=main:branchA
2623 # branchList=main:branchB
2624 # branchList=branchA:branchC
2625 configBranches = gitConfigList("git-p4.branchList")
2626 for branch in configBranches:
2627 if branch:
2628 (source, destination) = branch.split(":")
2629 self.knownBranches[destination] = source
2630
2631 lostAndFoundBranches.discard(destination)
2632
2633 if source not in self.knownBranches:
2634 lostAndFoundBranches.add(source)
2635
Simon Hausmann6555b2c2007-06-17 11:25:34 +02002636
2637 for branch in lostAndFoundBranches:
2638 self.knownBranches[branch] = branch
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002639
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01002640 def getBranchMappingFromGitBranches(self):
2641 branches = p4BranchesInGit(self.importIntoRemotes)
2642 for branch in branches.keys():
2643 if branch == "master":
2644 branch = "main"
2645 else:
2646 branch = branch[len(self.projectName):]
2647 self.knownBranches[branch] = branch
2648
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03002649 def updateOptionDict(self, d):
2650 option_keys = {}
2651 if self.keepRepoPath:
2652 option_keys['keepRepoPath'] = 1
2653
2654 d["options"] = ' '.join(sorted(option_keys.keys()))
2655
2656 def readOptions(self, d):
2657 self.keepRepoPath = (d.has_key('options')
2658 and ('keepRepoPath' in d['options']))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002659
Simon Hausmann8134f692007-08-26 16:44:55 +02002660 def gitRefForBranch(self, branch):
2661 if branch == "main":
2662 return self.refPrefix + "master"
2663
2664 if len(branch) <= 0:
2665 return branch
2666
2667 return self.refPrefix + self.projectName + branch
2668
Simon Hausmann1ca3d712007-08-26 17:36:55 +02002669 def gitCommitByP4Change(self, ref, change):
2670 if self.verbose:
2671 print "looking in ref " + ref + " for change %s using bisect..." % change
2672
2673 earliestCommit = ""
2674 latestCommit = parseRevision(ref)
2675
2676 while True:
2677 if self.verbose:
2678 print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
2679 next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
2680 if len(next) == 0:
2681 if self.verbose:
2682 print "argh"
2683 return ""
2684 log = extractLogMessageFromGitCommit(next)
2685 settings = extractSettingsGitLog(log)
2686 currentChange = int(settings['change'])
2687 if self.verbose:
2688 print "current change %s" % currentChange
2689
2690 if currentChange == change:
2691 if self.verbose:
2692 print "found %s" % next
2693 return next
2694
2695 if currentChange < change:
2696 earliestCommit = "^%s" % next
2697 else:
2698 latestCommit = "%s" % next
2699
2700 return ""
2701
2702 def importNewBranch(self, branch, maxChange):
2703 # make fast-import flush all changes to disk and update the refs using the checkpoint
2704 # command so that we can try to find the branch parent in the git history
2705 self.gitStream.write("checkpoint\n\n");
2706 self.gitStream.flush();
2707 branchPrefix = self.depotPaths[0] + branch + "/"
2708 range = "@1,%s" % maxChange
2709 #print "prefix" + branchPrefix
Lex Spoon96b2d542015-04-20 11:00:20 -04002710 changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)
Simon Hausmann1ca3d712007-08-26 17:36:55 +02002711 if len(changes) <= 0:
2712 return False
2713 firstChange = changes[0]
2714 #print "first change in branch: %s" % firstChange
2715 sourceBranch = self.knownBranches[branch]
2716 sourceDepotPath = self.depotPaths[0] + sourceBranch
2717 sourceRef = self.gitRefForBranch(sourceBranch)
2718 #print "source " + sourceBranch
2719
Luke Diamand52a48802012-01-19 09:52:25 +00002720 branchParentChange = int(p4Cmd(["changes", "-m", "1", "%s...@1,%s" % (sourceDepotPath, firstChange)])["change"])
Simon Hausmann1ca3d712007-08-26 17:36:55 +02002721 #print "branch parent: %s" % branchParentChange
2722 gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
2723 if len(gitParent) > 0:
2724 self.initialParents[self.gitRefForBranch(branch)] = gitParent
2725 #print "parent git commit: %s" % gitParent
2726
2727 self.importChanges(changes)
2728 return True
2729
Vitor Antunesfed23692012-01-25 23:48:22 +00002730 def searchParent(self, parent, branch, target):
2731 parentFound = False
Pete Wyckoffc7d34882013-01-26 22:11:21 -05002732 for blob in read_pipe_lines(["git", "rev-list", "--reverse",
2733 "--no-merges", parent]):
Vitor Antunesfed23692012-01-25 23:48:22 +00002734 blob = blob.strip()
2735 if len(read_pipe(["git", "diff-tree", blob, target])) == 0:
2736 parentFound = True
2737 if self.verbose:
2738 print "Found parent of %s in commit %s" % (branch, blob)
2739 break
2740 if parentFound:
2741 return blob
2742 else:
2743 return None
2744
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002745 def importChanges(self, changes):
2746 cnt = 1
2747 for change in changes:
Pete Wyckoff18fa13d2012-11-23 17:35:34 -05002748 description = p4_describe(change)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002749 self.updateOptionDict(description)
2750
2751 if not self.silent:
2752 sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
2753 sys.stdout.flush()
2754 cnt = cnt + 1
2755
2756 try:
2757 if self.detectBranches:
2758 branches = self.splitFilesIntoBranches(description)
2759 for branch in branches.keys():
2760 ## HACK --hwn
2761 branchPrefix = self.depotPaths[0] + branch + "/"
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002762 self.branchPrefixes = [ branchPrefix ]
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002763
2764 parent = ""
2765
2766 filesForCommit = branches[branch]
2767
2768 if self.verbose:
2769 print "branch is %s" % branch
2770
2771 self.updatedBranches.add(branch)
2772
2773 if branch not in self.createdBranches:
2774 self.createdBranches.add(branch)
2775 parent = self.knownBranches[branch]
2776 if parent == branch:
2777 parent = ""
Simon Hausmann1ca3d712007-08-26 17:36:55 +02002778 else:
2779 fullBranch = self.projectName + branch
2780 if fullBranch not in self.p4BranchesInGit:
2781 if not self.silent:
2782 print("\n Importing new branch %s" % fullBranch);
2783 if self.importNewBranch(branch, change - 1):
2784 parent = ""
2785 self.p4BranchesInGit.append(fullBranch)
2786 if not self.silent:
2787 print("\n Resuming with change %s" % change);
2788
2789 if self.verbose:
2790 print "parent determined through known branches: %s" % parent
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002791
Simon Hausmann8134f692007-08-26 16:44:55 +02002792 branch = self.gitRefForBranch(branch)
2793 parent = self.gitRefForBranch(parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002794
2795 if self.verbose:
2796 print "looking for initial parent for %s; current parent is %s" % (branch, parent)
2797
2798 if len(parent) == 0 and branch in self.initialParents:
2799 parent = self.initialParents[branch]
2800 del self.initialParents[branch]
2801
Vitor Antunesfed23692012-01-25 23:48:22 +00002802 blob = None
2803 if len(parent) > 0:
Pete Wyckoff4f9273d2013-01-26 22:11:04 -05002804 tempBranch = "%s/%d" % (self.tempBranchLocation, change)
Vitor Antunesfed23692012-01-25 23:48:22 +00002805 if self.verbose:
2806 print "Creating temporary branch: " + tempBranch
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002807 self.commit(description, filesForCommit, tempBranch)
Vitor Antunesfed23692012-01-25 23:48:22 +00002808 self.tempBranches.append(tempBranch)
2809 self.checkpoint()
2810 blob = self.searchParent(parent, branch, tempBranch)
2811 if blob:
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002812 self.commit(description, filesForCommit, branch, blob)
Vitor Antunesfed23692012-01-25 23:48:22 +00002813 else:
2814 if self.verbose:
2815 print "Parent of %s not found. Committing into head of %s" % (branch, parent)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002816 self.commit(description, filesForCommit, branch, parent)
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002817 else:
2818 files = self.extractFilesFromCommit(description)
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002819 self.commit(description, files, self.branch,
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002820 self.initialParent)
Pete Wyckoff47497842013-01-14 19:47:04 -05002821 # only needed once, to connect to the previous commit
Simon Hausmanne87f37a2007-08-26 16:00:52 +02002822 self.initialParent = ""
2823 except IOError:
2824 print self.gitError.read()
2825 sys.exit(1)
2826
Simon Hausmannc208a242007-08-26 16:07:18 +02002827 def importHeadRevision(self, revision):
2828 print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
2829
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04002830 details = {}
2831 details["user"] = "git perforce import user"
Pete Wyckoff1494fcb2011-02-19 08:17:56 -05002832 details["desc"] = ("Initial import of %s from the state at revision %s\n"
Simon Hausmannc208a242007-08-26 16:07:18 +02002833 % (' '.join(self.depotPaths), revision))
2834 details["change"] = revision
2835 newestRevision = 0
2836
2837 fileCnt = 0
Luke Diamand6de040d2011-10-16 10:47:52 -04002838 fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
2839
2840 for info in p4CmdList(["files"] + fileArgs):
Simon Hausmannc208a242007-08-26 16:07:18 +02002841
Pete Wyckoff68b28592011-02-19 08:17:55 -05002842 if 'code' in info and info['code'] == 'error':
Simon Hausmannc208a242007-08-26 16:07:18 +02002843 sys.stderr.write("p4 returned an error: %s\n"
2844 % info['data'])
Pete Wyckoffd88e7072011-02-19 08:17:58 -05002845 if info['data'].find("must refer to client") >= 0:
2846 sys.stderr.write("This particular p4 error is misleading.\n")
2847 sys.stderr.write("Perhaps the depot path was misspelled.\n");
2848 sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
Simon Hausmannc208a242007-08-26 16:07:18 +02002849 sys.exit(1)
Pete Wyckoff68b28592011-02-19 08:17:55 -05002850 if 'p4ExitCode' in info:
2851 sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
Simon Hausmannc208a242007-08-26 16:07:18 +02002852 sys.exit(1)
2853
2854
2855 change = int(info["change"])
2856 if change > newestRevision:
2857 newestRevision = change
2858
Pete Wyckoff56c09342011-02-19 08:17:57 -05002859 if info["action"] in self.delete_actions:
Simon Hausmannc208a242007-08-26 16:07:18 +02002860 # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
2861 #fileCnt = fileCnt + 1
2862 continue
2863
2864 for prop in ["depotFile", "rev", "action", "type" ]:
2865 details["%s%s" % (prop, fileCnt)] = info[prop]
2866
2867 fileCnt = fileCnt + 1
2868
2869 details["change"] = newestRevision
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04002870
Pete Wyckoff9dcb9f22012-04-08 20:18:01 -04002871 # Use time from top-most change so that all git p4 clones of
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04002872 # the same p4 repo have the same commit SHA1s.
Pete Wyckoff18fa13d2012-11-23 17:35:34 -05002873 res = p4_describe(newestRevision)
2874 details["time"] = res["time"]
Pete Wyckoff4e2e6ce2011-07-31 09:45:55 -04002875
Simon Hausmannc208a242007-08-26 16:07:18 +02002876 self.updateOptionDict(details)
2877 try:
Pete Wyckoffe63231e2012-08-11 12:55:02 -04002878 self.commit(details, self.extractFilesFromCommit(details), self.branch)
Simon Hausmannc208a242007-08-26 16:07:18 +02002879 except IOError:
2880 print "IO error with git fast-import. Is your git version recent enough?"
2881 print self.gitError.read()
2882
2883
Simon Hausmannb9847332007-03-20 20:54:23 +01002884 def run(self, args):
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002885 self.depotPaths = []
Simon Hausmann179caeb2007-03-22 22:17:42 +01002886 self.changeRange = ""
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002887 self.previousDepotPaths = []
Pete Wyckoff991a2de2013-01-14 19:46:56 -05002888 self.hasOrigin = False
Han-Wen Nienhuysce6f33c2007-05-23 16:46:29 -03002889
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002890 # map from branch depot path to parent branch
2891 self.knownBranches = {}
2892 self.initialParents = {}
Simon Hausmann179caeb2007-03-22 22:17:42 +01002893
Simon Hausmanna028a982007-05-23 00:03:08 +02002894 if self.importIntoRemotes:
2895 self.refPrefix = "refs/remotes/p4/"
2896 else:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02002897 self.refPrefix = "refs/heads/p4/"
Simon Hausmanna028a982007-05-23 00:03:08 +02002898
Pete Wyckoff991a2de2013-01-14 19:46:56 -05002899 if self.syncWithOrigin:
2900 self.hasOrigin = originP4BranchesExist()
2901 if self.hasOrigin:
2902 if not self.silent:
2903 print 'Syncing with origin first, using "git fetch origin"'
2904 system("git fetch origin")
Simon Hausmann10f880f2007-05-24 22:28:28 +02002905
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -05002906 branch_arg_given = bool(self.branch)
Simon Hausmann569d1bd2007-03-22 21:34:16 +01002907 if len(self.branch) == 0:
Marius Storm-Olsendb775552007-06-07 15:13:59 +02002908 self.branch = self.refPrefix + "master"
Simon Hausmanna028a982007-05-23 00:03:08 +02002909 if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
Simon Hausmann48df6fd2007-05-17 21:18:53 +02002910 system("git update-ref %s refs/heads/p4" % self.branch)
Pete Wyckoff55d12432013-01-14 19:46:59 -05002911 system("git branch -D p4")
Simon Hausmann179caeb2007-03-22 22:17:42 +01002912
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05002913 # accept either the command-line option, or the configuration variable
2914 if self.useClientSpec:
2915 # will use this after clone to set the variable
2916 self.useClientSpec_from_options = True
2917 else:
Pete Wyckoff0d609032013-01-26 22:11:24 -05002918 if gitConfigBool("git-p4.useclientspec"):
Pete Wyckoff09fca772011-12-24 21:07:39 -05002919 self.useClientSpec = True
2920 if self.useClientSpec:
Pete Wyckoff543987b2012-02-25 20:06:25 -05002921 self.clientSpecDirs = getClientSpec()
Tor Arvid Lund3a70cdf2008-02-18 15:22:08 +01002922
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03002923 # TODO: should always look at previous commits,
2924 # merge with previous imports, if possible.
2925 if args == []:
Simon Hausmannd414c742007-05-25 11:36:42 +02002926 if self.hasOrigin:
Simon Hausmann5ca44612007-08-24 17:44:16 +02002927 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
Pete Wyckoff3b650fc2013-01-14 19:46:58 -05002928
2929 # branches holds mapping from branch name to sha1
2930 branches = p4BranchesInGit(self.importIntoRemotes)
Pete Wyckoff8c9e8b62013-01-14 19:47:06 -05002931
2932 # restrict to just this one, disabling detect-branches
2933 if branch_arg_given:
2934 short = self.branch.split("/")[-1]
2935 if short in branches:
2936 self.p4BranchesInGit = [ short ]
2937 else:
2938 self.p4BranchesInGit = branches.keys()
Simon Hausmannabcd7902007-05-24 22:25:36 +02002939
2940 if len(self.p4BranchesInGit) > 1:
2941 if not self.silent:
2942 print "Importing from/into multiple branches"
2943 self.detectBranches = True
Pete Wyckoff8c9e8b62013-01-14 19:47:06 -05002944 for branch in branches.keys():
2945 self.initialParents[self.refPrefix + branch] = \
2946 branches[branch]
Simon Hausmann967f72e2007-03-23 09:30:41 +01002947
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002948 if self.verbose:
2949 print "branches: %s" % self.p4BranchesInGit
2950
2951 p4Change = 0
2952 for branch in self.p4BranchesInGit:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03002953 logMsg = extractLogMessageFromGitCommit(self.refPrefix + branch)
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03002954
2955 settings = extractSettingsGitLog(logMsg)
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002956
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03002957 self.readOptions(settings)
2958 if (settings.has_key('depot-paths')
2959 and settings.has_key ('change')):
2960 change = int(settings['change']) + 1
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002961 p4Change = max(p4Change, change)
2962
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03002963 depotPaths = sorted(settings['depot-paths'])
2964 if self.previousDepotPaths == []:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002965 self.previousDepotPaths = depotPaths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002966 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002967 paths = []
2968 for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
Vitor Antunes04d277b2011-08-19 00:44:03 +01002969 prev_list = prev.split("/")
2970 cur_list = cur.split("/")
2971 for i in range(0, min(len(cur_list), len(prev_list))):
2972 if cur_list[i] <> prev_list[i]:
Simon Hausmann583e1702007-06-07 09:37:13 +02002973 i = i - 1
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002974 break
2975
Vitor Antunes04d277b2011-08-19 00:44:03 +01002976 paths.append ("/".join(cur_list[:i + 1]))
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002977
2978 self.previousDepotPaths = paths
Simon Hausmann29bdbac2007-05-19 10:23:12 +02002979
2980 if p4Change > 0:
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03002981 self.depotPaths = sorted(self.previousDepotPaths)
Simon Hausmannd5904672007-05-19 11:07:32 +02002982 self.changeRange = "@%s,#head" % p4Change
Simon Hausmann341dc1c2007-05-21 00:39:16 +02002983 if not self.silent and not self.detectBranches:
Simon Hausmann967f72e2007-03-23 09:30:41 +01002984 print "Performing incremental import into %s git branch" % self.branch
Simon Hausmann569d1bd2007-03-22 21:34:16 +01002985
Pete Wyckoff40d69ac2013-01-14 19:47:03 -05002986 # accept multiple ref name abbreviations:
2987 # refs/foo/bar/branch -> use it exactly
2988 # p4/branch -> prepend refs/remotes/ or refs/heads/
2989 # branch -> prepend refs/remotes/p4/ or refs/heads/p4/
Simon Hausmannf9162f62007-05-17 09:02:45 +02002990 if not self.branch.startswith("refs/"):
Pete Wyckoff40d69ac2013-01-14 19:47:03 -05002991 if self.importIntoRemotes:
2992 prepend = "refs/remotes/"
2993 else:
2994 prepend = "refs/heads/"
2995 if not self.branch.startswith("p4/"):
2996 prepend += "p4/"
2997 self.branch = prepend + self.branch
Simon Hausmann179caeb2007-03-22 22:17:42 +01002998
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03002999 if len(args) == 0 and self.depotPaths:
Simon Hausmannb9847332007-03-20 20:54:23 +01003000 if not self.silent:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003001 print "Depot paths: %s" % ' '.join(self.depotPaths)
Simon Hausmannb9847332007-03-20 20:54:23 +01003002 else:
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003003 if self.depotPaths and self.depotPaths != args:
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003004 print ("previous import used depot path %s and now %s was specified. "
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003005 "This doesn't work!" % (' '.join (self.depotPaths),
3006 ' '.join (args)))
Simon Hausmannb9847332007-03-20 20:54:23 +01003007 sys.exit(1)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003008
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003009 self.depotPaths = sorted(args)
Simon Hausmannb9847332007-03-20 20:54:23 +01003010
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003011 revision = ""
Simon Hausmannb9847332007-03-20 20:54:23 +01003012 self.users = {}
Simon Hausmannb9847332007-03-20 20:54:23 +01003013
Pete Wyckoff58c8bc72011-12-24 21:07:35 -05003014 # Make sure no revision specifiers are used when --changesfile
3015 # is specified.
3016 bad_changesfile = False
3017 if len(self.changesFile) > 0:
3018 for p in self.depotPaths:
3019 if p.find("@") >= 0 or p.find("#") >= 0:
3020 bad_changesfile = True
3021 break
3022 if bad_changesfile:
3023 die("Option --changesfile is incompatible with revision specifiers")
3024
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003025 newPaths = []
3026 for p in self.depotPaths:
3027 if p.find("@") != -1:
3028 atIdx = p.index("@")
3029 self.changeRange = p[atIdx:]
3030 if self.changeRange == "@all":
3031 self.changeRange = ""
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003032 elif ',' not in self.changeRange:
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003033 revision = self.changeRange
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003034 self.changeRange = ""
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003035 p = p[:atIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003036 elif p.find("#") != -1:
3037 hashIdx = p.index("#")
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003038 revision = p[hashIdx:]
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003039 p = p[:hashIdx]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003040 elif self.previousDepotPaths == []:
Pete Wyckoff58c8bc72011-12-24 21:07:35 -05003041 # pay attention to changesfile, if given, else import
3042 # the entire p4 tree at the head revision
3043 if len(self.changesFile) == 0:
3044 revision = "#head"
Simon Hausmannb9847332007-03-20 20:54:23 +01003045
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003046 p = re.sub ("\.\.\.$", "", p)
3047 if not p.endswith("/"):
3048 p += "/"
3049
3050 newPaths.append(p)
3051
3052 self.depotPaths = newPaths
3053
Pete Wyckoffe63231e2012-08-11 12:55:02 -04003054 # --detect-branches may change this for each branch
3055 self.branchPrefixes = self.depotPaths
3056
Simon Hausmannb607e712007-05-20 10:55:54 +02003057 self.loadUserMapFromCache()
Simon Hausmanncb53e1f2007-04-08 00:12:02 +02003058 self.labels = {}
3059 if self.detectLabels:
3060 self.getLabels();
Simon Hausmannb9847332007-03-20 20:54:23 +01003061
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003062 if self.detectBranches:
Simon Hausmanndf450922007-06-08 08:49:22 +02003063 ## FIXME - what's a P4 projectName ?
3064 self.projectName = self.guessProjectName()
3065
Simon Hausmann38f9f5e2007-11-15 10:38:45 +01003066 if self.hasOrigin:
3067 self.getBranchMappingFromGitBranches()
3068 else:
3069 self.getBranchMapping()
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003070 if self.verbose:
3071 print "p4-git branches: %s" % self.p4BranchesInGit
3072 print "initial parents: %s" % self.initialParents
3073 for b in self.p4BranchesInGit:
3074 if b != "master":
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003075
3076 ## FIXME
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003077 b = b[len(self.projectName):]
3078 self.createdBranches.add(b)
Simon Hausmann4b97ffb2007-05-18 21:45:23 +02003079
Simon Hausmannf291b4e2007-04-14 11:21:50 +02003080 self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
Simon Hausmannb9847332007-03-20 20:54:23 +01003081
Pete Wyckoff78189be2012-11-23 17:35:36 -05003082 self.importProcess = subprocess.Popen(["git", "fast-import"],
3083 stdin=subprocess.PIPE,
3084 stdout=subprocess.PIPE,
3085 stderr=subprocess.PIPE);
3086 self.gitOutput = self.importProcess.stdout
3087 self.gitStream = self.importProcess.stdin
3088 self.gitError = self.importProcess.stderr
Simon Hausmannb9847332007-03-20 20:54:23 +01003089
Simon Hausmann1c49fc12007-08-26 16:04:34 +02003090 if revision:
Simon Hausmannc208a242007-08-26 16:07:18 +02003091 self.importHeadRevision(revision)
Simon Hausmannb9847332007-03-20 20:54:23 +01003092 else:
3093 changes = []
3094
Simon Hausmann0828ab12007-03-20 20:59:30 +01003095 if len(self.changesFile) > 0:
Simon Hausmannb9847332007-03-20 20:54:23 +01003096 output = open(self.changesFile).readlines()
Reilly Grant1d7367d2009-09-10 00:02:38 -07003097 changeSet = set()
Simon Hausmannb9847332007-03-20 20:54:23 +01003098 for line in output:
3099 changeSet.add(int(line))
3100
3101 for change in changeSet:
3102 changes.append(change)
3103
3104 changes.sort()
3105 else:
Pete Wyckoff9dcb9f22012-04-08 20:18:01 -04003106 # catch "git p4 sync" with no new branches, in a repo that
3107 # does not have any existing p4 branches
Pete Wyckoff5a8e84c2013-01-14 19:47:05 -05003108 if len(args) == 0:
3109 if not self.p4BranchesInGit:
3110 die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.")
3111
3112 # The default branch is master, unless --branch is used to
3113 # specify something else. Make sure it exists, or complain
3114 # nicely about how to use --branch.
3115 if not self.detectBranches:
3116 if not branch_exists(self.branch):
3117 if branch_arg_given:
3118 die("Error: branch %s does not exist." % self.branch)
3119 else:
3120 die("Error: no branch %s; perhaps specify one with --branch." %
3121 self.branch)
3122
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003123 if self.verbose:
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003124 print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003125 self.changeRange)
Lex Spoon96b2d542015-04-20 11:00:20 -04003126 changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)
Simon Hausmannb9847332007-03-20 20:54:23 +01003127
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02003128 if len(self.maxChanges) > 0:
Han-Wen Nienhuys7fcff9d2007-07-23 15:56:37 -07003129 changes = changes[:min(int(self.maxChanges), len(changes))]
Simon Hausmann01a9c9c2007-05-23 00:07:35 +02003130
Simon Hausmannb9847332007-03-20 20:54:23 +01003131 if len(changes) == 0:
Simon Hausmann0828ab12007-03-20 20:59:30 +01003132 if not self.silent:
Simon Hausmann341dc1c2007-05-21 00:39:16 +02003133 print "No changes to import!"
Luke Diamand06804c72012-04-11 17:21:24 +02003134 else:
3135 if not self.silent and not self.detectBranches:
3136 print "Import destination: %s" % self.branch
Simon Hausmannb9847332007-03-20 20:54:23 +01003137
Luke Diamand06804c72012-04-11 17:21:24 +02003138 self.updatedBranches = set()
Simon Hausmanna9d1a272007-06-11 23:28:03 +02003139
Pete Wyckoff47497842013-01-14 19:47:04 -05003140 if not self.detectBranches:
3141 if args:
3142 # start a new branch
3143 self.initialParent = ""
3144 else:
3145 # build on a previous revision
3146 self.initialParent = parseRevision(self.branch)
3147
Luke Diamand06804c72012-04-11 17:21:24 +02003148 self.importChanges(changes)
Simon Hausmann341dc1c2007-05-21 00:39:16 +02003149
Luke Diamand06804c72012-04-11 17:21:24 +02003150 if not self.silent:
3151 print ""
3152 if len(self.updatedBranches) > 0:
3153 sys.stdout.write("Updated branches: ")
3154 for b in self.updatedBranches:
3155 sys.stdout.write("%s " % b)
3156 sys.stdout.write("\n")
Simon Hausmannb9847332007-03-20 20:54:23 +01003157
Pete Wyckoff0d609032013-01-26 22:11:24 -05003158 if gitConfigBool("git-p4.importLabels"):
Luke Diamand06dcd152012-05-11 07:25:18 +01003159 self.importLabels = True
Luke Diamand06804c72012-04-11 17:21:24 +02003160
3161 if self.importLabels:
3162 p4Labels = getP4Labels(self.depotPaths)
3163 gitTags = getGitTags()
3164
3165 missingP4Labels = p4Labels - gitTags
3166 self.importP4Labels(self.gitStream, missingP4Labels)
Simon Hausmannb9847332007-03-20 20:54:23 +01003167
Simon Hausmannb9847332007-03-20 20:54:23 +01003168 self.gitStream.close()
Pete Wyckoff78189be2012-11-23 17:35:36 -05003169 if self.importProcess.wait() != 0:
Simon Hausmann29bdbac2007-05-19 10:23:12 +02003170 die("fast-import failed: %s" % self.gitError.read())
Simon Hausmannb9847332007-03-20 20:54:23 +01003171 self.gitOutput.close()
3172 self.gitError.close()
3173
Vitor Antunesfed23692012-01-25 23:48:22 +00003174 # Cleanup temporary branches created during import
3175 if self.tempBranches != []:
3176 for branch in self.tempBranches:
3177 read_pipe("git update-ref -d %s" % branch)
3178 os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
3179
Pete Wyckoff55d12432013-01-14 19:46:59 -05003180 # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
3181 # a convenient shortcut refname "p4".
3182 if self.importIntoRemotes:
3183 head_ref = self.refPrefix + "HEAD"
3184 if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
3185 system(["git", "symbolic-ref", head_ref, self.branch])
3186
Simon Hausmannb9847332007-03-20 20:54:23 +01003187 return True
3188
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003189class P4Rebase(Command):
3190 def __init__(self):
3191 Command.__init__(self)
Luke Diamand06804c72012-04-11 17:21:24 +02003192 self.options = [
3193 optparse.make_option("--import-labels", dest="importLabels", action="store_true"),
Luke Diamand06804c72012-04-11 17:21:24 +02003194 ]
Luke Diamand06804c72012-04-11 17:21:24 +02003195 self.importLabels = False
Han-Wen Nienhuyscebdf5a2007-05-23 16:53:11 -03003196 self.description = ("Fetches the latest revision from perforce and "
3197 + "rebases the current work (branch) against it")
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003198
3199 def run(self, args):
3200 sync = P4Sync()
Luke Diamand06804c72012-04-11 17:21:24 +02003201 sync.importLabels = self.importLabels
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003202 sync.run([])
Simon Hausmannd7e38682007-06-12 14:34:46 +02003203
Simon Hausmann14594f42007-08-22 09:07:15 +02003204 return self.rebase()
3205
3206 def rebase(self):
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01003207 if os.system("git update-index --refresh") != 0:
3208 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.");
3209 if len(read_pipe("git diff-index HEAD --")) > 0:
Veres Lajosf7e604e2013-06-19 07:37:24 +02003210 die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash.");
Simon Hausmann36ee4ee2008-01-07 14:21:45 +01003211
Simon Hausmannd7e38682007-06-12 14:34:46 +02003212 [upstream, settings] = findUpstreamBranchPoint()
3213 if len(upstream) == 0:
3214 die("Cannot find upstream branchpoint for rebase")
3215
3216 # the branchpoint may be p4/foo~3, so strip off the parent
3217 upstream = re.sub("~[0-9]+$", "", upstream)
3218
3219 print "Rebasing the current branch onto %s" % upstream
Han-Wen Nienhuysb25b2062007-05-23 18:49:35 -03003220 oldHead = read_pipe("git rev-parse HEAD").strip()
Simon Hausmannd7e38682007-06-12 14:34:46 +02003221 system("git rebase %s" % upstream)
Vlad Dogaru4e49d952014-04-07 16:19:11 +03003222 system("git diff-tree --stat --summary -M %s HEAD --" % oldHead)
Simon Hausmann01ce1fe2007-04-07 23:46:50 +02003223 return True
3224
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003225class P4Clone(P4Sync):
3226 def __init__(self):
3227 P4Sync.__init__(self)
3228 self.description = "Creates a new git repository and imports from Perforce into it"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003229 self.usage = "usage: %prog [options] //depot/path[@revRange]"
Tommy Thorn354081d2008-02-03 10:38:51 -08003230 self.options += [
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003231 optparse.make_option("--destination", dest="cloneDestination",
3232 action='store', default=None,
Tommy Thorn354081d2008-02-03 10:38:51 -08003233 help="where to leave result of the clone"),
Pete Wyckoff38200072011-02-19 08:18:01 -05003234 optparse.make_option("--bare", dest="cloneBare",
3235 action="store_true", default=False),
Tommy Thorn354081d2008-02-03 10:38:51 -08003236 ]
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003237 self.cloneDestination = None
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003238 self.needsGit = False
Pete Wyckoff38200072011-02-19 08:18:01 -05003239 self.cloneBare = False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003240
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003241 def defaultDestination(self, args):
3242 ## TODO: use common prefix of args?
3243 depotPath = args[0]
3244 depotDir = re.sub("(@[^@]*)$", "", depotPath)
3245 depotDir = re.sub("(#[^#]*)$", "", depotDir)
Toby Allsopp053d9e42008-02-05 09:41:43 +13003246 depotDir = re.sub(r"\.\.\.$", "", depotDir)
Han-Wen Nienhuys6a49f8e2007-05-23 18:49:35 -03003247 depotDir = re.sub(r"/$", "", depotDir)
3248 return os.path.split(depotDir)[1]
3249
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003250 def run(self, args):
3251 if len(args) < 1:
3252 return False
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003253
3254 if self.keepRepoPath and not self.cloneDestination:
3255 sys.stderr.write("Must specify destination for --keep-path\n")
3256 sys.exit(1)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003257
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003258 depotPaths = args
Simon Hausmann5e100b52007-06-07 21:12:25 +02003259
3260 if not self.cloneDestination and len(depotPaths) > 1:
3261 self.cloneDestination = depotPaths[-1]
3262 depotPaths = depotPaths[:-1]
3263
Tommy Thorn354081d2008-02-03 10:38:51 -08003264 self.cloneExclude = ["/"+p for p in self.cloneExclude]
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003265 for p in depotPaths:
3266 if not p.startswith("//"):
Pete Wyckoff0f487d32013-01-26 22:11:06 -05003267 sys.stderr.write('Depot paths must start with "//": %s\n' % p)
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003268 return False
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003269
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003270 if not self.cloneDestination:
Marius Storm-Olsen98ad4fa2007-06-07 15:08:33 +02003271 self.cloneDestination = self.defaultDestination(args)
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003272
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003273 print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
Pete Wyckoff38200072011-02-19 08:18:01 -05003274
Kevin Greenc3bf3f12007-06-11 16:48:07 -04003275 if not os.path.exists(self.cloneDestination):
3276 os.makedirs(self.cloneDestination)
Robert Blum053fd0c2008-08-01 12:50:03 -07003277 chdir(self.cloneDestination)
Pete Wyckoff38200072011-02-19 08:18:01 -05003278
3279 init_cmd = [ "git", "init" ]
3280 if self.cloneBare:
3281 init_cmd.append("--bare")
Brandon Caseya235e852013-01-26 11:14:33 -08003282 retcode = subprocess.call(init_cmd)
3283 if retcode:
3284 raise CalledProcessError(retcode, init_cmd)
Pete Wyckoff38200072011-02-19 08:18:01 -05003285
Han-Wen Nienhuys6326aa52007-05-23 18:49:35 -03003286 if not P4Sync.run(self, depotPaths):
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003287 return False
Pete Wyckoffc5959562013-01-14 19:47:01 -05003288
3289 # create a master branch and check out a work tree
3290 if gitBranchExists(self.branch):
3291 system([ "git", "branch", "master", self.branch ])
3292 if not self.cloneBare:
3293 system([ "git", "checkout", "-f" ])
3294 else:
3295 print 'Not checking out any branch, use ' \
3296 '"git checkout -q -b master <branch>"'
Han-Wen Nienhuys86dff6b2007-05-23 18:49:35 -03003297
Pete Wyckoffa93d33e2012-02-25 20:06:24 -05003298 # auto-set this variable if invoked with --use-client-spec
3299 if self.useClientSpec_from_options:
3300 system("git config --bool git-p4.useclientspec true")
3301
Simon Hausmannf9a3a4f2007-04-08 10:08:26 +02003302 return True
3303
Simon Hausmann09d89de2007-06-20 23:10:28 +02003304class P4Branches(Command):
3305 def __init__(self):
3306 Command.__init__(self)
3307 self.options = [ ]
3308 self.description = ("Shows the git branches that hold imports and their "
3309 + "corresponding perforce depot paths")
3310 self.verbose = False
3311
3312 def run(self, args):
Simon Hausmann5ca44612007-08-24 17:44:16 +02003313 if originP4BranchesExist():
3314 createOrUpdateBranchesFromOrigin()
3315
Simon Hausmann09d89de2007-06-20 23:10:28 +02003316 cmdline = "git rev-parse --symbolic "
3317 cmdline += " --remotes"
3318
3319 for line in read_pipe_lines(cmdline):
3320 line = line.strip()
3321
3322 if not line.startswith('p4/') or line == "p4/HEAD":
3323 continue
3324 branch = line
3325
3326 log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
3327 settings = extractSettingsGitLog(log)
3328
3329 print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
3330 return True
3331
Simon Hausmannb9847332007-03-20 20:54:23 +01003332class HelpFormatter(optparse.IndentedHelpFormatter):
3333 def __init__(self):
3334 optparse.IndentedHelpFormatter.__init__(self)
3335
3336 def format_description(self, description):
3337 if description:
3338 return description + "\n"
3339 else:
3340 return ""
Simon Hausmann4f5cf762007-03-19 22:25:17 +01003341
Simon Hausmann86949ee2007-03-19 20:59:12 +01003342def printUsage(commands):
3343 print "usage: %s <command> [options]" % sys.argv[0]
3344 print ""
3345 print "valid commands: %s" % ", ".join(commands)
3346 print ""
3347 print "Try %s <command> --help for command specific help." % sys.argv[0]
3348 print ""
3349
3350commands = {
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003351 "debug" : P4Debug,
3352 "submit" : P4Submit,
Marius Storm-Olsena9834f52007-10-09 16:16:09 +02003353 "commit" : P4Submit,
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003354 "sync" : P4Sync,
3355 "rebase" : P4Rebase,
3356 "clone" : P4Clone,
Simon Hausmann09d89de2007-06-20 23:10:28 +02003357 "rollback" : P4RollBack,
3358 "branches" : P4Branches
Simon Hausmann86949ee2007-03-19 20:59:12 +01003359}
3360
Simon Hausmann86949ee2007-03-19 20:59:12 +01003361
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003362def main():
3363 if len(sys.argv[1:]) == 0:
3364 printUsage(commands.keys())
3365 sys.exit(2)
Simon Hausmann86949ee2007-03-19 20:59:12 +01003366
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003367 cmdName = sys.argv[1]
3368 try:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003369 klass = commands[cmdName]
3370 cmd = klass()
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003371 except KeyError:
3372 print "unknown command %s" % cmdName
3373 print ""
3374 printUsage(commands.keys())
3375 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01003376
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003377 options = cmd.options
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003378 cmd.gitdir = os.environ.get("GIT_DIR", None)
Simon Hausmann86949ee2007-03-19 20:59:12 +01003379
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003380 args = sys.argv[2:]
Simon Hausmanne20a9e52007-03-26 00:13:51 +02003381
Pete Wyckoffb0ccc802012-09-09 16:16:10 -04003382 options.append(optparse.make_option("--verbose", "-v", dest="verbose", action="store_true"))
Luke Diamand6a10b6a2012-04-24 09:33:23 +01003383 if cmd.needsGit:
3384 options.append(optparse.make_option("--git-dir", dest="gitdir"))
Simon Hausmanne20a9e52007-03-26 00:13:51 +02003385
Luke Diamand6a10b6a2012-04-24 09:33:23 +01003386 parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
3387 options,
3388 description = cmd.description,
3389 formatter = HelpFormatter())
Simon Hausmann86949ee2007-03-19 20:59:12 +01003390
Luke Diamand6a10b6a2012-04-24 09:33:23 +01003391 (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003392 global verbose
3393 verbose = cmd.verbose
3394 if cmd.needsGit:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003395 if cmd.gitdir == None:
3396 cmd.gitdir = os.path.abspath(".git")
3397 if not isValidGitDir(cmd.gitdir):
3398 cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
3399 if os.path.exists(cmd.gitdir):
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003400 cdup = read_pipe("git rev-parse --show-cdup").strip()
3401 if len(cdup) > 0:
Robert Blum053fd0c2008-08-01 12:50:03 -07003402 chdir(cdup);
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003403
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003404 if not isValidGitDir(cmd.gitdir):
3405 if isValidGitDir(cmd.gitdir + "/.git"):
3406 cmd.gitdir += "/.git"
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003407 else:
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003408 die("fatal: cannot locate git repository at %s" % cmd.gitdir)
Simon Hausmann8910ac02007-03-26 08:18:55 +02003409
Han-Wen Nienhuysb86f7372007-05-23 18:49:35 -03003410 os.environ["GIT_DIR"] = cmd.gitdir
Simon Hausmann4f5cf762007-03-19 22:25:17 +01003411
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003412 if not cmd.run(args):
3413 parser.print_help()
Pete Wyckoff09fca772011-12-24 21:07:39 -05003414 sys.exit(2)
Simon Hausmann4f5cf762007-03-19 22:25:17 +01003415
Han-Wen Nienhuysbb6e09b2007-05-23 18:49:35 -03003416
3417if __name__ == '__main__':
3418 main()